@quereus/store 4.5.0 → 4.7.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.
Files changed (107) hide show
  1. package/README.md +34 -6
  2. package/dist/src/common/encoding.d.ts +4 -3
  3. package/dist/src/common/encoding.d.ts.map +1 -1
  4. package/dist/src/common/encoding.js +17 -13
  5. package/dist/src/common/encoding.js.map +1 -1
  6. package/dist/src/common/events.d.ts +31 -25
  7. package/dist/src/common/events.d.ts.map +1 -1
  8. package/dist/src/common/events.js +38 -39
  9. package/dist/src/common/events.js.map +1 -1
  10. package/dist/src/common/implicit-unique-index.d.ts +80 -0
  11. package/dist/src/common/implicit-unique-index.d.ts.map +1 -0
  12. package/dist/src/common/implicit-unique-index.js +148 -0
  13. package/dist/src/common/implicit-unique-index.js.map +1 -0
  14. package/dist/src/common/index.d.ts +7 -3
  15. package/dist/src/common/index.d.ts.map +1 -1
  16. package/dist/src/common/index.js +5 -2
  17. package/dist/src/common/index.js.map +1 -1
  18. package/dist/src/common/json-key.d.ts +30 -2
  19. package/dist/src/common/json-key.d.ts.map +1 -1
  20. package/dist/src/common/json-key.js +49 -2
  21. package/dist/src/common/json-key.js.map +1 -1
  22. package/dist/src/common/key-builder.d.ts +40 -20
  23. package/dist/src/common/key-builder.d.ts.map +1 -1
  24. package/dist/src/common/key-builder.js +31 -23
  25. package/dist/src/common/key-builder.js.map +1 -1
  26. package/dist/src/common/kv-store.d.ts +10 -0
  27. package/dist/src/common/kv-store.d.ts.map +1 -1
  28. package/dist/src/common/pk-key-resolution.d.ts +348 -0
  29. package/dist/src/common/pk-key-resolution.d.ts.map +1 -0
  30. package/dist/src/common/pk-key-resolution.js +481 -0
  31. package/dist/src/common/pk-key-resolution.js.map +1 -0
  32. package/dist/src/common/store-module-access-plan.d.ts +32 -0
  33. package/dist/src/common/store-module-access-plan.d.ts.map +1 -0
  34. package/dist/src/common/store-module-access-plan.js +428 -0
  35. package/dist/src/common/store-module-access-plan.js.map +1 -0
  36. package/dist/src/common/store-module-alter-column.d.ts +23 -0
  37. package/dist/src/common/store-module-alter-column.d.ts.map +1 -0
  38. package/dist/src/common/store-module-alter-column.js +389 -0
  39. package/dist/src/common/store-module-alter-column.js.map +1 -0
  40. package/dist/src/common/store-module-alter.d.ts +55 -0
  41. package/dist/src/common/store-module-alter.d.ts.map +1 -0
  42. package/dist/src/common/store-module-alter.js +504 -0
  43. package/dist/src/common/store-module-alter.js.map +1 -0
  44. package/dist/src/common/store-module-base.d.ts +268 -0
  45. package/dist/src/common/store-module-base.d.ts.map +1 -0
  46. package/dist/src/common/store-module-base.js +362 -0
  47. package/dist/src/common/store-module-base.js.map +1 -0
  48. package/dist/src/common/store-module-catalog.d.ts +269 -0
  49. package/dist/src/common/store-module-catalog.d.ts.map +1 -0
  50. package/dist/src/common/store-module-catalog.js +462 -0
  51. package/dist/src/common/store-module-catalog.js.map +1 -0
  52. package/dist/src/common/store-module-index-build.d.ts +88 -0
  53. package/dist/src/common/store-module-index-build.d.ts.map +1 -0
  54. package/dist/src/common/store-module-index-build.js +280 -0
  55. package/dist/src/common/store-module-index-build.js.map +1 -0
  56. package/dist/src/common/store-module-index.d.ts +116 -0
  57. package/dist/src/common/store-module-index.d.ts.map +1 -0
  58. package/dist/src/common/store-module-index.js +373 -0
  59. package/dist/src/common/store-module-index.js.map +1 -0
  60. package/dist/src/common/store-module-rename.d.ts +49 -0
  61. package/dist/src/common/store-module-rename.d.ts.map +1 -0
  62. package/dist/src/common/store-module-rename.js +268 -0
  63. package/dist/src/common/store-module-rename.js.map +1 -0
  64. package/dist/src/common/store-module-schema-rewrite.d.ts +57 -0
  65. package/dist/src/common/store-module-schema-rewrite.d.ts.map +1 -0
  66. package/dist/src/common/store-module-schema-rewrite.js +140 -0
  67. package/dist/src/common/store-module-schema-rewrite.js.map +1 -0
  68. package/dist/src/common/store-module-schema-sync.d.ts +232 -0
  69. package/dist/src/common/store-module-schema-sync.d.ts.map +1 -0
  70. package/dist/src/common/store-module-schema-sync.js +529 -0
  71. package/dist/src/common/store-module-schema-sync.js.map +1 -0
  72. package/dist/src/common/store-module.d.ts +43 -909
  73. package/dist/src/common/store-module.d.ts.map +1 -1
  74. package/dist/src/common/store-module.js +184 -3276
  75. package/dist/src/common/store-module.js.map +1 -1
  76. package/dist/src/common/store-table-base.d.ts +539 -0
  77. package/dist/src/common/store-table-base.d.ts.map +1 -0
  78. package/dist/src/common/store-table-base.js +900 -0
  79. package/dist/src/common/store-table-base.js.map +1 -0
  80. package/dist/src/common/store-table-constraints.d.ts +237 -0
  81. package/dist/src/common/store-table-constraints.d.ts.map +1 -0
  82. package/dist/src/common/store-table-constraints.js +624 -0
  83. package/dist/src/common/store-table-constraints.js.map +1 -0
  84. package/dist/src/common/store-table-scan.d.ts +452 -0
  85. package/dist/src/common/store-table-scan.d.ts.map +1 -0
  86. package/dist/src/common/store-table-scan.js +901 -0
  87. package/dist/src/common/store-table-scan.js.map +1 -0
  88. package/dist/src/common/store-table.d.ts +71 -1144
  89. package/dist/src/common/store-table.d.ts.map +1 -1
  90. package/dist/src/common/store-table.js +168 -2385
  91. package/dist/src/common/store-table.js.map +1 -1
  92. package/dist/src/testing/index.d.ts +12 -0
  93. package/dist/src/testing/index.d.ts.map +1 -0
  94. package/dist/src/testing/index.js +12 -0
  95. package/dist/src/testing/index.js.map +1 -0
  96. package/dist/src/testing/kv-assert.d.ts +19 -0
  97. package/dist/src/testing/kv-assert.d.ts.map +1 -0
  98. package/dist/src/testing/kv-assert.js +25 -0
  99. package/dist/src/testing/kv-assert.js.map +1 -0
  100. package/dist/src/testing/kv-conformance.d.ts.map +1 -1
  101. package/dist/src/testing/kv-conformance.js +1 -15
  102. package/dist/src/testing/kv-conformance.js.map +1 -1
  103. package/dist/src/testing/kv-provider-conformance.d.ts +54 -0
  104. package/dist/src/testing/kv-provider-conformance.d.ts.map +1 -0
  105. package/dist/src/testing/kv-provider-conformance.js +183 -0
  106. package/dist/src/testing/kv-provider-conformance.js.map +1 -0
  107. package/package.json +8 -8
