@fabrika/db 0.6.0 → 0.8.0
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.
- package/package.json +3 -3
- package/src/{form-admissions.ts → admissions.ts} +29 -36
- package/src/capability.ts +30 -3
- package/src/description.ts +2 -1
- package/src/errors.ts +8 -2
- package/src/file-columns.ts +184 -0
- package/src/file-references.ts +60 -0
- package/src/framework-tables.ts +100 -45
- package/src/framework.ts +7 -29
- package/src/guard.ts +42 -8
- package/src/index.ts +2 -13
- package/src/job-leases.ts +11 -0
- package/src/links.ts +1 -1
- package/src/migrations.ts +126 -0
- package/src/nodes.ts +35 -3
- package/src/orm-types.ts +152 -28
- package/src/orm.ts +397 -38
- package/src/policy.ts +221 -74
- package/src/registry.ts +7 -18
- package/src/store.ts +447 -143
- package/src/stored-files.ts +402 -0
- package/src/tables.ts +32 -26
- package/src/testing.ts +39 -7
- package/src/vocabulary-tables.ts +4 -4
- package/src/file-table-sql.ts +0 -133
- package/src/file-tables.ts +0 -622
- package/src/form-files.ts +0 -226
package/src/orm-types.ts
CHANGED
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
// may not call is an `Unavailable` tuple: calling it fails at the call, and the message names the fix.
|
|
4
4
|
|
|
5
5
|
import type { Selectable, Updateable } from 'kysely'
|
|
6
|
+
import type { RowRef } from './capability'
|
|
6
7
|
import type { ConditionTuple } from './description'
|
|
8
|
+
import type { FileReference } from './file-references'
|
|
7
9
|
import type {
|
|
8
10
|
ActionOf,
|
|
9
11
|
ApplicationTableName,
|
|
@@ -37,7 +39,7 @@ type ModeOf<P, T> = PolicyOf<P, T> extends { readonly scope: infer S } ? S exten
|
|
|
37
39
|
: S extends { readonly child: string } ? 'child'
|
|
38
40
|
: S extends { readonly app: string } ? 'app'
|
|
39
41
|
: S extends { readonly reference: string } ? 'reference'
|
|
40
|
-
:
|
|
42
|
+
: never
|
|
41
43
|
: never
|
|
42
44
|
|
|
43
45
|
/** The actions a table's `actions.appRead` or `actions.appCount` lists; `never` when it lists none. */
|
|
@@ -45,8 +47,6 @@ type AppActionsOf<P, T, F extends 'appRead' | 'appCount'> = PolicyOf<P, T> exten
|
|
|
45
47
|
{ readonly actions: { readonly [K in F]: readonly (infer A extends string)[] } } ? A
|
|
46
48
|
: never
|
|
47
49
|
|
|
48
|
-
type ReadActionOf<P, T> = PolicyOf<P, T> extends { readonly scope: { readonly read: infer A extends string } } ? A : ActionOf<P, T, 'read'>
|
|
49
|
-
|
|
50
50
|
/** Each column that declares its own read action, by column. */
|
|
51
51
|
type ColumnReadsOf<P, T> = PolicyOf<P, T> extends { readonly columns: infer C } ? {
|
|
52
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
|
|
@@ -74,7 +74,7 @@ type ColumnWriteAction<C> = { [K in keyof C]: C[K] extends { readonly write: inf
|
|
|
74
74
|
|
|
75
75
|
/** One table, as every handle reads it. */
|
|
76
76
|
export interface IndexedTable {
|
|
77
|
-
readonly mode: 'dimension' | 'child' | 'app' | 'reference'
|
|
77
|
+
readonly mode: 'dimension' | 'child' | 'app' | 'reference'
|
|
78
78
|
/** The dimension a dimension table owns. */
|
|
79
79
|
readonly dimension: string
|
|
80
80
|
/** The dimension its rows are listed and inserted in: the container's, or a top-level table's own. */
|
|
@@ -101,6 +101,8 @@ export interface IndexedTable {
|
|
|
101
101
|
/** The dimensions whose `Within` lists the table: every dimension of its chain, and a top-level table's own. */
|
|
102
102
|
readonly reach: string
|
|
103
103
|
readonly versioned: boolean
|
|
104
|
+
/** A queue table: its rows are claimed and finished through `row.ref`. */
|
|
105
|
+
readonly queued: boolean
|
|
104
106
|
}
|
|
105
107
|
|
|
106
108
|
type IndexedTableOf<DB, P, T extends keyof P & keyof DB & string, V = unknown> = {
|
|
@@ -109,22 +111,23 @@ type IndexedTableOf<DB, P, T extends keyof P & keyof DB & string, V = unknown> =
|
|
|
109
111
|
readonly list: ListDimensionOf<P, T>
|
|
110
112
|
readonly parent: ParentOf<P, T>
|
|
111
113
|
readonly actions: {
|
|
112
|
-
readonly read:
|
|
114
|
+
readonly read: ActionOf<P, T, 'read'>
|
|
113
115
|
readonly insert: ActionOf<P, T, 'insert'>
|
|
114
116
|
readonly update: ActionOf<P, T, 'update'>
|
|
115
117
|
readonly delete: ActionOf<P, T, 'delete'>
|
|
116
118
|
}
|
|
117
119
|
readonly appRead: AppActionsOf<P, T, 'appRead'>
|
|
118
120
|
readonly appCount: AppActionsOf<P, T, 'appCount'>
|
|
119
|
-
readonly row: RowType<DB[T]
|
|
121
|
+
readonly row: WithFiles<RowType<DB[T]>, FileColumnsOf<P, T>>
|
|
120
122
|
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>
|
|
123
|
+
readonly insert: WithFiles<InsertValues<DB, P, T>, FileColumnsOf<P, T>>
|
|
124
|
+
readonly update: WithFiles<UpdateValues<DB, P, T>, FileColumnsOf<P, T>>
|
|
125
|
+
readonly writes: { readonly [A in keyof UpdatesOf<DB, P, T>]: WithFiles<UpdatesOf<DB, P, T>[A], FileColumnsOf<P, T>> }
|
|
124
126
|
readonly chain: ChainColumns<P, T, V>
|
|
125
127
|
readonly refs: DimensionOf<P, T> | ChainDimensions<P, T, V>
|
|
126
128
|
readonly reach: ReachOf<P, T, V>
|
|
127
129
|
readonly versioned: PolicyOf<P, T> extends { readonly version: string } ? true : false
|
|
130
|
+
readonly queued: PolicyOf<P, T> extends { readonly queue: object } ? true : false
|
|
128
131
|
}
|
|
129
132
|
|
|
130
133
|
type OwnerTable<P, D> = { [K in keyof P]: P[K] extends { readonly scope: { readonly dimension: D } } ? K : never }[keyof P]
|
|
@@ -162,6 +165,13 @@ type ReachOf<P, T, V> = [ContainerOf<P, T>] extends [never] ? DimensionOf<P, T>
|
|
|
162
165
|
*/
|
|
163
166
|
type RowType<R> = [R] extends [Selectable<R>] ? [Selectable<R>] extends [R] ? R : Selectable<R> : Selectable<R>
|
|
164
167
|
|
|
168
|
+
/** The file columns of table `T`'s policy (`files: { <column>: '<kind>' }`). */
|
|
169
|
+
type FileColumnsOf<P, T> = PolicyOf<P, T> extends { readonly files: infer F } ? keyof F & string : never
|
|
170
|
+
|
|
171
|
+
/** A row or write of a table with file columns: each holds a file reference, `null` where the column is nullable. */
|
|
172
|
+
type WithFiles<R, Files extends string> = [Files] extends [never] ? R
|
|
173
|
+
: { [K in keyof R]: K extends Files ? (null extends R[K] ? FileReference | null : FileReference) : R[K] }
|
|
174
|
+
|
|
165
175
|
type ReferenceTableName<P> = { [K in keyof P]: P[K] extends { readonly scope: { readonly reference: string } } ? K : never }[keyof P] & string
|
|
166
176
|
|
|
167
177
|
/**
|
|
@@ -225,6 +235,66 @@ export type Saved<E extends IndexedTable> = E['versioned'] extends true ? { read
|
|
|
225
235
|
/** A versioned table's update and remove take `{ expectVersion }`; another table's take nothing. */
|
|
226
236
|
export type VersionArguments<E extends IndexedTable> = E['versioned'] extends true ? [options: { readonly expectVersion: number }] : []
|
|
227
237
|
|
|
238
|
+
/** The values an insert takes: the columns of the table's `insert` list. */
|
|
239
|
+
type InsertValuesOf<E extends IndexedTable> = { [C in keyof E['insert']]: E['insert'][C] }
|
|
240
|
+
|
|
241
|
+
/** `onConflict` of an insert: the columns of a unique index the migrations create, and what a conflict does. */
|
|
242
|
+
export interface InsertConflict<E extends IndexedTable, M extends 'nothing' | 'update'> {
|
|
243
|
+
readonly onConflict: { readonly columns: readonly (keyof E['row'] & string)[]; readonly do: M }
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* An insert: with `onConflict`, a conflict of its columns writes nothing (`'nothing'`, which answers `{ inserted: false }`)
|
|
248
|
+
* or updates the conflicting row's update columns (`'update'`, which also needs the table's update action).
|
|
249
|
+
*/
|
|
250
|
+
export interface InsertMethod<E extends IndexedTable> {
|
|
251
|
+
(values: InsertValuesOf<E>, options: InsertConflict<E, 'nothing'>): Promise<(Saved<E> & { readonly inserted: true }) | { readonly inserted: false }>
|
|
252
|
+
(values: InsertValuesOf<E>, options: InsertConflict<E, 'update'>): Promise<Saved<E> & { readonly inserted: boolean }>
|
|
253
|
+
// Last, so `Parameters` and `ReturnType`, which read the last signature, read the plain insert.
|
|
254
|
+
(values: InsertValuesOf<E>): Promise<Saved<E>>
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** What a claim takes: how many rows to lease, at most `MAX_LIST_LIMIT`, and conditions on the columns its action reads. */
|
|
258
|
+
export interface ClaimOptions<Row> {
|
|
259
|
+
readonly limit: number
|
|
260
|
+
readonly where?: readonly ConditionTuple<Row>[]
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** A claimed row: the columns of the claim's action, its new `version`, and `ref`, which `tx.db.on(ref)` finishes it through. */
|
|
264
|
+
export type ClaimedRow<T extends string, E extends IndexedTable, A extends string> =
|
|
265
|
+
& RowFor<E, A>
|
|
266
|
+
& { readonly version: number; readonly ref: RowRef<T, A> }
|
|
267
|
+
|
|
268
|
+
type ClaimRefusal<K extends DataKind> = K extends 'query' ? 'claim leases rows, so it runs only in a job'
|
|
269
|
+
: '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'
|
|
270
|
+
|
|
271
|
+
/** `claim` of a queue table, in a job, for a capability of its update action. */
|
|
272
|
+
type Claim<T extends string, E extends IndexedTable, A extends string, K extends DataKind> = K extends 'job'
|
|
273
|
+
? E['queued'] extends true ? [A] extends [E['actions']['update']] ? (options: ClaimOptions<RowFor<E, A>>) => Promise<ClaimedRow<T, E, A>[]>
|
|
274
|
+
: Unavailable<`a claim of ${T} needs its update action '${E['actions']['update'] & string}', and this capability holds '${A}'`>
|
|
275
|
+
: Unavailable<`${T} is not a queue table: declare queue: { attempts, due } in its policy`>
|
|
276
|
+
: Unavailable<ClaimRefusal<K>>
|
|
277
|
+
|
|
278
|
+
type JobWrite = Unavailable<'a job writes inside ctx.transaction((tx) => …): use tx.db there'>
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* `tx.db.on(row.ref)`: one claimed row, finished in the job's transaction. `update` writes the claim action's columns and,
|
|
282
|
+
* with `done: true`, clears `due`; `remove` needs the table's delete action; `requeue` makes the row due again at `at`
|
|
283
|
+
* (now by default) with no attempts.
|
|
284
|
+
*/
|
|
285
|
+
export interface ClaimedRowHandle<T extends string, E extends IndexedTable, A extends string, K extends DataKind> {
|
|
286
|
+
readonly update: K extends 'mutation' ? (
|
|
287
|
+
values: { [C in keyof UpdateValuesOf<E, A>]: UpdateValuesOf<E, A>[C] },
|
|
288
|
+
options: { readonly expectVersion: number; readonly done?: boolean },
|
|
289
|
+
) => Promise<Saved<E>>
|
|
290
|
+
: JobWrite
|
|
291
|
+
readonly remove: K extends 'mutation' ? [A] extends [E['actions']['delete']] ? (options: { readonly expectVersion: number }) => Promise<void>
|
|
292
|
+
: [E['actions']['delete']] extends [never] ? Unavailable<`${T} allows no remove: its policy declares no delete action`>
|
|
293
|
+
: Unavailable<`remove of a claimed ${T} row needs its delete action '${E['actions']['delete'] & string}', and this row was claimed with '${A}'`>
|
|
294
|
+
: JobWrite
|
|
295
|
+
readonly requeue: K extends 'mutation' ? (options?: { readonly at?: Date | string }) => Promise<Saved<E>> : JobWrite
|
|
296
|
+
}
|
|
297
|
+
|
|
228
298
|
/** The actions table `E` names: its read, insert, update and delete actions, and each column's own write action. */
|
|
229
299
|
type NamedActions<E extends IndexedTable> = (E['actions'][keyof E['actions']] | keyof E['writes']) & string
|
|
230
300
|
|
|
@@ -305,7 +375,7 @@ export interface TargetRow<T extends string, E extends IndexedTable, A extends s
|
|
|
305
375
|
* by the scope's value.
|
|
306
376
|
*/
|
|
307
377
|
export interface OwnerRow<T extends string, E extends IndexedTable, A extends string, K extends DataKind> {
|
|
308
|
-
readonly insert: Write<T, E, 'insert', A, K,
|
|
378
|
+
readonly insert: Write<T, E, 'insert', A, K, InsertMethod<E>>
|
|
309
379
|
}
|
|
310
380
|
|
|
311
381
|
/** A dimension no table owns has no row of its own; its subtree tables are still reachable. */
|
|
@@ -320,7 +390,7 @@ export interface UnownedTarget<D extends string> {
|
|
|
320
390
|
export interface ChildRows<T extends string, E extends IndexedTable, A extends string, K extends DataKind> {
|
|
321
391
|
readonly list: Read<T, E, A, (options?: ListOptions<RowFor<E, A>>) => Promise<ListPage<RowFor<E, A>>>>
|
|
322
392
|
readonly get: Read<T, E, A, (key: string) => Promise<RowFor<E, A>>>
|
|
323
|
-
readonly insert: Write<T, E, 'insert', A, K,
|
|
393
|
+
readonly insert: Write<T, E, 'insert', A, K, InsertMethod<E>>
|
|
324
394
|
readonly update: Update<
|
|
325
395
|
T,
|
|
326
396
|
E,
|
|
@@ -334,7 +404,7 @@ export interface ChildRows<T extends string, E extends IndexedTable, A extends s
|
|
|
334
404
|
/** A dimension table the target contains: listed in it, and inserted into it by a mutation of its insert action. */
|
|
335
405
|
export interface ContainedRows<T extends string, E extends IndexedTable, A extends string, K extends DataKind> {
|
|
336
406
|
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,
|
|
407
|
+
readonly insert: Write<T, E, 'insert', A, K, InsertMethod<E>>
|
|
338
408
|
}
|
|
339
409
|
|
|
340
410
|
type SubtreeTables<Ix extends AnyDbIndex, D extends string, A extends string, K extends DataKind, F extends TargetForm> =
|
|
@@ -350,8 +420,9 @@ export type ScopeTarget<Ix extends AnyDbIndex, D extends string, A extends strin
|
|
|
350
420
|
& ([OwnerOf<Ix, D>] extends [never] ? UnownedTarget<D> : TargetRow<OwnerOf<Ix, D>, Ix['tables'][OwnerOf<Ix, D>], A, K, F>)
|
|
351
421
|
& SubtreeTables<Ix, D, A, K, F>
|
|
352
422
|
|
|
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> {
|
|
423
|
+
/** A table listed in an `over` endpoint's dimension, read through its `Within`; a job claims a queue table's rows through it. */
|
|
424
|
+
export interface WithinRows<T extends string, E extends IndexedTable, A extends string, K extends DataKind> {
|
|
425
|
+
readonly claim: Claim<T, E, A, K>
|
|
355
426
|
readonly list: A extends E['actions']['read'] ? (options?: ListOptions<RowFor<E, A>>) => Promise<ListPage<RowFor<E, A>>>
|
|
356
427
|
: Unavailable<ListRefusal<T, E, A>>
|
|
357
428
|
readonly count: A extends E['actions']['read'] ? (options?: CountOptions<RowFor<E, A>>) => Promise<number> : Unavailable<ListRefusal<T, E, A>>
|
|
@@ -369,8 +440,8 @@ type ListRefusal<T extends string, E extends IndexedTable, A extends string> = `
|
|
|
369
440
|
* `ctx.db.within` of an `over` endpoint: every table whose containment chain holds its dimension (tasks two levels
|
|
370
441
|
* down through their stamped `org_id`), and a top-level table its own; child tables included.
|
|
371
442
|
*/
|
|
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>
|
|
443
|
+
export type WithinTables<Ix extends AnyDbIndex, D extends string, A extends string, K extends DataKind> = {
|
|
444
|
+
readonly [T in WithinIn<Ix, D>]: WithinRows<T, Ix['tables'][T], A, K>
|
|
374
445
|
}
|
|
375
446
|
|
|
376
447
|
/** An application table under the capability of an `app` endpoint of action `A`, in a site of kind `K`. */
|
|
@@ -387,7 +458,9 @@ export interface ApplicationRows<T extends string, E extends IndexedTable, A ext
|
|
|
387
458
|
readonly getForUpdate: K extends 'mutation' ? Read<T, E, A, (key: string) => Promise<RowFor<E, A>>>
|
|
388
459
|
: K extends 'job' ? Unavailable<'a job writes inside ctx.transaction((tx) => …): use tx.db there'>
|
|
389
460
|
: Unavailable<'getForUpdate locks the row, so it runs only in a mutation: declare the endpoint with r.mutation'>
|
|
390
|
-
|
|
461
|
+
/** Leases due rows of a queue table: a job's drain, before its transactions. */
|
|
462
|
+
readonly claim: Claim<T, E, A, K>
|
|
463
|
+
readonly insert: Write<T, E, 'insert', A, K, InsertMethod<E>>
|
|
391
464
|
readonly update: Update<
|
|
392
465
|
T,
|
|
393
466
|
E,
|
|
@@ -398,6 +471,59 @@ export interface ApplicationRows<T extends string, E extends IndexedTable, A ext
|
|
|
398
471
|
readonly remove: Write<T, E, 'delete', A, K, (key: string, ...version: VersionArguments<E>) => Promise<void>>
|
|
399
472
|
}
|
|
400
473
|
|
|
474
|
+
/**
|
|
475
|
+
* A public procedure's insert: it answers the new row's key (and version), which the data layer generated, and reads
|
|
476
|
+
* no column back; a conflict of `onConflict`'s columns writes nothing.
|
|
477
|
+
*/
|
|
478
|
+
export interface PublicInsertMethod<E extends IndexedTable> {
|
|
479
|
+
(values: InsertValuesOf<E>, options: InsertConflict<E, 'nothing'>): Promise<(Saved<E> & { readonly inserted: true }) | { readonly inserted: false }>
|
|
480
|
+
(values: InsertValuesOf<E>): Promise<Saved<E>>
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
type PublicRefusal<Op extends string> = Unavailable<`a public procedure does not ${Op} a row: it inserts, and reads under the table's read action`>
|
|
484
|
+
|
|
485
|
+
/** A public procedure updates and removes only a versioned row, by an id a verified signed token of its request carries. */
|
|
486
|
+
type PublicVersioned<T extends string, E extends IndexedTable, Member> = E['versioned'] extends true ? Member
|
|
487
|
+
: Unavailable<`a public procedure updates and removes only a versioned row: declare version in the policy of ${T}`>
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* An application table of a public procedure of action `A`, in a site of kind `K`: read under its read action only,
|
|
491
|
+
* inserted with nothing read back, and updated or removed only by the id a verified signed token of the request carries,
|
|
492
|
+
* under `expectVersion`; never locked or claimed.
|
|
493
|
+
*/
|
|
494
|
+
export interface PublicApplicationRows<T extends string, E extends IndexedTable, A extends string, K extends DataKind> {
|
|
495
|
+
readonly list: ApplicationRows<T, E, A, K>['list']
|
|
496
|
+
readonly count: ApplicationRows<T, E, A, K>['count']
|
|
497
|
+
readonly aggregate: ApplicationRows<T, E, A, K>['aggregate']
|
|
498
|
+
readonly get: A extends E['actions']['read'] ? (key: string) => Promise<RowFor<E, A>> : Unavailable<ListRefusal<T, E, A>>
|
|
499
|
+
readonly getForUpdate: PublicRefusal<'lock'>
|
|
500
|
+
readonly claim: PublicRefusal<'claim'>
|
|
501
|
+
/** By the id a verified signed token of the request carries; answers only the new version. */
|
|
502
|
+
readonly update: PublicVersioned<
|
|
503
|
+
T,
|
|
504
|
+
E,
|
|
505
|
+
Update<
|
|
506
|
+
T,
|
|
507
|
+
E,
|
|
508
|
+
A,
|
|
509
|
+
K,
|
|
510
|
+
(
|
|
511
|
+
key: string,
|
|
512
|
+
values: { [C in keyof UpdateValuesOf<E, A>]: UpdateValuesOf<E, A>[C] },
|
|
513
|
+
options: { readonly expectVersion: number },
|
|
514
|
+
) => Promise<{ readonly version: number }>
|
|
515
|
+
>
|
|
516
|
+
>
|
|
517
|
+
/** By the id a verified signed token of the request carries. */
|
|
518
|
+
readonly remove: PublicVersioned<T, E, Write<T, E, 'delete', A, K, (key: string, options: { readonly expectVersion: number }) => Promise<void>>>
|
|
519
|
+
readonly insert: Write<T, E, 'insert', A, K, PublicInsertMethod<E>>
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/** `ctx.db.app('<action>')` of a public procedure: the application tables only, each as `PublicApplicationRows`. */
|
|
523
|
+
export type PublicApplicationTables<Ix extends AnyDbIndex, A extends string, K extends DataKind> = {
|
|
524
|
+
readonly [T in Ix['applications']]: PublicApplicationRows<T, Ix['tables'][T], A, K>
|
|
525
|
+
}
|
|
526
|
+
|
|
401
527
|
/**
|
|
402
528
|
* A dimension or child table under an `app` endpoint of action `A`: every row read-only, with the columns `A` reads
|
|
403
529
|
* (`RowFor`), or only how many rows match. A count-only action has no `aggregate`: a group or a minimum is a value.
|
|
@@ -477,30 +603,28 @@ export type UnscopedMode = 'read' | 'write'
|
|
|
477
603
|
export type UnscopedDeclaration<Table extends string, Mode extends UnscopedMode> = { readonly [T in Table]?: Mode }
|
|
478
604
|
|
|
479
605
|
/**
|
|
480
|
-
* The tables of `U` whose mode may be `'write'` that no site writes: a reference table
|
|
481
|
-
*
|
|
606
|
+
* The tables of `U` whose mode may be `'write'` that no site writes: a reference table. A union or optional mode that
|
|
607
|
+
* admits `'write'` counts, because the declaration it types may hold it.
|
|
482
608
|
*/
|
|
483
609
|
type ReadOnlyWritten<Ix extends AnyDbIndex, U> = {
|
|
484
|
-
[T in keyof U & TableName<Ix>]: 'write' extends U[T] ? Ix['tables'][T]['mode'] extends 'reference'
|
|
610
|
+
[T in keyof U & TableName<Ix>]: 'write' extends U[T] ? Ix['tables'][T]['mode'] extends 'reference' ? T : never : never
|
|
485
611
|
}[keyof U & TableName<Ix>]
|
|
486
612
|
|
|
487
|
-
type ReadOnlyMessage<T extends string
|
|
488
|
-
|
|
489
|
-
: `${T} is a form's inbox, which only its form writes: declare unscoped: { ${T}: 'read' }`
|
|
613
|
+
type ReadOnlyMessage<T extends string> =
|
|
614
|
+
`${T} is a reference table, which no site writes: its rows come from a migration, so declare unscoped: { ${T}: 'read' }`
|
|
490
615
|
|
|
491
|
-
/** An `unscoped` declaration's `'write'` of a
|
|
616
|
+
/** An `unscoped` declaration's `'write'` of a reference table, refused at its own line. */
|
|
492
617
|
export type ReadOnlyUnscoped<Ix extends AnyDbIndex, U> = [ReadOnlyWritten<Ix, U>] extends [never] ? unknown
|
|
493
|
-
: { readonly [T in ReadOnlyWritten<Ix, U>]: Unavailable<ReadOnlyMessage<T
|
|
618
|
+
: { readonly [T in ReadOnlyWritten<Ix, U>]: Unavailable<ReadOnlyMessage<T>> }
|
|
494
619
|
|
|
495
|
-
type KeylessMessage<T extends string
|
|
496
|
-
: `${T} is a form's inbox, which has no key: read it with list`
|
|
620
|
+
type KeylessMessage<T extends string> = `${T} is a reference table, which has no key: read it with list`
|
|
497
621
|
|
|
498
622
|
/** A table a site declared `unscoped`: its scope predicate is `true`, and `'read'` writes nothing. */
|
|
499
623
|
export interface UnscopedRows<T extends string, E extends IndexedTable, M extends UnscopedMode, K extends DataKind> {
|
|
500
624
|
list(options?: ListOptions<TableRow<E>>): Promise<ListPage<TableRow<E>>>
|
|
501
625
|
count(options?: CountOptions<TableRow<E>>): Promise<number>
|
|
502
626
|
aggregate<const S extends AggregateSpec<TableRow<E>>>(spec: S): Promise<AggregateRow<TableRow<E>, S>[]>
|
|
503
|
-
readonly get: E['mode'] extends 'reference'
|
|
627
|
+
readonly get: E['mode'] extends 'reference' ? Unavailable<KeylessMessage<T>> : (key: string) => Promise<TableRow<E>>
|
|
504
628
|
readonly insert: UnscopedWrite<T, M, K, 'insert', (values: { [C in keyof UnscopedInsert<E>]: UnscopedInsert<E>[C] }) => Promise<Saved<E>>>
|
|
505
629
|
readonly update: UnscopedWrite<
|
|
506
630
|
T,
|