@quereus/sync 3.2.1 → 4.0.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 +6 -6
|
@@ -18,7 +18,7 @@ export interface StoredMigration {
|
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
20
|
* Serialize a migration for storage.
|
|
21
|
-
* Format:
|
|
21
|
+
* Format: 30 bytes HLC + 4 bytes version + 1 byte type length + type + ddl
|
|
22
22
|
*/
|
|
23
23
|
export declare function serializeMigration(migration: StoredMigration): Uint8Array;
|
|
24
24
|
/**
|
|
@@ -8,18 +8,18 @@ import { serializeHLC, deserializeHLC, compareHLC } from '../clock/hlc.js';
|
|
|
8
8
|
import { buildSchemaMigrationKey, buildSchemaMigrationScanBounds } from './keys.js';
|
|
9
9
|
/**
|
|
10
10
|
* Serialize a migration for storage.
|
|
11
|
-
* Format:
|
|
11
|
+
* Format: 30 bytes HLC + 4 bytes version + 1 byte type length + type + ddl
|
|
12
12
|
*/
|
|
13
13
|
export function serializeMigration(migration) {
|
|
14
14
|
const encoder = new TextEncoder();
|
|
15
15
|
const typeBytes = encoder.encode(migration.type);
|
|
16
16
|
const ddlBytes = encoder.encode(migration.ddl);
|
|
17
17
|
const hlcBytes = serializeHLC(migration.hlc);
|
|
18
|
-
const buffer = new Uint8Array(
|
|
18
|
+
const buffer = new Uint8Array(30 + 4 + 1 + typeBytes.length + ddlBytes.length);
|
|
19
19
|
let offset = 0;
|
|
20
|
-
// HLC (
|
|
20
|
+
// HLC (30 bytes)
|
|
21
21
|
buffer.set(hlcBytes, offset);
|
|
22
|
-
offset +=
|
|
22
|
+
offset += 30;
|
|
23
23
|
// Schema version (4 bytes, big-endian)
|
|
24
24
|
const view = new DataView(buffer.buffer);
|
|
25
25
|
view.setUint32(offset, migration.schemaVersion, false);
|
|
@@ -39,9 +39,9 @@ export function serializeMigration(migration) {
|
|
|
39
39
|
export function deserializeMigration(buffer) {
|
|
40
40
|
const decoder = new TextDecoder();
|
|
41
41
|
let offset = 0;
|
|
42
|
-
// HLC (
|
|
43
|
-
const hlc = deserializeHLC(buffer.slice(0,
|
|
44
|
-
offset +=
|
|
42
|
+
// HLC (30 bytes)
|
|
43
|
+
const hlc = deserializeHLC(buffer.slice(0, 30));
|
|
44
|
+
offset += 30;
|
|
45
45
|
// Schema version (4 bytes)
|
|
46
46
|
const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
47
47
|
const schemaVersion = view.getUint32(offset, false);
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* "most destructive wins" conflict resolution for schema changes.
|
|
6
6
|
*
|
|
7
7
|
* Key format: sv:{schema}.{table}:{column}
|
|
8
|
-
* Value: HLC (
|
|
8
|
+
* Value: HLC (30 bytes) + 1 byte type + type-specific data
|
|
9
9
|
*
|
|
10
10
|
* Column types:
|
|
11
11
|
* - 0x01: Regular column (type affinity, nullable, default)
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* "most destructive wins" conflict resolution for schema changes.
|
|
6
6
|
*
|
|
7
7
|
* Key format: sv:{schema}.{table}:{column}
|
|
8
|
-
* Value: HLC (
|
|
8
|
+
* Value: HLC (30 bytes) + 1 byte type + type-specific data
|
|
9
9
|
*
|
|
10
10
|
* Column types:
|
|
11
11
|
* - 0x01: Regular column (type affinity, nullable, default)
|
|
@@ -65,20 +65,20 @@ export function serializeSchemaVersion(version) {
|
|
|
65
65
|
if (version.ddl !== undefined)
|
|
66
66
|
data.ddl = version.ddl;
|
|
67
67
|
const dataBytes = encoder.encode(JSON.stringify(data));
|
|
68
|
-
const buffer = new Uint8Array(
|
|
68
|
+
const buffer = new Uint8Array(30 + 1 + dataBytes.length);
|
|
69
69
|
buffer.set(hlcBytes, 0);
|
|
70
|
-
buffer[
|
|
71
|
-
buffer.set(dataBytes,
|
|
70
|
+
buffer[30] = typeByte;
|
|
71
|
+
buffer.set(dataBytes, 31);
|
|
72
72
|
return buffer;
|
|
73
73
|
}
|
|
74
74
|
/**
|
|
75
75
|
* Deserialize a schema version from storage.
|
|
76
76
|
*/
|
|
77
77
|
export function deserializeSchemaVersion(buffer) {
|
|
78
|
-
const hlc = deserializeHLC(buffer.slice(0,
|
|
79
|
-
const typeByte = buffer[
|
|
78
|
+
const hlc = deserializeHLC(buffer.slice(0, 30));
|
|
79
|
+
const typeByte = buffer[30];
|
|
80
80
|
const type = typeByte === 0x01 ? 'column' : typeByte === 0x02 ? 'dropped' : 'table';
|
|
81
|
-
const dataJson = decoder.decode(buffer.slice(
|
|
81
|
+
const dataJson = decoder.decode(buffer.slice(31));
|
|
82
82
|
const data = dataJson ? JSON.parse(dataJson) : {};
|
|
83
83
|
return {
|
|
84
84
|
hlc,
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* When a row is deleted, a tombstone is created with the deletion HLC.
|
|
5
5
|
* Tombstones prevent deleted rows from being resurrected by older writes.
|
|
6
6
|
*/
|
|
7
|
-
import type { SqlValue } from '@quereus/quereus';
|
|
7
|
+
import type { Row, SqlValue } from '@quereus/quereus';
|
|
8
8
|
import type { KVStore, WriteBatch } from '@quereus/store';
|
|
9
9
|
import { type HLC } from '../clock/hlc.js';
|
|
10
10
|
/**
|
|
@@ -13,14 +13,29 @@ import { type HLC } from '../clock/hlc.js';
|
|
|
13
13
|
export interface Tombstone {
|
|
14
14
|
hlc: HLC;
|
|
15
15
|
createdAt: number;
|
|
16
|
+
/**
|
|
17
|
+
* Optional last-known row image before deletion (the engine's `oldRow` at delete
|
|
18
|
+
* time). Persisted so it reaches a receiver via `getChangesSince`, which
|
|
19
|
+
* re-resolves deletions from the tombstone. Best-effort audit/undo metadata:
|
|
20
|
+
* absent when the delete carried no `oldRow` and on snapshot-reconstructed
|
|
21
|
+
* tombstones. A tombstone with no `priorRow` serializes to the fixed 38-byte head.
|
|
22
|
+
*/
|
|
23
|
+
priorRow?: Row;
|
|
16
24
|
}
|
|
17
25
|
/**
|
|
18
26
|
* Serialize a tombstone for storage.
|
|
19
|
-
*
|
|
27
|
+
*
|
|
28
|
+
* Format: 30 bytes HLC + 8 bytes createdAt, then — only when a `priorRow` is
|
|
29
|
+
* present — a trailing JSON array of the row's values, each through the column
|
|
30
|
+
* version `encodeSqlValue` helper so `Uint8Array`/`bigint`/`null` cells round-trip.
|
|
31
|
+
* A tombstone with no `priorRow` serializes to the unchanged 38-byte head.
|
|
20
32
|
*/
|
|
21
33
|
export declare function serializeTombstone(tombstone: Tombstone): Uint8Array;
|
|
22
34
|
/**
|
|
23
|
-
* Deserialize a tombstone from storage.
|
|
35
|
+
* Deserialize a tombstone from storage. Absent-tolerant: bytes beyond the 38-byte
|
|
36
|
+
* head are the JSON-encoded `priorRow` (see {@link serializeTombstone}); a
|
|
37
|
+
* head-only buffer (no before-image, or a snapshot-reconstructed tombstone)
|
|
38
|
+
* deserializes with `priorRow` absent.
|
|
24
39
|
*/
|
|
25
40
|
export declare function deserializeTombstone(buffer: Uint8Array): Tombstone;
|
|
26
41
|
/**
|
|
@@ -28,20 +43,22 @@ export declare function deserializeTombstone(buffer: Uint8Array): Tombstone;
|
|
|
28
43
|
*/
|
|
29
44
|
export declare class TombstoneStore {
|
|
30
45
|
private readonly kv;
|
|
31
|
-
private readonly
|
|
32
|
-
constructor(kv: KVStore,
|
|
46
|
+
private readonly retentionHorizonMs;
|
|
47
|
+
constructor(kv: KVStore, retentionHorizonMs: number);
|
|
33
48
|
/**
|
|
34
49
|
* Get the tombstone for a row, if it exists.
|
|
35
50
|
*/
|
|
36
51
|
getTombstone(schemaName: string, tableName: string, pk: SqlValue[]): Promise<Tombstone | undefined>;
|
|
37
52
|
/**
|
|
38
|
-
* Create a tombstone for a deleted row.
|
|
53
|
+
* Create a tombstone for a deleted row. `priorRow` (optional) is the row's
|
|
54
|
+
* last-known image, persisted as best-effort audit/undo metadata.
|
|
39
55
|
*/
|
|
40
|
-
setTombstone(schemaName: string, tableName: string, pk: SqlValue[], hlc: HLC): Promise<void>;
|
|
56
|
+
setTombstone(schemaName: string, tableName: string, pk: SqlValue[], hlc: HLC, priorRow?: Row): Promise<void>;
|
|
41
57
|
/**
|
|
42
|
-
* Set tombstone in a batch.
|
|
58
|
+
* Set tombstone in a batch. `priorRow` (optional) is the row's last-known image,
|
|
59
|
+
* persisted as best-effort audit/undo metadata.
|
|
43
60
|
*/
|
|
44
|
-
setTombstoneBatch(batch: WriteBatch, schemaName: string, tableName: string, pk: SqlValue[], hlc: HLC): void;
|
|
61
|
+
setTombstoneBatch(batch: WriteBatch, schemaName: string, tableName: string, pk: SqlValue[], hlc: HLC, priorRow?: Row): void;
|
|
45
62
|
/**
|
|
46
63
|
* Delete a tombstone (used when resurrecting a row).
|
|
47
64
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tombstones.d.ts","sourceRoot":"","sources":["../../../src/metadata/tombstones.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"tombstones.d.ts","sourceRoot":"","sources":["../../../src/metadata/tombstones.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EAAE,KAAK,GAAG,EAA4C,MAAM,iBAAiB,CAAC;AAOrF;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,GAAG,CAAC;IACT,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,GAAG,CAAC;CAChB;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,SAAS,GAAG,UAAU,CAenE;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,UAAU,GAAG,SAAS,CAWlE;AAED;;GAEG;AACH,qBAAa,cAAc;IAEvB,OAAO,CAAC,QAAQ,CAAC,EAAE;IACnB,OAAO,CAAC,QAAQ,CAAC,kBAAkB;gBADlB,EAAE,EAAE,OAAO,EACX,kBAAkB,EAAE,MAAM;IAG7C;;OAEG;IACG,YAAY,CAChB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,EAAE,EAAE,QAAQ,EAAE,GACb,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC;IAOjC;;;OAGG;IACG,YAAY,CAChB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,EAAE,EAAE,QAAQ,EAAE,EACd,GAAG,EAAE,GAAG,EACR,QAAQ,CAAC,EAAE,GAAG,GACb,OAAO,CAAC,IAAI,CAAC;IAMhB;;;OAGG;IACH,iBAAiB,CACf,KAAK,EAAE,UAAU,EACjB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,EAAE,EAAE,QAAQ,EAAE,EACd,GAAG,EAAE,GAAG,EACR,QAAQ,CAAC,EAAE,GAAG,GACb,IAAI;IAMP;;OAEG;IACG,eAAe,CACnB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,EAAE,EAAE,QAAQ,EAAE,GACb,OAAO,CAAC,IAAI,CAAC;IAKhB;;;OAGG;IACG,oBAAoB,CACxB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,EAAE,EAAE,QAAQ,EAAE,EACd,WAAW,EAAE,GAAG,EAChB,iBAAiB,EAAE,OAAO,GACzB,OAAO,CAAC,OAAO,CAAC;IAanB;;;OAGG;IACG,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAkB1E;;OAEG;IACI,gBAAgB,CACrB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GAChB,aAAa,CAAC;QAAE,EAAE,EAAE,QAAQ,EAAE,CAAC;QAAC,SAAS,EAAE,SAAS,CAAA;KAAE,CAAC;CAc3D"}
|
|
@@ -5,37 +5,58 @@
|
|
|
5
5
|
* Tombstones prevent deleted rows from being resurrected by older writes.
|
|
6
6
|
*/
|
|
7
7
|
import { serializeHLC, deserializeHLC, compareHLC } from '../clock/hlc.js';
|
|
8
|
+
import { encodeSqlValue, decodeSqlValue } from './column-version.js';
|
|
8
9
|
import { buildTombstoneKey, buildTombstoneScanBounds, decodePK } from './keys.js';
|
|
10
|
+
/** Fixed-size head of a serialized tombstone: 30 bytes HLC + 8 bytes createdAt. */
|
|
11
|
+
const TOMBSTONE_HEAD_BYTES = 38;
|
|
9
12
|
/**
|
|
10
13
|
* Serialize a tombstone for storage.
|
|
11
|
-
*
|
|
14
|
+
*
|
|
15
|
+
* Format: 30 bytes HLC + 8 bytes createdAt, then — only when a `priorRow` is
|
|
16
|
+
* present — a trailing JSON array of the row's values, each through the column
|
|
17
|
+
* version `encodeSqlValue` helper so `Uint8Array`/`bigint`/`null` cells round-trip.
|
|
18
|
+
* A tombstone with no `priorRow` serializes to the unchanged 38-byte head.
|
|
12
19
|
*/
|
|
13
20
|
export function serializeTombstone(tombstone) {
|
|
14
|
-
const
|
|
21
|
+
const head = new Uint8Array(TOMBSTONE_HEAD_BYTES);
|
|
15
22
|
const hlcBytes = serializeHLC(tombstone.hlc);
|
|
16
|
-
|
|
17
|
-
const view = new DataView(
|
|
18
|
-
view.setBigUint64(
|
|
23
|
+
head.set(hlcBytes, 0);
|
|
24
|
+
const view = new DataView(head.buffer);
|
|
25
|
+
view.setBigUint64(30, BigInt(tombstone.createdAt), false);
|
|
26
|
+
if (tombstone.priorRow === undefined)
|
|
27
|
+
return head;
|
|
28
|
+
const payload = new TextEncoder().encode(JSON.stringify(tombstone.priorRow.map(encodeSqlValue)));
|
|
29
|
+
const result = new Uint8Array(head.length + payload.length);
|
|
30
|
+
result.set(head, 0);
|
|
31
|
+
result.set(payload, head.length);
|
|
19
32
|
return result;
|
|
20
33
|
}
|
|
21
34
|
/**
|
|
22
|
-
* Deserialize a tombstone from storage.
|
|
35
|
+
* Deserialize a tombstone from storage. Absent-tolerant: bytes beyond the 38-byte
|
|
36
|
+
* head are the JSON-encoded `priorRow` (see {@link serializeTombstone}); a
|
|
37
|
+
* head-only buffer (no before-image, or a snapshot-reconstructed tombstone)
|
|
38
|
+
* deserializes with `priorRow` absent.
|
|
23
39
|
*/
|
|
24
40
|
export function deserializeTombstone(buffer) {
|
|
25
|
-
const hlc = deserializeHLC(buffer.slice(0,
|
|
41
|
+
const hlc = deserializeHLC(buffer.slice(0, 30));
|
|
26
42
|
const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
27
|
-
const createdAt = Number(view.getBigUint64(
|
|
28
|
-
|
|
43
|
+
const createdAt = Number(view.getBigUint64(30, false));
|
|
44
|
+
const tombstone = { hlc, createdAt };
|
|
45
|
+
if (buffer.byteLength > TOMBSTONE_HEAD_BYTES) {
|
|
46
|
+
const values = JSON.parse(new TextDecoder().decode(buffer.slice(TOMBSTONE_HEAD_BYTES)));
|
|
47
|
+
tombstone.priorRow = values.map(decodeSqlValue);
|
|
48
|
+
}
|
|
49
|
+
return tombstone;
|
|
29
50
|
}
|
|
30
51
|
/**
|
|
31
52
|
* Tombstone store operations.
|
|
32
53
|
*/
|
|
33
54
|
export class TombstoneStore {
|
|
34
55
|
kv;
|
|
35
|
-
|
|
36
|
-
constructor(kv,
|
|
56
|
+
retentionHorizonMs;
|
|
57
|
+
constructor(kv, retentionHorizonMs) {
|
|
37
58
|
this.kv = kv;
|
|
38
|
-
this.
|
|
59
|
+
this.retentionHorizonMs = retentionHorizonMs;
|
|
39
60
|
}
|
|
40
61
|
/**
|
|
41
62
|
* Get the tombstone for a row, if it exists.
|
|
@@ -48,19 +69,21 @@ export class TombstoneStore {
|
|
|
48
69
|
return deserializeTombstone(data);
|
|
49
70
|
}
|
|
50
71
|
/**
|
|
51
|
-
* Create a tombstone for a deleted row.
|
|
72
|
+
* Create a tombstone for a deleted row. `priorRow` (optional) is the row's
|
|
73
|
+
* last-known image, persisted as best-effort audit/undo metadata.
|
|
52
74
|
*/
|
|
53
|
-
async setTombstone(schemaName, tableName, pk, hlc) {
|
|
75
|
+
async setTombstone(schemaName, tableName, pk, hlc, priorRow) {
|
|
54
76
|
const key = buildTombstoneKey(schemaName, tableName, pk);
|
|
55
|
-
const tombstone = { hlc, createdAt: Date.now() };
|
|
77
|
+
const tombstone = { hlc, createdAt: Date.now(), ...(priorRow !== undefined ? { priorRow } : {}) };
|
|
56
78
|
await this.kv.put(key, serializeTombstone(tombstone));
|
|
57
79
|
}
|
|
58
80
|
/**
|
|
59
|
-
* Set tombstone in a batch.
|
|
81
|
+
* Set tombstone in a batch. `priorRow` (optional) is the row's last-known image,
|
|
82
|
+
* persisted as best-effort audit/undo metadata.
|
|
60
83
|
*/
|
|
61
|
-
setTombstoneBatch(batch, schemaName, tableName, pk, hlc) {
|
|
84
|
+
setTombstoneBatch(batch, schemaName, tableName, pk, hlc, priorRow) {
|
|
62
85
|
const key = buildTombstoneKey(schemaName, tableName, pk);
|
|
63
|
-
const tombstone = { hlc, createdAt: Date.now() };
|
|
86
|
+
const tombstone = { hlc, createdAt: Date.now(), ...(priorRow !== undefined ? { priorRow } : {}) };
|
|
64
87
|
batch.put(key, serializeTombstone(tombstone));
|
|
65
88
|
}
|
|
66
89
|
/**
|
|
@@ -98,7 +121,7 @@ export class TombstoneStore {
|
|
|
98
121
|
let count = 0;
|
|
99
122
|
for await (const entry of this.kv.iterate(bounds)) {
|
|
100
123
|
const tombstone = deserializeTombstone(entry.value);
|
|
101
|
-
if (now - tombstone.createdAt > this.
|
|
124
|
+
if (now - tombstone.createdAt > this.retentionHorizonMs) {
|
|
102
125
|
batch.delete(entry.key);
|
|
103
126
|
count++;
|
|
104
127
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tombstones.js","sourceRoot":"","sources":["../../../src/metadata/tombstones.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,EAAY,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AACrF,OAAO,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"tombstones.js","sourceRoot":"","sources":["../../../src/metadata/tombstones.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `quereus_basis_lifecycle()` — in-SQL introspection of the durable per-basis-table
|
|
3
|
+
* lifecycle records the sync layer maintains on each lens deploy
|
|
4
|
+
* (`docs/migration.md` § 2 Converge). A zero-argument table-valued function listing
|
|
5
|
+
* every stored {@link BasisTableLifecycleRecord}, one row per record, so a developer
|
|
6
|
+
* can surface legacy / retirement-candidate tables without writing host code:
|
|
7
|
+
*
|
|
8
|
+
* ```sql
|
|
9
|
+
* select "table", state, "unmappedSince"
|
|
10
|
+
* from quereus_basis_lifecycle()
|
|
11
|
+
* where state = 'derivation-source-only'
|
|
12
|
+
* order by "unmappedSince";
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* Pure convenience over {@link SyncManager.getBasisTableLifecycle} — no engine change.
|
|
16
|
+
* The TVF closes over the `SyncManager` (the host's smallest registration seam), and
|
|
17
|
+
* the records are read programmatically from KV, so the rows survive a restart and a
|
|
18
|
+
* fresh `Database` over the same store reflects the prior deploy's classification.
|
|
19
|
+
*/
|
|
20
|
+
import type { Database } from '@quereus/quereus';
|
|
21
|
+
import type { SyncManager } from '../sync/manager.js';
|
|
22
|
+
/**
|
|
23
|
+
* Register the `quereus_basis_lifecycle()` introspection TVF against `db`, reading
|
|
24
|
+
* from `syncManager`. Opt-in: the host calls this once after `createSyncModule(...)`
|
|
25
|
+
* (no auto-registration — `createSyncModule` takes no `Database`, and relay-only
|
|
26
|
+
* deployments need no SQL surface). The `quereus_`-prefixed name avoids collision
|
|
27
|
+
* with user tables/functions.
|
|
28
|
+
*
|
|
29
|
+
* Safe to call once per `Database`; a repeat call (same name/arity) **replaces** the
|
|
30
|
+
* prior registration rather than erroring or corrupting state (engine
|
|
31
|
+
* `registerFunction` → `Schema.addFunction` is a keyed overwrite).
|
|
32
|
+
*/
|
|
33
|
+
export declare function registerBasisLifecycleTvf(db: Database, syncManager: SyncManager): void;
|
|
34
|
+
//# sourceMappingURL=basis-lifecycle-tvf.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"basis-lifecycle-tvf.d.ts","sourceRoot":"","sources":["../../../src/sql/basis-lifecycle-tvf.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAoB,MAAM,kBAAkB,CAAC;AAEnE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAoCtD;;;;;;;;;;GAUG;AACH,wBAAgB,yBAAyB,CAAC,EAAE,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,GAAG,IAAI,CAwCtF"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `quereus_basis_lifecycle()` — in-SQL introspection of the durable per-basis-table
|
|
3
|
+
* lifecycle records the sync layer maintains on each lens deploy
|
|
4
|
+
* (`docs/migration.md` § 2 Converge). A zero-argument table-valued function listing
|
|
5
|
+
* every stored {@link BasisTableLifecycleRecord}, one row per record, so a developer
|
|
6
|
+
* can surface legacy / retirement-candidate tables without writing host code:
|
|
7
|
+
*
|
|
8
|
+
* ```sql
|
|
9
|
+
* select "table", state, "unmappedSince"
|
|
10
|
+
* from quereus_basis_lifecycle()
|
|
11
|
+
* where state = 'derivation-source-only'
|
|
12
|
+
* order by "unmappedSince";
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* Pure convenience over {@link SyncManager.getBasisTableLifecycle} — no engine change.
|
|
16
|
+
* The TVF closes over the `SyncManager` (the host's smallest registration seam), and
|
|
17
|
+
* the records are read programmatically from KV, so the rows survive a restart and a
|
|
18
|
+
* fresh `Database` over the same store reflects the prior deploy's classification.
|
|
19
|
+
*/
|
|
20
|
+
import { createTableValuedFunction, INTEGER_TYPE, TEXT_TYPE } from '@quereus/quereus';
|
|
21
|
+
/** A read-only generated column spec for the TVF's relation return type. */
|
|
22
|
+
function col(name, logicalType, nullable) {
|
|
23
|
+
return {
|
|
24
|
+
name,
|
|
25
|
+
type: { typeClass: 'scalar', logicalType, nullable, isReadOnly: true },
|
|
26
|
+
generated: true,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Project one lifecycle record onto its ordered SQL row. Column order MUST match
|
|
31
|
+
* the `returnType.columns` list below. Optional record fields coalesce to `null`
|
|
32
|
+
* (a `Row` cannot hold `undefined`); booleans emit as INTEGER 0/1 (engine
|
|
33
|
+
* convention, mirroring `table_info`'s `notnull`/`pk`); `mappedBy` renders as a
|
|
34
|
+
* JSON array string (empty ⇒ `"[]"`, never null); the `evictPolicy` union
|
|
35
|
+
* (`'never'` | `'immediate'` | a numeric ms horizon) collapses to its string form.
|
|
36
|
+
*/
|
|
37
|
+
function rowFromRecord(r) {
|
|
38
|
+
return [
|
|
39
|
+
r.schema,
|
|
40
|
+
r.table,
|
|
41
|
+
r.state,
|
|
42
|
+
JSON.stringify(r.mappedBy ?? []),
|
|
43
|
+
r.derivationSource ? 1 : 0,
|
|
44
|
+
r.inBasis ? 1 : 0,
|
|
45
|
+
r.mappedSince ?? null,
|
|
46
|
+
r.unmappedSince ?? null,
|
|
47
|
+
r.detachedAt ?? null,
|
|
48
|
+
r.lastDirectlyMappedWriteAt ?? null,
|
|
49
|
+
r.evictPolicy == null ? null : String(r.evictPolicy),
|
|
50
|
+
];
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Register the `quereus_basis_lifecycle()` introspection TVF against `db`, reading
|
|
54
|
+
* from `syncManager`. Opt-in: the host calls this once after `createSyncModule(...)`
|
|
55
|
+
* (no auto-registration — `createSyncModule` takes no `Database`, and relay-only
|
|
56
|
+
* deployments need no SQL surface). The `quereus_`-prefixed name avoids collision
|
|
57
|
+
* with user tables/functions.
|
|
58
|
+
*
|
|
59
|
+
* Safe to call once per `Database`; a repeat call (same name/arity) **replaces** the
|
|
60
|
+
* prior registration rather than erroring or corrupting state (engine
|
|
61
|
+
* `registerFunction` → `Schema.addFunction` is a keyed overwrite).
|
|
62
|
+
*/
|
|
63
|
+
export function registerBasisLifecycleTvf(db, syncManager) {
|
|
64
|
+
const schema = createTableValuedFunction({
|
|
65
|
+
name: 'quereus_basis_lifecycle',
|
|
66
|
+
numArgs: 0,
|
|
67
|
+
// Records change across deploys (and across restarts), so the result is not
|
|
68
|
+
// a pure function of its (empty) argument list.
|
|
69
|
+
deterministic: false,
|
|
70
|
+
returnType: {
|
|
71
|
+
typeClass: 'relation',
|
|
72
|
+
isReadOnly: true,
|
|
73
|
+
isSet: false,
|
|
74
|
+
columns: [
|
|
75
|
+
col('schema', TEXT_TYPE, false),
|
|
76
|
+
col('table', TEXT_TYPE, false),
|
|
77
|
+
col('state', TEXT_TYPE, false),
|
|
78
|
+
col('mappedBy', TEXT_TYPE, false),
|
|
79
|
+
col('derivationSource', INTEGER_TYPE, false),
|
|
80
|
+
col('inBasis', INTEGER_TYPE, false),
|
|
81
|
+
col('mappedSince', INTEGER_TYPE, true),
|
|
82
|
+
col('unmappedSince', INTEGER_TYPE, true),
|
|
83
|
+
col('detachedAt', INTEGER_TYPE, true),
|
|
84
|
+
col('lastDirectlyMappedWriteAt', INTEGER_TYPE, true),
|
|
85
|
+
col('evictPolicy', TEXT_TYPE, true),
|
|
86
|
+
],
|
|
87
|
+
keys: [],
|
|
88
|
+
rowConstraints: [],
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
// Snapshot the full record array once, then yield — iteration is over a stable
|
|
92
|
+
// array, immune to a concurrent deploy mutating records mid-scan.
|
|
93
|
+
async function* () {
|
|
94
|
+
const records = await syncManager.getBasisTableLifecycle();
|
|
95
|
+
for (const record of records) {
|
|
96
|
+
yield rowFromRecord(record);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
db.registerFunction(schema);
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=basis-lifecycle-tvf.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"basis-lifecycle-tvf.js","sourceRoot":"","sources":["../../../src/sql/basis-lifecycle-tvf.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,EAAE,yBAAyB,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAItF,4EAA4E;AAC5E,SAAS,GAAG,CAAC,IAAY,EAAE,WAAwB,EAAE,QAAiB;IACpE,OAAO;QACL,IAAI;QACJ,IAAI,EAAE,EAAE,SAAS,EAAE,QAAiB,EAAE,WAAW,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE;QAC/E,SAAS,EAAE,IAAI;KAChB,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,aAAa,CAAC,CAA4B;IACjD,OAAO;QACL,CAAC,CAAC,MAAM;QACR,CAAC,CAAC,KAAK;QACP,CAAC,CAAC,KAAK;QACP,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,IAAI,EAAE,CAAC;QAChC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC1B,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACjB,CAAC,CAAC,WAAW,IAAI,IAAI;QACrB,CAAC,CAAC,aAAa,IAAI,IAAI;QACvB,CAAC,CAAC,UAAU,IAAI,IAAI;QACpB,CAAC,CAAC,yBAAyB,IAAI,IAAI;QACnC,CAAC,CAAC,WAAW,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC;KACrD,CAAC;AACJ,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,yBAAyB,CAAC,EAAY,EAAE,WAAwB;IAC9E,MAAM,MAAM,GAAG,yBAAyB,CACtC;QACE,IAAI,EAAE,yBAAyB;QAC/B,OAAO,EAAE,CAAC;QACV,4EAA4E;QAC5E,gDAAgD;QAChD,aAAa,EAAE,KAAK;QACpB,UAAU,EAAE;YACV,SAAS,EAAE,UAAU;YACrB,UAAU,EAAE,IAAI;YAChB,KAAK,EAAE,KAAK;YACZ,OAAO,EAAE;gBACP,GAAG,CAAC,QAAQ,EAAE,SAAS,EAAE,KAAK,CAAC;gBAC/B,GAAG,CAAC,OAAO,EAAE,SAAS,EAAE,KAAK,CAAC;gBAC9B,GAAG,CAAC,OAAO,EAAE,SAAS,EAAE,KAAK,CAAC;gBAC9B,GAAG,CAAC,UAAU,EAAE,SAAS,EAAE,KAAK,CAAC;gBACjC,GAAG,CAAC,kBAAkB,EAAE,YAAY,EAAE,KAAK,CAAC;gBAC5C,GAAG,CAAC,SAAS,EAAE,YAAY,EAAE,KAAK,CAAC;gBACnC,GAAG,CAAC,aAAa,EAAE,YAAY,EAAE,IAAI,CAAC;gBACtC,GAAG,CAAC,eAAe,EAAE,YAAY,EAAE,IAAI,CAAC;gBACxC,GAAG,CAAC,YAAY,EAAE,YAAY,EAAE,IAAI,CAAC;gBACrC,GAAG,CAAC,2BAA2B,EAAE,YAAY,EAAE,IAAI,CAAC;gBACpD,GAAG,CAAC,aAAa,EAAE,SAAS,EAAE,IAAI,CAAC;aACpC;YACD,IAAI,EAAE,EAAE;YACR,cAAc,EAAE,EAAE;SACnB;KACF;IACD,+EAA+E;IAC/E,kEAAkE;IAClE,KAAK,SAAS,CAAC;QACb,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,sBAAsB,EAAE,CAAC;QAC3D,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC,CACF,CAAC;IAEF,EAAE,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC;AAC9B,CAAC"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Group-atomic admission core for the sync ingress data-apply seam.
|
|
3
|
+
*
|
|
4
|
+
* Centralizes the docs/sync.md § Transactional Integrity During Sync write
|
|
5
|
+
* ordering — **data first → metadata second → abort with no metadata** — that
|
|
6
|
+
* every ingress modality must uphold:
|
|
7
|
+
* - wire `applyChanges` (change-applicator.ts) → `admitGroup`
|
|
8
|
+
* - non-streaming `applySnapshot` (snapshot.ts) → `admitGroup`
|
|
9
|
+
* - each streaming flush in `applySnapshotStream` (snapshot-stream.ts)
|
|
10
|
+
* → `applyDataToStore`
|
|
11
|
+
*
|
|
12
|
+
* Streaming keeps its own checkpoint-based consistency model (interleaved
|
|
13
|
+
* metadata/data flushes, resume on a saved checkpoint) and so reuses only the
|
|
14
|
+
* `applyDataToStore` seam — for the same `status:'error'` emission on a
|
|
15
|
+
* whole-batch flush throw — rather than the full `admitGroup` unit.
|
|
16
|
+
*/
|
|
17
|
+
import type { HLC } from '../clock/hlc.js';
|
|
18
|
+
import type { ApplyToStoreOptions, DataChangeToApply, SchemaChangeToApply } from './protocol.js';
|
|
19
|
+
import type { SyncContext } from './sync-context.js';
|
|
20
|
+
/**
|
|
21
|
+
* Data-first half of the admission invariant: apply this unit's data + schema to
|
|
22
|
+
* the store, emit `status:'error'` and rethrow on a whole-batch throw, surface any
|
|
23
|
+
* reported assertion violations, then abort (throw) on any per-change
|
|
24
|
+
* `ApplyToStoreResult.errors` — all BEFORE the caller commits any CRDT metadata.
|
|
25
|
+
* No-op when there is no `applyToStore` callback or nothing to apply.
|
|
26
|
+
*
|
|
27
|
+
* The two failure shapes are mutually exclusive (a throw never reaches
|
|
28
|
+
* `throwIfApplyErrors`), so `status:'error'` is emitted at most once. The
|
|
29
|
+
* violation emit does not touch sync-state, so it cannot affect that invariant.
|
|
30
|
+
*
|
|
31
|
+
* Assertion violations are emitted BEFORE the per-change abort gate. Whenever the
|
|
32
|
+
* seam reports a violation it ran in REPORT mode and the batch already COMMITTED —
|
|
33
|
+
* the violating row's data + derived effects (MV deltas, watch capture) are
|
|
34
|
+
* durably in storage. A later `throwIfApplyErrors` throw blocks only the CRDT-
|
|
35
|
+
* metadata commit; it does NOT roll back those storage writes. So a reported
|
|
36
|
+
* violation is a fact about already-committed data and must be surfaced even when
|
|
37
|
+
* an unrelated per-change error aborts the batch in the same invocation — the two
|
|
38
|
+
* facts are orthogonal: "B's data landed and broke a local invariant" and "the
|
|
39
|
+
* batch can't fully admit because A failed and will retry" are both true.
|
|
40
|
+
*
|
|
41
|
+
* Emitting before the abort closes a permanent-notification-loss gap: were the
|
|
42
|
+
* emit on the success branch only, a mixed batch (per-change error + reported
|
|
43
|
+
* violation) would throw past it, drop the event, and on retry the value-identical
|
|
44
|
+
* row is suppressed → empty seam batch → the assertion is never re-evaluated →
|
|
45
|
+
* the host is never notified about durably-committed violating data. That same
|
|
46
|
+
* value-identical suppression now correctly prevents DOUBLE-notification on retry
|
|
47
|
+
* (B is suppressed, so no second event fires), matching the idempotent-re-apply
|
|
48
|
+
* guarantee. On the pure-success path (no per-change errors) behavior is unchanged.
|
|
49
|
+
*/
|
|
50
|
+
export declare function applyDataToStore(ctx: SyncContext, dataChanges: DataChangeToApply[], schemaChanges: SchemaChangeToApply[], options: ApplyToStoreOptions): Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
* One group-atomic admission unit: data + schema to apply, the CRDT metadata to
|
|
53
|
+
* commit only once that data landed, and the local HLC clock watermark to merge
|
|
54
|
+
* on full-unit success.
|
|
55
|
+
*
|
|
56
|
+
* A "group" is one *admission unit*, not necessarily one source transaction — the
|
|
57
|
+
* wire path admits the whole resolved `ChangeSet[]` as a single all-or-nothing
|
|
58
|
+
* unit (the single per-peer `lastSyncHLC` watermark cannot express a partial
|
|
59
|
+
* commit), and each snapshot path admits its wholesale load as one unit.
|
|
60
|
+
*/
|
|
61
|
+
export interface AdmissionGroup {
|
|
62
|
+
readonly dataChanges: DataChangeToApply[];
|
|
63
|
+
readonly schemaChanges: SchemaChangeToApply[];
|
|
64
|
+
readonly applyOptions: ApplyToStoreOptions;
|
|
65
|
+
/** Commit this unit's CRDT metadata. Runs ONLY after the data write landed. */
|
|
66
|
+
readonly commitMetadata: () => Promise<void>;
|
|
67
|
+
/**
|
|
68
|
+
* Local HLC clock watermark to merge in on full-unit success. The per-PEER
|
|
69
|
+
* `lastSyncHLC` is the transport caller's concern (it differs per modality and
|
|
70
|
+
* transport) and is deliberately NOT advanced here.
|
|
71
|
+
*/
|
|
72
|
+
readonly watermarkHLC?: HLC;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Full group-atomic admission: data first → metadata second → advance the local
|
|
76
|
+
* clock watermark, aborting (with no metadata committed) on any data-apply
|
|
77
|
+
* failure. The realization of "apply a transaction group atomically, advance the
|
|
78
|
+
* watermark idempotently".
|
|
79
|
+
*/
|
|
80
|
+
export declare function admitGroup(ctx: SyncContext, group: AdmissionGroup): Promise<void>;
|
|
81
|
+
//# sourceMappingURL=admission.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admission.d.ts","sourceRoot":"","sources":["../../../src/sync/admission.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,KAAK,EACX,mBAAmB,EAEnB,iBAAiB,EACjB,mBAAmB,EACnB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGrD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAsB,gBAAgB,CACrC,GAAG,EAAE,WAAW,EAChB,WAAW,EAAE,iBAAiB,EAAE,EAChC,aAAa,EAAE,mBAAmB,EAAE,EACpC,OAAO,EAAE,mBAAmB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAgCf;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,WAAW,EAAE,iBAAiB,EAAE,CAAC;IAC1C,QAAQ,CAAC,aAAa,EAAE,mBAAmB,EAAE,CAAC;IAC9C,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,+EAA+E;IAC/E,QAAQ,CAAC,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,GAAG,CAAC;CAC5B;AAED;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,GAAG,EAAE,WAAW,EAAE,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAOvF"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Group-atomic admission core for the sync ingress data-apply seam.
|
|
3
|
+
*
|
|
4
|
+
* Centralizes the docs/sync.md § Transactional Integrity During Sync write
|
|
5
|
+
* ordering — **data first → metadata second → abort with no metadata** — that
|
|
6
|
+
* every ingress modality must uphold:
|
|
7
|
+
* - wire `applyChanges` (change-applicator.ts) → `admitGroup`
|
|
8
|
+
* - non-streaming `applySnapshot` (snapshot.ts) → `admitGroup`
|
|
9
|
+
* - each streaming flush in `applySnapshotStream` (snapshot-stream.ts)
|
|
10
|
+
* → `applyDataToStore`
|
|
11
|
+
*
|
|
12
|
+
* Streaming keeps its own checkpoint-based consistency model (interleaved
|
|
13
|
+
* metadata/data flushes, resume on a saved checkpoint) and so reuses only the
|
|
14
|
+
* `applyDataToStore` seam — for the same `status:'error'` emission on a
|
|
15
|
+
* whole-batch flush throw — rather than the full `admitGroup` unit.
|
|
16
|
+
*/
|
|
17
|
+
import { persistHLCState, throwIfApplyErrors, toError } from './sync-context.js';
|
|
18
|
+
/**
|
|
19
|
+
* Data-first half of the admission invariant: apply this unit's data + schema to
|
|
20
|
+
* the store, emit `status:'error'` and rethrow on a whole-batch throw, surface any
|
|
21
|
+
* reported assertion violations, then abort (throw) on any per-change
|
|
22
|
+
* `ApplyToStoreResult.errors` — all BEFORE the caller commits any CRDT metadata.
|
|
23
|
+
* No-op when there is no `applyToStore` callback or nothing to apply.
|
|
24
|
+
*
|
|
25
|
+
* The two failure shapes are mutually exclusive (a throw never reaches
|
|
26
|
+
* `throwIfApplyErrors`), so `status:'error'` is emitted at most once. The
|
|
27
|
+
* violation emit does not touch sync-state, so it cannot affect that invariant.
|
|
28
|
+
*
|
|
29
|
+
* Assertion violations are emitted BEFORE the per-change abort gate. Whenever the
|
|
30
|
+
* seam reports a violation it ran in REPORT mode and the batch already COMMITTED —
|
|
31
|
+
* the violating row's data + derived effects (MV deltas, watch capture) are
|
|
32
|
+
* durably in storage. A later `throwIfApplyErrors` throw blocks only the CRDT-
|
|
33
|
+
* metadata commit; it does NOT roll back those storage writes. So a reported
|
|
34
|
+
* violation is a fact about already-committed data and must be surfaced even when
|
|
35
|
+
* an unrelated per-change error aborts the batch in the same invocation — the two
|
|
36
|
+
* facts are orthogonal: "B's data landed and broke a local invariant" and "the
|
|
37
|
+
* batch can't fully admit because A failed and will retry" are both true.
|
|
38
|
+
*
|
|
39
|
+
* Emitting before the abort closes a permanent-notification-loss gap: were the
|
|
40
|
+
* emit on the success branch only, a mixed batch (per-change error + reported
|
|
41
|
+
* violation) would throw past it, drop the event, and on retry the value-identical
|
|
42
|
+
* row is suppressed → empty seam batch → the assertion is never re-evaluated →
|
|
43
|
+
* the host is never notified about durably-committed violating data. That same
|
|
44
|
+
* value-identical suppression now correctly prevents DOUBLE-notification on retry
|
|
45
|
+
* (B is suppressed, so no second event fires), matching the idempotent-re-apply
|
|
46
|
+
* guarantee. On the pure-success path (no per-change errors) behavior is unchanged.
|
|
47
|
+
*/
|
|
48
|
+
export async function applyDataToStore(ctx, dataChanges, schemaChanges, options) {
|
|
49
|
+
if (!ctx.applyToStore || (dataChanges.length === 0 && schemaChanges.length === 0))
|
|
50
|
+
return;
|
|
51
|
+
let result;
|
|
52
|
+
try {
|
|
53
|
+
result = await ctx.applyToStore(dataChanges, schemaChanges, options);
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
// Emit error state so UI can react. CRDT metadata is NOT committed,
|
|
57
|
+
// allowing the same changes to be re-resolved on the next sync attempt.
|
|
58
|
+
ctx.syncEvents.emitSyncStateChange({ status: 'error', error: toError(error) });
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
// Surface any local assertion violations the seam reported. A reported
|
|
62
|
+
// violation means the report-mode seam COMMITTED the violating row (data +
|
|
63
|
+
// derived effects durable); we notify the host once per violated assertion.
|
|
64
|
+
// This runs BEFORE the abort gate below so a co-occurring per-change error
|
|
65
|
+
// cannot drop the notification about data that is already committed.
|
|
66
|
+
if (result.assertionViolations) {
|
|
67
|
+
for (const violation of result.assertionViolations) {
|
|
68
|
+
ctx.syncEvents.emitAssertionViolation({
|
|
69
|
+
assertion: violation.assertion,
|
|
70
|
+
samples: violation.samples,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Per-change storage failures (the adapter collects rather than throws) abort
|
|
75
|
+
// the apply identically to a whole-batch throw: emit error + throw, with no
|
|
76
|
+
// metadata committed, so the whole batch re-resolves and re-applies
|
|
77
|
+
// idempotently on the next sync. See throwIfApplyErrors / docs/sync.md.
|
|
78
|
+
throwIfApplyErrors(ctx, result);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Full group-atomic admission: data first → metadata second → advance the local
|
|
82
|
+
* clock watermark, aborting (with no metadata committed) on any data-apply
|
|
83
|
+
* failure. The realization of "apply a transaction group atomically, advance the
|
|
84
|
+
* watermark idempotently".
|
|
85
|
+
*/
|
|
86
|
+
export async function admitGroup(ctx, group) {
|
|
87
|
+
await applyDataToStore(ctx, group.dataChanges, group.schemaChanges, group.applyOptions);
|
|
88
|
+
await group.commitMetadata(); // metadata SECOND
|
|
89
|
+
if (group.watermarkHLC) {
|
|
90
|
+
ctx.hlcManager.receive(group.watermarkHLC); // monotonic merge, idempotent
|
|
91
|
+
await persistHLCState(ctx);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=admission.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admission.js","sourceRoot":"","sources":["../../../src/sync/admission.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAUH,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEjF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACrC,GAAgB,EAChB,WAAgC,EAChC,aAAoC,EACpC,OAA4B;IAE5B,IAAI,CAAC,GAAG,CAAC,YAAY,IAAI,CAAC,WAAW,CAAC,MAAM,KAAK,CAAC,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC,CAAC;QAAE,OAAO;IAE1F,IAAI,MAA0B,CAAC;IAC/B,IAAI,CAAC;QACJ,MAAM,GAAG,MAAM,GAAG,CAAC,YAAY,CAAC,WAAW,EAAE,aAAa,EAAE,OAAO,CAAC,CAAC;IACtE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,oEAAoE;QACpE,wEAAwE;QACxE,GAAG,CAAC,UAAU,CAAC,mBAAmB,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC/E,MAAM,KAAK,CAAC;IACb,CAAC;IAED,uEAAuE;IACvE,2EAA2E;IAC3E,4EAA4E;IAC5E,2EAA2E;IAC3E,qEAAqE;IACrE,IAAI,MAAM,CAAC,mBAAmB,EAAE,CAAC;QAChC,KAAK,MAAM,SAAS,IAAI,MAAM,CAAC,mBAAmB,EAAE,CAAC;YACpD,GAAG,CAAC,UAAU,CAAC,sBAAsB,CAAC;gBACrC,SAAS,EAAE,SAAS,CAAC,SAAS;gBAC9B,OAAO,EAAE,SAAS,CAAC,OAAO;aAC1B,CAAC,CAAC;QACJ,CAAC;IACF,CAAC;IAED,8EAA8E;IAC9E,4EAA4E;IAC5E,oEAAoE;IACpE,wEAAwE;IACxE,kBAAkB,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;AACjC,CAAC;AA0BD;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,GAAgB,EAAE,KAAqB;IACvE,MAAM,gBAAgB,CAAC,GAAG,EAAE,KAAK,CAAC,WAAW,EAAE,KAAK,CAAC,aAAa,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC;IACxF,MAAM,KAAK,CAAC,cAAc,EAAE,CAAC,CAAoB,kBAAkB;IACnE,IAAI,KAAK,CAAC,YAAY,EAAE,CAAC;QACxB,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAI,8BAA8B;QAC7E,MAAM,eAAe,CAAC,GAAG,CAAC,CAAC;IAC5B,CAAC;AACF,CAAC"}
|