@sqlite-sync/core 0.9.3 → 0.10.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/admit-client-events-BDrJw4SK.js +35 -0
- package/dist/admit-client-events-BDrJw4SK.js.map +1 -0
- package/dist/crdt-sync-remote-source-Dtvyrk0R.js +504 -0
- package/dist/crdt-sync-remote-source-Dtvyrk0R.js.map +1 -0
- package/dist/{utils-Dr5JwcTP.js → crdt-table-schema-2w_AIpsV.js} +9 -2
- package/dist/crdt-table-schema-2w_AIpsV.js.map +1 -0
- package/dist/{index-iXdCBXzB.d.ts → index-B4s8GFWF.d.ts} +34 -23
- package/dist/index.d.ts +1 -1
- package/dist/index.js +31 -48
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +2 -2
- package/dist/internal.js +3 -3
- package/dist/{make-crdt-table-CvpTHmRY.js → make-crdt-table-D8VQ-ptj.js} +23 -14
- package/dist/make-crdt-table-D8VQ-ptj.js.map +1 -0
- package/dist/{reset-state-4Hn95CKG.js → reset-state-BVWEcT_z.js} +39 -429
- package/dist/reset-state-BVWEcT_z.js.map +1 -0
- package/dist/server.d.ts +30 -2
- package/dist/server.js +103 -3
- package/dist/server.js.map +1 -1
- package/dist/{system-schema-CkglcfTS.js → system-schema-C2JZDv6L.js} +3 -10
- package/dist/system-schema-C2JZDv6L.js.map +1 -0
- package/dist/worker.d.ts +3 -2
- package/dist/worker.js +52 -8
- package/dist/worker.js.map +1 -1
- package/package.json +2 -1
- package/dist/make-crdt-table-CvpTHmRY.js.map +0 -1
- package/dist/reset-state-4Hn95CKG.js.map +0 -1
- package/dist/system-schema-CkglcfTS.js.map +0 -1
- package/dist/utils-Dr5JwcTP.js.map +0 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { c as generateId, f as quoteId, n as isNoOpCrdtEventPayload, o as createTypedEventTarget, r as TypedBroadcastChannel, s as ensureSingletonExecution, t as CRDT_EVENT_NO_OP_PAYLOAD } from "./crdt-table-schema-2w_AIpsV.js";
|
|
2
|
+
import { c as serializeHLC, n as CrdtEventValidationError, r as validateNewCrdtEvent, s as deserializeHLC } from "./crdt-sync-remote-source-Dtvyrk0R.js";
|
|
3
|
+
import { u as createStoredValue } from "./system-schema-C2JZDv6L.js";
|
|
3
4
|
import { DummyDriver, Kysely, SqliteAdapter, SqliteIntrospector, SqliteQueryCompiler, sql } from "kysely";
|
|
4
5
|
import initXxhash from "xxhash-wasm";
|
|
5
6
|
import sqlite3InitModule from "@sqlite.org/sqlite-wasm";
|
|
@@ -132,81 +133,6 @@ const xxhash = {
|
|
|
132
133
|
h64
|
|
133
134
|
};
|
|
134
135
|
//#endregion
|
|
135
|
-
//#region src/hlc.ts
|
|
136
|
-
const MAX_COUNTER = 36 ** 5 - 1;
|
|
137
|
-
const DEFAULT_MAX_DRIFT_MS = 216e5;
|
|
138
|
-
var HLCCounter = class {
|
|
139
|
-
timestamp;
|
|
140
|
-
counter;
|
|
141
|
-
nodeId;
|
|
142
|
-
getTimestamp;
|
|
143
|
-
maxDrift;
|
|
144
|
-
constructor(nodeId, getTimestamp, maxDrift = DEFAULT_MAX_DRIFT_MS) {
|
|
145
|
-
this.timestamp = getTimestamp();
|
|
146
|
-
this.counter = 0;
|
|
147
|
-
this.nodeId = nodeId;
|
|
148
|
-
this.getTimestamp = getTimestamp;
|
|
149
|
-
this.maxDrift = maxDrift;
|
|
150
|
-
}
|
|
151
|
-
getCurrentHLC() {
|
|
152
|
-
return {
|
|
153
|
-
timestamp: this.timestamp,
|
|
154
|
-
counter: this.counter,
|
|
155
|
-
nodeId: this.nodeId
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
getNextHLC() {
|
|
159
|
-
const now = this.getTimestamp();
|
|
160
|
-
if (now > this.timestamp) {
|
|
161
|
-
this.timestamp = now;
|
|
162
|
-
this.counter = 0;
|
|
163
|
-
return this.getCurrentHLC();
|
|
164
|
-
}
|
|
165
|
-
this.counter++;
|
|
166
|
-
if (this.counter > MAX_COUNTER) throw new Error(`HLC counter overflow: exceeded max value ${MAX_COUNTER}`);
|
|
167
|
-
return this.getCurrentHLC();
|
|
168
|
-
}
|
|
169
|
-
mergeHLC(hlc) {
|
|
170
|
-
const now = this.getTimestamp();
|
|
171
|
-
if (hlc.timestamp - now > this.maxDrift) {
|
|
172
|
-
console.warn(`HLC: ignoring far-future timestamp (remote=${hlc.timestamp}, local=${now}, drift=${hlc.timestamp - now}ms)`);
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
if (this.timestamp === hlc.timestamp) this.counter = Math.max(this.counter, hlc.counter) + 1;
|
|
176
|
-
else if (this.timestamp > hlc.timestamp) this.counter++;
|
|
177
|
-
else {
|
|
178
|
-
this.timestamp = hlc.timestamp;
|
|
179
|
-
this.counter = hlc.counter + 1;
|
|
180
|
-
}
|
|
181
|
-
if (this.counter > MAX_COUNTER) throw new Error(`HLC counter overflow: exceeded max value ${MAX_COUNTER}`);
|
|
182
|
-
}
|
|
183
|
-
};
|
|
184
|
-
function serializeHLC(hlc) {
|
|
185
|
-
return `${hlc.timestamp.toString().padStart(15, "0")}:${hlc.counter.toString(36).padStart(5, "0")}:${hlc.nodeId}`;
|
|
186
|
-
}
|
|
187
|
-
function deserializeHLC(serialized) {
|
|
188
|
-
const parts = serialized.split(":");
|
|
189
|
-
if (parts.length < 3) throw new Error(`Invalid HLC format: expected at least 3 colon-separated segments, got ${parts.length}`);
|
|
190
|
-
const timestamp = parseInt(parts[0], 10);
|
|
191
|
-
const counter = parseInt(parts[1], 36);
|
|
192
|
-
if (Number.isNaN(timestamp) || Number.isNaN(counter)) throw new Error(`Invalid HLC values: timestamp=${parts[0]}, counter=${parts[1]}`);
|
|
193
|
-
return {
|
|
194
|
-
timestamp,
|
|
195
|
-
counter,
|
|
196
|
-
nodeId: parts.slice(2).join(":")
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
function compareHLC(one, two) {
|
|
200
|
-
if (one.timestamp === two.timestamp) {
|
|
201
|
-
if (one.counter === two.counter) {
|
|
202
|
-
if (one.nodeId === two.nodeId) return 0;
|
|
203
|
-
return one.nodeId < two.nodeId ? -1 : 1;
|
|
204
|
-
}
|
|
205
|
-
return one.counter - two.counter;
|
|
206
|
-
}
|
|
207
|
-
return one.timestamp - two.timestamp;
|
|
208
|
-
}
|
|
209
|
-
//#endregion
|
|
210
136
|
//#region src/introspection.ts
|
|
211
137
|
function tablesQuery(qb) {
|
|
212
138
|
return qb.selectFrom("sqlite_master").where("type", "in", ["table", "view"]).where("name", "not like", "sqlite_%").select([
|
|
@@ -775,93 +701,6 @@ WHERE "dataset" = ?
|
|
|
775
701
|
};
|
|
776
702
|
}
|
|
777
703
|
//#endregion
|
|
778
|
-
//#region src/schema/validate-crdt-event.ts
|
|
779
|
-
var CrdtEventValidationError = class extends Error {
|
|
780
|
-
errors;
|
|
781
|
-
constructor(errors) {
|
|
782
|
-
super(`Invalid CRDT events: ${errors.join("; ")}`);
|
|
783
|
-
this.errors = errors;
|
|
784
|
-
this.name = "CrdtEventValidationError";
|
|
785
|
-
}
|
|
786
|
-
};
|
|
787
|
-
const crdtEventTypes = [
|
|
788
|
-
"item-created",
|
|
789
|
-
"item-updated",
|
|
790
|
-
"item-deleted"
|
|
791
|
-
];
|
|
792
|
-
function validateNewCrdtEvent(schema, input) {
|
|
793
|
-
const errors = [];
|
|
794
|
-
if (!isRecord(input)) return {
|
|
795
|
-
success: false,
|
|
796
|
-
errors: ["Event must be an object"]
|
|
797
|
-
};
|
|
798
|
-
const type = input.type;
|
|
799
|
-
const dataset = input.dataset;
|
|
800
|
-
const itemId = input.item_id;
|
|
801
|
-
let payload = input.payload;
|
|
802
|
-
let payloadParseFailed = false;
|
|
803
|
-
if (typeof payload === "string") try {
|
|
804
|
-
payload = JSON.parse(payload);
|
|
805
|
-
} catch {
|
|
806
|
-
payloadParseFailed = true;
|
|
807
|
-
errors.push(`Event payload is not valid JSON`);
|
|
808
|
-
}
|
|
809
|
-
if (!isCrdtEventType(type)) errors.push(`Invalid event type "${String(type)}"`);
|
|
810
|
-
if (typeof dataset !== "string") errors.push(`Event dataset must be a string`);
|
|
811
|
-
if (typeof itemId !== "string") errors.push(`Event item_id must be a string`);
|
|
812
|
-
if (!payloadParseFailed && !isRecord(payload)) errors.push(`Event payload must be an object`);
|
|
813
|
-
const datasetMatch = typeof dataset === "string" ? findTableForDataset(schema, dataset) : null;
|
|
814
|
-
if (typeof dataset === "string" && !datasetMatch) errors.push(`Unknown dataset "${dataset}"`);
|
|
815
|
-
if (isCrdtEventType(type) && datasetMatch && isRecord(payload)) switch (type) {
|
|
816
|
-
case "item-created": {
|
|
817
|
-
const result = datasetMatch.table.validatePayload(payload, { event: "item-created" });
|
|
818
|
-
if (!result.success) errors.push(...result.errors.map((error) => `payload: ${error}`));
|
|
819
|
-
if (typeof itemId === "string" && "id" in payload && payload.id !== itemId) errors.push(`payload: Column "id" must match item_id "${itemId}"`);
|
|
820
|
-
break;
|
|
821
|
-
}
|
|
822
|
-
case "item-updated": {
|
|
823
|
-
const result = datasetMatch.table.validatePayload(payload, { event: "item-updated" });
|
|
824
|
-
if (!result.success) errors.push(...result.errors.map((error) => `payload: ${error}`));
|
|
825
|
-
break;
|
|
826
|
-
}
|
|
827
|
-
case "item-deleted": if (Object.keys(payload).length > 0) errors.push(`payload: item-deleted events must have an empty payload`);
|
|
828
|
-
}
|
|
829
|
-
if (errors.length > 0) return {
|
|
830
|
-
success: false,
|
|
831
|
-
errors
|
|
832
|
-
};
|
|
833
|
-
return {
|
|
834
|
-
success: true,
|
|
835
|
-
event: {
|
|
836
|
-
type,
|
|
837
|
-
dataset: datasetMatch?.baseTableName ?? dataset,
|
|
838
|
-
item_id: itemId,
|
|
839
|
-
payload
|
|
840
|
-
}
|
|
841
|
-
};
|
|
842
|
-
}
|
|
843
|
-
function findTableForDataset(schema, dataset) {
|
|
844
|
-
const config = schema.tablesConfig.find((tableConfig) => tableConfig.baseTableName === dataset || tableConfig.crdtTableName === dataset);
|
|
845
|
-
if (config) {
|
|
846
|
-
const table = schema.tables[config.crdtTableName];
|
|
847
|
-
return table ? {
|
|
848
|
-
table,
|
|
849
|
-
baseTableName: config.baseTableName
|
|
850
|
-
} : null;
|
|
851
|
-
}
|
|
852
|
-
const table = schema.tables[dataset];
|
|
853
|
-
return table ? {
|
|
854
|
-
table,
|
|
855
|
-
baseTableName: dataset
|
|
856
|
-
} : null;
|
|
857
|
-
}
|
|
858
|
-
function isRecord(value) {
|
|
859
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
860
|
-
}
|
|
861
|
-
function isCrdtEventType(value) {
|
|
862
|
-
return typeof value === "string" && crdtEventTypes.includes(value);
|
|
863
|
-
}
|
|
864
|
-
//#endregion
|
|
865
704
|
//#region src/schema/verify-sync-schema.ts
|
|
866
705
|
const acceptedDataTypes = {
|
|
867
706
|
text: ["TEXT"],
|
|
@@ -1098,6 +937,7 @@ function createCrdtApplyFunction({ getCrdtUpdateLog, insertItem, insertCrdtUpdat
|
|
|
1098
937
|
const applyItemUpdated = ({ event, meta }) => {
|
|
1099
938
|
if (!meta) throw new Error(`Item ${event.item_id} in dataset ${event.dataset} not found`);
|
|
1100
939
|
const eventPayload = event.type === "item-deleted" ? { tombstone: 1 } : JSON.parse(event.payload);
|
|
940
|
+
if (event.type === "item-created") eventPayload.tombstone = 0;
|
|
1101
941
|
const updatePayload = {};
|
|
1102
942
|
let hasUpdates = false;
|
|
1103
943
|
for (const [key, value] of Object.entries(eventPayload)) {
|
|
@@ -1178,6 +1018,17 @@ function createCrdtStorage(storage) {
|
|
|
1178
1018
|
const crdtEventsTable = storage.dbConfig.eventsTable.fullIdentifier;
|
|
1179
1019
|
const quotedEventsTable = quoteId(storage.dbConfig.eventsTable.fullIdentifier);
|
|
1180
1020
|
const noOpPayloadSqlLiteral = `'${CRDT_EVENT_NO_OP_PAYLOAD}'`;
|
|
1021
|
+
const [persistedClock] = db.executePreparedRaw({
|
|
1022
|
+
key: "get-persisted-hlc",
|
|
1023
|
+
sql: `select max("timestamp") as "timestamp" from (
|
|
1024
|
+
select max("timestamp") as "timestamp" from ${quotedEventsTable} where "status" = 'applied'
|
|
1025
|
+
union all
|
|
1026
|
+
select max(fields.value) as "timestamp"
|
|
1027
|
+
from ${quoteId(storage.dbConfig.updateLogTable.fullIdentifier)} as updates, json_each(updates.payload) as fields
|
|
1028
|
+
)`,
|
|
1029
|
+
meta: { loggerLevel: "system" }
|
|
1030
|
+
});
|
|
1031
|
+
if (persistedClock?.timestamp) storage.hlc.restoreHLC(deserializeHLC(persistedClock.timestamp));
|
|
1181
1032
|
const getInitialSequentialSyncId = () => {
|
|
1182
1033
|
const [firstPendingEvent] = db.executePrepared("get-first-pending-event", { status: "pending" }, (db, params) => db.selectFrom(crdtEventsTable).select("sync_id").where("status", "=", params("status")).orderBy("sync_id", "asc").limit(sql.lit(1)), { loggerLevel: "system" });
|
|
1183
1034
|
return firstPendingEvent ? firstPendingEvent.sync_id - 1 : localSyncId;
|
|
@@ -1273,7 +1124,13 @@ function createCrdtStorage(storage) {
|
|
|
1273
1124
|
};
|
|
1274
1125
|
};
|
|
1275
1126
|
const enqueueLocalEvents = (events, sourceNodeId) => {
|
|
1276
|
-
|
|
1127
|
+
const known = getFirstAcceptedSyncIdsByTimestamp(db, events);
|
|
1128
|
+
const newEvents = events.filter((event) => {
|
|
1129
|
+
if (known.has(event.timestamp)) return false;
|
|
1130
|
+
known.set(event.timestamp, 0);
|
|
1131
|
+
return true;
|
|
1132
|
+
});
|
|
1133
|
+
return enqueueEvents("local", sourceNodeId, newEvents);
|
|
1277
1134
|
};
|
|
1278
1135
|
const enqueueOwnEvents = (events) => {
|
|
1279
1136
|
return enqueueEvents("own", storage.nodeId, events);
|
|
@@ -1359,7 +1216,19 @@ function createCrdtStorage(storage) {
|
|
|
1359
1216
|
sync_id: syncId,
|
|
1360
1217
|
status: "pending"
|
|
1361
1218
|
};
|
|
1362
|
-
tx.executePreparedRaw({
|
|
1219
|
+
const [newerEvent] = tx.executePreparedRaw({
|
|
1220
|
+
key: "get-crdt-row-event-newer-than-snapshot",
|
|
1221
|
+
sql: `select "sync_id" from ${quotedEventsTable}
|
|
1222
|
+
where "dataset" = ? and "item_id" = ? and "timestamp" > ? and "payload" <> ${noOpPayloadSqlLiteral}
|
|
1223
|
+
limit 1`,
|
|
1224
|
+
params: [
|
|
1225
|
+
event.dataset,
|
|
1226
|
+
event.item_id,
|
|
1227
|
+
event.timestamp
|
|
1228
|
+
],
|
|
1229
|
+
meta: { loggerLevel: "system" }
|
|
1230
|
+
});
|
|
1231
|
+
if (!newerEvent) tx.executePreparedRaw({
|
|
1363
1232
|
key: "replace-crdt-row-history-with-no-ops",
|
|
1364
1233
|
sql: `update ${quotedEventsTable}
|
|
1365
1234
|
set "payload" = ?
|
|
@@ -1486,7 +1355,7 @@ where "dataset" = ? and "item_id" = ? and "payload" <> ${noOpPayloadSqlLiteral}`
|
|
|
1486
1355
|
persistEventHlcAccumulator();
|
|
1487
1356
|
};
|
|
1488
1357
|
const getFirstAcceptedSyncIdsByTimestamp = (tx, events) => {
|
|
1489
|
-
const timestamps = Array.from(new Set(events.
|
|
1358
|
+
const timestamps = Array.from(new Set(events.map((event) => event.timestamp)));
|
|
1490
1359
|
if (timestamps.length === 0) return /* @__PURE__ */ new Map();
|
|
1491
1360
|
const rows = timestamps.length === 1 ? tx.executePreparedRaw({
|
|
1492
1361
|
key: "get-first-accepted-sync-id-by-timestamp",
|
|
@@ -1602,7 +1471,7 @@ where "dataset" = ? and "item_id" = ? and "payload" <> ${noOpPayloadSqlLiteral}`
|
|
|
1602
1471
|
const failedRemoteSyncIds = [];
|
|
1603
1472
|
const appliedEventsToNotify = [];
|
|
1604
1473
|
db.executeTransaction((tx) => {
|
|
1605
|
-
const firstAcceptedSyncIdByTimestamp = getFirstAcceptedSyncIdsByTimestamp(tx, events);
|
|
1474
|
+
const firstAcceptedSyncIdByTimestamp = getFirstAcceptedSyncIdsByTimestamp(tx, events.filter((event) => event.origin !== "own-applied"));
|
|
1606
1475
|
const statusOnlyEvents = [];
|
|
1607
1476
|
for (const event of events) {
|
|
1608
1477
|
processPersistedEvent(tx, event, firstAcceptedSyncIdByTimestamp, statusOnlyEvents);
|
|
@@ -1656,265 +1525,6 @@ const createCrdtSyncProducer = ({ storage, broadcastEvents }) => {
|
|
|
1656
1525
|
});
|
|
1657
1526
|
};
|
|
1658
1527
|
//#endregion
|
|
1659
|
-
//#region src/sqlite-crdt/retry-remote-operation.ts
|
|
1660
|
-
const REMOTE_RETRY_OPTIONS = {
|
|
1661
|
-
maxAttempts: 3,
|
|
1662
|
-
backoffBaseMs: 100,
|
|
1663
|
-
backoffExponent: 1.5,
|
|
1664
|
-
backoffJitterMs: 150,
|
|
1665
|
-
timeoutMs: 1e4
|
|
1666
|
-
};
|
|
1667
|
-
var RetryTimeoutError = class extends Error {
|
|
1668
|
-
previous;
|
|
1669
|
-
constructor(message, previous) {
|
|
1670
|
-
super(message);
|
|
1671
|
-
this.previous = previous;
|
|
1672
|
-
this.name = "TimeoutError";
|
|
1673
|
-
}
|
|
1674
|
-
};
|
|
1675
|
-
const applyJitter = (delayMs, maxJitterMs) => {
|
|
1676
|
-
const jitter = Math.random() * maxJitterMs * (Math.random() > .5 ? 1 : -1);
|
|
1677
|
-
return Math.max(0, delayMs + jitter);
|
|
1678
|
-
};
|
|
1679
|
-
const delay = (delayMs) => new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
1680
|
-
const withTimeout = async (operation, timeoutMs, previousError) => {
|
|
1681
|
-
let timeoutId;
|
|
1682
|
-
try {
|
|
1683
|
-
return await Promise.race([operation(), new Promise((_, reject) => {
|
|
1684
|
-
timeoutId = setTimeout(() => reject(new RetryTimeoutError("Remote operation timed out", previousError)), timeoutMs);
|
|
1685
|
-
})]);
|
|
1686
|
-
} finally {
|
|
1687
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
1688
|
-
}
|
|
1689
|
-
};
|
|
1690
|
-
const retryRemoteOperation = async (operation, options) => {
|
|
1691
|
-
let lastError;
|
|
1692
|
-
for (let attempt = 1; attempt <= options.maxAttempts; attempt++) try {
|
|
1693
|
-
return await withTimeout(operation, options.timeoutMs, lastError);
|
|
1694
|
-
} catch (error) {
|
|
1695
|
-
lastError = error;
|
|
1696
|
-
if (attempt >= options.maxAttempts) throw error;
|
|
1697
|
-
const backoffDelay = applyJitter(options.backoffBaseMs * options.backoffExponent ** (attempt - 1), options.backoffJitterMs);
|
|
1698
|
-
if (backoffDelay > 0) await delay(backoffDelay);
|
|
1699
|
-
}
|
|
1700
|
-
throw lastError;
|
|
1701
|
-
};
|
|
1702
|
-
//#endregion
|
|
1703
|
-
//#region src/sqlite-crdt/crdt-sync-remote-source.ts
|
|
1704
|
-
var SchemaVersionMismatchError = class extends Error {
|
|
1705
|
-
remoteSchemaVersion;
|
|
1706
|
-
localSchemaVersion;
|
|
1707
|
-
constructor(remoteSchemaVersion, localSchemaVersion) {
|
|
1708
|
-
super(`Schema version mismatch: remote ${remoteSchemaVersion} != local ${localSchemaVersion}`);
|
|
1709
|
-
this.remoteSchemaVersion = remoteSchemaVersion;
|
|
1710
|
-
this.localSchemaVersion = localSchemaVersion;
|
|
1711
|
-
this.name = "SchemaVersionMismatchError";
|
|
1712
|
-
}
|
|
1713
|
-
};
|
|
1714
|
-
const createCrdtSyncRemoteSource = ({ bufferSize, storage, migrator, pullSyncId, pushSyncId, nodeId, remoteFactory }) => {
|
|
1715
|
-
const eventTarget = createTypedEventTarget();
|
|
1716
|
-
let remoteState = {
|
|
1717
|
-
type: "offline",
|
|
1718
|
-
reason: "NOT_INITIALIZED",
|
|
1719
|
-
deSynced: false,
|
|
1720
|
-
schemaVersionMismatched: false
|
|
1721
|
-
};
|
|
1722
|
-
const patchRemoteState = (state) => {
|
|
1723
|
-
remoteState = {
|
|
1724
|
-
...remoteState,
|
|
1725
|
-
...state
|
|
1726
|
-
};
|
|
1727
|
-
eventTarget.dispatchEvent("state-changed", remoteState.type);
|
|
1728
|
-
};
|
|
1729
|
-
const initRemote = ensureSingletonExecution(async () => {
|
|
1730
|
-
if (remoteState.type !== "offline") throw new Error("Remote source is not offline");
|
|
1731
|
-
if (!remoteFactory) {
|
|
1732
|
-
console.warn("Remote source factory not provided. Going offline.");
|
|
1733
|
-
patchRemoteState({
|
|
1734
|
-
type: "offline",
|
|
1735
|
-
reason: "NOT_INITIALIZED"
|
|
1736
|
-
});
|
|
1737
|
-
return;
|
|
1738
|
-
}
|
|
1739
|
-
patchRemoteState({ type: "pending" });
|
|
1740
|
-
const factoryResult = await tryCatchAsync(async () => {
|
|
1741
|
-
return await remoteFactory?.({ onEventsAvailable: ({ newSyncId, remoteEventHlcSum }) => {
|
|
1742
|
-
pullEvents({
|
|
1743
|
-
remoteSyncId: newSyncId,
|
|
1744
|
-
remoteEventHlcSum,
|
|
1745
|
-
includeSelf: false
|
|
1746
|
-
});
|
|
1747
|
-
} });
|
|
1748
|
-
});
|
|
1749
|
-
if (!factoryResult.success) {
|
|
1750
|
-
patchRemoteState({
|
|
1751
|
-
type: "offline",
|
|
1752
|
-
reason: "INITIALIZATION_FAILED"
|
|
1753
|
-
});
|
|
1754
|
-
console.warn("Failed to create remote source", factoryResult.error);
|
|
1755
|
-
return;
|
|
1756
|
-
}
|
|
1757
|
-
patchRemoteState({
|
|
1758
|
-
type: "online",
|
|
1759
|
-
source: factoryResult.data,
|
|
1760
|
-
deSynced: false,
|
|
1761
|
-
schemaVersionMismatched: false
|
|
1762
|
-
});
|
|
1763
|
-
}, { queueReExecution: false });
|
|
1764
|
-
const syncWithRemote = ensureSingletonExecution(async () => {
|
|
1765
|
-
if (remoteState.type !== "online") return;
|
|
1766
|
-
await pullEvents();
|
|
1767
|
-
await startPushingEvents();
|
|
1768
|
-
}, { queueReExecution: false });
|
|
1769
|
-
const goOffline = ensureSingletonExecution(async (reason) => {
|
|
1770
|
-
if (remoteState.type !== "online") return;
|
|
1771
|
-
const source = remoteState.source;
|
|
1772
|
-
patchRemoteState({ type: "pending" });
|
|
1773
|
-
const disconnectResult = await tryCatchAsync(async () => {
|
|
1774
|
-
return await source.disconnect?.();
|
|
1775
|
-
});
|
|
1776
|
-
if (!disconnectResult.success) console.warn("Error while disconnecting from remote source", disconnectResult.error);
|
|
1777
|
-
patchRemoteState({
|
|
1778
|
-
type: "offline",
|
|
1779
|
-
reason
|
|
1780
|
-
});
|
|
1781
|
-
}, { queueReExecution: false });
|
|
1782
|
-
const goOnline = async () => {
|
|
1783
|
-
if (remoteState.type !== "online") await initRemote();
|
|
1784
|
-
if (remoteState.type === "online") await syncWithRemote();
|
|
1785
|
-
};
|
|
1786
|
-
let requestedPullSyncId = null;
|
|
1787
|
-
let pullPromise = null;
|
|
1788
|
-
const pullEvents = (request) => {
|
|
1789
|
-
if (remoteState.type !== "online") return Promise.resolve();
|
|
1790
|
-
const remoteSyncId = request?.remoteSyncId;
|
|
1791
|
-
if (remoteSyncId !== void 0 && remoteSyncId <= pullSyncId.current) {
|
|
1792
|
-
checkRemoteConsistency(remoteSyncId, request?.remoteEventHlcSum ?? null);
|
|
1793
|
-
return Promise.resolve();
|
|
1794
|
-
}
|
|
1795
|
-
if (pullPromise) {
|
|
1796
|
-
if (remoteSyncId !== void 0 && (!requestedPullSyncId || requestedPullSyncId < remoteSyncId)) requestedPullSyncId = remoteSyncId;
|
|
1797
|
-
return pullPromise;
|
|
1798
|
-
}
|
|
1799
|
-
pullPromise = pullAllEvents({
|
|
1800
|
-
afterSyncId: pullSyncId.current,
|
|
1801
|
-
excludeNodeId: request?.includeSelf ? void 0 : nodeId
|
|
1802
|
-
}).catch((error) => {
|
|
1803
|
-
console.error("Error pulling events. Going offline.", error);
|
|
1804
|
-
goOffline("REMOTE_PULL_ERROR");
|
|
1805
|
-
}).finally(() => {
|
|
1806
|
-
pullPromise = null;
|
|
1807
|
-
const nextTarget = requestedPullSyncId;
|
|
1808
|
-
requestedPullSyncId = null;
|
|
1809
|
-
if (nextTarget && nextTarget > pullSyncId.current) pullEvents({ remoteSyncId: nextTarget });
|
|
1810
|
-
});
|
|
1811
|
-
return pullPromise;
|
|
1812
|
-
};
|
|
1813
|
-
const pullAllEvents = async (opts) => {
|
|
1814
|
-
let hasMore = true;
|
|
1815
|
-
let afterSyncId = opts.afterSyncId;
|
|
1816
|
-
while (hasMore) {
|
|
1817
|
-
if (remoteState.type !== "online") return;
|
|
1818
|
-
const source = remoteState.source;
|
|
1819
|
-
const response = await retryRemoteOperation(() => source.pullEvents({
|
|
1820
|
-
...opts,
|
|
1821
|
-
afterSyncId
|
|
1822
|
-
}), REMOTE_RETRY_OPTIONS);
|
|
1823
|
-
hasMore = response.hasMore;
|
|
1824
|
-
afterSyncId = response.nextSyncId;
|
|
1825
|
-
if (response.events) storage.enqueueRemoteEvents(response.events.map((x) => {
|
|
1826
|
-
if (x.schema_version > migrator.currentSchemaVersion) {
|
|
1827
|
-
eventTarget.dispatchEvent("remote-schema-version-mismatch", {
|
|
1828
|
-
remoteSchemaVersion: x.schema_version,
|
|
1829
|
-
localSchemaVersion: migrator.currentSchemaVersion
|
|
1830
|
-
});
|
|
1831
|
-
if (remoteState.type === "online" && !remoteState.schemaVersionMismatched) patchRemoteState({ schemaVersionMismatched: true });
|
|
1832
|
-
throw new SchemaVersionMismatchError(x.schema_version, migrator.currentSchemaVersion);
|
|
1833
|
-
}
|
|
1834
|
-
return x;
|
|
1835
|
-
}));
|
|
1836
|
-
if (response.nextSyncId <= pullSyncId.current) break;
|
|
1837
|
-
if (response.nextSyncId > pullSyncId.current) pullSyncId.current = response.nextSyncId;
|
|
1838
|
-
}
|
|
1839
|
-
};
|
|
1840
|
-
const checkRemoteConsistency = (remoteSyncId, remoteEventHlcSum) => {
|
|
1841
|
-
if (remoteEventHlcSum === null) return;
|
|
1842
|
-
if (remoteSyncId !== pullSyncId.current) return;
|
|
1843
|
-
if (!storage.checkIsQuiescent(pushSyncId.current)) return;
|
|
1844
|
-
const localEventHlcSum = storage.getEventHlcAccumulator();
|
|
1845
|
-
if (localEventHlcSum === null) return;
|
|
1846
|
-
if (localEventHlcSum === remoteEventHlcSum) return;
|
|
1847
|
-
eventTarget.dispatchEvent("de-sync-detected", { reason: "CHECKSUM_MISMATCH" });
|
|
1848
|
-
console.warn(`[sqlite-sync] De-sync detected at syncId ${remoteSyncId}: local HLC checksum ${localEventHlcSum} != remote ${remoteEventHlcSum}. Local and remote have diverged despite being caught up.`);
|
|
1849
|
-
if (remoteState.type === "online" && !remoteState.deSynced) patchRemoteState({ deSynced: true });
|
|
1850
|
-
};
|
|
1851
|
-
const startPushingEvents = ensureSingletonExecution(async () => {
|
|
1852
|
-
while (true) {
|
|
1853
|
-
const eventsBatch = storage.getEventsBatch({
|
|
1854
|
-
status: "applied",
|
|
1855
|
-
afterSyncId: pushSyncId.current,
|
|
1856
|
-
excludeOrigin: "remote",
|
|
1857
|
-
limit: bufferSize
|
|
1858
|
-
});
|
|
1859
|
-
if (eventsBatch.events.length === 0) break;
|
|
1860
|
-
if (remoteState.type !== "online") break;
|
|
1861
|
-
const source = remoteState.source;
|
|
1862
|
-
let response;
|
|
1863
|
-
try {
|
|
1864
|
-
response = await retryRemoteOperation(() => source.pushEvents({
|
|
1865
|
-
nodeId,
|
|
1866
|
-
events: eventsBatch.events.map((event) => ({
|
|
1867
|
-
schema_version: event.schema_version,
|
|
1868
|
-
timestamp: event.timestamp,
|
|
1869
|
-
type: event.type,
|
|
1870
|
-
dataset: event.dataset,
|
|
1871
|
-
item_id: event.item_id,
|
|
1872
|
-
payload: event.payload
|
|
1873
|
-
}))
|
|
1874
|
-
}), REMOTE_RETRY_OPTIONS);
|
|
1875
|
-
} catch (error) {
|
|
1876
|
-
console.error("Error pushing events. Going offline.", error);
|
|
1877
|
-
goOffline("REMOTE_PUSH_ERROR");
|
|
1878
|
-
return;
|
|
1879
|
-
}
|
|
1880
|
-
if (!response.ok) {
|
|
1881
|
-
console.error("Remote rejected pushed events. Going offline.");
|
|
1882
|
-
goOffline("REMOTE_PUSH_ERROR");
|
|
1883
|
-
return;
|
|
1884
|
-
}
|
|
1885
|
-
pushSyncId.current = eventsBatch.nextSyncId;
|
|
1886
|
-
if (response.beforeSyncId !== void 0 && response.afterSyncId !== void 0 && response.beforeSyncId <= pullSyncId.current && response.afterSyncId > pullSyncId.current) pullSyncId.current = response.afterSyncId;
|
|
1887
|
-
if (!eventsBatch.hasMore) break;
|
|
1888
|
-
}
|
|
1889
|
-
});
|
|
1890
|
-
const eventsAppliedSubscription = storage.addEventListener("events-applied", () => {
|
|
1891
|
-
startPushingEvents();
|
|
1892
|
-
});
|
|
1893
|
-
const remoteEventApplyFailedSubscription = storage.addEventListener("remote-event-apply-failed", () => {
|
|
1894
|
-
eventTarget.dispatchEvent("de-sync-detected", { reason: "ERROR_APPLYING_REMOTE_EVENT" });
|
|
1895
|
-
if (remoteState.type === "online" && !remoteState.deSynced) patchRemoteState({ deSynced: true });
|
|
1896
|
-
});
|
|
1897
|
-
const getState = () => ({
|
|
1898
|
-
remoteState: remoteState.type,
|
|
1899
|
-
deSynced: remoteState.deSynced,
|
|
1900
|
-
schemaVersionMismatched: remoteState.schemaVersionMismatched
|
|
1901
|
-
});
|
|
1902
|
-
const dispose = async () => {
|
|
1903
|
-
await goOffline("DISCONNECTED");
|
|
1904
|
-
eventsAppliedSubscription.unsubscribe();
|
|
1905
|
-
remoteEventApplyFailedSubscription.unsubscribe();
|
|
1906
|
-
};
|
|
1907
|
-
return {
|
|
1908
|
-
goOnline,
|
|
1909
|
-
goOffline,
|
|
1910
|
-
syncWithRemote,
|
|
1911
|
-
getState,
|
|
1912
|
-
dispose,
|
|
1913
|
-
addEventListener: eventTarget.addEventListener,
|
|
1914
|
-
removeEventListener: eventTarget.removeEventListener
|
|
1915
|
-
};
|
|
1916
|
-
};
|
|
1917
|
-
//#endregion
|
|
1918
1528
|
//#region src/worker-db/worker-common.ts
|
|
1919
1529
|
const syncDbWorkerLockName = "sync-db-worker-lock";
|
|
1920
1530
|
const syncDbClientLockName = "sync-db-client-lock";
|
|
@@ -2023,6 +1633,6 @@ function createReloadRequestHandler({ resetState, broadcast, generateEpoch = gen
|
|
|
2023
1633
|
};
|
|
2024
1634
|
}
|
|
2025
1635
|
//#endregion
|
|
2026
|
-
export {
|
|
1636
|
+
export { introspectDb as C, dummyKysely as D, createImportData as E, startPerformanceLogger as S, createExportData as T, formatSchemaVerificationIssues as _, createBroadcastChannels as a, createMigrator as b, isWorkerNotificationMessage as c, syncDbClientLockName as d, syncDbWorkerLockName as f, createSQLiteCrdtApplyFunction as g, createCrdtApplyFunction as h, createResetStateStore as i, isWorkerRequestMessage as l, createCrdtStorage as m, createIdbResetStore as n, isWorkerErrorResponseMessage as o, createCrdtSyncProducer as p, createReloadRequestHandler as r, isWorkerInitMessage as s, RESET_REQUEST_TTL_MS as t, isWorkerResponseMessage as u, verifySyncSchema as v, xxhash as w, SQLiteDbWrapper as x, createMigrations as y };
|
|
2027
1637
|
|
|
2028
|
-
//# sourceMappingURL=reset-state-
|
|
1638
|
+
//# sourceMappingURL=reset-state-BVWEcT_z.js.map
|