@voltro/database 0.54.0 → 0.56.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 +639 -2
- package/dist/{frameworkLiveTables-ChVR-C5F.js → frameworkLiveTables-CwDemhjW.js} +139 -139
- package/dist/index.d.ts +328 -3
- package/dist/index.js +788 -693
- package/dist/sql.d.ts +15 -24
- package/dist/sql.js +873 -853
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -621,6 +621,11 @@ export declare interface CdcConfig {
|
|
|
621
621
|
readonly exclusionRefreshFollows?: boolean;
|
|
622
622
|
}
|
|
623
623
|
|
|
624
|
+
/** Env var that tunes how long a change-claim row survives. Declared here rather
|
|
625
|
+
* than at the retention registration, because the OCCURRENCE COUNTER's window
|
|
626
|
+
* has to be derived from the same number and lives in this file. */
|
|
627
|
+
export declare const CHANGE_CLAIM_TTL_ENV = "VOLTRO_CHANGE_CLAIMS_TTL_HOURS";
|
|
628
|
+
|
|
624
629
|
/** Maximum `onChange` listeners a DataStore's change bus accepts before Node
|
|
625
630
|
* warns. See this file's header for why it is 512 and why it is not `0`. */
|
|
626
631
|
export declare const CHANGE_LISTENER_CEILING = 512;
|
|
@@ -638,6 +643,17 @@ export declare interface ChangeCaptureExclusionRefresh {
|
|
|
638
643
|
/** A row was removed from the table. `new` is null, always. */
|
|
639
644
|
export declare const changeDelete: (table: string, row: Row, meta?: ChangeEventMeta) => ChangeEvent;
|
|
640
645
|
|
|
646
|
+
/** The content half of a change identity — equal iff two replicas are looking
|
|
647
|
+
* at the same change (or at two changes with byte-identical images, which the
|
|
648
|
+
* occurrence counter then separates). */
|
|
649
|
+
export declare const changeDigest: (input: {
|
|
650
|
+
readonly pipe: string;
|
|
651
|
+
readonly op: "insert" | "update" | "delete";
|
|
652
|
+
readonly key: string;
|
|
653
|
+
readonly next: Record<string, unknown> | null;
|
|
654
|
+
readonly prev: Record<string, unknown> | null;
|
|
655
|
+
}) => string;
|
|
656
|
+
|
|
641
657
|
/**
|
|
642
658
|
* A row-change event emitted by the data store (in-memory) or the CDC
|
|
643
659
|
* consumer (Postgres logical replication). Drives the reactive engine.
|
|
@@ -766,6 +782,22 @@ export declare const changeSoftDelete: (table: string, row: Row, opts?: {
|
|
|
766
782
|
*/
|
|
767
783
|
export declare type ChangeStrategy = 'inline' | 'cdc';
|
|
768
784
|
|
|
785
|
+
/**
|
|
786
|
+
* A proven interruption in a store's change stream.
|
|
787
|
+
*
|
|
788
|
+
* Deliberately NOT a list of what was missed. A pub/sub transport keeps no log,
|
|
789
|
+
* so the missed changes are gone — the only honest recovery is to re-derive
|
|
790
|
+
* everything downstream, and a consumer that were handed a partial list would be
|
|
791
|
+
* tempted to patch instead. `reason` is for the log line; the instruction is the
|
|
792
|
+
* event itself.
|
|
793
|
+
*/
|
|
794
|
+
export declare interface ChangeStreamGap {
|
|
795
|
+
/** Human-readable cause — goes into the refresh's log line. */
|
|
796
|
+
readonly reason: string;
|
|
797
|
+
/** Wall-clock ms the stream was known to be down, when it is known. */
|
|
798
|
+
readonly downForMs?: number;
|
|
799
|
+
}
|
|
800
|
+
|
|
769
801
|
/** A row changed. Both sides are present, because an update has both. */
|
|
770
802
|
export declare const changeUpdate: (table: string, before: Row, after: Row, meta?: ChangeEventMeta) => ChangeEvent;
|
|
771
803
|
|
|
@@ -1772,6 +1804,9 @@ export declare const compileRawFragment: <Row extends object = Record<string, un
|
|
|
1772
1804
|
|
|
1773
1805
|
export declare const compileSelect: <Row extends object>(descriptor: QueryDescriptor, sql: SqlConstructor, namespace?: QueryNamespace) => Statement.Statement<Row>;
|
|
1774
1806
|
|
|
1807
|
+
/** Compose the durable identity written to `_voltro_cdcout_claims.changeKey`. */
|
|
1808
|
+
export declare const composeChangeKey: (digest: string, occurrence: number) => string;
|
|
1809
|
+
|
|
1775
1810
|
/**
|
|
1776
1811
|
* Compute the embedded row from a {@link VectorEmbeddingContext}. Returns
|
|
1777
1812
|
* the patch to merge into the row: `{ [as]: <vector> }`. When the source
|
|
@@ -2298,6 +2333,23 @@ export declare interface DataStore {
|
|
|
2298
2333
|
* must elect a single worker instead of filtering by origin.
|
|
2299
2334
|
*/
|
|
2300
2335
|
readonly changeScope?: 'local' | 'fleet';
|
|
2336
|
+
/**
|
|
2337
|
+
* Subscribe to CHANGE-STREAM GAPS — moments where this process proved it
|
|
2338
|
+
* stopped receiving changes for a while and has now started again.
|
|
2339
|
+
*
|
|
2340
|
+
* A gap is not an error to show a user; it is an instruction to the reactive
|
|
2341
|
+
* layer: everything derived from this store may be stale, so re-run it. A
|
|
2342
|
+
* live query is idempotent, which is what makes "re-run all of them" both
|
|
2343
|
+
* safe and the complete recovery. `voltro dev` and `voltro serve` wire this
|
|
2344
|
+
* to the dispatcher's `refreshAll`, the same recovery the broadcast bus's
|
|
2345
|
+
* peer-serial gap uses.
|
|
2346
|
+
*
|
|
2347
|
+
* Optional because only a store whose changes arrive over a TRANSPORT can
|
|
2348
|
+
* have one. An inline store emits its own writes in-process — there is no
|
|
2349
|
+
* connection to lose — and correctly does not implement this. Absent
|
|
2350
|
+
* therefore means "gaps are impossible here", never "gaps are not reported".
|
|
2351
|
+
*/
|
|
2352
|
+
readonly onChangeStreamGap?: (listener: (gap: ChangeStreamGap) => void) => () => void;
|
|
2301
2353
|
/**
|
|
2302
2354
|
* Bind a per-request tenant NAMESPACE, returning a store view whose
|
|
2303
2355
|
* every read + write is physically isolated to that namespace
|
|
@@ -2478,6 +2530,38 @@ export declare interface DataStore {
|
|
|
2478
2530
|
* callers guard with `if (store.refreshChangeCaptureExclusions)`.
|
|
2479
2531
|
*/
|
|
2480
2532
|
refreshChangeCaptureExclusions?(): Promise<ChangeCaptureExclusionRefresh>;
|
|
2533
|
+
/**
|
|
2534
|
+
* Run an Effect that needs this store's own `SqlClient` — the raw-SQL seam.
|
|
2535
|
+
*
|
|
2536
|
+
* The capability the STAGED SWAP is built on. `--mode replace` loads into
|
|
2537
|
+
* `_voltro_staging_<t>` tables and then swaps the CONTENT in one short
|
|
2538
|
+
* transaction (`DELETE FROM t; INSERT INTO t (…) SELECT … FROM
|
|
2539
|
+
* _voltro_staging_t`), which moves the long, network-bound load OUT of the
|
|
2540
|
+
* destructive transaction. None of that is expressible through the typed row
|
|
2541
|
+
* API: it is DDL plus a server-side `INSERT … SELECT`, and the whole point is
|
|
2542
|
+
* that no row crosses the wire.
|
|
2543
|
+
*
|
|
2544
|
+
* OPTIONAL on the interface, and declared here rather than duck-typed at the
|
|
2545
|
+
* call site for the reason the house rule gives: the four dialect stores are
|
|
2546
|
+
* four hand-written copies of one interface, and a capability nothing declares
|
|
2547
|
+
* is a capability nothing can be red about. A store that loses it now fails to
|
|
2548
|
+
* satisfy this member instead of silently falling out of `canStage()` and
|
|
2549
|
+
* taking the weaker path forever, on one dialect, in silence.
|
|
2550
|
+
*
|
|
2551
|
+
* The DECISION does not live per store — that would be the four-copies trap
|
|
2552
|
+
* wearing a different hat. `@voltro/data-transfer`'s `stagedSwap.ts` writes
|
|
2553
|
+
* the per-dialect SQL ONCE and sends it down this seam; the store contributes
|
|
2554
|
+
* a connection, not a policy.
|
|
2555
|
+
*
|
|
2556
|
+
* Callers guard with `if (store.run)` (or `canStage(store)`), and a store
|
|
2557
|
+
* without it keeps the path it already has: the in-memory store has no SQL
|
|
2558
|
+
* and no catalog, and neither does a bare `DataStore` view.
|
|
2559
|
+
*
|
|
2560
|
+
* `R` is deliberately `SqlClient.SqlClient`: the effect asks for the client
|
|
2561
|
+
* and the store provides its own, which is what makes this a SEAM rather than
|
|
2562
|
+
* a way to hand a store somebody else's connection.
|
|
2563
|
+
*/
|
|
2564
|
+
run?<A, E>(effect: Effect.Effect<A, E, SqlClient.SqlClient>): Promise<A>;
|
|
2481
2565
|
}
|
|
2482
2566
|
|
|
2483
2567
|
/** One transfer, as the inspector reads it back. */
|
|
@@ -2692,6 +2776,10 @@ export declare const decryptFieldsOnRead: (rows: ReadonlyArray<Row>, table: Tabl
|
|
|
2692
2776
|
*/
|
|
2693
2777
|
export declare const DEFAULT_ACQUIRE_TIMEOUT_MS = 10000;
|
|
2694
2778
|
|
|
2779
|
+
/** Default lifetime of a change-claim row, in hours. A lock ledger, not a
|
|
2780
|
+
* history — nobody reads yesterday's claim. */
|
|
2781
|
+
export declare const DEFAULT_CHANGE_CLAIM_TTL_HOURS = 1;
|
|
2782
|
+
|
|
2695
2783
|
/**
|
|
2696
2784
|
* How often a persistent fallback is allowed to say so.
|
|
2697
2785
|
*
|
|
@@ -3466,7 +3554,6 @@ export declare const formatSubjectGraph: (graph: SubjectGraph) => string;
|
|
|
3466
3554
|
*/
|
|
3467
3555
|
export declare const FRAMEWORK_LIVE_TABLES: ReadonlySet<string>;
|
|
3468
3556
|
|
|
3469
|
-
/** All framework tables as an array — useful for bootstrap DDL emission. */
|
|
3470
3557
|
export declare const frameworkTables: readonly [TableLike, TableLike, TableLike];
|
|
3471
3558
|
|
|
3472
3559
|
declare type FullTextIndexMeta = {
|
|
@@ -3828,6 +3915,13 @@ export declare interface InterruptedReplace {
|
|
|
3828
3915
|
readonly tables: number | null;
|
|
3829
3916
|
readonly capture: string | null;
|
|
3830
3917
|
readonly via: string;
|
|
3918
|
+
/** The staging tables the run created. Empty for every kind but `staging`. */
|
|
3919
|
+
readonly staging: ReadonlyArray<string>;
|
|
3920
|
+
/** Last sign of life from the run, or `null` when it never wrote one. */
|
|
3921
|
+
readonly heartbeatAt: Date | null;
|
|
3922
|
+
/** `true` when the run was started `--no-atomic` — its staging is a resume
|
|
3923
|
+
* point, not a leftover. */
|
|
3924
|
+
readonly resumable: boolean;
|
|
3831
3925
|
}
|
|
3832
3926
|
|
|
3833
3927
|
/**
|
|
@@ -3972,6 +4066,41 @@ export declare const isSeedDefinition: (value: unknown) => value is SeedDefiniti
|
|
|
3972
4066
|
*/
|
|
3973
4067
|
export declare const isSqliteFamily: (dialect: DialectId) => boolean;
|
|
3974
4068
|
|
|
4069
|
+
/**
|
|
4070
|
+
* True when `name` is a `--mode replace` staging table.
|
|
4071
|
+
*
|
|
4072
|
+
* ── This is a THIRD rule, and it must not be folded into the other two ─────
|
|
4073
|
+
*
|
|
4074
|
+
* The framework-table filter above is deliberately ASYMMETRIC: a framework
|
|
4075
|
+
* table nobody declares is never planned for a drop, and one we DO declare
|
|
4076
|
+
* diffs like any other table. Collapsing those two into one filter is what
|
|
4077
|
+
* produced a postgres-only evolution path for the framework's own tables, and
|
|
4078
|
+
* the comment in `boot.ts` exists to stop it being collapsed again.
|
|
4079
|
+
*
|
|
4080
|
+
* A staging table satisfies NEITHER half. It is not a schema fact at all — it is
|
|
4081
|
+
* a scratch copy of a target table that exists for the length of one import and
|
|
4082
|
+
* is dropped after the swap. So:
|
|
4083
|
+
*
|
|
4084
|
+
* - the DROP direction is already covered, because `_voltro_staging_` starts
|
|
4085
|
+
* with `_voltro_` and `isFrameworkOwnedLiveTable` therefore says "never
|
|
4086
|
+
* drop it". Correct: a `replace` in flight on another replica is loading
|
|
4087
|
+
* into it right now.
|
|
4088
|
+
* - the CREATE direction is NOT, and that is the gap this closes. `--mode
|
|
4089
|
+
* replace` REGISTERS each staging table as a clone of its target for the
|
|
4090
|
+
* duration of the load — it has to, or a typed write cannot resolve the
|
|
4091
|
+
* column types and a `json()` column is written unencoded. A registered
|
|
4092
|
+
* table is a DECLARED table, and by the asymmetry above a declared
|
|
4093
|
+
* framework table diffs like any other: a plan computed while an import is
|
|
4094
|
+
* in flight proposes `create-table _voltro_staging_notes` and, once
|
|
4095
|
+
* applied, a table nothing will ever declare again is kept forever.
|
|
4096
|
+
*
|
|
4097
|
+
* So staging is excluded from the DECLARED side (in `declaredSnapshot`, which is
|
|
4098
|
+
* the one input both the plan and its fingerprint are built from) and left to
|
|
4099
|
+
* the existing prefix rule on the live side. Two different rules, two different
|
|
4100
|
+
* places, on purpose.
|
|
4101
|
+
*/
|
|
4102
|
+
export declare const isStagingTableName: (name: string) => boolean;
|
|
4103
|
+
|
|
3975
4104
|
/**
|
|
3976
4105
|
* Does this table emit change events at all?
|
|
3977
4106
|
*
|
|
@@ -4527,6 +4656,67 @@ export declare const numeric: (precision: number, scale?: number) => ColumnBuild
|
|
|
4527
4656
|
*/
|
|
4528
4657
|
export declare const observeDbOp: <T>(dialect: DialectId | string, op: DbOp, run: () => Promise<T>) => Promise<T>;
|
|
4529
4658
|
|
|
4659
|
+
/**
|
|
4660
|
+
* How many times this replica has already seen each digest inside the dedup
|
|
4661
|
+
* window. Bounded on BOTH axes — a per-change map with no ceiling is an
|
|
4662
|
+
* unbounded table in memory:
|
|
4663
|
+
* * by time — entries untouched for `windowMs` are pruned, and
|
|
4664
|
+
* * by size — `maxEntries` oldest-first, so a burst of unique changes can't
|
|
4665
|
+
* grow it without limit.
|
|
4666
|
+
*
|
|
4667
|
+
* `windowMs` MUST be longer than the lifetime of a claim row in the database.
|
|
4668
|
+
* That ordering is the invariant: if a claim outlived the counter that
|
|
4669
|
+
* produced it, the next sighting of that digest would restart at occurrence 0,
|
|
4670
|
+
* collide with the retained claim, and be dropped as a duplicate it is not.
|
|
4671
|
+
*
|
|
4672
|
+
* **`maxEntries` is the SAME invariant reached by size, and it is worth saying
|
|
4673
|
+
* plainly rather than reassuringly.** An evicted digest also restarts at 0, so
|
|
4674
|
+
* an eviction is only harmless while no claim for that digest is still retained
|
|
4675
|
+
* — which is not the same thing as "the position a freshly-booted replica is
|
|
4676
|
+
* in", because a fresh replica has no claims of its own to collide with. The
|
|
4677
|
+
* exposure is narrow (it needs more than `maxEntries` distinct digests inside
|
|
4678
|
+
* the claim TTL *and* a content-identical repeat of an evicted one), and it is
|
|
4679
|
+
* real. Size the map against the change rate the gated tables actually carry,
|
|
4680
|
+
* the way `plugin-cdc-out` sizes it against its handoff buffer.
|
|
4681
|
+
*/
|
|
4682
|
+
export declare class OccurrenceCounter {
|
|
4683
|
+
#private;
|
|
4684
|
+
constructor(opts: {
|
|
4685
|
+
readonly windowMs: number;
|
|
4686
|
+
readonly maxEntries: number;
|
|
4687
|
+
});
|
|
4688
|
+
get size(): number;
|
|
4689
|
+
/** Record a sighting and return its occurrence index (0 for the first). */
|
|
4690
|
+
next(digest: string, nowMs: number): number;
|
|
4691
|
+
/**
|
|
4692
|
+
* Align this replica with the fleet from the claims already recorded in the
|
|
4693
|
+
* database — the counters of a process that booted mid-window would
|
|
4694
|
+
* otherwise restart at 0 and re-key changes the incumbents have already
|
|
4695
|
+
* keyed. Call ONCE, before the first observed change is keyed.
|
|
4696
|
+
*/
|
|
4697
|
+
seedFromChangeKeys(changeKeys: Iterable<string>, nowMs: number): void;
|
|
4698
|
+
/** Drop entries whose last sighting is older than the window. */
|
|
4699
|
+
prune(nowMs: number): void;
|
|
4700
|
+
}
|
|
4701
|
+
|
|
4702
|
+
/**
|
|
4703
|
+
* The counter window that keeps {@link OccurrenceCounter}'s invariant true for
|
|
4704
|
+
* the claim TTL this deployment actually runs with.
|
|
4705
|
+
*
|
|
4706
|
+
* The invariant is stated on the class: the window MUST be longer than the
|
|
4707
|
+
* lifetime of a claim row, or a digest whose counter entry was pruned restarts
|
|
4708
|
+
* at occurrence 0, collides with the retained claim, and is dropped as a
|
|
4709
|
+
* duplicate it is not. Three call sites hard-coded two hours against a
|
|
4710
|
+
* one-hour default — correct until an operator raises the TTL, at which point
|
|
4711
|
+
* the invariant breaks SILENTLY and in the direction that loses effects.
|
|
4712
|
+
*
|
|
4713
|
+
* So it is derived, not written down: twice the TTL, from the same env var the
|
|
4714
|
+
* retention policy reads.
|
|
4715
|
+
*/
|
|
4716
|
+
export declare const occurrenceWindowMs: (env?: {
|
|
4717
|
+
readonly [key: string]: string | undefined;
|
|
4718
|
+
}) => number;
|
|
4719
|
+
|
|
4530
4720
|
/**
|
|
4531
4721
|
* What a decrypt failure does. `'throw'` (default, and the ONLY safe production
|
|
4532
4722
|
* behaviour) surfaces a typed `FieldDecryptionError`. `'null'` degrades the one
|
|
@@ -4658,6 +4848,12 @@ export declare const paginateBy: (descriptor: QueryDescriptor, column: string, c
|
|
|
4658
4848
|
*/
|
|
4659
4849
|
export declare const paginateById: (descriptor: QueryDescriptor, cursor: string | undefined, limit: number) => QueryDescriptor;
|
|
4660
4850
|
|
|
4851
|
+
/** Split a stored `changeKey` back into its two halves (`null` when malformed). */
|
|
4852
|
+
export declare const parseChangeKey: (changeKey: string) => {
|
|
4853
|
+
digest: string;
|
|
4854
|
+
occurrence: number;
|
|
4855
|
+
} | null;
|
|
4856
|
+
|
|
4661
4857
|
/** Test seam — how many registrations are outstanding. */
|
|
4662
4858
|
export declare const pendingAttributionCount: () => number;
|
|
4663
4859
|
|
|
@@ -6593,6 +6789,86 @@ export declare interface SqlDialect {
|
|
|
6593
6789
|
readonly retryFilter: (error: unknown) => RetryDecision;
|
|
6594
6790
|
}
|
|
6595
6791
|
|
|
6792
|
+
/**
|
|
6793
|
+
* How long a staged run's silence has to last before its scratch tables are
|
|
6794
|
+
* treated as abandoned.
|
|
6795
|
+
*
|
|
6796
|
+
* Generous on purpose. The beat is written every couple of seconds while rows
|
|
6797
|
+
* land, so this is not "how long a load may take" — it is how long a load may
|
|
6798
|
+
* be stalled, or its bookkeeping write failing, before a boot on another
|
|
6799
|
+
* replica decides nobody is coming back. The cost of being wrong here is that
|
|
6800
|
+
* an in-flight import's swap fails with a missing table and the operator
|
|
6801
|
+
* re-runs it; the target is untouched either way, which is what makes an
|
|
6802
|
+
* automatic sweep affordable at all.
|
|
6803
|
+
*
|
|
6804
|
+
* Declared as `dataTransfer.stagingStaleMinutes` in `app.config.ts`;
|
|
6805
|
+
* `VOLTRO_STAGING_STALE_MINUTES` overrides that in turn, on the same rule every
|
|
6806
|
+
* other tunable here follows — an operator acting on a running deployment
|
|
6807
|
+
* outranks what the project declared.
|
|
6808
|
+
*/
|
|
6809
|
+
export declare const STAGING_STALE_MINUTES = 30;
|
|
6810
|
+
|
|
6811
|
+
/**
|
|
6812
|
+
* The prefix a STAGING table carries: `_voltro_staging_<target>`.
|
|
6813
|
+
*
|
|
6814
|
+
* Defined in this leaf module, not in `@voltro/data-transfer` where the swap
|
|
6815
|
+
* lives, because the differ has to know it and `@voltro/database` cannot depend
|
|
6816
|
+
* on the package that imports it. One definition; `stagedSwap.ts` re-exports
|
|
6817
|
+
* this one rather than spelling the prefix a second time.
|
|
6818
|
+
*/
|
|
6819
|
+
export declare const STAGING_TABLE_PREFIX = "_voltro_staging_";
|
|
6820
|
+
|
|
6821
|
+
/**
|
|
6822
|
+
* What a boot says about them.
|
|
6823
|
+
*
|
|
6824
|
+
* A NOTE, never a refusal — the target of a staged run is untouched by
|
|
6825
|
+
* definition. But it is said out loud, because the alternative is what shipped:
|
|
6826
|
+
* a set of tables holding a whole bundle that only an introspection could find,
|
|
6827
|
+
* so nobody knew they existed until a disk filled up.
|
|
6828
|
+
*/
|
|
6829
|
+
export declare const stagingLeftoverReport: (leftovers: StagingLeftovers, now: number) => string | undefined;
|
|
6830
|
+
|
|
6831
|
+
/** One staged run's leftovers, and what a boot may do about them. */
|
|
6832
|
+
export declare interface StagingLeftovers {
|
|
6833
|
+
/** Every `kind: 'staging'` row, for the report. */
|
|
6834
|
+
readonly all: ReadonlyArray<InterruptedReplace>;
|
|
6835
|
+
/**
|
|
6836
|
+
* The rows whose tables a boot may DROP: the beat is stale AND the run was
|
|
6837
|
+
* not resumable.
|
|
6838
|
+
*
|
|
6839
|
+
* Both halves are load-bearing and neither is enough alone. A fresh beat is
|
|
6840
|
+
* an import in flight — on this pod or another replica — and dropping its
|
|
6841
|
+
* staging would destroy a load in progress. A resumable run's staging is a
|
|
6842
|
+
* RESUME POINT that is meant to outlive the process, so no amount of silence
|
|
6843
|
+
* makes it collectable.
|
|
6844
|
+
*/
|
|
6845
|
+
readonly droppable: ReadonlyArray<InterruptedReplace>;
|
|
6846
|
+
}
|
|
6847
|
+
|
|
6848
|
+
/**
|
|
6849
|
+
* Decide, from the marker rows alone, which staging sets a boot may collect.
|
|
6850
|
+
*
|
|
6851
|
+
* Pure, and separated from the boot so the DECISION can be tested without a
|
|
6852
|
+
* database — the rule is three lines and every one of them has a way to be
|
|
6853
|
+
* quietly wrong in the direction that destroys someone's work.
|
|
6854
|
+
*/
|
|
6855
|
+
export declare const stagingLeftovers: (rows: ReadonlyArray<InterruptedReplace>, now: number, staleAfterMs: number) => StagingLeftovers;
|
|
6856
|
+
|
|
6857
|
+
/** Split a `kind: 'staging'` row's comma-separated name list. */
|
|
6858
|
+
export declare const stagingNamesOf: (raw: unknown) => ReadonlyArray<string>;
|
|
6859
|
+
|
|
6860
|
+
/**
|
|
6861
|
+
* The threshold in ms: the env override, else what the app declared, else the
|
|
6862
|
+
* default above.
|
|
6863
|
+
*
|
|
6864
|
+
* A non-positive or unparseable value at EITHER level falls through to the next
|
|
6865
|
+
* one rather than failing. This runs on a boot path whose whole contract is that
|
|
6866
|
+
* scratch-table housekeeping cannot stop a boot, so a typo in a tunable must not
|
|
6867
|
+
* be the thing that does — and the fall-through lands on a threshold that is
|
|
6868
|
+
* generous in the safe direction.
|
|
6869
|
+
*/
|
|
6870
|
+
export declare const stagingStaleAfterMs: (env: Record<string, string | undefined>, declaredMinutes?: number) => number;
|
|
6871
|
+
|
|
6596
6872
|
/**
|
|
6597
6873
|
* Fill `row.id` from the table's declared scheme when it is absent.
|
|
6598
6874
|
*
|
|
@@ -7896,8 +8172,20 @@ export declare const _voltroDataTransfersTable: TableLike;
|
|
|
7896
8172
|
* (the `Idempotency-Key` header on REST routes). `scope` namespaces the key
|
|
7897
8173
|
* per tenant + method + path; the UNIQUE on (scope, key) is the atomic claim
|
|
7898
8174
|
* arbiter (INSERT/upsert-on-conflict). `response` holds the cached
|
|
7899
|
-
* `{ status, body }` for replay.
|
|
7900
|
-
*
|
|
8175
|
+
* `{ status, body }` for replay.
|
|
8176
|
+
*
|
|
8177
|
+
* Created for EVERY sql app (`when: 'always'` in the cli's registry), empty
|
|
8178
|
+
* unless `idempotency` is set in `app.config.ts`. This used to say "created only
|
|
8179
|
+
* when idempotency is set", which is what the config gates — not what the table
|
|
8180
|
+
* registry does.
|
|
8181
|
+
*
|
|
8182
|
+
* Two things clean it up, and only one of them existed for most of this table's
|
|
8183
|
+
* life. The LAZY TTL drops a stale row when the SAME key is claimed again — and
|
|
8184
|
+
* a key is used once by definition, so a row whose key never returns was never
|
|
8185
|
+
* read and never deleted. The retention SWEEP is the half that bounds it
|
|
8186
|
+
* (`retentionSweep.ts`), and its window is floored by the app's own dedup window:
|
|
8187
|
+
* a record dropped while still inside that window lets the duplicate request it
|
|
8188
|
+
* exists to stop execute a second time.
|
|
7901
8189
|
*/
|
|
7902
8190
|
export declare const _voltroIdempotencyTable: TableLike;
|
|
7903
8191
|
|
|
@@ -7950,6 +8238,43 @@ export declare const _voltroMssqlCdcOffsetsTable: TableLike;
|
|
|
7950
8238
|
*/
|
|
7951
8239
|
export declare const _voltroReplaceInProgressTable: TableLike;
|
|
7952
8240
|
|
|
8241
|
+
/**
|
|
8242
|
+
* `_voltro_replica_observations` — what each replica knows about itself, put
|
|
8243
|
+
* somewhere every replica can read it.
|
|
8244
|
+
*
|
|
8245
|
+
* ── Why the shared store and not a fan-out ────────────────────────────────
|
|
8246
|
+
*
|
|
8247
|
+
* The fleet-wide questions an operator asks — "how many subscriptions is each
|
|
8248
|
+
* pod holding", "which of my queries can resume anywhere" — are answers each
|
|
8249
|
+
* process holds privately. Asking the others at read time means inventing
|
|
8250
|
+
* request/response over a pub/sub bus: a correlation id, a reply channel, a
|
|
8251
|
+
* deadline to guess, and a partial-result protocol. And it puts the DIAGNOSTIC
|
|
8252
|
+
* on the failure path, so the moment you most need it is the moment it times
|
|
8253
|
+
* out.
|
|
8254
|
+
*
|
|
8255
|
+
* Writing instead inverts that. The transport is the database — the one thing
|
|
8256
|
+
* that must be up for anything to work — and a replica that dies goes STALE
|
|
8257
|
+
* rather than silent: staleness is a number, and a number can be reported.
|
|
8258
|
+
* Paired with `instanceMembership`'s roster, "old" becomes "missing", which is
|
|
8259
|
+
* the honest partial result the fan-out would have had to build by hand.
|
|
8260
|
+
*
|
|
8261
|
+
* `coordinationState.recentReplicaIds` already answers a fleet question this
|
|
8262
|
+
* way, by reading `_voltro_schedule_runs`. This generalises that one case.
|
|
8263
|
+
*
|
|
8264
|
+
* ── The shape, and what it must not become ────────────────────────────────
|
|
8265
|
+
*
|
|
8266
|
+
* One row per `(replicaId, kind)`, upserted. So the table is bounded by fleet
|
|
8267
|
+
* size times the number of kinds, not by time — there is no history here and
|
|
8268
|
+
* there must not be: a history would need a retention policy, and this is a
|
|
8269
|
+
* cache of the present, not a log of the past.
|
|
8270
|
+
*
|
|
8271
|
+
* `payload` is a JSON STRING and small on purpose. Anything big enough to
|
|
8272
|
+
* warrant paging belongs on the process that holds it, reachable per replica —
|
|
8273
|
+
* writing a full subscription list here every heartbeat would be write
|
|
8274
|
+
* amplification for state nobody is reading.
|
|
8275
|
+
*/
|
|
8276
|
+
export declare const _voltroReplicaObservationsTable: TableLike;
|
|
8277
|
+
|
|
7953
8278
|
/**
|
|
7954
8279
|
* `_voltro_seeds` — one row per registered seed. Written to by the
|
|
7955
8280
|
* seed runner as each seed's lifecycle changes. Unlike migrations,
|