@sqlite-sync/core 0.12.3 → 0.13.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/dist/{reset-state-t0tTGqoh.js → crdt-sync-producer-BY-WobgO.js} +55 -241
- package/dist/crdt-sync-producer-BY-WobgO.js.map +1 -0
- package/dist/{crdt-sync-remote-source-CPXzmSIS.js → crdt-sync-remote-source-yeTEkOjH.js} +344 -12
- package/dist/crdt-sync-remote-source-yeTEkOjH.js.map +1 -0
- package/dist/{crdt-table-schema-BRm_WdcW.js → crdt-table-schema-D6Yf61Ex.js} +37 -13
- package/dist/crdt-table-schema-D6Yf61Ex.js.map +1 -0
- package/dist/{index-BHsKLqcK.d.ts → index-CH4lf3iX.d.ts} +166 -72
- package/dist/index.common-C8W9VKSY.js +276 -0
- package/dist/index.common-C8W9VKSY.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +20 -677
- package/dist/index.js.map +1 -1
- package/dist/index.native.d.ts +2 -0
- package/dist/index.native.js +9 -0
- package/dist/internal.d.ts +1 -1
- package/dist/internal.js +2 -2
- package/dist/local.d.ts +48 -0
- package/dist/local.js +255 -0
- package/dist/local.js.map +1 -0
- package/dist/{make-crdt-table-C6l0F-o_.js → make-crdt-table-DYCXOYB7.js} +13 -14
- package/dist/make-crdt-table-DYCXOYB7.js.map +1 -0
- package/dist/reset-state-CPOi_45B.js +220 -0
- package/dist/reset-state-CPOi_45B.js.map +1 -0
- package/dist/server.d.ts +8 -3
- package/dist/server.js +2 -2
- package/dist/server.js.map +1 -1
- package/dist/sqlite-reactive-db-DLS1xRZp.js +296 -0
- package/dist/sqlite-reactive-db-DLS1xRZp.js.map +1 -0
- package/dist/synced-db-database-B5M7MrBD.js +81 -0
- package/dist/synced-db-database-B5M7MrBD.js.map +1 -0
- package/dist/{system-schema-Dvl_BLId.js → system-schema-CXf3FmxN.js} +142 -58
- package/dist/system-schema-CXf3FmxN.js.map +1 -0
- package/dist/worker.d.ts +2 -10
- package/dist/worker.js +11 -177
- package/dist/worker.js.map +1 -1
- package/dist/ws-remote-source-BbPWrHB6.js +161 -0
- package/dist/ws-remote-source-BbPWrHB6.js.map +1 -0
- package/dist/ws-remote-source-DFBhDc6Z.d.ts +12 -0
- package/package.json +9 -1
- package/dist/crdt-sync-remote-source-CPXzmSIS.js.map +0 -1
- package/dist/crdt-table-schema-BRm_WdcW.js.map +0 -1
- package/dist/make-crdt-table-C6l0F-o_.js.map +0 -1
- package/dist/reset-state-t0tTGqoh.js.map +0 -1
- package/dist/system-schema-Dvl_BLId.js.map +0 -1
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { c as
|
|
1
|
+
import { b as dummyKysely, h as SQLiteDbWrapper, m as createStoredValue, y as introspectDb } from "./system-schema-CXf3FmxN.js";
|
|
2
|
+
import { c as ensureSingletonExecution, n as isNoOpCrdtEventPayload, p as quoteId, s as createTypedEventTarget, t as CRDT_EVENT_NO_OP_PAYLOAD } from "./crdt-table-schema-D6Yf61Ex.js";
|
|
3
|
+
import { c as deserializeHLC, i as validateNewCrdtEvent, l as serializeHLC, n as createStateHashTracker, r as CrdtEventValidationError } from "./crdt-sync-remote-source-yeTEkOjH.js";
|
|
4
4
|
import { sql } from "kysely";
|
|
5
|
-
import initXxhash from "xxhash-wasm";
|
|
6
|
-
import sqlite3InitModule from "@sqlite.org/sqlite-wasm";
|
|
7
|
-
import { createStore, del, get, set } from "idb-keyval";
|
|
8
5
|
//#region src/export-import.ts
|
|
9
6
|
function createTableNameNormalizer(tablesConfig) {
|
|
10
7
|
const tablesByAlias = /* @__PURE__ */ new Map();
|
|
@@ -107,24 +104,6 @@ function createExportData({ reactiveDb, tablesConfig, schemaVersion }) {
|
|
|
107
104
|
};
|
|
108
105
|
}
|
|
109
106
|
//#endregion
|
|
110
|
-
//#region src/hash.ts
|
|
111
|
-
let loadPromise = null;
|
|
112
|
-
let api = null;
|
|
113
|
-
function ensureLoaded() {
|
|
114
|
-
if (!loadPromise) loadPromise = initXxhash().then((hasher) => {
|
|
115
|
-
api = hasher;
|
|
116
|
-
});
|
|
117
|
-
return loadPromise;
|
|
118
|
-
}
|
|
119
|
-
function h64(input, seed = 0n) {
|
|
120
|
-
if (!api) throw new Error("xxhash is not initialized; call xxhash.ensureLoaded() first");
|
|
121
|
-
return api.h64(input, seed);
|
|
122
|
-
}
|
|
123
|
-
const xxhash = {
|
|
124
|
-
ensureLoaded,
|
|
125
|
-
h64
|
|
126
|
-
};
|
|
127
|
-
//#endregion
|
|
128
107
|
//#region src/migrations/migrator.ts
|
|
129
108
|
const protectedColumns = ["id", "tombstone"];
|
|
130
109
|
function assertColumnNotProtected(column, operation) {
|
|
@@ -176,11 +155,6 @@ const migrationSteps = {
|
|
|
176
155
|
},
|
|
177
156
|
addColumn: ({ table, column, type, defaultValue, build = (e) => e }) => ({
|
|
178
157
|
sql: (db) => db.schema.alterTable(table).addColumn(column, type, (x) => build(x).defaultTo(defaultValue)),
|
|
179
|
-
eventTransformer: { [table]: (event) => {
|
|
180
|
-
if (event.type !== "item-created") return event;
|
|
181
|
-
event.payload[column] = defaultValue;
|
|
182
|
-
return event;
|
|
183
|
-
} },
|
|
184
158
|
updateLogMutations: [{
|
|
185
159
|
type: "add-column",
|
|
186
160
|
table,
|
|
@@ -384,25 +358,21 @@ const acceptedDataTypes = {
|
|
|
384
358
|
],
|
|
385
359
|
enum: ["TEXT"]
|
|
386
360
|
};
|
|
387
|
-
let sqliteModule = null;
|
|
388
361
|
/**
|
|
389
362
|
* Verify that replaying the full migration history on a fresh database produces
|
|
390
|
-
* exactly the declared table schema.
|
|
391
|
-
*
|
|
392
|
-
* `t.table()` declarations.
|
|
363
|
+
* exactly the declared table schema. Applies all migrations to `driver`, which
|
|
364
|
+
* must be an empty throwaway database (it is closed afterwards), introspects the
|
|
365
|
+
* result, and diffs it against the `t.table()` declarations.
|
|
393
366
|
*
|
|
394
367
|
* Returns an empty array when migrations and schema agree. Intended for dev-time
|
|
395
368
|
* checks (`startDbWorker({ verifySchema: true })`) and tests:
|
|
396
369
|
* `expect(await verifySyncSchema(syncDbSchema)).toEqual([])`.
|
|
397
370
|
*/
|
|
398
|
-
|
|
399
|
-
if (!sqliteModule) sqliteModule = await sqlite3InitModule();
|
|
400
|
-
const sqlite3 = sqliteModule;
|
|
371
|
+
function verifySyncSchemaWithDriver(schema, driver) {
|
|
401
372
|
const db = new SQLiteDbWrapper({
|
|
402
|
-
|
|
373
|
+
driver,
|
|
403
374
|
logger: () => {},
|
|
404
|
-
loggerPrefix: "schema-verify"
|
|
405
|
-
sqlite3
|
|
375
|
+
loggerPrefix: "schema-verify"
|
|
406
376
|
});
|
|
407
377
|
try {
|
|
408
378
|
const migrator = createMigrator({
|
|
@@ -538,8 +508,9 @@ function formatExpected(meta) {
|
|
|
538
508
|
}
|
|
539
509
|
//#endregion
|
|
540
510
|
//#region src/sqlite-crdt/apply-crdt-event.ts
|
|
541
|
-
const createSQLiteCrdtApplyFunction = ({ db, dbConfig }) => {
|
|
511
|
+
const createSQLiteCrdtApplyFunction = ({ db, dbConfig, getStateHash }) => {
|
|
542
512
|
return createCrdtApplyFunction({
|
|
513
|
+
getStateHash,
|
|
543
514
|
getCrdtUpdateLog(opts) {
|
|
544
515
|
const [metaRow] = db.executePrepared("get-item-crdt-meta", {
|
|
545
516
|
item_id: opts.itemId,
|
|
@@ -583,7 +554,7 @@ const createSQLiteCrdtApplyFunction = ({ db, dbConfig }) => {
|
|
|
583
554
|
}
|
|
584
555
|
});
|
|
585
556
|
};
|
|
586
|
-
function createCrdtApplyFunction({ getCrdtUpdateLog, insertItem, insertCrdtUpdateLog, updateItem, updateCrdtUpdateLog }) {
|
|
557
|
+
function createCrdtApplyFunction({ getStateHash, getCrdtUpdateLog, insertItem, insertCrdtUpdateLog, updateItem, updateCrdtUpdateLog }) {
|
|
587
558
|
const applyItemCreated = ({ event }) => {
|
|
588
559
|
const eventPayload = JSON.parse(event.payload);
|
|
589
560
|
eventPayload.tombstone = 0;
|
|
@@ -598,25 +569,29 @@ function createCrdtApplyFunction({ getCrdtUpdateLog, insertItem, insertCrdtUpdat
|
|
|
598
569
|
itemId: event.item_id,
|
|
599
570
|
payload: JSON.stringify(newUpdateLog)
|
|
600
571
|
});
|
|
572
|
+
getStateHash?.()?.addItem(event.dataset, event.item_id, newUpdateLog);
|
|
601
573
|
};
|
|
602
574
|
const applyItemUpdated = ({ event, meta }) => {
|
|
603
575
|
if (!meta) throw new Error(`Item ${event.item_id} in dataset ${event.dataset} not found`);
|
|
604
576
|
const eventPayload = event.type === "item-deleted" ? { tombstone: 1 } : JSON.parse(event.payload);
|
|
605
577
|
if (event.type === "item-created") eventPayload.tombstone = 0;
|
|
606
578
|
const updatePayload = {};
|
|
607
|
-
|
|
579
|
+
const replacedCells = [];
|
|
580
|
+
const previousCreateHlc = meta.id;
|
|
608
581
|
for (const [key, value] of Object.entries(eventPayload)) {
|
|
609
582
|
if (key === "id") continue;
|
|
610
583
|
const lastUpdateTimestamp = meta[key];
|
|
611
584
|
const currentUpdateTimestamp = event.timestamp;
|
|
612
585
|
if (!lastUpdateTimestamp || !currentUpdateTimestamp || currentUpdateTimestamp > lastUpdateTimestamp) {
|
|
613
586
|
updatePayload[key] = value;
|
|
587
|
+
replacedCells.push([key, lastUpdateTimestamp]);
|
|
614
588
|
meta[key] = currentUpdateTimestamp;
|
|
615
|
-
hasUpdates = true;
|
|
616
589
|
}
|
|
617
590
|
}
|
|
618
|
-
|
|
619
|
-
|
|
591
|
+
const isLatestCreate = event.type === "item-created" && (!previousCreateHlc || event.timestamp > previousCreateHlc);
|
|
592
|
+
if (isLatestCreate) meta.id = event.timestamp;
|
|
593
|
+
if (replacedCells.length === 0 && !isLatestCreate) return;
|
|
594
|
+
if (replacedCells.length > 0) updateItem({
|
|
620
595
|
dataset: event.dataset,
|
|
621
596
|
itemId: event.item_id,
|
|
622
597
|
payload: updatePayload
|
|
@@ -626,6 +601,7 @@ function createCrdtApplyFunction({ getCrdtUpdateLog, insertItem, insertCrdtUpdat
|
|
|
626
601
|
itemId: event.item_id,
|
|
627
602
|
payload: JSON.stringify(meta)
|
|
628
603
|
});
|
|
604
|
+
getStateHash?.()?.updateItem(event.dataset, event.item_id, meta, previousCreateHlc, replacedCells);
|
|
629
605
|
};
|
|
630
606
|
return (event) => {
|
|
631
607
|
if (isNoOpCrdtEventPayload(event.payload)) return;
|
|
@@ -649,33 +625,6 @@ function createCrdtApplyFunction({ getCrdtUpdateLog, insertItem, insertCrdtUpdat
|
|
|
649
625
|
};
|
|
650
626
|
}
|
|
651
627
|
//#endregion
|
|
652
|
-
//#region src/sqlite-crdt/event-consistency.ts
|
|
653
|
-
const MASK_128 = (1n << 128n) - 1n;
|
|
654
|
-
const HEX_128_PATTERN = /^[0-9a-f]{32}$/;
|
|
655
|
-
function createEventHlcAccumulator(initialValue) {
|
|
656
|
-
let current = parseHex128(initialValue);
|
|
657
|
-
return {
|
|
658
|
-
add(timestamp) {
|
|
659
|
-
current = current + hash128BigInt(timestamp) & MASK_128;
|
|
660
|
-
},
|
|
661
|
-
get current() {
|
|
662
|
-
return toHex128(current);
|
|
663
|
-
}
|
|
664
|
-
};
|
|
665
|
-
}
|
|
666
|
-
function parseHex128(value) {
|
|
667
|
-
if (value === "") return 0n;
|
|
668
|
-
const normalized = value.toLowerCase();
|
|
669
|
-
if (!HEX_128_PATTERN.test(normalized)) throw new Error(`Invalid event HLC accumulator value: ${value}`);
|
|
670
|
-
return BigInt(`0x${normalized}`);
|
|
671
|
-
}
|
|
672
|
-
function toHex128(value) {
|
|
673
|
-
return value.toString(16).padStart(32, "0");
|
|
674
|
-
}
|
|
675
|
-
function hash128BigInt(value) {
|
|
676
|
-
return xxhash.h64(value, 0n) | xxhash.h64(value, 1n) << 64n;
|
|
677
|
-
}
|
|
678
|
-
//#endregion
|
|
679
628
|
//#region src/sqlite-crdt/crdt-storage.ts
|
|
680
629
|
function createCrdtStorage(storage) {
|
|
681
630
|
let localSyncId = storage.initialLocalSyncId;
|
|
@@ -848,7 +797,9 @@ function createCrdtStorage(storage) {
|
|
|
848
797
|
status: "pending"
|
|
849
798
|
}));
|
|
850
799
|
persistEvents(tx, persistedEvents);
|
|
851
|
-
|
|
800
|
+
withStateHash(() => {
|
|
801
|
+
for (const persistedEvent of persistedEvents) applyCrdtEvent(persistedEvent);
|
|
802
|
+
});
|
|
852
803
|
});
|
|
853
804
|
processEnqueuedEvents();
|
|
854
805
|
};
|
|
@@ -924,7 +875,7 @@ where "dataset" = ? and "item_id" = ? and "payload" <> ${noOpPayloadSqlLiteral}`
|
|
|
924
875
|
persistEvents(tx, [persistedEvent]);
|
|
925
876
|
applyCrdtEvent(persistedEvent);
|
|
926
877
|
};
|
|
927
|
-
const applyOwnIntentsFromTransaction = (tx, intents) => {
|
|
878
|
+
const applyOwnIntentsFromTransaction = (tx, intents) => withStateHash(() => {
|
|
928
879
|
let appliedEvents = 0;
|
|
929
880
|
for (const intent of intents) {
|
|
930
881
|
if (intent.type === "item-updated") {
|
|
@@ -940,12 +891,12 @@ where "dataset" = ? and "item_id" = ? and "payload" <> ${noOpPayloadSqlLiteral}`
|
|
|
940
891
|
appliedEvents++;
|
|
941
892
|
}
|
|
942
893
|
return { appliedEvents };
|
|
943
|
-
};
|
|
894
|
+
});
|
|
944
895
|
const dispatchEventsApplied = (previousSequentialSyncId) => {
|
|
945
896
|
if (sequentialSyncId <= previousSequentialSyncId) return;
|
|
946
897
|
eventTarget.dispatchEvent("events-applied", {
|
|
947
898
|
syncId: sequentialSyncId,
|
|
948
|
-
|
|
899
|
+
stateHash: sequentialSyncId === localSyncId ? getStateHash() : null
|
|
949
900
|
});
|
|
950
901
|
};
|
|
951
902
|
const publishProcessedBatch = ({ previousSequentialSyncId, nextSequentialSyncId, appliedEvents }) => {
|
|
@@ -988,34 +939,21 @@ where "dataset" = ? and "item_id" = ? and "payload" <> ${noOpPayloadSqlLiteral}`
|
|
|
988
939
|
limit: 1
|
|
989
940
|
}).events.length === 0;
|
|
990
941
|
};
|
|
991
|
-
const
|
|
942
|
+
const stateHash = createStateHashTracker({
|
|
992
943
|
db,
|
|
993
|
-
|
|
944
|
+
updateLogTable: storage.dbConfig.updateLogTable.fullIdentifier,
|
|
945
|
+
schema: storage.schema,
|
|
946
|
+
getSchemaVersion: () => storage.migrator.currentSchemaVersion,
|
|
947
|
+
store: storage.stateHash ?? null
|
|
994
948
|
});
|
|
995
|
-
const
|
|
996
|
-
const
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
let afterSyncId = 0;
|
|
1004
|
-
for (;;) {
|
|
1005
|
-
const rows = db.executePrepared("get-applied-event-timestamps", {
|
|
1006
|
-
status: "applied",
|
|
1007
|
-
afterSyncId,
|
|
1008
|
-
limit: batchSize
|
|
1009
|
-
}, (db, params) => db.selectFrom(crdtEventsTable).select(["sync_id", "timestamp"]).where("status", "=", params("status")).where("sync_id", ">", params("afterSyncId")).orderBy("sync_id", "asc").limit(params("limit")), { loggerLevel: "system" });
|
|
1010
|
-
if (rows.length === 0) break;
|
|
1011
|
-
for (const row of rows) {
|
|
1012
|
-
eventHlcAccumulator.add(row.timestamp);
|
|
1013
|
-
afterSyncId = row.sync_id;
|
|
1014
|
-
}
|
|
1015
|
-
if (rows.length < batchSize) break;
|
|
1016
|
-
}
|
|
1017
|
-
persistEventHlcAccumulator();
|
|
1018
|
-
};
|
|
949
|
+
const withStateHash = stateHash.track;
|
|
950
|
+
const getStateHash = stateHash.read;
|
|
951
|
+
const applyCrdtEventWithoutStateHash = createSQLiteCrdtApplyFunction({
|
|
952
|
+
db,
|
|
953
|
+
dbConfig: storage.dbConfig,
|
|
954
|
+
getStateHash: () => stateHash.active
|
|
955
|
+
});
|
|
956
|
+
const applyCrdtEvent = (event) => withStateHash(() => applyCrdtEventWithoutStateHash(event));
|
|
1019
957
|
const getFirstAcceptedSyncIdsByTimestamp = (tx, events) => {
|
|
1020
958
|
const timestamps = Array.from(new Set(events.map((event) => event.timestamp)));
|
|
1021
959
|
if (timestamps.length === 0) return /* @__PURE__ */ new Map();
|
|
@@ -1135,18 +1073,18 @@ where "dataset" = ? and "item_id" = ? and "payload" <> ${noOpPayloadSqlLiteral}`
|
|
|
1135
1073
|
db.executeTransaction((tx) => {
|
|
1136
1074
|
const firstAcceptedSyncIdByTimestamp = getFirstAcceptedSyncIdsByTimestamp(tx, events.filter((event) => event.origin !== "own-applied"));
|
|
1137
1075
|
const statusOnlyEvents = [];
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1076
|
+
withStateHash(() => {
|
|
1077
|
+
for (const event of events) {
|
|
1078
|
+
processPersistedEvent(tx, event, firstAcceptedSyncIdByTimestamp, statusOnlyEvents);
|
|
1079
|
+
if (event.status === "applied") {
|
|
1080
|
+
const firstAcceptedSyncId = firstAcceptedSyncIdByTimestamp.get(event.timestamp);
|
|
1081
|
+
if (firstAcceptedSyncId === void 0 || event.sync_id < firstAcceptedSyncId) firstAcceptedSyncIdByTimestamp.set(event.timestamp, event.sync_id);
|
|
1082
|
+
if (storage.onEventApplied) appliedEventsToNotify.push(event);
|
|
1083
|
+
}
|
|
1084
|
+
if (event.status === "failed" && event.origin === "remote") failedRemoteSyncIds.push(event.sync_id);
|
|
1145
1085
|
}
|
|
1146
|
-
|
|
1147
|
-
}
|
|
1086
|
+
});
|
|
1148
1087
|
updateEventStatuses(tx, statusOnlyEvents);
|
|
1149
|
-
persistEventHlcAccumulator();
|
|
1150
1088
|
});
|
|
1151
1089
|
publishProcessedBatch({
|
|
1152
1090
|
previousSequentialSyncId,
|
|
@@ -1156,7 +1094,7 @@ where "dataset" = ? and "item_id" = ? and "payload" <> ${noOpPayloadSqlLiteral}`
|
|
|
1156
1094
|
for (const syncId of failedRemoteSyncIds) eventTarget.dispatchEvent("remote-event-apply-failed", { syncId });
|
|
1157
1095
|
}
|
|
1158
1096
|
});
|
|
1159
|
-
|
|
1097
|
+
if (storage.stateHash) db.executeTransaction(stateHash.initialize);
|
|
1160
1098
|
processEnqueuedEvents();
|
|
1161
1099
|
return {
|
|
1162
1100
|
getEventsBatch,
|
|
@@ -1165,7 +1103,7 @@ where "dataset" = ? and "item_id" = ? and "payload" <> ${noOpPayloadSqlLiteral}`
|
|
|
1165
1103
|
applyOwnEvents,
|
|
1166
1104
|
applyOwnSnapshot,
|
|
1167
1105
|
checkIsQuiescent,
|
|
1168
|
-
|
|
1106
|
+
getStateHash,
|
|
1169
1107
|
addEventListener: eventTarget.addEventListener,
|
|
1170
1108
|
removeEventListener: eventTarget.removeEventListener,
|
|
1171
1109
|
internal: {
|
|
@@ -1181,135 +1119,11 @@ const createCrdtSyncProducer = ({ storage, broadcastEvents }) => {
|
|
|
1181
1119
|
storage.addEventListener("events-applied", (event) => {
|
|
1182
1120
|
broadcastEvents({
|
|
1183
1121
|
newSyncId: event.payload.syncId,
|
|
1184
|
-
|
|
1122
|
+
stateHash: event.payload.stateHash
|
|
1185
1123
|
});
|
|
1186
1124
|
});
|
|
1187
1125
|
};
|
|
1188
1126
|
//#endregion
|
|
1189
|
-
|
|
1190
|
-
const syncDbWorkerLockName = "sync-db-worker-lock";
|
|
1191
|
-
const syncDbClientLockName = "sync-db-client-lock";
|
|
1192
|
-
const broadcastChannelNames = {
|
|
1193
|
-
requests: "sync-db-worker-requests",
|
|
1194
|
-
responses: "sync-db-worker-responses"
|
|
1195
|
-
};
|
|
1196
|
-
const createBroadcastChannels = (prefix) => {
|
|
1197
|
-
return {
|
|
1198
|
-
requests: new TypedBroadcastChannel(`${prefix}-${broadcastChannelNames.requests}`),
|
|
1199
|
-
responses: new TypedBroadcastChannel(`${prefix}-${broadcastChannelNames.responses}`)
|
|
1200
|
-
};
|
|
1201
|
-
};
|
|
1202
|
-
function isWorkerInitMessage(message) {
|
|
1203
|
-
return typeof message === "object" && message !== null && "type" in message && message.type === "init";
|
|
1204
|
-
}
|
|
1205
|
-
function isWorkerRequestMessage(message) {
|
|
1206
|
-
return typeof message === "object" && message !== null && "type" in message && message.type === "request";
|
|
1207
|
-
}
|
|
1208
|
-
function isWorkerResponseMessage(message) {
|
|
1209
|
-
return typeof message === "object" && message !== null && "type" in message && "requestId" in message && "data" in message;
|
|
1210
|
-
}
|
|
1211
|
-
function isWorkerErrorResponseMessage(message) {
|
|
1212
|
-
return typeof message === "object" && message !== null && "type" in message && message.type === "error-response";
|
|
1213
|
-
}
|
|
1214
|
-
function isWorkerNotificationMessage(message) {
|
|
1215
|
-
return typeof message === "object" && message !== null && "notificationType" in message && !!message.notificationType;
|
|
1216
|
-
}
|
|
1217
|
-
//#endregion
|
|
1218
|
-
//#region src/worker-db/replica-lock.ts
|
|
1219
|
-
const replicaLockName = (dbId) => `sync-db-replica-lock-${dbId}`;
|
|
1220
|
-
function acquireReplicaLock({ dbId }) {
|
|
1221
|
-
return acquireLock(replicaLockName(dbId), { mode: "shared" });
|
|
1222
|
-
}
|
|
1223
|
-
function runWithNoActiveReplicas({ dbId, run }) {
|
|
1224
|
-
return navigator.locks.request(replicaLockName(dbId), {
|
|
1225
|
-
mode: "exclusive",
|
|
1226
|
-
ifAvailable: true
|
|
1227
|
-
}, async (lock) => {
|
|
1228
|
-
if (!lock) return false;
|
|
1229
|
-
await run();
|
|
1230
|
-
return true;
|
|
1231
|
-
});
|
|
1232
|
-
}
|
|
1233
|
-
//#endregion
|
|
1234
|
-
//#region src/worker-db/reset-state.ts
|
|
1235
|
-
/** Default IndexedDB-backed reset store (idb-keyval over a dedicated database). */
|
|
1236
|
-
function createIdbResetStore() {
|
|
1237
|
-
const store = createStore("sqlite-sync", "kv");
|
|
1238
|
-
return {
|
|
1239
|
-
get: (key) => get(key, store),
|
|
1240
|
-
set: (key, value) => set(key, value, store),
|
|
1241
|
-
delete: (key) => del(key, store)
|
|
1242
|
-
};
|
|
1243
|
-
}
|
|
1244
|
-
/**
|
|
1245
|
-
* A clean reset is a recovery action for a de-sync detected now. If the reload
|
|
1246
|
-
* never happens (broadcast lost, tab crashed mid-reload, browser killed the page),
|
|
1247
|
-
* the request must not fire on an arbitrary later cold start and silently wipe
|
|
1248
|
-
* local-only writes accumulated since.
|
|
1249
|
-
*/
|
|
1250
|
-
const RESET_REQUEST_TTL_MS = 6e5;
|
|
1251
|
-
const resetRequestKey = (dbId) => `sqlite-sync-reset-request-${dbId}`;
|
|
1252
|
-
const resetAppliedKey = (dbId) => `sqlite-sync-reset-applied-${dbId}`;
|
|
1253
|
-
/**
|
|
1254
|
-
* Worker-owned durable reset state. The reset decision must be owned by the
|
|
1255
|
-
* elected worker, not by tabs during `createSyncedDb` — otherwise a later
|
|
1256
|
-
* worker election could repeat an already-applied wipe.
|
|
1257
|
-
*/
|
|
1258
|
-
function createResetStateStore({ store, dbId, now = () => Date.now() }) {
|
|
1259
|
-
const requestKey = resetRequestKey(dbId);
|
|
1260
|
-
const appliedKey = resetAppliedKey(dbId);
|
|
1261
|
-
return {
|
|
1262
|
-
async writeResetRequest(epoch) {
|
|
1263
|
-
const request = {
|
|
1264
|
-
epoch,
|
|
1265
|
-
requestedAt: now()
|
|
1266
|
-
};
|
|
1267
|
-
await store.set(requestKey, request);
|
|
1268
|
-
return request;
|
|
1269
|
-
},
|
|
1270
|
-
/**
|
|
1271
|
-
* Read the pending reset request after winning the worker election.
|
|
1272
|
-
* Returns the request only when it has not been applied yet and is within
|
|
1273
|
-
* the TTL. Stale requests are deleted so they cannot fire on a later cold start.
|
|
1274
|
-
*/
|
|
1275
|
-
async resolvePendingReset() {
|
|
1276
|
-
const request = await store.get(requestKey);
|
|
1277
|
-
if (!request) return;
|
|
1278
|
-
if (now() - request.requestedAt > 6e5) {
|
|
1279
|
-
await store.delete(requestKey);
|
|
1280
|
-
return;
|
|
1281
|
-
}
|
|
1282
|
-
const appliedEpoch = await store.get(appliedKey);
|
|
1283
|
-
if (request.epoch === appliedEpoch) return;
|
|
1284
|
-
return request;
|
|
1285
|
-
},
|
|
1286
|
-
/**
|
|
1287
|
-
* Record the epoch as applied. Must be called only after the worker has
|
|
1288
|
-
* successfully initialized with `clearOnInit: true`, so a failed init can
|
|
1289
|
-
* be retried by a later elected worker.
|
|
1290
|
-
*/
|
|
1291
|
-
async markResetApplied(epoch) {
|
|
1292
|
-
await store.set(appliedKey, epoch);
|
|
1293
|
-
}
|
|
1294
|
-
};
|
|
1295
|
-
}
|
|
1296
|
-
/**
|
|
1297
|
-
* Worker-side `requestReload` RPC implementation. For `clean: true` the reset
|
|
1298
|
-
* request is durably stored before broadcasting and before the RPC resolves,
|
|
1299
|
-
* so the epoch survives no matter which path triggers the reload.
|
|
1300
|
-
*/
|
|
1301
|
-
function createReloadRequestHandler({ resetState, broadcast, generateEpoch = generateId }) {
|
|
1302
|
-
return async (options) => {
|
|
1303
|
-
const reloadEpoch = generateEpoch();
|
|
1304
|
-
if (options.clean) await resetState.writeResetRequest(reloadEpoch);
|
|
1305
|
-
broadcast({
|
|
1306
|
-
notificationType: "reload-requested",
|
|
1307
|
-
reloadEpoch,
|
|
1308
|
-
clean: options.clean
|
|
1309
|
-
});
|
|
1310
|
-
};
|
|
1311
|
-
}
|
|
1312
|
-
//#endregion
|
|
1313
|
-
export { xxhash as C, createMigrator as S, createImportData as T, createCrdtApplyFunction as _, acquireReplicaLock as a, verifySyncSchema as b, isWorkerErrorResponseMessage as c, isWorkerRequestMessage as d, isWorkerResponseMessage as f, createCrdtStorage as g, createCrdtSyncProducer as h, createResetStateStore as i, isWorkerInitMessage as l, syncDbWorkerLockName as m, createIdbResetStore as n, runWithNoActiveReplicas as o, syncDbClientLockName as p, createReloadRequestHandler as r, createBroadcastChannels as s, RESET_REQUEST_TTL_MS as t, isWorkerNotificationMessage as u, createSQLiteCrdtApplyFunction as v, createExportData as w, createMigrations as x, formatSchemaVerificationIssues as y };
|
|
1127
|
+
export { formatSchemaVerificationIssues as a, createMigrator as c, createSQLiteCrdtApplyFunction as i, createExportData as l, createCrdtStorage as n, verifySyncSchemaWithDriver as o, createCrdtApplyFunction as r, createMigrations as s, createCrdtSyncProducer as t, createImportData as u };
|
|
1314
1128
|
|
|
1315
|
-
//# sourceMappingURL=
|
|
1129
|
+
//# sourceMappingURL=crdt-sync-producer-BY-WobgO.js.map
|