@taladb/web 0.6.0 → 0.7.3
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/package.json +1 -1
- package/pkg/package.json +2 -2
- package/pkg/taladb_web.d.ts +84 -19
- package/pkg/taladb_web.js +155 -26
- package/pkg/taladb_web_bg.wasm +0 -0
- package/pkg/taladb_web_bg.wasm.d.ts +15 -10
- package/worker/taladb.worker.js +149 -9
package/package.json
CHANGED
package/pkg/package.json
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
"collaborators": [
|
|
5
5
|
"thinkgrid-labs"
|
|
6
6
|
],
|
|
7
|
-
"description": "TalaDB browser WASM bindings (wasm-bindgen + OPFS)",
|
|
8
|
-
"version": "0.
|
|
7
|
+
"description": "TalaDB browser WASM bindings (wasm-bindgen + OPFS) and Cloudflare Workers",
|
|
8
|
+
"version": "0.7.3",
|
|
9
9
|
"license": "MIT",
|
|
10
10
|
"files": [
|
|
11
11
|
"taladb_web_bg.wasm",
|
package/pkg/taladb_web.d.ts
CHANGED
|
@@ -16,11 +16,11 @@ export class CollectionWasm {
|
|
|
16
16
|
/**
|
|
17
17
|
* Create a vector index on `field`.
|
|
18
18
|
*
|
|
19
|
-
* `dimensions`
|
|
20
|
-
* `metric`
|
|
21
|
-
* `index_type`
|
|
22
|
-
* `hnsw_m`
|
|
23
|
-
* `hnsw_ef_construction`
|
|
19
|
+
* `dimensions` - expected vector length.
|
|
20
|
+
* `metric` - optional: `"cosine"` (default), `"dot"`, or `"euclidean"`.
|
|
21
|
+
* `index_type` - optional: `"flat"` (default) or `"hnsw"`.
|
|
22
|
+
* `hnsw_m` - HNSW connectivity (default 16).
|
|
23
|
+
* `hnsw_ef_construction` - build quality (default 200).
|
|
24
24
|
*/
|
|
25
25
|
createVectorIndex(field: string, dimensions: number, metric?: string | null, index_type?: string | null, hnsw_m?: number | null, hnsw_ef_construction?: number | null): void;
|
|
26
26
|
/**
|
|
@@ -46,7 +46,7 @@ export class CollectionWasm {
|
|
|
46
46
|
/**
|
|
47
47
|
* Find the `top_k` nearest documents to `query` on a vector index.
|
|
48
48
|
*
|
|
49
|
-
* `filter`
|
|
49
|
+
* `filter` - optional pre-filter (same format as `find`). Pass `null` to
|
|
50
50
|
* search across all documents that have the vector field.
|
|
51
51
|
*
|
|
52
52
|
* Returns a JSON array of `{ document: {...}, score: number }` objects.
|
|
@@ -108,7 +108,7 @@ export class TalaDBWasm {
|
|
|
108
108
|
* ```js
|
|
109
109
|
* const bytes = await opfs_load_snapshot('myapp.db'); // null on first open
|
|
110
110
|
* const db = TalaDBWasm.openWithSnapshot(bytes);
|
|
111
|
-
* //
|
|
111
|
+
* // ... mutations ...
|
|
112
112
|
* await opfs_flush_snapshot('myapp.db', db.exportSnapshot());
|
|
113
113
|
* ```
|
|
114
114
|
*/
|
|
@@ -119,6 +119,32 @@ export class WorkerDB {
|
|
|
119
119
|
private constructor();
|
|
120
120
|
free(): void;
|
|
121
121
|
[Symbol.dispose](): void;
|
|
122
|
+
/**
|
|
123
|
+
* Compact the underlying OPFS / redb storage file, reclaiming space freed
|
|
124
|
+
* by deletes and updates.
|
|
125
|
+
*
|
|
126
|
+
* Call this during idle periods (e.g. once on app startup after tombstone
|
|
127
|
+
* compaction). No-op on in-memory (IDB-fallback) databases.
|
|
128
|
+
*
|
|
129
|
+
* ```js
|
|
130
|
+
* db.compact();
|
|
131
|
+
* ```
|
|
132
|
+
*/
|
|
133
|
+
compact(): void;
|
|
134
|
+
/**
|
|
135
|
+
* Remove tombstones older than `before_ms` from the given collection.
|
|
136
|
+
*
|
|
137
|
+
* Call periodically (e.g. on app startup) after your sync retention window
|
|
138
|
+
* has elapsed so deleted document IDs no longer accumulate indefinitely.
|
|
139
|
+
* Returns the number of tombstones removed.
|
|
140
|
+
*
|
|
141
|
+
* ```js
|
|
142
|
+
* // Prune tombstones older than 30 days
|
|
143
|
+
* const cutoff = Date.now() - 30 * 24 * 60 * 60 * 1000;
|
|
144
|
+
* const pruned = db.compactTombstones('users', cutoff);
|
|
145
|
+
* ```
|
|
146
|
+
*/
|
|
147
|
+
compactTombstones(collection: string, before_ms: number): number;
|
|
122
148
|
/**
|
|
123
149
|
* Count matching documents.
|
|
124
150
|
*/
|
|
@@ -148,6 +174,18 @@ export class WorkerDB {
|
|
|
148
174
|
* Drop a vector index (and its HNSW graph if present).
|
|
149
175
|
*/
|
|
150
176
|
dropVectorIndex(collection: string, field: string): void;
|
|
177
|
+
/**
|
|
178
|
+
* Export a changeset for the given collections since `since_ms`.
|
|
179
|
+
*
|
|
180
|
+
* Returns a JSON string representing `Vec<Change>` that can be sent
|
|
181
|
+
* to a remote peer via fetch, WebSocket, or SSE.
|
|
182
|
+
*
|
|
183
|
+
* ```js
|
|
184
|
+
* const json = db.exportChangeset(JSON.stringify(['users', 'posts']), 0);
|
|
185
|
+
* await fetch('/sync', { method: 'POST', body: json });
|
|
186
|
+
* ```
|
|
187
|
+
*/
|
|
188
|
+
exportChangeset(collections_json: string, since_ms: number): string;
|
|
151
189
|
/**
|
|
152
190
|
* Serialize the entire in-memory database to bytes for persistence.
|
|
153
191
|
*
|
|
@@ -167,6 +205,19 @@ export class WorkerDB {
|
|
|
167
205
|
* Find one document. Returns a JSON object or `"null"`.
|
|
168
206
|
*/
|
|
169
207
|
findOne(collection: string, filter_json: string): string;
|
|
208
|
+
/**
|
|
209
|
+
* Import a remote changeset and merge it into the local database using
|
|
210
|
+
* Last-Write-Wins conflict resolution.
|
|
211
|
+
*
|
|
212
|
+
* Returns the number of documents actually changed.
|
|
213
|
+
*
|
|
214
|
+
* ```js
|
|
215
|
+
* const resp = await fetch('/sync?since=' + lastSync);
|
|
216
|
+
* const applied = db.importChangeset(await resp.text());
|
|
217
|
+
* if (applied > 0) { rerender(); }
|
|
218
|
+
* ```
|
|
219
|
+
*/
|
|
220
|
+
importChangeset(changeset_json: string): number;
|
|
170
221
|
/**
|
|
171
222
|
* Insert a document. Returns the new ULID as a string.
|
|
172
223
|
*/
|
|
@@ -175,6 +226,11 @@ export class WorkerDB {
|
|
|
175
226
|
* Insert many documents. Returns a JSON array of ULID strings.
|
|
176
227
|
*/
|
|
177
228
|
insertMany(collection: string, docs_json: string): string;
|
|
229
|
+
/**
|
|
230
|
+
* Returns a JSON array of all collection names in the database.
|
|
231
|
+
* Used by the Worker to build the collections list for exportChangeset.
|
|
232
|
+
*/
|
|
233
|
+
listCollections(): string;
|
|
178
234
|
/**
|
|
179
235
|
* Returns a JSON string `{ btree: string[], fts: string[], vector: string[] }`
|
|
180
236
|
* listing all indexes on the given collection.
|
|
@@ -187,7 +243,9 @@ export class WorkerDB {
|
|
|
187
243
|
/**
|
|
188
244
|
* Open a database backed by OPFS with HTTP push sync config.
|
|
189
245
|
*
|
|
190
|
-
*
|
|
246
|
+
* Not available when compiled with the `cf-workers` feature.
|
|
247
|
+
*
|
|
248
|
+
* `config_json` - JSON-serialised `TalaDbConfig`, or `null` to open without sync.
|
|
191
249
|
*
|
|
192
250
|
* ```js
|
|
193
251
|
* const handle = await file_handle.createSyncAccessHandle();
|
|
@@ -198,7 +256,7 @@ export class WorkerDB {
|
|
|
198
256
|
/**
|
|
199
257
|
* Open a database from an optional snapshot with HTTP push sync config.
|
|
200
258
|
*
|
|
201
|
-
* `config_json`
|
|
259
|
+
* `config_json` - JSON-serialised `TalaDbConfig`, or `null` to open without sync.
|
|
202
260
|
*
|
|
203
261
|
* ```js
|
|
204
262
|
* const db = WorkerDB.openWithConfigAndSnapshot(snapshot, JSON.stringify(config));
|
|
@@ -208,6 +266,8 @@ export class WorkerDB {
|
|
|
208
266
|
/**
|
|
209
267
|
* Open a database backed by an OPFS `FileSystemSyncAccessHandle`.
|
|
210
268
|
*
|
|
269
|
+
* Not available when compiled with the `cf-workers` feature.
|
|
270
|
+
*
|
|
211
271
|
* Call sequence in the SharedWorker:
|
|
212
272
|
* ```js
|
|
213
273
|
* const handle = await file_handle.createSyncAccessHandle();
|
|
@@ -298,6 +358,8 @@ export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembl
|
|
|
298
358
|
export interface InitOutput {
|
|
299
359
|
readonly memory: WebAssembly.Memory;
|
|
300
360
|
readonly __wbg_workerdb_free: (a: number, b: number) => void;
|
|
361
|
+
readonly workerdb_compact: (a: number) => [number, number];
|
|
362
|
+
readonly workerdb_compactTombstones: (a: number, b: number, c: number, d: number) => [number, number, number];
|
|
301
363
|
readonly workerdb_count: (a: number, b: number, c: number, d: number, e: number) => [number, number, number];
|
|
302
364
|
readonly workerdb_createFtsIndex: (a: number, b: number, c: number, d: number, e: number) => [number, number];
|
|
303
365
|
readonly workerdb_createIndex: (a: number, b: number, c: number, d: number, e: number) => [number, number];
|
|
@@ -307,12 +369,15 @@ export interface InitOutput {
|
|
|
307
369
|
readonly workerdb_dropFtsIndex: (a: number, b: number, c: number, d: number, e: number) => [number, number];
|
|
308
370
|
readonly workerdb_dropIndex: (a: number, b: number, c: number, d: number, e: number) => [number, number];
|
|
309
371
|
readonly workerdb_dropVectorIndex: (a: number, b: number, c: number, d: number, e: number) => [number, number];
|
|
372
|
+
readonly workerdb_exportChangeset: (a: number, b: number, c: number, d: number) => [number, number, number, number];
|
|
310
373
|
readonly workerdb_exportSnapshot: (a: number) => [number, number, number, number];
|
|
311
374
|
readonly workerdb_find: (a: number, b: number, c: number, d: number, e: number) => [number, number, number, number];
|
|
312
375
|
readonly workerdb_findNearest: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number) => [number, number, number, number];
|
|
313
376
|
readonly workerdb_findOne: (a: number, b: number, c: number, d: number, e: number) => [number, number, number, number];
|
|
377
|
+
readonly workerdb_importChangeset: (a: number, b: number, c: number) => [number, number, number];
|
|
314
378
|
readonly workerdb_insert: (a: number, b: number, c: number, d: number, e: number) => [number, number, number, number];
|
|
315
379
|
readonly workerdb_insertMany: (a: number, b: number, c: number, d: number, e: number) => [number, number, number, number];
|
|
380
|
+
readonly workerdb_listCollections: (a: number) => [number, number, number, number];
|
|
316
381
|
readonly workerdb_listIndexes: (a: number, b: number, c: number) => [number, number, number, number];
|
|
317
382
|
readonly workerdb_openInMemory: () => [number, number, number];
|
|
318
383
|
readonly workerdb_openWithConfigAndOpfs: (a: any, b: number, c: number) => [number, number, number];
|
|
@@ -339,25 +404,25 @@ export interface InitOutput {
|
|
|
339
404
|
readonly collectionwasm_updateMany: (a: number, b: any, c: any) => [number, number, number];
|
|
340
405
|
readonly collectionwasm_updateOne: (a: number, b: any, c: any) => [number, number, number];
|
|
341
406
|
readonly collectionwasm_upgradeVectorIndex: (a: number, b: number, c: number) => [number, number];
|
|
342
|
-
readonly taladbwasm_collection: (a: number, b: number, c: number) => number;
|
|
407
|
+
readonly taladbwasm_collection: (a: number, b: number, c: number) => [number, number, number];
|
|
343
408
|
readonly taladbwasm_exportSnapshot: (a: number) => [number, number, number, number];
|
|
344
409
|
readonly taladbwasm_openInMemory: () => [number, number, number];
|
|
345
410
|
readonly taladbwasm_openWithSnapshot: (a: number, b: number) => [number, number, number];
|
|
346
411
|
readonly init: () => void;
|
|
347
|
-
readonly idb_load_snapshot: (a: number, b: number) => any;
|
|
348
|
-
readonly idb_save_snapshot: (a: number, b: number, c: number, d: number) => any;
|
|
349
412
|
readonly is_opfs_available: () => any;
|
|
350
413
|
readonly opfs_delete_snapshot: (a: number, b: number) => any;
|
|
351
414
|
readonly opfs_flush_snapshot: (a: number, b: number, c: number, d: number) => any;
|
|
352
415
|
readonly opfs_load_snapshot: (a: number, b: number) => any;
|
|
416
|
+
readonly idb_load_snapshot: (a: number, b: number) => any;
|
|
417
|
+
readonly idb_save_snapshot: (a: number, b: number, c: number, d: number) => any;
|
|
353
418
|
readonly opfs_open_backend: (a: number, b: number) => any;
|
|
354
|
-
readonly
|
|
355
|
-
readonly
|
|
356
|
-
readonly
|
|
357
|
-
readonly
|
|
358
|
-
readonly
|
|
359
|
-
readonly
|
|
360
|
-
readonly
|
|
419
|
+
readonly wasm_bindgen__closure__destroy__hf5d71bc2d02a6064: (a: number, b: number) => void;
|
|
420
|
+
readonly wasm_bindgen__closure__destroy__h48e8a8f67ee293c6: (a: number, b: number) => void;
|
|
421
|
+
readonly wasm_bindgen__closure__destroy__h8b8dc50e88fa67bc: (a: number, b: number) => void;
|
|
422
|
+
readonly wasm_bindgen__convert__closures_____invoke__hfb64ebab929241fc: (a: number, b: number, c: any) => [number, number];
|
|
423
|
+
readonly wasm_bindgen__convert__closures_____invoke__h31b79bb0cf86afae: (a: number, b: number, c: any, d: any) => void;
|
|
424
|
+
readonly wasm_bindgen__convert__closures_____invoke__h57a4f572d4d576bf: (a: number, b: number, c: any) => void;
|
|
425
|
+
readonly wasm_bindgen__convert__closures_____invoke__had96e062ebd712a6: (a: number, b: number) => void;
|
|
361
426
|
readonly __wbindgen_malloc: (a: number, b: number) => number;
|
|
362
427
|
readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
|
|
363
428
|
readonly __wbindgen_exn_store: (a: number) => void;
|
package/pkg/taladb_web.js
CHANGED
|
@@ -45,11 +45,11 @@ export class CollectionWasm {
|
|
|
45
45
|
/**
|
|
46
46
|
* Create a vector index on `field`.
|
|
47
47
|
*
|
|
48
|
-
* `dimensions`
|
|
49
|
-
* `metric`
|
|
50
|
-
* `index_type`
|
|
51
|
-
* `hnsw_m`
|
|
52
|
-
* `hnsw_ef_construction`
|
|
48
|
+
* `dimensions` - expected vector length.
|
|
49
|
+
* `metric` - optional: `"cosine"` (default), `"dot"`, or `"euclidean"`.
|
|
50
|
+
* `index_type` - optional: `"flat"` (default) or `"hnsw"`.
|
|
51
|
+
* `hnsw_m` - HNSW connectivity (default 16).
|
|
52
|
+
* `hnsw_ef_construction` - build quality (default 200).
|
|
53
53
|
* @param {string} field
|
|
54
54
|
* @param {number} dimensions
|
|
55
55
|
* @param {string | null} [metric]
|
|
@@ -132,7 +132,7 @@ export class CollectionWasm {
|
|
|
132
132
|
/**
|
|
133
133
|
* Find the `top_k` nearest documents to `query` on a vector index.
|
|
134
134
|
*
|
|
135
|
-
* `filter`
|
|
135
|
+
* `filter` - optional pre-filter (same format as `find`). Pass `null` to
|
|
136
136
|
* search across all documents that have the vector field.
|
|
137
137
|
*
|
|
138
138
|
* Returns a JSON array of `{ document: {...}, score: number }` objects.
|
|
@@ -268,7 +268,10 @@ export class TalaDBWasm {
|
|
|
268
268
|
const ptr0 = passStringToWasm0(name, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
269
269
|
const len0 = WASM_VECTOR_LEN;
|
|
270
270
|
const ret = wasm.taladbwasm_collection(this.__wbg_ptr, ptr0, len0);
|
|
271
|
-
|
|
271
|
+
if (ret[2]) {
|
|
272
|
+
throw takeFromExternrefTable0(ret[1]);
|
|
273
|
+
}
|
|
274
|
+
return CollectionWasm.__wrap(ret[0]);
|
|
272
275
|
}
|
|
273
276
|
/**
|
|
274
277
|
* Serialize the entire in-memory database to bytes.
|
|
@@ -308,7 +311,7 @@ export class TalaDBWasm {
|
|
|
308
311
|
* ```js
|
|
309
312
|
* const bytes = await opfs_load_snapshot('myapp.db'); // null on first open
|
|
310
313
|
* const db = TalaDBWasm.openWithSnapshot(bytes);
|
|
311
|
-
* //
|
|
314
|
+
* // ... mutations ...
|
|
312
315
|
* await opfs_flush_snapshot('myapp.db', db.exportSnapshot());
|
|
313
316
|
* ```
|
|
314
317
|
* @param {Uint8Array | null} [snapshot]
|
|
@@ -344,6 +347,48 @@ export class WorkerDB {
|
|
|
344
347
|
const ptr = this.__destroy_into_raw();
|
|
345
348
|
wasm.__wbg_workerdb_free(ptr, 0);
|
|
346
349
|
}
|
|
350
|
+
/**
|
|
351
|
+
* Compact the underlying OPFS / redb storage file, reclaiming space freed
|
|
352
|
+
* by deletes and updates.
|
|
353
|
+
*
|
|
354
|
+
* Call this during idle periods (e.g. once on app startup after tombstone
|
|
355
|
+
* compaction). No-op on in-memory (IDB-fallback) databases.
|
|
356
|
+
*
|
|
357
|
+
* ```js
|
|
358
|
+
* db.compact();
|
|
359
|
+
* ```
|
|
360
|
+
*/
|
|
361
|
+
compact() {
|
|
362
|
+
const ret = wasm.workerdb_compact(this.__wbg_ptr);
|
|
363
|
+
if (ret[1]) {
|
|
364
|
+
throw takeFromExternrefTable0(ret[0]);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Remove tombstones older than `before_ms` from the given collection.
|
|
369
|
+
*
|
|
370
|
+
* Call periodically (e.g. on app startup) after your sync retention window
|
|
371
|
+
* has elapsed so deleted document IDs no longer accumulate indefinitely.
|
|
372
|
+
* Returns the number of tombstones removed.
|
|
373
|
+
*
|
|
374
|
+
* ```js
|
|
375
|
+
* // Prune tombstones older than 30 days
|
|
376
|
+
* const cutoff = Date.now() - 30 * 24 * 60 * 60 * 1000;
|
|
377
|
+
* const pruned = db.compactTombstones('users', cutoff);
|
|
378
|
+
* ```
|
|
379
|
+
* @param {string} collection
|
|
380
|
+
* @param {number} before_ms
|
|
381
|
+
* @returns {number}
|
|
382
|
+
*/
|
|
383
|
+
compactTombstones(collection, before_ms) {
|
|
384
|
+
const ptr0 = passStringToWasm0(collection, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
385
|
+
const len0 = WASM_VECTOR_LEN;
|
|
386
|
+
const ret = wasm.workerdb_compactTombstones(this.__wbg_ptr, ptr0, len0, before_ms);
|
|
387
|
+
if (ret[2]) {
|
|
388
|
+
throw takeFromExternrefTable0(ret[1]);
|
|
389
|
+
}
|
|
390
|
+
return ret[0] >>> 0;
|
|
391
|
+
}
|
|
347
392
|
/**
|
|
348
393
|
* Count matching documents.
|
|
349
394
|
* @param {string} collection
|
|
@@ -495,6 +540,40 @@ export class WorkerDB {
|
|
|
495
540
|
throw takeFromExternrefTable0(ret[0]);
|
|
496
541
|
}
|
|
497
542
|
}
|
|
543
|
+
/**
|
|
544
|
+
* Export a changeset for the given collections since `since_ms`.
|
|
545
|
+
*
|
|
546
|
+
* Returns a JSON string representing `Vec<Change>` that can be sent
|
|
547
|
+
* to a remote peer via fetch, WebSocket, or SSE.
|
|
548
|
+
*
|
|
549
|
+
* ```js
|
|
550
|
+
* const json = db.exportChangeset(JSON.stringify(['users', 'posts']), 0);
|
|
551
|
+
* await fetch('/sync', { method: 'POST', body: json });
|
|
552
|
+
* ```
|
|
553
|
+
* @param {string} collections_json
|
|
554
|
+
* @param {number} since_ms
|
|
555
|
+
* @returns {string}
|
|
556
|
+
*/
|
|
557
|
+
exportChangeset(collections_json, since_ms) {
|
|
558
|
+
let deferred3_0;
|
|
559
|
+
let deferred3_1;
|
|
560
|
+
try {
|
|
561
|
+
const ptr0 = passStringToWasm0(collections_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
562
|
+
const len0 = WASM_VECTOR_LEN;
|
|
563
|
+
const ret = wasm.workerdb_exportChangeset(this.__wbg_ptr, ptr0, len0, since_ms);
|
|
564
|
+
var ptr2 = ret[0];
|
|
565
|
+
var len2 = ret[1];
|
|
566
|
+
if (ret[3]) {
|
|
567
|
+
ptr2 = 0; len2 = 0;
|
|
568
|
+
throw takeFromExternrefTable0(ret[2]);
|
|
569
|
+
}
|
|
570
|
+
deferred3_0 = ptr2;
|
|
571
|
+
deferred3_1 = len2;
|
|
572
|
+
return getStringFromWasm0(ptr2, len2);
|
|
573
|
+
} finally {
|
|
574
|
+
wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
498
577
|
/**
|
|
499
578
|
* Serialize the entire in-memory database to bytes for persistence.
|
|
500
579
|
*
|
|
@@ -602,6 +681,29 @@ export class WorkerDB {
|
|
|
602
681
|
wasm.__wbindgen_free(deferred4_0, deferred4_1, 1);
|
|
603
682
|
}
|
|
604
683
|
}
|
|
684
|
+
/**
|
|
685
|
+
* Import a remote changeset and merge it into the local database using
|
|
686
|
+
* Last-Write-Wins conflict resolution.
|
|
687
|
+
*
|
|
688
|
+
* Returns the number of documents actually changed.
|
|
689
|
+
*
|
|
690
|
+
* ```js
|
|
691
|
+
* const resp = await fetch('/sync?since=' + lastSync);
|
|
692
|
+
* const applied = db.importChangeset(await resp.text());
|
|
693
|
+
* if (applied > 0) { rerender(); }
|
|
694
|
+
* ```
|
|
695
|
+
* @param {string} changeset_json
|
|
696
|
+
* @returns {number}
|
|
697
|
+
*/
|
|
698
|
+
importChangeset(changeset_json) {
|
|
699
|
+
const ptr0 = passStringToWasm0(changeset_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
700
|
+
const len0 = WASM_VECTOR_LEN;
|
|
701
|
+
const ret = wasm.workerdb_importChangeset(this.__wbg_ptr, ptr0, len0);
|
|
702
|
+
if (ret[2]) {
|
|
703
|
+
throw takeFromExternrefTable0(ret[1]);
|
|
704
|
+
}
|
|
705
|
+
return ret[0] >>> 0;
|
|
706
|
+
}
|
|
605
707
|
/**
|
|
606
708
|
* Insert a document. Returns the new ULID as a string.
|
|
607
709
|
* @param {string} collection
|
|
@@ -658,6 +760,29 @@ export class WorkerDB {
|
|
|
658
760
|
wasm.__wbindgen_free(deferred4_0, deferred4_1, 1);
|
|
659
761
|
}
|
|
660
762
|
}
|
|
763
|
+
/**
|
|
764
|
+
* Returns a JSON array of all collection names in the database.
|
|
765
|
+
* Used by the Worker to build the collections list for exportChangeset.
|
|
766
|
+
* @returns {string}
|
|
767
|
+
*/
|
|
768
|
+
listCollections() {
|
|
769
|
+
let deferred2_0;
|
|
770
|
+
let deferred2_1;
|
|
771
|
+
try {
|
|
772
|
+
const ret = wasm.workerdb_listCollections(this.__wbg_ptr);
|
|
773
|
+
var ptr1 = ret[0];
|
|
774
|
+
var len1 = ret[1];
|
|
775
|
+
if (ret[3]) {
|
|
776
|
+
ptr1 = 0; len1 = 0;
|
|
777
|
+
throw takeFromExternrefTable0(ret[2]);
|
|
778
|
+
}
|
|
779
|
+
deferred2_0 = ptr1;
|
|
780
|
+
deferred2_1 = len1;
|
|
781
|
+
return getStringFromWasm0(ptr1, len1);
|
|
782
|
+
} finally {
|
|
783
|
+
wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
661
786
|
/**
|
|
662
787
|
* Returns a JSON string `{ btree: string[], fts: string[], vector: string[] }`
|
|
663
788
|
* listing all indexes on the given collection.
|
|
@@ -698,7 +823,9 @@ export class WorkerDB {
|
|
|
698
823
|
/**
|
|
699
824
|
* Open a database backed by OPFS with HTTP push sync config.
|
|
700
825
|
*
|
|
701
|
-
*
|
|
826
|
+
* Not available when compiled with the `cf-workers` feature.
|
|
827
|
+
*
|
|
828
|
+
* `config_json` - JSON-serialised `TalaDbConfig`, or `null` to open without sync.
|
|
702
829
|
*
|
|
703
830
|
* ```js
|
|
704
831
|
* const handle = await file_handle.createSyncAccessHandle();
|
|
@@ -720,7 +847,7 @@ export class WorkerDB {
|
|
|
720
847
|
/**
|
|
721
848
|
* Open a database from an optional snapshot with HTTP push sync config.
|
|
722
849
|
*
|
|
723
|
-
* `config_json`
|
|
850
|
+
* `config_json` - JSON-serialised `TalaDbConfig`, or `null` to open without sync.
|
|
724
851
|
*
|
|
725
852
|
* ```js
|
|
726
853
|
* const db = WorkerDB.openWithConfigAndSnapshot(snapshot, JSON.stringify(config));
|
|
@@ -743,6 +870,8 @@ export class WorkerDB {
|
|
|
743
870
|
/**
|
|
744
871
|
* Open a database backed by an OPFS `FileSystemSyncAccessHandle`.
|
|
745
872
|
*
|
|
873
|
+
* Not available when compiled with the `cf-workers` feature.
|
|
874
|
+
*
|
|
746
875
|
* Call sequence in the SharedWorker:
|
|
747
876
|
* ```js
|
|
748
877
|
* const handle = await file_handle.createSyncAccessHandle();
|
|
@@ -1324,7 +1453,7 @@ function __wbg_get_imports() {
|
|
|
1324
1453
|
const a = state0.a;
|
|
1325
1454
|
state0.a = 0;
|
|
1326
1455
|
try {
|
|
1327
|
-
return
|
|
1456
|
+
return wasm_bindgen__convert__closures_____invoke__h31b79bb0cf86afae(a, state0.b, arg0, arg1);
|
|
1328
1457
|
} finally {
|
|
1329
1458
|
state0.a = a;
|
|
1330
1459
|
}
|
|
@@ -1346,7 +1475,7 @@ function __wbg_get_imports() {
|
|
|
1346
1475
|
const a = state0.a;
|
|
1347
1476
|
state0.a = 0;
|
|
1348
1477
|
try {
|
|
1349
|
-
return
|
|
1478
|
+
return wasm_bindgen__convert__closures_____invoke__h31b79bb0cf86afae(a, state0.b, arg0, arg1);
|
|
1350
1479
|
} finally {
|
|
1351
1480
|
state0.a = a;
|
|
1352
1481
|
}
|
|
@@ -1507,18 +1636,18 @@ function __wbg_get_imports() {
|
|
|
1507
1636
|
return ret;
|
|
1508
1637
|
},
|
|
1509
1638
|
__wbindgen_cast_0000000000000001: function(arg0, arg1) {
|
|
1510
|
-
// Cast intrinsic for `Closure(Closure { dtor_idx:
|
|
1511
|
-
const ret = makeMutClosure(arg0, arg1, wasm.
|
|
1639
|
+
// Cast intrinsic for `Closure(Closure { dtor_idx: 137, function: Function { arguments: [Externref], shim_idx: 138, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
|
1640
|
+
const ret = makeMutClosure(arg0, arg1, wasm.wasm_bindgen__closure__destroy__hf5d71bc2d02a6064, wasm_bindgen__convert__closures_____invoke__h57a4f572d4d576bf);
|
|
1512
1641
|
return ret;
|
|
1513
1642
|
},
|
|
1514
1643
|
__wbindgen_cast_0000000000000002: function(arg0, arg1) {
|
|
1515
|
-
// Cast intrinsic for `Closure(Closure { dtor_idx:
|
|
1516
|
-
const ret = makeMutClosure(arg0, arg1, wasm.
|
|
1644
|
+
// Cast intrinsic for `Closure(Closure { dtor_idx: 187, function: Function { arguments: [], shim_idx: 188, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
|
1645
|
+
const ret = makeMutClosure(arg0, arg1, wasm.wasm_bindgen__closure__destroy__h48e8a8f67ee293c6, wasm_bindgen__convert__closures_____invoke__had96e062ebd712a6);
|
|
1517
1646
|
return ret;
|
|
1518
1647
|
},
|
|
1519
1648
|
__wbindgen_cast_0000000000000003: function(arg0, arg1) {
|
|
1520
|
-
// Cast intrinsic for `Closure(Closure { dtor_idx:
|
|
1521
|
-
const ret = makeMutClosure(arg0, arg1, wasm.
|
|
1649
|
+
// Cast intrinsic for `Closure(Closure { dtor_idx: 629, function: Function { arguments: [Externref], shim_idx: 630, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
|
|
1650
|
+
const ret = makeMutClosure(arg0, arg1, wasm.wasm_bindgen__closure__destroy__h8b8dc50e88fa67bc, wasm_bindgen__convert__closures_____invoke__hfb64ebab929241fc);
|
|
1522
1651
|
return ret;
|
|
1523
1652
|
},
|
|
1524
1653
|
__wbindgen_cast_0000000000000004: function(arg0) {
|
|
@@ -1564,23 +1693,23 @@ function __wbg_get_imports() {
|
|
|
1564
1693
|
};
|
|
1565
1694
|
}
|
|
1566
1695
|
|
|
1567
|
-
function
|
|
1568
|
-
wasm.
|
|
1696
|
+
function wasm_bindgen__convert__closures_____invoke__had96e062ebd712a6(arg0, arg1) {
|
|
1697
|
+
wasm.wasm_bindgen__convert__closures_____invoke__had96e062ebd712a6(arg0, arg1);
|
|
1569
1698
|
}
|
|
1570
1699
|
|
|
1571
|
-
function
|
|
1572
|
-
wasm.
|
|
1700
|
+
function wasm_bindgen__convert__closures_____invoke__h57a4f572d4d576bf(arg0, arg1, arg2) {
|
|
1701
|
+
wasm.wasm_bindgen__convert__closures_____invoke__h57a4f572d4d576bf(arg0, arg1, arg2);
|
|
1573
1702
|
}
|
|
1574
1703
|
|
|
1575
|
-
function
|
|
1576
|
-
const ret = wasm.
|
|
1704
|
+
function wasm_bindgen__convert__closures_____invoke__hfb64ebab929241fc(arg0, arg1, arg2) {
|
|
1705
|
+
const ret = wasm.wasm_bindgen__convert__closures_____invoke__hfb64ebab929241fc(arg0, arg1, arg2);
|
|
1577
1706
|
if (ret[1]) {
|
|
1578
1707
|
throw takeFromExternrefTable0(ret[0]);
|
|
1579
1708
|
}
|
|
1580
1709
|
}
|
|
1581
1710
|
|
|
1582
|
-
function
|
|
1583
|
-
wasm.
|
|
1711
|
+
function wasm_bindgen__convert__closures_____invoke__h31b79bb0cf86afae(arg0, arg1, arg2, arg3) {
|
|
1712
|
+
wasm.wasm_bindgen__convert__closures_____invoke__h31b79bb0cf86afae(arg0, arg1, arg2, arg3);
|
|
1584
1713
|
}
|
|
1585
1714
|
|
|
1586
1715
|
|
package/pkg/taladb_web_bg.wasm
CHANGED
|
Binary file
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
/* eslint-disable */
|
|
3
3
|
export const memory: WebAssembly.Memory;
|
|
4
4
|
export const __wbg_workerdb_free: (a: number, b: number) => void;
|
|
5
|
+
export const workerdb_compact: (a: number) => [number, number];
|
|
6
|
+
export const workerdb_compactTombstones: (a: number, b: number, c: number, d: number) => [number, number, number];
|
|
5
7
|
export const workerdb_count: (a: number, b: number, c: number, d: number, e: number) => [number, number, number];
|
|
6
8
|
export const workerdb_createFtsIndex: (a: number, b: number, c: number, d: number, e: number) => [number, number];
|
|
7
9
|
export const workerdb_createIndex: (a: number, b: number, c: number, d: number, e: number) => [number, number];
|
|
@@ -11,12 +13,15 @@ export const workerdb_deleteOne: (a: number, b: number, c: number, d: number, e:
|
|
|
11
13
|
export const workerdb_dropFtsIndex: (a: number, b: number, c: number, d: number, e: number) => [number, number];
|
|
12
14
|
export const workerdb_dropIndex: (a: number, b: number, c: number, d: number, e: number) => [number, number];
|
|
13
15
|
export const workerdb_dropVectorIndex: (a: number, b: number, c: number, d: number, e: number) => [number, number];
|
|
16
|
+
export const workerdb_exportChangeset: (a: number, b: number, c: number, d: number) => [number, number, number, number];
|
|
14
17
|
export const workerdb_exportSnapshot: (a: number) => [number, number, number, number];
|
|
15
18
|
export const workerdb_find: (a: number, b: number, c: number, d: number, e: number) => [number, number, number, number];
|
|
16
19
|
export const workerdb_findNearest: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number) => [number, number, number, number];
|
|
17
20
|
export const workerdb_findOne: (a: number, b: number, c: number, d: number, e: number) => [number, number, number, number];
|
|
21
|
+
export const workerdb_importChangeset: (a: number, b: number, c: number) => [number, number, number];
|
|
18
22
|
export const workerdb_insert: (a: number, b: number, c: number, d: number, e: number) => [number, number, number, number];
|
|
19
23
|
export const workerdb_insertMany: (a: number, b: number, c: number, d: number, e: number) => [number, number, number, number];
|
|
24
|
+
export const workerdb_listCollections: (a: number) => [number, number, number, number];
|
|
20
25
|
export const workerdb_listIndexes: (a: number, b: number, c: number) => [number, number, number, number];
|
|
21
26
|
export const workerdb_openInMemory: () => [number, number, number];
|
|
22
27
|
export const workerdb_openWithConfigAndOpfs: (a: any, b: number, c: number) => [number, number, number];
|
|
@@ -43,25 +48,25 @@ export const collectionwasm_insertMany: (a: number, b: any) => [number, number,
|
|
|
43
48
|
export const collectionwasm_updateMany: (a: number, b: any, c: any) => [number, number, number];
|
|
44
49
|
export const collectionwasm_updateOne: (a: number, b: any, c: any) => [number, number, number];
|
|
45
50
|
export const collectionwasm_upgradeVectorIndex: (a: number, b: number, c: number) => [number, number];
|
|
46
|
-
export const taladbwasm_collection: (a: number, b: number, c: number) => number;
|
|
51
|
+
export const taladbwasm_collection: (a: number, b: number, c: number) => [number, number, number];
|
|
47
52
|
export const taladbwasm_exportSnapshot: (a: number) => [number, number, number, number];
|
|
48
53
|
export const taladbwasm_openInMemory: () => [number, number, number];
|
|
49
54
|
export const taladbwasm_openWithSnapshot: (a: number, b: number) => [number, number, number];
|
|
50
55
|
export const init: () => void;
|
|
51
|
-
export const idb_load_snapshot: (a: number, b: number) => any;
|
|
52
|
-
export const idb_save_snapshot: (a: number, b: number, c: number, d: number) => any;
|
|
53
56
|
export const is_opfs_available: () => any;
|
|
54
57
|
export const opfs_delete_snapshot: (a: number, b: number) => any;
|
|
55
58
|
export const opfs_flush_snapshot: (a: number, b: number, c: number, d: number) => any;
|
|
56
59
|
export const opfs_load_snapshot: (a: number, b: number) => any;
|
|
60
|
+
export const idb_load_snapshot: (a: number, b: number) => any;
|
|
61
|
+
export const idb_save_snapshot: (a: number, b: number, c: number, d: number) => any;
|
|
57
62
|
export const opfs_open_backend: (a: number, b: number) => any;
|
|
58
|
-
export const
|
|
59
|
-
export const
|
|
60
|
-
export const
|
|
61
|
-
export const
|
|
62
|
-
export const
|
|
63
|
-
export const
|
|
64
|
-
export const
|
|
63
|
+
export const wasm_bindgen__closure__destroy__hf5d71bc2d02a6064: (a: number, b: number) => void;
|
|
64
|
+
export const wasm_bindgen__closure__destroy__h48e8a8f67ee293c6: (a: number, b: number) => void;
|
|
65
|
+
export const wasm_bindgen__closure__destroy__h8b8dc50e88fa67bc: (a: number, b: number) => void;
|
|
66
|
+
export const wasm_bindgen__convert__closures_____invoke__hfb64ebab929241fc: (a: number, b: number, c: any) => [number, number];
|
|
67
|
+
export const wasm_bindgen__convert__closures_____invoke__h31b79bb0cf86afae: (a: number, b: number, c: any, d: any) => void;
|
|
68
|
+
export const wasm_bindgen__convert__closures_____invoke__h57a4f572d4d576bf: (a: number, b: number, c: any) => void;
|
|
69
|
+
export const wasm_bindgen__convert__closures_____invoke__had96e062ebd712a6: (a: number, b: number) => void;
|
|
65
70
|
export const __wbindgen_malloc: (a: number, b: number) => number;
|
|
66
71
|
export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
|
|
67
72
|
export const __wbindgen_exn_store: (a: number) => void;
|
package/worker/taladb.worker.js
CHANGED
|
@@ -48,6 +48,11 @@
|
|
|
48
48
|
* dropVectorIndex { collection, field }
|
|
49
49
|
* upgradeVectorIndex { collection, field }
|
|
50
50
|
* findNearest { collection, field, queryJson, topK, filterJson? }
|
|
51
|
+
* listCollections {} → JSON string[]
|
|
52
|
+
* compact {} → null
|
|
53
|
+
* compactTombstones { collection, beforeMs } → number pruned
|
|
54
|
+
* exportChangeset { collectionsJson, sinceMs? } → JSON changeset string
|
|
55
|
+
* importChangeset { changesetJson } → number of applied changes
|
|
51
56
|
* close {}
|
|
52
57
|
*
|
|
53
58
|
* Multi-tab live queries (BroadcastChannel)
|
|
@@ -57,6 +62,16 @@
|
|
|
57
62
|
* `"taladb:<dbName>"`. Other tabs listening on the same channel re-trigger
|
|
58
63
|
* their active `subscribe()` pollers immediately, bypassing the 300 ms tick.
|
|
59
64
|
*
|
|
65
|
+
* Secondary-tab write propagation
|
|
66
|
+
* --------------------------------
|
|
67
|
+
* Fallback (non-OPFS) tabs can also make writes that need to reach the primary
|
|
68
|
+
* tab's OPFS-backed database. After every mutating op a fallback tab exports a
|
|
69
|
+
* mini-changeset (all collections, since the previous sync point) and posts it
|
|
70
|
+
* as `{ type: 'taladb:secondary-write', changeset }` on the BroadcastChannel.
|
|
71
|
+
* The primary (OPFS) tab receives it, calls importChangeset(), and runs the
|
|
72
|
+
* normal onWriteCommitted() path — so all other tabs are notified and the OPFS
|
|
73
|
+
* file stays authoritative. LWW merge handles any concurrent edits.
|
|
74
|
+
*
|
|
60
75
|
* IndexedDB fallback (no OPFS)
|
|
61
76
|
* ----------------------------
|
|
62
77
|
* When OPFS is unavailable (cross-origin iframes, Firefox without storage
|
|
@@ -130,6 +145,14 @@ let broadcastChannel = null;
|
|
|
130
145
|
*/
|
|
131
146
|
let idbFallback = false;
|
|
132
147
|
|
|
148
|
+
/**
|
|
149
|
+
* Timestamp (ms) of the last changeset we exported and broadcast to the
|
|
150
|
+
* primary tab. Used as `sinceMs` for the next export so we only send the
|
|
151
|
+
* delta, not the entire database on every write.
|
|
152
|
+
* @type {number}
|
|
153
|
+
*/
|
|
154
|
+
let lastSecondaryPushMs = 0;
|
|
155
|
+
|
|
133
156
|
// ---------------------------------------------------------------------------
|
|
134
157
|
// IndexedDB helpers (used only when OPFS is unavailable)
|
|
135
158
|
// ---------------------------------------------------------------------------
|
|
@@ -193,20 +216,97 @@ async function idbSaveSnapshot(dbName, bytes) {
|
|
|
193
216
|
} catch { /* best-effort persistence — ignore failures */ }
|
|
194
217
|
}
|
|
195
218
|
|
|
219
|
+
// ---------------------------------------------------------------------------
|
|
220
|
+
// Debounced IDB snapshot
|
|
221
|
+
// ---------------------------------------------------------------------------
|
|
222
|
+
|
|
196
223
|
/**
|
|
197
|
-
*
|
|
198
|
-
*
|
|
224
|
+
* Debounce + max-interval parameters for IDB snapshot persistence.
|
|
225
|
+
*
|
|
226
|
+
* On every write we notify sibling tabs immediately via BroadcastChannel,
|
|
227
|
+
* but we defer the actual IDB persistence so that bulk inserts (insertMany,
|
|
228
|
+
* rapid sequential inserts) only produce a single IDB write rather than one
|
|
229
|
+
* per document. A max-interval cap ensures data is never more than 5 s stale
|
|
230
|
+
* in IDB even under continuous write load.
|
|
199
231
|
*/
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
232
|
+
const SNAPSHOT_DEBOUNCE_MS = 500;
|
|
233
|
+
const SNAPSHOT_MAX_INTERVAL_MS = 5000;
|
|
234
|
+
|
|
235
|
+
/** setTimeout handle for the pending debounced flush. */
|
|
236
|
+
let snapshotTimer = null;
|
|
237
|
+
|
|
238
|
+
/** Timestamp of the last completed IDB flush (ms since epoch). */
|
|
239
|
+
let lastSnapshotMs = 0;
|
|
240
|
+
|
|
241
|
+
/** Perform the IDB snapshot write and reset state. */
|
|
242
|
+
async function flushSnapshot() {
|
|
243
|
+
clearTimeout(snapshotTimer);
|
|
244
|
+
snapshotTimer = null;
|
|
245
|
+
lastSnapshotMs = Date.now();
|
|
204
246
|
if (db && activeDbName) {
|
|
205
247
|
try {
|
|
206
248
|
const bytes = db.exportSnapshot();
|
|
207
|
-
idbSaveSnapshot(activeDbName, bytes)
|
|
208
|
-
} catch { /*
|
|
249
|
+
await idbSaveSnapshot(activeDbName, bytes);
|
|
250
|
+
} catch { /* best-effort — ignore failures */ }
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Schedule (or immediately trigger) an IDB snapshot write.
|
|
256
|
+
*
|
|
257
|
+
* - If the last flush was more than SNAPSHOT_MAX_INTERVAL_MS ago, flush now.
|
|
258
|
+
* - Otherwise debounce: reset the timer to fire SNAPSHOT_DEBOUNCE_MS from now.
|
|
259
|
+
*/
|
|
260
|
+
function scheduleSnapshot() {
|
|
261
|
+
const now = Date.now();
|
|
262
|
+
if (now - lastSnapshotMs > SNAPSHOT_MAX_INTERVAL_MS) {
|
|
263
|
+
// Overdue — flush synchronously in the microtask queue.
|
|
264
|
+
flushSnapshot().catch(() => {});
|
|
265
|
+
return;
|
|
209
266
|
}
|
|
267
|
+
clearTimeout(snapshotTimer);
|
|
268
|
+
snapshotTimer = setTimeout(() => { flushSnapshot().catch(() => {}); }, SNAPSHOT_DEBOUNCE_MS);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* When running as a fallback (non-OPFS) tab, export the changes made since
|
|
273
|
+
* the last push and broadcast them to the primary tab for merging into OPFS.
|
|
274
|
+
*
|
|
275
|
+
* Uses exportChangeset with all known collections to keep the delta small.
|
|
276
|
+
* The primary tab's BroadcastChannel handler calls importChangeset() and runs
|
|
277
|
+
* its own onWriteCommitted(), which notifies all tabs (including this one) via
|
|
278
|
+
* taladb:changed so reads stay consistent.
|
|
279
|
+
*/
|
|
280
|
+
function pushChangesetToPrimary() {
|
|
281
|
+
if (!idbFallback || !db || !broadcastChannel || !activeDbName) return;
|
|
282
|
+
try {
|
|
283
|
+
// Collect all collection names from the current in-memory state.
|
|
284
|
+
// exportChangeset accepts a JSON array of collection names.
|
|
285
|
+
const collections = db.listCollections();
|
|
286
|
+
const changeset = db.exportChangeset(collections, lastSecondaryPushMs);
|
|
287
|
+
// Only broadcast if there is actually something to send.
|
|
288
|
+
const parsed = JSON.parse(changeset);
|
|
289
|
+
if (parsed.length === 0) return;
|
|
290
|
+
lastSecondaryPushMs = Date.now();
|
|
291
|
+
broadcastChannel.postMessage({ type: 'taladb:secondary-write', changeset });
|
|
292
|
+
log(`Broadcast ${parsed.length} change(s) to primary tab`);
|
|
293
|
+
} catch (err) {
|
|
294
|
+
warn('Failed to export changeset for primary tab:', err);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Notify sibling tabs of a write and schedule IDB persistence.
|
|
300
|
+
* Must be called after every mutating op.
|
|
301
|
+
*/
|
|
302
|
+
function onWriteCommitted() {
|
|
303
|
+
broadcastChannel?.postMessage('taladb:changed');
|
|
304
|
+
// Fallback tab: push local changes to the primary (OPFS) tab so they are
|
|
305
|
+
// merged into the authoritative database file.
|
|
306
|
+
pushChangesetToPrimary();
|
|
307
|
+
// Debounced IDB flush — keeps other tabs' fallback instances in sync via
|
|
308
|
+
// BroadcastChannel + snapshotDirty reload without writing to IDB on every op.
|
|
309
|
+
scheduleSnapshot();
|
|
210
310
|
}
|
|
211
311
|
|
|
212
312
|
// ---------------------------------------------------------------------------
|
|
@@ -357,7 +457,36 @@ async function dispatch(op, args) {
|
|
|
357
457
|
args.filterJson ?? 'null',
|
|
358
458
|
);
|
|
359
459
|
|
|
460
|
+
case 'listCollections':
|
|
461
|
+
return db.listCollections();
|
|
462
|
+
|
|
463
|
+
case 'compact':
|
|
464
|
+
// Compact the storage file, reclaiming freed space. No-op on IDB fallback.
|
|
465
|
+
db.compact();
|
|
466
|
+
return null;
|
|
467
|
+
|
|
468
|
+
case 'compactTombstones':
|
|
469
|
+
// Prune tombstones older than beforeMs from a collection.
|
|
470
|
+
// Returns the count of tombstones removed.
|
|
471
|
+
return db.compactTombstones(args.collection, args.beforeMs ?? 0);
|
|
472
|
+
|
|
473
|
+
case 'exportChangeset':
|
|
474
|
+
// Export a LWW changeset for the given collections since sinceMs.
|
|
475
|
+
// Returns a JSON string the caller can POST to a sync server.
|
|
476
|
+
return db.exportChangeset(args.collectionsJson, args.sinceMs ?? 0);
|
|
477
|
+
|
|
478
|
+
case 'importChangeset': {
|
|
479
|
+
// Apply a remote changeset (JSON string from sync server) using LWW.
|
|
480
|
+
// Triggers onWriteCommitted so multi-tab peers get notified.
|
|
481
|
+
const applied = db.importChangeset(args.changesetJson);
|
|
482
|
+
if (applied > 0) onWriteCommitted();
|
|
483
|
+
return applied;
|
|
484
|
+
}
|
|
485
|
+
|
|
360
486
|
case 'close':
|
|
487
|
+
// Flush any pending debounced snapshot before releasing the lock so
|
|
488
|
+
// no writes are lost when the tab closes or navigates away.
|
|
489
|
+
await flushSnapshot();
|
|
361
490
|
// Release the Web Lock and close the sync handle gracefully.
|
|
362
491
|
if (releaseLock) { releaseLock(); releaseLock = null; }
|
|
363
492
|
broadcastChannel?.close();
|
|
@@ -376,7 +505,7 @@ async function dispatch(op, args) {
|
|
|
376
505
|
// ---------------------------------------------------------------------------
|
|
377
506
|
|
|
378
507
|
async function doInit(dbName, configJson) {
|
|
379
|
-
const wasm = await import('../pkg/taladb_web.js');
|
|
508
|
+
const wasm = await import(/* @vite-ignore */ '../pkg/taladb_web.js');
|
|
380
509
|
await wasm.default();
|
|
381
510
|
|
|
382
511
|
// Hoist to module scope so snapshot reloads in dispatch() can use it.
|
|
@@ -402,6 +531,17 @@ async function doInit(dbName, configJson) {
|
|
|
402
531
|
const resolve = pendingSnapshotResolve;
|
|
403
532
|
pendingSnapshotResolve = null;
|
|
404
533
|
resolve();
|
|
534
|
+
} else if (e.data?.type === 'taladb:secondary-write' && !idbFallback && db) {
|
|
535
|
+
// Primary (OPFS) tab: a secondary tab made a write — merge it in via LWW.
|
|
536
|
+
try {
|
|
537
|
+
const applied = db.importChangeset(e.data.changeset);
|
|
538
|
+
if (applied > 0) {
|
|
539
|
+
log(`Merged ${applied} change(s) from secondary tab`);
|
|
540
|
+
onWriteCommitted();
|
|
541
|
+
}
|
|
542
|
+
} catch (err) {
|
|
543
|
+
warn('Failed to import secondary-tab changeset:', err);
|
|
544
|
+
}
|
|
405
545
|
}
|
|
406
546
|
};
|
|
407
547
|
log('BroadcastChannel opened:', `taladb:${dbName}`);
|