@fabrika/db 0.9.0-dev.1 → 0.9.0-dev.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,225 @@
1
+ import type { ColumnList, ColumnPolicies, FilePolicies, RecordActions, ReferencePolicy, TableActions, Tables, VersionColumn } from './policy';
2
+ declare const databaseType: unique symbol;
3
+ /**
4
+ * The generated column list (`src/db/schema.generated.ts`), which also carries the schema's row types, so that
5
+ * `defineTables(vocabulary, columns)` takes no type argument. The brand is type-only: no value has the property.
6
+ */
7
+ export type TypedColumnList<DB> = ColumnList<DB> & {
8
+ readonly [databaseType]?: DB;
9
+ };
10
+ /** The row types a typed column list carries; `never` for a list without them. */
11
+ export type ColumnTypes<C> = typeof databaseType extends keyof C ? C extends {
12
+ readonly [databaseType]?: infer DB;
13
+ } ? Exclude<DB, undefined> : never : never;
14
+ /**
15
+ * What `defineTables(vocabulary, columns)` reads of `@fabrika/app/contract`'s `defineVocabulary` result: the action
16
+ * and dimension names, and each dimension's container. `@fabrika/db` does not import `@fabrika/app`.
17
+ */
18
+ export interface TableVocabulary {
19
+ readonly actions: {
20
+ readonly [action: string]: string;
21
+ };
22
+ readonly dimensions: {
23
+ readonly [dimension: string]: {
24
+ readonly in?: string;
25
+ };
26
+ };
27
+ }
28
+ export type VocabularyAction<V> = V extends {
29
+ readonly actions: infer A;
30
+ } ? keyof A & string : never;
31
+ export type VocabularyDimension<V> = V extends {
32
+ readonly dimensions: infer D;
33
+ } ? keyof D & string : never;
34
+ /** The dimension `D` lies in, by the vocabulary; `never` for a top-level one. */
35
+ export type VocabularyContainerOf<V, D> = V extends {
36
+ readonly dimensions: infer Ds;
37
+ } ? D extends keyof Ds ? Ds[D] extends {
38
+ readonly in: infer C extends string;
39
+ } ? C : never : never : never;
40
+ /** A table without an `id` column names its key. */
41
+ interface NamesItsKey<Col extends string> {
42
+ readonly key: Col;
43
+ }
44
+ /** `key` defaults to `id`, so it is required only where the table has no `id` column. */
45
+ type KeyField<Col extends string> = 'id' extends Col ? {
46
+ readonly key?: Col;
47
+ } : NamesItsKey<Col>;
48
+ /** A contained dimension table's container column; `dimension`, when stated, must be the vocabulary's. */
49
+ export interface VocabularyContainer<C extends string, Col extends string> {
50
+ readonly column: Col;
51
+ readonly dimension?: C;
52
+ }
53
+ /** A contained table off the `<container>_id` convention names its container column. */
54
+ interface NamesItsContainerColumn<C extends string, Col extends string> {
55
+ readonly in: VocabularyContainer<C, Col>;
56
+ }
57
+ /** The container column defaults to `<container>_id`, so `in` is required only off that convention. */
58
+ type ContainerField<C extends string, Col extends string> = [C] extends [never] ? {
59
+ readonly in?: never;
60
+ } : `${C}_id` extends Col ? {
61
+ readonly in?: VocabularyContainer<C, Col>;
62
+ } : NamesItsContainerColumn<C, Col>;
63
+ /** The scope of a table that owns dimension `D`, which the vocabulary puts in `C` (`never`: in no container). */
64
+ type DimensionTableScope<D extends string, C extends string, Col extends string> = {
65
+ readonly dimension: D;
66
+ } & KeyField<Col> & ContainerField<C, Col>;
67
+ /** A dimension table's scope: the dimension it owns; its container is the vocabulary's. */
68
+ export type VocabularyDimensionScope<V, D extends string, Col extends string> = DimensionTableScope<D, VocabularyContainerOf<V, D>, Col>;
69
+ /** A child table's scope: `column` defaults to `<parent dimension>_id`, and the inherited chain is the parent's. */
70
+ export type VocabularyChildScope<Table extends string, Col extends string> = {
71
+ readonly child: Table;
72
+ readonly column?: Col;
73
+ readonly inherit?: readonly Col[];
74
+ } & KeyField<Col>;
75
+ interface WritableFields<Col extends string, A extends string, Ver extends Col, Acts = TableActions<A>> {
76
+ readonly actions: Acts;
77
+ readonly insert?: readonly Col[];
78
+ readonly update?: readonly Col[];
79
+ readonly version?: Ver;
80
+ readonly createdBy?: Col;
81
+ readonly columns?: ColumnPolicies<Col, A>;
82
+ /** The file columns, each with the file kind it holds (`c.files`): a `uuid` foreign key to `fabrika_files`. */
83
+ readonly files?: FilePolicies<Col>;
84
+ }
85
+ /**
86
+ * The scope of the dimension the policy states (`S`, its scope as written); for any other name, the dimension alone,
87
+ * so that a misspelt one is reported against the vocabulary's dimension names rather than against one of them. With
88
+ * no scope as written (an annotated map), every dimension's scope.
89
+ */
90
+ type DimensionScopes<V, Col extends string, S> = unknown extends S ? {
91
+ [D in VocabularyDimension<V>]: VocabularyDimensionScope<V, D, Col>;
92
+ }[VocabularyDimension<V>] : S extends {
93
+ readonly dimension: infer D extends VocabularyDimension<V>;
94
+ } ? DimensionTableScope<D, VocabularyContainerOf<V, D>, Col> : {
95
+ readonly dimension: VocabularyDimension<V>;
96
+ };
97
+ type StatedDimensionOf<P, T> = T extends keyof P ? P[T] extends {
98
+ readonly scope: {
99
+ readonly dimension: infer D extends string;
100
+ };
101
+ } ? D : never : never;
102
+ /** A child off the `<parent dimension>_id` convention names its column for the parent. */
103
+ interface NamesItsParentColumn<Col extends string> {
104
+ readonly column: Col;
105
+ }
106
+ /** The column for the parent defaults to `<ParentDimension>_id`, so `column` is required only off that convention. */
107
+ type ParentColumnField<ParentDimension extends string, Col extends string> = `${ParentDimension}_id` extends Col ? {
108
+ readonly column?: Col;
109
+ } : NamesItsParentColumn<Col>;
110
+ /** The scope of a child of `Parent`, a table that owns `ParentDimension`. */
111
+ type ChildTableScope<Parent extends string, ParentDimension extends string, Col extends string> = {
112
+ readonly child: Parent;
113
+ readonly inherit?: readonly Col[];
114
+ } & KeyField<Col> & ParentColumnField<ParentDimension, Col>;
115
+ /**
116
+ * The scope of the child as written, by the parent it states (`P`, the policies as written): its parent is a table
117
+ * that states a dimension scope, and its column defaults to that table's dimension. With no policies as written, any
118
+ * table.
119
+ */
120
+ type ChildScopes<Table extends string, Col extends string, S, P> = unknown extends P ? VocabularyChildScope<Table, Col> : S extends {
121
+ readonly child: infer Parent extends string;
122
+ } ? [StatedDimensionOf<P, Parent>] extends [never] ? {
123
+ readonly child: {
124
+ [T in keyof P]: P[T] extends {
125
+ readonly scope: {
126
+ readonly dimension: string;
127
+ };
128
+ } ? T : never;
129
+ }[keyof P] & string;
130
+ } : ChildTableScope<Parent, StatedDimensionOf<P, Parent>, Col> : VocabularyChildScope<Table, Col>;
131
+ type AuthoredDimensionPolicy<Col extends string, A extends string, V, Ver extends Col, S> = WritableFields<Col, A, Ver, RecordActions<A>> & {
132
+ readonly scope: DimensionScopes<V, Col, S>;
133
+ };
134
+ type AuthoredChildPolicy<Table extends string, Col extends string, A extends string, Ver extends Col, S, P> = WritableFields<Col, A, Ver, RecordActions<A>> & {
135
+ readonly scope: ChildScopes<Table, Col, S, P>;
136
+ };
137
+ /** An application table's scope: why its rows belong to no dimension; `key` defaults to `id`. */
138
+ type AuthoredApplicationPolicy<Col extends string, A extends string, Ver extends Col> = WritableFields<Col, A, Ver> & {
139
+ readonly scope: {
140
+ readonly app: string;
141
+ } & KeyField<Col>;
142
+ };
143
+ /**
144
+ * One table's policy as an application writes it under a vocabulary. `S` is its scope as written: its mode picks the
145
+ * one policy form a mistake is reported against, rather than a union of all four. `P` is every policy as written.
146
+ */
147
+ export type VocabularyTablePolicy<Table extends string, Col extends string, A extends string, V, Ver extends Col, S = unknown, P = unknown> = S extends {
148
+ readonly dimension: unknown;
149
+ } ? AuthoredDimensionPolicy<Col, A, V, Ver, S> : S extends {
150
+ readonly child: unknown;
151
+ } ? AuthoredChildPolicy<Table, Col, A, Ver, S, P> : S extends {
152
+ readonly app: unknown;
153
+ } ? AuthoredApplicationPolicy<Col, A, Ver> : S extends {
154
+ readonly reference: unknown;
155
+ } ? ReferencePolicy<Col, A> : AuthoredDimensionPolicy<Col, A, V, Ver, S> | AuthoredChildPolicy<Table, Col, A, Ver, S, P> | AuthoredApplicationPolicy<Col, A, Ver> | ReferencePolicy<Col, A>;
156
+ type StatedScope<P, T> = T extends keyof P ? P[T] extends {
157
+ readonly scope: infer S;
158
+ } ? S : unknown : unknown;
159
+ /** The policy of table `T` with nothing stated: every form, every dimension's scope, any parent. */
160
+ type AnyTablePolicy<DB, V, T extends keyof DB & string> = VocabularyTablePolicy<keyof DB & string, keyof DB[T] & string, VocabularyAction<V>, V, VersionColumn<DB[T]>>;
161
+ /**
162
+ * Every table of the schema, and no other, maps to a policy; a misspelt action or dimension fails at its own line.
163
+ * `P` is the policies as written, which picks each table's form by the scope it states; a policy written under this
164
+ * map's own annotation states nothing narrower, so it is checked against every form.
165
+ */
166
+ export type VocabularyPolicyMap<DB, V, P = unknown> = {
167
+ readonly [T in keyof DB & string]: AnyTablePolicy<DB, V, T> extends (T extends keyof P ? P[T] : unknown) ? AnyTablePolicy<DB, V, T> : VocabularyTablePolicy<keyof DB & string, keyof DB[T] & string, VocabularyAction<V>, V, VersionColumn<DB[T]>, StatedScope<P, T>, P>;
168
+ };
169
+ type Flat<T> = {
170
+ [K in keyof T]: T[K];
171
+ };
172
+ type KeyOf<S> = S extends {
173
+ readonly key: infer K extends string;
174
+ } ? K : 'id';
175
+ type ContainerColumn<S, C extends string> = S extends {
176
+ readonly in: {
177
+ readonly column: infer Col extends string;
178
+ };
179
+ } ? Col : `${C}_id`;
180
+ type ParentDimension<P, T> = T extends keyof P ? P[T] extends {
181
+ readonly scope: {
182
+ readonly dimension: infer D extends string;
183
+ };
184
+ } ? D : never : never;
185
+ type NormalScope<S, V, P> = S extends {
186
+ readonly dimension: infer D extends string;
187
+ } ? Flat<{
188
+ readonly dimension: D;
189
+ readonly key: KeyOf<S>;
190
+ } & ([VocabularyContainerOf<V, D>] extends [never] ? unknown : {
191
+ readonly in: {
192
+ readonly dimension: VocabularyContainerOf<V, D>;
193
+ readonly column: ContainerColumn<S, VocabularyContainerOf<V, D>>;
194
+ };
195
+ })> : S extends {
196
+ readonly child: infer T extends string;
197
+ } ? {
198
+ readonly child: T;
199
+ readonly key: KeyOf<S>;
200
+ readonly column: S extends {
201
+ readonly column: infer C extends string;
202
+ } ? C : `${ParentDimension<P, T>}_id`;
203
+ readonly inherit: readonly string[];
204
+ } : S extends {
205
+ readonly app: infer R extends string;
206
+ } ? {
207
+ readonly app: R;
208
+ readonly key: KeyOf<S>;
209
+ } : S;
210
+ /**
211
+ * The policies as the layer models them: every dimension table's key and container, and every child's key and
212
+ * column, stated as the vocabulary and the conventions derive them.
213
+ */
214
+ export type NormalizedPolicies<P, V> = {
215
+ readonly [T in keyof P]: P[T] extends {
216
+ readonly scope: infer S;
217
+ } ? Flat<Omit<P[T], 'scope'> & {
218
+ readonly scope: NormalScope<S, V, P>;
219
+ }> : P[T];
220
+ };
221
+ /** Takes the policies of exactly the tables of the schema, written in the vocabulary form. */
222
+ export type DefineVocabularyPolicies<DB, V> = <const P extends VocabularyPolicyMap<DB, V, P>>(policies: P & {
223
+ readonly [K in Exclude<keyof P, keyof DB>]: never;
224
+ }) => Tables<DB, NormalizedPolicies<P, V>>;
225
+ export {};