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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,334 @@
1
+ import { type OperationNode, type ReturningNode } from 'kysely';
2
+ import { type NameSet } from './builtins';
3
+ import { type AppGrant, type CapabilityAuthority, type Ref, type RefGrant, type Within, type WithinGrant } from './capability';
4
+ import { type Database, type Session } from './database';
5
+ import { type NoColumns, type Primitive, type ScopedSelect, type TableShape } from './description';
6
+ import { type FileBinding } from './file-columns';
7
+ import type { ForeignKeyPair, GuardContext, RootKind } from './guard';
8
+ import { type Acknowledgement, type ActionOf, type ChildTableName, type Container, type ContainerOf, type DimensionOf, type DimensionTableName, type InsertValues, type JoinableChildren, type ListDimensionOf, type MoveAcknowledgement, type Operation, type ParentOf, type PolicyRow, type TableModel, type TableModels, type Tables, type UpdateValues, type VersionArguments } from './policy';
9
+ import type { ScopeTerms, ScopeUse } from './sql';
10
+ import { type TablePolicies } from './tables';
11
+ export interface DataLayerRuntime {
12
+ readonly database: Database;
13
+ readonly capabilities: CapabilityAuthority;
14
+ /** Whether the request running now may only read (an export's); its every statement then runs `READ ONLY`. */
15
+ readonly readOnly?: () => boolean;
16
+ }
17
+ /** Empty until the framework connects the layer at boot; every table handle of one `scoped` call shares it. */
18
+ export interface Connection {
19
+ runtime: DataLayerRuntime | undefined;
20
+ }
21
+ export interface TableContext {
22
+ readonly model: TableModel;
23
+ readonly parent: TableModel | undefined;
24
+ /** The table that owns the dimension an insert or a move targets: the container's table, or a child's parent. */
25
+ readonly destination: TableModel | undefined;
26
+ readonly shape: TableShape;
27
+ readonly joins: Readonly<Record<string, readonly ForeignKeyPair[]>>;
28
+ readonly connection: Connection;
29
+ }
30
+ export declare function runtimeOf(connection: Connection): DataLayerRuntime;
31
+ export interface DimensionScopeModel {
32
+ readonly dimension: string;
33
+ readonly key: string;
34
+ readonly container: Container | undefined;
35
+ }
36
+ export declare function dimensionScope(model: TableModel): DimensionScopeModel;
37
+ export declare function childScope(context: TableContext): {
38
+ readonly key: string;
39
+ readonly column: string;
40
+ readonly parent: DimensionScopeModel;
41
+ };
42
+ /** The dimension that lists and inserts are scoped by: the container, or for a top-level table its own. */
43
+ export declare function listDimension(scope: DimensionScopeModel): string;
44
+ /** The actions that allow `operation` on the table, any one of them; throws when its policy declares none. */
45
+ export declare function requiredActions(context: TableContext, operation: Operation): readonly string[];
46
+ /** Refuses a capability whose action is not one of `operation`'s actions on the table. */
47
+ export declare function checkAction(context: TableContext, operation: Operation, action: string): void;
48
+ /** The object a Ref of the model's own dimension names, with every link of its chain. */
49
+ export declare function selfLinks(model: TableModel): readonly Container[];
50
+ /** This object, over the whole chain the Ref carries. */
51
+ export declare function objectPredicate(context: TableContext, capability: unknown, operation: Operation): OperationNode;
52
+ /** Rows of a dimension table within the scope values of its list dimension. */
53
+ export declare function dimensionListPredicate(context: TableContext, capability: unknown, operation: Operation): OperationNode | undefined;
54
+ /** Rows of a child table that belong to the parent object the Ref names, over the whole chain. */
55
+ export declare function parentPredicate(context: TableContext, grant: RefGrant): OperationNode;
56
+ export declare function childListPredicate(context: TableContext, capability: unknown, operation: Operation): OperationNode | undefined;
57
+ /**
58
+ * The column that places a row of `model` in `dimension`: the column of that link of its containment chain, or a
59
+ * top-level table's own key; `undefined` when no `Within` of the dimension reaches the table.
60
+ */
61
+ export declare function reachColumn(model: TableModel, dimension: string): string | undefined;
62
+ /**
63
+ * Rows of a table a `Within` of any dimension of its chain lists, by the table's read action: tasks under a `Within` of
64
+ * `org` through their stamped `org_id`, as under one of `project` through `project_id`.
65
+ */
66
+ export declare function reachPredicate(context: TableContext, capability: unknown): OperationNode | undefined;
67
+ /**
68
+ * `ctx.sql.scope`'s predicate: what the handles of `capability` match in the table for `use`, as column tests. A
69
+ * fragment that reads (`'read'`) needs an action the handles read the table with: any action it names for a `Ref`,
70
+ * its read action for a `Within`, an action `ctx.db.app` lists it for with an `app` capability, and its read action
71
+ * for a scoped site's read of an application table. A fragment on the statement's UPDATE or DELETE target needs that
72
+ * operation's own action, and an `app` capability writes application tables only. A `Ref` matches its object's column
73
+ * and every link of the chain it carries; a `Within` matches like `ctx.db.within` (its values, each container grant on
74
+ * its own column, or every row for `all`); an `app` capability, and a scoped read of an application table, every row.
75
+ */
76
+ export declare function scopeTerms(context: TableContext, capability: unknown, use: ScopeUse, assigned?: readonly string[]): ScopeTerms;
77
+ /**
78
+ * A `Ref` of `dimension` whose action updates columns of the table: its update action, or a column's own write action.
79
+ * Which columns it updates follows from the action (`updatableBy`).
80
+ */
81
+ export declare function updatingRef(context: TableContext, capability: unknown, dimension: string): RefGrant;
82
+ /**
83
+ * What an `app` capability reaches of a dimension or child table: its rows (`list`, a scope fragment) with the read
84
+ * action or an action of `appRead`; its counts (`count`, `aggregate`) with those or an action of `appCount`.
85
+ */
86
+ export type AppReach = 'rows' | 'counts';
87
+ /**
88
+ * Every row of a dimension or child table an `app` capability reads through `ctx.db.app`: `true`, for an action that
89
+ * reaches `reach` (`checkRecordReach`).
90
+ */
91
+ export declare function appRecordPredicate(context: TableContext, capability: unknown, reach: AppReach): OperationNode;
92
+ /** Every row a capability of an `app` endpoint reads in `table`, by the table's read action: `true`. */
93
+ export declare function appListPredicate(context: TableContext, capability: unknown): OperationNode;
94
+ /**
95
+ * Every row of an application table, for a `Ref` or a `Within` of its read action: what `ctx.db.app` of an `on`, `in`
96
+ * or `over` endpoint reads, read-only, as a reference table is read. The table's read action decides who reads it.
97
+ */
98
+ export declare function scopedApplicationRead(context: TableContext, capability: unknown): OperationNode;
99
+ /** One row of an application table by its key, for an `app` capability of `operation`'s action. */
100
+ export declare function appRowPredicate(context: TableContext, capability: unknown, key: unknown, operation: Operation): OperationNode;
101
+ /**
102
+ * What a claim of a queue table leases through `capability`: an application table's every row for an `app` capability,
103
+ * a dimension or child table's rows a `Within` lists. Either way its action is one of the table's update actions.
104
+ */
105
+ export declare function claimScope(context: TableContext, capability: unknown): {
106
+ readonly grant: AppGrant | WithinGrant;
107
+ readonly scope: OperationNode | undefined;
108
+ };
109
+ /**
110
+ * The one row a claim's `row.ref` names, for an action of `operation`: its key and every containment column it had when
111
+ * it was claimed, so a row moved since is not reached.
112
+ */
113
+ export declare function claimedRowPredicate(context: TableContext, capability: unknown, operation: 'update' | 'delete'): OperationNode;
114
+ /** The row of an application table `key` names. */
115
+ export declare function appKeyPredicate(model: TableModel, key: unknown): OperationNode;
116
+ /**
117
+ * The capability of an `app` endpoint whose action updates columns of an application table: its update action, or a
118
+ * column's own write action. Which columns it updates follows from the action (`updatableBy`).
119
+ */
120
+ export declare function updatingApp(context: TableContext, capability: unknown): AppGrant;
121
+ export declare function childObjectPredicate(context: TableContext, parent: unknown, key: unknown, operation: Operation): OperationNode;
122
+ export declare function guard(context: TableContext, root: RootKind, scope: OperationNode | undefined): GuardContext;
123
+ /**
124
+ * Where a table operation's statements run: a store's own sessions, one transaction per mutation, or the mini-ORM's
125
+ * invocation transaction (`orm.ts`), where every statement of a handler runs in one.
126
+ */
127
+ export interface Runner {
128
+ /** The session a read runs in. */
129
+ read(): Promise<Session>;
130
+ /** Runs `work` in one transaction. */
131
+ write<T>(work: (transaction: Session) => Promise<T>): Promise<T>;
132
+ /** Where file references are made and checked; a store's own runner has none, and its file columns read as ids. */
133
+ readonly files?: FileBinding;
134
+ }
135
+ /**
136
+ * Every mutation of a store runs in its own transaction. In a job attempt that transaction is fenced by the attempt's
137
+ * lease (`fencedTransaction`): an attempt that lost it, or ran out of time, writes nothing.
138
+ */
139
+ export declare function fencedWork<T>(runtime: DataLayerRuntime, work: (transaction: Session) => Promise<T>): Promise<T>;
140
+ /** The action a capability holds, which its operation's checks already admitted. */
141
+ export declare function heldAction(context: TableContext, capability: unknown): string;
142
+ /**
143
+ * Reads insert or update data through its own property descriptors, so no getter runs and no inherited or
144
+ * non-enumerable key slips past. Every key is checked before any value is read; unknown keys are refused, not
145
+ * dropped, so an agent learns about them.
146
+ */
147
+ export declare function readValues(values: unknown, allowed: NameSet, what: 'insert' | 'update', model: TableModel): Map<string, Primitive | null>;
148
+ export declare function versionArgument(context: TableContext, options: readonly unknown[]): OperationNode | undefined;
149
+ /**
150
+ * One row by its scope predicate, with `visible` columns (the table's read action's, unless a capability reads its own).
151
+ * `action` is what read it: each file column then answers a reference that reads the file only under a read action.
152
+ */
153
+ export declare function selectOne<O>(context: TableContext, run: Runner, scope: OperationNode, visible?: readonly string[], action?: string): Promise<O>;
154
+ export declare function answer(context: TableContext): ReturningNode;
155
+ export declare function keyOf(model: TableModel): string;
156
+ export declare function insertRow<O>(context: TableContext, run: Runner, values: ReadonlyMap<string, Primitive | null>, destination: RefGrant | undefined): Promise<O>;
157
+ /**
158
+ * `insert(values, { onConflict: { columns, do } })`: on a conflict of `columns` (a unique index of the table), `nothing`
159
+ * writes nothing and `update` sets the columns the table's update action writes, from `updates`.
160
+ */
161
+ export interface OnConflict {
162
+ readonly columns: readonly string[];
163
+ readonly mode: 'nothing' | 'update';
164
+ /** The values given for the table's update columns: what `update` sets on the conflicting row. */
165
+ readonly updates: ReadonlyMap<string, Primitive | null>;
166
+ }
167
+ /**
168
+ * The `onConflict` of an insert's options, read through own data properties; `undefined` without options. `'update'`
169
+ * also needs the capability's action to be one of the table's update actions.
170
+ */
171
+ export declare function onConflictOf(context: TableContext, options: unknown, action: string, given: ReadonlyMap<string, Primitive | null>): OnConflict | undefined;
172
+ /**
173
+ * The rows a write addresses: the scope predicate, and the caller's conditions beside it (an unscoped row's key, whose
174
+ * scope predicate is `true`).
175
+ */
176
+ export interface Addressed {
177
+ readonly scope: OperationNode;
178
+ readonly conditions?: OperationNode;
179
+ }
180
+ /**
181
+ * Zero changed rows → one follow-up read in the same transaction → 404 when the object is gone, else 409. An update
182
+ * that sets no column writes nothing, not even the version: it reads the acknowledgement under the same conditions.
183
+ */
184
+ export declare function updateRow<O>(context: TableContext, run: Runner, target: Addressed, changes: ReadonlyMap<string, Primitive | null>, expectedVersion: OperationNode | undefined, destination?: RefGrant): Promise<O>;
185
+ /** Like `updateRow`: under `expectVersion`, a row that changed is 409 and a missing one 404. */
186
+ export declare function removeRow(context: TableContext, run: Runner, target: Addressed, expectedVersion?: OperationNode): Promise<void>;
187
+ type Columns<Row> = keyof Row & string;
188
+ type NumericColumn<Row> = {
189
+ [K in Columns<Row>]: Exclude<Row[K], null> extends number | bigint | string ? K : never;
190
+ }[Columns<Row>];
191
+ export interface AggregateSpec<Row> {
192
+ readonly groupBy?: readonly Columns<Row>[];
193
+ readonly count?: boolean;
194
+ readonly sum?: readonly NumericColumn<Row>[];
195
+ readonly min?: readonly Columns<Row>[];
196
+ readonly max?: readonly Columns<Row>[];
197
+ readonly where?: (q: ScopedSelect<Row, Row, NoColumns>) => ScopedSelect<Row, unknown, NoColumns>;
198
+ }
199
+ type Listed<S, K extends 'groupBy' | 'sum' | 'min' | 'max'> = S extends {
200
+ readonly [P in K]: readonly (infer C extends string)[];
201
+ } ? C : never;
202
+ type Flat<T> = {
203
+ [K in keyof T]: T[K];
204
+ };
205
+ /**
206
+ * `count` is a string because Postgres returns a bigint. A sum is a string for integer and numeric columns and a
207
+ * number for floating-point ones; the column type alone cannot tell them apart.
208
+ */
209
+ export type AggregateRow<Row, S> = Flat<Pick<Row, Listed<S, 'groupBy'> & keyof Row> & (S extends {
210
+ readonly count: true;
211
+ } ? {
212
+ readonly count: string;
213
+ } : NoColumns) & {
214
+ readonly [K in Listed<S, 'sum'> as `sum_${K}`]: string | number | null;
215
+ } & {
216
+ readonly [K in Listed<S, 'min'> & keyof Row as `min_${K}`]: Row[K] | null;
217
+ } & {
218
+ readonly [K in Listed<S, 'max'> & keyof Row as `max_${K}`]: Row[K] | null;
219
+ }>;
220
+ export declare function aggregateRows<Row, O>(context: TableContext, run: Runner, scope: OperationNode | undefined, shape: TableShape, spec: AggregateSpec<Row>): Promise<O[]>;
221
+ /** An insert's row, stamped from its capability, the destination it locks, and what it does on a conflict. */
222
+ export interface PreparedInsert {
223
+ readonly row: Map<string, Primitive | null>;
224
+ readonly destination: RefGrant | undefined;
225
+ readonly conflict: OnConflict | undefined;
226
+ }
227
+ /**
228
+ * A dimension table's insert over a capability nobody type-checked: into a container (a new UUIDv7 key, the chain from
229
+ * the container's `Ref`), or for a top-level table the object the `Ref` names.
230
+ */
231
+ export declare function prepareDimensionInsert(context: TableContext, target: unknown, values: unknown, options?: unknown): PreparedInsert;
232
+ /** An application table's insert, for an `app` capability of its insert action: a new key, no containment. */
233
+ export declare function prepareApplicationInsert(context: TableContext, capability: unknown, values: unknown, options?: unknown): PreparedInsert;
234
+ /** A child table's insert under the parent its `Ref` names, whose chain is checked against the database. */
235
+ export declare function prepareChildInsert(context: TableContext, parent: unknown, values: unknown, options?: unknown): PreparedInsert;
236
+ /** A prepared insert, with its conflict handling when the caller named one. */
237
+ export declare function insertPrepared(context: TableContext, run: Runner, prepared: PreparedInsert): Promise<Readonly<Record<string, unknown>>>;
238
+ /** `DimensionTable.insert` over a capability nobody type-checked; the table probe of `@fabrika/db/testing` calls it too. */
239
+ export declare function insertIntoDimension<O>(context: TableContext, run: Runner, target: unknown, values: unknown): Promise<O>;
240
+ /** An application table's insert, for an `app` capability of its insert action. */
241
+ export declare function insertIntoApplication<O>(context: TableContext, run: Runner, capability: unknown, values: unknown): Promise<O>;
242
+ /** `ChildTable.insert` over a capability nobody type-checked. */
243
+ export declare function insertIntoChild<O>(context: TableContext, run: Runner, parent: unknown, values: unknown): Promise<O>;
244
+ type ReadAction<P, T> = ActionOf<P, T, 'read'>;
245
+ type Row<DB, P, T> = PolicyRow<DB, P, T>;
246
+ type Joinable<DB, P, T> = JoinableChildren<DB, P, T>;
247
+ type ParentDimension<P, T> = DimensionOf<P, ParentOf<P, T>>;
248
+ /** The table handle of a dimension table: every entry point takes a `Ref` or a `Within`. */
249
+ export declare class DimensionTable<DB, P, T extends string> {
250
+ #private;
251
+ constructor(context: TableContext);
252
+ select<S>(within: Within<ListDimensionOf<P, T>, ReadAction<P, T>>, build: (q: ScopedSelect<Row<DB, P, T>, Row<DB, P, T>, Joinable<DB, P, T>>) => ScopedSelect<Row<DB, P, T>, S, Joinable<DB, P, T>>): Promise<S[]>;
253
+ get(ref: Ref<DimensionOf<P, T>, ReadAction<P, T>>): Promise<Row<DB, P, T>>;
254
+ /**
255
+ * Into a container (the key is a new UUIDv7, the chain comes from the container's Ref and is checked against
256
+ * the database), or for a top-level table the object the `Ref` names. Answers an acknowledgement, not the row.
257
+ */
258
+ insert(target: Ref<ListDimensionOf<P, T>, ActionOf<P, T, 'insert'>>, values: InsertValues<DB, P, T>): Promise<Acknowledgement<P, T>>;
259
+ update(ref: Ref<DimensionOf<P, T>, ActionOf<P, T, 'update'>>, values: UpdateValues<DB, P, T>, ...options: VersionArguments<P, T>): Promise<Acknowledgement<P, T>>;
260
+ remove(ref: Ref<DimensionOf<P, T>, ActionOf<P, T, 'delete'>>): Promise<void>;
261
+ /**
262
+ * Moves the object to another container: the delete action at the source, the insert action at the target,
263
+ * whose chain is checked against the database. Every chain column changes; children that copy them follow
264
+ * through `ON UPDATE CASCADE` on their composite foreign key. Answers the acknowledgement and the new chain.
265
+ */
266
+ move(ref: Ref<DimensionOf<P, T>, ActionOf<P, T, 'delete'>>, to: Ref<ContainerOf<P, T>, ActionOf<P, T, 'insert'>>): Promise<MoveAcknowledgement<P, T>>;
267
+ aggregate<const S extends AggregateSpec<Row<DB, P, T>>>(within: Within<ListDimensionOf<P, T>, ReadAction<P, T>>, spec: S): Promise<AggregateRow<Row<DB, P, T>, S>[]>;
268
+ }
269
+ /** The scope a child list is reached through: one parent object, or the parent's list dimension. */
270
+ export type ChildListScope<P, T> = Ref<ParentDimension<P, T>, ReadAction<P, T>> | Within<ListDimensionOf<P, ParentOf<P, T>>, ReadAction<P, T>>;
271
+ /** The table handle of a child table: one row is addressed by its parent's `Ref` and its own key. */
272
+ export declare class ChildTable<DB, P, T extends string> {
273
+ #private;
274
+ constructor(context: TableContext);
275
+ select<S>(scope: ChildListScope<P, T>, build: (q: ScopedSelect<Row<DB, P, T>, Row<DB, P, T>, NoColumns>) => ScopedSelect<Row<DB, P, T>, S, NoColumns>): Promise<S[]>;
276
+ get(parent: Ref<ParentDimension<P, T>, ReadAction<P, T>>, key: string): Promise<Row<DB, P, T>>;
277
+ /** Under the parent the Ref names, whose chain is checked against the database. Answers an acknowledgement. */
278
+ insert(parent: Ref<ParentDimension<P, T>, ActionOf<P, T, 'insert'>>, values: InsertValues<DB, P, T>): Promise<Acknowledgement<P, T>>;
279
+ update(parent: Ref<ParentDimension<P, T>, ActionOf<P, T, 'update'>>, key: string, values: UpdateValues<DB, P, T>, ...options: VersionArguments<P, T>): Promise<Acknowledgement<P, T>>;
280
+ remove(parent: Ref<ParentDimension<P, T>, ActionOf<P, T, 'delete'>>, key: string): Promise<void>;
281
+ aggregate<const S extends AggregateSpec<Row<DB, P, T>>>(scope: ChildListScope<P, T>, spec: S): Promise<AggregateRow<Row<DB, P, T>, S>[]>;
282
+ }
283
+ /**
284
+ * One table's operations without the store types, for `@fabrika/db/testing` only: the generated isolation tests address
285
+ * a table by name. Every runtime check of a store applies; a child row is addressed by its parent's capability and
286
+ * `key`, a dimension row by its capability alone.
287
+ */
288
+ export interface TableProbe {
289
+ readonly mode: 'dimension' | 'child' | 'app';
290
+ get(capability: unknown, key?: string): Promise<Readonly<Record<string, unknown>>>;
291
+ select(capability: unknown): Promise<readonly Readonly<Record<string, unknown>>[]>;
292
+ /** `aggregate(capability, { count: true })`; zero rows when the capability reaches none. */
293
+ count(capability: unknown): Promise<string>;
294
+ /** `aggregate(capability, { groupBy: columns, count: true })`: the values of `columns` in each group, and its count. */
295
+ group(capability: unknown, columns: readonly string[]): Promise<readonly Readonly<Record<string, unknown>>[]>;
296
+ insert(capability: unknown, values: unknown): Promise<unknown>;
297
+ update(capability: unknown, key: string | undefined, values: unknown, expectVersion?: number): Promise<unknown>;
298
+ remove(capability: unknown, key?: string): Promise<void>;
299
+ }
300
+ /** What a select or an aggregate of a capability that reads `visible` may name: those columns, and no join. */
301
+ export declare function actionShape(model: TableModel, visible: readonly string[]): TableShape;
302
+ /** What `serve(contract, { database })` takes: the policies as data. `boot` binds only an object `scoped` created. */
303
+ export interface DataLayer {
304
+ /** Every table's policy as data, for the contract compiler. */
305
+ readonly tables: TablePolicies;
306
+ }
307
+ declare const layerTypes: unique symbol;
308
+ /**
309
+ * What `scoped` returns: the policies, typed by the tables they came from. It has no connection, no locator and no
310
+ * handle of its own; `boot` binds it through `@fabrika/db/framework`, which only `@fabrika/app` imports, and a
311
+ * handler reaches data through `ctx.db`. The brand is type-only: no value has the property.
312
+ */
313
+ export interface ScopedDatabase<DB, P> extends DataLayer {
314
+ readonly [layerTypes]?: {
315
+ readonly db: DB;
316
+ readonly policies: P;
317
+ };
318
+ }
319
+ /**
320
+ * Binds the table policies to the data layer. An application calls it once, in `src/app.ts`, and passes the result to
321
+ * `serve(contract, { database })`. Nothing reaches the database until `boot` binds that one object; another `scoped`
322
+ * result never gets a connection.
323
+ */
324
+ export declare function scoped<DB, P>(tables: Tables<DB, P>): ScopedDatabase<DB, P>;
325
+ /**
326
+ * A store over one dimension table of the layer: the framework's own tests and kits build them, never an application,
327
+ * whose handlers reach data through `ctx.db`. Exported from `@fabrika/db/testing` only.
328
+ */
329
+ export declare function defineStore<DB, P, const T extends DimensionTableName<P>, S>(database: ScopedDatabase<DB, P>, table: T, build: (t: DimensionTable<DB, P, T>) => S): S;
330
+ /** A store over one child table of the layer, as `defineStore`; exported from `@fabrika/db/testing` only. */
331
+ export declare function defineChildStore<DB, P, const T extends ChildTableName<P>, S>(database: ScopedDatabase<DB, P>, table: T, build: (t: ChildTable<DB, P, T>) => S): S;
332
+ /** The table that owns `dimension`: the one dimension table scoped by it (`defineTables` refuses a second). */
333
+ export declare function ownerOf(models: TableModels, dimension: string): TableModel | undefined;
334
+ export {};
@@ -0,0 +1,98 @@
1
+ import { type Session } from './database';
2
+ import type { DataLayerRuntime } from './store';
3
+ /** A stored file's states: written, waiting for its first reference, for its scan, served, or refused by the scan. */
4
+ export declare const STORED_FILE_STATUSES: readonly ["pending", "quarantined", "live", "rejected"];
5
+ export type StoredFileStatus = (typeof STORED_FILE_STATUSES)[number];
6
+ /** What a file reference carries: the file's metadata, never its bytes or its object. */
7
+ export interface StoredFileMeta {
8
+ readonly id: string;
9
+ readonly kind: string;
10
+ readonly name: string | null;
11
+ readonly type: string;
12
+ readonly size: number;
13
+ readonly status: StoredFileStatus;
14
+ }
15
+ /** A file about to be written: its row comes first, then the object under `objectKey`. */
16
+ export interface StagedFile {
17
+ readonly id: string;
18
+ readonly kind: string;
19
+ readonly objectKey: string;
20
+ readonly contentType: string;
21
+ readonly name: string | null;
22
+ /** The request (or job attempt) that writes it: only it may claim the file while it is pending. */
23
+ readonly request: string;
24
+ }
25
+ /** A stored file as a byte consumer reads it: only a live one is served, by its scanned version when its kind scans. */
26
+ export interface ServedFile extends StoredFileMeta {
27
+ readonly objectKey: string;
28
+ readonly objectVersion: string | null;
29
+ }
30
+ /** What the scan pass needs of a quarantined file. */
31
+ export interface QuarantinedStoredFile {
32
+ readonly id: string;
33
+ readonly objectKey: string;
34
+ readonly size: number;
35
+ readonly sha256: string;
36
+ readonly contentType: string;
37
+ readonly scanAttempts: number;
38
+ readonly dueSince: Date;
39
+ }
40
+ /** A file column the sweep looks a file up in: an application table's column, both validated identifiers. */
41
+ export interface FileColumnRef {
42
+ readonly table: string;
43
+ readonly column: string;
44
+ }
45
+ /** What a reference write learns of a file it locked. */
46
+ export interface LockedFile {
47
+ readonly kind: string;
48
+ readonly status: StoredFileStatus;
49
+ readonly request: string | null;
50
+ readonly scope: string | null;
51
+ /** Whether it is still within the pending grace, counted from its creation. */
52
+ readonly fresh: boolean;
53
+ }
54
+ export interface StoredFiles {
55
+ /** Commits the `pending` row of a file whose object is written next, on a connection of its own: the cleanup intent. */
56
+ stage(file: StagedFile): Promise<void>;
57
+ /** Records the written object's size and digest on its pending row, on a connection of its own. */
58
+ written(id: string, written: {
59
+ readonly size: number;
60
+ readonly sha256: string;
61
+ }): Promise<void>;
62
+ /**
63
+ * Deletes a pending row this request wrote and nobody claimed, and records its object for deletion, in one
64
+ * statement: the object key, or `null` when the file was claimed (or is gone).
65
+ */
66
+ abandon(id: string, request: string): Promise<string | null>;
67
+ /** A file as a byte consumer reads it, in `using` or on its own; `null` when the sweep deleted it. */
68
+ find(id: string, using?: Session): Promise<ServedFile | null>;
69
+ /** Quarantines a live file of a scanning kind again when it has no scanned version, so the scan decides once more. */
70
+ requarantine(id: string, version: string | null): Promise<void>;
71
+ /** Each file's metadata, by id, in `using`: what the data layer answers in a file column. */
72
+ metadata(using: Session, ids: readonly string[]): Promise<ReadonlyMap<string, StoredFileMeta>>;
73
+ quarantined(kinds: readonly string[], limit: number, now: Date): Promise<readonly QuarantinedStoredFile[]>;
74
+ promote(id: string, sha256: string, version: string): Promise<boolean>;
75
+ deferScan(id: string, attempts: number, after: Date): Promise<boolean>;
76
+ reject(id: string): Promise<boolean>;
77
+ /** Objects whose rows a sweep deleted and whose own deletion it did not finish. */
78
+ unfinishedDeletions(limit: number): Promise<readonly string[]>;
79
+ forgetDeletion(objectKey: string): Promise<void>;
80
+ /** Clears the legacy marker of every migrated file that one of `columns` references: it is an ordinary file now. */
81
+ adoptReferenced(columns: readonly FileColumnRef[]): Promise<number>;
82
+ /** At most `limit` files past the grace that no column of `columns` references (pending ones included), oldest first. */
83
+ unreferenced(columns: readonly FileColumnRef[], limit: number, graceMs: number): Promise<readonly string[]>;
84
+ /**
85
+ * Deletes the row of an unreferenced file and records its object for deletion, in one transaction; `null` when it
86
+ * survived: a reference exists (the foreign key refuses the delete), or it is legacy or within the grace.
87
+ */
88
+ remove(id: string, graceMs: number): Promise<string | null>;
89
+ }
90
+ /** Locks file `id` `for share` in `session`, the transaction that writes a reference to it; `null` when it is gone. */
91
+ export declare function lockStoredFile(session: Session, id: string, graceMs: number): Promise<LockedFile | null>;
92
+ /** Ends the pending state of a file the reference write locked: `quarantined` for a scanning kind, else `live`. */
93
+ export declare function claimStoredFile(session: Session, id: string, ended: 'quarantined' | 'live'): Promise<void>;
94
+ /** Sets the scope of a file's first dimensional reference; `false` when it already holds another. */
95
+ export declare function stampStoredFile(session: Session, id: string, scope: string): Promise<boolean>;
96
+ /** Each file's metadata, by id, in `using`: one statement for every file column of a read. */
97
+ export declare function storedFilesMetadata(using: Session, ids: readonly string[]): Promise<ReadonlyMap<string, StoredFileMeta>>;
98
+ export declare function storedFiles(runtime: () => DataLayerRuntime): StoredFiles;
@@ -0,0 +1,11 @@
1
+ import type { TablePolicyArtifact } from '@fabrika/auth-core';
2
+ import { type Tables } from './policy';
3
+ export { PolicyError } from './errors';
4
+ export type { FileReference } from './file-references';
5
+ export type { AnyDbIndex, ApplicationRows, ApplicationTables, AppRecordRows, ChildRows, ClaimedRow, ClaimedRowHandle, ClaimOptions, ContainedRows, CountOptions, DataKind, DbIndex, FragmentTable, IndexedTable, InsertConflict, InsertMethod, ListOptions, ListPage, NoUnscoped, OrderTerm, OwnerRow, PublicApplicationRows, PublicApplicationTables, PublicInsertMethod, ReadOnlyUnscoped, ReferenceRows, ReferenceTables, RowFor, RowOf, Saved, ScopedApplicationRows, ScopedApplicationTables, ScopeTarget, TableNameOf, TargetForm, TargetRow, Unavailable, UnownedTarget, UnscopedDeclaration, UnscopedInsert, UnscopedMode, UnscopedRows, UnscopedTables, VersionArguments, WithinRows, WithinTables, } from './orm-types';
6
+ export { type ApplicationPolicy, type ApplicationScope, type ChildPolicy, type ChildScope, type ColumnList, defineTables, type DimensionPolicy, type DimensionScope, type FilePolicies, isTables, type QueuePolicy, readTables, type RecordActions, type ReferencePolicy, type ReferenceScope, type TableAction, type TableActions, type TableInsert, Tables, type TableUpdate, type VersionColumn, type VocabularyTables, } from './policy';
7
+ export type { ColumnTypes, DefineVocabularyPolicies, NormalizedPolicies, TableVocabulary, TypedColumnList, VocabularyAction, VocabularyChildScope, VocabularyContainer, VocabularyContainerOf, VocabularyDimension, VocabularyDimensionScope, VocabularyPolicyMap, VocabularyTablePolicy, } from './vocabulary-tables';
8
+ /** Every table's policy in the form the contract artifact carries, by table name. */
9
+ export type TablePolicies = Readonly<Record<string, TablePolicyArtifact>>;
10
+ /** The artifact form of every policy `defineTables` (or `readTables`) checked. */
11
+ export declare function tablePolicies<DB, P>(tables: Tables<DB, P>): TablePolicies;
@@ -0,0 +1,12 @@
1
+ import { type NameSet } from './builtins';
2
+ /**
3
+ * A day as the layer answers it: the database's `YYYY-MM-DD` text, or a decoded `Date` that is exactly a UTC midnight
4
+ * in 0001–9999 — the only `Date` `Bun.SQL` decodes correctly from a `date`, whatever the process's time zone.
5
+ */
6
+ export declare function dayText(value: string | Date, where: string): string;
7
+ /** Replaces each value under a key of `days` in every row with its `YYYY-MM-DD` text, in place. */
8
+ export declare function answerDays<R>(table: string, days: NameSet, rows: R[]): R[];
9
+ /** A day a write binds: `YYYY-MM-DD` text of a real day, as the layer answers one. */
10
+ export declare function dayInput(value: string, where: string): string;
11
+ /** A timestamp a write binds as text: strict ISO 8601 with `Z` or an offset, every field in range. */
12
+ export declare function timestampInput(value: string, where: string): string;
@@ -0,0 +1,126 @@
1
+ import { Database } from './database';
2
+ import type { Primitive } from './description';
3
+ import { type Tables } from './policy';
4
+ import type { TableProbe } from './store';
5
+ export { ChildTable, defineChildStore, defineStore, DimensionTable, type TableProbe } from './store';
6
+ export declare const TEST_POSTGRES_URL = "FABRIKA_TEST_POSTGRES_URL";
7
+ /** Set to `1` by the platform build: a missing test database is then a failure, not a skip. */
8
+ export declare const TEST_POSTGRES_REQUIRED = "FABRIKA_TEST_POSTGRES_REQUIRED";
9
+ export interface TestDatabaseOptions {
10
+ /** The application's `db/migrations/` directory. */
11
+ readonly migrations: string | URL;
12
+ /** The test file, for the skip message. */
13
+ readonly label: string;
14
+ /**
15
+ * How long to wait for free connection slots, in ms, before the file fails saying so; `DEFAULT_CONNECTION_WAIT_MS` by
16
+ * default. Once one file of a process gave up, every later one fails at once.
17
+ */
18
+ readonly connectionWaitMs?: number;
19
+ }
20
+ /** A value a test row holds in a column; `null` for a nullable column the fixture left empty. */
21
+ export type SampleValue = Primitive | null;
22
+ /** One seeded row and where it lies. */
23
+ export interface WorldRow {
24
+ /** The row's key: the dimension value of a dimension table's row, a child's own key. */
25
+ readonly key: string;
26
+ /** A child row's parent key; `null` for a dimension table. */
27
+ readonly parent: string | null;
28
+ /** The value of the grant dimension the row lies in (`IsolationWorlds.dimension`). */
29
+ readonly scope: string;
30
+ /**
31
+ * Every dimension value the row lies in: a dimension table's own first, then each container, innermost first. A
32
+ * share link's chain is this list.
33
+ */
34
+ readonly chain: readonly {
35
+ readonly type: string;
36
+ readonly value: string;
37
+ }[];
38
+ /** The row's values of the policy's update columns, as seeded. */
39
+ readonly updates: Readonly<Record<string, SampleValue>>;
40
+ /**
41
+ * The distinct value written into every text column the fixture filled itself, in this row and in its containers.
42
+ * Each occurs in no other fixture, so one found in an answer to another scope is a leaked row.
43
+ */
44
+ readonly markers: readonly string[];
45
+ }
46
+ /** One seeded row of an application table, which lies in no dimension. */
47
+ export interface ApplicationRow {
48
+ readonly key: string;
49
+ /** The row's values of the policy's update columns, as seeded. */
50
+ readonly updates: Readonly<Record<string, SampleValue>>;
51
+ /** The distinct value written into every text column the fixture filled itself. */
52
+ readonly markers: readonly string[];
53
+ }
54
+ /** A row seeded under another: its key, and the distinct value written into every text column the fixture filled. */
55
+ export interface SeededRow {
56
+ readonly key: string;
57
+ readonly markers: readonly string[];
58
+ }
59
+ /**
60
+ * What the isolation fixture cannot seed for a schema: a CHECK its values fail, a required foreign key to a scoped table
61
+ * outside the containment chain. It is a limit of the generator, named with the constraint or the column, never a
62
+ * failure of the handler or the policy a case is about.
63
+ */
64
+ export declare class SeedLimitError extends Error {
65
+ readonly name = "SeedLimitError";
66
+ }
67
+ /** One row of a table in each of two containment chains that share no value. */
68
+ export interface IsolationWorlds {
69
+ /**
70
+ * The dimension a caller's grants are scoped by: a dimension table's container, or its own dimension when it has
71
+ * none; a child table's parent's.
72
+ */
73
+ readonly dimension: string;
74
+ readonly own: WorldRow;
75
+ readonly other: WorldRow;
76
+ }
77
+ export interface TestDatabase {
78
+ /** The connection `boot` binds the data layer to: the runtime role, on the migrated schema. */
79
+ readonly database: Database;
80
+ readonly schema: string;
81
+ /**
82
+ * Seeds one row of `table` in each of two fresh chains, with every container row a table owns. `values` (column →
83
+ * value) are written into both rows of `table`: the state an endpoint's precondition needs. A text column keeps its
84
+ * marker, so a seed of one, or of a column that places the row, throws.
85
+ */
86
+ seedWorlds<DB, P>(tables: Tables<DB, P>, table: string, values?: Readonly<Record<string, SampleValue>>): Promise<IsolationWorlds>;
87
+ /** Seeds one row of the application table `table`: its key, version and creator stamped, every other column sampled. */
88
+ seedRow<DB, P>(tables: Tables<DB, P>, table: string): Promise<ApplicationRow>;
89
+ /**
90
+ * Seeds one row of `table`, a child or dimension table that lies in the object of `under` (a row `seedWorlds`
91
+ * seeded), placed in that row's chain: the row a fixture names by id, such as a note's comment. Answers its key and
92
+ * the markers it holds.
93
+ */
94
+ seedUnder<DB, P>(tables: Tables<DB, P>, table: string, under: WorldRow): Promise<SeededRow>;
95
+ /** Values for the policy's insert columns of `table` that its schema accepts: every required one, sampled; a file column a seeded file. */
96
+ sampleInsert<DB, P>(tables: Tables<DB, P>, table: string): Promise<Readonly<Record<string, SampleValue>>>;
97
+ /**
98
+ * The object key of every file the fixture seeded into a file column, oldest first. Each stands for an empty object,
99
+ * which a test's object store holds when a handler serves or opens the file.
100
+ */
101
+ seededObjects(): readonly string[];
102
+ /** Closes the connections and drops the schema and its roles. */
103
+ close(): Promise<void>;
104
+ }
105
+ /**
106
+ * The object version a seeded file was scanned at: whoever stocks its empty object (`seededObjects`) stores it under
107
+ * this version, so a file of a scanning kind is served as a live one is, by its scanned version.
108
+ */
109
+ export declare const SEEDED_OBJECT_VERSION = "\"seeded\"";
110
+ /**
111
+ * A fresh schema with the framework's and the application's migrations applied by the real runner (`@fabrika/platform-node`) as its
112
+ * migration role, connected and seeded as its runtime role, or `null` after the standard skip message when
113
+ * `FABRIKA_TEST_POSTGRES_URL` is unset. That URL is the provisioning credential, which must be able to create roles.
114
+ * With `FABRIKA_TEST_POSTGRES_REQUIRED=1` a missing URL throws instead. The URL is a credential: no message names it.
115
+ */
116
+ export declare function testDatabase(options: TestDatabaseOptions): Promise<TestDatabase | null>;
117
+ /**
118
+ * The connections a test database holds once open: the provisioning connection, the seeder and the runtime role's.
119
+ * Migrating and reading the catalog each borrow one more for a moment, and so does the drop once the others ended.
120
+ */
121
+ export declare const TEST_DATABASE_CONNECTIONS: number;
122
+ /**
123
+ * The untyped handle over one table of the data layer `scoped(tables)` created; it reaches the database once `boot`
124
+ * has bound that layer. Every runtime check of a store applies: kind, dimension, action and the request's authority.
125
+ */
126
+ export declare function tableProbe(layer: unknown, table: string): TableProbe;