@sqlite-sync/core 0.1.1 → 0.3.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/{chunk-UGF5IU53.js → chunk-NHT3ELMN.js} +34 -6
- package/dist/chunk-NHT3ELMN.js.map +1 -0
- package/dist/{chunk-627DSM2Q.js → chunk-RKBTBPNC.js} +566 -177
- package/dist/chunk-RKBTBPNC.js.map +1 -0
- package/dist/{crdt-sync-remote-source-idoIjMcs.d.ts → crdt-sync-remote-source-Da77s4k0.d.ts} +131 -46
- package/dist/index.d.ts +66 -34
- package/dist/index.js +158 -98
- package/dist/index.js.map +1 -1
- package/dist/{crdt-schema-DQ1cYsFE.d.ts → reset-state-0LGwO78x.d.ts} +20 -2
- package/dist/server.d.ts +9 -2
- package/dist/server.js +2 -2
- package/dist/server.js.map +1 -1
- package/dist/worker.d.ts +11 -3
- package/dist/worker.js +119 -89
- package/dist/worker.js.map +1 -1
- package/package.json +7 -3
- package/dist/chunk-627DSM2Q.js.map +0 -1
- package/dist/chunk-UGF5IU53.js.map +0 -1
package/dist/worker.js
CHANGED
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SQLiteDbWrapper,
|
|
3
|
-
applyKyselyEventsBatchFilters,
|
|
4
3
|
applyWorkerDbSchema,
|
|
5
4
|
createBroadcastChannels,
|
|
6
5
|
createCrdtStorage,
|
|
7
6
|
createCrdtSyncProducer,
|
|
8
7
|
createCrdtSyncRemoteSource,
|
|
8
|
+
createIdbResetStore,
|
|
9
9
|
createMigrator,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
createStoredValue,
|
|
10
|
+
createReloadRequestHandler,
|
|
11
|
+
createResetStateStore,
|
|
13
12
|
isWorkerInitMessage,
|
|
14
13
|
isWorkerRequestMessage,
|
|
15
14
|
syncDbClientLockName,
|
|
16
|
-
syncDbWorkerLockName
|
|
17
|
-
|
|
15
|
+
syncDbWorkerLockName,
|
|
16
|
+
workerDbConfig,
|
|
17
|
+
xxhash
|
|
18
|
+
} from "./chunk-RKBTBPNC.js";
|
|
18
19
|
import {
|
|
19
20
|
createDeferredPromise,
|
|
20
21
|
jsonSafeParse
|
|
21
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-NHT3ELMN.js";
|
|
22
23
|
|
|
23
24
|
// src/web-socket/ws-remote-source.ts
|
|
24
25
|
var createWsRemoteSource = ({ createWebSocket }) => {
|
|
@@ -87,7 +88,7 @@ var createWsRemoteSource = ({ createWebSocket }) => {
|
|
|
87
88
|
break;
|
|
88
89
|
}
|
|
89
90
|
case "events-applied":
|
|
90
|
-
onEventsAvailable(message.newSyncId);
|
|
91
|
+
onEventsAvailable({ newSyncId: message.newSyncId, remoteEventHlcSum: message.eventHlcSum });
|
|
91
92
|
break;
|
|
92
93
|
default:
|
|
93
94
|
message;
|
|
@@ -106,6 +107,34 @@ var createWsRemoteSource = ({ createWebSocket }) => {
|
|
|
106
107
|
|
|
107
108
|
// src/worker-db/db-worker.ts
|
|
108
109
|
import sqlite3InitModule from "@sqlite.org/sqlite-wasm";
|
|
110
|
+
|
|
111
|
+
// src/worker-db/storage-version.ts
|
|
112
|
+
var LIB_STORAGE_VERSION = 1;
|
|
113
|
+
var storageVersionKey = (dbId) => `sqlite-sync-storage-version-${dbId}`;
|
|
114
|
+
function formatStorageVersion(appStorageVersion) {
|
|
115
|
+
return appStorageVersion === void 0 ? `lib-v${LIB_STORAGE_VERSION}` : `lib-v${LIB_STORAGE_VERSION}:app-${appStorageVersion}`;
|
|
116
|
+
}
|
|
117
|
+
function createStorageVersionStore({ store, dbId, appStorageVersion }) {
|
|
118
|
+
const key = storageVersionKey(dbId);
|
|
119
|
+
const currentVersion = formatStorageVersion(appStorageVersion);
|
|
120
|
+
return {
|
|
121
|
+
currentVersion,
|
|
122
|
+
async isVersionMismatch() {
|
|
123
|
+
const storedVersion = await store.get(key);
|
|
124
|
+
return storedVersion !== currentVersion;
|
|
125
|
+
},
|
|
126
|
+
/**
|
|
127
|
+
* Record the current version. Must be called only after the worker has
|
|
128
|
+
* successfully initialized with the wiped DB, so a failed init can be
|
|
129
|
+
* retried by a later elected worker.
|
|
130
|
+
*/
|
|
131
|
+
async markCurrentVersionApplied() {
|
|
132
|
+
await store.set(key, currentVersion);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// src/worker-db/db-worker.ts
|
|
109
138
|
var defaultLogger = (type, message, level = "info") => {
|
|
110
139
|
const logMessage = `[${type}] ${message}`;
|
|
111
140
|
switch (level) {
|
|
@@ -126,11 +155,28 @@ var defaultLogger = (type, message, level = "info") => {
|
|
|
126
155
|
async function createDbWorker(config, opts) {
|
|
127
156
|
const broadcastChannels = createBroadcastChannels(config.dbId);
|
|
128
157
|
const logger = opts.logger ?? defaultLogger;
|
|
129
|
-
const sqlite3 = await sqlite3InitModule();
|
|
158
|
+
const [sqlite3] = await Promise.all([sqlite3InitModule(), xxhash.ensureLoaded()]);
|
|
159
|
+
const resetStore = opts.resetStore ?? createIdbResetStore();
|
|
160
|
+
const resetState = createResetStateStore({
|
|
161
|
+
store: resetStore,
|
|
162
|
+
dbId: config.dbId
|
|
163
|
+
});
|
|
164
|
+
const storageVersion = createStorageVersionStore({
|
|
165
|
+
store: resetStore,
|
|
166
|
+
dbId: config.dbId,
|
|
167
|
+
appStorageVersion: opts.storageVersion
|
|
168
|
+
});
|
|
169
|
+
const [pendingReset, isVersionMismatch] = await Promise.all([
|
|
170
|
+
resetState.resolvePendingReset(),
|
|
171
|
+
storageVersion.isVersionMismatch()
|
|
172
|
+
]);
|
|
173
|
+
if (isVersionMismatch) {
|
|
174
|
+
logger("worker", `Storage version mismatch \u2014 resetting local DB to ${storageVersion.currentVersion}`, "warning");
|
|
175
|
+
}
|
|
130
176
|
const pool = await sqlite3.installOpfsSAHPoolVfs({
|
|
131
177
|
name: config.dbId,
|
|
132
178
|
directory: `.${config.dbId}`,
|
|
133
|
-
clearOnInit:
|
|
179
|
+
clearOnInit: !!pendingReset || isVersionMismatch,
|
|
134
180
|
initialCapacity: 8
|
|
135
181
|
});
|
|
136
182
|
const db = new SQLiteDbWrapper({
|
|
@@ -146,15 +192,11 @@ async function createDbWorker(config, opts) {
|
|
|
146
192
|
db.execute("PRAGMA worker.locking_mode=exclusive", { loggerLevel: "system" });
|
|
147
193
|
db.execute("PRAGMA worker.journal_mode=WAL", { loggerLevel: "system" });
|
|
148
194
|
db.execute("PRAGMA worker.synchronous=NORMAL", { loggerLevel: "system" });
|
|
149
|
-
applyWorkerDbSchema(db);
|
|
150
|
-
const kvStore = createSQLiteKvStore({
|
|
151
|
-
db,
|
|
152
|
-
metaTableName: "worker.kv"
|
|
153
|
-
});
|
|
195
|
+
const { kvStore } = applyWorkerDbSchema(db);
|
|
154
196
|
const migrator = createMigrator({
|
|
155
197
|
migrations: opts.syncDbSchema.migrations,
|
|
156
198
|
schemaVersion: kvStore.createNumberStoredValue("schema-version", -1),
|
|
157
|
-
updateLogTableName:
|
|
199
|
+
updateLogTableName: workerDbConfig.updateLogTable.fullIdentifier
|
|
158
200
|
});
|
|
159
201
|
migrator.migrateDbToLatest({
|
|
160
202
|
startTransaction: (callback) => {
|
|
@@ -162,12 +204,15 @@ async function createDbWorker(config, opts) {
|
|
|
162
204
|
}
|
|
163
205
|
});
|
|
164
206
|
db.invalidateDbSchema();
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
207
|
+
if (pendingReset) {
|
|
208
|
+
await resetState.markResetApplied(pendingReset.epoch);
|
|
209
|
+
}
|
|
210
|
+
if (isVersionMismatch) {
|
|
211
|
+
await storageVersion.markCurrentVersionApplied();
|
|
212
|
+
}
|
|
168
213
|
const crdtStorage = createCrdtStorage({
|
|
169
214
|
nodeId: config.clientId,
|
|
170
|
-
|
|
215
|
+
initialLocalSyncId: getMaxSyncId(db, "none"),
|
|
171
216
|
migrator,
|
|
172
217
|
hlc: {
|
|
173
218
|
getNextHLC() {
|
|
@@ -177,32 +222,20 @@ async function createDbWorker(config, opts) {
|
|
|
177
222
|
return;
|
|
178
223
|
}
|
|
179
224
|
},
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
updateLogTableName: "crdt_update_log"
|
|
184
|
-
}),
|
|
185
|
-
persistEvent: (event) => persistEvent(db, event),
|
|
186
|
-
getEventsBatch: (opts2) => getEventsBatch(db, opts2),
|
|
187
|
-
updateEvent: (syncId, update) => updateEvent(db, syncId, update)
|
|
225
|
+
db,
|
|
226
|
+
dbConfig: workerDbConfig,
|
|
227
|
+
eventHlcAccumulator: kvStore.createStringStoredValue("crdt.consistency.event_hlc_sum.v2", "")
|
|
188
228
|
});
|
|
189
229
|
createCrdtSyncProducer({
|
|
190
230
|
storage: crdtStorage,
|
|
191
231
|
broadcastEvents: (chunk) => {
|
|
192
232
|
broadcastChannels.responses.postMessage({
|
|
193
233
|
notificationType: "new-event-chunk-applied",
|
|
194
|
-
newSyncId: chunk.newSyncId
|
|
234
|
+
newSyncId: chunk.newSyncId,
|
|
235
|
+
eventHlcSum: chunk.eventHlcSum
|
|
195
236
|
});
|
|
196
237
|
}
|
|
197
238
|
});
|
|
198
|
-
const postState = () => {
|
|
199
|
-
broadcastChannels.responses.postMessage({
|
|
200
|
-
notificationType: "state-changed",
|
|
201
|
-
state: {
|
|
202
|
-
remoteState: remoteSource.getState()
|
|
203
|
-
}
|
|
204
|
-
});
|
|
205
|
-
};
|
|
206
239
|
const remoteSource = createRemoteSource({
|
|
207
240
|
kvStore,
|
|
208
241
|
crdtStorage,
|
|
@@ -211,38 +244,72 @@ async function createDbWorker(config, opts) {
|
|
|
211
244
|
remoteFactory: opts.createRemoteSource
|
|
212
245
|
});
|
|
213
246
|
remoteSource.goOnline();
|
|
214
|
-
|
|
247
|
+
const broadcastNotification = (notification) => {
|
|
248
|
+
broadcastChannels.responses.postMessage(notification);
|
|
249
|
+
};
|
|
250
|
+
const postState = () => {
|
|
251
|
+
broadcastNotification({
|
|
252
|
+
notificationType: "state-changed",
|
|
253
|
+
state: {
|
|
254
|
+
remoteState: remoteSource.getState()
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
};
|
|
258
|
+
const stateChangedSubscription = remoteSource.addEventListener("state-changed", () => {
|
|
215
259
|
postState();
|
|
216
260
|
});
|
|
261
|
+
const deSyncDetectedSubscription = remoteSource.addEventListener("de-sync-detected", (event) => {
|
|
262
|
+
broadcastNotification({
|
|
263
|
+
notificationType: "de-sync-detected",
|
|
264
|
+
reason: event.payload.reason
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
const remoteSchemaVersionMismatchSubscription = remoteSource.addEventListener(
|
|
268
|
+
"remote-schema-version-mismatch",
|
|
269
|
+
(event) => {
|
|
270
|
+
broadcastNotification({
|
|
271
|
+
notificationType: "remote-schema-version-mismatch",
|
|
272
|
+
remoteSchemaVersion: event.payload.remoteSchemaVersion,
|
|
273
|
+
localSchemaVersion: event.payload.localSchemaVersion
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
);
|
|
217
277
|
const rpcTarget = {
|
|
218
278
|
execute: (query) => db.execute(query),
|
|
219
279
|
getSnapshot: () => {
|
|
280
|
+
const appliedSyncId = getMaxSyncId(db, "pending");
|
|
220
281
|
db.execute("PRAGMA journal_mode=off", { loggerLevel: "system" });
|
|
221
282
|
const file = db.createSnapshot();
|
|
222
283
|
db.execute("PRAGMA journal_mode=WAL", { loggerLevel: "system" });
|
|
223
284
|
return {
|
|
224
285
|
file,
|
|
225
|
-
syncId:
|
|
286
|
+
syncId: appliedSyncId,
|
|
226
287
|
schemaVersion: migrator.currentSchemaVersion
|
|
227
288
|
};
|
|
228
289
|
},
|
|
229
290
|
postState,
|
|
230
291
|
pushTabEvents: (request) => {
|
|
231
|
-
crdtStorage.enqueueLocalEvents(request.events, request.nodeId);
|
|
292
|
+
const { beforeSyncId, afterSyncId } = crdtStorage.enqueueLocalEvents(request.events, request.nodeId);
|
|
232
293
|
return {
|
|
233
|
-
ok: true
|
|
294
|
+
ok: true,
|
|
295
|
+
beforeSyncId,
|
|
296
|
+
afterSyncId
|
|
234
297
|
};
|
|
235
298
|
},
|
|
236
299
|
pullEvents: (request) => {
|
|
237
300
|
return crdtStorage.getEventsBatch({
|
|
238
301
|
afterSyncId: request.afterSyncId,
|
|
239
302
|
status: "applied",
|
|
240
|
-
excludeNodeId: request.excludeNodeId,
|
|
303
|
+
excludeNodeId: request.excludeNodeId ?? "",
|
|
241
304
|
limit: 100
|
|
242
305
|
});
|
|
243
306
|
},
|
|
244
307
|
goOnline: () => remoteSource.goOnline(),
|
|
245
|
-
goOffline: () => remoteSource.goOffline("DISCONNECTED")
|
|
308
|
+
goOffline: () => remoteSource.goOffline("DISCONNECTED"),
|
|
309
|
+
requestReload: createReloadRequestHandler({
|
|
310
|
+
resetState,
|
|
311
|
+
broadcast: broadcastNotification
|
|
312
|
+
})
|
|
246
313
|
};
|
|
247
314
|
broadcastChannels.requests.onmessage = (event) => {
|
|
248
315
|
const message = event.data;
|
|
@@ -283,6 +350,9 @@ async function createDbWorker(config, opts) {
|
|
|
283
350
|
};
|
|
284
351
|
rpcTarget.postState();
|
|
285
352
|
return async () => {
|
|
353
|
+
stateChangedSubscription.unsubscribe();
|
|
354
|
+
deSyncDetectedSubscription.unsubscribe();
|
|
355
|
+
remoteSchemaVersionMismatchSubscription.unsubscribe();
|
|
286
356
|
await remoteSource.dispose();
|
|
287
357
|
broadcastChannels.requests.close();
|
|
288
358
|
broadcastChannels.responses.close();
|
|
@@ -339,54 +409,14 @@ async function startDbWorker(opts) {
|
|
|
339
409
|
});
|
|
340
410
|
self.close();
|
|
341
411
|
}
|
|
342
|
-
function
|
|
343
|
-
const result = db.executePrepared(
|
|
344
|
-
"get-
|
|
345
|
-
{},
|
|
346
|
-
(db2) => db2.selectFrom("worker.crdt_events").select((eb) => eb.fn.max("sync_id").as("sync_id")),
|
|
347
|
-
{ loggerLevel: "system" }
|
|
348
|
-
);
|
|
349
|
-
return result[0]?.sync_id ?? 0;
|
|
350
|
-
}
|
|
351
|
-
function persistEvent(db, event) {
|
|
352
|
-
db.executePrepared(
|
|
353
|
-
"persist-crdt-event",
|
|
354
|
-
event,
|
|
355
|
-
(db2, params) => db2.insertInto("worker.crdt_events").values({
|
|
356
|
-
type: params("type"),
|
|
357
|
-
dataset: params("dataset"),
|
|
358
|
-
item_id: params("item_id"),
|
|
359
|
-
payload: params("payload"),
|
|
360
|
-
schema_version: params("schema_version"),
|
|
361
|
-
sync_id: params("sync_id"),
|
|
362
|
-
status: params("status"),
|
|
363
|
-
timestamp: params("timestamp"),
|
|
364
|
-
origin: params("origin"),
|
|
365
|
-
source_node_id: params("source_node_id")
|
|
366
|
-
}),
|
|
367
|
-
{ loggerLevel: "system" }
|
|
368
|
-
);
|
|
369
|
-
}
|
|
370
|
-
function getEventsBatch(db, opts) {
|
|
371
|
-
return db.executeKysely(
|
|
372
|
-
(db2) => applyKyselyEventsBatchFilters(db2.selectFrom("worker.crdt_events").selectAll(), opts),
|
|
373
|
-
{ loggerLevel: "system" }
|
|
374
|
-
).rows;
|
|
375
|
-
}
|
|
376
|
-
function updateEvent(db, syncId, update) {
|
|
377
|
-
db.executePrepared(
|
|
378
|
-
"update-crdt-event",
|
|
379
|
-
{ syncId, ...update },
|
|
380
|
-
(db2, params) => db2.updateTable("worker.crdt_events").set({
|
|
381
|
-
status: params("status"),
|
|
382
|
-
schema_version: params("schema_version"),
|
|
383
|
-
type: params("type"),
|
|
384
|
-
dataset: params("dataset"),
|
|
385
|
-
item_id: params("item_id"),
|
|
386
|
-
payload: params("payload")
|
|
387
|
-
}).where("sync_id", "=", params("syncId")),
|
|
412
|
+
function getMaxSyncId(db, excludingStatus) {
|
|
413
|
+
const [result] = db.executePrepared(
|
|
414
|
+
"get-max-sync-id",
|
|
415
|
+
{ excludingStatus },
|
|
416
|
+
(db2, params) => db2.selectFrom("worker.crdt_events").where("status", "!=", params("excludingStatus")).select((eb) => eb.fn.max("sync_id").as("sync_id")),
|
|
388
417
|
{ loggerLevel: "system" }
|
|
389
418
|
);
|
|
419
|
+
return result?.sync_id ?? 0;
|
|
390
420
|
}
|
|
391
421
|
export {
|
|
392
422
|
createWsRemoteSource,
|
package/dist/worker.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/web-socket/ws-remote-source.ts","../src/worker-db/db-worker.ts"],"sourcesContent":["import type { SyncServerMessage, SyncServerRequest } from \"../server\";\nimport type { CreateRemoteSourceFactory } from \"../sqlite-crdt/crdt-sync-remote-source\";\nimport { createDeferredPromise, type DeferredPromise, jsonSafeParse } from \"../utils\";\nimport type { EventsPullRequest, EventsPushRequest, EventsPushResponse, GetEventsBatch } from \"../worker\";\n\ntype WsRemoteSourceConfig = {\n createWebSocket: () => Pick<WebSocket, \"send\" | \"onmessage\" | \"close\" | \"addEventListener\">;\n};\n\nexport const createWsRemoteSource = ({ createWebSocket }: WsRemoteSourceConfig): CreateRemoteSourceFactory => {\n return async ({ onEventsAvailable }) => {\n const socket = createWebSocket();\n\n const openPromise = createDeferredPromise<void>({\n timeout: 5000,\n onTimeout: () => {\n socket.close();\n },\n });\n socket.addEventListener(\"open\", () => {\n openPromise.resolve(undefined);\n });\n await openPromise.promise;\n\n const requestsMap = new Map<string, DeferredPromise<unknown>>();\n\n const pushEvents = async (request: EventsPushRequest): Promise<EventsPushResponse> => {\n const requestId = crypto.randomUUID();\n const promise = createDeferredPromise<EventsPushResponse>({ timeout: 5000 });\n requestsMap.set(requestId, promise as DeferredPromise<unknown>);\n\n const wsRequest: SyncServerRequest = {\n type: \"push-events\",\n requestId,\n nodeId: request.nodeId,\n events: request.events,\n };\n socket.send(JSON.stringify(wsRequest));\n\n return promise.promise;\n };\n\n const pullEvents = async (request: EventsPullRequest): Promise<GetEventsBatch> => {\n const requestId = crypto.randomUUID();\n const promise = createDeferredPromise<GetEventsBatch>({ timeout: 2000 });\n requestsMap.set(requestId, promise as DeferredPromise<unknown>);\n\n const wsRequest: SyncServerRequest = {\n type: \"pull-events\",\n requestId,\n afterSyncId: request.afterSyncId,\n excludeNodeId: request.excludeNodeId,\n };\n socket.send(JSON.stringify(wsRequest));\n\n return promise.promise;\n };\n\n socket.onmessage = (event) => {\n const result = jsonSafeParse<SyncServerMessage>(event.data);\n\n if (!result.success || !(\"type\" in result.data) || !result.data.type) {\n return;\n }\n\n const message = result.data;\n\n switch (message.type) {\n case \"events-pull-response\": {\n const promise = requestsMap.get(message.requestId);\n if (!promise) {\n return;\n }\n promise.resolve(message.data);\n requestsMap.delete(message.requestId);\n break;\n }\n case \"events-push-response\": {\n const promise = requestsMap.get(message.requestId);\n if (!promise) {\n return;\n }\n promise.resolve(message.data);\n requestsMap.delete(message.requestId);\n break;\n }\n case \"events-applied\":\n onEventsAvailable(message.newSyncId);\n break;\n default:\n message satisfies never;\n return;\n }\n };\n\n return {\n pushEvents,\n pullEvents,\n disconnect: () => {\n socket.close();\n },\n };\n };\n};\n","import sqlite3InitModule from \"@sqlite.org/sqlite-wasm\";\nimport type { Logger } from \"../logger\";\nimport { createMigrator, type SyncDbMigrator } from \"../migrations/migrator\";\nimport { applyWorkerDbSchema, type WorkerDbSchema } from \"../migrations/system-schema\";\nimport { createSQLiteCrdtApplyFunction } from \"../sqlite-crdt/apply-crdt-event\";\nimport type { SyncDbSchema } from \"../sqlite-crdt/crdt-schema\";\nimport {\n type CrdtStorage,\n createCrdtStorage,\n type EventUpdate,\n type GetEventsOptions,\n} from \"../sqlite-crdt/crdt-storage\";\nimport { createCrdtSyncProducer } from \"../sqlite-crdt/crdt-sync-producer\";\nimport { type CreateRemoteSourceFactory, createCrdtSyncRemoteSource } from \"../sqlite-crdt/crdt-sync-remote-source\";\nimport type { PersistedCrdtEvent } from \"../sqlite-crdt/crdt-table-schema\";\nimport { applyKyselyEventsBatchFilters } from \"../sqlite-crdt/events-batch-filters\";\nimport { createStoredValue } from \"../sqlite-crdt/stored-value\";\nimport { SQLiteDbWrapper } from \"../sqlite-db-wrapper\";\nimport { createSQLiteKvStore, type KvStore } from \"../sqlite-kv-store\";\nimport { createDeferredPromise } from \"../utils\";\nimport {\n createBroadcastChannels,\n isWorkerInitMessage,\n isWorkerRequestMessage,\n syncDbClientLockName,\n syncDbWorkerLockName,\n type WorkerConfig,\n type WorkerErrorResponseMessage,\n type WorkerResponseMessage,\n type WorkerRpc,\n} from \"./worker-common\";\n\nconst defaultLogger: Logger = (type, message, level = \"info\") => {\n const logMessage = `[${type}] ${message}`;\n switch (level) {\n case \"info\":\n console.log(logMessage);\n break;\n case \"warning\":\n console.warn(logMessage);\n break;\n case \"error\":\n console.error(logMessage);\n break;\n case \"trace\":\n console.trace(logMessage);\n break;\n }\n};\n\nasync function createDbWorker(config: WorkerConfig, opts: WorkerOptions) {\n const broadcastChannels = createBroadcastChannels(config.dbId);\n const logger = opts.logger ?? defaultLogger;\n\n const sqlite3 = await sqlite3InitModule();\n\n const pool = await sqlite3.installOpfsSAHPoolVfs({\n name: config.dbId,\n directory: `.${config.dbId}`,\n clearOnInit: config.clearOnInit,\n initialCapacity: 8,\n });\n\n const db = new SQLiteDbWrapper<WorkerDbSchema>({\n db: () => new pool.OpfsSAHPoolDb(`/${config.dbId}-main.db`),\n logger: logger,\n loggerPrefix: \"worker\",\n sqlite3,\n });\n\n db.execute(\"PRAGMA locking_mode=exclusive\", { loggerLevel: \"system\" });\n db.execute(\"PRAGMA journal_mode=WAL\", { loggerLevel: \"system\" });\n db.execute(\"PRAGMA synchronous=NORMAL\", { loggerLevel: \"system\" });\n\n db.execute(`ATTACH DATABASE '/${config.dbId}-worker.db' as worker`, { loggerLevel: \"system\" });\n db.execute(\"PRAGMA worker.locking_mode=exclusive\", { loggerLevel: \"system\" });\n db.execute(\"PRAGMA worker.journal_mode=WAL\", { loggerLevel: \"system\" });\n db.execute(\"PRAGMA worker.synchronous=NORMAL\", { loggerLevel: \"system\" });\n\n applyWorkerDbSchema(db);\n\n const kvStore = createSQLiteKvStore({\n db,\n metaTableName: \"worker.kv\",\n });\n\n const migrator = createMigrator({\n migrations: opts.syncDbSchema.migrations,\n schemaVersion: kvStore.createNumberStoredValue(\"schema-version\", -1),\n updateLogTableName: '\"crdt_update_log\"',\n });\n migrator.migrateDbToLatest({\n startTransaction: (callback) => {\n db.executeTransaction((tx) => callback({ execute: (sql, parameters) => tx.execute({ sql, parameters }) }));\n },\n });\n db.invalidateDbSchema();\n\n const localSyncId = createStoredValue({\n initialValue: getLatestSyncId(db),\n });\n\n const crdtStorage = createCrdtStorage({\n nodeId: config.clientId,\n syncId: localSyncId,\n migrator,\n hlc: {\n getNextHLC() {\n throw new Error(\"Worker DB should not call getNextHLC\");\n },\n mergeHLC() {\n return;\n },\n },\n transaction: (callback) => db.executeTransaction(callback),\n handleCrdtEventApply: createSQLiteCrdtApplyFunction({\n db,\n updateLogTableName: \"crdt_update_log\",\n }),\n persistEvent: (event) => persistEvent(db, event),\n getEventsBatch: (opts) => getEventsBatch(db, opts),\n updateEvent: (syncId, update) => updateEvent(db, syncId, update),\n });\n\n createCrdtSyncProducer({\n storage: crdtStorage,\n broadcastEvents: (chunk) => {\n broadcastChannels.responses.postMessage({\n notificationType: \"new-event-chunk-applied\",\n newSyncId: chunk.newSyncId,\n });\n },\n });\n\n const postState = () => {\n broadcastChannels.responses.postMessage({\n notificationType: \"state-changed\",\n state: {\n remoteState: remoteSource.getState(),\n },\n });\n };\n\n const remoteSource = createRemoteSource({\n kvStore,\n crdtStorage,\n migrator,\n clientId: config.clientId,\n remoteFactory: opts.createRemoteSource,\n });\n remoteSource.goOnline();\n\n remoteSource.addEventListener(\"state-changed\", () => {\n postState();\n });\n\n const rpcTarget: WorkerRpc = {\n execute: (query) => db.execute(query),\n getSnapshot: () => {\n db.execute(\"PRAGMA journal_mode=off\", { loggerLevel: \"system\" });\n const file = db.createSnapshot();\n db.execute(\"PRAGMA journal_mode=WAL\", { loggerLevel: \"system\" });\n return {\n file,\n syncId: localSyncId.current,\n schemaVersion: migrator.currentSchemaVersion,\n };\n },\n postState,\n pushTabEvents: (request) => {\n crdtStorage.enqueueLocalEvents(request.events, request.nodeId);\n return {\n ok: true,\n };\n },\n pullEvents: (request) => {\n return crdtStorage.getEventsBatch({\n afterSyncId: request.afterSyncId,\n status: \"applied\",\n excludeNodeId: request.excludeNodeId,\n limit: 100,\n });\n },\n goOnline: () => remoteSource.goOnline(),\n goOffline: () => remoteSource.goOffline(\"DISCONNECTED\"),\n };\n\n broadcastChannels.requests.onmessage = (event) => {\n const message = event.data;\n\n if (!isWorkerRequestMessage(message)) {\n return;\n }\n\n const sendError = (error: unknown) => {\n const response: WorkerErrorResponseMessage = {\n type: \"error-response\",\n requestId: message.requestId,\n error: error instanceof Error ? error.message : String(error),\n };\n broadcastChannels.responses.postMessage(response);\n };\n\n try {\n const method = rpcTarget[message.method] as () => ReturnType<WorkerRpc[keyof WorkerRpc]>;\n const data = method.apply(null, message.args as []);\n\n if (data instanceof Promise) {\n data\n .then((result) => {\n const response: WorkerResponseMessage = {\n type: \"response\",\n requestId: message.requestId,\n data: result,\n };\n broadcastChannels.responses.postMessage(response);\n })\n .catch(sendError);\n } else {\n const response: WorkerResponseMessage = {\n type: \"response\",\n requestId: message.requestId,\n data,\n };\n broadcastChannels.responses.postMessage(response);\n }\n } catch (error) {\n sendError(error);\n }\n };\n\n rpcTarget.postState();\n\n return async () => {\n await remoteSource.dispose();\n broadcastChannels.requests.close();\n broadcastChannels.responses.close();\n db.close();\n };\n}\n\ntype InitRemoteOptions = {\n kvStore: KvStore;\n clientId: string;\n crdtStorage: CrdtStorage;\n migrator: SyncDbMigrator;\n remoteFactory?: CreateRemoteSourceFactory;\n};\n\nfunction createRemoteSource({ kvStore, clientId, crdtStorage, migrator, remoteFactory }: InitRemoteOptions) {\n return createCrdtSyncRemoteSource({\n bufferSize: 50,\n pullSyncId: kvStore.createNumberStoredValue(\"pull-sync-id\", -1),\n pushSyncId: kvStore.createNumberStoredValue(\"push-sync-id\", -1),\n nodeId: clientId,\n storage: crdtStorage,\n migrator,\n remoteFactory,\n });\n}\n\nexport async function getWorkerConfig<Props = never>(): Promise<WorkerConfig<Props>> {\n let configSet = false;\n const responsePromise = createDeferredPromise<WorkerConfig>();\n\n self.onmessage = (event: MessageEvent<unknown>) => {\n if (configSet) {\n console.error(\"Worker config already set\");\n return;\n }\n\n const message = event.data;\n if (!isWorkerInitMessage(message)) {\n return;\n }\n\n responsePromise.resolve(message.config);\n configSet = true;\n };\n\n return responsePromise.promise;\n}\n\ntype WorkerOptions = {\n syncDbSchema: SyncDbSchema;\n logger?: Logger;\n createRemoteSource?: CreateRemoteSourceFactory;\n workerConfig?: WorkerConfig;\n};\n\nexport async function startDbWorker(opts: WorkerOptions) {\n const config = opts.workerConfig ?? (await getWorkerConfig());\n\n await navigator.locks.request(`${syncDbWorkerLockName}-${config.dbId}`, { mode: \"exclusive\" }, async (lock) => {\n if (!lock) {\n return;\n }\n\n const cleanup = await createDbWorker(config, opts);\n\n const clientLockName = `${syncDbClientLockName}-${config.dbId}`;\n await new Promise<void>((resolve) => {\n const interval = setInterval(async () => {\n const { held } = await navigator.locks.query();\n const hasClients = held?.some((l) => l.name === clientLockName && l.mode === \"shared\");\n if (!hasClients) {\n clearInterval(interval);\n resolve();\n }\n }, 5_000);\n });\n\n await cleanup();\n });\n\n self.close();\n}\n\nfunction getLatestSyncId(db: SQLiteDbWrapper<WorkerDbSchema>) {\n const result = db.executePrepared(\n \"get-latest-sync-id\",\n {},\n (db) => db.selectFrom(\"worker.crdt_events\").select((eb) => eb.fn.max(\"sync_id\").as(\"sync_id\")),\n { loggerLevel: \"system\" },\n );\n return result[0]?.sync_id ?? 0;\n}\n\nfunction persistEvent(db: SQLiteDbWrapper<WorkerDbSchema>, event: PersistedCrdtEvent) {\n db.executePrepared(\n \"persist-crdt-event\",\n event,\n (db, params) =>\n db.insertInto(\"worker.crdt_events\").values({\n type: params(\"type\"),\n dataset: params(\"dataset\"),\n item_id: params(\"item_id\"),\n payload: params(\"payload\"),\n schema_version: params(\"schema_version\"),\n sync_id: params(\"sync_id\"),\n status: params(\"status\"),\n timestamp: params(\"timestamp\"),\n origin: params(\"origin\"),\n source_node_id: params(\"source_node_id\"),\n }),\n { loggerLevel: \"system\" },\n );\n}\n\nfunction getEventsBatch(db: SQLiteDbWrapper<WorkerDbSchema>, opts: GetEventsOptions) {\n return db.executeKysely(\n (db) => applyKyselyEventsBatchFilters(db.selectFrom(\"worker.crdt_events\").selectAll(), opts),\n { loggerLevel: \"system\" },\n ).rows;\n}\n\nfunction updateEvent(db: SQLiteDbWrapper<WorkerDbSchema>, syncId: number, update: EventUpdate) {\n db.executePrepared(\n \"update-crdt-event\",\n { syncId, ...update },\n (db, params) =>\n db\n .updateTable(\"worker.crdt_events\")\n .set({\n status: params(\"status\"),\n schema_version: params(\"schema_version\"),\n type: params(\"type\"),\n dataset: params(\"dataset\"),\n item_id: params(\"item_id\"),\n payload: params(\"payload\"),\n })\n .where(\"sync_id\", \"=\", params(\"syncId\")),\n { loggerLevel: \"system\" },\n );\n}\n"],"mappings":"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","names":["opts","db"]}
|
|
1
|
+
{"version":3,"sources":["../src/web-socket/ws-remote-source.ts","../src/worker-db/db-worker.ts","../src/worker-db/storage-version.ts"],"sourcesContent":["import type { SyncServerMessage, SyncServerRequest } from \"../server\";\nimport type { CreateRemoteSourceFactory } from \"../sqlite-crdt/crdt-sync-remote-source\";\nimport { createDeferredPromise, type DeferredPromise, jsonSafeParse } from \"../utils\";\nimport type { EventsPullRequest, EventsPushRequest, EventsPushResponse, GetEventsBatch } from \"../worker\";\n\ntype WsRemoteSourceConfig = {\n createWebSocket: () => Pick<WebSocket, \"send\" | \"onmessage\" | \"close\" | \"addEventListener\">;\n};\n\nexport const createWsRemoteSource = ({ createWebSocket }: WsRemoteSourceConfig): CreateRemoteSourceFactory => {\n return async ({ onEventsAvailable }) => {\n const socket = createWebSocket();\n\n const openPromise = createDeferredPromise<void>({\n timeout: 5000,\n onTimeout: () => {\n socket.close();\n },\n });\n socket.addEventListener(\"open\", () => {\n openPromise.resolve(undefined);\n });\n await openPromise.promise;\n\n const requestsMap = new Map<string, DeferredPromise<unknown>>();\n\n const pushEvents = async (request: EventsPushRequest): Promise<EventsPushResponse> => {\n const requestId = crypto.randomUUID();\n const promise = createDeferredPromise<EventsPushResponse>({ timeout: 5000 });\n requestsMap.set(requestId, promise as DeferredPromise<unknown>);\n\n const wsRequest: SyncServerRequest = {\n type: \"push-events\",\n requestId,\n nodeId: request.nodeId,\n events: request.events,\n };\n socket.send(JSON.stringify(wsRequest));\n\n return promise.promise;\n };\n\n const pullEvents = async (request: EventsPullRequest): Promise<GetEventsBatch> => {\n const requestId = crypto.randomUUID();\n const promise = createDeferredPromise<GetEventsBatch>({ timeout: 2000 });\n requestsMap.set(requestId, promise as DeferredPromise<unknown>);\n\n const wsRequest: SyncServerRequest = {\n type: \"pull-events\",\n requestId,\n afterSyncId: request.afterSyncId,\n excludeNodeId: request.excludeNodeId,\n };\n socket.send(JSON.stringify(wsRequest));\n\n return promise.promise;\n };\n\n socket.onmessage = (event) => {\n const result = jsonSafeParse<SyncServerMessage>(event.data);\n\n if (!result.success || !(\"type\" in result.data) || !result.data.type) {\n return;\n }\n\n const message = result.data;\n\n switch (message.type) {\n case \"events-pull-response\": {\n const promise = requestsMap.get(message.requestId);\n if (!promise) {\n return;\n }\n promise.resolve(message.data);\n requestsMap.delete(message.requestId);\n break;\n }\n case \"events-push-response\": {\n const promise = requestsMap.get(message.requestId);\n if (!promise) {\n return;\n }\n promise.resolve(message.data);\n requestsMap.delete(message.requestId);\n break;\n }\n case \"events-applied\":\n onEventsAvailable({ newSyncId: message.newSyncId, remoteEventHlcSum: message.eventHlcSum });\n break;\n default:\n message satisfies never;\n return;\n }\n };\n\n return {\n pushEvents,\n pullEvents,\n disconnect: () => {\n socket.close();\n },\n };\n };\n};\n","import sqlite3InitModule from \"@sqlite.org/sqlite-wasm\";\nimport { xxhash } from \"../hash\";\nimport type { Logger } from \"../logger\";\nimport { createMigrator, type SyncDbMigrator } from \"../migrations/migrator\";\nimport { applyWorkerDbSchema, type WorkerDbSchema, workerDbConfig } from \"../migrations/system-schema\";\nimport type { SyncDbSchema } from \"../sqlite-crdt/crdt-schema\";\nimport { type CrdtStorage, createCrdtStorage } from \"../sqlite-crdt/crdt-storage\";\nimport { createCrdtSyncProducer } from \"../sqlite-crdt/crdt-sync-producer\";\nimport { type CreateRemoteSourceFactory, createCrdtSyncRemoteSource } from \"../sqlite-crdt/crdt-sync-remote-source\";\nimport type { CrdtEventStatus } from \"../sqlite-crdt/crdt-table-schema\";\nimport { SQLiteDbWrapper } from \"../sqlite-db-wrapper\";\nimport type { KvStore } from \"../sqlite-kv-store\";\nimport { createDeferredPromise } from \"../utils\";\nimport { createIdbResetStore, createReloadRequestHandler, createResetStateStore, type ResetStore } from \"./reset-state\";\nimport { createStorageVersionStore } from \"./storage-version\";\nimport {\n createBroadcastChannels,\n isWorkerInitMessage,\n isWorkerRequestMessage,\n syncDbClientLockName,\n syncDbWorkerLockName,\n type WorkerConfig,\n type WorkerErrorResponseMessage,\n type WorkerNotificationMessage,\n type WorkerResponseMessage,\n type WorkerRpc,\n} from \"./worker-common\";\n\nconst defaultLogger: Logger = (type, message, level = \"info\") => {\n const logMessage = `[${type}] ${message}`;\n switch (level) {\n case \"info\":\n console.log(logMessage);\n break;\n case \"warning\":\n console.warn(logMessage);\n break;\n case \"error\":\n console.error(logMessage);\n break;\n case \"trace\":\n console.trace(logMessage);\n break;\n }\n};\n\nasync function createDbWorker(config: WorkerConfig, opts: WorkerOptions) {\n const broadcastChannels = createBroadcastChannels(config.dbId);\n const logger = opts.logger ?? defaultLogger;\n\n const [sqlite3] = await Promise.all([sqlite3InitModule(), xxhash.ensureLoaded()]);\n\n const resetStore = opts.resetStore ?? createIdbResetStore();\n const resetState = createResetStateStore({\n store: resetStore,\n dbId: config.dbId,\n });\n const storageVersion = createStorageVersionStore({\n store: resetStore,\n dbId: config.dbId,\n appStorageVersion: opts.storageVersion,\n });\n const [pendingReset, isVersionMismatch] = await Promise.all([\n resetState.resolvePendingReset(),\n storageVersion.isVersionMismatch(),\n ]);\n\n if (isVersionMismatch) {\n logger(\"worker\", `Storage version mismatch — resetting local DB to ${storageVersion.currentVersion}`, \"warning\");\n }\n\n const pool = await sqlite3.installOpfsSAHPoolVfs({\n name: config.dbId,\n directory: `.${config.dbId}`,\n clearOnInit: !!pendingReset || isVersionMismatch,\n initialCapacity: 8,\n });\n\n const db = new SQLiteDbWrapper<WorkerDbSchema>({\n db: () => new pool.OpfsSAHPoolDb(`/${config.dbId}-main.db`),\n logger: logger,\n loggerPrefix: \"worker\",\n sqlite3,\n });\n\n db.execute(\"PRAGMA locking_mode=exclusive\", { loggerLevel: \"system\" });\n db.execute(\"PRAGMA journal_mode=WAL\", { loggerLevel: \"system\" });\n db.execute(\"PRAGMA synchronous=NORMAL\", { loggerLevel: \"system\" });\n\n db.execute(`ATTACH DATABASE '/${config.dbId}-worker.db' as worker`, { loggerLevel: \"system\" });\n db.execute(\"PRAGMA worker.locking_mode=exclusive\", { loggerLevel: \"system\" });\n db.execute(\"PRAGMA worker.journal_mode=WAL\", { loggerLevel: \"system\" });\n db.execute(\"PRAGMA worker.synchronous=NORMAL\", { loggerLevel: \"system\" });\n\n const { kvStore } = applyWorkerDbSchema(db);\n\n const migrator = createMigrator({\n migrations: opts.syncDbSchema.migrations,\n schemaVersion: kvStore.createNumberStoredValue(\"schema-version\", -1),\n updateLogTableName: workerDbConfig.updateLogTable.fullIdentifier,\n });\n migrator.migrateDbToLatest({\n startTransaction: (callback) => {\n db.executeTransaction((tx) => callback({ execute: (sql, parameters) => tx.execute({ sql, parameters }) }));\n },\n });\n db.invalidateDbSchema();\n\n // Record the applied reset epoch / storage version only after the wiped DB\n // initialized successfully, so a failed init can be retried by a later\n // elected worker, while a later election does not wipe again.\n if (pendingReset) {\n await resetState.markResetApplied(pendingReset.epoch);\n }\n if (isVersionMismatch) {\n await storageVersion.markCurrentVersionApplied();\n }\n\n const crdtStorage = createCrdtStorage({\n nodeId: config.clientId,\n initialLocalSyncId: getMaxSyncId(db, \"none\"),\n migrator,\n hlc: {\n getNextHLC() {\n throw new Error(\"Worker DB should not call getNextHLC\");\n },\n mergeHLC() {\n return;\n },\n },\n db,\n dbConfig: workerDbConfig,\n eventHlcAccumulator: kvStore.createStringStoredValue(\"crdt.consistency.event_hlc_sum.v2\", \"\"),\n });\n\n createCrdtSyncProducer({\n storage: crdtStorage,\n broadcastEvents: (chunk) => {\n broadcastChannels.responses.postMessage({\n notificationType: \"new-event-chunk-applied\",\n newSyncId: chunk.newSyncId,\n eventHlcSum: chunk.eventHlcSum,\n });\n },\n });\n\n const remoteSource = createRemoteSource({\n kvStore,\n crdtStorage,\n migrator,\n clientId: config.clientId,\n remoteFactory: opts.createRemoteSource,\n });\n remoteSource.goOnline();\n\n const broadcastNotification = (notification: WorkerNotificationMessage) => {\n broadcastChannels.responses.postMessage(notification);\n };\n\n const postState = () => {\n broadcastNotification({\n notificationType: \"state-changed\",\n state: {\n remoteState: remoteSource.getState(),\n },\n });\n };\n const stateChangedSubscription = remoteSource.addEventListener(\"state-changed\", () => {\n postState();\n });\n const deSyncDetectedSubscription = remoteSource.addEventListener(\"de-sync-detected\", (event) => {\n broadcastNotification({\n notificationType: \"de-sync-detected\",\n reason: event.payload.reason,\n });\n });\n const remoteSchemaVersionMismatchSubscription = remoteSource.addEventListener(\n \"remote-schema-version-mismatch\",\n (event) => {\n broadcastNotification({\n notificationType: \"remote-schema-version-mismatch\",\n remoteSchemaVersion: event.payload.remoteSchemaVersion,\n localSchemaVersion: event.payload.localSchemaVersion,\n });\n },\n );\n\n const rpcTarget: WorkerRpc = {\n execute: (query) => db.execute(query),\n getSnapshot: () => {\n const appliedSyncId = getMaxSyncId(db, \"pending\");\n db.execute(\"PRAGMA journal_mode=off\", { loggerLevel: \"system\" });\n const file = db.createSnapshot();\n db.execute(\"PRAGMA journal_mode=WAL\", { loggerLevel: \"system\" });\n return {\n file,\n syncId: appliedSyncId,\n schemaVersion: migrator.currentSchemaVersion,\n };\n },\n postState,\n pushTabEvents: (request) => {\n const { beforeSyncId, afterSyncId } = crdtStorage.enqueueLocalEvents(request.events, request.nodeId);\n return {\n ok: true,\n beforeSyncId,\n afterSyncId,\n };\n },\n pullEvents: (request) => {\n return crdtStorage.getEventsBatch({\n afterSyncId: request.afterSyncId,\n status: \"applied\",\n excludeNodeId: request.excludeNodeId ?? \"\",\n limit: 100,\n });\n },\n goOnline: () => remoteSource.goOnline(),\n goOffline: () => remoteSource.goOffline(\"DISCONNECTED\"),\n requestReload: createReloadRequestHandler({\n resetState,\n broadcast: broadcastNotification,\n }),\n };\n\n broadcastChannels.requests.onmessage = (event) => {\n const message = event.data;\n\n if (!isWorkerRequestMessage(message)) {\n return;\n }\n\n const sendError = (error: unknown) => {\n const response: WorkerErrorResponseMessage = {\n type: \"error-response\",\n requestId: message.requestId,\n error: error instanceof Error ? error.message : String(error),\n };\n broadcastChannels.responses.postMessage(response);\n };\n\n try {\n const method = rpcTarget[message.method] as () => ReturnType<WorkerRpc[keyof WorkerRpc]>;\n const data = method.apply(null, message.args as []);\n\n if (data instanceof Promise) {\n data\n .then((result) => {\n const response: WorkerResponseMessage = {\n type: \"response\",\n requestId: message.requestId,\n data: result,\n };\n broadcastChannels.responses.postMessage(response);\n })\n .catch(sendError);\n } else {\n const response: WorkerResponseMessage = {\n type: \"response\",\n requestId: message.requestId,\n data,\n };\n broadcastChannels.responses.postMessage(response);\n }\n } catch (error) {\n sendError(error);\n }\n };\n\n rpcTarget.postState();\n\n return async () => {\n stateChangedSubscription.unsubscribe();\n deSyncDetectedSubscription.unsubscribe();\n remoteSchemaVersionMismatchSubscription.unsubscribe();\n await remoteSource.dispose();\n broadcastChannels.requests.close();\n broadcastChannels.responses.close();\n db.close();\n };\n}\n\ntype InitRemoteOptions = {\n kvStore: KvStore;\n clientId: string;\n crdtStorage: CrdtStorage;\n migrator: SyncDbMigrator;\n remoteFactory?: CreateRemoteSourceFactory;\n};\n\nfunction createRemoteSource({ kvStore, clientId, crdtStorage, migrator, remoteFactory }: InitRemoteOptions) {\n return createCrdtSyncRemoteSource({\n bufferSize: 50,\n pullSyncId: kvStore.createNumberStoredValue(\"pull-sync-id\", -1),\n pushSyncId: kvStore.createNumberStoredValue(\"push-sync-id\", -1),\n nodeId: clientId,\n storage: crdtStorage,\n migrator,\n remoteFactory,\n });\n}\n\nexport async function getWorkerConfig<Props = never>(): Promise<WorkerConfig<Props>> {\n let configSet = false;\n const responsePromise = createDeferredPromise<WorkerConfig>();\n\n self.onmessage = (event: MessageEvent<unknown>) => {\n if (configSet) {\n console.error(\"Worker config already set\");\n return;\n }\n\n const message = event.data;\n if (!isWorkerInitMessage(message)) {\n return;\n }\n\n responsePromise.resolve(message.config);\n configSet = true;\n };\n\n return responsePromise.promise;\n}\n\ntype WorkerOptions = {\n syncDbSchema: SyncDbSchema;\n logger?: Logger;\n createRemoteSource?: CreateRemoteSourceFactory;\n workerConfig?: WorkerConfig;\n /** Durable storage for reset state. Defaults to an IndexedDB-backed store. */\n resetStore?: ResetStore;\n /**\n * App-provided storage version, combined with the library's internal storage\n * version. Bump it when deploying a code change that old persisted local DBs\n * cannot survive — on mismatch the elected worker wipes the local DB on startup.\n */\n storageVersion?: string;\n};\n\nexport async function startDbWorker(opts: WorkerOptions) {\n const config = opts.workerConfig ?? (await getWorkerConfig());\n\n await navigator.locks.request(`${syncDbWorkerLockName}-${config.dbId}`, { mode: \"exclusive\" }, async (lock) => {\n if (!lock) {\n return;\n }\n\n const cleanup = await createDbWorker(config, opts);\n\n const clientLockName = `${syncDbClientLockName}-${config.dbId}`;\n await new Promise<void>((resolve) => {\n const interval = setInterval(async () => {\n const { held } = await navigator.locks.query();\n const hasClients = held?.some((l) => l.name === clientLockName && l.mode === \"shared\");\n if (!hasClients) {\n clearInterval(interval);\n resolve();\n }\n }, 5_000);\n });\n\n await cleanup();\n });\n\n self.close();\n}\n\nfunction getMaxSyncId(db: SQLiteDbWrapper<WorkerDbSchema>, excludingStatus: \"none\" | \"pending\") {\n const [result] = db.executePrepared(\n \"get-max-sync-id\",\n { excludingStatus: excludingStatus as CrdtEventStatus },\n (db, params) =>\n db\n .selectFrom(\"worker.crdt_events\")\n .where(\"status\", \"!=\", params(\"excludingStatus\"))\n .select((eb) => eb.fn.max(\"sync_id\").as(\"sync_id\")),\n { loggerLevel: \"system\" },\n );\n\n return result?.sync_id ?? 0;\n}\n","import type { ResetStore } from \"./reset-state\";\n\n/**\n * Internal persisted-storage format version. Bump when the library changes\n * the worker DB layout in a way old persisted databases cannot survive —\n * every client resets its local DB on the next worker start.\n */\nexport const LIB_STORAGE_VERSION = 1;\n\nconst storageVersionKey = (dbId: string) => `sqlite-sync-storage-version-${dbId}`;\n\nexport function formatStorageVersion(appStorageVersion: string | undefined): string {\n return appStorageVersion === undefined\n ? `lib-v${LIB_STORAGE_VERSION}`\n : `lib-v${LIB_STORAGE_VERSION}:app-${appStorageVersion}`;\n}\n\ntype StorageVersionStoreOptions = {\n store: ResetStore;\n dbId: string;\n /** Dev-provided app storage version, combined with the internal lib version. */\n appStorageVersion?: string;\n};\n\nexport type StorageVersionStore = ReturnType<typeof createStorageVersionStore>;\n\n/**\n * Worker-owned durable storage version. The current version combines the\n * internal lib version with the dev-provided app version; when the stored\n * version does not match (including a missing record), the elected worker\n * initializes with `clearOnInit: true`. Wiping on a missing record is\n * harmless for fresh installs and correctly resets databases persisted\n * before versioning existed.\n */\nexport function createStorageVersionStore({ store, dbId, appStorageVersion }: StorageVersionStoreOptions) {\n const key = storageVersionKey(dbId);\n const currentVersion = formatStorageVersion(appStorageVersion);\n\n return {\n currentVersion,\n async isVersionMismatch(): Promise<boolean> {\n const storedVersion = await store.get<string>(key);\n return storedVersion !== currentVersion;\n },\n /**\n * Record the current version. Must be called only after the worker has\n * successfully initialized with the wiped DB, so a failed init can be\n * retried by a later elected worker.\n */\n async markCurrentVersionApplied(): Promise<void> {\n await store.set(key, currentVersion);\n },\n };\n}\n"],"mappings":"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","names":["db"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sqlite-sync/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "SQLite synchronization library with CRDT support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -38,18 +38,22 @@
|
|
|
38
38
|
],
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@sqlite.org/sqlite-wasm": "^3.51.2-build5",
|
|
41
|
+
"idb-keyval": "^6.2.5",
|
|
41
42
|
"kysely": "^0.28.10",
|
|
42
43
|
"retry-as-promised": "^7.1.1",
|
|
44
|
+
"xxhash-wasm": "^1.1.0",
|
|
43
45
|
"zod": "^4.3.6"
|
|
44
46
|
},
|
|
45
47
|
"devDependencies": {
|
|
46
48
|
"@types/node": "^22.10.2",
|
|
47
49
|
"tsup": "^8.3.5",
|
|
48
|
-
"typescript": "~5.9.3"
|
|
50
|
+
"typescript": "~5.9.3",
|
|
51
|
+
"vitest": "^4.0.18"
|
|
49
52
|
},
|
|
50
53
|
"scripts": {
|
|
51
54
|
"build": "tsup",
|
|
52
55
|
"dev": "tsup --watch",
|
|
53
|
-
"
|
|
56
|
+
"test": "vitest run",
|
|
57
|
+
"typecheck": "tsgo --noEmit"
|
|
54
58
|
}
|
|
55
59
|
}
|