@taladb/web 0.6.0 → 0.6.1
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 +1 -1
- package/pkg/taladb_web.d.ts +50 -2
- package/pkg/taladb_web.js +111 -6
- package/pkg/taladb_web_bg.wasm +0 -0
- package/pkg/taladb_web_bg.wasm.d.ts +6 -2
- package/worker/taladb.worker.js +142 -8
package/package.json
CHANGED
package/pkg/package.json
CHANGED
package/pkg/taladb_web.d.ts
CHANGED
|
@@ -119,6 +119,20 @@ export class WorkerDB {
|
|
|
119
119
|
private constructor();
|
|
120
120
|
free(): void;
|
|
121
121
|
[Symbol.dispose](): void;
|
|
122
|
+
/**
|
|
123
|
+
* Remove tombstones older than `before_ms` from the given collection.
|
|
124
|
+
*
|
|
125
|
+
* Call periodically (e.g. on app startup) after your sync retention window
|
|
126
|
+
* has elapsed so deleted document IDs no longer accumulate indefinitely.
|
|
127
|
+
* Returns the number of tombstones removed.
|
|
128
|
+
*
|
|
129
|
+
* ```js
|
|
130
|
+
* // Prune tombstones older than 30 days
|
|
131
|
+
* const cutoff = Date.now() - 30 * 24 * 60 * 60 * 1000;
|
|
132
|
+
* const pruned = db.compactTombstones('users', cutoff);
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
compactTombstones(collection: string, before_ms: number): number;
|
|
122
136
|
/**
|
|
123
137
|
* Count matching documents.
|
|
124
138
|
*/
|
|
@@ -148,6 +162,18 @@ export class WorkerDB {
|
|
|
148
162
|
* Drop a vector index (and its HNSW graph if present).
|
|
149
163
|
*/
|
|
150
164
|
dropVectorIndex(collection: string, field: string): void;
|
|
165
|
+
/**
|
|
166
|
+
* Export a changeset for the given collections since `since_ms`.
|
|
167
|
+
*
|
|
168
|
+
* Returns a JSON string representing `Vec<Change>` that can be sent
|
|
169
|
+
* to a remote peer via fetch, WebSocket, or SSE.
|
|
170
|
+
*
|
|
171
|
+
* ```js
|
|
172
|
+
* const json = db.exportChangeset(JSON.stringify(['users', 'posts']), 0);
|
|
173
|
+
* await fetch('/sync', { method: 'POST', body: json });
|
|
174
|
+
* ```
|
|
175
|
+
*/
|
|
176
|
+
exportChangeset(collections_json: string, since_ms: number): string;
|
|
151
177
|
/**
|
|
152
178
|
* Serialize the entire in-memory database to bytes for persistence.
|
|
153
179
|
*
|
|
@@ -167,6 +193,19 @@ export class WorkerDB {
|
|
|
167
193
|
* Find one document. Returns a JSON object or `"null"`.
|
|
168
194
|
*/
|
|
169
195
|
findOne(collection: string, filter_json: string): string;
|
|
196
|
+
/**
|
|
197
|
+
* Import a remote changeset and merge it into the local database using
|
|
198
|
+
* Last-Write-Wins conflict resolution.
|
|
199
|
+
*
|
|
200
|
+
* Returns the number of documents actually changed.
|
|
201
|
+
*
|
|
202
|
+
* ```js
|
|
203
|
+
* const resp = await fetch('/sync?since=' + lastSync);
|
|
204
|
+
* const applied = db.importChangeset(await resp.text());
|
|
205
|
+
* if (applied > 0) { /* refresh UI */ }
|
|
206
|
+
* ```
|
|
207
|
+
*/
|
|
208
|
+
importChangeset(changeset_json: string): number;
|
|
170
209
|
/**
|
|
171
210
|
* Insert a document. Returns the new ULID as a string.
|
|
172
211
|
*/
|
|
@@ -175,6 +214,11 @@ export class WorkerDB {
|
|
|
175
214
|
* Insert many documents. Returns a JSON array of ULID strings.
|
|
176
215
|
*/
|
|
177
216
|
insertMany(collection: string, docs_json: string): string;
|
|
217
|
+
/**
|
|
218
|
+
* Returns a JSON array of all collection names in the database.
|
|
219
|
+
* Used by the Worker to build the collections list for exportChangeset.
|
|
220
|
+
*/
|
|
221
|
+
listCollections(): string;
|
|
178
222
|
/**
|
|
179
223
|
* Returns a JSON string `{ btree: string[], fts: string[], vector: string[] }`
|
|
180
224
|
* listing all indexes on the given collection.
|
|
@@ -298,6 +342,7 @@ export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembl
|
|
|
298
342
|
export interface InitOutput {
|
|
299
343
|
readonly memory: WebAssembly.Memory;
|
|
300
344
|
readonly __wbg_workerdb_free: (a: number, b: number) => void;
|
|
345
|
+
readonly workerdb_compactTombstones: (a: number, b: number, c: number, d: number) => [number, number, number];
|
|
301
346
|
readonly workerdb_count: (a: number, b: number, c: number, d: number, e: number) => [number, number, number];
|
|
302
347
|
readonly workerdb_createFtsIndex: (a: number, b: number, c: number, d: number, e: number) => [number, number];
|
|
303
348
|
readonly workerdb_createIndex: (a: number, b: number, c: number, d: number, e: number) => [number, number];
|
|
@@ -307,12 +352,15 @@ export interface InitOutput {
|
|
|
307
352
|
readonly workerdb_dropFtsIndex: (a: number, b: number, c: number, d: number, e: number) => [number, number];
|
|
308
353
|
readonly workerdb_dropIndex: (a: number, b: number, c: number, d: number, e: number) => [number, number];
|
|
309
354
|
readonly workerdb_dropVectorIndex: (a: number, b: number, c: number, d: number, e: number) => [number, number];
|
|
355
|
+
readonly workerdb_exportChangeset: (a: number, b: number, c: number, d: number) => [number, number, number, number];
|
|
310
356
|
readonly workerdb_exportSnapshot: (a: number) => [number, number, number, number];
|
|
311
357
|
readonly workerdb_find: (a: number, b: number, c: number, d: number, e: number) => [number, number, number, number];
|
|
312
358
|
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
359
|
readonly workerdb_findOne: (a: number, b: number, c: number, d: number, e: number) => [number, number, number, number];
|
|
360
|
+
readonly workerdb_importChangeset: (a: number, b: number, c: number) => [number, number, number];
|
|
314
361
|
readonly workerdb_insert: (a: number, b: number, c: number, d: number, e: number) => [number, number, number, number];
|
|
315
362
|
readonly workerdb_insertMany: (a: number, b: number, c: number, d: number, e: number) => [number, number, number, number];
|
|
363
|
+
readonly workerdb_listCollections: (a: number) => [number, number, number, number];
|
|
316
364
|
readonly workerdb_listIndexes: (a: number, b: number, c: number) => [number, number, number, number];
|
|
317
365
|
readonly workerdb_openInMemory: () => [number, number, number];
|
|
318
366
|
readonly workerdb_openWithConfigAndOpfs: (a: any, b: number, c: number) => [number, number, number];
|
|
@@ -351,12 +399,12 @@ export interface InitOutput {
|
|
|
351
399
|
readonly opfs_flush_snapshot: (a: number, b: number, c: number, d: number) => any;
|
|
352
400
|
readonly opfs_load_snapshot: (a: number, b: number) => any;
|
|
353
401
|
readonly opfs_open_backend: (a: number, b: number) => any;
|
|
354
|
-
readonly
|
|
402
|
+
readonly wasm_bindgen__closure__destroy__h72641f1e1c5255af: (a: number, b: number) => void;
|
|
355
403
|
readonly wasm_bindgen__closure__destroy__h478053cb26acc43f: (a: number, b: number) => void;
|
|
356
404
|
readonly wasm_bindgen__closure__destroy__h1febc128ce23ccd1: (a: number, b: number) => void;
|
|
357
405
|
readonly wasm_bindgen__convert__closures_____invoke__h3022e61d849d0b12: (a: number, b: number, c: any) => [number, number];
|
|
358
406
|
readonly wasm_bindgen__convert__closures_____invoke__h470c93d8a24e0015: (a: number, b: number, c: any, d: any) => void;
|
|
359
|
-
readonly
|
|
407
|
+
readonly wasm_bindgen__convert__closures_____invoke__h296c8d2fd599fbcf: (a: number, b: number, c: any) => void;
|
|
360
408
|
readonly wasm_bindgen__convert__closures_____invoke__h325a87e086f6f78c: (a: number, b: number) => void;
|
|
361
409
|
readonly __wbindgen_malloc: (a: number, b: number) => number;
|
|
362
410
|
readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
|
package/pkg/taladb_web.js
CHANGED
|
@@ -344,6 +344,31 @@ export class WorkerDB {
|
|
|
344
344
|
const ptr = this.__destroy_into_raw();
|
|
345
345
|
wasm.__wbg_workerdb_free(ptr, 0);
|
|
346
346
|
}
|
|
347
|
+
/**
|
|
348
|
+
* Remove tombstones older than `before_ms` from the given collection.
|
|
349
|
+
*
|
|
350
|
+
* Call periodically (e.g. on app startup) after your sync retention window
|
|
351
|
+
* has elapsed so deleted document IDs no longer accumulate indefinitely.
|
|
352
|
+
* Returns the number of tombstones removed.
|
|
353
|
+
*
|
|
354
|
+
* ```js
|
|
355
|
+
* // Prune tombstones older than 30 days
|
|
356
|
+
* const cutoff = Date.now() - 30 * 24 * 60 * 60 * 1000;
|
|
357
|
+
* const pruned = db.compactTombstones('users', cutoff);
|
|
358
|
+
* ```
|
|
359
|
+
* @param {string} collection
|
|
360
|
+
* @param {number} before_ms
|
|
361
|
+
* @returns {number}
|
|
362
|
+
*/
|
|
363
|
+
compactTombstones(collection, before_ms) {
|
|
364
|
+
const ptr0 = passStringToWasm0(collection, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
365
|
+
const len0 = WASM_VECTOR_LEN;
|
|
366
|
+
const ret = wasm.workerdb_compactTombstones(this.__wbg_ptr, ptr0, len0, before_ms);
|
|
367
|
+
if (ret[2]) {
|
|
368
|
+
throw takeFromExternrefTable0(ret[1]);
|
|
369
|
+
}
|
|
370
|
+
return ret[0] >>> 0;
|
|
371
|
+
}
|
|
347
372
|
/**
|
|
348
373
|
* Count matching documents.
|
|
349
374
|
* @param {string} collection
|
|
@@ -495,6 +520,40 @@ export class WorkerDB {
|
|
|
495
520
|
throw takeFromExternrefTable0(ret[0]);
|
|
496
521
|
}
|
|
497
522
|
}
|
|
523
|
+
/**
|
|
524
|
+
* Export a changeset for the given collections since `since_ms`.
|
|
525
|
+
*
|
|
526
|
+
* Returns a JSON string representing `Vec<Change>` that can be sent
|
|
527
|
+
* to a remote peer via fetch, WebSocket, or SSE.
|
|
528
|
+
*
|
|
529
|
+
* ```js
|
|
530
|
+
* const json = db.exportChangeset(JSON.stringify(['users', 'posts']), 0);
|
|
531
|
+
* await fetch('/sync', { method: 'POST', body: json });
|
|
532
|
+
* ```
|
|
533
|
+
* @param {string} collections_json
|
|
534
|
+
* @param {number} since_ms
|
|
535
|
+
* @returns {string}
|
|
536
|
+
*/
|
|
537
|
+
exportChangeset(collections_json, since_ms) {
|
|
538
|
+
let deferred3_0;
|
|
539
|
+
let deferred3_1;
|
|
540
|
+
try {
|
|
541
|
+
const ptr0 = passStringToWasm0(collections_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
542
|
+
const len0 = WASM_VECTOR_LEN;
|
|
543
|
+
const ret = wasm.workerdb_exportChangeset(this.__wbg_ptr, ptr0, len0, since_ms);
|
|
544
|
+
var ptr2 = ret[0];
|
|
545
|
+
var len2 = ret[1];
|
|
546
|
+
if (ret[3]) {
|
|
547
|
+
ptr2 = 0; len2 = 0;
|
|
548
|
+
throw takeFromExternrefTable0(ret[2]);
|
|
549
|
+
}
|
|
550
|
+
deferred3_0 = ptr2;
|
|
551
|
+
deferred3_1 = len2;
|
|
552
|
+
return getStringFromWasm0(ptr2, len2);
|
|
553
|
+
} finally {
|
|
554
|
+
wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
498
557
|
/**
|
|
499
558
|
* Serialize the entire in-memory database to bytes for persistence.
|
|
500
559
|
*
|
|
@@ -602,6 +661,29 @@ export class WorkerDB {
|
|
|
602
661
|
wasm.__wbindgen_free(deferred4_0, deferred4_1, 1);
|
|
603
662
|
}
|
|
604
663
|
}
|
|
664
|
+
/**
|
|
665
|
+
* Import a remote changeset and merge it into the local database using
|
|
666
|
+
* Last-Write-Wins conflict resolution.
|
|
667
|
+
*
|
|
668
|
+
* Returns the number of documents actually changed.
|
|
669
|
+
*
|
|
670
|
+
* ```js
|
|
671
|
+
* const resp = await fetch('/sync?since=' + lastSync);
|
|
672
|
+
* const applied = db.importChangeset(await resp.text());
|
|
673
|
+
* if (applied > 0) { /* refresh UI */ }
|
|
674
|
+
* ```
|
|
675
|
+
* @param {string} changeset_json
|
|
676
|
+
* @returns {number}
|
|
677
|
+
*/
|
|
678
|
+
importChangeset(changeset_json) {
|
|
679
|
+
const ptr0 = passStringToWasm0(changeset_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
680
|
+
const len0 = WASM_VECTOR_LEN;
|
|
681
|
+
const ret = wasm.workerdb_importChangeset(this.__wbg_ptr, ptr0, len0);
|
|
682
|
+
if (ret[2]) {
|
|
683
|
+
throw takeFromExternrefTable0(ret[1]);
|
|
684
|
+
}
|
|
685
|
+
return ret[0] >>> 0;
|
|
686
|
+
}
|
|
605
687
|
/**
|
|
606
688
|
* Insert a document. Returns the new ULID as a string.
|
|
607
689
|
* @param {string} collection
|
|
@@ -658,6 +740,29 @@ export class WorkerDB {
|
|
|
658
740
|
wasm.__wbindgen_free(deferred4_0, deferred4_1, 1);
|
|
659
741
|
}
|
|
660
742
|
}
|
|
743
|
+
/**
|
|
744
|
+
* Returns a JSON array of all collection names in the database.
|
|
745
|
+
* Used by the Worker to build the collections list for exportChangeset.
|
|
746
|
+
* @returns {string}
|
|
747
|
+
*/
|
|
748
|
+
listCollections() {
|
|
749
|
+
let deferred2_0;
|
|
750
|
+
let deferred2_1;
|
|
751
|
+
try {
|
|
752
|
+
const ret = wasm.workerdb_listCollections(this.__wbg_ptr);
|
|
753
|
+
var ptr1 = ret[0];
|
|
754
|
+
var len1 = ret[1];
|
|
755
|
+
if (ret[3]) {
|
|
756
|
+
ptr1 = 0; len1 = 0;
|
|
757
|
+
throw takeFromExternrefTable0(ret[2]);
|
|
758
|
+
}
|
|
759
|
+
deferred2_0 = ptr1;
|
|
760
|
+
deferred2_1 = len1;
|
|
761
|
+
return getStringFromWasm0(ptr1, len1);
|
|
762
|
+
} finally {
|
|
763
|
+
wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
661
766
|
/**
|
|
662
767
|
* Returns a JSON string `{ btree: string[], fts: string[], vector: string[] }`
|
|
663
768
|
* listing all indexes on the given collection.
|
|
@@ -1507,17 +1612,17 @@ function __wbg_get_imports() {
|
|
|
1507
1612
|
return ret;
|
|
1508
1613
|
},
|
|
1509
1614
|
__wbindgen_cast_0000000000000001: function(arg0, arg1) {
|
|
1510
|
-
// Cast intrinsic for `Closure(Closure { dtor_idx:
|
|
1511
|
-
const ret = makeMutClosure(arg0, arg1, wasm.
|
|
1615
|
+
// Cast intrinsic for `Closure(Closure { dtor_idx: 102, function: Function { arguments: [Externref], shim_idx: 103, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
|
1616
|
+
const ret = makeMutClosure(arg0, arg1, wasm.wasm_bindgen__closure__destroy__h72641f1e1c5255af, wasm_bindgen__convert__closures_____invoke__h296c8d2fd599fbcf);
|
|
1512
1617
|
return ret;
|
|
1513
1618
|
},
|
|
1514
1619
|
__wbindgen_cast_0000000000000002: function(arg0, arg1) {
|
|
1515
|
-
// Cast intrinsic for `Closure(Closure { dtor_idx:
|
|
1620
|
+
// Cast intrinsic for `Closure(Closure { dtor_idx: 181, function: Function { arguments: [], shim_idx: 182, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
|
1516
1621
|
const ret = makeMutClosure(arg0, arg1, wasm.wasm_bindgen__closure__destroy__h478053cb26acc43f, wasm_bindgen__convert__closures_____invoke__h325a87e086f6f78c);
|
|
1517
1622
|
return ret;
|
|
1518
1623
|
},
|
|
1519
1624
|
__wbindgen_cast_0000000000000003: function(arg0, arg1) {
|
|
1520
|
-
// Cast intrinsic for `Closure(Closure { dtor_idx:
|
|
1625
|
+
// Cast intrinsic for `Closure(Closure { dtor_idx: 589, function: Function { arguments: [Externref], shim_idx: 590, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
|
|
1521
1626
|
const ret = makeMutClosure(arg0, arg1, wasm.wasm_bindgen__closure__destroy__h1febc128ce23ccd1, wasm_bindgen__convert__closures_____invoke__h3022e61d849d0b12);
|
|
1522
1627
|
return ret;
|
|
1523
1628
|
},
|
|
@@ -1568,8 +1673,8 @@ function wasm_bindgen__convert__closures_____invoke__h325a87e086f6f78c(arg0, arg
|
|
|
1568
1673
|
wasm.wasm_bindgen__convert__closures_____invoke__h325a87e086f6f78c(arg0, arg1);
|
|
1569
1674
|
}
|
|
1570
1675
|
|
|
1571
|
-
function
|
|
1572
|
-
wasm.
|
|
1676
|
+
function wasm_bindgen__convert__closures_____invoke__h296c8d2fd599fbcf(arg0, arg1, arg2) {
|
|
1677
|
+
wasm.wasm_bindgen__convert__closures_____invoke__h296c8d2fd599fbcf(arg0, arg1, arg2);
|
|
1573
1678
|
}
|
|
1574
1679
|
|
|
1575
1680
|
function wasm_bindgen__convert__closures_____invoke__h3022e61d849d0b12(arg0, arg1, arg2) {
|
package/pkg/taladb_web_bg.wasm
CHANGED
|
Binary file
|
|
@@ -2,6 +2,7 @@
|
|
|
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_compactTombstones: (a: number, b: number, c: number, d: number) => [number, number, number];
|
|
5
6
|
export const workerdb_count: (a: number, b: number, c: number, d: number, e: number) => [number, number, number];
|
|
6
7
|
export const workerdb_createFtsIndex: (a: number, b: number, c: number, d: number, e: number) => [number, number];
|
|
7
8
|
export const workerdb_createIndex: (a: number, b: number, c: number, d: number, e: number) => [number, number];
|
|
@@ -11,12 +12,15 @@ export const workerdb_deleteOne: (a: number, b: number, c: number, d: number, e:
|
|
|
11
12
|
export const workerdb_dropFtsIndex: (a: number, b: number, c: number, d: number, e: number) => [number, number];
|
|
12
13
|
export const workerdb_dropIndex: (a: number, b: number, c: number, d: number, e: number) => [number, number];
|
|
13
14
|
export const workerdb_dropVectorIndex: (a: number, b: number, c: number, d: number, e: number) => [number, number];
|
|
15
|
+
export const workerdb_exportChangeset: (a: number, b: number, c: number, d: number) => [number, number, number, number];
|
|
14
16
|
export const workerdb_exportSnapshot: (a: number) => [number, number, number, number];
|
|
15
17
|
export const workerdb_find: (a: number, b: number, c: number, d: number, e: number) => [number, number, number, number];
|
|
16
18
|
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
19
|
export const workerdb_findOne: (a: number, b: number, c: number, d: number, e: number) => [number, number, number, number];
|
|
20
|
+
export const workerdb_importChangeset: (a: number, b: number, c: number) => [number, number, number];
|
|
18
21
|
export const workerdb_insert: (a: number, b: number, c: number, d: number, e: number) => [number, number, number, number];
|
|
19
22
|
export const workerdb_insertMany: (a: number, b: number, c: number, d: number, e: number) => [number, number, number, number];
|
|
23
|
+
export const workerdb_listCollections: (a: number) => [number, number, number, number];
|
|
20
24
|
export const workerdb_listIndexes: (a: number, b: number, c: number) => [number, number, number, number];
|
|
21
25
|
export const workerdb_openInMemory: () => [number, number, number];
|
|
22
26
|
export const workerdb_openWithConfigAndOpfs: (a: any, b: number, c: number) => [number, number, number];
|
|
@@ -55,12 +59,12 @@ export const opfs_delete_snapshot: (a: number, b: number) => any;
|
|
|
55
59
|
export const opfs_flush_snapshot: (a: number, b: number, c: number, d: number) => any;
|
|
56
60
|
export const opfs_load_snapshot: (a: number, b: number) => any;
|
|
57
61
|
export const opfs_open_backend: (a: number, b: number) => any;
|
|
58
|
-
export const
|
|
62
|
+
export const wasm_bindgen__closure__destroy__h72641f1e1c5255af: (a: number, b: number) => void;
|
|
59
63
|
export const wasm_bindgen__closure__destroy__h478053cb26acc43f: (a: number, b: number) => void;
|
|
60
64
|
export const wasm_bindgen__closure__destroy__h1febc128ce23ccd1: (a: number, b: number) => void;
|
|
61
65
|
export const wasm_bindgen__convert__closures_____invoke__h3022e61d849d0b12: (a: number, b: number, c: any) => [number, number];
|
|
62
66
|
export const wasm_bindgen__convert__closures_____invoke__h470c93d8a24e0015: (a: number, b: number, c: any, d: any) => void;
|
|
63
|
-
export const
|
|
67
|
+
export const wasm_bindgen__convert__closures_____invoke__h296c8d2fd599fbcf: (a: number, b: number, c: any) => void;
|
|
64
68
|
export const wasm_bindgen__convert__closures_____invoke__h325a87e086f6f78c: (a: number, b: number) => void;
|
|
65
69
|
export const __wbindgen_malloc: (a: number, b: number) => number;
|
|
66
70
|
export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
|
package/worker/taladb.worker.js
CHANGED
|
@@ -48,6 +48,10 @@
|
|
|
48
48
|
* dropVectorIndex { collection, field }
|
|
49
49
|
* upgradeVectorIndex { collection, field }
|
|
50
50
|
* findNearest { collection, field, queryJson, topK, filterJson? }
|
|
51
|
+
* listCollections {} → JSON string[]
|
|
52
|
+
* compactTombstones { collection, beforeMs } → number pruned
|
|
53
|
+
* exportChangeset { collectionsJson, sinceMs? } → JSON changeset string
|
|
54
|
+
* importChangeset { changesetJson } → number of applied changes
|
|
51
55
|
* close {}
|
|
52
56
|
*
|
|
53
57
|
* Multi-tab live queries (BroadcastChannel)
|
|
@@ -57,6 +61,16 @@
|
|
|
57
61
|
* `"taladb:<dbName>"`. Other tabs listening on the same channel re-trigger
|
|
58
62
|
* their active `subscribe()` pollers immediately, bypassing the 300 ms tick.
|
|
59
63
|
*
|
|
64
|
+
* Secondary-tab write propagation
|
|
65
|
+
* --------------------------------
|
|
66
|
+
* Fallback (non-OPFS) tabs can also make writes that need to reach the primary
|
|
67
|
+
* tab's OPFS-backed database. After every mutating op a fallback tab exports a
|
|
68
|
+
* mini-changeset (all collections, since the previous sync point) and posts it
|
|
69
|
+
* as `{ type: 'taladb:secondary-write', changeset }` on the BroadcastChannel.
|
|
70
|
+
* The primary (OPFS) tab receives it, calls importChangeset(), and runs the
|
|
71
|
+
* normal onWriteCommitted() path — so all other tabs are notified and the OPFS
|
|
72
|
+
* file stays authoritative. LWW merge handles any concurrent edits.
|
|
73
|
+
*
|
|
60
74
|
* IndexedDB fallback (no OPFS)
|
|
61
75
|
* ----------------------------
|
|
62
76
|
* When OPFS is unavailable (cross-origin iframes, Firefox without storage
|
|
@@ -130,6 +144,14 @@ let broadcastChannel = null;
|
|
|
130
144
|
*/
|
|
131
145
|
let idbFallback = false;
|
|
132
146
|
|
|
147
|
+
/**
|
|
148
|
+
* Timestamp (ms) of the last changeset we exported and broadcast to the
|
|
149
|
+
* primary tab. Used as `sinceMs` for the next export so we only send the
|
|
150
|
+
* delta, not the entire database on every write.
|
|
151
|
+
* @type {number}
|
|
152
|
+
*/
|
|
153
|
+
let lastSecondaryPushMs = 0;
|
|
154
|
+
|
|
133
155
|
// ---------------------------------------------------------------------------
|
|
134
156
|
// IndexedDB helpers (used only when OPFS is unavailable)
|
|
135
157
|
// ---------------------------------------------------------------------------
|
|
@@ -193,22 +215,99 @@ async function idbSaveSnapshot(dbName, bytes) {
|
|
|
193
215
|
} catch { /* best-effort persistence — ignore failures */ }
|
|
194
216
|
}
|
|
195
217
|
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
// Debounced IDB snapshot
|
|
220
|
+
// ---------------------------------------------------------------------------
|
|
221
|
+
|
|
196
222
|
/**
|
|
197
|
-
*
|
|
198
|
-
*
|
|
223
|
+
* Debounce + max-interval parameters for IDB snapshot persistence.
|
|
224
|
+
*
|
|
225
|
+
* On every write we notify sibling tabs immediately via BroadcastChannel,
|
|
226
|
+
* but we defer the actual IDB persistence so that bulk inserts (insertMany,
|
|
227
|
+
* rapid sequential inserts) only produce a single IDB write rather than one
|
|
228
|
+
* per document. A max-interval cap ensures data is never more than 5 s stale
|
|
229
|
+
* in IDB even under continuous write load.
|
|
199
230
|
*/
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
231
|
+
const SNAPSHOT_DEBOUNCE_MS = 500;
|
|
232
|
+
const SNAPSHOT_MAX_INTERVAL_MS = 5000;
|
|
233
|
+
|
|
234
|
+
/** setTimeout handle for the pending debounced flush. */
|
|
235
|
+
let snapshotTimer = null;
|
|
236
|
+
|
|
237
|
+
/** Timestamp of the last completed IDB flush (ms since epoch). */
|
|
238
|
+
let lastSnapshotMs = 0;
|
|
239
|
+
|
|
240
|
+
/** Perform the IDB snapshot write and reset state. */
|
|
241
|
+
async function flushSnapshot() {
|
|
242
|
+
clearTimeout(snapshotTimer);
|
|
243
|
+
snapshotTimer = null;
|
|
244
|
+
lastSnapshotMs = Date.now();
|
|
204
245
|
if (db && activeDbName) {
|
|
205
246
|
try {
|
|
206
247
|
const bytes = db.exportSnapshot();
|
|
207
|
-
idbSaveSnapshot(activeDbName, bytes)
|
|
208
|
-
} catch { /*
|
|
248
|
+
await idbSaveSnapshot(activeDbName, bytes);
|
|
249
|
+
} catch { /* best-effort — ignore failures */ }
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Schedule (or immediately trigger) an IDB snapshot write.
|
|
255
|
+
*
|
|
256
|
+
* - If the last flush was more than SNAPSHOT_MAX_INTERVAL_MS ago, flush now.
|
|
257
|
+
* - Otherwise debounce: reset the timer to fire SNAPSHOT_DEBOUNCE_MS from now.
|
|
258
|
+
*/
|
|
259
|
+
function scheduleSnapshot() {
|
|
260
|
+
const now = Date.now();
|
|
261
|
+
if (now - lastSnapshotMs > SNAPSHOT_MAX_INTERVAL_MS) {
|
|
262
|
+
// Overdue — flush synchronously in the microtask queue.
|
|
263
|
+
flushSnapshot().catch(() => {});
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
clearTimeout(snapshotTimer);
|
|
267
|
+
snapshotTimer = setTimeout(() => { flushSnapshot().catch(() => {}); }, SNAPSHOT_DEBOUNCE_MS);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* When running as a fallback (non-OPFS) tab, export the changes made since
|
|
272
|
+
* the last push and broadcast them to the primary tab for merging into OPFS.
|
|
273
|
+
*
|
|
274
|
+
* Uses exportChangeset with all known collections to keep the delta small.
|
|
275
|
+
* The primary tab's BroadcastChannel handler calls importChangeset() and runs
|
|
276
|
+
* its own onWriteCommitted(), which notifies all tabs (including this one) via
|
|
277
|
+
* taladb:changed so reads stay consistent.
|
|
278
|
+
*/
|
|
279
|
+
function pushChangesetToPrimary() {
|
|
280
|
+
if (!idbFallback || !db || !broadcastChannel || !activeDbName) return;
|
|
281
|
+
try {
|
|
282
|
+
// Collect all collection names from the current in-memory state.
|
|
283
|
+
// exportChangeset accepts a JSON array of collection names.
|
|
284
|
+
const collections = db.listCollections();
|
|
285
|
+
const changeset = db.exportChangeset(collections, lastSecondaryPushMs);
|
|
286
|
+
// Only broadcast if there is actually something to send.
|
|
287
|
+
const parsed = JSON.parse(changeset);
|
|
288
|
+
if (parsed.length === 0) return;
|
|
289
|
+
lastSecondaryPushMs = Date.now();
|
|
290
|
+
broadcastChannel.postMessage({ type: 'taladb:secondary-write', changeset });
|
|
291
|
+
log(`Broadcast ${parsed.length} change(s) to primary tab`);
|
|
292
|
+
} catch (err) {
|
|
293
|
+
warn('Failed to export changeset for primary tab:', err);
|
|
209
294
|
}
|
|
210
295
|
}
|
|
211
296
|
|
|
297
|
+
/**
|
|
298
|
+
* Notify sibling tabs of a write and schedule IDB persistence.
|
|
299
|
+
* Must be called after every mutating op.
|
|
300
|
+
*/
|
|
301
|
+
function onWriteCommitted() {
|
|
302
|
+
broadcastChannel?.postMessage('taladb:changed');
|
|
303
|
+
// Fallback tab: push local changes to the primary (OPFS) tab so they are
|
|
304
|
+
// merged into the authoritative database file.
|
|
305
|
+
pushChangesetToPrimary();
|
|
306
|
+
// Debounced IDB flush — keeps other tabs' fallback instances in sync via
|
|
307
|
+
// BroadcastChannel + snapshotDirty reload without writing to IDB on every op.
|
|
308
|
+
scheduleSnapshot();
|
|
309
|
+
}
|
|
310
|
+
|
|
212
311
|
// ---------------------------------------------------------------------------
|
|
213
312
|
// Dedicated Worker message handler
|
|
214
313
|
// ---------------------------------------------------------------------------
|
|
@@ -357,7 +456,31 @@ async function dispatch(op, args) {
|
|
|
357
456
|
args.filterJson ?? 'null',
|
|
358
457
|
);
|
|
359
458
|
|
|
459
|
+
case 'listCollections':
|
|
460
|
+
return db.listCollections();
|
|
461
|
+
|
|
462
|
+
case 'compactTombstones':
|
|
463
|
+
// Prune tombstones older than beforeMs from a collection.
|
|
464
|
+
// Returns the count of tombstones removed.
|
|
465
|
+
return db.compactTombstones(args.collection, args.beforeMs ?? 0);
|
|
466
|
+
|
|
467
|
+
case 'exportChangeset':
|
|
468
|
+
// Export a LWW changeset for the given collections since sinceMs.
|
|
469
|
+
// Returns a JSON string the caller can POST to a sync server.
|
|
470
|
+
return db.exportChangeset(args.collectionsJson, args.sinceMs ?? 0);
|
|
471
|
+
|
|
472
|
+
case 'importChangeset': {
|
|
473
|
+
// Apply a remote changeset (JSON string from sync server) using LWW.
|
|
474
|
+
// Triggers onWriteCommitted so multi-tab peers get notified.
|
|
475
|
+
const applied = db.importChangeset(args.changesetJson);
|
|
476
|
+
if (applied > 0) onWriteCommitted();
|
|
477
|
+
return applied;
|
|
478
|
+
}
|
|
479
|
+
|
|
360
480
|
case 'close':
|
|
481
|
+
// Flush any pending debounced snapshot before releasing the lock so
|
|
482
|
+
// no writes are lost when the tab closes or navigates away.
|
|
483
|
+
await flushSnapshot();
|
|
361
484
|
// Release the Web Lock and close the sync handle gracefully.
|
|
362
485
|
if (releaseLock) { releaseLock(); releaseLock = null; }
|
|
363
486
|
broadcastChannel?.close();
|
|
@@ -402,6 +525,17 @@ async function doInit(dbName, configJson) {
|
|
|
402
525
|
const resolve = pendingSnapshotResolve;
|
|
403
526
|
pendingSnapshotResolve = null;
|
|
404
527
|
resolve();
|
|
528
|
+
} else if (e.data?.type === 'taladb:secondary-write' && !idbFallback && db) {
|
|
529
|
+
// Primary (OPFS) tab: a secondary tab made a write — merge it in via LWW.
|
|
530
|
+
try {
|
|
531
|
+
const applied = db.importChangeset(e.data.changeset);
|
|
532
|
+
if (applied > 0) {
|
|
533
|
+
log(`Merged ${applied} change(s) from secondary tab`);
|
|
534
|
+
onWriteCommitted();
|
|
535
|
+
}
|
|
536
|
+
} catch (err) {
|
|
537
|
+
warn('Failed to import secondary-tab changeset:', err);
|
|
538
|
+
}
|
|
405
539
|
}
|
|
406
540
|
};
|
|
407
541
|
log('BroadcastChannel opened:', `taladb:${dbName}`);
|