@voltro/database 0.27.0 → 0.29.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 +513 -0
- package/dist/{frameworkLiveTables-CS_Hyhgz.js → frameworkLiveTables-CMyumigs.js} +169 -125
- package/dist/index.d.ts +305 -0
- package/dist/index.js +634 -594
- package/dist/sql.d.ts +649 -0
- package/dist/sql.js +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -488,6 +488,9 @@ export declare interface CdcConfig {
|
|
|
488
488
|
* warns. See this file's header for why it is 512 and why it is not `0`. */
|
|
489
489
|
export declare const CHANGE_LISTENER_CEILING = 512;
|
|
490
490
|
|
|
491
|
+
/** A row was removed from the table. `new` is null, always. */
|
|
492
|
+
export declare const changeDelete: (table: string, row: Row, meta?: ChangeEventMeta) => ChangeEvent;
|
|
493
|
+
|
|
491
494
|
/**
|
|
492
495
|
* A row-change event emitted by the data store (in-memory) or the CDC
|
|
493
496
|
* consumer (Postgres logical replication). Drives the reactive engine.
|
|
@@ -548,6 +551,29 @@ export declare type ChangeEvent = {
|
|
|
548
551
|
readonly procedure?: string;
|
|
549
552
|
};
|
|
550
553
|
|
|
554
|
+
/** Attribution and origin, identical on every constructor. */
|
|
555
|
+
export declare type ChangeEventMeta = Pick<ChangeEvent, 'origin' | 'traceId' | 'subjectId' | 'procedure'>;
|
|
556
|
+
|
|
557
|
+
/** A row was created. `old` is null, always. */
|
|
558
|
+
export declare const changeInsert: (table: string, row: Row, meta?: ChangeEventMeta) => ChangeEvent;
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* A row was SOFT-deleted — an `update` that sets `deletedAt`.
|
|
562
|
+
*
|
|
563
|
+
* Not a `delete`, and that is the point. A test reaching for `changeDelete`
|
|
564
|
+
* here is describing a hard delete, which is a different event with different
|
|
565
|
+
* consequences: a soft delete is reversible, so anything cascading off it
|
|
566
|
+
* destroys rows a restore cannot bring back.
|
|
567
|
+
*
|
|
568
|
+
* `at` defaults to a fixed instant rather than `new Date()`: the value is never
|
|
569
|
+
* what a soft-delete rule reads — the null → non-null TRANSITION is — and a
|
|
570
|
+
* moving default makes a test that snapshots the event flake for no reason.
|
|
571
|
+
*/
|
|
572
|
+
export declare const changeSoftDelete: (table: string, row: Row, opts?: {
|
|
573
|
+
readonly at?: string | Date;
|
|
574
|
+
readonly column?: string;
|
|
575
|
+
} & ChangeEventMeta) => ChangeEvent;
|
|
576
|
+
|
|
551
577
|
/**
|
|
552
578
|
* Whether the DataStore emits ChangeEvents inline from each write
|
|
553
579
|
* (`inline`) or via the dialect's CDC consumer (`cdc`).
|
|
@@ -560,6 +586,9 @@ export declare type ChangeEvent = {
|
|
|
560
586
|
*/
|
|
561
587
|
export declare type ChangeStrategy = 'inline' | 'cdc';
|
|
562
588
|
|
|
589
|
+
/** A row changed. Both sides are present, because an update has both. */
|
|
590
|
+
export declare const changeUpdate: (table: string, before: Row, after: Row, meta?: ChangeEventMeta) => ChangeEvent;
|
|
591
|
+
|
|
563
592
|
/**
|
|
564
593
|
* Claim the attribution for an echo, or `undefined` if this write did not
|
|
565
594
|
* originate here.
|
|
@@ -683,6 +712,33 @@ export declare class ColumnBuilder<TsType, Type extends ColumnType, HasDefault e
|
|
|
683
712
|
* neither.)
|
|
684
713
|
*/
|
|
685
714
|
serverOnly(): this;
|
|
715
|
+
/**
|
|
716
|
+
* Restrict this column's WIRE visibility to subjects holding one of `scopes`.
|
|
717
|
+
* The graded middle of the exposure axis: a plain column is visible to every
|
|
718
|
+
* client, `.serverOnly()` hides it from every client, and
|
|
719
|
+
* `.readableBy('billing:read')` strips it from wire OUTPUT for any subject that
|
|
720
|
+
* holds NONE of the listed scopes while leaving it for one that holds ANY.
|
|
721
|
+
*
|
|
722
|
+
* amountCents: integer().readableBy('billing:read'),
|
|
723
|
+
* ssn: text().readableBy('hr:read', 'admin:pii'),
|
|
724
|
+
*
|
|
725
|
+
* The scope strings are the SAME vocabulary as `guards:` / `ctx.access`, and
|
|
726
|
+
* are matched against the subject's EFFECTIVE scopes (raw subject scopes ∪ any
|
|
727
|
+
* rbac role-derived scopes). `admin:full` sees every `.readableBy()` column,
|
|
728
|
+
* exactly as it satisfies every guard. The strip runs at the runtime wire
|
|
729
|
+
* boundary (the Dispatcher's read chokepoint, shared with `.serverOnly()`), so
|
|
730
|
+
* server-internal `ctx.query` reads still see the value — this is an exposure
|
|
731
|
+
* concern, not an at-rest one.
|
|
732
|
+
*
|
|
733
|
+
* Declaration rules, both rejected at the schema file:
|
|
734
|
+
* - at least ONE scope is required — `.readableBy()` with none would mean
|
|
735
|
+
* "readable by nobody", which is `.serverOnly()`; the empty call is
|
|
736
|
+
* almost always that mistake, so it throws rather than silently behaving
|
|
737
|
+
* like `.serverOnly()`;
|
|
738
|
+
* - each scope must be a non-blank string (a typo'd `''` never unlocks
|
|
739
|
+
* anything, so it is refused up front).
|
|
740
|
+
*/
|
|
741
|
+
readableBy(...scopes: ReadonlyArray<string>): this;
|
|
686
742
|
/**
|
|
687
743
|
* Mark this column as the row's OPTIMISTIC-CONCURRENCY version.
|
|
688
744
|
*
|
|
@@ -733,6 +789,13 @@ export declare class ColumnBuilder<TsType, Type extends ColumnType, HasDefault e
|
|
|
733
789
|
*
|
|
734
790
|
* Pairs with {@link safe}: under a masking profile, every column in an
|
|
735
791
|
* exported table must be one or the other, or the export refuses (fail-closed).
|
|
792
|
+
*
|
|
793
|
+
* NOT a wire-exposure or authorization control. This is the EXPORT-masking axis
|
|
794
|
+
* only; it never strips the column from a client response and never gates a
|
|
795
|
+
* read. To keep a column off the wire, use {@link serverOnly} (hidden from every
|
|
796
|
+
* client) or {@link readableBy} (hidden unless the subject holds a scope) — those
|
|
797
|
+
* are enforced at the runtime wire boundary. Marking a PII column `.sensitive()`
|
|
798
|
+
* does NOT stop it reaching an anonymous caller; that is a job for `.readableBy()`.
|
|
736
799
|
*/
|
|
737
800
|
sensitive(cls: SensitivityClass): this;
|
|
738
801
|
/**
|
|
@@ -1153,6 +1216,45 @@ export declare interface ColumnDefinition<TsType, Type extends ColumnType = Colu
|
|
|
1153
1216
|
* COSTS is per command — see `serverOnly()` below for the matrix.
|
|
1154
1217
|
*/
|
|
1155
1218
|
readonly serverOnly?: boolean;
|
|
1219
|
+
/**
|
|
1220
|
+
* Field-level read scopes, set by `.readableBy(...scopes)`. A NON-EMPTY list
|
|
1221
|
+
* of scope strings; the column is stripped from wire OUTPUT for any subject
|
|
1222
|
+
* that holds NONE of them, and present for a subject holding at least ONE.
|
|
1223
|
+
* This is the graded middle of the exposure axis: `.serverOnly()` hides a
|
|
1224
|
+
* column from EVERYONE on the wire, a plain column is visible to everyone,
|
|
1225
|
+
* and `.readableBy('billing:read')` is visible only to callers scoped for it.
|
|
1226
|
+
*
|
|
1227
|
+
* The scope strings are the SAME vocabulary as `guards:` / `ctx.access` — they
|
|
1228
|
+
* are checked against the subject's EFFECTIVE scope set (raw subject scopes ∪
|
|
1229
|
+
* any rbac role-derived scopes), and the `admin:full` bypass sees every
|
|
1230
|
+
* `.readableBy()` column, exactly as it satisfies every guard. Empty here is a
|
|
1231
|
+
* declaration error (`.readableBy()` with no scope would mean "nobody" — use
|
|
1232
|
+
* `.serverOnly()` for that), so the field is never present as `[]`.
|
|
1233
|
+
*
|
|
1234
|
+
* Orthogonal to `.encrypted()` (storage-at-rest) and to `.sensitive()` /
|
|
1235
|
+
* `.safe()` (export-time masking): this is purely the WIRE-exposure axis.
|
|
1236
|
+
*/
|
|
1237
|
+
readonly readableBy?: ReadonlyArray<string>;
|
|
1238
|
+
/**
|
|
1239
|
+
* CRDT-managed marker, set by the `crdtText()` column constructor. When `true`
|
|
1240
|
+
* the column holds an ENCODED CRDT state (opaque bytes — a Yjs update blob
|
|
1241
|
+
* behind `@voltro/local-first`'s swappable backend) rather than a plain value.
|
|
1242
|
+
*
|
|
1243
|
+
* It changes NOTHING about the column's DDL or how the declarative differ
|
|
1244
|
+
* treats it: the storage type is `bytes` (BYTEA / BLOB / LONGBLOB /
|
|
1245
|
+
* VARBINARY), a normal storable type, and the planner compares it structurally
|
|
1246
|
+
* like any other `bytes` column — introspection never reports this flag, and
|
|
1247
|
+
* `sameColumnShape` never reads it, so a `crdtText()` column round-trips
|
|
1248
|
+
* through a plan with zero churn (no phantom re-plan).
|
|
1249
|
+
*
|
|
1250
|
+
* What it DOES drive lives above the DDL, in the runtime write path: the
|
|
1251
|
+
* MutationStore reads it (via the SchemaRegistry) to run the AUTHORITATIVE
|
|
1252
|
+
* server-side merge on write — an incoming encoded update is folded into the
|
|
1253
|
+
* stored state with `mergeCrdtStates` before the row is written, so two
|
|
1254
|
+
* concurrent clients converge. Orthogonal to every other flag here; a CRDT
|
|
1255
|
+
* column is just `bytes` with server-merge semantics.
|
|
1256
|
+
*/
|
|
1257
|
+
readonly crdtManaged?: boolean;
|
|
1156
1258
|
/**
|
|
1157
1259
|
* Optimistic-concurrency marker, set by `.version()`.
|
|
1158
1260
|
*
|
|
@@ -1514,6 +1616,49 @@ export declare const count: (alias?: string) => AggregateColumn;
|
|
|
1514
1616
|
|
|
1515
1617
|
export declare const countDistinct: (column: string, alias?: string) => AggregateColumn;
|
|
1516
1618
|
|
|
1619
|
+
/**
|
|
1620
|
+
* The names of a table's CRDT-managed columns (`crdtText()`). These store an
|
|
1621
|
+
* encoded CRDT state as `bytes` and get an authoritative SERVER-SIDE merge on
|
|
1622
|
+
* every write (the runtime folds the incoming update into the stored state
|
|
1623
|
+
* before writing). Pure — no store, no context; the runtime's SchemaRegistry
|
|
1624
|
+
* uses the same signal to gate `mergeCrdtStates` on the write path.
|
|
1625
|
+
*/
|
|
1626
|
+
export declare const crdtManagedColumns: (table: TableLike) => ReadonlyArray<string>;
|
|
1627
|
+
|
|
1628
|
+
/**
|
|
1629
|
+
* CRDT-managed text column — a field whose value is a CONFLICT-FREE replicated
|
|
1630
|
+
* text document rather than a plain string. Two clients editing it at the same
|
|
1631
|
+
* time converge without a last-write-wins loser.
|
|
1632
|
+
*
|
|
1633
|
+
* ```ts
|
|
1634
|
+
* export const documents = table('documents', {
|
|
1635
|
+
* id: id(),
|
|
1636
|
+
* title: text(),
|
|
1637
|
+
* body: crdtText(), // CRDT-managed field
|
|
1638
|
+
* })
|
|
1639
|
+
* ```
|
|
1640
|
+
*
|
|
1641
|
+
* STORAGE. The column stores the ENCODED CRDT state as `bytes` (BYTEA / BLOB /
|
|
1642
|
+
* LONGBLOB / VARBINARY) — an opaque Yjs update blob produced by
|
|
1643
|
+
* `@voltro/local-first`. There is no special DDL: to the declarative differ it
|
|
1644
|
+
* is an ordinary nullable `bytes` column, so it plans and round-trips like any
|
|
1645
|
+
* other (no phantom re-plan). The row type is `Uint8Array | null`; decode it to
|
|
1646
|
+
* a string with `@voltro/local-first`'s `decodeCrdtText(state)`, and produce
|
|
1647
|
+
* writes with a `crdtText()` handle's `.encode()`.
|
|
1648
|
+
*
|
|
1649
|
+
* NULLABLE by design: an insert may omit the column (no document yet), and the
|
|
1650
|
+
* first write establishes the state. A stored `null` is treated as the empty
|
|
1651
|
+
* document by the server merge.
|
|
1652
|
+
*
|
|
1653
|
+
* SERVER MERGE. The authoritative merge runs in the runtime on write: the
|
|
1654
|
+
* MutationStore folds an incoming encoded update into the stored state with
|
|
1655
|
+
* `mergeCrdtStates` before writing, and the reactive engine broadcasts the
|
|
1656
|
+
* merged result. That is what makes the column converge; the browser-safe merge
|
|
1657
|
+
* primitives it uses live in `@voltro/local-first` and never import the
|
|
1658
|
+
* `database` handle.
|
|
1659
|
+
*/
|
|
1660
|
+
export declare const crdtText: () => ColumnBuilder<Uint8Array | null, "bytes">;
|
|
1661
|
+
|
|
1517
1662
|
/** The attribution on the current async stack, or `undefined` outside a request. */
|
|
1518
1663
|
export declare const currentWriteAttribution: () => WriteAttribution | undefined;
|
|
1519
1664
|
|
|
@@ -2823,6 +2968,13 @@ export declare const isFileMigration: (value: unknown) => value is FileMigration
|
|
|
2823
2968
|
* but no user schema declares — never plan a drop for it. */
|
|
2824
2969
|
export declare const isFrameworkOwnedLiveTable: (name: string) => boolean;
|
|
2825
2970
|
|
|
2971
|
+
/**
|
|
2972
|
+
* Whether a table carries `localFirst()`. Pure reflection over its applied
|
|
2973
|
+
* mixins — the same signal the runtime registry uses, exposed for tooling and
|
|
2974
|
+
* for a client that builds its sync set from the schema.
|
|
2975
|
+
*/
|
|
2976
|
+
export declare const isLocalFirst: (table: LocalFirstMarked) => boolean;
|
|
2977
|
+
|
|
2826
2978
|
export declare const isMigrationDefinition: (value: unknown) => value is MigrationDefinition;
|
|
2827
2979
|
|
|
2828
2980
|
/**
|
|
@@ -2991,6 +3143,38 @@ export declare type LeafOperator = 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' |
|
|
|
2991
3143
|
/** Every currently-registered retention spec. The boot sweep reads this each tick. */
|
|
2992
3144
|
export declare const listRetentions: () => ReadonlyArray<RetentionSpec>;
|
|
2993
3145
|
|
|
3146
|
+
/**
|
|
3147
|
+
* Opt a table into local-first behaviour.
|
|
3148
|
+
*
|
|
3149
|
+
* ```ts
|
|
3150
|
+
* export const documents = table('documents', {
|
|
3151
|
+
* id: id(),
|
|
3152
|
+
* title: text(),
|
|
3153
|
+
* body: crdtText(), // CRDT-managed field
|
|
3154
|
+
* }).with(timestamps, tenant, localFirst())
|
|
3155
|
+
* ```
|
|
3156
|
+
*
|
|
3157
|
+
* Adds no column — it is a marker the framework reflects on to sync + persist
|
|
3158
|
+
* the table client-side. Compose it with `crdtText()` columns for converging
|
|
3159
|
+
* rich fields; plain columns on a local-first table sync last-write-wins.
|
|
3160
|
+
*/
|
|
3161
|
+
export declare const localFirst: () => MixinDefinition<{}>;
|
|
3162
|
+
|
|
3163
|
+
/** The minimum shape {@link isLocalFirst} reflects on — a table's applied
|
|
3164
|
+
* mixins. Compatible with `Table` from `@voltro/database`. */
|
|
3165
|
+
export declare interface LocalFirstMarked {
|
|
3166
|
+
readonly appliedMixins: ReadonlyArray<AnyMixin>;
|
|
3167
|
+
}
|
|
3168
|
+
|
|
3169
|
+
/**
|
|
3170
|
+
* The names of the local-first tables in a schema (a record of `table()`s).
|
|
3171
|
+
* The discovery surface a boot step / client-sync-set builder consumes to learn
|
|
3172
|
+
* which tables get a local mirror + sync loop.
|
|
3173
|
+
*/
|
|
3174
|
+
export declare const localFirstTables: (schema: Readonly<Record<string, LocalFirstMarked & {
|
|
3175
|
+
readonly tableName: string;
|
|
3176
|
+
}>>) => ReadonlyArray<string>;
|
|
3177
|
+
|
|
2994
3178
|
export declare const lt: <RowOf = Record<string, unknown>, K extends keyof RowOf & string = keyof RowOf & string>(column: K, value: RowOf[K]) => PredicateLeaf;
|
|
2995
3179
|
|
|
2996
3180
|
export declare const lte: <RowOf = Record<string, unknown>, K extends keyof RowOf & string = keyof RowOf & string>(column: K, value: RowOf[K]) => PredicateLeaf;
|
|
@@ -4385,6 +4569,25 @@ declare interface RawSqlFragment {
|
|
|
4385
4569
|
readonly dependsOn?: ReadonlyArray<string>;
|
|
4386
4570
|
}
|
|
4387
4571
|
|
|
4572
|
+
/** One `.readableBy(...scopes)` column: the field NAME plus the scope set that
|
|
4573
|
+
* makes it visible. */
|
|
4574
|
+
export declare interface ReadableByColumn {
|
|
4575
|
+
readonly column: string;
|
|
4576
|
+
/** Non-empty — a subject holding ANY of these sees the column. */
|
|
4577
|
+
readonly scopes: ReadonlyArray<string>;
|
|
4578
|
+
}
|
|
4579
|
+
|
|
4580
|
+
/**
|
|
4581
|
+
* The `.readableBy(...scopes)` columns of a table — the field-level read-scope
|
|
4582
|
+
* layer of the wire-exposure axis. Each entry names a column and the scope set
|
|
4583
|
+
* that unlocks it (a subject holding ANY of the scopes sees it; one holding
|
|
4584
|
+
* NONE has it stripped from wire output). Empty when the table declares none —
|
|
4585
|
+
* which lets the runtime strip stay subject-INDEPENDENT (and its read/diff memo
|
|
4586
|
+
* shared) for the common table that has no per-field permissions. Pure — no
|
|
4587
|
+
* store, no context.
|
|
4588
|
+
*/
|
|
4589
|
+
export declare const readableByColumns: (table: TableLike) => ReadonlyArray<ReadableByColumn>;
|
|
4590
|
+
|
|
4388
4591
|
/** Floating-point number. Postgres `DOUBLE PRECISION`, mysql/mariadb `DOUBLE`,
|
|
4389
4592
|
* mssql `FLOAT`, sqlite `REAL`. Drivers return JS numbers — no codec. Use for
|
|
4390
4593
|
* fractional values (ratings, percentages, measurements) where `integer()`
|
|
@@ -4768,6 +4971,50 @@ export declare const rowSchema: <T extends TableLike>(table: T, options?: {
|
|
|
4768
4971
|
readonly omit?: ReadonlyArray<string>;
|
|
4769
4972
|
}) => Schema.Schema.Any;
|
|
4770
4973
|
|
|
4974
|
+
/** The context a rule predicate is evaluated with. */
|
|
4975
|
+
export declare interface RuleContext {
|
|
4976
|
+
/** Transactional store — read other tables in the mutation's snapshot. */
|
|
4977
|
+
readonly store: RuleReadStore;
|
|
4978
|
+
/** The calling subject (tenant / user), for tenant-aware rules. */
|
|
4979
|
+
readonly subject: unknown;
|
|
4980
|
+
}
|
|
4981
|
+
|
|
4982
|
+
/**
|
|
4983
|
+
* What a rule predicate may return. `true` / `void` / `undefined` = the rule
|
|
4984
|
+
* HOLDS. `false` = violated with the rule's default detail. A
|
|
4985
|
+
* `RuleViolationDetail` object = violated, carrying per-row i18n params / a
|
|
4986
|
+
* field pointer for THIS specific row.
|
|
4987
|
+
*/
|
|
4988
|
+
export declare type RuleOutcome = boolean | void | RuleViolationDetail;
|
|
4989
|
+
|
|
4990
|
+
/**
|
|
4991
|
+
* A rule predicate over the POST-WRITE row. Runs inside the mutation
|
|
4992
|
+
* transaction. Return `true`/nothing when the invariant holds; return `false`
|
|
4993
|
+
* or a `RuleViolationDetail` to signal a violation. May be async (cross-table
|
|
4994
|
+
* reads).
|
|
4995
|
+
*/
|
|
4996
|
+
export declare type RulePredicate = (row: Record<string, unknown>, context: RuleContext) => RuleOutcome | Promise<RuleOutcome>;
|
|
4997
|
+
|
|
4998
|
+
/** Minimal READ surface (a subset of `DataStore`) handed to a rule predicate —
|
|
4999
|
+
* the SAME transactional store the mutation writes through, so cross-table
|
|
5000
|
+
* reads share its MVCC snapshot and cannot race the write the rule guards. */
|
|
5001
|
+
export declare interface RuleReadStore {
|
|
5002
|
+
query(descriptor: QueryDescriptor): Promise<ReadonlyArray<Record<string, unknown>>>;
|
|
5003
|
+
}
|
|
5004
|
+
|
|
5005
|
+
/** Severity of a business rule. `error` rolls the mutation back; `warning`
|
|
5006
|
+
* logs + audits but lets the write commit (useful during data migrations). */
|
|
5007
|
+
export declare type RuleSeverity = 'error' | 'warning';
|
|
5008
|
+
|
|
5009
|
+
export declare interface RuleViolationDetail {
|
|
5010
|
+
/** i18n params for the violation message (per this row). */
|
|
5011
|
+
readonly params?: Record<string, unknown>;
|
|
5012
|
+
/** Field path the violation pinpoints, if any. */
|
|
5013
|
+
readonly field?: string;
|
|
5014
|
+
/** Human-readable message (fallback / dev aid). */
|
|
5015
|
+
readonly message?: string;
|
|
5016
|
+
}
|
|
5017
|
+
|
|
4771
5018
|
/**
|
|
4772
5019
|
* Run `fn` with `attribution` active for its whole (sync + awaited) execution.
|
|
4773
5020
|
*
|
|
@@ -5278,6 +5525,12 @@ export declare interface Table<Name extends string, Fields extends Record<string
|
|
|
5278
5525
|
readonly appliedUniques: ReadonlyArray<TableUnique>;
|
|
5279
5526
|
readonly appliedFullText: ReadonlyArray<TableFullTextIndex>;
|
|
5280
5527
|
readonly appliedChecks: ReadonlyArray<TableCheck>;
|
|
5528
|
+
/**
|
|
5529
|
+
* The cross-table business rules DECLARED on this table, set by
|
|
5530
|
+
* `.rule(name, predicate)`. Evaluated by the runtime inside the mutation
|
|
5531
|
+
* transaction (see `TableRule`); they emit no DDL.
|
|
5532
|
+
*/
|
|
5533
|
+
readonly appliedRules: ReadonlyArray<TableRule>;
|
|
5281
5534
|
/**
|
|
5282
5535
|
* The name this table used to have, set by `.renamedFrom('old_name')`.
|
|
5283
5536
|
* Named apart from the METHOD that sets it — one interface cannot carry both.
|
|
@@ -5539,6 +5792,38 @@ export declare interface Table<Name extends string, Fields extends Record<string
|
|
|
5539
5792
|
* For a single-column check, `column.check(expr)` is the terser form.
|
|
5540
5793
|
*/
|
|
5541
5794
|
check: <const CkName extends string>(name: CkName, expr: string) => Table<Name, Fields, Reactive, IxNames>;
|
|
5795
|
+
/**
|
|
5796
|
+
* Declare a CROSS-TABLE business rule (S3). Unlike `.check(name, expr)` — a
|
|
5797
|
+
* single-row SQL `CHECK` the database enforces — a rule is a PREDICATE the
|
|
5798
|
+
* runtime evaluates INSIDE the mutation's transaction, after the write and
|
|
5799
|
+
* before commit. It can read OTHER tables in the same snapshot and rolls the
|
|
5800
|
+
* mutation back with a typed `BusinessRuleViolation` on failure.
|
|
5801
|
+
*
|
|
5802
|
+
* The predicate receives the POST-WRITE row and a `context` whose `store` is
|
|
5803
|
+
* the SAME transactional store the mutation wrote through. Return `true`
|
|
5804
|
+
* (or nothing) when the invariant holds; return `false` or a
|
|
5805
|
+
* `RuleViolationDetail` (i18n `params` / a `field` pointer) to signal a
|
|
5806
|
+
* violation.
|
|
5807
|
+
*
|
|
5808
|
+
* ```ts
|
|
5809
|
+
* table('invoices', { id: id(), total: integer() }).rule(
|
|
5810
|
+
* 'totalMatchesLineItems',
|
|
5811
|
+
* async (row, { store }) => {
|
|
5812
|
+
* const items = await store.query(
|
|
5813
|
+
* lineItems.where(eq('invoiceId', row.id)).descriptor,
|
|
5814
|
+
* )
|
|
5815
|
+
* const sum = items.reduce((a, l) => a + (l.amount as number), 0)
|
|
5816
|
+
* return sum === row.total || { params: { computed: sum, declared: row.total } }
|
|
5817
|
+
* },
|
|
5818
|
+
* )
|
|
5819
|
+
* ```
|
|
5820
|
+
*
|
|
5821
|
+
* `severity: 'warning'` logs + audits the violation but lets the write
|
|
5822
|
+
* commit (useful during data migration); the default `'error'` rolls back.
|
|
5823
|
+
*/
|
|
5824
|
+
rule: <const RuleName extends string>(name: RuleName, predicate: RulePredicate, options?: {
|
|
5825
|
+
readonly severity?: RuleSeverity;
|
|
5826
|
+
}) => Table<Name, Fields, Reactive, IxNames>;
|
|
5542
5827
|
/**
|
|
5543
5828
|
* Declare a full-text-search index (S7). One method, three back-
|
|
5544
5829
|
* ends:
|
|
@@ -5757,6 +6042,21 @@ export declare interface TableLike {
|
|
|
5757
6042
|
* Internally we always store the thunk form. */
|
|
5758
6043
|
declare type TableRef = TableLike | (() => TableLike);
|
|
5759
6044
|
|
|
6045
|
+
/**
|
|
6046
|
+
* A named cross-table business rule, attached by `.rule(name, predicate)`.
|
|
6047
|
+
*
|
|
6048
|
+
* UNLIKE `.check(name, expr)` — a single-row SQL `CHECK` emitted as DDL and
|
|
6049
|
+
* enforced by the database — a rule is a PREDICATE the runtime evaluates INSIDE
|
|
6050
|
+
* the mutation's transaction, AFTER the write and BEFORE commit. It can read
|
|
6051
|
+
* OTHER tables in the same MVCC snapshot and rolls the whole mutation back
|
|
6052
|
+
* (typed `BusinessRuleViolation`) on violation. It emits no DDL.
|
|
6053
|
+
*/
|
|
6054
|
+
export declare interface TableRule {
|
|
6055
|
+
readonly name: string;
|
|
6056
|
+
readonly predicate: RulePredicate;
|
|
6057
|
+
readonly severity: RuleSeverity;
|
|
6058
|
+
}
|
|
6059
|
+
|
|
5760
6060
|
/** A page read failed while streaming a table. Carries the underlying store
|
|
5761
6061
|
* error as `cause` so callers can inspect the transient-ness for retry. */
|
|
5762
6062
|
export declare class TableStreamError extends TableStreamError_base<{
|
|
@@ -6223,6 +6523,11 @@ export declare const viewSql: (v: View<string, Record<string, ColumnDefinition<u
|
|
|
6223
6523
|
/** Stable mixin id — the runtime keys read-filtering off it. */
|
|
6224
6524
|
export declare const VOLTRO_EXPIRES_MIXIN_ID = "voltro/expires";
|
|
6225
6525
|
|
|
6526
|
+
/** Stable mixin id — the runtime keys local-first discovery off it. Re-declared
|
|
6527
|
+
* (without importing this mixin) in the runtime's SchemaRegistry; kept in step
|
|
6528
|
+
* by `localFirstMixinId.test.ts`. */
|
|
6529
|
+
export declare const VOLTRO_LOCAL_FIRST_MIXIN_ID: "voltro/localFirst";
|
|
6530
|
+
|
|
6226
6531
|
/**
|
|
6227
6532
|
* `_voltro_api_keys` — first-class API key management. Only the SHA-256 HASH
|
|
6228
6533
|
* of a key is stored (`hashedKey`, unique); the raw token is shown once at
|