@@ -0,0 +1,232 @@
1
+ /**
2
+ * Keeping the engine's live schema and the persisted catalog in agreement, in both
3
+ * directions: reading the catalog back at open (rehydration), lazily reconnecting a
4
+ * table the engine already knows about, and reacting to the engine's schema-change
5
+ * notifications (catalog-only tag swaps, view/materialized-view DDL upkeep, and the
6
+ * stale-materialized-view set that survives a restart).
7
+ *
8
+ * Third layer of the store-module chain:
9
+ * StoreModuleBase -> StoreModuleCatalog -> StoreModuleSchemaSync -> StoreModuleIndex
10
+ * -> StoreModuleAlterColumn -> StoreModuleAlter -> StoreModuleRename -> StoreModule
11
+ */
12
+ import type { Database } from '@quereus/quereus';
13
+ import { StoreTable } from './store-table.js';
14
+ import { StoreModuleCatalog } from './store-module-catalog.js';
15
+ /**
16
+ * Result of catalog rehydration.
17
+ *
18
+ * `views` / `materializedViews` are additive (existing consumers — e.g.
19
+ * `quoomb-web` — read only `.errors`). Errors from any phase land in `errors`.
20
+ */
21
+ export interface RehydrationResult {
22
+ tables: string[];
23
+ indexes: string[];
24
+ views: string[];
25
+ materializedViews: string[];
26
+ errors: RehydrationError[];
27
+ }
28
+ /**
29
+ * An error encountered while rehydrating a single DDL entry.
30
+ */
31
+ export interface RehydrationError {
32
+ ddl: string;
33
+ error: Error;
34
+ }
35
+ export declare abstract class StoreModuleSchemaSync extends StoreModuleCatalog {
36
+ /**
37
+ * Returns the connected StoreTable for `schemaName.tableName`, lazily
38
+ * reconnecting from the engine's schema registry when absent.
39
+ *
40
+ * `renameTable` evicts the old key from `this.tables` and expects the next
41
+ * `connect()` to repopulate under the new name, but `apply schema` can run
42
+ * follow-up DDL (ALTER TABLE, CREATE/DROP INDEX) against the new name
43
+ * without an intervening connect. Mirror connect()'s schemaManager lookup
44
+ * so that DDL finds the moved table. Safe for the index paths because
45
+ * SchemaManager calls the module BEFORE mutating the registered table
46
+ * schema, so the reconnected cache matches what a connected instance holds.
47
+ */
48
+ protected getOrReconnectTable(db: Database, schemaName: string, tableName: string): StoreTable | undefined;
49
+ /**
50
+ * Rehydrate the persisted catalog into the in-memory schema manager, in
51
+ * dependency order.
52
+ *
53
+ * Re-asserts the engine schema-change subscription up front — normally already
54
+ * established by `StoreModule.onRegister` at `Database.registerModule`, but this
55
+ * entry point is also reachable with a `db` this module was never registered on, and
56
+ * phase 3 cannot observe MV staleness without it. Then loads every catalog entry
57
+ * once, classifies each by its key prefix into {tables, views, materialized views},
58
+ * and imports in three phases:
59
+ *
60
+ * 1. **Tables** — `importCatalog` (connect to existing storage; refresh connected
61
+ * `StoreTable` schemas).
62
+ * 2. **Views** — `importCatalog` (engine silent-register; body validation deferred
63
+ * to query time, so order among views — and view-over-MV / view-over-view —
64
+ * does not matter, and no schema-change event fires → phase 2 writes nothing).
65
+ * 3. **Materialized views** — `importCatalog` per entry (engine re-materialize:
66
+ * rebuilds the memory backing from current source data, re-registers row-time
67
+ * maintenance, re-runs the eligibility gate — the same core the create emitter
68
+ * uses, but silent: no `materialized_view_added` fires, so phase 3 writes
69
+ * nothing back to the catalog). A store-hosted backing that phase 1 already
70
+ * rehydrated is **adopted without the refill** when the engine's adopt gates
71
+ * pass — see the clean-shutdown marker below.
72
+ *
73
+ * **Clean-shutdown marker.** Before anything loads, the reserved
74
+ * `\x00meta\x00clean_shutdown` catalog entry (written by `StoreModule.closeAll` after
75
+ * every batch flushed) is consumed: parsed into `{ trusted, staleAtClose }`, then
76
+ * **deleted immediately** — single-use, so a crash later in this session (or a
77
+ * second rehydrate without an intervening clean close) is detected at the next open
78
+ * and every adopt falls back to the always-correct drop+refill, self-healing any
79
+ * crash-window divergence (coordinated commit is not 2PC across stores). The marker
80
+ * payload is the JSON set of MVs that were **stale-at-close** (row-time maintenance
81
+ * detached, so the durable backing may be behind); phase 3 withholds trust per-entry
82
+ * for those — `trustBackings: trusted && !staleAtClose.has(name)` — so a
83
+ * stale-at-close MV refills (recomputing content and re-arming maintenance) while
84
+ * every live-at-close MV keeps the fast path. The one shared `adoptedBackings` set
85
+ * composes across fixpoint rounds (an upstream MV adopted in round 1 enables its
86
+ * dependent in round 2, while a refilled — or stale-at-close — upstream is never
87
+ * added to it, forcing dependents to refill).
88
+ *
89
+ * **MV-over-MV ordering** is handled by a fixpoint retry rather than a static topo
90
+ * sort: an MV's resolved `sourceTables` are computed at import time, not serialized
91
+ * in the DDL, so they are unavailable before import. Each round passes the names of
92
+ * every OTHER still-pending MV entry as `pendingDerivations`; the engine defers any
93
+ * entry whose body reads one (its source already pre-exists as a phase-1 plain
94
+ * table, so the body would otherwise plan against content the upstream's own import
95
+ * may be about to replace). The loop repeats while any MV makes progress — robust to
96
+ * arbitrary nesting depth. A genuinely unbuildable MV — a missing (e.g. memory)
97
+ * source, or an unresolvable cycle — makes no progress in a round and is recorded in
98
+ * `errors`.
99
+ *
100
+ * Per-entry errors in any phase are collected (not fatal) so one bad object does not
101
+ * abort the rest.
102
+ *
103
+ * Call after `db.registerModule()` (and `db.setDefaultVtabName()` if DDL may lack a
104
+ * USING clause).
105
+ */
106
+ rehydrateCatalog(db: Database): Promise<RehydrationResult>;
107
+ /**
108
+ * Subscribe (once) to the engine's `SchemaChangeNotifier`, the channel every
109
+ * event-driven catalog write rides: `table_added` (a new store table's entry),
110
+ * catalog-only mutations that bypass `module.alterTable` — notably `ALTER … SET TAGS`
111
+ * and the programmatic `setTableTags`/`setColumnTags`/`setConstraintTags` — and the
112
+ * whole view / materialized-view lifecycle.
113
+ *
114
+ * Normally called from `StoreModule.onRegister` at `Database.registerModule`, before
115
+ * any statement runs; also called from `rehydrateCatalog` and the `create`/`connect`/
116
+ * `alterTable` hooks, which are reachable with a `db` this module was not registered
117
+ * on. Idempotent.
118
+ *
119
+ * One `StoreModule` instance is assumed to serve one `Database`. A later hook
120
+ * carrying a *different* `db` keeps the existing subscription (multi-database
121
+ * sharing of a single module instance is out of scope) and logs.
122
+ */
123
+ protected ensureSchemaSubscription(db: Database): void;
124
+ /**
125
+ * Engine schema-change listener. Persists the catalog incrementally for the events
126
+ * that bypass `module.alterTable` / `module.destroy`:
127
+ *
128
+ * - `table_added` — a table the engine has just fully validated and registered. This is
129
+ * what persists a store table nobody ever reads or writes (the lazy
130
+ * first-storage-access save never runs for one).
131
+ * - `table_modified` — every catalog-only tag swap (and the redundant follow-up a
132
+ * structural ALTER fires). Keeps a connected `StoreTable`'s cached schema consistent
133
+ * (SET TAGS does not call `updateSchema`) then read-compare-writes the table bundle.
134
+ * - `view_added` / `view_modified` / `view_removed` — plain `CREATE`/`ALTER … SET TAGS`/
135
+ * `DROP VIEW` (the engine fires these from the runtime emitters).
136
+ * - `materialized_view_added` / `_modified` / `_refreshed` / `_removed` — MV lifecycle.
137
+ * Like `table_modified`, the `_added`/`_modified`/`_refreshed` arms also synchronously
138
+ * refresh the connected `StoreTable`'s cached schema so a tag change (e.g.
139
+ * `quereus.sync.replicate`) takes effect immediately without reopen.
140
+ *
141
+ * Unlike the table path there is **no** catalog-absent self-filter for view/MV
142
+ * add/remove: one `StoreModule` instance serves one `Database`, so that database's
143
+ * views/MVs belong in its catalog unconditionally. A MEMORY-hosted maintained table
144
+ * fires `table_added`/`table_removed`/`table_modified` like any table; those stay
145
+ * ignored (`table_added` fails the ownership filter, `table_removed` falls through,
146
+ * its `table_modified` is catalog-absent → skipped), so only the MV entry persists for
147
+ * it. A STORE-hosted maintained table additionally persists its own table bundle
148
+ * through the ordinary store-table machinery (which phase-1 rehydrate connects for the
149
+ * adopt fast path).
150
+ *
151
+ * Synchronous by contract (`notifyChange` does not await listeners); every async write
152
+ * rides `persistQueue`, drained by `closeAll`/`whenCatalogPersisted`.
153
+ *
154
+ * After dispatching the event's own catalog persistence, the listener recomputes the
155
+ * durable stale-MV set and compare-writes it (see {@link persistStaleMvSetIfChanged}).
156
+ * Because the engine's MV manager subscribes to the same notifier in the `Database`
157
+ * constructor — before this lazy subscription — its listener runs first, so the
158
+ * `derivation.stale` flags are already current when this recompute reads them.
159
+ */
160
+ private onEngineSchemaChange;
161
+ /**
162
+ * Dispatch a single engine schema-change event to its catalog-persistence arm.
163
+ *
164
+ * The `table_added` arm is where a store table's catalog entry is written. It fires
165
+ * from `SchemaManager.createTable` only AFTER the engine has fully validated the table
166
+ * (notably `validateForeignKeyCollations`, which runs after `module.create` returns and
167
+ * throws WITHOUT calling `destroy`) and registered it — so, unlike an eager write
168
+ * inside `StoreModule.create`, it can never leave an entry behind for a table the user
169
+ * never got, which would reopen as a phantom table.
170
+ *
171
+ * Two things distinguish this arm from the view/MV arms above it:
172
+ *
173
+ * - **It self-filters on ownership.** Every table the engine registers is announced,
174
+ * including memory tables and other modules'. `ownsTableCatalogEntry` is the test for
175
+ * "would this module write that table's entry" (and already handles the isolation
176
+ * wrapper, whose `vtabModule` is the wrapper exposing us as `underlying`). The
177
+ * view/MV arms need no such filter — views and MVs are not owned by a module.
178
+ * - **It compare-writes rather than blind-puts.** `table_added` is re-delivered during
179
+ * rehydration for a store-hosted materialized-view backing
180
+ * (`SchemaManager.createBackingTable`), where the entry phase 1 already wrote is
181
+ * current; comparing keeps that a no-op so a second consecutive reopen still yields
182
+ * identical catalog bytes. (Plain table import during rehydration is silent — no
183
+ * `table_added` fires at all.)
184
+ *
185
+ * A failure on this path is logged and swallowed by `enqueuePersist`, not raised on the
186
+ * statement — identical to how `create view` / `create materialized view` already
187
+ * persist. That is why `StoreTableBase.initializeStore` keeps its first-storage-access
188
+ * catalog write as a compare-write BACKSTOP: it is the only site where a table whose
189
+ * DDL text cannot be encoded at all (a lone surrogate in a quoted column name, a
190
+ * DEFAULT string literal, a CHECK constant) still raises on a statement.
191
+ */
192
+ private dispatchSchemaChange;
193
+ /**
194
+ * Shared MV add/modify/refresh handling. Narrow defensively — a derivation-less
195
+ * payload would be an engine bug, so skip. Otherwise, mirror `table_modified`:
196
+ * synchronously refresh a connected `StoreTable`'s cached schema (so a tag change
197
+ * such as `quereus.sync.replicate` takes effect immediately without reopen) before
198
+ * enqueuing the catalog DDL persist.
199
+ */
200
+ private refreshConnectedMaterializedView;
201
+ /**
202
+ * The qualified lowercased `schema.mv` names of every maintained table currently
203
+ * marked `derivation.stale` — an MV whose row-time maintenance detached mid-session
204
+ * (a body-relevant `table_modified`/`table_removed` on a source, or a cascade
205
+ * backing-invalidation), so its durable backing may be behind. The single source of
206
+ * truth for both the clean-shutdown marker payload and the durable stale-MV set.
207
+ *
208
+ * No subscribed db ⇒ the empty set. Since `StoreModule.onRegister` subscribes at
209
+ * `Database.registerModule`, that now means a module never registered on any
210
+ * `Database` (constructed and closed directly) — which by construction observed no
211
+ * schema at all, so it detached no persisted MV's maintenance. Memory-backed MVs that appear here
212
+ * are harmless — their catalog entries always refill (no phase-1 pre-existing
213
+ * backing), so withholding trust from them is a no-op.
214
+ */
215
+ protected computeStaleMvSet(): string[];
216
+ /**
217
+ * Recompute the durable stale-MV set and, only when it differs from the last value
218
+ * enqueued this subscription (`StoreModuleBase.lastPersistedStaleMvs`), enqueue a `sync: true`
219
+ * point-write of it onto `StoreModuleBase.persistQueue`. The compare-skip keeps an unrelated
220
+ * `table_modified` (a tag swap that changes no MV's staleness) from costing an fsync.
221
+ *
222
+ * The recompute reads `derivation.stale` synchronously (the flags are current at
223
+ * listener-dispatch time); the field is updated synchronously at enqueue time, and
224
+ * `persistQueue` serializes the writes in order, so the field always names the last
225
+ * enqueued value. Riding `persistQueue` (rather than a bare `put`) also serializes
226
+ * this `sync` behind the triggering event's own source-DDL compare-write, so the
227
+ * stale-set becomes durable no-later-than the source DDL that caused the staleness —
228
+ * the ordering the adopt soundness argument depends on.
229
+ */
230
+ private persistStaleMvSetIfChanged;
231
+ }
232
+ //# sourceMappingURL=store-module-schema-sync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-module-schema-sync.d.ts","sourceRoot":"","sources":["../../../src/common/store-module-schema-sync.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAA6D,MAAM,kBAAkB,CAAC;AAE5G,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAE/D;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IACjC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,MAAM,EAAE,gBAAgB,EAAE,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,KAAK,CAAC;CACb;AAED,8BAAsB,qBAAsB,SAAQ,kBAAkB;IACrE;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,mBAAmB,CAAC,EAAE,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;IAoB1G;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAwDG;IACG,gBAAgB,CAAC,EAAE,EAAE,QAAQ,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAuLhE;;;;;;;;;;;;;;;OAeG;IACH,SAAS,CAAC,wBAAwB,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI;IAkBtD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAmCG;IACH,OAAO,CAAC,oBAAoB,CAU1B;IAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8BG;IACH,OAAO,CAAC,oBAAoB;IA0E5B;;;;;;OAMG;IACH,OAAO,CAAC,gCAAgC;IAQxC;;;;;;;;;;;;;OAaG;IACH,SAAS,CAAC,iBAAiB,IAAI,MAAM,EAAE;IAQvC;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,0BAA0B;CAWlC"}