@quereus/sync 3.3.0 → 4.1.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/README.md +17 -7
- package/dist/src/clock/hlc.d.ts +43 -3
- package/dist/src/clock/hlc.d.ts.map +1 -1
- package/dist/src/clock/hlc.js +60 -10
- package/dist/src/clock/hlc.js.map +1 -1
- package/dist/src/create-sync-module.d.ts +46 -9
- package/dist/src/create-sync-module.d.ts.map +1 -1
- package/dist/src/create-sync-module.js +11 -8
- package/dist/src/create-sync-module.js.map +1 -1
- package/dist/src/index.d.ts +10 -8
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +8 -5
- package/dist/src/index.js.map +1 -1
- package/dist/src/metadata/basis-lifecycle.d.ts +180 -0
- package/dist/src/metadata/basis-lifecycle.d.ts.map +1 -0
- package/dist/src/metadata/basis-lifecycle.js +211 -0
- package/dist/src/metadata/basis-lifecycle.js.map +1 -0
- package/dist/src/metadata/change-log.d.ts +1 -1
- package/dist/src/metadata/change-log.d.ts.map +1 -1
- package/dist/src/metadata/change-log.js +6 -6
- package/dist/src/metadata/change-log.js.map +1 -1
- package/dist/src/metadata/column-version.d.ts +14 -5
- package/dist/src/metadata/column-version.d.ts.map +1 -1
- package/dist/src/metadata/column-version.js +22 -12
- package/dist/src/metadata/column-version.js.map +1 -1
- package/dist/src/metadata/index.d.ts +2 -0
- package/dist/src/metadata/index.d.ts.map +1 -1
- package/dist/src/metadata/index.js +2 -0
- package/dist/src/metadata/index.js.map +1 -1
- package/dist/src/metadata/keys.d.ts +52 -2
- package/dist/src/metadata/keys.d.ts.map +1 -1
- package/dist/src/metadata/keys.js +99 -14
- package/dist/src/metadata/keys.js.map +1 -1
- package/dist/src/metadata/peer-state.d.ts +1 -1
- package/dist/src/metadata/peer-state.js +5 -5
- package/dist/src/metadata/quarantine.d.ts +90 -0
- package/dist/src/metadata/quarantine.d.ts.map +1 -0
- package/dist/src/metadata/quarantine.js +167 -0
- package/dist/src/metadata/quarantine.js.map +1 -0
- package/dist/src/metadata/schema-migration.d.ts +1 -1
- package/dist/src/metadata/schema-migration.js +7 -7
- package/dist/src/metadata/schema-version.d.ts +1 -1
- package/dist/src/metadata/schema-version.js +7 -7
- package/dist/src/metadata/tombstones.d.ts +26 -9
- package/dist/src/metadata/tombstones.d.ts.map +1 -1
- package/dist/src/metadata/tombstones.js +42 -19
- package/dist/src/metadata/tombstones.js.map +1 -1
- package/dist/src/sql/basis-lifecycle-tvf.d.ts +34 -0
- package/dist/src/sql/basis-lifecycle-tvf.d.ts.map +1 -0
- package/dist/src/sql/basis-lifecycle-tvf.js +101 -0
- package/dist/src/sql/basis-lifecycle-tvf.js.map +1 -0
- package/dist/src/sync/admission.d.ts +81 -0
- package/dist/src/sync/admission.d.ts.map +1 -0
- package/dist/src/sync/admission.js +94 -0
- package/dist/src/sync/admission.js.map +1 -0
- package/dist/src/sync/change-applicator.d.ts +65 -2
- package/dist/src/sync/change-applicator.d.ts.map +1 -1
- package/dist/src/sync/change-applicator.js +493 -68
- package/dist/src/sync/change-applicator.js.map +1 -1
- package/dist/src/sync/change-grouping.d.ts +43 -0
- package/dist/src/sync/change-grouping.d.ts.map +1 -0
- package/dist/src/sync/change-grouping.js +103 -0
- package/dist/src/sync/change-grouping.js.map +1 -0
- package/dist/src/sync/events.d.ts +159 -1
- package/dist/src/sync/events.d.ts.map +1 -1
- package/dist/src/sync/events.js +50 -0
- package/dist/src/sync/events.js.map +1 -1
- package/dist/src/sync/manager.d.ts +87 -0
- package/dist/src/sync/manager.d.ts.map +1 -1
- package/dist/src/sync/protocol.d.ts +174 -11
- package/dist/src/sync/protocol.d.ts.map +1 -1
- package/dist/src/sync/protocol.js +4 -1
- package/dist/src/sync/protocol.js.map +1 -1
- package/dist/src/sync/snapshot-stream.d.ts.map +1 -1
- package/dist/src/sync/snapshot-stream.js +89 -19
- package/dist/src/sync/snapshot-stream.js.map +1 -1
- package/dist/src/sync/snapshot.d.ts.map +1 -1
- package/dist/src/sync/snapshot.js +63 -44
- package/dist/src/sync/snapshot.js.map +1 -1
- package/dist/src/sync/store-adapter.d.ts +117 -15
- package/dist/src/sync/store-adapter.d.ts.map +1 -1
- package/dist/src/sync/store-adapter.js +247 -91
- package/dist/src/sync/store-adapter.js.map +1 -1
- package/dist/src/sync/sync-context.d.ts +44 -1
- package/dist/src/sync/sync-context.d.ts.map +1 -1
- package/dist/src/sync/sync-context.js +26 -0
- package/dist/src/sync/sync-context.js.map +1 -1
- package/dist/src/sync/sync-manager-impl.d.ts +247 -13
- package/dist/src/sync/sync-manager-impl.d.ts.map +1 -1
- package/dist/src/sync/sync-manager-impl.js +788 -193
- package/dist/src/sync/sync-manager-impl.js.map +1 -1
- package/package.json +9 -9
|
@@ -1,11 +1,104 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Store adapter for applying remote sync changes.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Implements the ApplyToStoreCallback interface over the store module's
|
|
5
|
+
* external row-write entry point (`StoreTable.applyExternalRowChanges` —
|
|
6
|
+
* table-owned key encoding, secondary-index and stats maintenance) plus the
|
|
7
|
+
* engine's batch ingestion seam (`Database.ingestExternalRowChanges` — change
|
|
8
|
+
* capture for `Database.watch` + global assertions, materialized-view
|
|
9
|
+
* maintenance, opt-in parent-side FK actions).
|
|
10
|
+
*
|
|
11
|
+
* Per `applyToStore(dataChanges, schemaChanges, options)` invocation:
|
|
12
|
+
* 1. Schema changes execute first via `db.exec` (DDL before DML), with the
|
|
13
|
+
* resulting module schema events pre-marked remote.
|
|
14
|
+
* 2. Data changes are grouped per table, then per row; each row group
|
|
15
|
+
* collapses to ONE `ExternalRowOp` (a delete in the group wins over
|
|
16
|
+
* column updates; column updates merge onto the pre-read existing row,
|
|
17
|
+
* or onto a PK+nulls partial row when absent — UPSERT semantics).
|
|
18
|
+
* 3. `StoreTable.applyExternalRowChanges(ops)` applies the table's ops to
|
|
19
|
+
* committed storage and returns the EFFECTIVE changes (no-ops — absent
|
|
20
|
+
* delete, value-identical upsert — are suppressed: no storage write, no
|
|
21
|
+
* module event, no seam report).
|
|
22
|
+
* 4. Module data events are emitted from the effective changes with
|
|
23
|
+
* `remote: true` (so the SyncManager never re-records inbound changes),
|
|
24
|
+
* carrying accurate `oldRow` before-images and derived `changedColumns`.
|
|
25
|
+
* 5. After all tables' storage writes, ONE seam call reports the
|
|
26
|
+
* accumulated effective changes: `db.ingestExternalRowChanges(batch,
|
|
27
|
+
* { applyForeignKeyActions, assertionFailureMode: 'report' })` — capture +
|
|
28
|
+
* MV facets default on, commit-time global assertions in REPORT mode (a
|
|
29
|
+
* violation is collected into the result and the batch still commits).
|
|
30
|
+
* Changes recorded in `result.errors` are excluded from the batch.
|
|
31
|
+
*
|
|
32
|
+
* Snapshot bootstrap (per-call, not sticky): a known-complete wholesale load
|
|
33
|
+
* defers MV maintenance and watch capture for the whole transfer and converges
|
|
34
|
+
* once at the end.
|
|
35
|
+
* - `bootstrap` flush: steps 1–4 run unchanged (storage rows applied, remote
|
|
36
|
+
* module events emitted) but step 5 — the seam call — is SKIPPED. MV
|
|
37
|
+
* maintenance + capture are deferred to the finalize and FK actions are off
|
|
38
|
+
* for a wholesale load, so the only seam facet skipping the call drops
|
|
39
|
+
* outright is commit-time GLOBAL ASSERTION evaluation over the bootstrapped
|
|
40
|
+
* rows — deliberate under the seam's trust-the-origin contract. The
|
|
41
|
+
* incremental path enforces global assertions because it **merges** deltas
|
|
42
|
+
* from possibly many origins into the receiver's existing state, and a
|
|
43
|
+
* cross-origin merge can produce a global-invariant violation no single
|
|
44
|
+
* origin ever saw. Bootstrap does something different: it installs **one**
|
|
45
|
+
* origin's already-converged state **wholesale (replace, not merge)** — no
|
|
46
|
+
* merge means no merge-introduced violation, so a complete snapshot already
|
|
47
|
+
* satisfied the origin's assertions and re-checking is redundant. The
|
|
48
|
+
* `bootstrapFinalize` therefore does NOT evaluate any global assertion —
|
|
49
|
+
* not even a no-dependency one. This uniform skip is consistent with the
|
|
50
|
+
* seam's general trust-the-origin posture for every other constraint type
|
|
51
|
+
* (see `docs/materialized-views.md` § Trust boundary). MV-backed assertions
|
|
52
|
+
* would see the MV only after `refreshAllMaterializedViews()`; under
|
|
53
|
+
* trust-the-origin they are not evaluated at finalize at all, so MV-refresh
|
|
54
|
+
* ordering is moot for assertions. Residual risk (a corrupt/hostile snapshot
|
|
55
|
+
* installs invariant-violating data) is already unguarded for every other
|
|
56
|
+
* constraint type; a one-off assertion sweep would be inconsistent
|
|
57
|
+
* defense-in-depth — a separate integrity layer is the right fix if origins
|
|
58
|
+
* are ever distrusted. Per-flush evaluation also could not serve bootstrap
|
|
59
|
+
* correctly: it could spuriously fail a valid snapshot whose cross-table
|
|
60
|
+
* assertion sees children before parents. The remaining seam work for a
|
|
61
|
+
* wholesale load is otherwise deferred, so skipping the call also removes
|
|
62
|
+
* the per-flush transaction/savepoint and the per-flush full-rebuild.
|
|
63
|
+
* - `bootstrapFinalize` call (empty data/schema): converges every MV in
|
|
64
|
+
* dependency order via `db.refreshAllMaterializedViews()`, then fires a
|
|
65
|
+
* coarse `db.notifyExternalChange` per bootstrapped base table and per
|
|
66
|
+
* refreshed MV — one whole-table watch invalidation instead of per-row
|
|
67
|
+
* capture. The caller issues it before clearing the snapshot checkpoint, so
|
|
68
|
+
* a finalize throw leaves the checkpoint in place and the transfer retries.
|
|
69
|
+
*
|
|
70
|
+
* Inbound assertion violations are DETECT-AND-NOTIFY, not throw. Under the
|
|
71
|
+
* seam's trust-the-origin contract the merged data must land regardless, so a
|
|
72
|
+
* **local** commit-time global assertion can only usefully *notify*: the seam
|
|
73
|
+
* runs in report mode, so a violation is collected and RETURNED in
|
|
74
|
+
* `result.assertionViolations` while the batch commits — the derived effects
|
|
75
|
+
* (MV backing deltas, capture entries) for the violating row land on the FIRST
|
|
76
|
+
* attempt, so an incremental MV / `Database.watch` subscriber stays consistent
|
|
77
|
+
* with the base table and there is no divergence and no retry. The consumer
|
|
78
|
+
* (admission.ts) surfaces each returned violation to the host as an
|
|
79
|
+
* `onAssertionViolation` event; the host decides policy.
|
|
80
|
+
*
|
|
81
|
+
* A genuine per-change STORAGE failure is different and still aborts: the
|
|
82
|
+
* adapter collects it in `result.errors` (it keeps applying other tables), and
|
|
83
|
+
* the consumer treats any non-empty `errors` like a whole-batch throw — emit
|
|
84
|
+
* `status:'error'`, leave CRDT metadata uncommitted, re-resolve next sync.
|
|
85
|
+
* Re-application is idempotent (value-identical upserts suppress). "Orthogonal"
|
|
86
|
+
* (above) means the two outcomes are independent facts, NOT that a co-occurring
|
|
87
|
+
* violation is dropped: when ONE batch carries both a per-change error and a
|
|
88
|
+
* reported violation, the violation's row already committed in report mode, so
|
|
89
|
+
* the consumer surfaces the `onAssertionViolation` event BEFORE the per-change
|
|
90
|
+
* abort throws (see admission.ts `applyDataToStore`). The abort still blocks the
|
|
91
|
+
* metadata commit and the batch still re-resolves.
|
|
92
|
+
*
|
|
93
|
+
* Constraints (see docs/materialized-views.md § External row-change
|
|
94
|
+
* ingestion): the callback is host-driven — never invoke it from within
|
|
95
|
+
* statement execution or vtab callbacks (exec-mutex deadlock), and hosts
|
|
96
|
+
* should not drive it while holding an open explicit transaction on `db`
|
|
97
|
+
* (the seam would join that transaction, so a later rollback diverges
|
|
98
|
+
* MV/capture state from the already-committed storage rows; recoverable via
|
|
99
|
+
* MV refresh).
|
|
7
100
|
*/
|
|
8
|
-
import {
|
|
101
|
+
import { compareSqlValues } from '@quereus/quereus';
|
|
9
102
|
import { toError } from './sync-context.js';
|
|
10
103
|
/**
|
|
11
104
|
* Creates an ApplyToStoreCallback for applying remote sync changes.
|
|
@@ -14,13 +107,20 @@ import { toError } from './sync-context.js';
|
|
|
14
107
|
* - UPSERT semantics for column changes (insert if row doesn't exist, update if it does)
|
|
15
108
|
* - Row deletions by primary key
|
|
16
109
|
* - DDL execution for schema changes
|
|
110
|
+
* - Post-apply reporting through the engine's ingestion seam (MV maintenance,
|
|
111
|
+
* `Database.watch` capture, opt-in FK actions)
|
|
17
112
|
*
|
|
18
113
|
* All data change events are emitted with `remote: true` to prevent
|
|
19
114
|
* the SyncManager from re-recording CRDT metadata.
|
|
20
115
|
*/
|
|
21
116
|
export function createStoreAdapter(options) {
|
|
22
|
-
const { db,
|
|
117
|
+
const { db, storeModule, events, applyForeignKeyActions = false } = options;
|
|
23
118
|
return async (dataChanges, schemaChanges, applyOptions) => {
|
|
119
|
+
// Bootstrap finalize carries no data/schema changes: converge the MVs whose
|
|
120
|
+
// per-flush maintenance was deferred, then coarse-notify the watchers.
|
|
121
|
+
if (applyOptions.bootstrapFinalize) {
|
|
122
|
+
return finalizeBootstrap(db, applyOptions);
|
|
123
|
+
}
|
|
24
124
|
const result = {
|
|
25
125
|
dataChangesApplied: 0,
|
|
26
126
|
schemaChangesApplied: 0,
|
|
@@ -39,23 +139,43 @@ export function createStoreAdapter(options) {
|
|
|
39
139
|
});
|
|
40
140
|
}
|
|
41
141
|
}
|
|
42
|
-
//
|
|
43
|
-
//
|
|
142
|
+
// Effective changes accumulated across all tables, in apply order,
|
|
143
|
+
// for the single end-of-invocation seam call. The order here is
|
|
144
|
+
// table-grouped (first-appearance opSeq); it is NOT a dependency
|
|
145
|
+
// order and the FK-actions facet does not require one — both FK
|
|
146
|
+
// helpers re-read post-write storage applied above.
|
|
147
|
+
const seamBatch = [];
|
|
148
|
+
// Apply data changes per table; the resolved StoreTable owns key
|
|
149
|
+
// encoding, store resolution, and secondary-index maintenance.
|
|
44
150
|
const changesByTable = groupChangesByTable(dataChanges);
|
|
45
|
-
// Apply data changes per table
|
|
46
151
|
for (const [tableKey, tableChanges] of changesByTable) {
|
|
47
152
|
const [schemaName, tableName] = tableKey.split('.');
|
|
48
153
|
try {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
154
|
+
const table = storeModule.getTableForExternalWrite(db, schemaName, tableName);
|
|
155
|
+
if (!table) {
|
|
156
|
+
throw new Error(`Table not found for external write: ${schemaName}.${tableName}`);
|
|
157
|
+
}
|
|
158
|
+
// One ExternalRowOp per row group: multiple same-row changes collapse
|
|
159
|
+
// to a single effective op, so the seam's same-row before-image
|
|
160
|
+
// chaining rule is satisfied trivially (oldRow = true pre-batch image).
|
|
161
|
+
const ops = [];
|
|
162
|
+
for (const rowChanges of groupChangesByRow(tableChanges).values()) {
|
|
163
|
+
ops.push(await buildRowOp(table, rowChanges));
|
|
56
164
|
}
|
|
165
|
+
const effective = await table.applyExternalRowChanges(ops);
|
|
166
|
+
emitEffectiveChanges(events, table.getSchema(), effective);
|
|
167
|
+
// On a bootstrap flush the seam is skipped (deferred to finalize), so
|
|
168
|
+
// there is no batch to accumulate — don't build one.
|
|
169
|
+
if (!applyOptions.bootstrap) {
|
|
170
|
+
for (const change of effective) {
|
|
171
|
+
seamBatch.push({ schemaName, tableName, change });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
result.dataChangesApplied += tableChanges.length;
|
|
57
175
|
}
|
|
58
176
|
catch (error) {
|
|
177
|
+
// Errored changes are excluded from the seam batch; earlier tables'
|
|
178
|
+
// applied changes still report below.
|
|
59
179
|
for (const change of tableChanges) {
|
|
60
180
|
result.errors.push({
|
|
61
181
|
change,
|
|
@@ -64,9 +184,54 @@ export function createStoreAdapter(options) {
|
|
|
64
184
|
}
|
|
65
185
|
}
|
|
66
186
|
}
|
|
187
|
+
// One seam call per invocation, after all storage writes. Driven in
|
|
188
|
+
// assertion REPORT mode: a commit-time global-assertion violation over the
|
|
189
|
+
// inbound batch is COLLECTED and returned (not thrown), and the batch still
|
|
190
|
+
// commits — derived effects (MV deltas, watch capture) for the violating row
|
|
191
|
+
// land on this FIRST attempt, so there is no divergence and no retry. The
|
|
192
|
+
// returned violations are copied into the result for the consumer to surface
|
|
193
|
+
// to the host (see admission.ts). Empty on a bootstrap flush (the batch was
|
|
194
|
+
// never built — maintenance is deferred to the end-of-snapshot
|
|
195
|
+
// `bootstrapFinalize` convergence). A genuine per-change storage failure
|
|
196
|
+
// (collected in `result.errors`) still aborts the apply — that path is
|
|
197
|
+
// orthogonal.
|
|
198
|
+
if (seamBatch.length > 0) {
|
|
199
|
+
const seamResult = await db.ingestExternalRowChanges(seamBatch, {
|
|
200
|
+
applyForeignKeyActions,
|
|
201
|
+
assertionFailureMode: 'report',
|
|
202
|
+
});
|
|
203
|
+
if (seamResult.assertionViolations.length > 0) {
|
|
204
|
+
result.assertionViolations = seamResult.assertionViolations;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
67
207
|
return result;
|
|
68
208
|
};
|
|
69
209
|
}
|
|
210
|
+
/**
|
|
211
|
+
* Finalize a snapshot bootstrap: the per-flush MV maintenance and watch capture
|
|
212
|
+
* were deferred (each bootstrap flush skipped the seam), so converge every MV
|
|
213
|
+
* in dependency order and fire a coarse whole-table watch invalidation for each
|
|
214
|
+
* bootstrapped base table and each refreshed MV — base-table and MV watchers
|
|
215
|
+
* re-read once instead of seeing per-row capture.
|
|
216
|
+
*
|
|
217
|
+
* A throw from `refreshAllMaterializedViews` propagates to the caller, which
|
|
218
|
+
* runs the finalize before clearing the snapshot checkpoint — so the checkpoint
|
|
219
|
+
* survives and the transfer retries (the storage rows are already correct, so
|
|
220
|
+
* the retry's finalize rebuilds cleanly).
|
|
221
|
+
*/
|
|
222
|
+
async function finalizeBootstrap(db, options) {
|
|
223
|
+
const refreshed = await db.refreshAllMaterializedViews();
|
|
224
|
+
// Coarse base-table invalidation per bootstrapped table (note the
|
|
225
|
+
// table-then-schema argument order of `notifyExternalChange`)...
|
|
226
|
+
for (const { schema, table } of options.bootstrapTables ?? []) {
|
|
227
|
+
await db.notifyExternalChange(table, schema);
|
|
228
|
+
}
|
|
229
|
+
// ...and per refreshed MV, so MV watchers re-read.
|
|
230
|
+
for (const { schemaName, name } of refreshed) {
|
|
231
|
+
await db.notifyExternalChange(name, schemaName);
|
|
232
|
+
}
|
|
233
|
+
return { dataChangesApplied: 0, schemaChangesApplied: 0, errors: [] };
|
|
234
|
+
}
|
|
70
235
|
/**
|
|
71
236
|
* Group data changes by table (schema.table).
|
|
72
237
|
*/
|
|
@@ -109,8 +274,8 @@ async function applySchemaChange(db, events, change, _options) {
|
|
|
109
274
|
const eventType = change.type.startsWith('drop') ? 'drop' : change.type.startsWith('create') ? 'create' : 'alter';
|
|
110
275
|
const objectType = change.type.includes('table') ? 'table' : 'index';
|
|
111
276
|
// Register this as an expected remote event BEFORE executing DDL.
|
|
112
|
-
// When
|
|
113
|
-
//
|
|
277
|
+
// When the module emits the event, it will be automatically marked as
|
|
278
|
+
// remote, so SyncManager won't re-record it.
|
|
114
279
|
// This approach avoids race conditions with concurrent local DDL.
|
|
115
280
|
events.expectRemoteSchemaEvent({
|
|
116
281
|
type: eventType,
|
|
@@ -137,98 +302,89 @@ async function applySchemaChange(db, events, change, _options) {
|
|
|
137
302
|
// The module's event is automatically marked as remote.
|
|
138
303
|
}
|
|
139
304
|
/**
|
|
140
|
-
*
|
|
305
|
+
* Collapse one row group's changes into a single ExternalRowOp.
|
|
306
|
+
* A delete in the group wins over column updates.
|
|
141
307
|
*/
|
|
142
|
-
async function
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
const tableSchema = getTableSchema(schema, table);
|
|
147
|
-
if (!tableSchema) {
|
|
148
|
-
throw new Error(`Table schema not found: ${schema}.${table}`);
|
|
149
|
-
}
|
|
150
|
-
const encodeOptions = { collation };
|
|
151
|
-
const pkDirections = tableSchema.primaryKeyDefinition.map(p => !!p.desc);
|
|
152
|
-
const dataKey = buildDataKey(pk, encodeOptions, pkDirections);
|
|
153
|
-
// Check for delete operations first
|
|
154
|
-
const deleteChange = changes.find(c => c.type === 'delete');
|
|
155
|
-
if (deleteChange) {
|
|
156
|
-
await applyDelete(kv, events, tableSchema, dataKey, pk);
|
|
157
|
-
return;
|
|
308
|
+
async function buildRowOp(table, changes) {
|
|
309
|
+
const { pk } = changes[0];
|
|
310
|
+
if (changes.some(c => c.type === 'delete')) {
|
|
311
|
+
return { op: 'delete', pk };
|
|
158
312
|
}
|
|
159
|
-
|
|
160
|
-
await applyColumnUpdates(kv, events, tableSchema, dataKey, pk, changes, { serializeRow, deserializeRow });
|
|
313
|
+
return { op: 'upsert', row: await mergeColumnUpdates(table, pk, changes) };
|
|
161
314
|
}
|
|
162
315
|
/**
|
|
163
|
-
*
|
|
316
|
+
* Merge column updates onto the row's current image (UPSERT semantics):
|
|
317
|
+
* pre-read the existing row by PK, or build a PK+nulls partial row when
|
|
318
|
+
* absent (column changes may arrive before the rest of the row).
|
|
164
319
|
*/
|
|
165
|
-
async function
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
events.emitDataChange({
|
|
169
|
-
type: 'delete',
|
|
170
|
-
schemaName: tableSchema.schemaName,
|
|
171
|
-
tableName: tableSchema.name,
|
|
172
|
-
key: pk,
|
|
173
|
-
remote: true,
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
/**
|
|
177
|
-
* Apply column updates with UPSERT semantics.
|
|
178
|
-
*/
|
|
179
|
-
async function applyColumnUpdates(kv, events, tableSchema, dataKey, pk, changes, serialization) {
|
|
180
|
-
const { serializeRow, deserializeRow } = serialization;
|
|
181
|
-
// Read existing row if any
|
|
182
|
-
const existingData = await kv.get(dataKey);
|
|
320
|
+
async function mergeColumnUpdates(table, pk, changes) {
|
|
321
|
+
const tableSchema = table.getSchema();
|
|
322
|
+
const existing = await table.readRowByPk(pk);
|
|
183
323
|
let row;
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
row = deserializeRow(existingData);
|
|
324
|
+
if (existing) {
|
|
325
|
+
row = [...existing];
|
|
187
326
|
}
|
|
188
327
|
else {
|
|
189
|
-
// Create new row with nulls
|
|
190
328
|
row = new Array(tableSchema.columns.length).fill(null);
|
|
191
|
-
// Set PK values
|
|
192
329
|
for (let i = 0; i < tableSchema.primaryKeyDefinition.length; i++) {
|
|
193
|
-
|
|
194
|
-
row[pkDef.index] = pk[i];
|
|
330
|
+
row[tableSchema.primaryKeyDefinition[i].index] = pk[i];
|
|
195
331
|
}
|
|
196
|
-
isInsert = true;
|
|
197
332
|
}
|
|
198
|
-
|
|
199
|
-
const changedColumns = [];
|
|
333
|
+
let columnsApplied = 0;
|
|
200
334
|
for (const change of changes) {
|
|
201
|
-
if (change.columns)
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
335
|
+
if (!change.columns)
|
|
336
|
+
continue;
|
|
337
|
+
for (const [colName, value] of Object.entries(change.columns)) {
|
|
338
|
+
const colIndex = tableSchema.columnIndexMap.get(colName.toLowerCase());
|
|
339
|
+
if (colIndex !== undefined) {
|
|
340
|
+
row[colIndex] = value;
|
|
341
|
+
columnsApplied++;
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
// Column name not found in schema - this could be a sync bug
|
|
345
|
+
console.warn(`[Sync] Column '${colName}' not found in ${tableSchema.schemaName}.${tableSchema.name}. ` +
|
|
346
|
+
`Available columns: ${[...tableSchema.columnIndexMap.keys()].join(', ')}`);
|
|
213
347
|
}
|
|
214
348
|
}
|
|
215
349
|
}
|
|
216
|
-
|
|
217
|
-
if (changedColumns.length === 0 && !isInsert) {
|
|
350
|
+
if (columnsApplied === 0 && existing) {
|
|
218
351
|
console.warn(`[Sync] No columns were applied for ${tableSchema.schemaName}.${tableSchema.name} pk=${JSON.stringify(pk)}. ` +
|
|
219
352
|
`This may indicate a column name mismatch between source and destination.`);
|
|
220
353
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
354
|
+
return row;
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Emit module data change events for the effective changes, with
|
|
358
|
+
* `remote: true` so the SyncManager never re-records them. Suppressed no-ops
|
|
359
|
+
* (absent delete, value-identical upsert) were never reported by the store,
|
|
360
|
+
* so they emit nothing — deliberate.
|
|
361
|
+
*/
|
|
362
|
+
function emitEffectiveChanges(events, tableSchema, effective) {
|
|
363
|
+
for (const change of effective) {
|
|
364
|
+
const row = change.newRow ?? change.oldRow;
|
|
365
|
+
const pk = tableSchema.primaryKeyDefinition.map(p => row[p.index]);
|
|
366
|
+
events.emitDataChange({
|
|
367
|
+
type: change.op,
|
|
368
|
+
schemaName: tableSchema.schemaName,
|
|
369
|
+
tableName: tableSchema.name,
|
|
370
|
+
key: pk,
|
|
371
|
+
oldRow: change.oldRow,
|
|
372
|
+
newRow: change.newRow,
|
|
373
|
+
changedColumns: change.op === 'update'
|
|
374
|
+
? diffChangedColumns(tableSchema, change.oldRow, change.newRow)
|
|
375
|
+
: undefined,
|
|
376
|
+
remote: true,
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
/** Column names whose values differ between the effective before/after images. */
|
|
381
|
+
function diffChangedColumns(tableSchema, oldRow, newRow) {
|
|
382
|
+
const changed = [];
|
|
383
|
+
for (let i = 0; i < tableSchema.columns.length; i++) {
|
|
384
|
+
if (compareSqlValues(oldRow[i], newRow[i]) !== 0) {
|
|
385
|
+
changed.push(tableSchema.columns[i].name);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return changed;
|
|
233
389
|
}
|
|
234
390
|
//# sourceMappingURL=store-adapter.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store-adapter.js","sourceRoot":"","sources":["../../../src/sync/store-adapter.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"store-adapter.js","sourceRoot":"","sources":["../../../src/sync/store-adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,7 +11,9 @@ import type { ColumnVersionStore } from '../metadata/column-version.js';
|
|
|
11
11
|
import type { TombstoneStore } from '../metadata/tombstones.js';
|
|
12
12
|
import type { ChangeLogStore } from '../metadata/change-log.js';
|
|
13
13
|
import type { SchemaMigrationStore } from '../metadata/schema-migration.js';
|
|
14
|
-
import type {
|
|
14
|
+
import type { QuarantineStore } from '../metadata/quarantine.js';
|
|
15
|
+
import type { BasisLifecycleStore } from '../metadata/basis-lifecycle.js';
|
|
16
|
+
import type { SyncConfig, ApplyToStoreCallback, ApplyToStoreResult, UnknownTableDisposition } from './protocol.js';
|
|
15
17
|
import type { SyncEventEmitterImpl } from './events.js';
|
|
16
18
|
/**
|
|
17
19
|
* Context shared across sync sub-modules.
|
|
@@ -27,10 +29,34 @@ export interface SyncContext {
|
|
|
27
29
|
readonly tombstones: TombstoneStore;
|
|
28
30
|
readonly changeLog: ChangeLogStore;
|
|
29
31
|
readonly schemaMigrations: SchemaMigrationStore;
|
|
32
|
+
readonly quarantine: QuarantineStore;
|
|
33
|
+
readonly basisLifecycle: BasisLifecycleStore;
|
|
30
34
|
readonly syncEvents: SyncEventEmitterImpl;
|
|
31
35
|
readonly applyToStore?: ApplyToStoreCallback;
|
|
32
36
|
getSiteId(): SiteId;
|
|
33
37
|
getCurrentHLC(): HLC;
|
|
38
|
+
/**
|
|
39
|
+
* Whether `(schema, table)` is in the local basis. Backed by the
|
|
40
|
+
* `getTableSchema` oracle; when no oracle was provided detection is inert and
|
|
41
|
+
* this returns `true` for every table (the store adapter's defensive throw
|
|
42
|
+
* remains the fallback for genuinely-retired tables).
|
|
43
|
+
*/
|
|
44
|
+
isTableInBasis(schema: string, table: string): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Current column names for an in-basis table, or `undefined` when the table is
|
|
47
|
+
* outside the local basis (or no `getTableSchema` oracle was wired). Backs both
|
|
48
|
+
* the drain path's basis gate (`undefined` ⇒ the held group's table is not back,
|
|
49
|
+
* skip it) and its schema-drift filter (a held column change for a column absent
|
|
50
|
+
* from this set is drift-dropped). Implemented via `getTableSchema` in
|
|
51
|
+
* `SyncManagerImpl`.
|
|
52
|
+
*/
|
|
53
|
+
getTableColumnNames(schema: string, table: string): readonly string[] | undefined;
|
|
54
|
+
/**
|
|
55
|
+
* Record cumulative unknown-table disposition stats (surfaced via
|
|
56
|
+
* `SyncManager.getUnknownTableStats`). Telemetry-only; called once per
|
|
57
|
+
* diverted `(schema, table)` group after successful admission.
|
|
58
|
+
*/
|
|
59
|
+
recordUnknownTable(disposition: UnknownTableDisposition, schema: string, table: string, changeCount: number): void;
|
|
34
60
|
}
|
|
35
61
|
/**
|
|
36
62
|
* Persist HLC state to the KV store (standalone put).
|
|
@@ -40,6 +66,23 @@ export declare function persistHLCState(ctx: SyncContext): Promise<void>;
|
|
|
40
66
|
* Normalize an unknown caught value into an Error instance.
|
|
41
67
|
*/
|
|
42
68
|
export declare function toError(error: unknown): Error;
|
|
69
|
+
/**
|
|
70
|
+
* Abort the apply if the store reported any per-change errors.
|
|
71
|
+
*
|
|
72
|
+
* The store adapter continues applying other tables when one fails, collecting
|
|
73
|
+
* each failure in `result.errors` rather than throwing — so the maximal set of
|
|
74
|
+
* resolvable rows reaches committed storage. The *consumer* must still treat
|
|
75
|
+
* any non-empty `errors` exactly like the whole-batch throw path: emit an error
|
|
76
|
+
* sync-state event and throw BEFORE committing any CRDT metadata, so the whole
|
|
77
|
+
* batch re-resolves and re-applies idempotently on the next sync attempt.
|
|
78
|
+
*
|
|
79
|
+
* This upholds the write-ordering invariant (docs/sync.md § Transactional
|
|
80
|
+
* Integrity): CRDT metadata must not be committed when the corresponding data
|
|
81
|
+
* write did not land — for per-change failures, not just whole-batch throws.
|
|
82
|
+
*
|
|
83
|
+
* No-op when `result.errors` is empty.
|
|
84
|
+
*/
|
|
85
|
+
export declare function throwIfApplyErrors(ctx: SyncContext, result: ApplyToStoreResult): void;
|
|
43
86
|
/**
|
|
44
87
|
* Write HLC state into an existing WriteBatch.
|
|
45
88
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync-context.d.ts","sourceRoot":"","sources":["../../../src/sync/sync-context.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACxE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAC5E,OAAO,KAAK,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"sync-context.d.ts","sourceRoot":"","sources":["../../../src/sync/sync-context.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACxE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAC5E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAC1E,OAAO,KAAK,EAAE,UAAU,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AACnH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAGxD;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC3B,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,QAAQ,CAAC,cAAc,EAAE,kBAAkB,CAAC;IAC5C,QAAQ,CAAC,UAAU,EAAE,cAAc,CAAC;IACpC,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC;IACnC,QAAQ,CAAC,gBAAgB,EAAE,oBAAoB,CAAC;IAChD,QAAQ,CAAC,UAAU,EAAE,eAAe,CAAC;IACrC,QAAQ,CAAC,cAAc,EAAE,mBAAmB,CAAC;IAC7C,QAAQ,CAAC,UAAU,EAAE,oBAAoB,CAAC;IAC1C,QAAQ,CAAC,YAAY,CAAC,EAAE,oBAAoB,CAAC;IAE7C,SAAS,IAAI,MAAM,CAAC;IACpB,aAAa,IAAI,GAAG,CAAC;IAErB;;;;;OAKG;IACH,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;IAEvD;;;;;;;OAOG;IACH,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IAElF;;;;OAIG;IACH,kBAAkB,CACjB,WAAW,EAAE,uBAAuB,EACpC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,GACjB,IAAI,CAAC;CACR;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAOrE;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CAE7C;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,kBAAkB,GAAG,IAAI,CAarF;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CACnC,GAAG,EAAE,WAAW,EAChB,KAAK,EAAE,OAAO,gBAAgB,EAAE,UAAU,GACxC,IAAI,CAON"}
|
|
@@ -22,6 +22,32 @@ export async function persistHLCState(ctx) {
|
|
|
22
22
|
export function toError(error) {
|
|
23
23
|
return error instanceof Error ? error : new Error(String(error));
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Abort the apply if the store reported any per-change errors.
|
|
27
|
+
*
|
|
28
|
+
* The store adapter continues applying other tables when one fails, collecting
|
|
29
|
+
* each failure in `result.errors` rather than throwing — so the maximal set of
|
|
30
|
+
* resolvable rows reaches committed storage. The *consumer* must still treat
|
|
31
|
+
* any non-empty `errors` exactly like the whole-batch throw path: emit an error
|
|
32
|
+
* sync-state event and throw BEFORE committing any CRDT metadata, so the whole
|
|
33
|
+
* batch re-resolves and re-applies idempotently on the next sync attempt.
|
|
34
|
+
*
|
|
35
|
+
* This upholds the write-ordering invariant (docs/sync.md § Transactional
|
|
36
|
+
* Integrity): CRDT metadata must not be committed when the corresponding data
|
|
37
|
+
* write did not land — for per-change failures, not just whole-batch throws.
|
|
38
|
+
*
|
|
39
|
+
* No-op when `result.errors` is empty.
|
|
40
|
+
*/
|
|
41
|
+
export function throwIfApplyErrors(ctx, result) {
|
|
42
|
+
if (result.errors.length === 0)
|
|
43
|
+
return;
|
|
44
|
+
const detail = result.errors
|
|
45
|
+
.map(({ change, error }) => `${change.schema}.${change.table} (${change.type}): ${error.message}`)
|
|
46
|
+
.join('; ');
|
|
47
|
+
const error = new Error(`apply-to-store failed for ${result.errors.length} change(s): ${detail}`, { cause: result.errors[0].error });
|
|
48
|
+
ctx.syncEvents.emitSyncStateChange({ status: 'error', error });
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
25
51
|
/**
|
|
26
52
|
* Write HLC state into an existing WriteBatch.
|
|
27
53
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync-context.js","sourceRoot":"","sources":["../../../src/sync/sync-context.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"sync-context.js","sourceRoot":"","sources":["../../../src/sync/sync-context.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAaH,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAuDtD;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,GAAgB;IACrD,MAAM,KAAK,GAAG,GAAG,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAC;IACxC,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC;IAClC,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACzC,IAAI,CAAC,YAAY,CAAC,CAAC,EAAE,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAC5C,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACxC,MAAM,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,eAAe,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;AACrD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,OAAO,CAAC,KAAc;IACrC,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;AAClE,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAAgB,EAAE,MAA0B;IAC9E,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAEvC,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM;SAC1B,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,KAAK,KAAK,MAAM,CAAC,IAAI,MAAM,KAAK,CAAC,OAAO,EAAE,CAAC;SACjG,IAAI,CAAC,IAAI,CAAC,CAAC;IACb,MAAM,KAAK,GAAG,IAAI,KAAK,CACtB,6BAA6B,MAAM,CAAC,MAAM,CAAC,MAAM,eAAe,MAAM,EAAE,EACxE,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,CACjC,CAAC;IAEF,GAAG,CAAC,UAAU,CAAC,mBAAmB,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;IAC/D,MAAM,KAAK,CAAC;AACb,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,oBAAoB,CACnC,GAAgB,EAChB,KAA0C;IAE1C,MAAM,KAAK,GAAG,GAAG,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAC;IACxC,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC;IAClC,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACzC,IAAI,CAAC,YAAY,CAAC,CAAC,EAAE,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAC5C,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACxC,KAAK,CAAC,GAAG,CAAC,eAAe,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;AAC9C,CAAC"}
|