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

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,638 @@
1
+ import type { Selectable, Updateable } from 'kysely';
2
+ import type { RowRef } from './capability';
3
+ import type { ConditionTuple } from './description';
4
+ import type { FileReference } from './file-references';
5
+ import type { ActionOf, ApplicationTableName, ChildTableName, ContainerOf, DimensionOf, DimensionTableName, InsertValues, ListDimensionOf, ParentOf, Tables, UpdateValues } from './policy';
6
+ import type { AggregateRow, AggregateSpec } from './store';
7
+ import type { VocabularyContainerOf } from './vocabulary-tables';
8
+ /** A member an endpoint may not use. It has no call signature, so a call fails at its line with this message. */
9
+ export type Unavailable<Message extends string> = readonly [unavailable: Message];
10
+ /** A query reads; a mutation also writes and locks; a job reads, and writes only inside its fenced transaction. */
11
+ export type DataKind = 'query' | 'mutation' | 'job';
12
+ /** What an endpoint's access form makes of `ctx.db.target`: one object (`on`), or a scope to read and insert in (`in`). */
13
+ export type TargetForm = 'on' | 'in';
14
+ type Flat<T> = {
15
+ [K in keyof T]: T[K];
16
+ };
17
+ type PolicyOf<P, T> = T extends keyof P ? P[T] : never;
18
+ type ModeOf<P, T> = PolicyOf<P, T> extends {
19
+ readonly scope: infer S;
20
+ } ? S extends {
21
+ readonly dimension: string;
22
+ } ? 'dimension' : S extends {
23
+ readonly child: string;
24
+ } ? 'child' : S extends {
25
+ readonly app: string;
26
+ } ? 'app' : S extends {
27
+ readonly reference: string;
28
+ } ? 'reference' : never : never;
29
+ /** The actions a table's `actions.appRead` or `actions.appCount` lists; `never` when it lists none. */
30
+ type AppActionsOf<P, T, F extends 'appRead' | 'appCount'> = PolicyOf<P, T> extends {
31
+ readonly actions: {
32
+ readonly [K in F]: readonly (infer A extends string)[];
33
+ };
34
+ } ? A : never;
35
+ /** Each column that declares its own read action, by column. */
36
+ type ColumnReadsOf<P, T> = PolicyOf<P, T> extends {
37
+ readonly columns: infer C;
38
+ } ? {
39
+ readonly [K in keyof C as C[K] extends {
40
+ readonly read: string;
41
+ } ? K : never]: C[K] extends {
42
+ readonly read: infer A extends string;
43
+ } ? A : never;
44
+ } : Record<never, never>;
45
+ type ColumnPoliciesOf<P, T> = PolicyOf<P, T> extends {
46
+ readonly columns: infer C;
47
+ } ? C : never;
48
+ /** The columns that declare `A` as their own write action. */
49
+ type ColumnsWrittenBy<C, A> = {
50
+ [K in keyof C]: C[K] extends {
51
+ readonly write: A;
52
+ } ? K : never;
53
+ }[keyof C];
54
+ /**
55
+ * Each action that updates the table, with the values a capability of it updates: the update action its update list,
56
+ * a column's own write action the columns that declare it, and no other.
57
+ */
58
+ type UpdatesOf<DB, P, T extends keyof DB> = {
59
+ readonly [A in ActionOf<P, T, 'update'>]: UpdateValues<DB, P, T>;
60
+ } & {
61
+ readonly [A in ColumnWriteAction<ColumnPoliciesOf<P, T>>]: Flat<Pick<Updateable<DB[T]>, ColumnsWrittenBy<ColumnPoliciesOf<P, T>, A> & keyof Updateable<DB[T]>>>;
62
+ };
63
+ type ColumnWriteAction<C> = {
64
+ [K in keyof C]: C[K] extends {
65
+ readonly write: infer A extends string;
66
+ } ? A : never;
67
+ }[keyof C];
68
+ /** One table, as every handle reads it. */
69
+ export interface IndexedTable {
70
+ readonly mode: 'dimension' | 'child' | 'app' | 'reference';
71
+ /** The dimension a dimension table owns. */
72
+ readonly dimension: string;
73
+ /** The dimension its rows are listed and inserted in: the container's, or a top-level table's own. */
74
+ readonly list: string;
75
+ /** A child table's parent table. */
76
+ readonly parent: string;
77
+ readonly actions: {
78
+ readonly read: string;
79
+ readonly insert: string;
80
+ readonly update: string;
81
+ readonly delete: string;
82
+ };
83
+ /** The actions besides `read` with which an `app` endpoint lists every row (`actions.appRead`). */
84
+ readonly appRead: string;
85
+ /** The actions with which an `app` endpoint only counts every row (`actions.appCount`). */
86
+ readonly appCount: string;
87
+ /** Every column, before any is hidden. */
88
+ readonly row: object;
89
+ /** The columns that declare their own read action. */
90
+ readonly reads: object;
91
+ readonly insert: object;
92
+ readonly update: object;
93
+ /** Each action that updates the table, with the values it updates (`columns: { assignee: { write: 'tasks.assign' } }`). */
94
+ readonly writes: object;
95
+ /** The containment columns a row carries: what a scoped write stamps from its capability, and an unscoped one takes. */
96
+ readonly chain: string;
97
+ /** The dimensions whose `Ref` reaches the table: a dimension table's own and every dimension of its chain. */
98
+ readonly refs: string;
99
+ /** The dimensions whose `Within` lists the table: every dimension of its chain, and a top-level table's own. */
100
+ readonly reach: string;
101
+ readonly versioned: boolean;
102
+ /** A queue table: its rows are claimed and finished through `row.ref`. */
103
+ readonly queued: boolean;
104
+ }
105
+ type IndexedTableOf<DB, P, T extends keyof P & keyof DB & string, V = unknown> = {
106
+ readonly mode: ModeOf<P, T>;
107
+ readonly dimension: DimensionOf<P, T>;
108
+ readonly list: ListDimensionOf<P, T>;
109
+ readonly parent: ParentOf<P, T>;
110
+ readonly actions: {
111
+ readonly read: ActionOf<P, T, 'read'>;
112
+ readonly insert: ActionOf<P, T, 'insert'>;
113
+ readonly update: ActionOf<P, T, 'update'>;
114
+ readonly delete: ActionOf<P, T, 'delete'>;
115
+ };
116
+ readonly appRead: AppActionsOf<P, T, 'appRead'>;
117
+ readonly appCount: AppActionsOf<P, T, 'appCount'>;
118
+ readonly row: WithFiles<RowType<DB[T]>, FileColumnsOf<P, T>>;
119
+ readonly reads: ColumnReadsOf<P, T>;
120
+ readonly insert: WithFiles<InsertValues<DB, P, T>, FileColumnsOf<P, T>>;
121
+ readonly update: WithFiles<UpdateValues<DB, P, T>, FileColumnsOf<P, T>>;
122
+ readonly writes: {
123
+ readonly [A in keyof UpdatesOf<DB, P, T>]: WithFiles<UpdatesOf<DB, P, T>[A], FileColumnsOf<P, T>>;
124
+ };
125
+ readonly chain: ChainColumns<P, T, V>;
126
+ readonly refs: DimensionOf<P, T> | ChainDimensions<P, T, V>;
127
+ readonly reach: ReachOf<P, T, V>;
128
+ readonly versioned: PolicyOf<P, T> extends {
129
+ readonly version: string;
130
+ } ? true : false;
131
+ readonly queued: PolicyOf<P, T> extends {
132
+ readonly queue: object;
133
+ } ? true : false;
134
+ };
135
+ type OwnerTable<P, D> = {
136
+ [K in keyof P]: P[K] extends {
137
+ readonly scope: {
138
+ readonly dimension: D;
139
+ };
140
+ } ? K : never;
141
+ }[keyof P];
142
+ /** The dimensions above `D` by the vocabulary `V`, nearest first: `never` without a vocabulary or at the top. */
143
+ type VocabularyAncestors<V, D> = VocabularyContainerOf<V, D> extends infer C extends string ? [C] extends [never] ? never : C | VocabularyAncestors<V, C> : never;
144
+ /**
145
+ * A row's containment columns: a dimension table's container and its chain; a child's parent and its. Above a dimension
146
+ * no table owns, the vocabulary's ancestry on the `<dimension>_id` convention, as the layer stamps it.
147
+ */
148
+ type ChainColumns<P, T, V> = PolicyOf<P, T> extends {
149
+ readonly scope: infer S;
150
+ } ? S extends {
151
+ readonly in: {
152
+ readonly dimension: infer C extends string;
153
+ readonly column: infer Col extends string;
154
+ };
155
+ } ? Col | ([OwnerTable<P, C>] extends [never] ? `${VocabularyAncestors<V, C>}_id` : ChainColumns<P, OwnerTable<P, C>, V>) : S extends {
156
+ readonly child: infer Parent extends string;
157
+ readonly column: infer Col extends string;
158
+ } ? Col | ChainColumns<P, Parent, V> : never : never;
159
+ /** The dimensions of a row's containment chain: a dimension table's container and above; a child's parent and above. */
160
+ type ChainDimensions<P, T, V> = PolicyOf<P, T> extends {
161
+ readonly scope: infer S;
162
+ } ? S extends {
163
+ readonly in: {
164
+ readonly dimension: infer C extends string;
165
+ };
166
+ } ? C | ([OwnerTable<P, C>] extends [never] ? VocabularyAncestors<V, C> : ChainDimensions<P, OwnerTable<P, C>, V>) : S extends {
167
+ readonly child: infer Parent extends string;
168
+ } ? DimensionOf<P, Parent> | ChainDimensions<P, Parent, V> : never : never;
169
+ /** The dimensions whose `Within` lists table `T`: every dimension of its chain, and a top-level table's own. */
170
+ type ReachOf<P, T, V> = [ContainerOf<P, T>] extends [never] ? DimensionOf<P, T> | ChainDimensions<P, T, V> : ChainDimensions<P, T, V>;
171
+ /**
172
+ * A row as reads return it: the generated interface itself where no column has a separate write type, so a
173
+ * diagnostic names it (`DealsTable`), and Kysely's `Selectable` of it otherwise.
174
+ */
175
+ type RowType<R> = [R] extends [Selectable<R>] ? [Selectable<R>] extends [R] ? R : Selectable<R> : Selectable<R>;
176
+ /** The file columns of table `T`'s policy (`files: { <column>: '<kind>' }`). */
177
+ type FileColumnsOf<P, T> = PolicyOf<P, T> extends {
178
+ readonly files: infer F;
179
+ } ? keyof F & string : never;
180
+ /** A row or write of a table with file columns: each holds a file reference, `null` where the column is nullable. */
181
+ type WithFiles<R, Files extends string> = [Files] extends [never] ? R : {
182
+ [K in keyof R]: K extends Files ? (null extends R[K] ? FileReference | null : FileReference) : R[K];
183
+ };
184
+ type ReferenceTableName<P> = {
185
+ [K in keyof P]: P[K] extends {
186
+ readonly scope: {
187
+ readonly reference: string;
188
+ };
189
+ } ? K : never;
190
+ }[keyof P] & string;
191
+ /**
192
+ * Every table of an application, and the lookups the handles need, built once by the kit (`defineApp`) so that each
193
+ * endpoint's context is a property lookup, not a pass over the policies. `V`, the vocabulary, gives a row's chain above
194
+ * a dimension no table owns, as the layer derives it.
195
+ */
196
+ export type DbIndex<DB, P, V = unknown> = {
197
+ readonly tables: {
198
+ readonly [T in keyof P & keyof DB & string]: IndexedTableOf<DB, P, T, V>;
199
+ };
200
+ /** Dimension → the table that owns it. */
201
+ readonly owner: {
202
+ readonly [T in DimensionTableName<P> as DimensionOf<P, T>]: T;
203
+ };
204
+ /** Parent table → its child tables. */
205
+ readonly children: {
206
+ readonly [T in ChildTableName<P> as ParentOf<P, T>]: T;
207
+ };
208
+ /** Dimension → the dimension tables listed in it: those it contains, and a top-level table its own. */
209
+ readonly listed: {
210
+ readonly [T in DimensionTableName<P> as ListDimensionOf<P, T>]: T;
211
+ };
212
+ /** Dimension → the tables an `over` of it lists: each whose containment chain holds it, and a top-level table its own. */
213
+ readonly within: {
214
+ readonly [T in DimensionTableName<P> | ChildTableName<P> as ReachOf<P, T, V>]: T;
215
+ };
216
+ readonly references: ReferenceTableName<P>;
217
+ /** The application tables: an `app` endpoint reads and writes them. */
218
+ readonly applications: ApplicationTableName<P>;
219
+ /** The dimension and child tables: an `app` endpoint reads each whose read action is its own. */
220
+ readonly records: DimensionTableName<P> | ChildTableName<P>;
221
+ };
222
+ /** What every handle type takes: a `DbIndex`. */
223
+ export interface AnyDbIndex {
224
+ readonly tables: {
225
+ readonly [table: string]: IndexedTable;
226
+ };
227
+ readonly owner: object;
228
+ readonly children: object;
229
+ readonly listed: object;
230
+ readonly within: object;
231
+ readonly references: string;
232
+ readonly applications: string;
233
+ readonly records: string;
234
+ }
235
+ type TableName<Ix extends AnyDbIndex> = keyof Ix['tables'] & string;
236
+ type Lookup<M, K> = K extends keyof M ? M[K] & string : never;
237
+ type OwnerOf<Ix extends AnyDbIndex, D> = Lookup<Ix['owner'], D>;
238
+ type ChildrenOf<Ix extends AnyDbIndex, T> = Lookup<Ix['children'], T>;
239
+ type ListedIn<Ix extends AnyDbIndex, D> = Lookup<Ix['listed'], D>;
240
+ type WithinIn<Ix extends AnyDbIndex, D> = Lookup<Ix['within'], D>;
241
+ /**
242
+ * The columns a capability of action `A` cannot read: each that declares a read action other than exactly `A`. A
243
+ * union `A` (a widened capability type) holds no single action provably, so it reads none of them.
244
+ */
245
+ type HiddenFor<Reads, A> = {
246
+ [C in keyof Reads]: [A] extends [Reads[C]] ? ([Reads[C]] extends [A] ? never : C) : C;
247
+ }[keyof Reads];
248
+ /**
249
+ * A row as a capability of action `A` reads, filters and sorts it: a column that declares another read action is
250
+ * absent. The type keeps the generated row's name, so a read of a hidden column reports
251
+ * `Property 'margin' does not exist on type 'Omit<Selectable<DealsTable>, "margin">'`.
252
+ */
253
+ export type RowFor<E extends IndexedTable, A extends string> = [HiddenFor<E['reads'], A>] extends [never] ? E['row'] : Omit<E['row'], HiddenFor<E['reads'], A>>;
254
+ /** What a paved-road write answers: the key, and the new version of a versioned table. */
255
+ export type Saved<E extends IndexedTable> = E['versioned'] extends true ? {
256
+ readonly id: string;
257
+ readonly version: number;
258
+ } : {
259
+ readonly id: string;
260
+ };
261
+ /** A versioned table's update and remove take `{ expectVersion }`; another table's take nothing. */
262
+ export type VersionArguments<E extends IndexedTable> = E['versioned'] extends true ? [options: {
263
+ readonly expectVersion: number;
264
+ }] : [];
265
+ /** The values an insert takes: the columns of the table's `insert` list. */
266
+ type InsertValuesOf<E extends IndexedTable> = {
267
+ [C in keyof E['insert']]: E['insert'][C];
268
+ };
269
+ /** `onConflict` of an insert: the columns of a unique index the migrations create, and what a conflict does. */
270
+ export interface InsertConflict<E extends IndexedTable, M extends 'nothing' | 'update'> {
271
+ readonly onConflict: {
272
+ readonly columns: readonly (keyof E['row'] & string)[];
273
+ readonly do: M;
274
+ };
275
+ }
276
+ /**
277
+ * An insert: with `onConflict`, a conflict of its columns writes nothing (`'nothing'`, which answers `{ inserted: false }`)
278
+ * or updates the conflicting row's update columns (`'update'`, which also needs the table's update action).
279
+ */
280
+ export interface InsertMethod<E extends IndexedTable> {
281
+ (values: InsertValuesOf<E>, options: InsertConflict<E, 'nothing'>): Promise<(Saved<E> & {
282
+ readonly inserted: true;
283
+ }) | {
284
+ readonly inserted: false;
285
+ }>;
286
+ (values: InsertValuesOf<E>, options: InsertConflict<E, 'update'>): Promise<Saved<E> & {
287
+ readonly inserted: boolean;
288
+ }>;
289
+ (values: InsertValuesOf<E>): Promise<Saved<E>>;
290
+ }
291
+ /** What a claim takes: how many rows to lease, at most `MAX_LIST_LIMIT`, and conditions on the columns its action reads. */
292
+ export interface ClaimOptions<Row> {
293
+ readonly limit: number;
294
+ readonly where?: readonly ConditionTuple<Row>[];
295
+ }
296
+ /** A claimed row: the columns of the claim's action, its new `version`, and `ref`, which `tx.db.on(ref)` finishes it through. */
297
+ export type ClaimedRow<T extends string, E extends IndexedTable, A extends string> = RowFor<E, A> & {
298
+ readonly version: number;
299
+ readonly ref: RowRef<T, A>;
300
+ };
301
+ type ClaimRefusal<K extends DataKind> = K extends 'query' ? 'claim leases rows, so it runs only in a job' : 'claim leases rows in a transaction of its own: a job calls it on ctx.db before ctx.transaction, never inside one or in a request';
302
+ /** `claim` of a queue table, in a job, for a capability of its update action. */
303
+ type Claim<T extends string, E extends IndexedTable, A extends string, K extends DataKind> = K extends 'job' ? E['queued'] extends true ? [A] extends [E['actions']['update']] ? (options: ClaimOptions<RowFor<E, A>>) => Promise<ClaimedRow<T, E, A>[]> : Unavailable<`a claim of ${T} needs its update action '${E['actions']['update'] & string}', and this capability holds '${A}'`> : Unavailable<`${T} is not a queue table: declare queue: { attempts, due } in its policy`> : Unavailable<ClaimRefusal<K>>;
304
+ type JobWrite = Unavailable<'a job writes inside ctx.transaction((tx) => …): use tx.db there'>;
305
+ /**
306
+ * `tx.db.on(row.ref)`: one claimed row, finished in the job's transaction. `update` writes the claim action's columns and,
307
+ * with `done: true`, clears `due`; `remove` needs the table's delete action; `requeue` makes the row due again at `at`
308
+ * (now by default) with no attempts.
309
+ */
310
+ export interface ClaimedRowHandle<T extends string, E extends IndexedTable, A extends string, K extends DataKind> {
311
+ readonly update: K extends 'mutation' ? (values: {
312
+ [C in keyof UpdateValuesOf<E, A>]: UpdateValuesOf<E, A>[C];
313
+ }, options: {
314
+ readonly expectVersion: number;
315
+ readonly done?: boolean;
316
+ }) => Promise<Saved<E>> : JobWrite;
317
+ readonly remove: K extends 'mutation' ? [A] extends [E['actions']['delete']] ? (options: {
318
+ readonly expectVersion: number;
319
+ }) => Promise<void> : [E['actions']['delete']] extends [never] ? Unavailable<`${T} allows no remove: its policy declares no delete action`> : Unavailable<`remove of a claimed ${T} row needs its delete action '${E['actions']['delete'] & string}', and this row was claimed with '${A}'`> : JobWrite;
320
+ readonly requeue: K extends 'mutation' ? (options?: {
321
+ readonly at?: Date | string;
322
+ }) => Promise<Saved<E>> : JobWrite;
323
+ }
324
+ /** The actions table `E` names: its read, insert, update and delete actions, and each column's own write action. */
325
+ type NamedActions<E extends IndexedTable> = (E['actions'][keyof E['actions']] | keyof E['writes']) & string;
326
+ type Names<E extends IndexedTable, A extends string> = [A] extends [NamedActions<E>] ? true : false;
327
+ type Operation = 'insert' | 'update' | 'delete';
328
+ /** `Member` in a mutation whose action is the table's `Op` action; otherwise the message that says which fix applies. */
329
+ type Write<T extends string, E extends IndexedTable, Op extends Operation, A extends string, K extends DataKind, Member> = K extends 'mutation' ? [A] extends [E['actions'][Op]] ? Member : Unavailable<`${Op} on ${T} needs the action '${E['actions'][Op] & string}', and this endpoint holds '${A}'`> : K extends 'job' ? Unavailable<'a job writes inside ctx.transaction((tx) => …): use tx.db there'> : Unavailable<`${Op extends 'delete' ? 'remove' : Op} writes, so it runs only in a mutation: declare the endpoint with r.mutation`>;
330
+ /** What an update of `T` needs, for a capability of `A` that updates nothing of it. */
331
+ type UpdateRefusal<T extends string, E extends IndexedTable, A extends string> = [Exclude<keyof E['writes'], E['actions']['update']>] extends [never] ? `update on ${T} needs the action '${E['actions']['update'] & string}', and this endpoint holds '${A}'` : [E['actions']['update']] extends [never] ? `update on ${T} needs a column's own write action, and this endpoint holds '${A}'` : `update on ${T} needs the action '${E['actions']['update'] & string}' or a column's own write action, and this endpoint holds '${A}'`;
332
+ /**
333
+ * `Member` in a mutation whose action updates columns of the table (the update action, or a column's own write
334
+ * action); otherwise the message that says which fix applies.
335
+ */
336
+ type Update<T extends string, E extends IndexedTable, A extends string, K extends DataKind, Member> = K extends 'mutation' ? [A] extends [keyof E['writes']] ? Member : Unavailable<UpdateRefusal<T, E, A>> : K extends 'job' ? Unavailable<'a job writes inside ctx.transaction((tx) => …): use tx.db there'> : Unavailable<'update writes, so it runs only in a mutation: declare the endpoint with r.mutation'>;
337
+ /** An update's values: any non-empty subset of the columns a capability of `A` updates; an empty one is refused. */
338
+ type UpdateValuesOf<E extends IndexedTable, A extends string> = E['writes'][A & keyof E['writes']];
339
+ type Read<T extends string, E extends IndexedTable, A extends string, Member> = Names<E, A> extends true ? Member : Unavailable<`${T} does not name the action '${A}': read it from an endpoint whose action ${T} names, or declare unscoped`>;
340
+ /** One page of a keyset list; `next` is the cursor `after` takes, `null` on the last page. */
341
+ export interface ListPage<Row> {
342
+ readonly items: Row[];
343
+ readonly next: string | null;
344
+ }
345
+ export type OrderTerm<Row> = {
346
+ [C in keyof Row & string]: readonly [C, 'asc' | 'desc'];
347
+ }[keyof Row & string];
348
+ export interface ListOptions<Row> {
349
+ readonly where?: readonly ConditionTuple<Row>[];
350
+ readonly orderBy?: readonly OrderTerm<Row>[];
351
+ readonly limit?: number;
352
+ readonly after?: string;
353
+ }
354
+ export interface CountOptions<Row> {
355
+ readonly where?: readonly ConditionTuple<Row>[];
356
+ }
357
+ /** The target's own row: `ctx.db.target` (or `ctx.db.created`) of an `on`, `in` or `creates` endpoint. */
358
+ export interface TargetRow<T extends string, E extends IndexedTable, A extends string, K extends DataKind, F extends TargetForm> {
359
+ /** The row; a column that declares another read action than `A` is hidden. */
360
+ readonly get: Read<T, E, A, () => Promise<RowFor<E, A>>>;
361
+ /** `SELECT … FOR UPDATE` on the target: a precondition read that no concurrent write overtakes. */
362
+ readonly getForUpdate: K extends 'mutation' ? F extends 'on' ? Read<T, E, A, () => Promise<RowFor<E, A>>> : Unavailable<'getForUpdate locks one object: declare access with on'> : K extends 'job' ? Unavailable<'a job writes inside ctx.transaction((tx) => …): use tx.db there'> : Unavailable<'getForUpdate locks the row, so it runs only in a mutation: declare the endpoint with r.mutation'>;
363
+ readonly update: F extends 'on' ? Update<T, E, A, K, (values: {
364
+ [C in keyof UpdateValuesOf<E, A>]: UpdateValuesOf<E, A>[C];
365
+ }, ...version: VersionArguments<E>) => Promise<Saved<E>>> : Unavailable<'update acts on one object: declare access with on'>;
366
+ readonly remove: F extends 'on' ? Write<T, E, 'delete', A, K, (...version: VersionArguments<E>) => Promise<void>> : Unavailable<'remove acts on one object: declare access with on'>;
367
+ }
368
+ /**
369
+ * The row of a top-level scope, under `in` or `creates`: IAM holds the scope, the application inserts its row, keyed
370
+ * by the scope's value.
371
+ */
372
+ export interface OwnerRow<T extends string, E extends IndexedTable, A extends string, K extends DataKind> {
373
+ readonly insert: Write<T, E, 'insert', A, K, InsertMethod<E>>;
374
+ }
375
+ /** A dimension no table owns has no row of its own; its subtree tables are still reachable. */
376
+ export interface UnownedTarget<D extends string> {
377
+ readonly get: Unavailable<`no table owns the dimension ${D}, so ctx.db.target has no row of its own`>;
378
+ readonly getForUpdate: Unavailable<`no table owns the dimension ${D}, so ctx.db.target has no row of its own`>;
379
+ readonly update: Unavailable<`no table owns the dimension ${D}, so ctx.db.target has no row of its own`>;
380
+ readonly remove: Unavailable<`no table owns the dimension ${D}, so ctx.db.target has no row of its own`>;
381
+ }
382
+ /** A child table of the target: its rows are addressed by their own key under the target. */
383
+ export interface ChildRows<T extends string, E extends IndexedTable, A extends string, K extends DataKind> {
384
+ readonly list: Read<T, E, A, (options?: ListOptions<RowFor<E, A>>) => Promise<ListPage<RowFor<E, A>>>>;
385
+ readonly get: Read<T, E, A, (key: string) => Promise<RowFor<E, A>>>;
386
+ readonly insert: Write<T, E, 'insert', A, K, InsertMethod<E>>;
387
+ readonly update: Update<T, E, A, K, (key: string, values: {
388
+ [C in keyof UpdateValuesOf<E, A>]: UpdateValuesOf<E, A>[C];
389
+ }, ...version: VersionArguments<E>) => Promise<Saved<E>>>;
390
+ readonly remove: Write<T, E, 'delete', A, K, (key: string, ...version: VersionArguments<E>) => Promise<void>>;
391
+ }
392
+ /** A dimension table the target contains: listed in it, and inserted into it by a mutation of its insert action. */
393
+ export interface ContainedRows<T extends string, E extends IndexedTable, A extends string, K extends DataKind> {
394
+ readonly list: Read<T, E, A, (options?: ListOptions<RowFor<E, A>>) => Promise<ListPage<RowFor<E, A>>>>;
395
+ readonly insert: Write<T, E, 'insert', A, K, InsertMethod<E>>;
396
+ }
397
+ type SubtreeTables<Ix extends AnyDbIndex, D extends string, A extends string, K extends DataKind, F extends TargetForm> = {
398
+ readonly [C in ChildrenOf<Ix, OwnerOf<Ix, D>>]: ChildRows<C, Ix['tables'][C], A, K>;
399
+ } & {
400
+ readonly [C in Exclude<ListedIn<Ix, D>, OwnerOf<Ix, D>>]: ContainedRows<C, Ix['tables'][C], A, K>;
401
+ } & {
402
+ readonly [C in F extends 'in' ? OwnerOf<Ix, D> & ListedIn<Ix, D> : never]: OwnerRow<C, Ix['tables'][C], A, K>;
403
+ };
404
+ /**
405
+ * `ctx.db.target`: the target's row and its subtree — its child tables and the dimension tables it contains; under
406
+ * `in` (and `creates`) also the insert of a top-level scope's own row.
407
+ */
408
+ export type ScopeTarget<Ix extends AnyDbIndex, D extends string, A extends string, K extends DataKind, F extends TargetForm> = ([OwnerOf<Ix, D>] extends [never] ? UnownedTarget<D> : TargetRow<OwnerOf<Ix, D>, Ix['tables'][OwnerOf<Ix, D>], A, K, F>) & SubtreeTables<Ix, D, A, K, F>;
409
+ /** A table listed in an `over` endpoint's dimension, read through its `Within`; a job claims a queue table's rows through it. */
410
+ export interface WithinRows<T extends string, E extends IndexedTable, A extends string, K extends DataKind> {
411
+ readonly claim: Claim<T, E, A, K>;
412
+ readonly list: A extends E['actions']['read'] ? (options?: ListOptions<RowFor<E, A>>) => Promise<ListPage<RowFor<E, A>>> : Unavailable<ListRefusal<T, E, A>>;
413
+ readonly count: A extends E['actions']['read'] ? (options?: CountOptions<RowFor<E, A>>) => Promise<number> : Unavailable<ListRefusal<T, E, A>>;
414
+ readonly aggregate: A extends E['actions']['read'] ? <const S extends AggregateSpec<RowFor<E, A>>>(spec: S) => Promise<AggregateRow<RowFor<E, A>, S>[]> : Unavailable<ListRefusal<T, E, A>>;
415
+ }
416
+ /** Inlined where used: an alias of its own would be printed by name, with the table's index entry spelled out. */
417
+ type ListRefusal<T extends string, E extends IndexedTable, A extends string> = `a list of ${T} needs its read action '${E['actions']['read'] & string}', and this endpoint holds '${A}'`;
418
+ /**
419
+ * `ctx.db.within` of an `over` endpoint: every table whose containment chain holds its dimension (tasks two levels
420
+ * down through their stamped `org_id`), and a top-level table its own; child tables included.
421
+ */
422
+ export type WithinTables<Ix extends AnyDbIndex, D extends string, A extends string, K extends DataKind> = {
423
+ readonly [T in WithinIn<Ix, D>]: WithinRows<T, Ix['tables'][T], A, K>;
424
+ };
425
+ /** An application table under the capability of an `app` endpoint of action `A`, in a site of kind `K`. */
426
+ export interface ApplicationRows<T extends string, E extends IndexedTable, A extends string, K extends DataKind> {
427
+ readonly list: A extends E['actions']['read'] ? (options?: ListOptions<RowFor<E, A>>) => Promise<ListPage<RowFor<E, A>>> : Unavailable<ListRefusal<T, E, A>>;
428
+ readonly count: A extends E['actions']['read'] ? (options?: CountOptions<RowFor<E, A>>) => Promise<number> : Unavailable<ListRefusal<T, E, A>>;
429
+ readonly aggregate: A extends E['actions']['read'] ? <const S extends AggregateSpec<RowFor<E, A>>>(spec: S) => Promise<AggregateRow<RowFor<E, A>, S>[]> : Unavailable<ListRefusal<T, E, A>>;
430
+ /** One row by its key; a column that declares another read action than `A` is hidden. */
431
+ readonly get: Read<T, E, A, (key: string) => Promise<RowFor<E, A>>>;
432
+ /** `SELECT … FOR UPDATE` of one row by its key: a precondition read that no concurrent write overtakes. */
433
+ readonly getForUpdate: K extends 'mutation' ? Read<T, E, A, (key: string) => Promise<RowFor<E, A>>> : K extends 'job' ? Unavailable<'a job writes inside ctx.transaction((tx) => …): use tx.db there'> : Unavailable<'getForUpdate locks the row, so it runs only in a mutation: declare the endpoint with r.mutation'>;
434
+ /** Leases due rows of a queue table: a job's drain, before its transactions. */
435
+ readonly claim: Claim<T, E, A, K>;
436
+ readonly insert: Write<T, E, 'insert', A, K, InsertMethod<E>>;
437
+ readonly update: Update<T, E, A, K, (key: string, values: {
438
+ [C in keyof UpdateValuesOf<E, A>]: UpdateValuesOf<E, A>[C];
439
+ }, ...version: VersionArguments<E>) => Promise<Saved<E>>>;
440
+ readonly remove: Write<T, E, 'delete', A, K, (key: string, ...version: VersionArguments<E>) => Promise<void>>;
441
+ }
442
+ /**
443
+ * A public procedure's insert: it answers the new row's key (and version), which the data layer generated, and reads
444
+ * no column back; a conflict of `onConflict`'s columns writes nothing.
445
+ */
446
+ export interface PublicInsertMethod<E extends IndexedTable> {
447
+ (values: InsertValuesOf<E>, options: InsertConflict<E, 'nothing'>): Promise<(Saved<E> & {
448
+ readonly inserted: true;
449
+ }) | {
450
+ readonly inserted: false;
451
+ }>;
452
+ (values: InsertValuesOf<E>): Promise<Saved<E>>;
453
+ }
454
+ type PublicRefusal<Op extends string> = Unavailable<`a public procedure does not ${Op} a row: it inserts, and reads under the table's read action`>;
455
+ /** A public procedure updates and removes only a versioned row, by an id a verified signed token of its request carries. */
456
+ type PublicVersioned<T extends string, E extends IndexedTable, Member> = E['versioned'] extends true ? Member : Unavailable<`a public procedure updates and removes only a versioned row: declare version in the policy of ${T}`>;
457
+ /**
458
+ * An application table of a public procedure of action `A`, in a site of kind `K`: read under its read action only,
459
+ * inserted with nothing read back, and updated or removed only by the id a verified signed token of the request carries,
460
+ * under `expectVersion`; never locked or claimed.
461
+ */
462
+ export interface PublicApplicationRows<T extends string, E extends IndexedTable, A extends string, K extends DataKind> {
463
+ readonly list: ApplicationRows<T, E, A, K>['list'];
464
+ readonly count: ApplicationRows<T, E, A, K>['count'];
465
+ readonly aggregate: ApplicationRows<T, E, A, K>['aggregate'];
466
+ readonly get: A extends E['actions']['read'] ? (key: string) => Promise<RowFor<E, A>> : Unavailable<ListRefusal<T, E, A>>;
467
+ readonly getForUpdate: PublicRefusal<'lock'>;
468
+ readonly claim: PublicRefusal<'claim'>;
469
+ /** By the id a verified signed token of the request carries; answers only the new version. */
470
+ readonly update: PublicVersioned<T, E, Update<T, E, A, K, (key: string, values: {
471
+ [C in keyof UpdateValuesOf<E, A>]: UpdateValuesOf<E, A>[C];
472
+ }, options: {
473
+ readonly expectVersion: number;
474
+ }) => Promise<{
475
+ readonly version: number;
476
+ }>>>;
477
+ /** By the id a verified signed token of the request carries. */
478
+ readonly remove: PublicVersioned<T, E, Write<T, E, 'delete', A, K, (key: string, options: {
479
+ readonly expectVersion: number;
480
+ }) => Promise<void>>>;
481
+ readonly insert: Write<T, E, 'insert', A, K, PublicInsertMethod<E>>;
482
+ }
483
+ /** `ctx.db.app('<action>')` of a public procedure: the application tables only, each as `PublicApplicationRows`. */
484
+ export type PublicApplicationTables<Ix extends AnyDbIndex, A extends string, K extends DataKind> = {
485
+ readonly [T in Ix['applications']]: PublicApplicationRows<T, Ix['tables'][T], A, K>;
486
+ };
487
+ /**
488
+ * A dimension or child table under an `app` endpoint of action `A`: every row read-only, with the columns `A` reads
489
+ * (`RowFor`), or only how many rows match. A count-only action has no `aggregate`: a group or a minimum is a value.
490
+ */
491
+ export interface AppRecordRows<T extends string, E extends IndexedTable, A extends string> {
492
+ readonly list: [A] extends [E['actions']['read'] | E['appRead']] ? (options?: ListOptions<RowFor<E, A>>) => Promise<ListPage<RowFor<E, A>>> : Unavailable<`a list of ${T} needs its read action '${E['actions']['read'] & string}' or an action of its appRead, and this endpoint holds '${A}'`>;
493
+ readonly count: [A] extends [E['actions']['read'] | E['appRead'] | E['appCount']] ? (options?: CountOptions<RowFor<E, A>>) => Promise<number> : Unavailable<`a count of ${T} needs its read action '${E['actions']['read'] & string}' or an action of its appRead or appCount, and this endpoint holds '${A}'`>;
494
+ readonly aggregate: [A] extends [E['actions']['read'] | E['appRead']] ? <const S extends AggregateSpec<RowFor<E, A>>>(spec: S) => Promise<AggregateRow<RowFor<E, A>, S>[]> : Unavailable<`an aggregate of ${T} needs its read action '${E['actions']['read'] & string}' or an action of its appRead, and this endpoint holds '${A}': an appCount action only counts`>;
495
+ }
496
+ /**
497
+ * `ctx.db.app` of an `app` endpoint: every application table, and every dimension and child table read-only, all
498
+ * rows, where the endpoint's action is its read action or an action of its `appRead` (`over` with `'all'` reads them
499
+ * so), or only their counts with an action of its `appCount`.
500
+ */
501
+ export type ApplicationTables<Ix extends AnyDbIndex, A extends string, K extends DataKind> = {
502
+ readonly [T in Ix['applications']]: ApplicationRows<T, Ix['tables'][T], A, K>;
503
+ } & {
504
+ readonly [T in Ix['records']]: AppRecordRows<T, Ix['tables'][T], A>;
505
+ };
506
+ /** The row a site reads without a capability of its own: through the table's read action. */
507
+ type TableRow<E extends IndexedTable> = RowFor<E, E['actions']['read']>;
508
+ /**
509
+ * An application table read from an `on`, `in` or `over` endpoint whose action is its read action: every row, as a
510
+ * reference table is read, and no write.
511
+ */
512
+ export interface ScopedApplicationRows<E extends IndexedTable> {
513
+ list(options?: ListOptions<TableRow<E>>): Promise<ListPage<TableRow<E>>>;
514
+ count(options?: CountOptions<TableRow<E>>): Promise<number>;
515
+ aggregate<const S extends AggregateSpec<TableRow<E>>>(spec: S): Promise<AggregateRow<TableRow<E>, S>[]>;
516
+ /** One row by its key. */
517
+ get(key: string): Promise<TableRow<E>>;
518
+ readonly getForUpdate: Unavailable<'a scoped endpoint only reads an application table: write it from an endpoint declared access: { action, app: true }'>;
519
+ readonly insert: Unavailable<'a scoped endpoint only reads an application table: write it from an endpoint declared access: { action, app: true }'>;
520
+ readonly update: Unavailable<'a scoped endpoint only reads an application table: write it from an endpoint declared access: { action, app: true }'>;
521
+ readonly remove: Unavailable<'a scoped endpoint only reads an application table: write it from an endpoint declared access: { action, app: true }'>;
522
+ }
523
+ /**
524
+ * `ctx.db.app` of an `on`, `in` or `over` endpoint of action `A`: each application table whose read action is `A`,
525
+ * read-only; any other is the message that names its read action.
526
+ */
527
+ export type ScopedApplicationTables<Ix extends AnyDbIndex, A extends string> = {
528
+ readonly [T in Ix['applications']]: [A] extends [Ix['tables'][T]['actions']['read']] ? ScopedApplicationRows<Ix['tables'][T]> : Unavailable<`a scoped endpoint reads ${T} with its read action '${Ix['tables'][T]['actions']['read'] & string}', and this endpoint holds '${A}'`>;
529
+ };
530
+ /** A reference table: every endpoint reads it, through its read action, and none writes it. */
531
+ export interface ReferenceRows<E extends IndexedTable> {
532
+ list(options?: ListOptions<TableRow<E>>): Promise<ListPage<TableRow<E>>>;
533
+ count(options?: CountOptions<TableRow<E>>): Promise<number>;
534
+ }
535
+ /** `ctx.db.reference`. */
536
+ export type ReferenceTables<Ix extends AnyDbIndex> = {
537
+ readonly [T in Ix['references']]: ReferenceRows<Ix['tables'][T]>;
538
+ };
539
+ /** The mode an endpoint, job or webhook declares for a table outside its scope. */
540
+ export type UnscopedMode = 'read' | 'write';
541
+ /** `unscoped: { <table>: 'read' | 'write' }` as a site may declare it: only tables of `src/db/tables.ts`. */
542
+ export type UnscopedDeclaration<Table extends string, Mode extends UnscopedMode> = {
543
+ readonly [T in Table]?: Mode;
544
+ };
545
+ /**
546
+ * The tables of `U` whose mode may be `'write'` that no site writes: a reference table. A union or optional mode that
547
+ * admits `'write'` counts, because the declaration it types may hold it.
548
+ */
549
+ type ReadOnlyWritten<Ix extends AnyDbIndex, U> = {
550
+ [T in keyof U & TableName<Ix>]: 'write' extends U[T] ? Ix['tables'][T]['mode'] extends 'reference' ? T : never : never;
551
+ }[keyof U & TableName<Ix>];
552
+ type ReadOnlyMessage<T extends string> = `${T} is a reference table, which no site writes: its rows come from a migration, so declare unscoped: { ${T}: 'read' }`;
553
+ /** An `unscoped` declaration's `'write'` of a reference table, refused at its own line. */
554
+ export type ReadOnlyUnscoped<Ix extends AnyDbIndex, U> = [ReadOnlyWritten<Ix, U>] extends [never] ? unknown : {
555
+ readonly [T in ReadOnlyWritten<Ix, U>]: Unavailable<ReadOnlyMessage<T>>;
556
+ };
557
+ type KeylessMessage<T extends string> = `${T} is a reference table, which has no key: read it with list`;
558
+ /** A table a site declared `unscoped`: its scope predicate is `true`, and `'read'` writes nothing. */
559
+ export interface UnscopedRows<T extends string, E extends IndexedTable, M extends UnscopedMode, K extends DataKind> {
560
+ list(options?: ListOptions<TableRow<E>>): Promise<ListPage<TableRow<E>>>;
561
+ count(options?: CountOptions<TableRow<E>>): Promise<number>;
562
+ aggregate<const S extends AggregateSpec<TableRow<E>>>(spec: S): Promise<AggregateRow<TableRow<E>, S>[]>;
563
+ readonly get: E['mode'] extends 'reference' ? Unavailable<KeylessMessage<T>> : (key: string) => Promise<TableRow<E>>;
564
+ readonly insert: UnscopedWrite<T, M, K, 'insert', (values: {
565
+ [C in keyof UnscopedInsert<E>]: UnscopedInsert<E>[C];
566
+ }) => Promise<Saved<E>>>;
567
+ readonly update: UnscopedWrite<T, M, K, 'update', (key: string, values: {
568
+ [C in keyof E['update']]: E['update'][C];
569
+ }, ...version: VersionArguments<E>) => Promise<Saved<E>>>;
570
+ readonly remove: UnscopedWrite<T, M, K, 'remove', (key: string, ...version: VersionArguments<E>) => Promise<void>>;
571
+ }
572
+ /**
573
+ * An unscoped insert has no capability to stamp the containment from, so it takes the containment columns; the key,
574
+ * the version and the creator stay the layer's.
575
+ */
576
+ export type UnscopedInsert<E extends IndexedTable> = Flat<E['insert'] & {
577
+ [C in E['chain'] & keyof E['row']]: E['row'][C];
578
+ }>;
579
+ /** `Member` for a table declared `'write'`, in a site that writes there; otherwise the declaration or place to fix. */
580
+ type UnscopedWrite<T extends string, M extends UnscopedMode, K extends DataKind, Op extends string, Member> = M extends 'write' ? K extends 'job' ? Unavailable<'a job writes inside ctx.transaction((tx) => …): use tx.db there'> : Member : Unavailable<`${T} is declared unscoped: 'read', so ${Op} needs unscoped: { ${T}: 'write' }`>;
581
+ /** `ctx.db.unscoped`: the declared tables in their mode; any other table names the declaration to add. */
582
+ export type UnscopedTables<Ix extends AnyDbIndex, U, K extends DataKind> = {
583
+ readonly [T in TableName<Ix>]: U extends {
584
+ readonly [Q in T]: infer M extends UnscopedMode;
585
+ } ? UnscopedRows<T, Ix['tables'][T], M, K> : Unavailable<`${T} is outside this site's scope: declare unscoped: { ${T}: 'read' } on it`>;
586
+ };
587
+ /** `ctx.db.unscoped` of a site that declares none. */
588
+ export type NoUnscoped = Unavailable<"this site declares no unscoped table: add unscoped: { <table>: 'read' } to its declaration">;
589
+ type TableEntry<Tb, T> = Tb extends Tables<infer DB, infer P> ? T extends keyof P & keyof DB & string ? IndexedTableOf<DB, P, T> : never : never;
590
+ /** The tables of a `defineTables` result. */
591
+ export type TableNameOf<Tb> = Tb extends Tables<infer DB, infer P> ? keyof P & keyof DB & string : never;
592
+ /**
593
+ * The row of table `T` as a capability of `A` reads it — what `ctx.db.*.get()` and a list's items are:
594
+ * `RowOf<typeof tables, 'tasks', 'tasks.read'>`. `A` is an action the table names and defaults to its read action;
595
+ * every column that declares another read action is absent.
596
+ */
597
+ export type RowOf<Tb, T extends TableNameOf<Tb>, A extends (TableEntry<Tb, T>['actions'][keyof TableEntry<Tb, T>['actions']] | keyof TableEntry<Tb, T>['writes']) & string = TableEntry<Tb, T>['actions']['read']> = RowFor<TableEntry<Tb, T>, A>;
598
+ /** What a capability type says of itself: its kind, and its dimension and action. */
599
+ type GrantOf<C> = C extends {
600
+ readonly '~capability': infer G;
601
+ } ? G : never;
602
+ /** The tables a fragment of a `Ref` of `D` and `A` reads: its chain's with an action they name, and an application table's by its read action. */
603
+ type RefFragment<Ix extends AnyDbIndex, D extends string, A extends string> = {
604
+ [T in TableName<Ix>]: Ix['tables'][T]['mode'] extends 'app' ? (A extends Ix['tables'][T]['actions']['read'] ? T : never) : D extends Ix['tables'][T]['refs'] ? (A extends NamedActions<Ix['tables'][T]> ? T : never) : never;
605
+ }[TableName<Ix>];
606
+ /**
607
+ * What a `Within` writes on a `'write'` site: the target of an UPDATE or DELETE whose action it holds, a column's own
608
+ * write action included (the statement's SET is checked against its columns at run time).
609
+ */
610
+ type WithinWrites<E extends IndexedTable, M extends 'read' | 'write'> = M extends 'write' ? E['actions']['update'] | E['actions']['delete'] | (keyof E['writes'] & string) : never;
611
+ /** The tables a fragment of a `Within` of `D` and `A` reaches: those it lists by their read action, or writes on `M`. */
612
+ type WithinFragment<Ix extends AnyDbIndex, D extends string, A extends string, M extends 'read' | 'write'> = {
613
+ [T in TableName<Ix>]: Ix['tables'][T]['mode'] extends 'app' ? (A extends Ix['tables'][T]['actions']['read'] ? T : never) : D extends Ix['tables'][T]['reach'] ? (A extends Ix['tables'][T]['actions']['read'] | WithinWrites<Ix['tables'][T], M> ? T : never) : never;
614
+ }[TableName<Ix>];
615
+ /** The tables a fragment of an `app` capability of `A` reaches: as `ctx.db.app` lists them for that action. */
616
+ type AppFragment<Ix extends AnyDbIndex, A extends string> = {
617
+ [T in TableName<Ix>]: Ix['tables'][T]['mode'] extends 'app' ? (A extends NamedActions<Ix['tables'][T]> ? T : never) : Ix['tables'][T]['mode'] extends 'dimension' | 'child' ? (A extends Ix['tables'][T]['actions']['read'] | Ix['tables'][T]['appRead'] ? T : never) : never;
618
+ }[TableName<Ix>];
619
+ /**
620
+ * The tables `ctx.sql.scope(capability, table)` takes for a capability of type `C` on a site of mode `M`: those its
621
+ * fragment reaches for a use the site may make of it, so a fragment the runtime refuses for every use is a type error
622
+ * at the table. A dimension or an action widened to `string` proves nothing, so it takes any table.
623
+ */
624
+ export type FragmentTable<Ix extends AnyDbIndex, C, M extends 'read' | 'write'> = GrantOf<C> extends {
625
+ readonly kind: 'ref';
626
+ readonly dimension: infer D extends string;
627
+ readonly action: infer A extends string;
628
+ } ? string extends D | A ? TableName<Ix> : OrNoTable<RefFragment<Ix, D, A>, A> : GrantOf<C> extends {
629
+ readonly kind: 'within';
630
+ readonly dimension: infer D extends string;
631
+ readonly action: infer A extends string;
632
+ } ? string extends D | A ? TableName<Ix> : OrNoTable<WithinFragment<Ix, D, A, M>, A> : GrantOf<C> extends {
633
+ readonly kind: 'app';
634
+ readonly action: infer A extends string;
635
+ } ? string extends A ? TableName<Ix> : OrNoTable<AppFragment<Ix, A>, A> : TableName<Ix>;
636
+ /** The tables, or the message that says none takes a fragment of `A`: a parameter of type `never` names no fix. */
637
+ type OrNoTable<T, A extends string> = [T] extends [never] ? `no table takes a scope fragment of ${A}: reach its tables through ctx.db` : T;
638
+ export {};