@sqlite-sync/core 0.8.2 → 0.9.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/index-Ct_YbBmV.d.ts +1159 -0
- package/dist/index.d.ts +2 -351
- package/dist/index.js +904 -1285
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +2 -0
- package/dist/internal.js +3 -0
- package/dist/make-crdt-table-BvsEnvu-.js +107 -0
- package/dist/make-crdt-table-BvsEnvu-.js.map +1 -0
- package/dist/reset-state-D3nsfx_t.js +2018 -0
- package/dist/reset-state-D3nsfx_t.js.map +1 -0
- package/dist/server.d.ts +65 -67
- package/dist/server.js +30 -32
- package/dist/server.js.map +1 -1
- package/dist/system-schema-CkglcfTS.js +185 -0
- package/dist/system-schema-CkglcfTS.js.map +1 -0
- package/dist/utils-Dr5JwcTP.js +143 -0
- package/dist/utils-Dr5JwcTP.js.map +1 -0
- package/dist/worker.d.ts +35 -37
- package/dist/worker.js +399 -479
- package/dist/worker.js.map +1 -1
- package/package.json +10 -6
- package/dist/chunk-GMF45NFH.js +0 -2298
- package/dist/chunk-GMF45NFH.js.map +0 -1
- package/dist/chunk-NHT3ELMN.js +0 -160
- package/dist/chunk-NHT3ELMN.js.map +0 -1
- package/dist/crdt-sync-remote-source-CnB4p4Ry.d.ts +0 -713
- package/dist/reset-state-D6UslXaj.d.ts +0 -19
package/dist/index.js
CHANGED
|
@@ -1,1330 +1,949 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
RESET_REQUEST_TTL_MS,
|
|
6
|
-
SQLiteDbWrapper,
|
|
7
|
-
applyMemoryDbSchema,
|
|
8
|
-
applyWorkerDbSchema,
|
|
9
|
-
baseSystemMigrations,
|
|
10
|
-
compareHLC,
|
|
11
|
-
createBroadcastChannels,
|
|
12
|
-
createCrdtApplyFunction,
|
|
13
|
-
createCrdtStorage,
|
|
14
|
-
createCrdtSyncProducer,
|
|
15
|
-
createCrdtSyncRemoteSource,
|
|
16
|
-
createExportData,
|
|
17
|
-
createImportData,
|
|
18
|
-
createKvStoreTableQuery,
|
|
19
|
-
createMigrations,
|
|
20
|
-
createMigrator,
|
|
21
|
-
createSQLiteCrdtApplyFunction,
|
|
22
|
-
createSQLiteKvStore,
|
|
23
|
-
createStoredValue,
|
|
24
|
-
createSystemDbConfig,
|
|
25
|
-
deserializeHLC,
|
|
26
|
-
dummyKysely,
|
|
27
|
-
formatSchemaVerificationIssues,
|
|
28
|
-
introspectDb,
|
|
29
|
-
isNoOpCrdtEventPayload,
|
|
30
|
-
isWorkerErrorResponseMessage,
|
|
31
|
-
isWorkerNotificationMessage,
|
|
32
|
-
isWorkerResponseMessage,
|
|
33
|
-
memoryDbConfig,
|
|
34
|
-
runSystemMigrations,
|
|
35
|
-
serializeHLC,
|
|
36
|
-
startPerformanceLogger,
|
|
37
|
-
syncDbClientLockName,
|
|
38
|
-
validateNewCrdtEvent,
|
|
39
|
-
verifySyncSchema,
|
|
40
|
-
xxhash
|
|
41
|
-
} from "./chunk-GMF45NFH.js";
|
|
42
|
-
import {
|
|
43
|
-
TypedBroadcastChannel,
|
|
44
|
-
TypedEvent,
|
|
45
|
-
createDeferredPromise,
|
|
46
|
-
createTypedEventTarget,
|
|
47
|
-
generateId,
|
|
48
|
-
jsonSafeParse,
|
|
49
|
-
noop,
|
|
50
|
-
quoteId,
|
|
51
|
-
tryCatch,
|
|
52
|
-
tryCatchAsync
|
|
53
|
-
} from "./chunk-NHT3ELMN.js";
|
|
54
|
-
|
|
55
|
-
// src/memory-db/sqlite-reactive-db.ts
|
|
1
|
+
import { A as serializeHLC, C as createMigrator, D as HLCCounter, E as introspectDb, M as createExportData, N as createImportData, O as compareHLC, P as dummyKysely, S as createMigrations, T as startPerformanceLogger, _ as createSQLiteCrdtApplyFunction, a as createBroadcastChannels, b as CrdtEventValidationError, c as isWorkerNotificationMessage, d as syncDbClientLockName, g as createCrdtApplyFunction, h as createCrdtStorage, j as xxhash, k as deserializeHLC, m as createCrdtSyncProducer, o as isWorkerErrorResponseMessage, p as createCrdtSyncRemoteSource, t as RESET_REQUEST_TTL_MS, u as isWorkerResponseMessage, v as formatSchemaVerificationIssues, w as SQLiteDbWrapper, x as validateNewCrdtEvent, y as verifySyncSchema } from "./reset-state-D3nsfx_t.js";
|
|
2
|
+
import { c as noop, d as tryCatch, f as tryCatchAsync, i as createTypedEventTarget, n as TypedEvent, o as generateId, r as createDeferredPromise, s as jsonSafeParse, t as TypedBroadcastChannel, u as quoteId } from "./utils-Dr5JwcTP.js";
|
|
3
|
+
import { a as memoryDbConfig, c as createKvStoreTableQuery, d as CRDT_EVENT_NO_OP_PAYLOAD, f as isNoOpCrdtEventPayload, l as createSQLiteKvStore, n as applyWorkerDbSchema, t as applyMemoryDbSchema, u as createStoredValue } from "./system-schema-CkglcfTS.js";
|
|
4
|
+
import { a as registerCrdtIntentDrainer, i as makeCrdtTable } from "./make-crdt-table-BvsEnvu-.js";
|
|
56
5
|
import sqlite3InitModule from "@sqlite.org/sqlite-wasm";
|
|
57
|
-
|
|
58
|
-
// src/bound-map.ts
|
|
6
|
+
//#region src/bound-map.ts
|
|
59
7
|
var BoundMap = class {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
this.map.forEach((value, key) => {
|
|
96
|
-
onRemove(key, value);
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
this.map.clear();
|
|
100
|
-
};
|
|
8
|
+
map = /* @__PURE__ */ new Map();
|
|
9
|
+
maxSize;
|
|
10
|
+
onRemove;
|
|
11
|
+
constructor(opts) {
|
|
12
|
+
this.maxSize = opts.maxSize;
|
|
13
|
+
this.onRemove = opts.onRemove;
|
|
14
|
+
}
|
|
15
|
+
set = (key, value) => {
|
|
16
|
+
if (this.onRemove && this.map.has(key)) {
|
|
17
|
+
const old = this.map.get(key);
|
|
18
|
+
this.map.set(key, value);
|
|
19
|
+
this.onRemove(key, old);
|
|
20
|
+
} else this.map.set(key, value);
|
|
21
|
+
if (this.map.size > this.maxSize) {
|
|
22
|
+
const firstKey = this.map.keys().next().value;
|
|
23
|
+
this.delete(firstKey);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
get = (key) => {
|
|
27
|
+
return this.map.get(key);
|
|
28
|
+
};
|
|
29
|
+
delete = (key) => {
|
|
30
|
+
if (this.onRemove && this.map.has(key)) {
|
|
31
|
+
const value = this.map.get(key);
|
|
32
|
+
this.map.delete(key);
|
|
33
|
+
this.onRemove(key, value);
|
|
34
|
+
} else this.map.delete(key);
|
|
35
|
+
};
|
|
36
|
+
clear = () => {
|
|
37
|
+
const onRemove = this.onRemove;
|
|
38
|
+
if (onRemove) this.map.forEach((value, key) => {
|
|
39
|
+
onRemove(key, value);
|
|
40
|
+
});
|
|
41
|
+
this.map.clear();
|
|
42
|
+
};
|
|
101
43
|
};
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region src/memory-db/sqlite-reactive-db.ts
|
|
46
|
+
let sqliteModule = null;
|
|
105
47
|
function createSQLiteReactiveDb(opts) {
|
|
106
|
-
|
|
48
|
+
return SQLiteReactiveDb.create(opts);
|
|
107
49
|
}
|
|
108
|
-
var SQLiteReactiveDb = class
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
registerDbHooks() {
|
|
370
|
-
let updateQueue = /* @__PURE__ */ new Set();
|
|
371
|
-
this.sqlite3.capi.sqlite3_update_hook(
|
|
372
|
-
this.db.ensureDb,
|
|
373
|
-
(_ctx, _opId, _db, table) => {
|
|
374
|
-
updateQueue.add(table);
|
|
375
|
-
},
|
|
376
|
-
0
|
|
377
|
-
);
|
|
378
|
-
this.sqlite3.capi.sqlite3_rollback_hook(
|
|
379
|
-
this.db.ensureDb,
|
|
380
|
-
() => {
|
|
381
|
-
if (updateQueue.size === 0) {
|
|
382
|
-
return 0;
|
|
383
|
-
}
|
|
384
|
-
updateQueue.clear();
|
|
385
|
-
this.eventTarget.dispatchEvent("transaction-rolled-back", void 0);
|
|
386
|
-
return 0;
|
|
387
|
-
},
|
|
388
|
-
0
|
|
389
|
-
);
|
|
390
|
-
this.sqlite3.capi.sqlite3_commit_hook(
|
|
391
|
-
this.db.ensureDb,
|
|
392
|
-
() => {
|
|
393
|
-
if (updateQueue.size === 0) {
|
|
394
|
-
return 0;
|
|
395
|
-
}
|
|
396
|
-
const tables = updateQueue;
|
|
397
|
-
updateQueue = /* @__PURE__ */ new Set();
|
|
398
|
-
this.eventTarget.dispatchEvent("transaction-committed", void 0);
|
|
399
|
-
queueMicrotask(() => {
|
|
400
|
-
this.notifyTableSubscribers(tables);
|
|
401
|
-
});
|
|
402
|
-
return 0;
|
|
403
|
-
},
|
|
404
|
-
0
|
|
405
|
-
);
|
|
406
|
-
}
|
|
407
|
-
createSnapshot() {
|
|
408
|
-
const perf = startPerformanceLogger(this.logger);
|
|
409
|
-
const snapshot = this.sqlite3.capi.sqlite3_js_db_export(this.db.ensureDb);
|
|
410
|
-
perf.logEnd("createSnapshot", `snapshot size: ${snapshot.byteLength}`, "info");
|
|
411
|
-
return snapshot;
|
|
412
|
-
}
|
|
413
|
-
useSnapshot(snapshot) {
|
|
414
|
-
this.db.useSnapshot(snapshot);
|
|
415
|
-
this.notifyTableSubscribers();
|
|
416
|
-
}
|
|
417
|
-
dispose() {
|
|
418
|
-
for (const entries of this.sharedLiveQueries.values()) {
|
|
419
|
-
for (const entry of entries) {
|
|
420
|
-
this.cancelSharedLiveQueryCleanup(entry);
|
|
421
|
-
entry.unsubscribeFromLiveQuery?.();
|
|
422
|
-
entry.unsubscribeFromLiveQuery = null;
|
|
423
|
-
entry.listeners.clear();
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
this.sharedLiveQueries.clear();
|
|
427
|
-
this.liveQueryStatements.clear();
|
|
428
|
-
if (this.tablesUsedStatement) {
|
|
429
|
-
this.tablesUsedStatement.finalize();
|
|
430
|
-
this.tablesUsedStatement = null;
|
|
431
|
-
}
|
|
432
|
-
this.db.close();
|
|
433
|
-
}
|
|
50
|
+
var SQLiteReactiveDb = class SQLiteReactiveDb {
|
|
51
|
+
db;
|
|
52
|
+
sqlite3;
|
|
53
|
+
logger;
|
|
54
|
+
tablesUsedStatement = null;
|
|
55
|
+
eventTarget = createTypedEventTarget();
|
|
56
|
+
mutationVersion = 0;
|
|
57
|
+
tableMutationVersions = /* @__PURE__ */ new Map();
|
|
58
|
+
constructor(sqlite3, logger) {
|
|
59
|
+
this.sqlite3 = sqlite3;
|
|
60
|
+
this.logger = logger;
|
|
61
|
+
this.db = new SQLiteDbWrapper({
|
|
62
|
+
db: () => new sqlite3.oo1.DB({ filename: ":memory:" }),
|
|
63
|
+
logger: this.logger,
|
|
64
|
+
loggerPrefix: "memory",
|
|
65
|
+
sqlite3
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
static async create(opts) {
|
|
69
|
+
const logger = opts.logger;
|
|
70
|
+
const perf = startPerformanceLogger(logger, "system");
|
|
71
|
+
if (!sqliteModule) sqliteModule = await sqlite3InitModule();
|
|
72
|
+
const db = new SQLiteReactiveDb(sqliteModule, logger);
|
|
73
|
+
if (opts.snapshot) db.useSnapshot(opts.snapshot);
|
|
74
|
+
db.registerDbHooks();
|
|
75
|
+
perf.logEnd("createSQLiteMemoryDb", "success", "system");
|
|
76
|
+
return db;
|
|
77
|
+
}
|
|
78
|
+
liveQueryStatements = new BoundMap({
|
|
79
|
+
maxSize: 100,
|
|
80
|
+
onRemove(_, value) {
|
|
81
|
+
value.finalize();
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
sharedLiveQueries = /* @__PURE__ */ new Map();
|
|
85
|
+
createLiveQuery(query) {
|
|
86
|
+
const fetchRows = (parameters) => {
|
|
87
|
+
let statement = this.liveQueryStatements.get(query.sql);
|
|
88
|
+
if (!statement) {
|
|
89
|
+
statement = this.db.prepare(query.sql);
|
|
90
|
+
this.liveQueryStatements.set(query.sql, statement);
|
|
91
|
+
}
|
|
92
|
+
return statement.execute(parameters);
|
|
93
|
+
};
|
|
94
|
+
let rows = null;
|
|
95
|
+
const getRows = () => {
|
|
96
|
+
if (!rows) rows = fetchRows(query.parameters);
|
|
97
|
+
return rows;
|
|
98
|
+
};
|
|
99
|
+
let subscriber = null;
|
|
100
|
+
let lastParameters = query.parameters;
|
|
101
|
+
const refresh = (parameters) => {
|
|
102
|
+
if (parameters) lastParameters = parameters;
|
|
103
|
+
rows = fetchRows(lastParameters);
|
|
104
|
+
subscriber?.();
|
|
105
|
+
};
|
|
106
|
+
const subscribe = (onchange) => {
|
|
107
|
+
if (subscriber) throw new Error("Subscriber already exists");
|
|
108
|
+
subscriber = onchange;
|
|
109
|
+
const subscription = this.subscribeToQueryChanges({
|
|
110
|
+
sql: query.sql,
|
|
111
|
+
onDataChange: refresh
|
|
112
|
+
});
|
|
113
|
+
return () => {
|
|
114
|
+
subscription.unsubscribe();
|
|
115
|
+
subscriber = null;
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
return {
|
|
119
|
+
getRows,
|
|
120
|
+
refresh,
|
|
121
|
+
subscribe
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
getSharedLiveQuery(query) {
|
|
125
|
+
const existingEntry = this.sharedLiveQueries.get(query.sql)?.find((entry) => parametersAreEqual(entry.parameters, query.parameters));
|
|
126
|
+
if (existingEntry) {
|
|
127
|
+
if (existingEntry.listeners.size === 0) this.scheduleSharedLiveQueryCleanup(existingEntry);
|
|
128
|
+
return existingEntry;
|
|
129
|
+
}
|
|
130
|
+
const liveQuery = this.createLiveQuery(query);
|
|
131
|
+
let hasDetachedFromLiveQuery = false;
|
|
132
|
+
const entry = {
|
|
133
|
+
sql: query.sql,
|
|
134
|
+
parameters: query.parameters,
|
|
135
|
+
listeners: /* @__PURE__ */ new Set(),
|
|
136
|
+
unsubscribeFromLiveQuery: null,
|
|
137
|
+
cleanupTimeout: null,
|
|
138
|
+
getRows: () => liveQuery.getRows(),
|
|
139
|
+
refresh: () => {
|
|
140
|
+
liveQuery.refresh();
|
|
141
|
+
},
|
|
142
|
+
getSubscriberCount: () => entry.listeners.size,
|
|
143
|
+
subscribe: (onchange) => {
|
|
144
|
+
this.cancelSharedLiveQueryCleanup(entry);
|
|
145
|
+
this.registerSharedLiveQueryEntry(entry);
|
|
146
|
+
entry.listeners.add(onchange);
|
|
147
|
+
if (!entry.unsubscribeFromLiveQuery) {
|
|
148
|
+
entry.unsubscribeFromLiveQuery = liveQuery.subscribe(() => {
|
|
149
|
+
for (const listener of entry.listeners) listener();
|
|
150
|
+
});
|
|
151
|
+
if (hasDetachedFromLiveQuery) {
|
|
152
|
+
hasDetachedFromLiveQuery = false;
|
|
153
|
+
liveQuery.refresh();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return () => {
|
|
157
|
+
entry.listeners.delete(onchange);
|
|
158
|
+
if (entry.listeners.size === 0) {
|
|
159
|
+
entry.unsubscribeFromLiveQuery?.();
|
|
160
|
+
entry.unsubscribeFromLiveQuery = null;
|
|
161
|
+
hasDetachedFromLiveQuery = true;
|
|
162
|
+
this.scheduleSharedLiveQueryCleanup(entry);
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
this.registerSharedLiveQueryEntry(entry);
|
|
168
|
+
this.scheduleSharedLiveQueryCleanup(entry);
|
|
169
|
+
return entry;
|
|
170
|
+
}
|
|
171
|
+
registerSharedLiveQueryEntry(entry) {
|
|
172
|
+
const matchingSqlEntries = this.sharedLiveQueries.get(entry.sql) ?? [];
|
|
173
|
+
if (!matchingSqlEntries.includes(entry)) {
|
|
174
|
+
matchingSqlEntries.push(entry);
|
|
175
|
+
this.sharedLiveQueries.set(entry.sql, matchingSqlEntries);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
getSharedLiveQueriesSnapshot() {
|
|
179
|
+
const snapshot = [];
|
|
180
|
+
for (const entries of this.sharedLiveQueries.values()) for (const entry of entries) snapshot.push({
|
|
181
|
+
sql: entry.sql,
|
|
182
|
+
parameters: entry.parameters,
|
|
183
|
+
subscriberCount: entry.listeners.size
|
|
184
|
+
});
|
|
185
|
+
return snapshot;
|
|
186
|
+
}
|
|
187
|
+
scheduleSharedLiveQueryCleanup(entry) {
|
|
188
|
+
this.cancelSharedLiveQueryCleanup(entry);
|
|
189
|
+
entry.cleanupTimeout = setTimeout(() => {
|
|
190
|
+
entry.cleanupTimeout = null;
|
|
191
|
+
this.releaseSharedLiveQuery(entry);
|
|
192
|
+
}, 0);
|
|
193
|
+
}
|
|
194
|
+
cancelSharedLiveQueryCleanup(entry) {
|
|
195
|
+
if (entry.cleanupTimeout) {
|
|
196
|
+
clearTimeout(entry.cleanupTimeout);
|
|
197
|
+
entry.cleanupTimeout = null;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
releaseSharedLiveQuery(entry) {
|
|
201
|
+
if (entry.listeners.size > 0 || entry.unsubscribeFromLiveQuery) return;
|
|
202
|
+
const matchingSqlEntries = this.sharedLiveQueries.get(entry.sql);
|
|
203
|
+
if (!matchingSqlEntries) return;
|
|
204
|
+
const nextEntries = matchingSqlEntries.filter((candidate) => candidate !== entry);
|
|
205
|
+
if (nextEntries.length > 0) this.sharedLiveQueries.set(entry.sql, nextEntries);
|
|
206
|
+
else this.sharedLiveQueries.delete(entry.sql);
|
|
207
|
+
}
|
|
208
|
+
subscribeToQueryChanges(params) {
|
|
209
|
+
const { sql, onDataChange } = params;
|
|
210
|
+
const tables = this.getTablesUsed(sql);
|
|
211
|
+
const readTables = /* @__PURE__ */ new Set();
|
|
212
|
+
for (const table of tables) if (!readTables.has(table.name)) readTables.add(table.name);
|
|
213
|
+
else if (table.isWrite) throw new Error("This query writes and reads from the same table. This may cause infinite loops.");
|
|
214
|
+
const notifyDataChange = createDebouncedCallback(() => {
|
|
215
|
+
onDataChange();
|
|
216
|
+
}, 30);
|
|
217
|
+
for (const table of readTables) this.eventTarget.addEventListener(`table:${table}`, notifyDataChange);
|
|
218
|
+
this.eventTarget.addEventListener("any-table-changed", notifyDataChange);
|
|
219
|
+
return { unsubscribe: () => {
|
|
220
|
+
for (const table of readTables) this.eventTarget.removeEventListener(`table:${table}`, notifyDataChange);
|
|
221
|
+
this.eventTarget.removeEventListener("any-table-changed", notifyDataChange);
|
|
222
|
+
} };
|
|
223
|
+
}
|
|
224
|
+
subscribeToTableChanges(table, onChanges) {
|
|
225
|
+
this.eventTarget.addEventListener(`table:${table}`, onChanges);
|
|
226
|
+
this.eventTarget.addEventListener("any-table-changed", onChanges);
|
|
227
|
+
return { unsubscribe: () => {
|
|
228
|
+
this.eventTarget.removeEventListener(`table:${table}`, onChanges);
|
|
229
|
+
this.eventTarget.removeEventListener("any-table-changed", onChanges);
|
|
230
|
+
} };
|
|
231
|
+
}
|
|
232
|
+
getTablesUsed(query) {
|
|
233
|
+
if (!this.tablesUsedStatement) this.tablesUsedStatement = this.db.prepare("select t.tbl_name as name, u.wr as isWrite from tables_used(?) as u inner join sqlite_master as t on t.name = u.name where u.schema = 'main'", { loggerLevel: "system" });
|
|
234
|
+
const tables = this.tablesUsedStatement.execute([query]);
|
|
235
|
+
if (tables.length === 0 && query.toLowerCase().includes("delete")) tables.push(...this.getClearedTables(query));
|
|
236
|
+
return tables;
|
|
237
|
+
}
|
|
238
|
+
getClearedTables(query) {
|
|
239
|
+
const operations = this.db.execute(`EXPLAIN ${query.split(";")[0]}`, { loggerLevel: "system" }).rows;
|
|
240
|
+
const clearedTablesRootPages = /* @__PURE__ */ new Set();
|
|
241
|
+
for (const operation of operations) if (operation.opcode === "Clear" && operation.p2 === 0) clearedTablesRootPages.add(operation.p1);
|
|
242
|
+
if (clearedTablesRootPages.size === 0) return [];
|
|
243
|
+
return this.db.execute(`select t.tbl_name as name, true as isWrite from sqlite_master as t where t.rootpage in (${Array.from(clearedTablesRootPages).join(",")})`, { loggerLevel: "system" }).rows;
|
|
244
|
+
}
|
|
245
|
+
addEventListener(type, listener) {
|
|
246
|
+
this.eventTarget.addEventListener(type, listener);
|
|
247
|
+
}
|
|
248
|
+
removeEventListener(type, listener) {
|
|
249
|
+
this.eventTarget.removeEventListener(type, listener);
|
|
250
|
+
}
|
|
251
|
+
getTableMutationVersion(table) {
|
|
252
|
+
return this.tableMutationVersions.get(table) ?? 0;
|
|
253
|
+
}
|
|
254
|
+
notifyTableSubscribers(tables = null) {
|
|
255
|
+
if (!tables) {
|
|
256
|
+
this.eventTarget.dispatchEvent("any-table-changed", void 0);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
for (const table of tables) this.eventTarget.dispatchEvent(`table:${table}`, void 0);
|
|
260
|
+
}
|
|
261
|
+
registerDbHooks() {
|
|
262
|
+
let updateQueue = /* @__PURE__ */ new Set();
|
|
263
|
+
this.sqlite3.capi.sqlite3_update_hook(this.db.ensureDb, (_ctx, _opId, _db, table) => {
|
|
264
|
+
updateQueue.add(table);
|
|
265
|
+
this.tableMutationVersions.set(table, ++this.mutationVersion);
|
|
266
|
+
}, 0);
|
|
267
|
+
this.sqlite3.capi.sqlite3_rollback_hook(this.db.ensureDb, () => {
|
|
268
|
+
if (updateQueue.size === 0) return 0;
|
|
269
|
+
updateQueue.clear();
|
|
270
|
+
this.eventTarget.dispatchEvent("transaction-rolled-back", void 0);
|
|
271
|
+
return 0;
|
|
272
|
+
}, 0);
|
|
273
|
+
this.sqlite3.capi.sqlite3_commit_hook(this.db.ensureDb, () => {
|
|
274
|
+
if (updateQueue.size === 0) return 0;
|
|
275
|
+
const tables = updateQueue;
|
|
276
|
+
updateQueue = /* @__PURE__ */ new Set();
|
|
277
|
+
this.eventTarget.dispatchEvent("transaction-committed", void 0);
|
|
278
|
+
queueMicrotask(() => {
|
|
279
|
+
this.notifyTableSubscribers(tables);
|
|
280
|
+
});
|
|
281
|
+
return 0;
|
|
282
|
+
}, 0);
|
|
283
|
+
}
|
|
284
|
+
createSnapshot() {
|
|
285
|
+
const perf = startPerformanceLogger(this.logger);
|
|
286
|
+
const snapshot = this.sqlite3.capi.sqlite3_js_db_export(this.db.ensureDb);
|
|
287
|
+
perf.logEnd("createSnapshot", `snapshot size: ${snapshot.byteLength}`, "info");
|
|
288
|
+
return snapshot;
|
|
289
|
+
}
|
|
290
|
+
useSnapshot(snapshot) {
|
|
291
|
+
this.db.useSnapshot(snapshot);
|
|
292
|
+
this.notifyTableSubscribers();
|
|
293
|
+
}
|
|
294
|
+
dispose() {
|
|
295
|
+
for (const entries of this.sharedLiveQueries.values()) for (const entry of entries) {
|
|
296
|
+
this.cancelSharedLiveQueryCleanup(entry);
|
|
297
|
+
entry.unsubscribeFromLiveQuery?.();
|
|
298
|
+
entry.unsubscribeFromLiveQuery = null;
|
|
299
|
+
entry.listeners.clear();
|
|
300
|
+
}
|
|
301
|
+
this.sharedLiveQueries.clear();
|
|
302
|
+
this.liveQueryStatements.clear();
|
|
303
|
+
if (this.tablesUsedStatement) {
|
|
304
|
+
this.tablesUsedStatement.finalize();
|
|
305
|
+
this.tablesUsedStatement = null;
|
|
306
|
+
}
|
|
307
|
+
this.db.close();
|
|
308
|
+
}
|
|
434
309
|
};
|
|
435
310
|
function createDebouncedCallback(callback, delay) {
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
timeout = setTimeout(effect, delay);
|
|
453
|
-
};
|
|
311
|
+
let timeout = null;
|
|
312
|
+
let shouldCallWithoutDelay = true;
|
|
313
|
+
return (...args) => {
|
|
314
|
+
if (shouldCallWithoutDelay) {
|
|
315
|
+
callback(...args);
|
|
316
|
+
shouldCallWithoutDelay = false;
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
const effect = () => {
|
|
320
|
+
timeout = null;
|
|
321
|
+
shouldCallWithoutDelay = true;
|
|
322
|
+
return callback(...args);
|
|
323
|
+
};
|
|
324
|
+
if (timeout) clearTimeout(timeout);
|
|
325
|
+
timeout = setTimeout(effect, delay);
|
|
326
|
+
};
|
|
454
327
|
}
|
|
455
328
|
function parametersAreEqual(a, b) {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
return false;
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
return true;
|
|
329
|
+
if (a === b) return true;
|
|
330
|
+
if (!a || !b) return false;
|
|
331
|
+
if (a.length !== b.length) return false;
|
|
332
|
+
for (let i = 0; i < a.length; i++) if (!Object.is(a[i], b[i])) return false;
|
|
333
|
+
return true;
|
|
465
334
|
}
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
335
|
+
//#endregion
|
|
336
|
+
//#region src/schema/define-sync-schema.ts
|
|
337
|
+
const RESERVED_TABLE_NAMES = /* @__PURE__ */ new Set([
|
|
338
|
+
"change_history",
|
|
339
|
+
"crdt_events",
|
|
340
|
+
"worker.crdt_events",
|
|
341
|
+
"persisted_crdt_events",
|
|
342
|
+
"crdt_update_log",
|
|
343
|
+
"crdt_change_intents",
|
|
344
|
+
"worker.kv"
|
|
475
345
|
]);
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
}
|
|
513
|
-
};
|
|
346
|
+
/**
|
|
347
|
+
* Define a sync database schema from `t.table()` builders.
|
|
348
|
+
*
|
|
349
|
+
* Record keys are the crdt (view) table names; base table names default to the key
|
|
350
|
+
* prefixed with "_", overridable per table via `t.table(cols, { baseName })`.
|
|
351
|
+
*/
|
|
352
|
+
function defineSyncSchema({ tables, migrations }) {
|
|
353
|
+
const tablesConfig = Object.entries(tables).map(([crdtTableName, table]) => ({
|
|
354
|
+
crdtTableName,
|
|
355
|
+
baseTableName: table.baseName ?? `_${crdtTableName}`
|
|
356
|
+
}));
|
|
357
|
+
const seenNames = /* @__PURE__ */ new Set();
|
|
358
|
+
for (const { crdtTableName, baseTableName } of tablesConfig) for (const name of [crdtTableName, baseTableName]) {
|
|
359
|
+
const normalizedName = name.toLowerCase();
|
|
360
|
+
if (RESERVED_TABLE_NAMES.has(normalizedName)) throw new Error(`Table name "${name}" is reserved by sqlite-sync and cannot be used in a sync schema`);
|
|
361
|
+
if (seenNames.has(normalizedName)) throw new Error(`Duplicate table name "${name}" in sync schema`);
|
|
362
|
+
seenNames.add(normalizedName);
|
|
363
|
+
}
|
|
364
|
+
return {
|
|
365
|
+
tables,
|
|
366
|
+
get tablesConfig() {
|
|
367
|
+
return tablesConfig;
|
|
368
|
+
},
|
|
369
|
+
get migrations() {
|
|
370
|
+
return migrations;
|
|
371
|
+
},
|
|
372
|
+
get "~clientSchema"() {
|
|
373
|
+
throw new Error("~clientSchema is type-only and cannot be accessed at runtime");
|
|
374
|
+
},
|
|
375
|
+
get "~serverSchema"() {
|
|
376
|
+
throw new Error("~serverSchema is type-only and cannot be accessed at runtime");
|
|
377
|
+
},
|
|
378
|
+
get "~mutationsSchema"() {
|
|
379
|
+
throw new Error("~mutationsSchema is type-only and cannot be accessed at runtime");
|
|
380
|
+
}
|
|
381
|
+
};
|
|
514
382
|
}
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
var ColumnBuilder = class
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
383
|
+
//#endregion
|
|
384
|
+
//#region src/schema/table-builder.ts
|
|
385
|
+
var ColumnBuilder = class ColumnBuilder {
|
|
386
|
+
meta;
|
|
387
|
+
constructor(meta) {
|
|
388
|
+
this.meta = meta;
|
|
389
|
+
}
|
|
390
|
+
/** Allow SQL NULL; widens the value type with `null`. */
|
|
391
|
+
nullable() {
|
|
392
|
+
return new ColumnBuilder({
|
|
393
|
+
...this.meta,
|
|
394
|
+
nullable: true
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
/** Declare a column default. The value is used both in DDL and when migrating older events. */
|
|
398
|
+
default(value) {
|
|
399
|
+
return new ColumnBuilder({
|
|
400
|
+
...this.meta,
|
|
401
|
+
hasDefault: true,
|
|
402
|
+
defaultValue: value
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
/** Narrow the TypeScript value type without changing storage, e.g. `t.text().$type<"movie" | "tv">()`. */
|
|
406
|
+
$type() {
|
|
407
|
+
return this;
|
|
408
|
+
}
|
|
409
|
+
/** Human/AI-readable description, surfaced in generated schema docs. */
|
|
410
|
+
describe(description) {
|
|
411
|
+
return new ColumnBuilder({
|
|
412
|
+
...this.meta,
|
|
413
|
+
description
|
|
414
|
+
});
|
|
415
|
+
}
|
|
537
416
|
};
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
417
|
+
const RESERVED_COLUMNS = ["id", "tombstone"];
|
|
418
|
+
const idColumnMeta = {
|
|
419
|
+
kind: "text",
|
|
420
|
+
sqlType: "text",
|
|
421
|
+
nullable: false,
|
|
422
|
+
hasDefault: false,
|
|
423
|
+
description: "Unique immutable item id"
|
|
545
424
|
};
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
425
|
+
const tombstoneColumnMeta = {
|
|
426
|
+
kind: "boolean",
|
|
427
|
+
sqlType: "integer",
|
|
428
|
+
nullable: false,
|
|
429
|
+
hasDefault: true,
|
|
430
|
+
defaultValue: false,
|
|
431
|
+
description: "Soft-delete marker"
|
|
553
432
|
};
|
|
554
|
-
var TableBuilder = class
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
}
|
|
610
|
-
for (const [name, value] of Object.entries(payload)) {
|
|
611
|
-
const meta = this.columns[name];
|
|
612
|
-
if (!meta || event === "item-updated" && name === "id") {
|
|
613
|
-
continue;
|
|
614
|
-
}
|
|
615
|
-
const error = validateValue(name, value, meta);
|
|
616
|
-
if (error) {
|
|
617
|
-
errors.push(error);
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
return errors.length === 0 ? { success: true } : { success: false, errors };
|
|
621
|
-
}
|
|
433
|
+
var TableBuilder = class TableBuilder {
|
|
434
|
+
userColumns;
|
|
435
|
+
/** All columns in DDL order, including the auto-injected `id` and `tombstone`. */
|
|
436
|
+
columns;
|
|
437
|
+
baseName;
|
|
438
|
+
description;
|
|
439
|
+
constructor(userColumns, options) {
|
|
440
|
+
this.userColumns = userColumns;
|
|
441
|
+
for (const reserved of RESERVED_COLUMNS) if (reserved in userColumns) throw new Error(`Column "${reserved}" is added automatically to every synced table and cannot be declared`);
|
|
442
|
+
this.columns = {
|
|
443
|
+
id: idColumnMeta,
|
|
444
|
+
...Object.fromEntries(Object.entries(userColumns).map(([name, column]) => [name, column.meta])),
|
|
445
|
+
tombstone: tombstoneColumnMeta
|
|
446
|
+
};
|
|
447
|
+
this.baseName = options?.baseName;
|
|
448
|
+
this.description = options?.description;
|
|
449
|
+
}
|
|
450
|
+
describe(description) {
|
|
451
|
+
return new TableBuilder(this.userColumns, {
|
|
452
|
+
baseName: this.baseName,
|
|
453
|
+
description
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
/** Type-only: the row shape returned by queries. Do not access at runtime. */
|
|
457
|
+
get $row() {
|
|
458
|
+
return null;
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Validate a CRDT event payload against the declared columns.
|
|
462
|
+
* `item-created` payloads must contain every column without a default (nullable columns may be omitted);
|
|
463
|
+
* `item-updated` payloads are partial and must not touch `id` or set `tombstone` to a deleted value.
|
|
464
|
+
*/
|
|
465
|
+
validatePayload(payload, options) {
|
|
466
|
+
const event = options?.event ?? "item-created";
|
|
467
|
+
const errors = [];
|
|
468
|
+
for (const key of Object.keys(payload)) if (!(key in this.columns)) errors.push(`Unknown column "${key}"`);
|
|
469
|
+
if (event === "item-updated") {
|
|
470
|
+
if ("id" in payload) errors.push(`Column "id" is immutable and cannot appear in an update payload`);
|
|
471
|
+
if (payload.tombstone === true || payload.tombstone === 1) errors.push(`Column "tombstone" cannot be set to true/1 in an update payload; use an item-deleted event`);
|
|
472
|
+
}
|
|
473
|
+
if (event === "item-created") for (const [name, meta] of Object.entries(this.columns)) {
|
|
474
|
+
if (name in payload || name === "tombstone" || meta.nullable || meta.hasDefault) continue;
|
|
475
|
+
errors.push(`Missing required column "${name}"`);
|
|
476
|
+
}
|
|
477
|
+
for (const [name, value] of Object.entries(payload)) {
|
|
478
|
+
const meta = this.columns[name];
|
|
479
|
+
if (!meta || event === "item-updated" && name === "id") continue;
|
|
480
|
+
const error = validateValue(name, value, meta);
|
|
481
|
+
if (error) errors.push(error);
|
|
482
|
+
}
|
|
483
|
+
return errors.length === 0 ? { success: true } : {
|
|
484
|
+
success: false,
|
|
485
|
+
errors
|
|
486
|
+
};
|
|
487
|
+
}
|
|
622
488
|
};
|
|
623
489
|
function describeValue(value) {
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
490
|
+
if (value === null) return "null";
|
|
491
|
+
if (Array.isArray(value)) return "array";
|
|
492
|
+
return typeof value;
|
|
627
493
|
}
|
|
628
494
|
function validateValue(name, value, meta) {
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
case "boolean":
|
|
640
|
-
return typeof value === "boolean" || value === 0 || value === 1 ? null : `Column "${name}" expects a boolean (true/false or 0/1), got ${describeValue(value)}`;
|
|
641
|
-
case "enum": {
|
|
642
|
-
if (typeof value !== "string") {
|
|
643
|
-
return `Column "${name}" expects one of ${formatEnumValues(meta)}, got ${describeValue(value)}`;
|
|
644
|
-
}
|
|
645
|
-
return meta.enumValues?.includes(value) ? null : `Column "${name}" expects one of ${formatEnumValues(meta)}, got "${value}"`;
|
|
646
|
-
}
|
|
647
|
-
}
|
|
495
|
+
if (value === null) return meta.nullable ? null : `Column "${name}" is not nullable, got null`;
|
|
496
|
+
switch (meta.kind) {
|
|
497
|
+
case "text": return typeof value === "string" ? null : `Column "${name}" expects text, got ${describeValue(value)}`;
|
|
498
|
+
case "integer": return typeof value === "number" && Number.isInteger(value) ? null : `Column "${name}" expects an integer, got ${describeValue(value)}`;
|
|
499
|
+
case "real": return typeof value === "number" && Number.isFinite(value) ? null : `Column "${name}" expects a number, got ${describeValue(value)}`;
|
|
500
|
+
case "boolean": return typeof value === "boolean" || value === 0 || value === 1 ? null : `Column "${name}" expects a boolean (true/false or 0/1), got ${describeValue(value)}`;
|
|
501
|
+
case "enum":
|
|
502
|
+
if (typeof value !== "string") return `Column "${name}" expects one of ${formatEnumValues(meta)}, got ${describeValue(value)}`;
|
|
503
|
+
return meta.enumValues?.includes(value) ? null : `Column "${name}" expects one of ${formatEnumValues(meta)}, got "${value}"`;
|
|
504
|
+
}
|
|
648
505
|
}
|
|
649
506
|
function formatEnumValues(meta) {
|
|
650
|
-
|
|
507
|
+
return (meta.enumValues ?? []).map((value) => `"${value}"`).join(" | ");
|
|
651
508
|
}
|
|
652
509
|
function column(kind, sqlType) {
|
|
653
|
-
|
|
510
|
+
return new ColumnBuilder({
|
|
511
|
+
kind,
|
|
512
|
+
sqlType,
|
|
513
|
+
nullable: false,
|
|
514
|
+
hasDefault: false
|
|
515
|
+
});
|
|
654
516
|
}
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
517
|
+
const t = {
|
|
518
|
+
text: () => column("text", "text"),
|
|
519
|
+
integer: () => column("integer", "integer"),
|
|
520
|
+
real: () => column("real", "real"),
|
|
521
|
+
/** Stored as INTEGER 0/1; always bind booleans as 0/1 when writing raw SQL. */
|
|
522
|
+
boolean: () => column("boolean", "integer"),
|
|
523
|
+
/**
|
|
524
|
+
* Union of string literals stored as TEXT, e.g. `t.enum(["movie", "tv"])`. Unlike `$type`, the
|
|
525
|
+
* allowed values are kept as runtime metadata, so payloads are validated against them.
|
|
526
|
+
* For an open-ended set, use `t.text().$type<"idle" | (string & {})>()` instead.
|
|
527
|
+
*/
|
|
528
|
+
enum: (values) => new ColumnBuilder({
|
|
529
|
+
kind: "enum",
|
|
530
|
+
sqlType: "text",
|
|
531
|
+
nullable: false,
|
|
532
|
+
hasDefault: false,
|
|
533
|
+
enumValues: values
|
|
534
|
+
}),
|
|
535
|
+
table: (columns, options) => new TableBuilder(columns, options)
|
|
674
536
|
};
|
|
675
|
-
|
|
676
|
-
|
|
537
|
+
//#endregion
|
|
538
|
+
//#region src/sqlite-crdt/crdt-storage-mutator.ts
|
|
677
539
|
function createCrdtStorageMutator({ storage }) {
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
baseTableName,
|
|
723
|
-
crdtTableName
|
|
724
|
-
}) {
|
|
725
|
-
const tableSchema = db.dbSchema[baseTableName];
|
|
726
|
-
if (!tableSchema) {
|
|
727
|
-
throw new Error(`Table ${baseTableName} not found`);
|
|
728
|
-
}
|
|
729
|
-
const columns = new Map(tableSchema.columns.map((c) => [c.name, c]));
|
|
730
|
-
const idColumn = columns.get("id");
|
|
731
|
-
if (!idColumn) {
|
|
732
|
-
throw new Error(
|
|
733
|
-
`Table "${baseTableName}" is missing a required "id" column. CRDT tables must have an "id" column to identify items.`
|
|
734
|
-
);
|
|
735
|
-
}
|
|
736
|
-
if (idColumn.dataType.toUpperCase() !== "TEXT") {
|
|
737
|
-
throw new Error(
|
|
738
|
-
`Table "${baseTableName}": "id" column must be of type TEXT, got "${idColumn.dataType}". CRDT item IDs are stored as strings.`
|
|
739
|
-
);
|
|
740
|
-
}
|
|
741
|
-
const tombstoneColumn = columns.get("tombstone");
|
|
742
|
-
if (!tombstoneColumn) {
|
|
743
|
-
throw new Error(
|
|
744
|
-
`Table "${baseTableName}" is missing a required "tombstone" column. CRDT tables must have a "tombstone" INTEGER column for soft deletes.`
|
|
745
|
-
);
|
|
746
|
-
}
|
|
747
|
-
const tombstoneType = tombstoneColumn.dataType.toUpperCase();
|
|
748
|
-
if (tombstoneType !== "INTEGER" && tombstoneType !== "BOOLEAN") {
|
|
749
|
-
throw new Error(
|
|
750
|
-
`Table "${baseTableName}": "tombstone" column must be of type INTEGER or BOOLEAN, got "${tombstoneColumn.dataType}". It is compared as 0/1 for soft deletes.`
|
|
751
|
-
);
|
|
752
|
-
}
|
|
753
|
-
db.execute(
|
|
754
|
-
`
|
|
755
|
-
create view ${quoteId(crdtTableName)} as
|
|
756
|
-
select * from ${quoteId(baseTableName)}
|
|
757
|
-
where tombstone = 0;`,
|
|
758
|
-
{ loggerLevel: "system" }
|
|
759
|
-
);
|
|
760
|
-
const allColumnNames = tableSchema.columns.map((column2) => column2.name);
|
|
761
|
-
const jsonPayload = (from) => `'{'||${allColumnNames.map((col) => `'"${col}":'||json_quote(${from}.${quoteId(col)})`).join("||','||")}||'}'`;
|
|
762
|
-
db.execute(
|
|
763
|
-
`
|
|
764
|
-
create trigger ${quoteId(`${crdtTableName}_created`)}
|
|
765
|
-
instead of insert on ${quoteId(crdtTableName)}
|
|
766
|
-
for each row
|
|
767
|
-
begin
|
|
768
|
-
select handle_item_created('${baseTableName}', ${jsonPayload("new")});
|
|
769
|
-
end;
|
|
770
|
-
`,
|
|
771
|
-
{ loggerLevel: "system" }
|
|
772
|
-
);
|
|
773
|
-
db.execute(
|
|
774
|
-
`
|
|
775
|
-
create trigger ${quoteId(`${crdtTableName}_updated`)}
|
|
776
|
-
instead of update on ${quoteId(crdtTableName)}
|
|
777
|
-
for each row
|
|
778
|
-
begin
|
|
779
|
-
select handle_item_updated(
|
|
780
|
-
'${baseTableName}',
|
|
781
|
-
${jsonPayload("old")},
|
|
782
|
-
${jsonPayload("new")}
|
|
783
|
-
);
|
|
784
|
-
end;
|
|
785
|
-
`,
|
|
786
|
-
{ loggerLevel: "system" }
|
|
787
|
-
);
|
|
788
|
-
db.execute(
|
|
789
|
-
`
|
|
790
|
-
create trigger ${quoteId(`${crdtTableName}_deleted`)}
|
|
791
|
-
instead of delete on ${quoteId(crdtTableName)}
|
|
792
|
-
for each row
|
|
793
|
-
when old.tombstone = 0
|
|
794
|
-
begin
|
|
795
|
-
select handle_item_deleted('${baseTableName}', old.id);
|
|
796
|
-
end;
|
|
797
|
-
`,
|
|
798
|
-
{ loggerLevel: "system" }
|
|
799
|
-
);
|
|
800
|
-
}
|
|
801
|
-
function registerCrdtFunctions({
|
|
802
|
-
reactiveDb,
|
|
803
|
-
storage
|
|
804
|
-
}) {
|
|
805
|
-
let eventApplied = false;
|
|
806
|
-
reactiveDb.db.createScalarFunction({
|
|
807
|
-
name: "handle_item_created",
|
|
808
|
-
deterministic: false,
|
|
809
|
-
directOnly: false,
|
|
810
|
-
innocuous: false,
|
|
811
|
-
callback: (dataset, payloadRaw) => {
|
|
812
|
-
const payload = JSON.parse(payloadRaw);
|
|
813
|
-
storage.internal.applyOwnEventFromTransaction(reactiveDb.db, {
|
|
814
|
-
type: "item-created",
|
|
815
|
-
dataset,
|
|
816
|
-
item_id: payload.id,
|
|
817
|
-
payload: payloadRaw
|
|
818
|
-
});
|
|
819
|
-
eventApplied = true;
|
|
820
|
-
return void 0;
|
|
821
|
-
}
|
|
822
|
-
});
|
|
823
|
-
reactiveDb.db.createScalarFunction({
|
|
824
|
-
name: "handle_item_updated",
|
|
825
|
-
deterministic: false,
|
|
826
|
-
directOnly: false,
|
|
827
|
-
innocuous: false,
|
|
828
|
-
callback: (dataset, oldPayloadRaw, newPayloadRaw) => {
|
|
829
|
-
if (oldPayloadRaw === newPayloadRaw) {
|
|
830
|
-
return void 0;
|
|
831
|
-
}
|
|
832
|
-
const tableSchema = reactiveDb.db.dbSchema[dataset];
|
|
833
|
-
if (!tableSchema?.columns) {
|
|
834
|
-
throw new Error(`Schema not found for dataset: ${dataset}`);
|
|
835
|
-
}
|
|
836
|
-
const oldPayload = JSON.parse(oldPayloadRaw);
|
|
837
|
-
const newPayload = JSON.parse(newPayloadRaw);
|
|
838
|
-
let hasDiff = false;
|
|
839
|
-
const updatePayload = {};
|
|
840
|
-
for (const column2 of tableSchema.columns) {
|
|
841
|
-
const oldValue = oldPayload[column2.name];
|
|
842
|
-
const newValue = newPayload[column2.name];
|
|
843
|
-
if (oldValue === newValue) {
|
|
844
|
-
continue;
|
|
845
|
-
}
|
|
846
|
-
if (column2.name === "id") {
|
|
847
|
-
throw new Error(
|
|
848
|
-
`Cannot update the "id" column of an item. It is used to identify the item and must be immutable.`
|
|
849
|
-
);
|
|
850
|
-
}
|
|
851
|
-
hasDiff = true;
|
|
852
|
-
updatePayload[column2.name] = newValue;
|
|
853
|
-
}
|
|
854
|
-
if (!hasDiff) {
|
|
855
|
-
return;
|
|
856
|
-
}
|
|
857
|
-
storage.internal.applyOwnEventFromTransaction(reactiveDb.db, {
|
|
858
|
-
type: "item-updated",
|
|
859
|
-
dataset,
|
|
860
|
-
item_id: oldPayload.id,
|
|
861
|
-
payload: JSON.stringify(updatePayload)
|
|
862
|
-
});
|
|
863
|
-
eventApplied = true;
|
|
864
|
-
return void 0;
|
|
865
|
-
}
|
|
866
|
-
});
|
|
867
|
-
reactiveDb.db.createScalarFunction({
|
|
868
|
-
name: "handle_item_deleted",
|
|
869
|
-
deterministic: false,
|
|
870
|
-
directOnly: false,
|
|
871
|
-
innocuous: false,
|
|
872
|
-
callback: (dataset, itemId) => {
|
|
873
|
-
storage.internal.applyOwnEventFromTransaction(reactiveDb.db, {
|
|
874
|
-
type: "item-deleted",
|
|
875
|
-
dataset,
|
|
876
|
-
item_id: itemId,
|
|
877
|
-
payload: "{}"
|
|
878
|
-
});
|
|
879
|
-
eventApplied = true;
|
|
880
|
-
return void 0;
|
|
881
|
-
}
|
|
882
|
-
});
|
|
883
|
-
reactiveDb.addEventListener("transaction-committed", () => {
|
|
884
|
-
if (eventApplied) {
|
|
885
|
-
eventApplied = false;
|
|
886
|
-
void storage.internal.processEnqueuedEvents();
|
|
887
|
-
}
|
|
888
|
-
});
|
|
889
|
-
reactiveDb.addEventListener("transaction-rolled-back", () => {
|
|
890
|
-
eventApplied = false;
|
|
891
|
-
});
|
|
540
|
+
const mapToStorageEvent = (event) => {
|
|
541
|
+
switch (event.type) {
|
|
542
|
+
case "item-created": return {
|
|
543
|
+
type: "item-created",
|
|
544
|
+
dataset: event.dataset,
|
|
545
|
+
item_id: event.item_id,
|
|
546
|
+
payload: JSON.stringify(event.payload)
|
|
547
|
+
};
|
|
548
|
+
case "item-updated": return {
|
|
549
|
+
type: "item-updated",
|
|
550
|
+
dataset: event.dataset,
|
|
551
|
+
item_id: event.item_id,
|
|
552
|
+
payload: JSON.stringify(event.payload)
|
|
553
|
+
};
|
|
554
|
+
case "item-deleted": return {
|
|
555
|
+
type: "item-deleted",
|
|
556
|
+
dataset: event.dataset,
|
|
557
|
+
item_id: event.item_id,
|
|
558
|
+
payload: "{}"
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
const enqueueEvents = (events) => {
|
|
563
|
+
storage.enqueueOwnEvents(events.map(mapToStorageEvent));
|
|
564
|
+
};
|
|
565
|
+
const createEvent = (event) => {
|
|
566
|
+
return event;
|
|
567
|
+
};
|
|
568
|
+
const enqueueEvent = (event) => {
|
|
569
|
+
storage.enqueueOwnEvents([mapToStorageEvent(event)]);
|
|
570
|
+
};
|
|
571
|
+
const enqueueSnapshot = (snapshot) => {
|
|
572
|
+
storage.applyOwnSnapshot({
|
|
573
|
+
dataset: snapshot.dataset,
|
|
574
|
+
item_id: snapshot.id,
|
|
575
|
+
patch: { ...snapshot.patch }
|
|
576
|
+
});
|
|
577
|
+
};
|
|
578
|
+
return {
|
|
579
|
+
enqueueEvents,
|
|
580
|
+
createEvent,
|
|
581
|
+
enqueueEvent,
|
|
582
|
+
enqueueSnapshot
|
|
583
|
+
};
|
|
892
584
|
}
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
585
|
+
//#endregion
|
|
586
|
+
//#region src/db-id.ts
|
|
587
|
+
const dbIdRegex = /^[a-zA-Z][a-zA-Z\-0-9]{2,63}$/;
|
|
896
588
|
function validateDbId(dbId) {
|
|
897
|
-
|
|
898
|
-
throw new Error("Invalid dbId. Must be between 3 and 64 characters long and start with a letter.");
|
|
899
|
-
}
|
|
589
|
+
if (!dbIdRegex.test(dbId)) throw new Error("Invalid dbId. Must be between 3 and 64 characters long and start with a letter.");
|
|
900
590
|
}
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
591
|
+
//#endregion
|
|
592
|
+
//#region src/devtools-registry.ts
|
|
593
|
+
const devtoolsRegistrySymbol = Symbol.for("@sqlite-sync/devtools");
|
|
904
594
|
function createSQLiteSyncDevtoolsRegistry() {
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
isUnregistered = true;
|
|
940
|
-
if (!instances.delete(instance.instanceId)) return;
|
|
941
|
-
updateSnapshot();
|
|
942
|
-
};
|
|
943
|
-
}
|
|
944
|
-
};
|
|
595
|
+
const instances = /* @__PURE__ */ new Map();
|
|
596
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
597
|
+
let snapshot = { instances: [] };
|
|
598
|
+
const notify = () => {
|
|
599
|
+
for (const listener of listeners) listener();
|
|
600
|
+
};
|
|
601
|
+
const updateSnapshot = () => {
|
|
602
|
+
snapshot = { instances: Array.from(instances.values()) };
|
|
603
|
+
notify();
|
|
604
|
+
};
|
|
605
|
+
return {
|
|
606
|
+
version: 1,
|
|
607
|
+
instances,
|
|
608
|
+
subscribe(listener) {
|
|
609
|
+
listeners.add(listener);
|
|
610
|
+
return () => {
|
|
611
|
+
listeners.delete(listener);
|
|
612
|
+
};
|
|
613
|
+
},
|
|
614
|
+
getSnapshot() {
|
|
615
|
+
return snapshot;
|
|
616
|
+
},
|
|
617
|
+
register(instance) {
|
|
618
|
+
instances.set(instance.instanceId, instance);
|
|
619
|
+
updateSnapshot();
|
|
620
|
+
let isUnregistered = false;
|
|
621
|
+
return () => {
|
|
622
|
+
if (isUnregistered) return;
|
|
623
|
+
isUnregistered = true;
|
|
624
|
+
if (!instances.delete(instance.instanceId)) return;
|
|
625
|
+
updateSnapshot();
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
};
|
|
945
629
|
}
|
|
946
630
|
function getOrCreateSQLiteSyncDevtoolsRegistry() {
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
631
|
+
const registryGlobal = globalThis;
|
|
632
|
+
registryGlobal[devtoolsRegistrySymbol] ??= createSQLiteSyncDevtoolsRegistry();
|
|
633
|
+
return registryGlobal[devtoolsRegistrySymbol];
|
|
950
634
|
}
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
async function createMemoryDb({
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
})
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
hlc: hlcCounter,
|
|
981
|
-
migrator,
|
|
982
|
-
db,
|
|
983
|
-
dbConfig: memoryDbConfig,
|
|
984
|
-
eventHlcAccumulator,
|
|
985
|
-
schema: syncDbSchema
|
|
986
|
-
});
|
|
987
|
-
registerCrdtFunctions({
|
|
988
|
-
reactiveDb,
|
|
989
|
-
storage: crdtStorage
|
|
990
|
-
});
|
|
991
|
-
return {
|
|
992
|
-
crdtStorage
|
|
993
|
-
};
|
|
635
|
+
//#endregion
|
|
636
|
+
//#region src/memory-db/memory-db.ts
|
|
637
|
+
async function createMemoryDb({ nodeId, migrator, reactiveDb: _reactiveDb, hlcCounter, crdtTables, initializeSchema = true, initialSyncId, eventHlcAccumulator, syncDbSchema }) {
|
|
638
|
+
await xxhash.ensureLoaded();
|
|
639
|
+
const reactiveDb = _reactiveDb;
|
|
640
|
+
const db = reactiveDb.db;
|
|
641
|
+
if (initializeSchema) {
|
|
642
|
+
applyMemoryDbSchema(db);
|
|
643
|
+
for (const table of crdtTables) makeCrdtTable({
|
|
644
|
+
db,
|
|
645
|
+
baseTableName: table.baseTableName,
|
|
646
|
+
crdtTableName: table.crdtTableName
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
const crdtStorage = createCrdtStorage({
|
|
650
|
+
nodeId,
|
|
651
|
+
initialLocalSyncId: initialSyncId ?? getCurrentSyncId(db),
|
|
652
|
+
hlc: hlcCounter,
|
|
653
|
+
migrator,
|
|
654
|
+
db,
|
|
655
|
+
dbConfig: memoryDbConfig,
|
|
656
|
+
eventHlcAccumulator,
|
|
657
|
+
schema: syncDbSchema
|
|
658
|
+
});
|
|
659
|
+
registerCrdtIntentDrainer({
|
|
660
|
+
reactiveDb,
|
|
661
|
+
storage: crdtStorage
|
|
662
|
+
});
|
|
663
|
+
return { crdtStorage };
|
|
994
664
|
}
|
|
995
665
|
function getCurrentSyncId(db) {
|
|
996
|
-
|
|
997
|
-
loggerLevel: "system"
|
|
998
|
-
}).rows[0]?.syncId ?? 0;
|
|
666
|
+
return db.execute("SELECT coalesce(max(sync_id), 0) AS syncId FROM persisted_crdt_events", { loggerLevel: "system" }).rows[0]?.syncId ?? 0;
|
|
999
667
|
}
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
const dispose = () => {
|
|
1074
|
-
isDisposed = true;
|
|
1075
|
-
broadcastChannels.responses.onmessage = null;
|
|
1076
|
-
for (const [id, deferred] of workerRequestsMap) {
|
|
1077
|
-
deferred.reject(new Error("Worker client disposed"));
|
|
1078
|
-
workerRequestsMap.delete(id);
|
|
1079
|
-
}
|
|
1080
|
-
};
|
|
1081
|
-
return {
|
|
1082
|
-
...rpc,
|
|
1083
|
-
subscribe: eventTarget.addEventListener,
|
|
1084
|
-
getState: () => workerState,
|
|
1085
|
-
dispose
|
|
1086
|
-
};
|
|
668
|
+
//#endregion
|
|
669
|
+
//#region src/worker-db/db-worker-client.ts
|
|
670
|
+
const createWorkerDbClient = async ({ broadcastChannels, worker, config }) => {
|
|
671
|
+
const eventTarget = createTypedEventTarget();
|
|
672
|
+
const workerRequestsMap = /* @__PURE__ */ new Map();
|
|
673
|
+
let isDisposed = false;
|
|
674
|
+
const queryWorker = (method, args) => {
|
|
675
|
+
if (isDisposed) return Promise.reject(/* @__PURE__ */ new Error("Worker client disposed"));
|
|
676
|
+
const requestId = crypto.randomUUID();
|
|
677
|
+
const promise = createDeferredPromise({
|
|
678
|
+
timeout: 3e4,
|
|
679
|
+
onTimeout: () => workerRequestsMap.delete(requestId)
|
|
680
|
+
});
|
|
681
|
+
workerRequestsMap.set(requestId, promise);
|
|
682
|
+
const request = {
|
|
683
|
+
type: "request",
|
|
684
|
+
requestId,
|
|
685
|
+
method,
|
|
686
|
+
args
|
|
687
|
+
};
|
|
688
|
+
broadcastChannels.requests.postMessage(request);
|
|
689
|
+
return promise.promise;
|
|
690
|
+
};
|
|
691
|
+
const handleWorkerResponse = (message) => {
|
|
692
|
+
const promise = workerRequestsMap.get(message.requestId);
|
|
693
|
+
if (!promise) return;
|
|
694
|
+
promise.resolve(message.data);
|
|
695
|
+
workerRequestsMap.delete(message.requestId);
|
|
696
|
+
};
|
|
697
|
+
const handleWorkerError = (message) => {
|
|
698
|
+
const promise = workerRequestsMap.get(message.requestId);
|
|
699
|
+
if (!promise) return;
|
|
700
|
+
promise.reject(new Error(message.error));
|
|
701
|
+
workerRequestsMap.delete(message.requestId);
|
|
702
|
+
};
|
|
703
|
+
broadcastChannels.responses.onmessage = (event) => {
|
|
704
|
+
const message = event.data;
|
|
705
|
+
if (isWorkerResponseMessage(message)) handleWorkerResponse(message);
|
|
706
|
+
else if (isWorkerErrorResponseMessage(message)) handleWorkerError(message);
|
|
707
|
+
else if (isWorkerNotificationMessage(message)) eventTarget.dispatchEvent(message.notificationType, message);
|
|
708
|
+
};
|
|
709
|
+
const rpc = {
|
|
710
|
+
execute: (query) => queryWorker("execute", [query]),
|
|
711
|
+
getSnapshot: () => queryWorker("getSnapshot", []),
|
|
712
|
+
importData: (data, opts) => queryWorker("importData", [data, opts]),
|
|
713
|
+
pushTabEvents: (request) => queryWorker("pushTabEvents", [request]),
|
|
714
|
+
pullEvents: (params) => queryWorker("pullEvents", [params]),
|
|
715
|
+
postState: () => queryWorker("postState", []),
|
|
716
|
+
goOnline: () => queryWorker("goOnline", []),
|
|
717
|
+
goOffline: () => queryWorker("goOffline", []),
|
|
718
|
+
requestReload: (options) => queryWorker("requestReload", [options])
|
|
719
|
+
};
|
|
720
|
+
const statePromise = awaitWorkerState(eventTarget);
|
|
721
|
+
postWorkerConfig(worker, config);
|
|
722
|
+
rpc.postState().catch(noop);
|
|
723
|
+
let workerState = await statePromise;
|
|
724
|
+
eventTarget.addEventListener("state-changed", (event) => {
|
|
725
|
+
workerState = event.payload.state;
|
|
726
|
+
});
|
|
727
|
+
const dispose = () => {
|
|
728
|
+
isDisposed = true;
|
|
729
|
+
broadcastChannels.responses.onmessage = null;
|
|
730
|
+
for (const [id, deferred] of workerRequestsMap) {
|
|
731
|
+
deferred.reject(/* @__PURE__ */ new Error("Worker client disposed"));
|
|
732
|
+
workerRequestsMap.delete(id);
|
|
733
|
+
}
|
|
734
|
+
};
|
|
735
|
+
return {
|
|
736
|
+
...rpc,
|
|
737
|
+
subscribe: eventTarget.addEventListener,
|
|
738
|
+
getState: () => workerState,
|
|
739
|
+
dispose
|
|
740
|
+
};
|
|
1087
741
|
};
|
|
1088
742
|
function awaitWorkerState(eventTarget) {
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
743
|
+
const promise = createDeferredPromise({ timeout: 15e3 });
|
|
744
|
+
const subscription = eventTarget.addEventListener("state-changed", (event) => {
|
|
745
|
+
promise.resolve(event.payload.state);
|
|
746
|
+
subscription.unsubscribe();
|
|
747
|
+
});
|
|
748
|
+
return promise.promise;
|
|
1095
749
|
}
|
|
1096
750
|
function postWorkerConfig(worker, config) {
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
751
|
+
const configMessage = {
|
|
752
|
+
type: "init",
|
|
753
|
+
config
|
|
754
|
+
};
|
|
755
|
+
worker.postMessage(configMessage);
|
|
1102
756
|
}
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
757
|
+
//#endregion
|
|
758
|
+
//#region src/sync-db.ts
|
|
759
|
+
function createSqlExecutor(tx, unsafe) {
|
|
760
|
+
return {
|
|
761
|
+
execute: (query) => tx.execute(query, unsafe ? { skipAfterMutatingStatement: true } : void 0),
|
|
762
|
+
executeKysely: (factory) => tx.executeKysely(factory, unsafe ? { skipAfterMutatingStatement: true } : void 0)
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
/** @internal Exported from this module for focused facade tests, but not from the package entry point. */
|
|
766
|
+
function createSyncedDbDatabase(reactiveDb) {
|
|
767
|
+
const safeExecutor = createSqlExecutor(reactiveDb.db, false);
|
|
768
|
+
const unsafeExecutor = createSqlExecutor(reactiveDb.db, true);
|
|
769
|
+
return {
|
|
770
|
+
...safeExecutor,
|
|
771
|
+
unsafe: unsafeExecutor,
|
|
772
|
+
executeTransaction: (callback) => reactiveDb.db.executeTransaction((tx) => {
|
|
773
|
+
const safeTransactionExecutor = createSqlExecutor(tx, false);
|
|
774
|
+
const unsafeTransactionExecutor = createSqlExecutor(tx, true);
|
|
775
|
+
return callback({
|
|
776
|
+
...safeTransactionExecutor,
|
|
777
|
+
unsafe: unsafeTransactionExecutor,
|
|
778
|
+
sql: tx.sql.bind(tx)
|
|
779
|
+
});
|
|
780
|
+
}),
|
|
781
|
+
createLiveQuery: reactiveDb.createLiveQuery.bind(reactiveDb),
|
|
782
|
+
getSharedLiveQuery: reactiveDb.getSharedLiveQuery.bind(reactiveDb)
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
const defaultLogger = (type, message, level = "info") => {
|
|
786
|
+
const logMessage = `[${type}] ${message}`;
|
|
787
|
+
switch (level) {
|
|
788
|
+
case "info":
|
|
789
|
+
console.log(logMessage);
|
|
790
|
+
break;
|
|
791
|
+
case "warning":
|
|
792
|
+
console.warn(logMessage);
|
|
793
|
+
break;
|
|
794
|
+
case "error":
|
|
795
|
+
console.error(logMessage);
|
|
796
|
+
break;
|
|
797
|
+
case "trace": console.trace(logMessage);
|
|
798
|
+
}
|
|
1121
799
|
};
|
|
1122
800
|
async function createSyncedDb(options) {
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
}
|
|
1268
|
-
};
|
|
1269
|
-
unregisterDevtools = getOrCreateSQLiteSyncDevtoolsRegistry().register({
|
|
1270
|
-
instanceId,
|
|
1271
|
-
dbId: options.dbId,
|
|
1272
|
-
createdAt: Date.now(),
|
|
1273
|
-
instance: syncedDb
|
|
1274
|
-
});
|
|
1275
|
-
return syncedDb;
|
|
801
|
+
validateDbId(options.dbId);
|
|
802
|
+
const perf = startPerformanceLogger(defaultLogger);
|
|
803
|
+
const instanceId = generateId();
|
|
804
|
+
const tabId = generateId();
|
|
805
|
+
const broadcastChannels = createBroadcastChannels(options.dbId);
|
|
806
|
+
const clientLockAcquired = createDeferredPromise();
|
|
807
|
+
const clientLockRelease = createDeferredPromise();
|
|
808
|
+
navigator.locks.request(`${syncDbClientLockName}-${options.dbId}`, { mode: "shared" }, () => {
|
|
809
|
+
clientLockAcquired.resolve();
|
|
810
|
+
return clientLockRelease.promise;
|
|
811
|
+
});
|
|
812
|
+
await clientLockAcquired.promise;
|
|
813
|
+
const workerClient = await createWorkerDbClient({
|
|
814
|
+
worker: options.worker,
|
|
815
|
+
config: {
|
|
816
|
+
clientId: generateId(),
|
|
817
|
+
dbId: options.dbId,
|
|
818
|
+
props: options.workerProps
|
|
819
|
+
},
|
|
820
|
+
broadcastChannels
|
|
821
|
+
});
|
|
822
|
+
const hlcCounter = new HLCCounter(tabId, () => Date.now());
|
|
823
|
+
const workerClientSnapshot = await workerClient.getSnapshot();
|
|
824
|
+
const reactiveDb = await createSQLiteReactiveDb({
|
|
825
|
+
snapshot: workerClientSnapshot.file,
|
|
826
|
+
logger: defaultLogger
|
|
827
|
+
});
|
|
828
|
+
const memoryDbMigrator = {
|
|
829
|
+
currentSchemaVersion: workerClientSnapshot.schemaVersion,
|
|
830
|
+
latestSchemaVersion: workerClientSnapshot.schemaVersion,
|
|
831
|
+
migrateDbToLatest: () => {
|
|
832
|
+
throw new Error("Memory DB migrations are not implemented");
|
|
833
|
+
},
|
|
834
|
+
migrateEvent: (event, targetVersion) => {
|
|
835
|
+
if (event.schema_version === targetVersion) return event;
|
|
836
|
+
throw new Error("Memory DB migrations are not implemented");
|
|
837
|
+
},
|
|
838
|
+
migrateEvents: (events) => events
|
|
839
|
+
};
|
|
840
|
+
const { crdtStorage } = await createMemoryDb({
|
|
841
|
+
nodeId: tabId,
|
|
842
|
+
migrator: memoryDbMigrator,
|
|
843
|
+
reactiveDb,
|
|
844
|
+
hlcCounter,
|
|
845
|
+
crdtTables: options.syncDbSchema.tablesConfig,
|
|
846
|
+
syncDbSchema: options.syncDbSchema
|
|
847
|
+
});
|
|
848
|
+
const pullSyncId = createStoredValue({ initialValue: workerClientSnapshot.syncId });
|
|
849
|
+
const pushSyncId = createStoredValue({ initialValue: 0 });
|
|
850
|
+
const tabRemoteSource = createCrdtSyncRemoteSource({
|
|
851
|
+
bufferSize: 500,
|
|
852
|
+
pullSyncId,
|
|
853
|
+
pushSyncId,
|
|
854
|
+
storage: crdtStorage,
|
|
855
|
+
nodeId: tabId,
|
|
856
|
+
migrator: memoryDbMigrator,
|
|
857
|
+
remoteFactory: ({ onEventsAvailable }) => {
|
|
858
|
+
const subscription = workerClient.subscribe("new-event-chunk-applied", (event) => {
|
|
859
|
+
onEventsAvailable({
|
|
860
|
+
newSyncId: event.payload.newSyncId,
|
|
861
|
+
remoteEventHlcSum: event.payload.eventHlcSum
|
|
862
|
+
});
|
|
863
|
+
});
|
|
864
|
+
return {
|
|
865
|
+
pullEvents: (request) => workerClient.pullEvents(request),
|
|
866
|
+
pushEvents: (request) => workerClient.pushTabEvents(request),
|
|
867
|
+
disconnect: () => {
|
|
868
|
+
subscription.unsubscribe();
|
|
869
|
+
}
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
});
|
|
873
|
+
tabRemoteSource.goOnline();
|
|
874
|
+
const reloadRequestedSubscription = workerClient.subscribe("reload-requested", () => {
|
|
875
|
+
globalThis.location?.reload();
|
|
876
|
+
});
|
|
877
|
+
perf.logEnd("createSyncedDb", "initialized", "info");
|
|
878
|
+
const exportData = createExportData({
|
|
879
|
+
reactiveDb,
|
|
880
|
+
tablesConfig: options.syncDbSchema.tablesConfig,
|
|
881
|
+
schemaVersion: workerClientSnapshot.schemaVersion
|
|
882
|
+
});
|
|
883
|
+
const importData = (data, opts) => workerClient.importData(data, opts);
|
|
884
|
+
let isDisposed = false;
|
|
885
|
+
let unregisterDevtools;
|
|
886
|
+
const dispose = async () => {
|
|
887
|
+
if (isDisposed) return;
|
|
888
|
+
isDisposed = true;
|
|
889
|
+
unregisterDevtools?.();
|
|
890
|
+
reloadRequestedSubscription.unsubscribe();
|
|
891
|
+
clientLockRelease.resolve();
|
|
892
|
+
await tabRemoteSource.dispose();
|
|
893
|
+
broadcastChannels.requests.close();
|
|
894
|
+
broadcastChannels.responses.close();
|
|
895
|
+
workerClient.dispose();
|
|
896
|
+
reactiveDb.dispose();
|
|
897
|
+
};
|
|
898
|
+
const syncedDb = {
|
|
899
|
+
db: createSyncedDbDatabase(reactiveDb),
|
|
900
|
+
state: {
|
|
901
|
+
getState: workerClient.getState.bind(workerClient),
|
|
902
|
+
subscribe: (onChange) => {
|
|
903
|
+
const { unsubscribe } = workerClient.subscribe("state-changed", onChange);
|
|
904
|
+
return unsubscribe;
|
|
905
|
+
},
|
|
906
|
+
goOnline: workerClient.goOnline.bind(workerClient),
|
|
907
|
+
goOffline: workerClient.goOffline.bind(workerClient)
|
|
908
|
+
},
|
|
909
|
+
/**
|
|
910
|
+
* Ask the elected worker to broadcast a page reload to all tabs for this dbId.
|
|
911
|
+
*
|
|
912
|
+
* With `clean: true` the worker durably records a reset request epoch before
|
|
913
|
+
* broadcasting, so the worker elected on the next startup initializes with
|
|
914
|
+
* `clearOnInit: true` and wipes the persisted DB. Destructive — use as a
|
|
915
|
+
* recovery path when the durable worker DB may be de-synced.
|
|
916
|
+
*
|
|
917
|
+
* Pending in-memory tab events are not preserved, and the returned promise
|
|
918
|
+
* may never settle in the caller — the page typically unloads first.
|
|
919
|
+
*/
|
|
920
|
+
requestReload: async (options) => {
|
|
921
|
+
await workerClient.requestReload(options);
|
|
922
|
+
setTimeout(() => globalThis.location?.reload(), 250);
|
|
923
|
+
},
|
|
924
|
+
subscribe: workerClient.subscribe,
|
|
925
|
+
exportData,
|
|
926
|
+
importData,
|
|
927
|
+
dispose,
|
|
928
|
+
_internal: {
|
|
929
|
+
executeAsync: workerClient.execute.bind(workerClient),
|
|
930
|
+
getMemoryQueryTables: reactiveDb.getTablesUsed.bind(reactiveDb),
|
|
931
|
+
getSharedLiveQueriesSnapshot: reactiveDb.getSharedLiveQueriesSnapshot.bind(reactiveDb),
|
|
932
|
+
crdtTableNames: options.syncDbSchema.tablesConfig.map((table) => table.crdtTableName),
|
|
933
|
+
crdtTablesConfig: options.syncDbSchema.tablesConfig,
|
|
934
|
+
schemaVersion: workerClientSnapshot.schemaVersion,
|
|
935
|
+
migrationVersions: Object.keys(options.syncDbSchema.migrations).map(Number).sort((a, b) => a - b)
|
|
936
|
+
}
|
|
937
|
+
};
|
|
938
|
+
unregisterDevtools = getOrCreateSQLiteSyncDevtoolsRegistry().register({
|
|
939
|
+
instanceId,
|
|
940
|
+
dbId: options.dbId,
|
|
941
|
+
createdAt: Date.now(),
|
|
942
|
+
instance: syncedDb
|
|
943
|
+
});
|
|
944
|
+
return syncedDb;
|
|
1276
945
|
}
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
CrdtEventValidationError,
|
|
1281
|
-
HLCCounter,
|
|
1282
|
-
RESET_REQUEST_TTL_MS,
|
|
1283
|
-
SQLiteDbWrapper,
|
|
1284
|
-
SQLiteReactiveDb,
|
|
1285
|
-
TableBuilder,
|
|
1286
|
-
TypedBroadcastChannel,
|
|
1287
|
-
TypedEvent,
|
|
1288
|
-
applyMemoryDbSchema,
|
|
1289
|
-
applyWorkerDbSchema,
|
|
1290
|
-
baseSystemMigrations,
|
|
1291
|
-
compareHLC,
|
|
1292
|
-
createCrdtApplyFunction,
|
|
1293
|
-
createCrdtStorage,
|
|
1294
|
-
createCrdtStorageMutator,
|
|
1295
|
-
createCrdtSyncProducer,
|
|
1296
|
-
createCrdtSyncRemoteSource,
|
|
1297
|
-
createDeferredPromise,
|
|
1298
|
-
createExportData,
|
|
1299
|
-
createImportData,
|
|
1300
|
-
createKvStoreTableQuery,
|
|
1301
|
-
createMigrations,
|
|
1302
|
-
createMigrator,
|
|
1303
|
-
createSQLiteCrdtApplyFunction,
|
|
1304
|
-
createSQLiteKvStore,
|
|
1305
|
-
createStoredValue,
|
|
1306
|
-
createSyncedDb,
|
|
1307
|
-
createSystemDbConfig,
|
|
1308
|
-
createTypedEventTarget,
|
|
1309
|
-
defineSyncSchema,
|
|
1310
|
-
deserializeHLC,
|
|
1311
|
-
dummyKysely,
|
|
1312
|
-
formatSchemaVerificationIssues,
|
|
1313
|
-
generateId,
|
|
1314
|
-
introspectDb,
|
|
1315
|
-
isNoOpCrdtEventPayload,
|
|
1316
|
-
jsonSafeParse,
|
|
1317
|
-
makeCrdtTable,
|
|
1318
|
-
parametersAreEqual,
|
|
1319
|
-
quoteId,
|
|
1320
|
-
runSystemMigrations,
|
|
1321
|
-
serializeHLC,
|
|
1322
|
-
startPerformanceLogger,
|
|
1323
|
-
t,
|
|
1324
|
-
tryCatch,
|
|
1325
|
-
tryCatchAsync,
|
|
1326
|
-
validateNewCrdtEvent,
|
|
1327
|
-
verifySyncSchema,
|
|
1328
|
-
xxhash
|
|
1329
|
-
};
|
|
946
|
+
//#endregion
|
|
947
|
+
export { CRDT_EVENT_NO_OP_PAYLOAD, ColumnBuilder, CrdtEventValidationError, HLCCounter, RESET_REQUEST_TTL_MS, SQLiteDbWrapper, SQLiteReactiveDb, TableBuilder, TypedBroadcastChannel, TypedEvent, applyMemoryDbSchema, applyWorkerDbSchema, compareHLC, createCrdtApplyFunction, createCrdtStorage, createCrdtStorageMutator, createCrdtSyncProducer, createCrdtSyncRemoteSource, createDeferredPromise, createExportData, createImportData, createKvStoreTableQuery, createMigrations, createMigrator, createSQLiteCrdtApplyFunction, createSQLiteKvStore, createStoredValue, createSyncedDb, createTypedEventTarget, defineSyncSchema, deserializeHLC, dummyKysely, formatSchemaVerificationIssues, generateId, introspectDb, isNoOpCrdtEventPayload, jsonSafeParse, makeCrdtTable, parametersAreEqual, quoteId, serializeHLC, startPerformanceLogger, t, tryCatch, tryCatchAsync, validateNewCrdtEvent, verifySyncSchema, xxhash };
|
|
948
|
+
|
|
1330
949
|
//# sourceMappingURL=index.js.map
|