@fabrika/db 0.9.0-dev.1 → 0.9.0-dev.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +13 -12
- package/src/testing.ts +1 -0
- package/types/src/admissions.d.ts +21 -0
- package/types/src/builtins.d.ts +42 -0
- package/types/src/capability.d.ts +114 -0
- package/types/src/codegen.d.ts +52 -0
- package/types/src/database.d.ts +124 -0
- package/types/src/description.d.ts +109 -0
- package/types/src/errors.d.ts +97 -0
- package/types/src/file-columns.d.ts +37 -0
- package/types/src/file-references.d.ts +27 -0
- package/types/src/framework-tables.d.ts +59 -0
- package/types/src/framework.d.ts +32 -0
- package/types/src/guard.d.ts +23 -0
- package/types/src/identifier.d.ts +4 -0
- package/types/src/index.d.ts +10 -0
- package/types/src/job-leases.d.ts +84 -0
- package/types/src/links.d.ts +13 -0
- package/types/src/migrations.d.ts +25 -0
- package/types/src/nodes.d.ts +62 -0
- package/types/src/orm-types.d.ts +638 -0
- package/types/src/orm.d.ts +77 -0
- package/types/src/policy.d.ts +350 -0
- package/types/src/production.d.ts +5 -0
- package/types/src/registry.d.ts +35 -0
- package/types/src/sql.d.ts +59 -0
- package/types/src/store.d.ts +334 -0
- package/types/src/stored-files.d.ts +98 -0
- package/types/src/tables.d.ts +11 -0
- package/types/src/temporal.d.ts +12 -0
- package/types/src/testing.d.ts +126 -0
- package/types/src/vocabulary-tables.d.ts +225 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { InvocationTransaction, Session } from './database';
|
|
2
|
+
import { type FileBinding } from './file-columns';
|
|
3
|
+
import type { DataKind, TargetForm, UnscopedMode } from './orm-types';
|
|
4
|
+
import { type TableModels } from './policy';
|
|
5
|
+
import { type SqlSite, type SqlTag } from './sql';
|
|
6
|
+
import { type DataLayerRuntime, type TableContext } from './store';
|
|
7
|
+
/** A list page holds this many rows unless the caller asks for fewer, and never more than `MAX_LIST_LIMIT`. */
|
|
8
|
+
export declare const DEFAULT_LIST_LIMIT = 50;
|
|
9
|
+
export declare const MAX_LIST_LIMIT = 500;
|
|
10
|
+
/** What every handle of one site, in one invocation, is made for. */
|
|
11
|
+
export interface DataSite {
|
|
12
|
+
/** A query reads; a mutation also writes and locks; a job reads, and writes only in its fenced transaction. */
|
|
13
|
+
readonly kind: DataKind;
|
|
14
|
+
/** The tables the site declares `unscoped`, each in its mode. */
|
|
15
|
+
readonly unscoped: Readonly<Record<string, UnscopedMode>>;
|
|
16
|
+
/** Who an unscoped insert is stamped with (`createdBy`): no capability carries a principal for it. */
|
|
17
|
+
readonly principal: string;
|
|
18
|
+
/** A job's schedule period, in ms: a queue claim's first backoff, doubled at each later claim of the row. */
|
|
19
|
+
readonly tickMs?: number;
|
|
20
|
+
/**
|
|
21
|
+
* A public procedure, which anyone calls: its `ctx.db.app` inserts and reads nothing back, reads only under a table's
|
|
22
|
+
* read action, and reaches application tables only. It updates or removes only a versioned row whose id a verified
|
|
23
|
+
* signed token of its request carries (`carried`); it never locks or claims a row.
|
|
24
|
+
*/
|
|
25
|
+
readonly public?: true;
|
|
26
|
+
/**
|
|
27
|
+
* Where the site's file references belong: each file column it reads answers one, and a file column it writes takes
|
|
28
|
+
* only one it holds. Absent where the framework serves no file; the columns then read as ids and refuse every write.
|
|
29
|
+
*/
|
|
30
|
+
readonly files?: FileBinding;
|
|
31
|
+
/** The texts the verified signed tokens of a public procedure's request carry: the row ids it may update or remove. */
|
|
32
|
+
readonly carried?: ReadonlySet<string>;
|
|
33
|
+
/** Throws unless the work running now is the invocation these handles were made for. */
|
|
34
|
+
check(): void;
|
|
35
|
+
/** The session every statement of the site runs in. */
|
|
36
|
+
session(): Promise<Session>;
|
|
37
|
+
}
|
|
38
|
+
/** The handles of one site; the framework composes `ctx.db` from them by the site's access form. */
|
|
39
|
+
export interface DataHandles {
|
|
40
|
+
/** `ctx.db.target` (or `ctx.db.created`, or a job's `ctx.db.on`/`.in`): a `Ref`'s row and subtree. */
|
|
41
|
+
target(capability: unknown, form: TargetForm): object;
|
|
42
|
+
/** `ctx.db.within` (or a job's `ctx.db.over`): every table listed in a `Within`'s dimension. */
|
|
43
|
+
within(capability: unknown): object;
|
|
44
|
+
/**
|
|
45
|
+
* `ctx.db.app`. Of an `app` endpoint: each application table, and each dimension and child table, read-only, by the
|
|
46
|
+
* capability's action held application-wide. Of an `on`, `in` or `over` endpoint (a `Ref` or a `Within`): each
|
|
47
|
+
* application table, read-only, every row, where the capability's action is its read action.
|
|
48
|
+
*/
|
|
49
|
+
app(capability: unknown): object;
|
|
50
|
+
/** `ctx.db.reference`. */
|
|
51
|
+
readonly reference: object;
|
|
52
|
+
/** `ctx.db.unscoped`, or `undefined` for a site that declares none. */
|
|
53
|
+
readonly unscoped: object | undefined;
|
|
54
|
+
}
|
|
55
|
+
/** The data side of one bound layer, which only the framework reaches (`@fabrika/db/framework`). */
|
|
56
|
+
export interface DataAccess {
|
|
57
|
+
/** One invocation's transaction over the bound connection; a query's is `READ ONLY`. */
|
|
58
|
+
invocation(readOnly: boolean): InvocationTransaction;
|
|
59
|
+
/** A statement-at-a-time session: what a job's reads outside its transaction run in. */
|
|
60
|
+
direct(): Session;
|
|
61
|
+
/** One transaction, fenced by the lease of the job attempt running now: a job's `ctx.transaction`. */
|
|
62
|
+
transaction<T>(work: (session: Session) => Promise<T>): Promise<T>;
|
|
63
|
+
/** The handles of one site. Throws for a declaration this layer cannot honour. */
|
|
64
|
+
handles(site: DataSite): DataHandles;
|
|
65
|
+
/** `ctx.sql` of a site that declares it, running in the site's session on the application's schema. */
|
|
66
|
+
sql(site: SqlSite): SqlTag;
|
|
67
|
+
}
|
|
68
|
+
/** What `scoped` hands this module: the layer's tables and the way to its runtime. */
|
|
69
|
+
export interface DataLayerParts {
|
|
70
|
+
readonly models: TableModels;
|
|
71
|
+
contextOf(table: string): TableContext;
|
|
72
|
+
runtime(): DataLayerRuntime;
|
|
73
|
+
newSession(runtime: DataLayerRuntime): Session;
|
|
74
|
+
newInvocation(runtime: DataLayerRuntime, readOnly: boolean): InvocationTransaction;
|
|
75
|
+
fenced<T>(runtime: DataLayerRuntime, work: (session: Session) => Promise<T>): Promise<T>;
|
|
76
|
+
}
|
|
77
|
+
export declare function dataAccess(parts: DataLayerParts): DataAccess;
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import type { Insertable, Selectable, SelectType, Updateable } from 'kysely';
|
|
2
|
+
import { type NameSet } from './builtins';
|
|
3
|
+
import type { Unavailable } from './orm-types';
|
|
4
|
+
export type { RowOf } from './orm-types';
|
|
5
|
+
import type { ColumnTypes, DefineVocabularyPolicies, TableVocabulary } from './vocabulary-tables';
|
|
6
|
+
export interface DimensionScope<Col extends string> {
|
|
7
|
+
readonly dimension: string;
|
|
8
|
+
readonly key: Col;
|
|
9
|
+
readonly in?: {
|
|
10
|
+
readonly dimension: string;
|
|
11
|
+
readonly column: Col;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export interface ChildScope<Table extends string, Col extends string> {
|
|
15
|
+
readonly child: Table;
|
|
16
|
+
readonly key: Col;
|
|
17
|
+
readonly column: Col;
|
|
18
|
+
readonly inherit: readonly Col[];
|
|
19
|
+
}
|
|
20
|
+
export interface ReferenceScope {
|
|
21
|
+
readonly reference: string;
|
|
22
|
+
}
|
|
23
|
+
/** Rows of the whole application, in no dimension; the value says why. `key` defaults to `id`. */
|
|
24
|
+
export interface ApplicationScope<Col extends string> {
|
|
25
|
+
readonly app: string;
|
|
26
|
+
readonly key?: Col;
|
|
27
|
+
}
|
|
28
|
+
/** One action, or a list of them: a table that staff and a public procedure both read names both actions. */
|
|
29
|
+
export type TableAction<A extends string> = A | readonly A[];
|
|
30
|
+
export interface TableActions<A extends string> {
|
|
31
|
+
readonly read: TableAction<A>;
|
|
32
|
+
readonly insert?: TableAction<A>;
|
|
33
|
+
readonly update?: TableAction<A>;
|
|
34
|
+
readonly delete?: TableAction<A>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* A queue table's columns (ADR-0060's drain): `due` is when a row is next claimed, `null` once it is done; `attempts`
|
|
38
|
+
* counts its claims, and a row with `maxAttempts` (default 10) of them is no longer claimed.
|
|
39
|
+
*/
|
|
40
|
+
export interface QueuePolicy<Col extends string> {
|
|
41
|
+
readonly attempts: Col;
|
|
42
|
+
readonly due: Col;
|
|
43
|
+
readonly maxAttempts?: number;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* A dimension or child table's actions: also those besides `read` with which an `app` endpoint lists every row,
|
|
47
|
+
* read-only (`appRead`), and those with which it only counts and aggregates every row (`appCount`).
|
|
48
|
+
*/
|
|
49
|
+
export interface RecordActions<A extends string> extends TableActions<A> {
|
|
50
|
+
readonly appRead?: readonly A[];
|
|
51
|
+
readonly appCount?: readonly A[];
|
|
52
|
+
}
|
|
53
|
+
interface WritablePolicy<Col extends string, A extends string, V extends Col, Acts = TableActions<A>> {
|
|
54
|
+
readonly actions: Acts;
|
|
55
|
+
readonly insert?: readonly Col[];
|
|
56
|
+
readonly update?: readonly Col[];
|
|
57
|
+
/** An INTEGER (int4) column: Postgres returns a BIGINT as a string, so it cannot be compared as a number. */
|
|
58
|
+
readonly version?: V;
|
|
59
|
+
readonly createdBy?: Col;
|
|
60
|
+
readonly columns?: ColumnPolicies<Col, A>;
|
|
61
|
+
/** A queue table: its rows are claimed (`claim`) and finished through `row.ref`; it needs `version` and an update action. */
|
|
62
|
+
readonly queue?: QueuePolicy<Col>;
|
|
63
|
+
/**
|
|
64
|
+
* The file columns, each with the file kind it holds: a `uuid` foreign key to `fabrika_files`, written only with a
|
|
65
|
+
* file reference. The table's actions are the file's authority.
|
|
66
|
+
*/
|
|
67
|
+
readonly files?: FilePolicies<Col>;
|
|
68
|
+
}
|
|
69
|
+
/** A table's file columns: column → the name of a file kind of the contract (`c.files`). */
|
|
70
|
+
export type FilePolicies<Col extends string> = {
|
|
71
|
+
readonly [C in Col]?: string;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* A column's own actions. `read`: only a capability of exactly that action reads, filters or sorts by the column;
|
|
75
|
+
* without one it falls back to the table's read action. `write`: only a capability of that action updates the column,
|
|
76
|
+
* and the table's update action does not, so the column is not in `update`.
|
|
77
|
+
*/
|
|
78
|
+
export type ColumnPolicies<Col extends string, A extends string> = {
|
|
79
|
+
readonly [C in Col]?: ColumnPolicy<A>;
|
|
80
|
+
};
|
|
81
|
+
/** A column's own read action, its own write action, or both. */
|
|
82
|
+
export interface ColumnPolicy<A extends string> {
|
|
83
|
+
readonly read?: A;
|
|
84
|
+
readonly write?: A;
|
|
85
|
+
}
|
|
86
|
+
/** A table no handler writes (a reference table) declares column read actions only. */
|
|
87
|
+
export type ColumnReadPolicies<Col extends string, A extends string> = {
|
|
88
|
+
readonly [C in Col]?: {
|
|
89
|
+
readonly read: A;
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
export interface DimensionPolicy<Col extends string, A extends string, V extends Col = Col> extends WritablePolicy<Col, A, V, RecordActions<A>> {
|
|
93
|
+
readonly scope: DimensionScope<Col>;
|
|
94
|
+
}
|
|
95
|
+
export interface ChildPolicy<Table extends string, Col extends string, A extends string, V extends Col = Col> extends WritablePolicy<Col, A, V, RecordActions<A>> {
|
|
96
|
+
readonly scope: ChildScope<Table, Col>;
|
|
97
|
+
}
|
|
98
|
+
/** An application table: written like a dimension table, by an `app` endpoint whose action is the operation's. */
|
|
99
|
+
export interface ApplicationPolicy<Col extends string, A extends string, V extends Col = Col> extends WritablePolicy<Col, A, V> {
|
|
100
|
+
readonly scope: ApplicationScope<Col>;
|
|
101
|
+
}
|
|
102
|
+
interface ReadOnlyPolicy<Col extends string, A extends string> {
|
|
103
|
+
readonly insert?: never;
|
|
104
|
+
readonly update?: never;
|
|
105
|
+
readonly version?: never;
|
|
106
|
+
readonly createdBy?: never;
|
|
107
|
+
readonly queue?: never;
|
|
108
|
+
readonly files?: never;
|
|
109
|
+
readonly columns?: ColumnReadPolicies<Col, A>;
|
|
110
|
+
}
|
|
111
|
+
export interface ReferencePolicy<Col extends string, A extends string> extends ReadOnlyPolicy<Col, A> {
|
|
112
|
+
readonly scope: ReferenceScope;
|
|
113
|
+
readonly actions: {
|
|
114
|
+
readonly read: TableAction<A>;
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/** The columns a version can live in: those whose generated select type is `number`, an INTEGER (int4). */
|
|
118
|
+
export type VersionColumn<Row> = {
|
|
119
|
+
[K in keyof Row & string]: SelectType<Row[K]> extends number ? K : never;
|
|
120
|
+
}[keyof Row & string];
|
|
121
|
+
/** A foreign key within the schema, as codegen reads it: `columns[i]` references column `references[i]` of `table`. */
|
|
122
|
+
export interface ForeignKey<Col extends string = string, Table extends string = string> {
|
|
123
|
+
readonly columns: readonly Col[];
|
|
124
|
+
readonly table: Table;
|
|
125
|
+
readonly references: readonly string[];
|
|
126
|
+
}
|
|
127
|
+
/** One table of the generated column list: its column names, and its foreign keys to tables of the schema. */
|
|
128
|
+
export interface TableColumns<Col extends string = string, Table extends string = string> {
|
|
129
|
+
readonly columns: readonly Col[];
|
|
130
|
+
readonly foreignKeys: readonly ForeignKey<Col, Table>[];
|
|
131
|
+
/** The columns of Postgres type `date`, which a read answers as `YYYY-MM-DD` text; absent when there are none. */
|
|
132
|
+
readonly dates?: readonly Col[];
|
|
133
|
+
/** The `timestamp` and `timestamptz` columns, which a write takes as a `Date` or strict ISO 8601 text; absent when none. */
|
|
134
|
+
readonly timestamps?: readonly Col[];
|
|
135
|
+
}
|
|
136
|
+
/** The columns and foreign keys of every table, generated next to the schema types. */
|
|
137
|
+
export type ColumnList<DB> = {
|
|
138
|
+
readonly [T in keyof DB & string]: TableColumns<keyof DB[T] & string, keyof DB & string>;
|
|
139
|
+
};
|
|
140
|
+
export interface Container {
|
|
141
|
+
readonly dimension: string;
|
|
142
|
+
readonly column: string;
|
|
143
|
+
}
|
|
144
|
+
export type ScopeModel = {
|
|
145
|
+
readonly mode: 'dimension';
|
|
146
|
+
readonly dimension: string;
|
|
147
|
+
readonly key: string;
|
|
148
|
+
readonly container: Container | undefined;
|
|
149
|
+
} | {
|
|
150
|
+
readonly mode: 'child';
|
|
151
|
+
readonly parent: string;
|
|
152
|
+
readonly key: string;
|
|
153
|
+
readonly column: string;
|
|
154
|
+
readonly inherit: readonly string[];
|
|
155
|
+
} | {
|
|
156
|
+
readonly mode: 'reference';
|
|
157
|
+
readonly reason: string;
|
|
158
|
+
} | {
|
|
159
|
+
readonly mode: 'app';
|
|
160
|
+
readonly reason: string;
|
|
161
|
+
readonly key: string;
|
|
162
|
+
};
|
|
163
|
+
/** Each operation's actions: any one of them allows it. `read` names at least one; an empty list allows no operation. */
|
|
164
|
+
export interface ActionModel {
|
|
165
|
+
readonly read: readonly string[];
|
|
166
|
+
readonly insert: readonly string[];
|
|
167
|
+
readonly update: readonly string[];
|
|
168
|
+
readonly delete: readonly string[];
|
|
169
|
+
/** A dimension or child table's actions besides `read` with which an `app` endpoint reads every row, read-only. */
|
|
170
|
+
readonly appRead: readonly string[];
|
|
171
|
+
/** A dimension or child table's actions with which an `app` endpoint counts and aggregates every row, and reads none. */
|
|
172
|
+
readonly appCount: readonly string[];
|
|
173
|
+
}
|
|
174
|
+
export interface QueueModel {
|
|
175
|
+
readonly attempts: string;
|
|
176
|
+
readonly due: string;
|
|
177
|
+
readonly maxAttempts: number;
|
|
178
|
+
}
|
|
179
|
+
/** How many claims a queue row takes before it is no longer claimed, unless its policy states `maxAttempts`. */
|
|
180
|
+
export declare const DEFAULT_MAX_ATTEMPTS = 10;
|
|
181
|
+
/** One table's policy, checked against its column list. */
|
|
182
|
+
export interface TableModel {
|
|
183
|
+
readonly name: string;
|
|
184
|
+
readonly scope: ScopeModel;
|
|
185
|
+
readonly actions: ActionModel;
|
|
186
|
+
/** Every column the table has. */
|
|
187
|
+
readonly columns: NameSet;
|
|
188
|
+
/** The columns every read action of the table reads, in column-list order. */
|
|
189
|
+
readonly visible: readonly string[];
|
|
190
|
+
readonly insert: NameSet;
|
|
191
|
+
readonly update: NameSet;
|
|
192
|
+
readonly version: string | undefined;
|
|
193
|
+
readonly createdBy: string | undefined;
|
|
194
|
+
readonly queue: QueueModel | undefined;
|
|
195
|
+
/** The file columns, each with its file kind: a reference to `fabrika_files`, written only with a file reference. */
|
|
196
|
+
readonly files: Readonly<Record<string, string>>;
|
|
197
|
+
/** The columns that declare their own read action, as declared (also those equal to the table's). */
|
|
198
|
+
readonly columnReads: Readonly<Record<string, string>>;
|
|
199
|
+
/** The columns that declare their own write action: only a capability of it updates them. */
|
|
200
|
+
readonly columnWrites: Readonly<Record<string, string>>;
|
|
201
|
+
/** The columns of Postgres type `date`, read as `YYYY-MM-DD` text. */
|
|
202
|
+
readonly dates: NameSet;
|
|
203
|
+
/** The `timestamp` and `timestamptz` columns: a write's text must be strict ISO 8601 with an offset. */
|
|
204
|
+
readonly timestamps: NameSet;
|
|
205
|
+
/** The table's foreign keys, from the column list; a containment column must be one to its container's key. */
|
|
206
|
+
readonly foreignKeys: readonly ForeignKey[];
|
|
207
|
+
/**
|
|
208
|
+
* The containment links this table carries a column for, nearest first: a dimension table's container and
|
|
209
|
+
* the container's own chain; a child's parent and the parent's chain. Every predicate of a Ref covers all.
|
|
210
|
+
*/
|
|
211
|
+
readonly chain: readonly Container[];
|
|
212
|
+
}
|
|
213
|
+
/** Every table's model by name; read it through `modelOf`, never by a plain property access. */
|
|
214
|
+
export type TableModels = Readonly<Record<string, TableModel>>;
|
|
215
|
+
/** The validated policies of every table; carries the schema and the literal policy types to the store. */
|
|
216
|
+
export declare class Tables<DB, P> {
|
|
217
|
+
#private;
|
|
218
|
+
constructor(models: TableModels);
|
|
219
|
+
}
|
|
220
|
+
export declare function tableModels<DB, P>(tables: Tables<DB, P>): TableModels;
|
|
221
|
+
/** Whether `value` is what `defineTables` returned, rather than another form of the same policies. */
|
|
222
|
+
export declare function isTables<DB, P, Other extends object>(value: Tables<DB, P> | Other): value is Tables<DB, P>;
|
|
223
|
+
export declare function modelOf(models: TableModels, table: string): TableModel | undefined;
|
|
224
|
+
/**
|
|
225
|
+
* Declares the access policy of every table, under the application's vocabulary:
|
|
226
|
+
* `defineTables(vocabulary, columns)({ notes: { scope: …, actions: … }, … })`. `columns` is the generated column list,
|
|
227
|
+
* which carries the schema's row types; a list without them makes the policies uncallable. The action and dimension
|
|
228
|
+
* names are the vocabulary's, and each table's containment comes from it: a key defaults to `id`, a container column
|
|
229
|
+
* to `<container>_id`, a child's column to `<parent dimension>_id`, and its inherited chain is its parent's.
|
|
230
|
+
*/
|
|
231
|
+
export declare function defineTables<const V extends TableVocabulary, C extends object>(vocabulary: V, columns: C): VocabularyTables<V, C>;
|
|
232
|
+
/** `defineTables(vocabulary, columns)` answers the policies' builder, or names the fix for an untyped column list. */
|
|
233
|
+
export type VocabularyTables<V, C> = [ColumnTypes<C>] extends [never] ? Unavailable<'the column list carries no row types: import columns from the generated src/db/schema.generated.ts (fabrika db codegen)'> : DefineVocabularyPolicies<ColumnTypes<C>, V>;
|
|
234
|
+
/**
|
|
235
|
+
* The same policies read from values nobody type-checked: `src/db/tables.ts` and its vocabulary as the static reader
|
|
236
|
+
* has them from a commit. Every shape `defineTables` refuses at runtime is refused here, through the same parse.
|
|
237
|
+
*/
|
|
238
|
+
export declare function readTables(columns: unknown, policies: unknown, vocabulary: unknown): Tables<unknown, unknown>;
|
|
239
|
+
/**
|
|
240
|
+
* The columns a capability of `action` updates in `model`: the update list for the table's update action, the
|
|
241
|
+
* columns that declare it as their own write action for any other; none for an action that writes nothing here.
|
|
242
|
+
*/
|
|
243
|
+
export declare function updatableBy(model: TableModel, action: string): NameSet;
|
|
244
|
+
/** Whether the table names `action`: one of its table actions, or a column's own write action. */
|
|
245
|
+
export declare function namesAction(model: TableModel, action: string): boolean;
|
|
246
|
+
/**
|
|
247
|
+
* The columns a capability of `action` reads: every one that declares no read action of its own, or exactly this
|
|
248
|
+
* one. A capability reads its own row, so a write `Ref` sees the columns of its action and none of another's.
|
|
249
|
+
*/
|
|
250
|
+
export declare function visibleFor(model: TableModel, action: string): readonly string[];
|
|
251
|
+
/** Every action that updates a column of `model`: its update action and each column's own write action. */
|
|
252
|
+
export declare function updateActions(model: TableModel): readonly string[];
|
|
253
|
+
/** Whether `names` holds `name`: an operation's actions, the one way a policy check reads a list. */
|
|
254
|
+
export declare function includes(names: readonly string[], name: unknown): boolean;
|
|
255
|
+
type PolicyOf<P, T> = T extends keyof P ? P[T] : never;
|
|
256
|
+
type ScopeOf<P, T> = PolicyOf<P, T> extends {
|
|
257
|
+
readonly scope: infer S;
|
|
258
|
+
} ? S : never;
|
|
259
|
+
export type Operation = 'read' | 'insert' | 'update' | 'delete';
|
|
260
|
+
/** The action, or the union of the actions, of operation `Op` on table `T`; `never` when the policy names none. */
|
|
261
|
+
export type ActionOf<P, T, Op extends Operation> = PolicyOf<P, T> extends {
|
|
262
|
+
readonly actions: infer Acts;
|
|
263
|
+
} ? Acts extends {
|
|
264
|
+
readonly [K in Op]: infer A;
|
|
265
|
+
} ? A extends string ? A : A extends readonly (infer L extends string)[] ? L : never : never : never;
|
|
266
|
+
export type DimensionOf<P, T> = ScopeOf<P, T> extends {
|
|
267
|
+
readonly dimension: infer D extends string;
|
|
268
|
+
} ? D : never;
|
|
269
|
+
export type ContainerOf<P, T> = ScopeOf<P, T> extends {
|
|
270
|
+
readonly in: {
|
|
271
|
+
readonly dimension: infer D extends string;
|
|
272
|
+
};
|
|
273
|
+
} ? D : never;
|
|
274
|
+
/** The dimension that lists and inserts of T are scoped by: its container, or for a top-level table its own. */
|
|
275
|
+
export type ListDimensionOf<P, T> = [ContainerOf<P, T>] extends [never] ? DimensionOf<P, T> : ContainerOf<P, T>;
|
|
276
|
+
export type ParentOf<P, T> = ScopeOf<P, T> extends {
|
|
277
|
+
readonly child: infer C extends string;
|
|
278
|
+
} ? C : never;
|
|
279
|
+
export type DimensionTableName<P> = {
|
|
280
|
+
[K in keyof P]: P[K] extends {
|
|
281
|
+
readonly scope: {
|
|
282
|
+
readonly dimension: string;
|
|
283
|
+
};
|
|
284
|
+
} ? K : never;
|
|
285
|
+
}[keyof P] & string;
|
|
286
|
+
export type ChildTableName<P> = {
|
|
287
|
+
[K in keyof P]: P[K] extends {
|
|
288
|
+
readonly scope: {
|
|
289
|
+
readonly child: string;
|
|
290
|
+
};
|
|
291
|
+
} ? K : never;
|
|
292
|
+
}[keyof P] & string;
|
|
293
|
+
export type ApplicationTableName<P> = {
|
|
294
|
+
[K in keyof P]: P[K] extends {
|
|
295
|
+
readonly scope: {
|
|
296
|
+
readonly app: string;
|
|
297
|
+
};
|
|
298
|
+
} ? K : never;
|
|
299
|
+
}[keyof P] & string;
|
|
300
|
+
type ListedColumns<P, T, Field extends 'insert' | 'update'> = PolicyOf<P, T> extends {
|
|
301
|
+
readonly [K in Field]: readonly (infer C extends string)[];
|
|
302
|
+
} ? C : never;
|
|
303
|
+
type Flat<T> = {
|
|
304
|
+
[K in keyof T]: T[K];
|
|
305
|
+
};
|
|
306
|
+
type ColumnPoliciesOf<P, T> = PolicyOf<P, T> extends {
|
|
307
|
+
readonly columns: infer C;
|
|
308
|
+
} ? C : never;
|
|
309
|
+
type TableReadAction<P, T> = PolicyOf<P, T> extends {
|
|
310
|
+
readonly scope: {
|
|
311
|
+
readonly read: infer A;
|
|
312
|
+
};
|
|
313
|
+
} ? A : ActionOf<P, T, 'read'>;
|
|
314
|
+
/** Columns whose own read action is not the table's: no read by the table's read action returns them. */
|
|
315
|
+
type RestrictedColumns<P, T> = [ColumnPoliciesOf<P, T>] extends [never] ? never : {
|
|
316
|
+
[K in keyof ColumnPoliciesOf<P, T>]: ColumnPoliciesOf<P, T>[K] extends {
|
|
317
|
+
readonly read: infer R;
|
|
318
|
+
} ? ([TableReadAction<P, T>] extends [R] ? never : K) : never;
|
|
319
|
+
}[keyof ColumnPoliciesOf<P, T>];
|
|
320
|
+
/** A row of table `T` as a store reads it, by the table's read action. */
|
|
321
|
+
export type PolicyRow<DB, P, T> = T extends keyof DB ? Flat<Omit<Selectable<DB[T]>, RestrictedColumns<P, T>>> : never;
|
|
322
|
+
export type InsertValues<DB, P, T> = T extends keyof DB ? Flat<Pick<Insertable<DB[T]>, ListedColumns<P, T, 'insert'> & keyof Insertable<DB[T]>>> : never;
|
|
323
|
+
export type UpdateValues<DB, P, T> = T extends keyof DB ? Flat<Pick<Updateable<DB[T]>, ListedColumns<P, T, 'update'> & keyof Updateable<DB[T]>>> : never;
|
|
324
|
+
/** The values a store inserts into table `T` of a `defineTables` result: `TableInsert<typeof tables, 'deals'>`. */
|
|
325
|
+
export type TableInsert<Tb, T> = Tb extends Tables<infer DB, infer P> ? InsertValues<DB, P, T> : never;
|
|
326
|
+
/** The values a store updates in table `T` of a `defineTables` result: `TableUpdate<typeof tables, 'deals'>`. */
|
|
327
|
+
export type TableUpdate<Tb, T> = Tb extends Tables<infer DB, infer P> ? UpdateValues<DB, P, T> : never;
|
|
328
|
+
/** What a mutation answers: the key, and the new version of a versioned table. It returns no row data. */
|
|
329
|
+
export type Acknowledgement<P, T> = PolicyOf<P, T> extends {
|
|
330
|
+
readonly version: string;
|
|
331
|
+
} ? {
|
|
332
|
+
readonly id: string;
|
|
333
|
+
readonly version: number;
|
|
334
|
+
} : {
|
|
335
|
+
readonly id: string;
|
|
336
|
+
};
|
|
337
|
+
/** A move also answers the object's new containment chain, by dimension. */
|
|
338
|
+
export type MoveAcknowledgement<P, T> = Acknowledgement<P, T> & {
|
|
339
|
+
readonly chain: Readonly<Record<string, string>>;
|
|
340
|
+
};
|
|
341
|
+
/** A table with a version column takes `{ expectVersion }` on update; one without takes nothing. */
|
|
342
|
+
export type VersionArguments<P, T> = PolicyOf<P, T> extends {
|
|
343
|
+
readonly version: string;
|
|
344
|
+
} ? [options: {
|
|
345
|
+
readonly expectVersion: number;
|
|
346
|
+
}] : [];
|
|
347
|
+
/** Children of T that a select of T can join: their read action is T's read action. */
|
|
348
|
+
export type JoinableChildren<DB, P, T> = {
|
|
349
|
+
[K in ChildTableName<P> as ParentOf<P, K> extends T ? (ActionOf<P, K, 'read'> extends ActionOf<P, T, 'read'> ? K : never) : never]: PolicyRow<DB, P, K>;
|
|
350
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Marks this process as one that serves an application; called by the generated entrypoint only. */
|
|
2
|
+
export declare function markProductionProcess(): void;
|
|
3
|
+
export declare function isProductionProcess(): boolean;
|
|
4
|
+
/** Throws when this process serves an application: `what` is a test helper, which production never runs. */
|
|
5
|
+
export declare function refuseInProduction(what: string): void;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Admissions } from './admissions';
|
|
2
|
+
import type { Session } from './database';
|
|
3
|
+
import type { WebhookTables } from './framework-tables';
|
|
4
|
+
import type { JobLeases } from './job-leases';
|
|
5
|
+
import type { DataAccess } from './orm';
|
|
6
|
+
import type { DataLayerRuntime, TableContext, TableProbe } from './store';
|
|
7
|
+
import type { StoredFiles } from './stored-files';
|
|
8
|
+
/**
|
|
9
|
+
* Answers what the framework's access check reads from the database: an object's containers, by dimension. `using` is
|
|
10
|
+
* the session to read in (a job's transaction, which sees the object it just wrote); a statement of its own otherwise.
|
|
11
|
+
*/
|
|
12
|
+
export type Locator = (dimension: string, id: string, using?: Session) => Promise<ReadonlyMap<string, string> | null>;
|
|
13
|
+
/** What the framework writes itself: its own `fabrika_*` tables. Usable once bound. */
|
|
14
|
+
export interface FrameworkTables {
|
|
15
|
+
readonly webhooks: WebhookTables;
|
|
16
|
+
readonly jobs: JobLeases;
|
|
17
|
+
/** A procedure's admission buckets, the challenge tokens it accepted and its byte budget. */
|
|
18
|
+
readonly admissions: Admissions;
|
|
19
|
+
/** Every stored file (`fabrika_files`): staged before its object, claimed at its first reference, swept unreferenced. */
|
|
20
|
+
readonly files: StoredFiles;
|
|
21
|
+
}
|
|
22
|
+
export interface FrameworkSide {
|
|
23
|
+
/** Connects the layer once and answers its locator; a second call throws. */
|
|
24
|
+
bind(runtime: DataLayerRuntime): Locator;
|
|
25
|
+
readonly framework: FrameworkTables;
|
|
26
|
+
/** The untyped handle over one table that `@fabrika/db/testing` hands the isolation tests. */
|
|
27
|
+
probe(table: string): TableProbe;
|
|
28
|
+
/** One table's context, for the framework's own store builders (`defineStore`, `@fabrika/db/testing`). */
|
|
29
|
+
tableContext(table: string, mode: 'dimension' | 'child'): TableContext;
|
|
30
|
+
/** The mini-ORM: invocation transactions and the handles of `ctx.db`; usable once bound. */
|
|
31
|
+
readonly data: DataAccess;
|
|
32
|
+
}
|
|
33
|
+
export declare function register(database: object, side: FrameworkSide): void;
|
|
34
|
+
/** The framework side of an object `scoped` created; undefined for anything else, a structural copy included. */
|
|
35
|
+
export declare function frameworkSideOf(value: unknown): FrameworkSide | undefined;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { z } from 'zod/v4';
|
|
2
|
+
import { type Session } from './database';
|
|
3
|
+
/** What a site declares: `'read'` runs queries only, `'write'` also `INSERT`, `UPDATE` and `DELETE`. */
|
|
4
|
+
export type SqlMode = 'read' | 'write';
|
|
5
|
+
/** A value an interpolation binds: a scalar, a `Date` (bound as ISO text), or a list of strings or numbers (an array). */
|
|
6
|
+
export type SqlValue = string | number | boolean | bigint | Date | null | readonly (string | number)[];
|
|
7
|
+
/** One test of a capability's scope on a table: the column holds the value, or one of the list. */
|
|
8
|
+
export interface ScopeTest {
|
|
9
|
+
readonly column: string;
|
|
10
|
+
readonly value: string | readonly string[];
|
|
11
|
+
}
|
|
12
|
+
/** A capability's predicate on one table: every row (`'all'`), or an OR of AND groups; no group matches nothing. */
|
|
13
|
+
export type ScopeTerms = 'all' | readonly (readonly ScopeTest[])[];
|
|
14
|
+
/** What a fragment's table is to its statement: read, or the target of its UPDATE or DELETE. */
|
|
15
|
+
export type ScopeUse = 'read' | 'update' | 'delete';
|
|
16
|
+
/**
|
|
17
|
+
* The scope of `capability` on `table` for `use`, as the data layer predicates its handles; throws when it does not
|
|
18
|
+
* reach it for that use. `assigned` is the columns an UPDATE's SET names (none when the SET cannot be read with
|
|
19
|
+
* certainty), and `undefined` before a statement is known.
|
|
20
|
+
*/
|
|
21
|
+
export type ScopeOf = (capability: unknown, table: string, use: ScopeUse, assigned?: readonly string[]) => ScopeTerms;
|
|
22
|
+
/** The operation a write statement performs on one of its targets. */
|
|
23
|
+
export type WriteOperation = 'insert' | 'update' | 'delete';
|
|
24
|
+
/** Throws for a table no statement may write with `operation`: a reference table. */
|
|
25
|
+
export type WriteTarget = (table: string, operation: WriteOperation) => void;
|
|
26
|
+
/**
|
|
27
|
+
* `ctx.sql.scope(capability, table, alias?)`: the capability's whole predicate on one table, as SQL with bound values.
|
|
28
|
+
* A fragment is a frozen object with no prototype; what it holds lives in this module's registry, read through
|
|
29
|
+
* methods captured at load, so no object an application builds or changes is ever taken for one.
|
|
30
|
+
*/
|
|
31
|
+
export interface SqlScope {
|
|
32
|
+
readonly kind: 'sql-scope';
|
|
33
|
+
}
|
|
34
|
+
/** `ctx.sql(Row)`: the tag for rows of `Row`, and `scope` for the capability's predicate inside its text. */
|
|
35
|
+
export interface SqlTag {
|
|
36
|
+
<S extends z.core.$ZodType>(row: S): (text: TemplateStringsArray, ...values: readonly (SqlValue | SqlScope)[]) => Promise<z.output<S>[]>;
|
|
37
|
+
scope(capability: unknown, table: string, alias?: string): SqlScope;
|
|
38
|
+
}
|
|
39
|
+
/** The site a tag is made for: its mode, its binding to the invocation, and the session its statements run in. */
|
|
40
|
+
export interface SqlSite {
|
|
41
|
+
readonly mode: SqlMode;
|
|
42
|
+
/** Throws unless the work running now is the invocation the tag was made for. */
|
|
43
|
+
check(): void;
|
|
44
|
+
session(): Promise<Session>;
|
|
45
|
+
/** Fails the site's transaction (the invocation's, or a job's fenced one), so it commits nothing. */
|
|
46
|
+
fail(): void;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The SQL `parts` make, with `$1…$n` for the values, or a `QueryRefusedError` that names what is refused: more than
|
|
50
|
+
* one statement, any statement but a query (and `INSERT`/`UPDATE`/`DELETE` for `'write'`), a nested statement of
|
|
51
|
+
* another kind, a qualified name outside `schema`, a `fabrika_*` or `pg_*` name, a function call outside the reviewed
|
|
52
|
+
* built-ins, and a value placed where it would not be bound.
|
|
53
|
+
*/
|
|
54
|
+
export declare function classifySql(parts: readonly string[], mode: SqlMode, schema: string): string;
|
|
55
|
+
/**
|
|
56
|
+
* `ctx.sql` of one site. Every call first checks that the work running now is the site's invocation, then classifies
|
|
57
|
+
* the text, then binds the values; only then does it reach the session, so a refused statement sends nothing.
|
|
58
|
+
*/
|
|
59
|
+
export declare function sqlTag(site: SqlSite, schema: string | undefined, termsOf: ScopeOf, writeTarget: WriteTarget): SqlTag;
|