@voltro/database 0.33.0 → 0.34.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 +1801 -0
- package/dist/{frameworkLiveTables-DbAgeVOU.js → frameworkLiveTables-CeuTYhWm.js} +159 -130
- package/dist/index.d.ts +774 -11
- package/dist/index.js +1039 -710
- package/dist/sql.d.ts +247 -10
- package/dist/sql.js +1269 -912
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ConfigError } from 'effect';
|
|
2
|
+
import { Context } from 'effect';
|
|
2
3
|
import { Effect } from 'effect';
|
|
3
4
|
import { Exit } from 'effect';
|
|
4
5
|
import { Layer } from 'effect';
|
|
@@ -8,6 +9,7 @@ import { SqlClient } from '@effect/sql';
|
|
|
8
9
|
import { SqlError } from '@effect/sql';
|
|
9
10
|
import { Statement } from '@effect/sql';
|
|
10
11
|
import { Stream } from 'effect';
|
|
12
|
+
import { TransactionConnection } from '@effect/sql/SqlClient';
|
|
11
13
|
import { VoidIfEmpty } from 'effect/Types';
|
|
12
14
|
import { YieldableError } from 'effect/Cause';
|
|
13
15
|
|
|
@@ -182,6 +184,7 @@ export declare const attributionFields: (explicit?: WriteAttribution | undefined
|
|
|
182
184
|
traceId?: string;
|
|
183
185
|
subjectId?: string | null;
|
|
184
186
|
procedure?: string;
|
|
187
|
+
via?: "agent";
|
|
185
188
|
};
|
|
186
189
|
|
|
187
190
|
/** The write's identity, as both sides can compute it. NOT the row image: the
|
|
@@ -316,6 +319,13 @@ export declare interface BranchExecutor {
|
|
|
316
319
|
readonly createTable: (namespace: string, table: string) => Promise<void>;
|
|
317
320
|
/** Copy one table's rows from the parent namespace into the branch (`INSERT … SELECT`). */
|
|
318
321
|
readonly copyTable: (fromNamespace: string, toNamespace: string, table: string) => Promise<void>;
|
|
322
|
+
/** Replay one of the parent's foreign keys into the branch, re-pointed at the
|
|
323
|
+
* branch's own copy of the target table. `LIKE … INCLUDING ALL` does not copy
|
|
324
|
+
* foreign keys — see {@link BranchForeignKey} for why that is not optional. */
|
|
325
|
+
readonly addForeignKey: (namespace: string, fk: BranchForeignKey) => Promise<void>;
|
|
326
|
+
/** Restore an index's NAME, which the `LIKE` copy re-derived. Idempotent + a
|
|
327
|
+
* no-op when the name already matches — see {@link BranchIndexName}. */
|
|
328
|
+
readonly restoreIndexName: (namespace: string, index: BranchIndexName) => Promise<void>;
|
|
319
329
|
/** Drop the whole branch namespace (teardown). */
|
|
320
330
|
readonly dropNamespace: (namespace: string) => Promise<void>;
|
|
321
331
|
/** Create a Neon copy-on-write branch from `parentBranch`; RESOLVE to the new
|
|
@@ -325,6 +335,69 @@ export declare interface BranchExecutor {
|
|
|
325
335
|
readonly neonBranchDelete: (branchId: string) => Promise<void>;
|
|
326
336
|
}
|
|
327
337
|
|
|
338
|
+
/**
|
|
339
|
+
* One foreign key to REPLAY into the branch namespace, re-pointed at the
|
|
340
|
+
* branch's own copy of the target table.
|
|
341
|
+
*
|
|
342
|
+
* This exists because of a silent fidelity hole in the namespace mechanism, and
|
|
343
|
+
* the hole is a property of Postgres rather than of our emission:
|
|
344
|
+
* `CREATE TABLE … (LIKE parent INCLUDING ALL)` copies columns, defaults, CHECK
|
|
345
|
+
* constraints, identity and indexes — and does NOT copy FOREIGN KEYS. There is
|
|
346
|
+
* no `INCLUDING` clause that does. So a namespace branch of a schema with
|
|
347
|
+
* referential integrity came up WITHOUT any of it: a PR preview bound to that
|
|
348
|
+
* branch accepts writes production rejects, and a migration rehearsed on it is
|
|
349
|
+
* rehearsing a different schema from the one it claims to.
|
|
350
|
+
*
|
|
351
|
+
* The caller supplies them (introspection already carries them per column, as
|
|
352
|
+
* `ColumnSnapshot.references`), because the branch primitive deliberately does
|
|
353
|
+
* no I/O of its own.
|
|
354
|
+
*/
|
|
355
|
+
export declare interface BranchForeignKey {
|
|
356
|
+
/** Table (in the branch) that carries the FK column. */
|
|
357
|
+
readonly table: string;
|
|
358
|
+
readonly column: string;
|
|
359
|
+
/** Referenced table — resolved to the BRANCH's copy, never the parent's. */
|
|
360
|
+
readonly targetTable: string;
|
|
361
|
+
readonly targetColumn: string;
|
|
362
|
+
readonly onDelete?: 'cascade' | 'restrict' | 'setNull' | 'noAction';
|
|
363
|
+
readonly onUpdate?: 'cascade' | 'restrict' | 'setNull' | 'noAction';
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* One index whose NAME must be restored on the branch.
|
|
368
|
+
*
|
|
369
|
+
* The second thing `LIKE … INCLUDING ALL` does not carry, found the same way as
|
|
370
|
+
* the foreign keys — by branching a real converged schema and watching the
|
|
371
|
+
* differ propose work. Postgres copies the index and **re-derives its name from
|
|
372
|
+
* the table and columns**; the source name survives only when it happened to
|
|
373
|
+
* equal that derivation. Measured on pg 17:
|
|
374
|
+
*
|
|
375
|
+
* probeparent._voltro_api_keys byApiKeyTenant
|
|
376
|
+
* br_pr9_probe._voltro_api_keys _voltro_api_keys_tenantId_idx
|
|
377
|
+
* probeparent._voltro_idempotency _voltro_idempotency_scope_key_uq
|
|
378
|
+
* br_pr9_probe._voltro_idempotency _voltro_idempotency_scope_key_idx
|
|
379
|
+
*
|
|
380
|
+
* The planner compares indexes BY NAME, so every custom-named index on the
|
|
381
|
+
* parent reads as a different index on the branch: a rehearsal proposed
|
|
382
|
+
* `rename-index` plus a spurious `drop-unique-composite`/`add-unique-composite`
|
|
383
|
+
* pair for a schema that was already converged. Which is worse than noise — the
|
|
384
|
+
* rehearsal is supposed to be the thing that tells you what your migration does.
|
|
385
|
+
*
|
|
386
|
+
* PRIMARY KEYS are excluded: `<table>_pkey` is postgres's own derivation AND
|
|
387
|
+
* what four of the five introspectors fabricate, so the copy is already right.
|
|
388
|
+
* Expression indexes are excluded too — their key list carries no column to
|
|
389
|
+
* match on.
|
|
390
|
+
*/
|
|
391
|
+
export declare interface BranchIndexName {
|
|
392
|
+
readonly table: string;
|
|
393
|
+
/** Key columns IN INDEX ORDER — how the branch's copy is identified, since its
|
|
394
|
+
* re-derived name is exactly what we do not know. */
|
|
395
|
+
readonly columns: ReadonlyArray<string>;
|
|
396
|
+
readonly unique: boolean;
|
|
397
|
+
/** The name the PARENT's index carries, and the one to restore. */
|
|
398
|
+
readonly name: string;
|
|
399
|
+
}
|
|
400
|
+
|
|
328
401
|
export declare interface BranchLimitDecision {
|
|
329
402
|
/** Namespaces to tear down now (past TTL). */
|
|
330
403
|
readonly evict: ReadonlyArray<string>;
|
|
@@ -375,6 +448,14 @@ export declare interface BranchPlanInput {
|
|
|
375
448
|
readonly seed: BranchSeed;
|
|
376
449
|
/** namespace only: the source schema to snapshot from (required for `'copy'`). */
|
|
377
450
|
readonly parentNamespace?: string;
|
|
451
|
+
/** namespace only: the parent's foreign keys, replayed into the branch —
|
|
452
|
+
* `LIKE … INCLUDING ALL` does not copy them. Ignored by `neon-cow`, whose
|
|
453
|
+
* copy-on-write branch carries the parent's catalog verbatim. */
|
|
454
|
+
readonly foreignKeys?: ReadonlyArray<BranchForeignKey>;
|
|
455
|
+
/** namespace only: composite UNIQUE constraints whose NAME the `LIKE` copy
|
|
456
|
+
* re-derived by the `LIKE` copy. Same reason as `foreignKeys` — a branch that
|
|
457
|
+
* differs from the parent is not a copy of it. See {@link BranchIndexName}. */
|
|
458
|
+
readonly indexNames?: ReadonlyArray<BranchIndexName>;
|
|
378
459
|
/** neon-cow only: the base Neon branch to copy-on-write from (default `'main'`). */
|
|
379
460
|
readonly neonParentBranch?: string;
|
|
380
461
|
}
|
|
@@ -393,6 +474,14 @@ export declare interface BranchProvisionRequest {
|
|
|
393
474
|
readonly tableNames: ReadonlyArray<string>;
|
|
394
475
|
/** namespace mechanism, `seed: 'copy'`: the source schema to snapshot from. */
|
|
395
476
|
readonly parentNamespace?: string;
|
|
477
|
+
/** namespace mechanism: the parent's foreign keys, replayed into the branch
|
|
478
|
+
* after the tables exist (and after the rows, for a `copy` seed). Omitting
|
|
479
|
+
* them yields a branch with NO referential integrity — see
|
|
480
|
+
* {@link BranchForeignKey}. */
|
|
481
|
+
readonly foreignKeys?: ReadonlyArray<BranchForeignKey>;
|
|
482
|
+
/** namespace mechanism: the parent's index names, restored on the branch after
|
|
483
|
+
* the `LIKE` copy re-derived them — see {@link BranchIndexName}. */
|
|
484
|
+
readonly indexNames?: ReadonlyArray<BranchIndexName>;
|
|
396
485
|
/** The owned-DB connection string (Neon detection drives the fast-path). */
|
|
397
486
|
readonly dbUrl?: string;
|
|
398
487
|
/** neon mechanism: the base branch to copy-on-write from (default `'main'`). */
|
|
@@ -431,7 +520,7 @@ export declare interface BranchSqlRunner {
|
|
|
431
520
|
export declare type BranchState = 'requested' | 'provisioning' | 'ready' | 'destroying' | 'destroyed' | 'failed';
|
|
432
521
|
|
|
433
522
|
export declare interface BranchStep {
|
|
434
|
-
readonly kind: 'create-namespace' | 'create-table' | 'copy-table' | 'seed' | 'drop-namespace' | 'neon-branch-create' | 'neon-branch-delete';
|
|
523
|
+
readonly kind: 'create-namespace' | 'create-table' | 'copy-table' | 'copy-foreign-key' | 'restore-index-name' | 'seed' | 'drop-namespace' | 'neon-branch-create' | 'neon-branch-delete';
|
|
435
524
|
readonly detail: string;
|
|
436
525
|
readonly table?: string;
|
|
437
526
|
}
|
|
@@ -443,6 +532,37 @@ export declare class BranchTransitionInvalid extends Error {
|
|
|
443
532
|
constructor(from: BranchState, event: BranchEvent);
|
|
444
533
|
}
|
|
445
534
|
|
|
535
|
+
/**
|
|
536
|
+
* Per-dialect ceilings, keyed by the same ids `DialectId` uses.
|
|
537
|
+
*
|
|
538
|
+
* Deliberately NOT a `Record<DialectId, …>` typed against the dialect union:
|
|
539
|
+
* `@voltro/database` owns both, so the union would compile-check the map — but
|
|
540
|
+
* the stores look their entry up by a string they hold anyway (mysql's store
|
|
541
|
+
* carries `variant: 'mysql' | 'mariadb'`), and an unknown key must degrade to
|
|
542
|
+
* "no chunking" rather than to `undefined.maxBindParameters` at runtime. The
|
|
543
|
+
* parity test asserts every shipped dialect has an entry, which is the check
|
|
544
|
+
* that actually matters.
|
|
545
|
+
*/
|
|
546
|
+
export declare const BULK_INSERT_LIMITS: Readonly<Record<string, BulkInsertLimits>>;
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* What one `INSERT … VALUES` statement may carry on a dialect.
|
|
550
|
+
*
|
|
551
|
+
* Both bounds are real and independent; see the header for the mssql case where
|
|
552
|
+
* the row bound binds first.
|
|
553
|
+
*/
|
|
554
|
+
export declare interface BulkInsertLimits {
|
|
555
|
+
/** Bind parameters (`?` / `$n`) the engine accepts in ONE statement. */
|
|
556
|
+
readonly maxBindParameters: number;
|
|
557
|
+
/** Row constructors the engine accepts in one `VALUES` clause, when the
|
|
558
|
+
* engine caps that separately. */
|
|
559
|
+
readonly maxRowsPerStatement?: number;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/** The ceilings for `dialectId`, or `undefined` when we do not know them —
|
|
563
|
+
* which must read as "emit one statement, exactly as before". */
|
|
564
|
+
export declare const bulkInsertLimitsFor: (dialectId: string) => BulkInsertLimits | undefined;
|
|
565
|
+
|
|
446
566
|
/** Raw binary column — `BYTEA` (postgres) / `BLOB` (sqlite) / `LONGBLOB`
|
|
447
567
|
* (mysql/mariadb) / `VARBINARY(MAX)` (mssql). Values round-trip as
|
|
448
568
|
* `Uint8Array`. Use for storing bytes IN the database (e.g. the
|
|
@@ -500,6 +620,33 @@ export declare type ChangeEvent = {
|
|
|
500
620
|
readonly op: 'insert' | 'update' | 'delete';
|
|
501
621
|
readonly old: Row | null;
|
|
502
622
|
readonly new: Row | null;
|
|
623
|
+
/**
|
|
624
|
+
* Set when the TRANSPORT could not carry this change's row images and they
|
|
625
|
+
* had to be RECONSTRUCTED. Absent on every ordinary event, which is the only
|
|
626
|
+
* case where `old`/`new` are the images as they were at commit.
|
|
627
|
+
*
|
|
628
|
+
* Postgres caps a `pg_notify` payload at 8000 bytes, and under
|
|
629
|
+
* `changeStrategy: 'cdc'` the NOTIFY trigger is the SOLE emitter — so a row
|
|
630
|
+
* that does not fit used to arrive with both images null and every tap
|
|
631
|
+
* (search index, analytics mirror, cdc-out, row history) dropped it as a
|
|
632
|
+
* non-event, permanently, with nothing logged. The CDC consumer re-reads the
|
|
633
|
+
* row by primary key before the event reaches anyone; this field says what
|
|
634
|
+
* that recovered:
|
|
635
|
+
*
|
|
636
|
+
* - `'rehydrated'` — insert/update. `new` is the row RE-READ from the
|
|
637
|
+
* database, so it is the row as it is NOW, not necessarily the image the
|
|
638
|
+
* write that fired this event produced (a later write to the same row can
|
|
639
|
+
* already have landed). `old` is null — postgres keeps no pre-image once
|
|
640
|
+
* the transaction is gone.
|
|
641
|
+
* - `'tombstone'` — delete. `old` carries the PRIMARY KEY AND NOTHING ELSE:
|
|
642
|
+
* the row is gone, so the pre-image is unrecoverable and the key is the
|
|
643
|
+
* whole truth. Enough to REMOVE the row downstream; not a snapshot of what
|
|
644
|
+
* was deleted, and anything recording history must not treat it as one.
|
|
645
|
+
* - `'unrecovered'` — both images are null. The change is known to have
|
|
646
|
+
* happened and its content is not available (no key in the payload, the
|
|
647
|
+
* re-read failed, or the row was already gone). Re-read or resync.
|
|
648
|
+
*/
|
|
649
|
+
readonly oversized?: 'rehydrated' | 'tombstone' | 'unrecovered';
|
|
503
650
|
/**
|
|
504
651
|
* How the event reached this process. Absent/'inline' = emitted by this
|
|
505
652
|
* process's own write path; 'injected' = delivered over a cross-instance
|
|
@@ -549,10 +696,16 @@ export declare type ChangeEvent = {
|
|
|
549
696
|
* an absent `traceId`.
|
|
550
697
|
*/
|
|
551
698
|
readonly procedure?: string;
|
|
699
|
+
/**
|
|
700
|
+
* The write was made BY AN AGENT acting as `subjectId`, not by that subject
|
|
701
|
+
* directly. Mirrors `WriteAttribution.via` — see it for why the identity is
|
|
702
|
+
* NOT changed instead. Absent = a direct call, which is a fact, not a gap.
|
|
703
|
+
*/
|
|
704
|
+
readonly via?: 'agent';
|
|
552
705
|
};
|
|
553
706
|
|
|
554
707
|
/** Attribution and origin, identical on every constructor. */
|
|
555
|
-
export declare type ChangeEventMeta = Pick<ChangeEvent, 'origin' | 'traceId' | 'subjectId' | 'procedure'>;
|
|
708
|
+
export declare type ChangeEventMeta = Pick<ChangeEvent, 'origin' | 'traceId' | 'subjectId' | 'procedure' | 'via'>;
|
|
556
709
|
|
|
557
710
|
/** A row was created. `old` is null, always. */
|
|
558
711
|
export declare const changeInsert: (table: string, row: Row, meta?: ChangeEventMeta) => ChangeEvent;
|
|
@@ -589,6 +742,19 @@ export declare type ChangeStrategy = 'inline' | 'cdc';
|
|
|
589
742
|
/** A row changed. Both sides are present, because an update has both. */
|
|
590
743
|
export declare const changeUpdate: (table: string, before: Row, after: Row, meta?: ChangeEventMeta) => ChangeEvent;
|
|
591
744
|
|
|
745
|
+
/**
|
|
746
|
+
* Split `rows` into groups that each fit in one statement on this dialect.
|
|
747
|
+
*
|
|
748
|
+
* Returns a single group whenever the whole array already fits — which is the
|
|
749
|
+
* overwhelmingly common case, and it must stay allocation-cheap and, more
|
|
750
|
+
* importantly, must keep emitting the SAME single statement it did before. A
|
|
751
|
+
* chunker that split a 3-row insert into 3 would be a correct implementation of
|
|
752
|
+
* the wrong thing.
|
|
753
|
+
*
|
|
754
|
+
* `undefined` limits (an unknown dialect) → one group, unchanged behaviour.
|
|
755
|
+
*/
|
|
756
|
+
export declare const chunkRowsForInsert: <T extends Record<string, unknown>>(rows: ReadonlyArray<T>, limits: BulkInsertLimits | undefined) => ReadonlyArray<ReadonlyArray<T>>;
|
|
757
|
+
|
|
592
758
|
/**
|
|
593
759
|
* Claim the attribution for an echo, or `undefined` if this write did not
|
|
594
760
|
* originate here.
|
|
@@ -1468,6 +1634,18 @@ declare type ColumnOptionalForInsert<K extends PropertyKey, D> = K extends AutoF
|
|
|
1468
1634
|
*/
|
|
1469
1635
|
export declare const columnSchema: <TsType, C extends ColumnType>(def: ColumnDefinition<TsType, C>) => Schema.Schema.Any;
|
|
1470
1636
|
|
|
1637
|
+
/**
|
|
1638
|
+
* How many bind parameters one row costs.
|
|
1639
|
+
*
|
|
1640
|
+
* The MAX across rows, not the first row's count: `sql.insert` builds its
|
|
1641
|
+
* column list from the union of the rows' keys and binds a value (a NULL where
|
|
1642
|
+
* a row omits the column) in every position, so a set whose first row is narrow
|
|
1643
|
+
* and whose tenth is wide costs the WIDE row's count for every row. Counting
|
|
1644
|
+
* the first row would under-count exactly the shape most likely to be near the
|
|
1645
|
+
* ceiling.
|
|
1646
|
+
*/
|
|
1647
|
+
export declare const columnsPerRow: (rows: ReadonlyArray<Record<string, unknown>>) => number;
|
|
1648
|
+
|
|
1471
1649
|
export declare type ColumnType = 'id' | 'text' | 'integer' | 'real' | 'decimal' | 'bigint' | 'boolean' | 'timestamp' | 'date' | 'json' | 'bytes' | 'reference' | 'vector' | 'enum' | 'array' | 'interval' | 'raw';
|
|
1472
1650
|
|
|
1473
1651
|
/**
|
|
@@ -1564,6 +1742,75 @@ export declare interface ConnectionConfig {
|
|
|
1564
1742
|
*/
|
|
1565
1743
|
readonly ssl?: boolean;
|
|
1566
1744
|
readonly maxConnections?: number;
|
|
1745
|
+
/**
|
|
1746
|
+
* How long a caller waits for a free pooled connection before the acquire
|
|
1747
|
+
* FAILS (ms). Default {@link DEFAULT_ACQUIRE_TIMEOUT_MS}; `0` restores the
|
|
1748
|
+
* driver's own (unbounded) behaviour. Env `DB_ACQUIRE_TIMEOUT_MS`.
|
|
1749
|
+
*
|
|
1750
|
+
* This is the OTHER half of `statementTimeoutMs`. That one bounds a query the
|
|
1751
|
+
* SERVER is running; nothing bounded a query the client had not sent yet — a
|
|
1752
|
+
* request arriving when every pooled connection is busy did not fail, did not
|
|
1753
|
+
* retry and logged nothing, it simply sat in the driver's queue. A bounded
|
|
1754
|
+
* failure names the pool as the cause at the moment it IS the cause, instead
|
|
1755
|
+
* of surfacing as an unexplained latency spike somewhere with no connection
|
|
1756
|
+
* information in it.
|
|
1757
|
+
*
|
|
1758
|
+
* **What each dialect can actually enforce** — the drivers differ, and the
|
|
1759
|
+
* difference is not papered over:
|
|
1760
|
+
* - **postgres** — exact. Maps to node-postgres' `connectionTimeoutMillis`,
|
|
1761
|
+
* which bounds BOTH establishing a new connection and waiting in the
|
|
1762
|
+
* pool's pending queue for a busy one (`pg-pool`'s `_pendingQueue`
|
|
1763
|
+
* timer). This is the full guarantee.
|
|
1764
|
+
* - **mysql/mariadb** — establishment only, and that is the whole of it.
|
|
1765
|
+
* mysql2's pool has NO time-based acquire bound at all
|
|
1766
|
+
* (`lib/base/pool.js` pushes the callback onto `_connectionQueue` with no
|
|
1767
|
+
* timer). The waiting half is UNBOUNDED by default and deliberately so —
|
|
1768
|
+
* see {@link ConnectionConfig.acquireQueueLimit} for the length bound
|
|
1769
|
+
* that exists, why it is opt-in, and what it costs.
|
|
1770
|
+
* - **mssql** — establishment only. Maps to `@effect/sql-mssql`'s
|
|
1771
|
+
* `connectTimeout` (tedious connect + the boot `SELECT 1` probe); its
|
|
1772
|
+
* pool is an Effect `Pool` whose `get` has no timeout knob.
|
|
1773
|
+
* - **sqlite** — ignored. One in-process connection, no pool to exhaust.
|
|
1774
|
+
*/
|
|
1775
|
+
readonly acquireTimeoutMs?: number;
|
|
1776
|
+
/**
|
|
1777
|
+
* **mysql/mariadb only, OPT-IN, and a hazard** — how many callers may be
|
|
1778
|
+
* QUEUED waiting for a free pooled connection before the next one fails
|
|
1779
|
+
* immediately (mysql2's `queueLimit`). Unset by default (mysql2's own `0` =
|
|
1780
|
+
* unbounded queue). Env `DB_ACQUIRE_QUEUE_LIMIT`.
|
|
1781
|
+
*
|
|
1782
|
+
* It looks like the mysql substitute for `acquireTimeoutMs`, which mysql2
|
|
1783
|
+
* cannot express for the waiting phase. **It is not, and a default of 100
|
|
1784
|
+
* shipped for exactly one change set before it wedged a process.** A LENGTH
|
|
1785
|
+
* bound and a TIME bound are not interchangeable, and the difference is the
|
|
1786
|
+
* reason this is opt-in:
|
|
1787
|
+
*
|
|
1788
|
+
* - A time bound self-throttles. The acquire fails only after the wait has
|
|
1789
|
+
* elapsed, so a caller that retries on failure retries no faster than the
|
|
1790
|
+
* timeout — a busy loop is impossible.
|
|
1791
|
+
* - A length bound is free. Past the limit mysql2 rejects the acquire
|
|
1792
|
+
* **synchronously** (`lib/base/pool.js` — the queue-limit branch is the one
|
|
1793
|
+
* error return that does not go through `process.nextTick`), so a caller
|
|
1794
|
+
* that retries on failure retries at CPU speed, in the same tick, forever.
|
|
1795
|
+
* The event loop is never reached again: no timer fires, nothing drains the
|
|
1796
|
+
* queue that caused the rejection, and nothing is logged.
|
|
1797
|
+
*
|
|
1798
|
+
* That caller is not hypothetical and not ours to fix: `@effect/cluster`
|
|
1799
|
+
* releases shards one statement per shard (300 by default) and wraps each in
|
|
1800
|
+
* `Effect.eventually` — retry until success, no schedule, no delay, logged at
|
|
1801
|
+
* debug. On a `shardLockDisableAdvisory` (Galera / PXC) runner those releases
|
|
1802
|
+
* are pooled `DELETE FROM cluster_locks` statements, so every graceful
|
|
1803
|
+
* shutdown queued ~290 acquires against a 10-connection pool, crossed the
|
|
1804
|
+
* limit, and spun the process at 100% CPU with no output.
|
|
1805
|
+
*
|
|
1806
|
+
* So: set it only where you know nothing retries an acquire failure without a
|
|
1807
|
+
* delay, and set it well above the fan-out of anything that might.
|
|
1808
|
+
*
|
|
1809
|
+
* Other dialects ignore it: postgres bounds the wait by TIME (which is
|
|
1810
|
+
* strictly better — it does not care how many are ahead of you), and mssql's
|
|
1811
|
+
* Effect `Pool` exposes neither.
|
|
1812
|
+
*/
|
|
1813
|
+
readonly acquireQueueLimit?: number;
|
|
1567
1814
|
/**
|
|
1568
1815
|
* Per-statement timeout in ms — a runaway query (missing index, cartesian
|
|
1569
1816
|
* join) is aborted instead of holding a pooled connection forever, which under
|
|
@@ -1602,6 +1849,22 @@ export declare interface ConnectionConfig {
|
|
|
1602
1849
|
readonly schema?: string;
|
|
1603
1850
|
}
|
|
1604
1851
|
|
|
1852
|
+
/**
|
|
1853
|
+
* A stable, non-disclosing id for the database a connection points at.
|
|
1854
|
+
*
|
|
1855
|
+
* `undefined` when the inputs do not identify one — which a caller must treat as
|
|
1856
|
+
* "cannot tell", never as "different".
|
|
1857
|
+
*/
|
|
1858
|
+
export declare const connectionIdentity: (input: ConnectionIdentityInput) => string | undefined;
|
|
1859
|
+
|
|
1860
|
+
export declare interface ConnectionIdentityInput {
|
|
1861
|
+
readonly dialect?: string | undefined;
|
|
1862
|
+
readonly url?: string | undefined;
|
|
1863
|
+
readonly host?: string | undefined;
|
|
1864
|
+
readonly port?: number | undefined;
|
|
1865
|
+
readonly database?: string | undefined;
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1605
1868
|
/** Case-insensitive substring match on a text column. Non-indexable —
|
|
1606
1869
|
* always evaluated as an unindexed leaf (the matcher's pre-filter only
|
|
1607
1870
|
* recognizes eq/in/range). Lowers to `ILIKE '%…%'` on Postgres. */
|
|
@@ -2012,6 +2275,29 @@ export declare interface DbEnumHandle<Values extends ReadonlyArray<string>> {
|
|
|
2012
2275
|
readonly column: () => ColumnBuilder<Values[number], 'enum'>;
|
|
2013
2276
|
}
|
|
2014
2277
|
|
|
2278
|
+
export declare const dbMetricTagCacheSizeForTests: () => number;
|
|
2279
|
+
|
|
2280
|
+
/**
|
|
2281
|
+
* What kind of database work is being timed.
|
|
2282
|
+
*
|
|
2283
|
+
* A CLOSED union on purpose — it is a metric label, and a label whose value set
|
|
2284
|
+
* is open is a cardinality incident waiting for the first caller who passes a
|
|
2285
|
+
* table name. Adding a member here is a deliberate act with a matching entry in
|
|
2286
|
+
* the docs' metrics table.
|
|
2287
|
+
*/
|
|
2288
|
+
export declare type DbOp = 'select' | 'insert' | 'update' | 'delete' | 'upsert' | 'raw' | 'transaction' | 'ddl';
|
|
2289
|
+
|
|
2290
|
+
/** Current in-flight count for `dialect` — the gauge's value, for tests. */
|
|
2291
|
+
export declare const dbOperationsInFlight: (dialect: string) => number;
|
|
2292
|
+
|
|
2293
|
+
/** One completed database operation. */
|
|
2294
|
+
export declare interface DbOpSample {
|
|
2295
|
+
readonly dialect: DialectId | string;
|
|
2296
|
+
readonly op: DbOp;
|
|
2297
|
+
readonly durationMs: number;
|
|
2298
|
+
readonly status: 'ok' | 'error';
|
|
2299
|
+
}
|
|
2300
|
+
|
|
2015
2301
|
/**
|
|
2016
2302
|
* Fixed-point exact-decimal column — money, tax rates, anything where
|
|
2017
2303
|
* floating-point rounding is unacceptable. Emits `NUMERIC(precision, scale)`
|
|
@@ -2089,6 +2375,34 @@ export declare const decryptFieldsOnRead: (rows: ReadonlyArray<Row>, table: Tabl
|
|
|
2089
2375
|
}) => void;
|
|
2090
2376
|
}) => ReadonlyArray<Row>;
|
|
2091
2377
|
|
|
2378
|
+
/**
|
|
2379
|
+
* 10 s — the framework's default `ConnectionConfig.acquireTimeoutMs`.
|
|
2380
|
+
*
|
|
2381
|
+
* Long enough that a healthy pool under a burst never trips it (a connection is
|
|
2382
|
+
* normally handed over in single-digit milliseconds) and short enough that "the
|
|
2383
|
+
* pool is exhausted" reaches a log line while it is still the answer to a
|
|
2384
|
+
* question somebody is asking. Every dialect that can bound an acquire reads
|
|
2385
|
+
* THIS constant, so the number is one decision rather than four.
|
|
2386
|
+
*/
|
|
2387
|
+
export declare const DEFAULT_ACQUIRE_TIMEOUT_MS = 10000;
|
|
2388
|
+
|
|
2389
|
+
/**
|
|
2390
|
+
* How often a persistent fallback is allowed to say so.
|
|
2391
|
+
*
|
|
2392
|
+
* A once-per-table warning (the shape `makeOversizedReporter` uses) is right for
|
|
2393
|
+
* a condition that either recovers or does not recur. This one is different: it
|
|
2394
|
+
* is a PERMANENT per-query cliff, and after the single line scrolls out of the
|
|
2395
|
+
* log there is nothing left to find. Re-warning on an interval keeps a
|
|
2396
|
+
* still-degraded query present in a log an operator greps a week later, without
|
|
2397
|
+
* turning a hot path into a log flood.
|
|
2398
|
+
*
|
|
2399
|
+
* Env: `VOLTRO_DB_EAGER_FALLBACK_WARN_INTERVAL_MS`. `0` warns once per
|
|
2400
|
+
* (table, reason) and never again.
|
|
2401
|
+
*/
|
|
2402
|
+
export declare const DEFAULT_EAGER_FALLBACK_WARN_INTERVAL_MS = 300000;
|
|
2403
|
+
|
|
2404
|
+
export declare const DEFAULT_SUBJECT_GRAPH_DEPTH = 4;
|
|
2405
|
+
|
|
2092
2406
|
export declare const defineMigration: (input: MigrationDefinitionInput) => MigrationDefinition;
|
|
2093
2407
|
|
|
2094
2408
|
/**
|
|
@@ -2111,6 +2425,42 @@ export declare const defineSeed: (input: SeedDefinitionInput) => SeedDefinition;
|
|
|
2111
2425
|
|
|
2112
2426
|
export declare const denseRank: (alias?: string) => WindowBuilder;
|
|
2113
2427
|
|
|
2428
|
+
/**
|
|
2429
|
+
* Walk the schema outward from `subjectTable` and return every route to a table
|
|
2430
|
+
* holding that subject's rows.
|
|
2431
|
+
*
|
|
2432
|
+
* Breadth-first with a visited set keyed on (table, foreignKey), so a cyclic
|
|
2433
|
+
* schema terminates and a table reachable two ways contributes both routes ONCE
|
|
2434
|
+
* each rather than looping. Two tables can legitimately be reached by different
|
|
2435
|
+
* columns (`createdBy` and `updatedBy` both pointing at `actors`) and both are
|
|
2436
|
+
* kept — dropping one would silently narrow an erasure.
|
|
2437
|
+
*/
|
|
2438
|
+
export declare const deriveSubjectGraph: (input: DeriveSubjectGraphInput) => SubjectGraph;
|
|
2439
|
+
|
|
2440
|
+
export declare interface DeriveSubjectGraphInput {
|
|
2441
|
+
/** The table whose primary key IS the subject id (`users`, `actors`, …). */
|
|
2442
|
+
readonly subjectTable: string;
|
|
2443
|
+
/** Every registered table. Defaults to the live table registry at call time —
|
|
2444
|
+
* a default rather than a fixed read, so a test can hand in a schema without
|
|
2445
|
+
* writing to a process-global other suites also use. */
|
|
2446
|
+
readonly tables?: ReadonlyArray<TableLike>;
|
|
2447
|
+
/** Every registered relation, as `allRegisteredRelations()` returns them.
|
|
2448
|
+
* Defaults to the live relations registry. */
|
|
2449
|
+
readonly relations?: ReadonlyArray<{
|
|
2450
|
+
readonly source: string;
|
|
2451
|
+
readonly name: string;
|
|
2452
|
+
readonly relation: Relation;
|
|
2453
|
+
}>;
|
|
2454
|
+
/**
|
|
2455
|
+
* Hop ceiling. 4 is deep enough for `user → org-membership → project → task`
|
|
2456
|
+
* and shallow enough that a densely-linked schema does not fan out into every
|
|
2457
|
+
* table it has. Anything cut is REPORTED as `depth-truncated`.
|
|
2458
|
+
*/
|
|
2459
|
+
readonly maxDepth?: number;
|
|
2460
|
+
/** Tables never to walk INTO. */
|
|
2461
|
+
readonly exclude?: ReadonlyArray<string>;
|
|
2462
|
+
}
|
|
2463
|
+
|
|
2114
2464
|
/**
|
|
2115
2465
|
* Lower-cases + naively singularizes a table name into a typeid prefix.
|
|
2116
2466
|
*
|
|
@@ -2167,6 +2517,57 @@ export declare interface DialectReplicationAdapter {
|
|
|
2167
2517
|
compare(primaryPosition: string, replicaPosition: string): CaughtUpVerdict;
|
|
2168
2518
|
}
|
|
2169
2519
|
|
|
2520
|
+
/**
|
|
2521
|
+
* The parts of a transaction that are genuinely the DIALECT's. Anything not in
|
|
2522
|
+
* here is owned by `runRetryingTransaction` / `runStoreTransaction` and must not
|
|
2523
|
+
* be re-decided per store — see the header.
|
|
2524
|
+
*/
|
|
2525
|
+
export declare interface DialectTransactionSpec {
|
|
2526
|
+
/** `<Store>.transactional` / `<Store>.runInNamespace` — prefixes the
|
|
2527
|
+
* missing-connection error so a failure names the entry point it came from. */
|
|
2528
|
+
readonly label: string;
|
|
2529
|
+
/**
|
|
2530
|
+
* Which dialect this transaction belongs to — the `dialect` label on
|
|
2531
|
+
* `voltro_db_queries_total{op="transaction"}` and its duration histogram.
|
|
2532
|
+
*
|
|
2533
|
+
* REQUIRED rather than derived from `span.attributes['db.system']`: that
|
|
2534
|
+
* field carries OpenTelemetry's spelling (`postgresql`, not `postgres`) and
|
|
2535
|
+
* mysql and mariadb share one value there, so a metric derived from it would
|
|
2536
|
+
* silently merge two dialects. Required also means a new dialect store cannot
|
|
2537
|
+
* be written without deciding what its transactions are called.
|
|
2538
|
+
*/
|
|
2539
|
+
readonly dialect: DialectId;
|
|
2540
|
+
/** The store's `sql.withTransaction` — issues BEGIN/COMMIT/ROLLBACK and
|
|
2541
|
+
* provides `TransactionConnection`. */
|
|
2542
|
+
readonly withTransaction: <A>(effect: Effect.Effect<A, unknown, never>) => Effect.Effect<A, unknown, never>;
|
|
2543
|
+
/** The store's managed runtime, ALWAYS as `runPromiseExit` — `runPromise` is
|
|
2544
|
+
* the bug this file exists to prevent, so the shared bracket never sees one. */
|
|
2545
|
+
readonly runPromiseExit: <A>(effect: Effect.Effect<A, unknown, never>) => Promise<Exit.Exit<A, unknown>>;
|
|
2546
|
+
/** Transient-contention predicate: pg `40001`/`40P01`, mysql deadlock/lock-wait,
|
|
2547
|
+
* sqlite `SQLITE_BUSY`, turso's MVCC write-write conflict, mssql `1205`. Drives
|
|
2548
|
+
* BOTH the retry schedule and the commit-defect promotion. */
|
|
2549
|
+
readonly isRetryable: (e: unknown) => boolean;
|
|
2550
|
+
/** OpenTelemetry span for the whole boundary (retries included). */
|
|
2551
|
+
readonly span: {
|
|
2552
|
+
readonly name: string;
|
|
2553
|
+
readonly attributes: Readonly<Record<string, string>>;
|
|
2554
|
+
};
|
|
2555
|
+
/**
|
|
2556
|
+
* Runs on the transaction's connection BEFORE the caller's work, once per
|
|
2557
|
+
* attempt. Postgres' namespace binding (`SET LOCAL search_path TO "<ns>"`)
|
|
2558
|
+
* is the only current use; it must be inside the transaction, on this
|
|
2559
|
+
* connection, or a pooled handoff leaks the tenant's schema into the next
|
|
2560
|
+
* request.
|
|
2561
|
+
*/
|
|
2562
|
+
readonly prepare?: (conn: TransactionConnectionContext) => Effect.Effect<unknown, unknown, never>;
|
|
2563
|
+
/**
|
|
2564
|
+
* Wraps the finished program (retry + span already applied) immediately
|
|
2565
|
+
* before it runs. Turso sets the fiber flag that upgrades `BEGIN` to
|
|
2566
|
+
* `BEGIN CONCURRENT` here; every other dialect omits it.
|
|
2567
|
+
*/
|
|
2568
|
+
readonly wrapProgram?: <A>(effect: Effect.Effect<A, unknown, never>) => Effect.Effect<A, unknown, never>;
|
|
2569
|
+
}
|
|
2570
|
+
|
|
2170
2571
|
/**
|
|
2171
2572
|
* Drain accumulated WARN-level identifier messages. Called once by
|
|
2172
2573
|
* `voltro dev` (and `voltro start`) after schema discovery so the boot
|
|
@@ -2216,6 +2617,26 @@ export declare class EagerCardinalityError extends Error {
|
|
|
2216
2617
|
constructor(message: string);
|
|
2217
2618
|
}
|
|
2218
2619
|
|
|
2620
|
+
export declare interface EagerFallbackEvent {
|
|
2621
|
+
readonly dialect: DialectId | string;
|
|
2622
|
+
readonly table: string;
|
|
2623
|
+
readonly reason: EagerFallbackReason;
|
|
2624
|
+
/** The error the JSON-agg statement threw. Absent for `not-compilable`. */
|
|
2625
|
+
readonly error?: unknown;
|
|
2626
|
+
}
|
|
2627
|
+
|
|
2628
|
+
/** Why an eager-load query is running on the walker instead of JSON-agg. */
|
|
2629
|
+
export declare type EagerFallbackReason =
|
|
2630
|
+
/** `compileEagerJson` returned `null` — the shape is not compilable (an
|
|
2631
|
+
* unregistered relation, an ambiguous inferred FK, or a namespaced read on a
|
|
2632
|
+
* dialect whose JSON-agg compiler does not qualify identifiers). Steady
|
|
2633
|
+
* state for a given query: it will never take the fast path. */
|
|
2634
|
+
'not-compilable'
|
|
2635
|
+
/** The compiled JSON-agg statement THREW and the walker re-ran the work. This
|
|
2636
|
+
* is the one to alert on: the fast path exists, is being attempted, and is
|
|
2637
|
+
* failing — so every such query pays BOTH paths. */
|
|
2638
|
+
| 'execute-failed';
|
|
2639
|
+
|
|
2219
2640
|
/**
|
|
2220
2641
|
* Compiled JSON-aggregation plan: a `Statement.Statement` ready to
|
|
2221
2642
|
* execute via `SqlClient.SqlClient`, plus a `decode()` that restores
|
|
@@ -2447,6 +2868,13 @@ export declare const expires: () => MixinDefinition<{
|
|
|
2447
2868
|
readonly expiresAt: ColumnDefinition<Date | null, "timestamp", boolean>;
|
|
2448
2869
|
}>;
|
|
2449
2870
|
|
|
2871
|
+
/** The subset of `@voltro/logger`'s logger this module needs. Structural so
|
|
2872
|
+
* this package keeps its browser-safe import surface. */
|
|
2873
|
+
export declare interface FallbackLogger {
|
|
2874
|
+
readonly warn: (message: string, fields?: Record<string, unknown>) => void;
|
|
2875
|
+
readonly debug: (message: string, fields?: Record<string, unknown>) => void;
|
|
2876
|
+
}
|
|
2877
|
+
|
|
2450
2878
|
/** The cipher the store middleware injects. Operates on opaque strings. */
|
|
2451
2879
|
export declare interface FieldCipher {
|
|
2452
2880
|
readonly encrypt: (plaintext: string) => string;
|
|
@@ -2543,6 +2971,26 @@ export declare interface FileMigrationContext {
|
|
|
2543
2971
|
readonly appliedAt: string;
|
|
2544
2972
|
}
|
|
2545
2973
|
|
|
2974
|
+
/**
|
|
2975
|
+
* Fire the `onSchemaChange` seeds for the tables an apply just changed.
|
|
2976
|
+
*
|
|
2977
|
+
* Unlike tenant-create this does NOT propagate: a schema apply that succeeded
|
|
2978
|
+
* must not be reported as failed because a data fixture threw. The runner logs
|
|
2979
|
+
* + records the failure in `_voltro_seeds`.
|
|
2980
|
+
*/
|
|
2981
|
+
export declare const fireSchemaChangeSeeds: (event: SchemaChangeSeedEvent) => Promise<void>;
|
|
2982
|
+
|
|
2983
|
+
/**
|
|
2984
|
+
* Fire the `onTenantCreate` seeds for a freshly provisioned namespace.
|
|
2985
|
+
*
|
|
2986
|
+
* Awaited, and a rejection PROPAGATES to the provisioning caller on purpose:
|
|
2987
|
+
* a tenant whose namespace exists but whose seed data does not is a broken
|
|
2988
|
+
* tenant that reports success, which is the exact failure class this seam was
|
|
2989
|
+
* built to remove. `provisionTenantNamespace` is idempotent, so the caller's
|
|
2990
|
+
* retry is safe.
|
|
2991
|
+
*/
|
|
2992
|
+
export declare const fireTenantCreateSeeds: (event: TenantCreateSeedEvent) => Promise<void>;
|
|
2993
|
+
|
|
2546
2994
|
/** Flatten an intersection into a single object type for readable errors/hovers. */
|
|
2547
2995
|
declare type Flatten<O> = {
|
|
2548
2996
|
[K in keyof O]: O[K];
|
|
@@ -2565,6 +3013,10 @@ declare interface FormatIssueOptions {
|
|
|
2565
3013
|
readonly indent?: string;
|
|
2566
3014
|
}
|
|
2567
3015
|
|
|
3016
|
+
/** Human-readable rendering of a derived graph — what `voltro privacy scope`
|
|
3017
|
+
* prints, and what a DPO reads before signing off on a DSAR process. */
|
|
3018
|
+
export declare const formatSubjectGraph: (graph: SubjectGraph) => string;
|
|
3019
|
+
|
|
2568
3020
|
/**
|
|
2569
3021
|
* Live tables the framework's own runtime creates under a name that does NOT
|
|
2570
3022
|
* start with a reserved prefix, so the prefix rule below cannot recognise them.
|
|
@@ -3140,6 +3592,9 @@ export declare const lead: (column: string, offset?: number, alias?: string) =>
|
|
|
3140
3592
|
|
|
3141
3593
|
export declare type LeafOperator = 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'notIn' | 'contains' | 'startsWith' | 'arrayContains' | 'arrayOverlaps' | 'arrayHas' | 'isNull' | 'isNotNull' | 'spatial';
|
|
3142
3594
|
|
|
3595
|
+
/** Every registration conflict since the last {@link clearRetentions}. */
|
|
3596
|
+
export declare const listRetentionConflicts: () => ReadonlyArray<RetentionConflict>;
|
|
3597
|
+
|
|
3143
3598
|
/** Every currently-registered retention spec. The boot sweep reads this each tick. */
|
|
3144
3599
|
export declare const listRetentions: () => ReadonlyArray<RetentionSpec>;
|
|
3145
3600
|
|
|
@@ -3179,14 +3634,40 @@ export declare const lt: <RowOf = Record<string, unknown>, K extends keyof RowOf
|
|
|
3179
3634
|
|
|
3180
3635
|
export declare const lte: <RowOf = Record<string, unknown>, K extends keyof RowOf & string = keyof RowOf & string>(column: K, value: RowOf[K]) => PredicateLeaf;
|
|
3181
3636
|
|
|
3637
|
+
/**
|
|
3638
|
+
* Build the store-scoped fallback reporter: counts EVERY occurrence, logs a
|
|
3639
|
+
* rate-limited line.
|
|
3640
|
+
*
|
|
3641
|
+
* Instance-scoped rather than a module global, for the reason
|
|
3642
|
+
* `makeOversizedReporter` states: the rate limit must be the STORE's, so a
|
|
3643
|
+
* second store in the same process (a read replica, a test harness) cannot
|
|
3644
|
+
* silence the first one's first warning.
|
|
3645
|
+
*
|
|
3646
|
+
* The COUNTER is never rate-limited. That separation is the whole point of
|
|
3647
|
+
* PERF-23 — the log line is for a human reading it at the moment it happens,
|
|
3648
|
+
* and the counter is for the dashboard that has to show a cliff which started
|
|
3649
|
+
* three weeks ago.
|
|
3650
|
+
*/
|
|
3651
|
+
export declare const makeEagerFallbackReporter: (log: FallbackLogger, options?: {
|
|
3652
|
+
readonly warnIntervalMs?: number;
|
|
3653
|
+
readonly now?: () => number;
|
|
3654
|
+
}) => ((event: EagerFallbackEvent) => void);
|
|
3655
|
+
|
|
3182
3656
|
/**
|
|
3183
3657
|
* A concrete {@link BranchExecutor} for the NAMESPACE mechanism on Postgres — it
|
|
3184
3658
|
* emits schema DDL + table-copy DML through an injected {@link BranchSqlRunner}.
|
|
3185
3659
|
* `createTable` clones the parent table's STRUCTURE (`LIKE … INCLUDING ALL`);
|
|
3186
|
-
* `copyTable` snapshots its rows (`INSERT … SELECT`);
|
|
3660
|
+
* `copyTable` snapshots its rows (`INSERT … SELECT`); `addForeignKey` replays the
|
|
3661
|
+
* referential integrity `LIKE` does not carry; teardown drops the schema.
|
|
3187
3662
|
* The Neon ops throw — a copy-on-write branch needs a Neon connection (which
|
|
3188
3663
|
* resolves to the `neon-cow` mechanism + its own executor).
|
|
3189
3664
|
*
|
|
3665
|
+
* POSTGRES ONLY, and the name is the whole warning: every statement here is
|
|
3666
|
+
* postgres syntax (`CREATE SCHEMA`, `LIKE … INCLUDING ALL`, `"` quoting). MySQL /
|
|
3667
|
+
* MariaDB spell the clone `CREATE TABLE x LIKE y` and quote with backticks;
|
|
3668
|
+
* sqlite has no schema namespace to create. A caller on another dialect needs
|
|
3669
|
+
* its own executor — the PLAN is dialect-agnostic, this executor is not.
|
|
3670
|
+
*
|
|
3190
3671
|
* Pure SQL emission: unit-tested with a recording runner (no live DB to BUILD or
|
|
3191
3672
|
* TEST — only to RUN, like any SQL). The cloud wires `run` to its owned store.
|
|
3192
3673
|
*/
|
|
@@ -3482,6 +3963,15 @@ export declare const neq: <RowOf = Record<string, unknown>, K extends keyof RowO
|
|
|
3482
3963
|
* double-destroy or a provision-after-destroyed can't corrupt state). */
|
|
3483
3964
|
export declare const nextBranchState: (current: BranchState, event: BranchEvent) => BranchState;
|
|
3484
3965
|
|
|
3966
|
+
/** The normalised `(host, port, database)` a connection points at, before
|
|
3967
|
+
* hashing. Exported for the tests — the digest is not readable, and a
|
|
3968
|
+
* normalisation bug in it would be invisible otherwise. */
|
|
3969
|
+
export declare const normaliseTarget: (input: ConnectionIdentityInput) => {
|
|
3970
|
+
readonly host: string;
|
|
3971
|
+
readonly port: number;
|
|
3972
|
+
readonly database: string;
|
|
3973
|
+
} | undefined;
|
|
3974
|
+
|
|
3485
3975
|
/** Negate a sub-predicate: `not(eq('archived', true))` → `NOT (...)`.
|
|
3486
3976
|
* Wraps any subtree, including `and(...)`/`or(...)`. */
|
|
3487
3977
|
export declare const not: (predicate: Predicate) => NotPredicate;
|
|
@@ -3506,6 +3996,16 @@ export declare interface NotPredicate {
|
|
|
3506
3996
|
*/
|
|
3507
3997
|
export declare const numeric: (precision: number, scale?: number) => ColumnBuilder<string, "decimal">;
|
|
3508
3998
|
|
|
3999
|
+
/**
|
|
4000
|
+
* Time `run`, count it, and hold the in-flight gauge for its duration.
|
|
4001
|
+
*
|
|
4002
|
+
* The `finally` is load-bearing in two directions: a throw must still be TIMED
|
|
4003
|
+
* (a statement that fails after 30 seconds is the interesting one) and must
|
|
4004
|
+
* still release the gauge, or a single failing query leaks a permanent +1 and
|
|
4005
|
+
* the saturation signal drifts upward forever.
|
|
4006
|
+
*/
|
|
4007
|
+
export declare const observeDbOp: <T>(dialect: DialectId | string, op: DbOp, run: () => Promise<T>) => Promise<T>;
|
|
4008
|
+
|
|
3509
4009
|
/**
|
|
3510
4010
|
* What a decrypt failure does. `'throw'` (default, and the ONLY safe production
|
|
3511
4011
|
* behaviour) surfaces a typed `FieldDecryptionError`. `'null'` degrades the one
|
|
@@ -3650,10 +4150,18 @@ export declare const planBranch: (input: BranchPlanInput) => ReadonlyArray<Branc
|
|
|
3650
4150
|
/**
|
|
3651
4151
|
* The ordered steps to stand a branch up — what the cloud executor runs
|
|
3652
4152
|
* (create-namespace + create-table via `provisionTenantNamespace`, copy-table
|
|
3653
|
-
* via cross-namespace `INSERT … SELECT`,
|
|
4153
|
+
* via cross-namespace `INSERT … SELECT`, copy-foreign-key via
|
|
4154
|
+
* `ALTER TABLE … ADD CONSTRAINT`, seed via the boot seeds). Pure plan, so
|
|
3654
4155
|
* it's unit-testable; the executor is the cloud side.
|
|
4156
|
+
*
|
|
4157
|
+
* The FK replay is sequenced AFTER the row copy on purpose: `INSERT … SELECT`
|
|
4158
|
+
* per table has no topological order, so a child table copied before its parent
|
|
4159
|
+
* would violate a constraint that is only correct once every table is populated.
|
|
4160
|
+
* It is sequenced BEFORE the `fresh` seed for the mirror-image reason — seeds
|
|
4161
|
+
* write through the app's own paths and should meet the same integrity rules
|
|
4162
|
+
* production has.
|
|
3655
4163
|
*/
|
|
3656
|
-
export declare const planBranchProvision: (branchNamespace: string, tableNames: ReadonlyArray<string>, seed: BranchSeed, parentNamespace?: string) => ReadonlyArray<BranchStep>;
|
|
4164
|
+
export declare const planBranchProvision: (branchNamespace: string, tableNames: ReadonlyArray<string>, seed: BranchSeed, parentNamespace?: string, foreignKeys?: ReadonlyArray<BranchForeignKey>, indexNames?: ReadonlyArray<BranchIndexName>) => ReadonlyArray<BranchStep>;
|
|
3657
4165
|
|
|
3658
4166
|
/** The teardown plan — drop the whole branch namespace (idempotent on the
|
|
3659
4167
|
* executor side). */
|
|
@@ -4583,11 +5091,22 @@ declare interface RawSqlFragment {
|
|
|
4583
5091
|
readonly strings: ReadonlyArray<string>;
|
|
4584
5092
|
readonly values: ReadonlyArray<unknown>;
|
|
4585
5093
|
/**
|
|
4586
|
-
* Tables this raw read depends on, for reactive invalidation. Raw
|
|
4587
|
-
*
|
|
4588
|
-
*
|
|
4589
|
-
* `store.raw(fragment, { dependsOn })`)
|
|
4590
|
-
*
|
|
5094
|
+
* Tables this raw read depends on, for reactive invalidation. Raw reads are
|
|
5095
|
+
* otherwise untracked — the query planner cannot infer which tables an
|
|
5096
|
+
* arbitrary SQL string touches. Declare them here (or via
|
|
5097
|
+
* `store.raw(fragment, { dependsOn })`).
|
|
5098
|
+
*
|
|
5099
|
+
* WHAT IT DRIVES, stated narrowly because the previous wording promised more
|
|
5100
|
+
* than any code delivered. For a live query whose handler returns a COMPUTED
|
|
5101
|
+
* VALUE — the shape whose handler is genuinely re-run on a change — these
|
|
5102
|
+
* tables JOIN the query's declared `source:`, so a write to one recomputes the
|
|
5103
|
+
* subscription. That is the only shape where re-running can refresh a raw
|
|
5104
|
+
* result. A handler that returns a query DESCRIPTOR re-runs the DESCRIPTOR on
|
|
5105
|
+
* a change, not the handler, so declaring tables cannot refresh the raw read
|
|
5106
|
+
* there; the runtime warns about that case at subscribe time instead.
|
|
5107
|
+
*
|
|
5108
|
+
* Best-effort in both directions: the tables are recorded as declared and
|
|
5109
|
+
* never validated against the SQL.
|
|
4591
5110
|
*/
|
|
4592
5111
|
readonly dependsOn?: ReadonlyArray<string>;
|
|
4593
5112
|
}
|
|
@@ -4617,6 +5136,15 @@ export declare const readableByColumns: (table: TableLike) => ReadonlyArray<Read
|
|
|
4617
5136
|
* would truncate. */
|
|
4618
5137
|
export declare const real: () => ColumnBuilder<number, "real", boolean>;
|
|
4619
5138
|
|
|
5139
|
+
/**
|
|
5140
|
+
* Record one completed operation: the counter, the duration histogram, and — on
|
|
5141
|
+
* a throw — the error counter.
|
|
5142
|
+
*
|
|
5143
|
+
* Prefer {@link observeDbOp}, which also moves the in-flight gauge and cannot
|
|
5144
|
+
* forget the `error` arm on a throw.
|
|
5145
|
+
*/
|
|
5146
|
+
export declare const recordDbOp: (s: DbOpSample) => void;
|
|
5147
|
+
|
|
4620
5148
|
/** One write, as the store sees it at the moment it happens. */
|
|
4621
5149
|
export declare interface RecordedWrite {
|
|
4622
5150
|
readonly table: string;
|
|
@@ -4733,7 +5261,33 @@ export declare const registerPendingAttribution: (key: string, attribution: Writ
|
|
|
4733
5261
|
*/
|
|
4734
5262
|
export declare const registerRelations: (spec: RelationsSpec) => void;
|
|
4735
5263
|
|
|
4736
|
-
/**
|
|
5264
|
+
/**
|
|
5265
|
+
* Register the retention bound for a table.
|
|
5266
|
+
*
|
|
5267
|
+
* ── Two registrations for one table used to be a silent last-write-wins ─────
|
|
5268
|
+
*
|
|
5269
|
+
* A consumer registered `_voltro_schedule_claims` at 1 hour from a startup, and
|
|
5270
|
+
* one second later the framework registered its own default for the same table.
|
|
5271
|
+
* Ours won, nothing said so, and their startup went on logging `bounded to 1h`
|
|
5272
|
+
* at every boot while the table kept everything younger than the framework's
|
|
5273
|
+
* TTL. They found it by counting rows, not by reading a log.
|
|
5274
|
+
*
|
|
5275
|
+
* `registry.set(table, spec)` is what did that, and the comment above it —
|
|
5276
|
+
* "so a double-registration (e.g. plugin re-init) is idempotent" — describes a
|
|
5277
|
+
* case that really exists (a plugin re-registering ITS OWN spec) and silently
|
|
5278
|
+
* covered a different one.
|
|
5279
|
+
*
|
|
5280
|
+
* **Precedence now decides, and a real conflict is always reported.** An app's
|
|
5281
|
+
* registration outranks a plugin's, which outranks a framework default; a tie
|
|
5282
|
+
* keeps the later one, as before. `source` defaults to `'app'`, so an
|
|
5283
|
+
* application does not have to know the field exists to win with it.
|
|
5284
|
+
*
|
|
5285
|
+
* The direction was not obvious and is worth stating: the loser here is chosen
|
|
5286
|
+
* by WHO registered, not by which TTL is narrower. A narrower TTL deletes more,
|
|
5287
|
+
* and picking "narrower wins" would let a framework default we tighten in some
|
|
5288
|
+
* future release silently start deleting an app's data faster than the app
|
|
5289
|
+
* asked for. Whoever owns the data decides; we are the fallback.
|
|
5290
|
+
*/
|
|
4737
5291
|
export declare const registerRetention: (spec: RetentionSpec) => void;
|
|
4738
5292
|
|
|
4739
5293
|
/**
|
|
@@ -4808,6 +5362,9 @@ export declare const requireTable: (tableName: string) => TableLike;
|
|
|
4808
5362
|
|
|
4809
5363
|
export declare const requireTenants: () => (() => TableLike);
|
|
4810
5364
|
|
|
5365
|
+
/** Test seams for the cardinality guard — the caches are module-private otherwise. */
|
|
5366
|
+
export declare const resetDbMetricTagCacheForTests: () => void;
|
|
5367
|
+
|
|
4811
5368
|
/**
|
|
4812
5369
|
* Re-read SNOWFLAKE_MACHINE_ID from the environment. Use in tests or
|
|
4813
5370
|
* after a runtime reconfiguration. Production code should set the env
|
|
@@ -4874,6 +5431,13 @@ export declare const resolveActorSnapshot: (store: ActorLookupStore, subjectId:
|
|
|
4874
5431
|
*/
|
|
4875
5432
|
export declare const resolveBranchMechanism: (opts: BranchMechanismOptions) => BranchMechanism;
|
|
4876
5433
|
|
|
5434
|
+
/** Resolve the re-warn interval. Env overrides the caller, like every other
|
|
5435
|
+
* tunable here; an unparseable or negative value falls through to the default
|
|
5436
|
+
* rather than being honoured. */
|
|
5437
|
+
export declare const resolveEagerFallbackWarnIntervalMs: (configured?: number | undefined, env?: {
|
|
5438
|
+
readonly VOLTRO_DB_EAGER_FALLBACK_WARN_INTERVAL_MS?: string;
|
|
5439
|
+
}) => number;
|
|
5440
|
+
|
|
4877
5441
|
/**
|
|
4878
5442
|
* Answer a transport echo with the attribution of the local write that produced
|
|
4879
5443
|
* it, or with `undefined` when the write came from another replica.
|
|
@@ -4943,6 +5507,22 @@ export declare const resolveTenantHome: (tenantId: string | null | undefined, co
|
|
|
4943
5507
|
*/
|
|
4944
5508
|
export declare const resolveTenantNamespace: (tenantId: string | null | undefined) => string;
|
|
4945
5509
|
|
|
5510
|
+
export declare interface RetentionConflict {
|
|
5511
|
+
readonly table: string;
|
|
5512
|
+
readonly kept: {
|
|
5513
|
+
readonly source: RetentionSource;
|
|
5514
|
+
readonly ttlMs: number;
|
|
5515
|
+
};
|
|
5516
|
+
readonly dropped: {
|
|
5517
|
+
readonly source: RetentionSource;
|
|
5518
|
+
readonly ttlMs: number;
|
|
5519
|
+
};
|
|
5520
|
+
}
|
|
5521
|
+
|
|
5522
|
+
/** Who registered a bound. Higher precedence first: an app's deliberate policy
|
|
5523
|
+
* outranks a plugin's, which outranks a framework default. */
|
|
5524
|
+
export declare type RetentionSource = 'app' | 'plugin' | 'framework';
|
|
5525
|
+
|
|
4946
5526
|
/** A registered bound on one append-only table. */
|
|
4947
5527
|
export declare interface RetentionSpec {
|
|
4948
5528
|
/** Physical table name, e.g. `_voltro_schedule_runs`. */
|
|
@@ -4961,6 +5541,16 @@ export declare interface RetentionSpec {
|
|
|
4961
5541
|
* plain time-only purge.
|
|
4962
5542
|
*/
|
|
4963
5543
|
readonly where?: Predicate;
|
|
5544
|
+
/**
|
|
5545
|
+
* Who registered this. Decides which registration wins when two arrive for
|
|
5546
|
+
* one table — see {@link registerRetention}.
|
|
5547
|
+
*
|
|
5548
|
+
* DEFAULTS TO `'app'`, and that default is the feature: an application
|
|
5549
|
+
* registering a bound has said something deliberate about ITS data, so it
|
|
5550
|
+
* outranks a framework default without having to know this field exists. The
|
|
5551
|
+
* framework and plugins pass their own value explicitly.
|
|
5552
|
+
*/
|
|
5553
|
+
readonly source?: RetentionSource;
|
|
4964
5554
|
/** Optional human label for logs (defaults to the table name). */
|
|
4965
5555
|
readonly label?: string;
|
|
4966
5556
|
/**
|
|
@@ -5048,6 +5638,57 @@ export declare interface RuleViolationDetail {
|
|
|
5048
5638
|
readonly message?: string;
|
|
5049
5639
|
}
|
|
5050
5640
|
|
|
5641
|
+
/**
|
|
5642
|
+
* Run `body` inside one retrying transaction and settle its Exit.
|
|
5643
|
+
*
|
|
5644
|
+
* Owns, for every dialect:
|
|
5645
|
+
* 1. the connection handshake — `withTransaction` + `TransactionConnection`,
|
|
5646
|
+
* with a loud failure rather than a silent non-transactional run when the
|
|
5647
|
+
* service is somehow absent;
|
|
5648
|
+
* 2. **commit-defect promotion** — `@effect/sql` runs COMMIT as `Effect.orDie`,
|
|
5649
|
+
* so a serialization/deadlock error raised AT COMMIT arrives as a defect and
|
|
5650
|
+
* `Effect.retry` never sees it. A retryable defect is promoted back to a
|
|
5651
|
+
* typed failure so the schedule can replay it; anything else re-dies, so
|
|
5652
|
+
* genuine defects keep crash semantics;
|
|
5653
|
+
* 3. the retry schedule — exponential backoff from 10ms, up to 3 retries,
|
|
5654
|
+
* gated on the dialect's `isRetryable`. Each attempt re-runs `body` from
|
|
5655
|
+
* scratch against a FRESH transaction;
|
|
5656
|
+
* 4. the span, covering all attempts as one logical operation;
|
|
5657
|
+
* 5. settling through `settleTransactionExit`, never `runPromise`.
|
|
5658
|
+
*
|
|
5659
|
+
* `body` receives the attempt number (1-based) so a caller can build per-attempt
|
|
5660
|
+
* state that must not be shared across a retry.
|
|
5661
|
+
*/
|
|
5662
|
+
export declare const runRetryingTransaction: <A>(spec: DialectTransactionSpec & {
|
|
5663
|
+
readonly body: (conn: TransactionConnectionContext, attempt: number) => Promise<A>;
|
|
5664
|
+
}) => Promise<A>;
|
|
5665
|
+
|
|
5666
|
+
/**
|
|
5667
|
+
* THE entry point every dialect store's `transactional()` — and postgres'
|
|
5668
|
+
* `runInNamespace()` — routes through.
|
|
5669
|
+
*
|
|
5670
|
+
* On top of `runRetryingTransaction` it owns the two things that are about the
|
|
5671
|
+
* CALLER rather than the engine:
|
|
5672
|
+
*
|
|
5673
|
+
* - **write attribution.** Captured HERE, synchronously, before any await:
|
|
5674
|
+
* `transactional()` is entered from the request's async-local scope but the
|
|
5675
|
+
* callback runs from inside the Effect, where — measured against live
|
|
5676
|
+
* postgres — that scope is EMPTY. The captured value is handed to
|
|
5677
|
+
* `makeView` so every write the view makes carries the identity explicitly
|
|
5678
|
+
* (a pool handoff cannot strand it), and the scope is re-entered around
|
|
5679
|
+
* `work` for anything the view reaches that is not threaded. `undefined`
|
|
5680
|
+
* stays `undefined`: "no request behind this write" and "a request that knew
|
|
5681
|
+
* nothing" are different facts and must not blur.
|
|
5682
|
+
* - **event drain ordering.** A fresh view per attempt, and `commitEvents()`
|
|
5683
|
+
* only after the Exit settled successfully — so a retried attempt's buffered
|
|
5684
|
+
* events are dropped with its unreachable view and subscribers see exactly
|
|
5685
|
+
* the winning attempt, once.
|
|
5686
|
+
*/
|
|
5687
|
+
export declare const runStoreTransaction: <T, V extends DataStore & TransactionalViewHandle>(spec: DialectTransactionSpec & {
|
|
5688
|
+
readonly work: (tx: DataStore) => Promise<T>;
|
|
5689
|
+
readonly makeView: (conn: TransactionConnectionContext, attr: WriteAttribution | undefined) => V;
|
|
5690
|
+
}) => Promise<T>;
|
|
5691
|
+
|
|
5051
5692
|
/**
|
|
5052
5693
|
* Run `fn` with `attribution` active for its whole (sync + awaited) execution.
|
|
5053
5694
|
*
|
|
@@ -5065,6 +5706,16 @@ export declare const runWithWriteAttribution: <T>(attribution: WriteAttribution,
|
|
|
5065
5706
|
*/
|
|
5066
5707
|
export declare const runWriteRecorders: (port: TxnRecorderPort, write: RecordedWrite) => Promise<void>;
|
|
5067
5708
|
|
|
5709
|
+
/**
|
|
5710
|
+
* Are two connections pointed at the same database?
|
|
5711
|
+
*
|
|
5712
|
+
* Three answers, and the third is the load-bearing one: `'unknown'` means at
|
|
5713
|
+
* least one side could not be identified, and the caller keeps whatever
|
|
5714
|
+
* conservative behaviour it had. Collapsing that into `false` is how a guard
|
|
5715
|
+
* stops guarding.
|
|
5716
|
+
*/
|
|
5717
|
+
export declare const sameDatabase: (a: string | undefined, b: string | undefined) => "same" | "different" | "unknown";
|
|
5718
|
+
|
|
5068
5719
|
/**
|
|
5069
5720
|
* Lower a tenant id to the safe identifier fragment used inside a
|
|
5070
5721
|
* namespace name. ONLY `[a-z0-9_]` survive; every other character —
|
|
@@ -5082,6 +5733,15 @@ export declare const runWriteRecorders: (port: TxnRecorderPort, write: RecordedW
|
|
|
5082
5733
|
*/
|
|
5083
5734
|
export declare const sanitizeIdentifierFragment: (tenantId: string) => string;
|
|
5084
5735
|
|
|
5736
|
+
/** What a schema-change firing knows: which tables the apply actually touched.
|
|
5737
|
+
* A seed watches table NAMES (`watchedTables`), so this is the list the
|
|
5738
|
+
* runner intersects against — an empty list fires nothing. */
|
|
5739
|
+
export declare interface SchemaChangeSeedEvent {
|
|
5740
|
+
readonly changedTables: ReadonlyArray<string>;
|
|
5741
|
+
}
|
|
5742
|
+
|
|
5743
|
+
export declare type SchemaChangeSeedHook = (event: SchemaChangeSeedEvent) => Promise<void>;
|
|
5744
|
+
|
|
5085
5745
|
/**
|
|
5086
5746
|
* The PUBLIC structural view of a built table — name, columns, and applied
|
|
5087
5747
|
* indexes — with NONE of the fluent builder methods.
|
|
@@ -5129,6 +5789,7 @@ export declare interface SeedDefinition {
|
|
|
5129
5789
|
readonly name: string;
|
|
5130
5790
|
readonly lifecycle: SeedLifecycle;
|
|
5131
5791
|
readonly cron?: string;
|
|
5792
|
+
readonly timezone?: string;
|
|
5132
5793
|
readonly watchedTables?: ReadonlyArray<string>;
|
|
5133
5794
|
readonly fingerprint?: (ctx: {
|
|
5134
5795
|
src: string;
|
|
@@ -5143,6 +5804,15 @@ export declare interface SeedDefinitionInput {
|
|
|
5143
5804
|
readonly lifecycle: SeedLifecycle;
|
|
5144
5805
|
/** Required for `lifecycle: 'cron'`. Standard cron expression or `@hourly`/`@daily` etc. */
|
|
5145
5806
|
readonly cron?: string;
|
|
5807
|
+
/**
|
|
5808
|
+
* IANA timezone the `cron` expression is read in. Default `'UTC'`.
|
|
5809
|
+
*
|
|
5810
|
+
* Defaulted rather than required, unlike `defineSchedule`'s: a seed is
|
|
5811
|
+
* reference data, so "02:00 in Europe/Berlin" is a much rarer requirement
|
|
5812
|
+
* than it is for a business schedule — but server-local is never the answer,
|
|
5813
|
+
* so the default is an explicit UTC rather than the container's clock.
|
|
5814
|
+
*/
|
|
5815
|
+
readonly timezone?: string;
|
|
5146
5816
|
/** Required for `lifecycle: 'onSchemaChange'`. Table names to watch. */
|
|
5147
5817
|
readonly watchedTables?: ReadonlyArray<string>;
|
|
5148
5818
|
/**
|
|
@@ -5268,6 +5938,12 @@ export declare const serverOnlyColumns: (table: TableLike) => ReadonlyArray<stri
|
|
|
5268
5938
|
* to the pure resolvers in tests instead. */
|
|
5269
5939
|
export declare const setResidencyConfig: (config: ResidencyConfig) => ResidencyConfig;
|
|
5270
5940
|
|
|
5941
|
+
/** Install (or, with `null`, remove) the `onSchemaChange` runner. */
|
|
5942
|
+
export declare const setSchemaChangeSeedHook: (hook: SchemaChangeSeedHook | null) => void;
|
|
5943
|
+
|
|
5944
|
+
/** Install (or, with `null`, remove) the `onTenantCreate` runner. */
|
|
5945
|
+
export declare const setTenantCreateSeedHook: (hook: TenantCreateSeedHook | null) => void;
|
|
5946
|
+
|
|
5271
5947
|
/**
|
|
5272
5948
|
* Unwrap a transaction program's `Exit`: return the value, or throw the
|
|
5273
5949
|
* ORIGINAL error rather than Effect's `FiberFailure` wrapper.
|
|
@@ -5490,6 +6166,51 @@ export declare interface StreamTableOptions {
|
|
|
5490
6166
|
readonly retry?: Schedule.Schedule<unknown, TableStreamError>;
|
|
5491
6167
|
}
|
|
5492
6168
|
|
|
6169
|
+
export declare interface SubjectGraph {
|
|
6170
|
+
readonly subjectTable: string;
|
|
6171
|
+
readonly paths: ReadonlyArray<SubjectPath>;
|
|
6172
|
+
readonly limitations: ReadonlyArray<SubjectGraphLimitation>;
|
|
6173
|
+
}
|
|
6174
|
+
|
|
6175
|
+
/** A known blind spot in a derived graph. Always reported. */
|
|
6176
|
+
export declare interface SubjectGraphLimitation {
|
|
6177
|
+
readonly kind:
|
|
6178
|
+
/** BFS hit `maxDepth`; tables beyond `table` were not explored. */
|
|
6179
|
+
'depth-truncated'
|
|
6180
|
+
/** A table nothing links to the subject — invisible to this derivation. */
|
|
6181
|
+
| 'unreachable'
|
|
6182
|
+
/** Excluded by configuration. */
|
|
6183
|
+
| 'excluded';
|
|
6184
|
+
readonly table: string;
|
|
6185
|
+
readonly detail: string;
|
|
6186
|
+
}
|
|
6187
|
+
|
|
6188
|
+
/** One hop away from the previous table. */
|
|
6189
|
+
export declare interface SubjectHop {
|
|
6190
|
+
/** The table this hop lands on. */
|
|
6191
|
+
readonly table: string;
|
|
6192
|
+
/** Column on `table` holding the PREVIOUS row's key. */
|
|
6193
|
+
readonly foreignKey: string;
|
|
6194
|
+
/** Column on the PREVIOUS table whose value `foreignKey` holds. Almost always
|
|
6195
|
+
* `id`; `sourceKey` on a relation can move it. */
|
|
6196
|
+
readonly parentKey: string;
|
|
6197
|
+
}
|
|
6198
|
+
|
|
6199
|
+
/**
|
|
6200
|
+
* An ordered route from the subject table to a table holding their data.
|
|
6201
|
+
* `hops` is empty for the subject table itself (the row keyed by the subject id).
|
|
6202
|
+
*/
|
|
6203
|
+
export declare interface SubjectPath {
|
|
6204
|
+
/** Terminal table — where rows are read/erased. */
|
|
6205
|
+
readonly table: string;
|
|
6206
|
+
readonly hops: ReadonlyArray<SubjectHop>;
|
|
6207
|
+
/** `hops.length`. 0 = the subject row itself, 1 = a direct child. */
|
|
6208
|
+
readonly depth: number;
|
|
6209
|
+
/** Which declaration produced it — a declared `relations()` block, an
|
|
6210
|
+
* inferred `reference()` column, or an app-supplied `subjectScopes` entry. */
|
|
6211
|
+
readonly via: 'relation' | 'reference' | 'declared';
|
|
6212
|
+
}
|
|
6213
|
+
|
|
5493
6214
|
/**
|
|
5494
6215
|
* `col IN (SELECT col FROM ...)` / `col NOT IN (SELECT ...)` — the
|
|
5495
6216
|
* RHS is a sub-query descriptor. The sub-query MUST project a
|
|
@@ -6117,6 +6838,17 @@ export declare interface TableUnique {
|
|
|
6117
6838
|
*/
|
|
6118
6839
|
export declare const teardownBranch: (branchId: string, mechanism: BranchMechanism, executor: BranchExecutor) => Promise<ReadonlyArray<BranchStep>>;
|
|
6119
6840
|
|
|
6841
|
+
/** What a tenant-create firing knows: which namespace was just provisioned. */
|
|
6842
|
+
export declare interface TenantCreateSeedEvent {
|
|
6843
|
+
/** The namespace (postgres schema / mysql database / attached sqlite file)
|
|
6844
|
+
* that was just created. Seeds run scoped to THIS namespace, never the
|
|
6845
|
+
* shared one. */
|
|
6846
|
+
readonly namespace: string;
|
|
6847
|
+
readonly dialect: string;
|
|
6848
|
+
}
|
|
6849
|
+
|
|
6850
|
+
export declare type TenantCreateSeedHook = (event: TenantCreateSeedEvent) => Promise<void>;
|
|
6851
|
+
|
|
6120
6852
|
/** A tenant's declared home — the region it's pinned to, optionally a named
|
|
6121
6853
|
* connection/secret key for that region's DB. */
|
|
6122
6854
|
export declare interface TenantHome {
|
|
@@ -6212,6 +6944,22 @@ export declare const timestampMs: Schema.Schema<Date, number>;
|
|
|
6212
6944
|
*/
|
|
6213
6945
|
export declare const timestampMsOrNull: Schema.Schema<Date | null, number | null>;
|
|
6214
6946
|
|
|
6947
|
+
/**
|
|
6948
|
+
* A store's per-transaction `DataStore` view: buffers the ChangeEvents its
|
|
6949
|
+
* writes produce and drains them to the parent emitter only once the
|
|
6950
|
+
* transaction has actually committed.
|
|
6951
|
+
*/
|
|
6952
|
+
export declare interface TransactionalViewHandle {
|
|
6953
|
+
commitEvents(): void;
|
|
6954
|
+
}
|
|
6955
|
+
|
|
6956
|
+
/**
|
|
6957
|
+
* The `[Connection, depth]` tuple `@effect/sql` provides inside
|
|
6958
|
+
* `withTransaction`. Every statement that must run on the transaction's OWN
|
|
6959
|
+
* connection (rather than a fresh one from the pool) is given this explicitly.
|
|
6960
|
+
*/
|
|
6961
|
+
export declare type TransactionConnectionContext = Context.Tag.Service<typeof TransactionConnection>;
|
|
6962
|
+
|
|
6215
6963
|
/** Append one row inside the caller's transaction. Insert-only by design. */
|
|
6216
6964
|
export declare type TxnAppend = (table: string, row: Row) => Promise<void>;
|
|
6217
6965
|
|
|
@@ -6730,6 +7478,21 @@ export declare interface WriteAttribution {
|
|
|
6730
7478
|
/** `null` is meaningful: a resolved subject with no acting user (an
|
|
6731
7479
|
* anonymous or system call), as distinct from `undefined` = no request. */
|
|
6732
7480
|
readonly subjectId?: string | null;
|
|
7481
|
+
/**
|
|
7482
|
+
* The write was made BY AN AGENT on the subject's behalf.
|
|
7483
|
+
*
|
|
7484
|
+
* `subjectId` stays the PERSON — an agent never escalates identity, which is
|
|
7485
|
+
* the whole property `@voltro/ai`'s `agentActor` encodes. So without a second
|
|
7486
|
+
* field, "Anna archived this invoice" and "an agent archived it while acting
|
|
7487
|
+
* as Anna" are the same row, and the difference is the one a reviewer of an
|
|
7488
|
+
* agent rollout actually asks about.
|
|
7489
|
+
*
|
|
7490
|
+
* Set by the transports that KNOW: the app-tool surface reached over MCP
|
|
7491
|
+
* (`@voltro/cli`'s `agentToolSurface`) and any in-process `appTools` caller
|
|
7492
|
+
* that opts in. Absent for an ordinary request — absent means "a human or a
|
|
7493
|
+
* service called this directly", not "unknown".
|
|
7494
|
+
*/
|
|
7495
|
+
readonly via?: 'agent';
|
|
6733
7496
|
}
|
|
6734
7497
|
|
|
6735
7498
|
export declare type WriteRecorder = (port: TxnRecorderPort, write: RecordedWrite) => Promise<void>;
|