@fabrika/db 0.0.0-stage → 0.0.1

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