@voltro/database 0.18.0 → 0.20.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/CHANGELOG.md +245 -3
- package/dist/index.d.ts +209 -4
- package/dist/index.js +490 -441
- package/dist/sql.d.ts +3 -2
- package/dist/sql.js +327 -327
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -159,6 +159,30 @@ export declare const arrayOverlaps: <RowOf = Record<string, unknown>, K extends
|
|
|
159
159
|
*/
|
|
160
160
|
export declare const attachEagerLoads: (rows: ReadonlyArray<Row_2>, spec: WithSpec, sourceTable: TableLike, lookup: EagerLookup) => Promise<ReadonlyArray<Row_2>>;
|
|
161
161
|
|
|
162
|
+
/**
|
|
163
|
+
* The attribution fields to spread onto a `ChangeEvent` at creation.
|
|
164
|
+
*
|
|
165
|
+
* Returns an object with the keys OMITTED rather than set to `undefined`, so an
|
|
166
|
+
* unattributed event is byte-identical to one from before this existed — no
|
|
167
|
+
* `traceId: undefined` appearing in a snapshot, a broadcast payload, or a
|
|
168
|
+
* consumer's `Object.keys`.
|
|
169
|
+
*
|
|
170
|
+
* **Call it where the event is CREATED, not where it is delivered.** A
|
|
171
|
+
* transactional write queues its events and flushes them after commit, by which
|
|
172
|
+
* time the async-local scope is gone. Stamping at creation is what makes the
|
|
173
|
+
* transactional path carry identity at all.
|
|
174
|
+
*/
|
|
175
|
+
export declare const attributionFields: () => {
|
|
176
|
+
traceId?: string;
|
|
177
|
+
subjectId?: string | null;
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
/** The write's identity, as both sides can compute it. NOT the row image: the
|
|
181
|
+
* transport re-encodes values (dates as strings, numbers as strings on some
|
|
182
|
+
* drivers), so an image hash would disagree across the two sides and silently
|
|
183
|
+
* never match. `(table, op, pk)` is stable through any re-encoding. */
|
|
184
|
+
export declare const attributionKey: (table: string, op: string, primaryKey: unknown) => string;
|
|
185
|
+
|
|
162
186
|
/**
|
|
163
187
|
* Audit a list of tables in one call. Convenience for the CLI's boot
|
|
164
188
|
* path; same shape as calling `auditTableIndexes` per table and
|
|
@@ -464,6 +488,32 @@ export declare type ChangeEvent = {
|
|
|
464
488
|
* so the consumer must elect a single worker instead.
|
|
465
489
|
*/
|
|
466
490
|
readonly origin?: 'inline' | 'injected';
|
|
491
|
+
/**
|
|
492
|
+
* The trace this write happened under — the SAME id `auditPlugin` records as
|
|
493
|
+
* `AuditEvent.traceId` and `voltro logs --trace` resolves.
|
|
494
|
+
*
|
|
495
|
+
* This is the correlation bridge. Without it `plugin-versioning` knows WHAT
|
|
496
|
+
* changed and the audit sink knows WHO called and whether it was refused, and
|
|
497
|
+
* nothing joins the two. With it, "what did this call touch" is one query.
|
|
498
|
+
*
|
|
499
|
+
* Absent means the write had no request behind it: a seed, a `*.startup.tsx`,
|
|
500
|
+
* a schedule, a workflow step — or an event injected from another replica.
|
|
501
|
+
* For `origin: 'injected'` the honest answer is the ORIGINATING replica's
|
|
502
|
+
* trace or nothing at all; stamping the receiving replica's ambient trace
|
|
503
|
+
* would attribute a remote write to a local request, which is worse than
|
|
504
|
+
* silence.
|
|
505
|
+
*/
|
|
506
|
+
readonly traceId?: string;
|
|
507
|
+
/**
|
|
508
|
+
* The acting identity behind the write — the same one `audit()` stamps into
|
|
509
|
+
* `createdBy`/`updatedBy`, so a row's stamp and its change event never
|
|
510
|
+
* disagree. For an API-key subject that is the person, not the credential;
|
|
511
|
+
* which key was used is recoverable from the audit row sharing `traceId`.
|
|
512
|
+
*
|
|
513
|
+
* `null` = a resolved subject with no acting user (anonymous / system).
|
|
514
|
+
* `undefined` = no request context at all. The two are different facts.
|
|
515
|
+
*/
|
|
516
|
+
readonly subjectId?: string | null;
|
|
467
517
|
};
|
|
468
518
|
|
|
469
519
|
/**
|
|
@@ -478,9 +528,22 @@ export declare type ChangeEvent = {
|
|
|
478
528
|
*/
|
|
479
529
|
export declare type ChangeStrategy = 'inline' | 'cdc';
|
|
480
530
|
|
|
531
|
+
/**
|
|
532
|
+
* Claim the attribution for an echo, or `undefined` if this write did not
|
|
533
|
+
* originate here.
|
|
534
|
+
*
|
|
535
|
+
* Claiming REMOVES it: one registration answers exactly one echo, so a second
|
|
536
|
+
* echo of a different write to the same row cannot inherit the first's
|
|
537
|
+
* identity.
|
|
538
|
+
*/
|
|
539
|
+
export declare const claimPendingAttribution: (key: string) => WriteAttribution | undefined;
|
|
540
|
+
|
|
481
541
|
/** Wipe — used by tests + the dev-loop hot-reload. */
|
|
482
542
|
export declare const clearEnumRenames: () => void;
|
|
483
543
|
|
|
544
|
+
/** Test seam. */
|
|
545
|
+
export declare const clearPendingAttribution: () => void;
|
|
546
|
+
|
|
484
547
|
/** Wipe the registry. Used by tests + the dev-loop hot-reload. */
|
|
485
548
|
export declare const clearRelationsRegistry: () => void;
|
|
486
549
|
|
|
@@ -492,6 +555,9 @@ export declare const clearRetentions: () => void;
|
|
|
492
555
|
/** Wipe — used by tests + the dev-loop hot-reload. */
|
|
493
556
|
export declare const clearTableRegistry: () => void;
|
|
494
557
|
|
|
558
|
+
/** Test seam / dev-restart seam. */
|
|
559
|
+
export declare const clearWriteRecorders: () => void;
|
|
560
|
+
|
|
495
561
|
/**
|
|
496
562
|
* The value to bind for `column`, given what the table declares it as.
|
|
497
563
|
* Returns `value` unchanged whenever nothing unambiguous applies — including
|
|
@@ -564,8 +630,25 @@ export declare class ColumnBuilder<TsType, Type extends ColumnType, HasDefault e
|
|
|
564
630
|
* keyHash: text().serverOnly(),
|
|
565
631
|
*
|
|
566
632
|
* A column is exposed to both server and client by default; this opts it OUT of
|
|
567
|
-
* the client. The `crud.*` read helpers strip it from every returned row
|
|
568
|
-
*
|
|
633
|
+
* the client. The `crud.*` read helpers strip it from every returned row.
|
|
634
|
+
*
|
|
635
|
+
* A HAND-WRITTEN query output is the case `crud.*` cannot cover, so an audit
|
|
636
|
+
* checks it: a wire-reachable query whose `source` table carries a serverOnly
|
|
637
|
+
* column that its output DECLARES. What that costs depends on the command,
|
|
638
|
+
* and the difference is deliberate — a refused boot mid-edit is worse than
|
|
639
|
+
* the bug; in production it is not:
|
|
640
|
+
*
|
|
641
|
+
* | command | on a leak |
|
|
642
|
+
* |--------------------------------|------------------------------------|
|
|
643
|
+
* | `voltro serve` | **boot fails** |
|
|
644
|
+
* | `voltro doctor` | **exits non-zero** |
|
|
645
|
+
* | `voltro dev` | warns |
|
|
646
|
+
*
|
|
647
|
+
* `VOLTRO_SERVER_ONLY=strict` makes `voltro dev` fail too; `=warn` downgrades
|
|
648
|
+
* serve; `=off` silences it. (`voltro check` does NOT run this — it has a
|
|
649
|
+
* live-api mode with no access to your table definitions, and a rule that
|
|
650
|
+
* fires in one of its two modes would be worse than one that fires in
|
|
651
|
+
* neither.)
|
|
569
652
|
*/
|
|
570
653
|
serverOnly(): this;
|
|
571
654
|
/**
|
|
@@ -1005,8 +1088,9 @@ export declare interface ColumnDefinition<TsType, Type extends ColumnType = Colu
|
|
|
1005
1088
|
* but not encrypted; a private note the owner may read is encrypted but not
|
|
1006
1089
|
* serverOnly. The default is exposed to BOTH server and client; `.serverOnly()`
|
|
1007
1090
|
* opts a column OUT of the client. The table-aware read paths (`crud.*`) strip
|
|
1008
|
-
* it automatically, and
|
|
1009
|
-
*
|
|
1091
|
+
* it automatically, and an audit flags a wire-reachable query whose `source`
|
|
1092
|
+
* table carries a serverOnly column that its output declares. What that audit
|
|
1093
|
+
* COSTS is per command — see `serverOnly()` below for the matrix.
|
|
1010
1094
|
*/
|
|
1011
1095
|
readonly serverOnly?: boolean;
|
|
1012
1096
|
/**
|
|
@@ -1328,6 +1412,9 @@ export declare const count: (alias?: string) => AggregateColumn;
|
|
|
1328
1412
|
|
|
1329
1413
|
export declare const countDistinct: (column: string, alias?: string) => AggregateColumn;
|
|
1330
1414
|
|
|
1415
|
+
/** The attribution on the current async stack, or `undefined` outside a request. */
|
|
1416
|
+
export declare const currentWriteAttribution: () => WriteAttribution | undefined;
|
|
1417
|
+
|
|
1331
1418
|
/**
|
|
1332
1419
|
* The application's `database` handle. Indexed by table name; each entry is
|
|
1333
1420
|
* a `Query` over the table's row type AND a `using()` constraint over the
|
|
@@ -2799,6 +2886,24 @@ export declare interface ManyToManyRelation {
|
|
|
2799
2886
|
readonly targetKey: string;
|
|
2800
2887
|
}
|
|
2801
2888
|
|
|
2889
|
+
/**
|
|
2890
|
+
* A predicate that matches NO row — the "this caller may see nothing" case a
|
|
2891
|
+
* row filter needs and the one it is most expensive to get wrong.
|
|
2892
|
+
*
|
|
2893
|
+
* The natural spelling is `inSet(col, [])`, and in many query builders that is
|
|
2894
|
+
* the dangerous one: an empty `IN ()` gets dropped, and a DROPPED predicate does
|
|
2895
|
+
* not narrow — it WIDENS, to the whole tenant. Here it is safe (the SQL
|
|
2896
|
+
* compiler emits `FALSE`, the in-memory evaluator returns false, and both are
|
|
2897
|
+
* pinned by tests) but an adopting team wrote `eq('id', '')` instead, because
|
|
2898
|
+
* they could not tell from the outside and would not bet a visibility rule on
|
|
2899
|
+
* it. They were right not to.
|
|
2900
|
+
*
|
|
2901
|
+
* So this exists to say the thing rather than encode it: a reviewer reading
|
|
2902
|
+
* `MATCHES_NO_ROWS` sees the intent, where `eq('id', '')` looks like a bug and
|
|
2903
|
+
* `inSet(col, [])` looks like an oversight.
|
|
2904
|
+
*/
|
|
2905
|
+
export declare const MATCHES_NO_ROWS: PredicateLeaf;
|
|
2906
|
+
|
|
2802
2907
|
/**
|
|
2803
2908
|
* A mixin's materialised, SERIALIZABLE field shape: `FieldDefinitions<Input>`
|
|
2804
2909
|
* run through `Resolve` so it's emitted as the concrete
|
|
@@ -3154,6 +3259,9 @@ export declare const paginateBy: (descriptor: QueryDescriptor, column: string, c
|
|
|
3154
3259
|
*/
|
|
3155
3260
|
export declare const paginateById: (descriptor: QueryDescriptor, cursor: string | undefined, limit: number) => QueryDescriptor;
|
|
3156
3261
|
|
|
3262
|
+
/** Test seam — how many registrations are outstanding. */
|
|
3263
|
+
export declare const pendingAttributionCount: () => number;
|
|
3264
|
+
|
|
3157
3265
|
/**
|
|
3158
3266
|
* Dispatch the provision plan by mechanism — the instant Neon CoW branch-create,
|
|
3159
3267
|
* or the portable namespace snapshot fan-out. One call site for the cloud
|
|
@@ -4039,6 +4147,29 @@ declare interface RawSqlFragment {
|
|
|
4039
4147
|
* would truncate. */
|
|
4040
4148
|
export declare const real: () => ColumnBuilder<number, "real", boolean>;
|
|
4041
4149
|
|
|
4150
|
+
/** One write, as the store sees it at the moment it happens. */
|
|
4151
|
+
export declare interface RecordedWrite {
|
|
4152
|
+
readonly table: string;
|
|
4153
|
+
readonly op: 'insert' | 'update' | 'delete';
|
|
4154
|
+
/** Row AFTER the write — present on insert/update, null on delete. */
|
|
4155
|
+
readonly next: Row | null;
|
|
4156
|
+
/** Row BEFORE the write, where the store had it. */
|
|
4157
|
+
readonly prev: Row | null;
|
|
4158
|
+
/** Request identity, when the write had a request behind it. */
|
|
4159
|
+
readonly traceId?: string | undefined;
|
|
4160
|
+
readonly subjectId?: string | null | undefined;
|
|
4161
|
+
}
|
|
4162
|
+
|
|
4163
|
+
/**
|
|
4164
|
+
* Is ANY recorder interested in `table`?
|
|
4165
|
+
*
|
|
4166
|
+
* The stores call this on every write, so it is the hot path: a store with no
|
|
4167
|
+
* recorders registered pays one `Map.size` check and nothing else, and never
|
|
4168
|
+
* awaits. Keeping `routeEvent`'s cost at zero for the default configuration is
|
|
4169
|
+
* what makes shipping this as an opt-in honest.
|
|
4170
|
+
*/
|
|
4171
|
+
export declare const recordsTable: (table: string) => boolean;
|
|
4172
|
+
|
|
4042
4173
|
/**
|
|
4043
4174
|
* Foreign-key reference to another table's id column.
|
|
4044
4175
|
*
|
|
@@ -4093,6 +4224,15 @@ export declare const registerCoreTables: (options: CoreTableRegistration) => voi
|
|
|
4093
4224
|
*/
|
|
4094
4225
|
export declare const registerDiscoveredRelations: (moduleExports: Record<string, unknown>) => number;
|
|
4095
4226
|
|
|
4227
|
+
/**
|
|
4228
|
+
* Remember this write's attribution so its transport echo can be re-attributed.
|
|
4229
|
+
*
|
|
4230
|
+
* No-op when there is nothing to remember — a write with no request behind it
|
|
4231
|
+
* must stay unattributed, and registering an empty entry would make the queue
|
|
4232
|
+
* lie about which echo belongs to which write.
|
|
4233
|
+
*/
|
|
4234
|
+
export declare const registerPendingAttribution: (key: string, attribution: WriteAttribution) => void;
|
|
4235
|
+
|
|
4096
4236
|
/**
|
|
4097
4237
|
* Register a single `relations()` spec. Multiple specs on the same
|
|
4098
4238
|
* source MERGE — a second spec adds to the first one. A name
|
|
@@ -4113,6 +4253,14 @@ export declare const registerRetention: (spec: RetentionSpec) => void;
|
|
|
4113
4253
|
*/
|
|
4114
4254
|
export declare const registerTable: (table: TableLike) => void;
|
|
4115
4255
|
|
|
4256
|
+
/** Register (or replace) a recorder. Replacing is what a plugin re-activating
|
|
4257
|
+
* on a dev restart needs. */
|
|
4258
|
+
export declare const registerWriteRecorder: (id: string, registration: {
|
|
4259
|
+
readonly recorder: WriteRecorder;
|
|
4260
|
+
readonly tables: Iterable<string>;
|
|
4261
|
+
readonly ownTables: Iterable<string>;
|
|
4262
|
+
}) => void;
|
|
4263
|
+
|
|
4116
4264
|
export declare type Relation = OneRelation | ManyRelation | ManyToManyRelation;
|
|
4117
4265
|
|
|
4118
4266
|
/**
|
|
@@ -4332,6 +4480,23 @@ export declare const rowSchema: <T extends TableLike>(table: T, options?: {
|
|
|
4332
4480
|
readonly omit?: ReadonlyArray<string>;
|
|
4333
4481
|
}) => Schema.Schema.Any;
|
|
4334
4482
|
|
|
4483
|
+
/**
|
|
4484
|
+
* Run `fn` with `attribution` active for its whole (sync + awaited) execution.
|
|
4485
|
+
*
|
|
4486
|
+
* Nesting is last-wins by design: a plugin that re-enters the store inside a
|
|
4487
|
+
* handler is still acting under that request.
|
|
4488
|
+
*/
|
|
4489
|
+
export declare const runWithWriteAttribution: <T>(attribution: WriteAttribution, fn: () => T) => T;
|
|
4490
|
+
|
|
4491
|
+
/**
|
|
4492
|
+
* Run every recorder that claims `write.table`.
|
|
4493
|
+
*
|
|
4494
|
+
* Deliberately NOT error-swallowing — see the header. Swallowing here would
|
|
4495
|
+
* reproduce the post-commit hole while looking like it had closed it, which is
|
|
4496
|
+
* the worse of the two by a distance.
|
|
4497
|
+
*/
|
|
4498
|
+
export declare const runWriteRecorders: (port: TxnRecorderPort, write: RecordedWrite) => Promise<void>;
|
|
4499
|
+
|
|
4335
4500
|
/**
|
|
4336
4501
|
* Lower a tenant id to the safe identifier fragment used inside a
|
|
4337
4502
|
* namespace name. ONLY `[a-z0-9_]` survive; every other character —
|
|
@@ -5334,6 +5499,26 @@ export declare const timestampMs: Schema.Schema<Date, number>;
|
|
|
5334
5499
|
*/
|
|
5335
5500
|
export declare const timestampMsOrNull: Schema.Schema<Date | null, number | null>;
|
|
5336
5501
|
|
|
5502
|
+
/** Append one row inside the caller's transaction. Insert-only by design. */
|
|
5503
|
+
export declare type TxnAppend = (table: string, row: Row) => Promise<void>;
|
|
5504
|
+
|
|
5505
|
+
/**
|
|
5506
|
+
* `MAX(column)` over the rows matching an equality filter, on the caller's
|
|
5507
|
+
* transaction connection. `null` when nothing matches.
|
|
5508
|
+
*
|
|
5509
|
+
* Equality-only and single-aggregate on purpose — see the header. It exists so
|
|
5510
|
+
* an append-only trail can number its own entries; it is not a query facility,
|
|
5511
|
+
* and widening it into one would give back exactly the read-modify-write reach
|
|
5512
|
+
* this seam was shaped to withhold.
|
|
5513
|
+
*/
|
|
5514
|
+
export declare type TxnMaxOf = (table: string, column: string, where: Readonly<Record<string, string>>) => Promise<number | null>;
|
|
5515
|
+
|
|
5516
|
+
/** Everything a recorder may do inside the caller's transaction. */
|
|
5517
|
+
export declare interface TxnRecorderPort {
|
|
5518
|
+
readonly append: TxnAppend;
|
|
5519
|
+
readonly maxOf: TxnMaxOf;
|
|
5520
|
+
}
|
|
5521
|
+
|
|
5337
5522
|
/** The minimal store surface `insertRow` needs — satisfied by `ctx.store`. */
|
|
5338
5523
|
export declare interface TypedInsertStore {
|
|
5339
5524
|
readonly insert: (table: string, row: Row) => Promise<Row>;
|
|
@@ -5387,6 +5572,8 @@ export declare interface UniqueSpec {
|
|
|
5387
5572
|
readonly dedup?: 'fail' | 'suffix-counter' | Statement.Fragment;
|
|
5388
5573
|
}
|
|
5389
5574
|
|
|
5575
|
+
export declare const unregisterWriteRecorder: (id: string) => void;
|
|
5576
|
+
|
|
5390
5577
|
/**
|
|
5391
5578
|
* Bulk-update rows matching `where`, typed against the table: the patch is a
|
|
5392
5579
|
* `Partial<InferRow<T>>`, so a misspelled column or a value of the wrong type
|
|
@@ -5733,4 +5920,22 @@ export declare interface WindowSpec {
|
|
|
5733
5920
|
/** Tree of relations to eager-load. */
|
|
5734
5921
|
export declare type WithSpec = Readonly<Record<string, true | EagerLoadSpec>>;
|
|
5735
5922
|
|
|
5923
|
+
/**
|
|
5924
|
+
* Who is writing, and under which call.
|
|
5925
|
+
*
|
|
5926
|
+
* `subjectId` is the ACTING identity — the same one the `audit()` mixin stamps
|
|
5927
|
+
* into `createdBy`/`updatedBy`. For an API key that is the person behind the
|
|
5928
|
+
* credential, not the credential: two answers to "who wrote this row" on one
|
|
5929
|
+
* write would be worse than one. Which KEY was used is recoverable from the
|
|
5930
|
+
* audit sink row sharing this `traceId` — that is what the bridge is for.
|
|
5931
|
+
*/
|
|
5932
|
+
export declare interface WriteAttribution {
|
|
5933
|
+
readonly traceId?: string;
|
|
5934
|
+
/** `null` is meaningful: a resolved subject with no acting user (an
|
|
5935
|
+
* anonymous or system call), as distinct from `undefined` = no request. */
|
|
5936
|
+
readonly subjectId?: string | null;
|
|
5937
|
+
}
|
|
5938
|
+
|
|
5939
|
+
export declare type WriteRecorder = (port: TxnRecorderPort, write: RecordedWrite) => Promise<void>;
|
|
5940
|
+
|
|
5736
5941
|
export { }
|