@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taladb/web",
3
- "version": "0.6.0",
3
+ "version": "0.7.3",
4
4
  "description": "TalaDB WASM bindings — document queries and vector search in the browser",
5
5
  "main": "pkg/taladb_web.js",
6
6
  "types": "pkg/taladb_web.d.ts",
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.6.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",
@@ -16,11 +16,11 @@ export class CollectionWasm {
16
16
  /**
17
17
  * Create a vector index on `field`.
18
18
  *
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).
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` optional pre-filter (same format as `find`). Pass `null` to
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
- * // mutations
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
- * `config_json` JSON-serialised `TalaDbConfig`, or `null` to open without sync.
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` JSON-serialised `TalaDbConfig`, or `null` to open without sync.
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 wasm_bindgen__closure__destroy__hf3c3b5f99d903397: (a: number, b: number) => void;
355
- readonly wasm_bindgen__closure__destroy__h478053cb26acc43f: (a: number, b: number) => void;
356
- readonly wasm_bindgen__closure__destroy__h1febc128ce23ccd1: (a: number, b: number) => void;
357
- readonly wasm_bindgen__convert__closures_____invoke__h3022e61d849d0b12: (a: number, b: number, c: any) => [number, number];
358
- readonly wasm_bindgen__convert__closures_____invoke__h470c93d8a24e0015: (a: number, b: number, c: any, d: any) => void;
359
- readonly wasm_bindgen__convert__closures_____invoke__h524ec4525bd0d07f: (a: number, b: number, c: any) => void;
360
- readonly wasm_bindgen__convert__closures_____invoke__h325a87e086f6f78c: (a: number, b: number) => void;
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` 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).
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` optional pre-filter (same format as `find`). Pass `null` to
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
- return CollectionWasm.__wrap(ret);
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
- * // mutations
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
- * `config_json` JSON-serialised `TalaDbConfig`, or `null` to open without sync.
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` JSON-serialised `TalaDbConfig`, or `null` to open without sync.
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 wasm_bindgen__convert__closures_____invoke__h470c93d8a24e0015(a, state0.b, arg0, arg1);
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 wasm_bindgen__convert__closures_____invoke__h470c93d8a24e0015(a, state0.b, arg0, arg1);
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: 156, function: Function { arguments: [Externref], shim_idx: 157, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
1511
- const ret = makeMutClosure(arg0, arg1, wasm.wasm_bindgen__closure__destroy__hf3c3b5f99d903397, wasm_bindgen__convert__closures_____invoke__h524ec4525bd0d07f);
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: 170, function: Function { arguments: [], shim_idx: 171, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
1516
- const ret = makeMutClosure(arg0, arg1, wasm.wasm_bindgen__closure__destroy__h478053cb26acc43f, wasm_bindgen__convert__closures_____invoke__h325a87e086f6f78c);
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: 575, function: Function { arguments: [Externref], shim_idx: 576, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
1521
- const ret = makeMutClosure(arg0, arg1, wasm.wasm_bindgen__closure__destroy__h1febc128ce23ccd1, wasm_bindgen__convert__closures_____invoke__h3022e61d849d0b12);
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 wasm_bindgen__convert__closures_____invoke__h325a87e086f6f78c(arg0, arg1) {
1568
- wasm.wasm_bindgen__convert__closures_____invoke__h325a87e086f6f78c(arg0, arg1);
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 wasm_bindgen__convert__closures_____invoke__h524ec4525bd0d07f(arg0, arg1, arg2) {
1572
- wasm.wasm_bindgen__convert__closures_____invoke__h524ec4525bd0d07f(arg0, arg1, arg2);
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 wasm_bindgen__convert__closures_____invoke__h3022e61d849d0b12(arg0, arg1, arg2) {
1576
- const ret = wasm.wasm_bindgen__convert__closures_____invoke__h3022e61d849d0b12(arg0, arg1, arg2);
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 wasm_bindgen__convert__closures_____invoke__h470c93d8a24e0015(arg0, arg1, arg2, arg3) {
1583
- wasm.wasm_bindgen__convert__closures_____invoke__h470c93d8a24e0015(arg0, arg1, arg2, arg3);
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
 
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 wasm_bindgen__closure__destroy__hf3c3b5f99d903397: (a: number, b: number) => void;
59
- export const wasm_bindgen__closure__destroy__h478053cb26acc43f: (a: number, b: number) => void;
60
- export const wasm_bindgen__closure__destroy__h1febc128ce23ccd1: (a: number, b: number) => void;
61
- export const wasm_bindgen__convert__closures_____invoke__h3022e61d849d0b12: (a: number, b: number, c: any) => [number, number];
62
- export const wasm_bindgen__convert__closures_____invoke__h470c93d8a24e0015: (a: number, b: number, c: any, d: any) => void;
63
- export const wasm_bindgen__convert__closures_____invoke__h524ec4525bd0d07f: (a: number, b: number, c: any) => void;
64
- export const wasm_bindgen__convert__closures_____invoke__h325a87e086f6f78c: (a: number, b: number) => void;
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;
@@ -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
- * Notify sibling tabs of a write and, when in IDB-fallback mode, flush the
198
- * updated snapshot to IndexedDB. Must be called after every mutating op.
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
- function onWriteCommitted() {
201
- broadcastChannel?.postMessage('taladb:changed');
202
- // Always flush a snapshot to IDB after every write — this keeps other tabs'
203
- // IDB-fallback instances in sync via BroadcastChannel + snapshotDirty reload.
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).catch(() => {});
208
- } catch { /* ignore snapshot export errors */ }
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}`);