taladb 0.8.4 → 0.9.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/dist/index.js CHANGED
@@ -134,15 +134,13 @@ function unsupportedSync(runtime) {
134
134
  };
135
135
  }
136
136
  async function readCursor(cursorCol, target) {
137
- const doc = await cursorCol.findOne({ _id: target });
138
- return doc?.sinceMs ?? 0;
137
+ const doc = await cursorCol.findOne({ target });
138
+ return { pushMs: doc?.pushMs ?? 0, pullMs: doc?.pullMs ?? 0 };
139
139
  }
140
- async function writeCursor(cursorCol, target, sinceMs) {
141
- const existing = await cursorCol.findOne({ _id: target });
142
- if (existing) {
143
- await cursorCol.updateOne({ _id: target }, { $set: { sinceMs } });
144
- } else {
145
- await cursorCol.insert({ _id: target, sinceMs });
140
+ async function writeCursor(cursorCol, target, cursor) {
141
+ const updated = await cursorCol.updateOne({ target }, { $set: { ...cursor } });
142
+ if (!updated) {
143
+ await cursorCol.insert({ target, ...cursor });
146
144
  }
147
145
  }
148
146
  async function runSync(handle, adapter, options) {
@@ -158,12 +156,11 @@ async function runSync(handle, adapter, options) {
158
156
  }
159
157
  const collections = await resolveCollections(handle, options);
160
158
  const cursorCol = handle.collection(CURSOR_COLLECTION);
161
- const sinceMs = await readCursor(cursorCol, target);
162
- const startedAt = Date.now();
163
- const local = doPush ? await handle.exportChanges(collections, sinceMs) : "[]";
159
+ const cursor = await readCursor(cursorCol, target);
160
+ const local = doPush ? await handle.exportChanges(collections, 0) : "[]";
164
161
  let pulled = 0;
165
162
  if (doPull) {
166
- const remote = await adapter.pull(sinceMs);
163
+ const remote = await adapter.pull(0);
167
164
  if (remote && remote !== "[]") {
168
165
  pulled = await handle.importChanges(remote);
169
166
  }
@@ -173,8 +170,11 @@ async function runSync(handle, adapter, options) {
173
170
  pushed = JSON.parse(local).length;
174
171
  await adapter.push(local);
175
172
  }
176
- await writeCursor(cursorCol, target, startedAt);
177
- return { pushed, pulled, cursor: startedAt };
173
+ await writeCursor(cursorCol, target, {
174
+ pushMs: cursor.pushMs,
175
+ pullMs: cursor.pullMs
176
+ });
177
+ return { pushed, pulled, cursor: 0 };
178
178
  }
179
179
 
180
180
  // src/http-adapter.ts
@@ -182,7 +182,7 @@ var HttpSyncAdapter = class {
182
182
  constructor(options) {
183
183
  this.endpoint = options.endpoint.replace(/\/$/, "");
184
184
  this.headers = options.headers ?? {};
185
- const f = options.fetch ?? globalThis.fetch;
185
+ const f = options.fetch ?? globalThis.fetch?.bind(globalThis);
186
186
  if (!f) {
187
187
  throw new Error(
188
188
  "HttpSyncAdapter: no fetch available. Pass options.fetch on runtimes without a global fetch."
@@ -309,28 +309,44 @@ var WorkerProxy = class {
309
309
  this.pending.clear();
310
310
  }
311
311
  };
312
- function makePoller(findFn, callback) {
312
+ function makePoller(findFn, callback, onError) {
313
313
  let active = true;
314
314
  let lastJson = "";
315
+ let running = false;
316
+ let rerun = false;
315
317
  const poll = async () => {
316
318
  if (!active) return;
319
+ if (running) {
320
+ rerun = true;
321
+ return;
322
+ }
323
+ running = true;
317
324
  try {
318
325
  const docs = await findFn();
326
+ if (!active) return;
319
327
  const json = JSON.stringify(docs);
320
328
  if (json !== lastJson) {
321
329
  lastJson = json;
322
330
  callback(docs);
323
331
  }
324
- } catch {
332
+ } catch (error) {
333
+ if (active) onError?.(error);
334
+ } finally {
335
+ running = false;
336
+ if (active) {
337
+ if (rerun) {
338
+ rerun = false;
339
+ void poll();
340
+ } else setTimeout(poll, 300);
341
+ }
325
342
  }
326
- if (active) setTimeout(poll, 300);
327
343
  };
328
344
  poll();
329
345
  return () => {
330
346
  active = false;
331
347
  };
332
348
  }
333
- async function createBrowserDB(dbName, config) {
349
+ async function createBrowserDB(dbName, config, passphrase) {
334
350
  const workerUrl = new URL("@taladb/web/worker/taladb.worker.js", import_meta.url);
335
351
  const worker = new Worker(workerUrl, { type: "module", name: "taladb" });
336
352
  const proxy = new WorkerProxy(worker);
@@ -338,7 +354,13 @@ async function createBrowserDB(dbName, config) {
338
354
  proxy.abort(new Error(`taladb worker error: ${e.message ?? "unknown"}`));
339
355
  };
340
356
  const configJson = config !== void 0 ? JSON.stringify(config) : void 0;
341
- await proxy.send("init", { dbName, configJson });
357
+ try {
358
+ await proxy.send("init", { dbName, configJson, passphrase });
359
+ } catch (e) {
360
+ proxy.abort(e instanceof Error ? e : new Error(String(e)));
361
+ worker.terminate();
362
+ throw e;
363
+ }
342
364
  const nudgeCallbacks = /* @__PURE__ */ new Set();
343
365
  let channel = null;
344
366
  if (typeof BroadcastChannel !== "undefined") {
@@ -399,6 +421,8 @@ async function createBrowserDB(dbName, config) {
399
421
  },
400
422
  createIndex: (field) => proxy.send("createIndex", { collection: name, field }),
401
423
  dropIndex: (field) => proxy.send("dropIndex", { collection: name, field }),
424
+ createCompoundIndex: (fields) => proxy.send("createCompoundIndex", { collection: name, fieldsJson: JSON.stringify(fields) }),
425
+ dropCompoundIndex: (fields) => proxy.send("dropCompoundIndex", { collection: name, fieldsJson: JSON.stringify(fields) }),
402
426
  createFtsIndex: (field) => proxy.send("createFtsIndex", { collection: name, field }),
403
427
  dropFtsIndex: (field) => proxy.send("dropFtsIndex", { collection: name, field }),
404
428
  createVectorIndex: (field, options) => {
@@ -429,12 +453,19 @@ async function createBrowserDB(dbName, config) {
429
453
  });
430
454
  return JSON.parse(json);
431
455
  },
432
- subscribe: (filter, callback) => {
456
+ subscribe: (filter, callback, onError) => {
433
457
  let active = true;
434
458
  let lastJson = "";
435
459
  let timer = null;
460
+ let running = false;
461
+ let rerun = false;
436
462
  const poll = async () => {
437
463
  if (!active) return;
464
+ if (running) {
465
+ rerun = true;
466
+ return;
467
+ }
468
+ running = true;
438
469
  if (timer !== null) {
439
470
  clearTimeout(timer);
440
471
  timer = null;
@@ -444,13 +475,22 @@ async function createBrowserDB(dbName, config) {
444
475
  collection: name,
445
476
  filterJson: filter ? s(filter) : "null"
446
477
  });
478
+ if (!active) return;
447
479
  if (json !== lastJson) {
448
480
  lastJson = json;
449
481
  callback(JSON.parse(json));
450
482
  }
451
- } catch {
483
+ } catch (error) {
484
+ if (active) onError?.(error);
485
+ } finally {
486
+ running = false;
487
+ }
488
+ if (active) {
489
+ if (rerun) {
490
+ rerun = false;
491
+ void poll();
492
+ } else timer = setTimeout(poll, 300);
452
493
  }
453
- if (active) timer = setTimeout(poll, 300);
454
494
  };
455
495
  nudgeCallbacks.add(poll);
456
496
  poll();
@@ -466,9 +506,11 @@ async function createBrowserDB(dbName, config) {
466
506
  };
467
507
  return opts ? applySchema(wrapped, opts) : wrapped;
468
508
  }
469
- return {
509
+ const handle = {
470
510
  collection: (name, opts) => wrapCollection(name, opts),
471
511
  compact: () => proxy.send("compact"),
512
+ syncStatus: async () => JSON.parse(await proxy.send("syncStatus")),
513
+ flushSync: (timeoutMs = 5e3) => proxy.send("flushSync", { timeoutMs }),
472
514
  close: async () => {
473
515
  channel?.close();
474
516
  try {
@@ -478,13 +520,21 @@ async function createBrowserDB(dbName, config) {
478
520
  proxy.abort(new Error("taladb worker closed"));
479
521
  }
480
522
  },
481
- ...unsupportedSync("browser (OPFS worker)")
523
+ // All engine work (export scan, LWW merge) runs inside the worker, off the
524
+ // main thread — a sync pass never blocks rendering, whatever its size.
525
+ exportChanges: (collections, sinceMs) => proxy.send("exportChangeset", { collectionsJson: JSON.stringify(collections), sinceMs }),
526
+ importChanges: (changeset) => proxy.send("importChangeset", { changesetJson: changeset }),
527
+ listCollectionNames: async () => JSON.parse(await proxy.send("listCollections")),
528
+ sync: (adapter, options) => runSync(handle, adapter, options)
482
529
  };
530
+ return handle;
483
531
  }
484
- async function createNodeDB(dbName, config) {
485
- const { TalaDBNode } = await import("@taladb/node");
532
+ async function createNodeDB(dbName, config, passphrase) {
533
+ const native = await import("@taladb/node");
534
+ const TalaDBNode = native.TalaDbNode ?? native.TalaDBNode;
535
+ if (!TalaDBNode) throw new Error("@taladb/node loaded but exports no TalaDbNode class \u2014 rebuild the native module");
486
536
  const configJson = config !== void 0 ? JSON.stringify(config) : null;
487
- const db = TalaDBNode.open(dbName, configJson);
537
+ const db = TalaDBNode.open(dbName, configJson, passphrase ?? null);
488
538
  function wrapCollection(name, opts) {
489
539
  const col = db.collection(name);
490
540
  const wrapped = {
@@ -500,6 +550,8 @@ async function createNodeDB(dbName, config) {
500
550
  aggregate: async (pipeline) => col.aggregate(pipeline),
501
551
  createIndex: async (field) => col.createIndex(field),
502
552
  dropIndex: async (field) => col.dropIndex(field),
553
+ createCompoundIndex: async (fields) => col.createCompoundIndex(fields),
554
+ dropCompoundIndex: async (fields) => col.dropCompoundIndex(fields),
503
555
  createFtsIndex: async (field) => col.createFtsIndex(field),
504
556
  dropFtsIndex: async (field) => col.dropFtsIndex(field),
505
557
  createVectorIndex: async (field, options) => col.createVectorIndex(field, options.dimensions, options.metric ?? null, options.indexType ?? null, options.hnswM ?? null, options.hnswEfConstruction ?? null),
@@ -513,7 +565,7 @@ async function createNodeDB(dbName, config) {
513
565
  const raw = await col.findNearest(field, vector, topK, filter ?? null);
514
566
  return raw;
515
567
  },
516
- subscribe: (filter, callback) => makePoller(async () => col.find(filter ?? null), callback)
568
+ subscribe: (filter, callback, onError) => makePoller(async () => col.find(filter ?? null), callback, onError)
517
569
  };
518
570
  return opts ? applySchema(wrapped, opts) : wrapped;
519
571
  }
@@ -551,6 +603,8 @@ async function createNativeDB(_dbName) {
551
603
  aggregate: async (pipeline) => native.aggregate(name, pipeline),
552
604
  createIndex: async (field) => native.createIndex(name, field),
553
605
  dropIndex: async (field) => native.dropIndex(name, field),
606
+ createCompoundIndex: async (fields) => native.createCompoundIndex(name, fields),
607
+ dropCompoundIndex: async (fields) => native.dropCompoundIndex(name, fields),
554
608
  createFtsIndex: async (field) => native.createFtsIndex(name, field),
555
609
  dropFtsIndex: async (field) => native.dropFtsIndex(name, field),
556
610
  createVectorIndex: async (field, options) => {
@@ -570,18 +624,35 @@ async function createNativeDB(_dbName) {
570
624
  const raw = native.findNearest(name, field, vector, topK, filter ?? null);
571
625
  return raw;
572
626
  },
573
- subscribe: (filter, callback) => makePoller(async () => native.find(name, filter ?? {}), callback)
627
+ subscribe: (filter, callback, onError) => makePoller(async () => native.find(name, filter ?? {}), callback, onError)
574
628
  };
575
629
  return opts ? applySchema(wrapped, opts) : wrapped;
576
630
  }
631
+ const syncSurface = typeof native.exportChanges === "function" && typeof native.importChanges === "function" && typeof native.listCollectionNames === "function" ? (() => {
632
+ const handle = {
633
+ collection: (name, opts) => wrapCollection(name, opts),
634
+ exportChanges: async (collections, sinceMs) => native.exportChanges(collections, sinceMs),
635
+ importChanges: async (changeset) => native.importChanges(changeset),
636
+ listCollectionNames: async () => native.listCollectionNames(),
637
+ sync: (adapter, options) => runSync(handle, adapter, options)
638
+ };
639
+ return {
640
+ exportChanges: handle.exportChanges,
641
+ importChanges: handle.importChanges,
642
+ sync: handle.sync
643
+ };
644
+ })() : unsupportedSync("react-native");
577
645
  return {
578
646
  collection: (name, opts) => wrapCollection(name, opts),
579
647
  compact: async () => native.compact(),
580
648
  close: async () => native.close(),
581
- ...unsupportedSync("react-native")
649
+ ...syncSurface
582
650
  };
583
651
  }
584
652
  async function openDB(dbName = "taladb.db", options) {
653
+ if (options?.passphrase !== void 0 && options.passphrase.length === 0) {
654
+ throw new Error("TalaDB encryption passphrase must not be empty");
655
+ }
585
656
  let resolvedConfig;
586
657
  if (options?.config !== void 0) {
587
658
  validateConfig(options.config);
@@ -592,11 +663,14 @@ async function openDB(dbName = "taladb.db", options) {
592
663
  const platform = detectPlatform();
593
664
  switch (platform) {
594
665
  case "browser":
595
- return createBrowserDB(dbName, resolvedConfig);
666
+ return createBrowserDB(dbName, resolvedConfig, options?.passphrase);
596
667
  case "react-native":
668
+ if (options?.passphrase !== void 0) {
669
+ throw new Error("On React Native, pass the passphrase in the config JSON to TalaDBModule.initialize(); refusing to assume the already-open native database is encrypted");
670
+ }
597
671
  return createNativeDB(dbName);
598
672
  case "node":
599
- return createNodeDB(dbName, resolvedConfig);
673
+ return createNodeDB(dbName, resolvedConfig, options?.passphrase);
600
674
  }
601
675
  }
602
676
  // Annotate the CommonJS export names for ESM import in node:
package/dist/index.mjs CHANGED
@@ -96,15 +96,13 @@ function unsupportedSync(runtime) {
96
96
  };
97
97
  }
98
98
  async function readCursor(cursorCol, target) {
99
- const doc = await cursorCol.findOne({ _id: target });
100
- return doc?.sinceMs ?? 0;
99
+ const doc = await cursorCol.findOne({ target });
100
+ return { pushMs: doc?.pushMs ?? 0, pullMs: doc?.pullMs ?? 0 };
101
101
  }
102
- async function writeCursor(cursorCol, target, sinceMs) {
103
- const existing = await cursorCol.findOne({ _id: target });
104
- if (existing) {
105
- await cursorCol.updateOne({ _id: target }, { $set: { sinceMs } });
106
- } else {
107
- await cursorCol.insert({ _id: target, sinceMs });
102
+ async function writeCursor(cursorCol, target, cursor) {
103
+ const updated = await cursorCol.updateOne({ target }, { $set: { ...cursor } });
104
+ if (!updated) {
105
+ await cursorCol.insert({ target, ...cursor });
108
106
  }
109
107
  }
110
108
  async function runSync(handle, adapter, options) {
@@ -120,12 +118,11 @@ async function runSync(handle, adapter, options) {
120
118
  }
121
119
  const collections = await resolveCollections(handle, options);
122
120
  const cursorCol = handle.collection(CURSOR_COLLECTION);
123
- const sinceMs = await readCursor(cursorCol, target);
124
- const startedAt = Date.now();
125
- const local = doPush ? await handle.exportChanges(collections, sinceMs) : "[]";
121
+ const cursor = await readCursor(cursorCol, target);
122
+ const local = doPush ? await handle.exportChanges(collections, 0) : "[]";
126
123
  let pulled = 0;
127
124
  if (doPull) {
128
- const remote = await adapter.pull(sinceMs);
125
+ const remote = await adapter.pull(0);
129
126
  if (remote && remote !== "[]") {
130
127
  pulled = await handle.importChanges(remote);
131
128
  }
@@ -135,8 +132,11 @@ async function runSync(handle, adapter, options) {
135
132
  pushed = JSON.parse(local).length;
136
133
  await adapter.push(local);
137
134
  }
138
- await writeCursor(cursorCol, target, startedAt);
139
- return { pushed, pulled, cursor: startedAt };
135
+ await writeCursor(cursorCol, target, {
136
+ pushMs: cursor.pushMs,
137
+ pullMs: cursor.pullMs
138
+ });
139
+ return { pushed, pulled, cursor: 0 };
140
140
  }
141
141
 
142
142
  // src/http-adapter.ts
@@ -144,7 +144,7 @@ var HttpSyncAdapter = class {
144
144
  constructor(options) {
145
145
  this.endpoint = options.endpoint.replace(/\/$/, "");
146
146
  this.headers = options.headers ?? {};
147
- const f = options.fetch ?? globalThis.fetch;
147
+ const f = options.fetch ?? globalThis.fetch?.bind(globalThis);
148
148
  if (!f) {
149
149
  throw new Error(
150
150
  "HttpSyncAdapter: no fetch available. Pass options.fetch on runtimes without a global fetch."
@@ -270,28 +270,44 @@ var WorkerProxy = class {
270
270
  this.pending.clear();
271
271
  }
272
272
  };
273
- function makePoller(findFn, callback) {
273
+ function makePoller(findFn, callback, onError) {
274
274
  let active = true;
275
275
  let lastJson = "";
276
+ let running = false;
277
+ let rerun = false;
276
278
  const poll = async () => {
277
279
  if (!active) return;
280
+ if (running) {
281
+ rerun = true;
282
+ return;
283
+ }
284
+ running = true;
278
285
  try {
279
286
  const docs = await findFn();
287
+ if (!active) return;
280
288
  const json = JSON.stringify(docs);
281
289
  if (json !== lastJson) {
282
290
  lastJson = json;
283
291
  callback(docs);
284
292
  }
285
- } catch {
293
+ } catch (error) {
294
+ if (active) onError?.(error);
295
+ } finally {
296
+ running = false;
297
+ if (active) {
298
+ if (rerun) {
299
+ rerun = false;
300
+ void poll();
301
+ } else setTimeout(poll, 300);
302
+ }
286
303
  }
287
- if (active) setTimeout(poll, 300);
288
304
  };
289
305
  poll();
290
306
  return () => {
291
307
  active = false;
292
308
  };
293
309
  }
294
- async function createBrowserDB(dbName, config) {
310
+ async function createBrowserDB(dbName, config, passphrase) {
295
311
  const workerUrl = new URL("@taladb/web/worker/taladb.worker.js", import.meta.url);
296
312
  const worker = new Worker(workerUrl, { type: "module", name: "taladb" });
297
313
  const proxy = new WorkerProxy(worker);
@@ -299,7 +315,13 @@ async function createBrowserDB(dbName, config) {
299
315
  proxy.abort(new Error(`taladb worker error: ${e.message ?? "unknown"}`));
300
316
  };
301
317
  const configJson = config !== void 0 ? JSON.stringify(config) : void 0;
302
- await proxy.send("init", { dbName, configJson });
318
+ try {
319
+ await proxy.send("init", { dbName, configJson, passphrase });
320
+ } catch (e) {
321
+ proxy.abort(e instanceof Error ? e : new Error(String(e)));
322
+ worker.terminate();
323
+ throw e;
324
+ }
303
325
  const nudgeCallbacks = /* @__PURE__ */ new Set();
304
326
  let channel = null;
305
327
  if (typeof BroadcastChannel !== "undefined") {
@@ -360,6 +382,8 @@ async function createBrowserDB(dbName, config) {
360
382
  },
361
383
  createIndex: (field) => proxy.send("createIndex", { collection: name, field }),
362
384
  dropIndex: (field) => proxy.send("dropIndex", { collection: name, field }),
385
+ createCompoundIndex: (fields) => proxy.send("createCompoundIndex", { collection: name, fieldsJson: JSON.stringify(fields) }),
386
+ dropCompoundIndex: (fields) => proxy.send("dropCompoundIndex", { collection: name, fieldsJson: JSON.stringify(fields) }),
363
387
  createFtsIndex: (field) => proxy.send("createFtsIndex", { collection: name, field }),
364
388
  dropFtsIndex: (field) => proxy.send("dropFtsIndex", { collection: name, field }),
365
389
  createVectorIndex: (field, options) => {
@@ -390,12 +414,19 @@ async function createBrowserDB(dbName, config) {
390
414
  });
391
415
  return JSON.parse(json);
392
416
  },
393
- subscribe: (filter, callback) => {
417
+ subscribe: (filter, callback, onError) => {
394
418
  let active = true;
395
419
  let lastJson = "";
396
420
  let timer = null;
421
+ let running = false;
422
+ let rerun = false;
397
423
  const poll = async () => {
398
424
  if (!active) return;
425
+ if (running) {
426
+ rerun = true;
427
+ return;
428
+ }
429
+ running = true;
399
430
  if (timer !== null) {
400
431
  clearTimeout(timer);
401
432
  timer = null;
@@ -405,13 +436,22 @@ async function createBrowserDB(dbName, config) {
405
436
  collection: name,
406
437
  filterJson: filter ? s(filter) : "null"
407
438
  });
439
+ if (!active) return;
408
440
  if (json !== lastJson) {
409
441
  lastJson = json;
410
442
  callback(JSON.parse(json));
411
443
  }
412
- } catch {
444
+ } catch (error) {
445
+ if (active) onError?.(error);
446
+ } finally {
447
+ running = false;
448
+ }
449
+ if (active) {
450
+ if (rerun) {
451
+ rerun = false;
452
+ void poll();
453
+ } else timer = setTimeout(poll, 300);
413
454
  }
414
- if (active) timer = setTimeout(poll, 300);
415
455
  };
416
456
  nudgeCallbacks.add(poll);
417
457
  poll();
@@ -427,9 +467,11 @@ async function createBrowserDB(dbName, config) {
427
467
  };
428
468
  return opts ? applySchema(wrapped, opts) : wrapped;
429
469
  }
430
- return {
470
+ const handle = {
431
471
  collection: (name, opts) => wrapCollection(name, opts),
432
472
  compact: () => proxy.send("compact"),
473
+ syncStatus: async () => JSON.parse(await proxy.send("syncStatus")),
474
+ flushSync: (timeoutMs = 5e3) => proxy.send("flushSync", { timeoutMs }),
433
475
  close: async () => {
434
476
  channel?.close();
435
477
  try {
@@ -439,13 +481,21 @@ async function createBrowserDB(dbName, config) {
439
481
  proxy.abort(new Error("taladb worker closed"));
440
482
  }
441
483
  },
442
- ...unsupportedSync("browser (OPFS worker)")
484
+ // All engine work (export scan, LWW merge) runs inside the worker, off the
485
+ // main thread — a sync pass never blocks rendering, whatever its size.
486
+ exportChanges: (collections, sinceMs) => proxy.send("exportChangeset", { collectionsJson: JSON.stringify(collections), sinceMs }),
487
+ importChanges: (changeset) => proxy.send("importChangeset", { changesetJson: changeset }),
488
+ listCollectionNames: async () => JSON.parse(await proxy.send("listCollections")),
489
+ sync: (adapter, options) => runSync(handle, adapter, options)
443
490
  };
491
+ return handle;
444
492
  }
445
- async function createNodeDB(dbName, config) {
446
- const { TalaDBNode } = await import("@taladb/node");
493
+ async function createNodeDB(dbName, config, passphrase) {
494
+ const native = await import("@taladb/node");
495
+ const TalaDBNode = native.TalaDbNode ?? native.TalaDBNode;
496
+ if (!TalaDBNode) throw new Error("@taladb/node loaded but exports no TalaDbNode class \u2014 rebuild the native module");
447
497
  const configJson = config !== void 0 ? JSON.stringify(config) : null;
448
- const db = TalaDBNode.open(dbName, configJson);
498
+ const db = TalaDBNode.open(dbName, configJson, passphrase ?? null);
449
499
  function wrapCollection(name, opts) {
450
500
  const col = db.collection(name);
451
501
  const wrapped = {
@@ -461,6 +511,8 @@ async function createNodeDB(dbName, config) {
461
511
  aggregate: async (pipeline) => col.aggregate(pipeline),
462
512
  createIndex: async (field) => col.createIndex(field),
463
513
  dropIndex: async (field) => col.dropIndex(field),
514
+ createCompoundIndex: async (fields) => col.createCompoundIndex(fields),
515
+ dropCompoundIndex: async (fields) => col.dropCompoundIndex(fields),
464
516
  createFtsIndex: async (field) => col.createFtsIndex(field),
465
517
  dropFtsIndex: async (field) => col.dropFtsIndex(field),
466
518
  createVectorIndex: async (field, options) => col.createVectorIndex(field, options.dimensions, options.metric ?? null, options.indexType ?? null, options.hnswM ?? null, options.hnswEfConstruction ?? null),
@@ -474,7 +526,7 @@ async function createNodeDB(dbName, config) {
474
526
  const raw = await col.findNearest(field, vector, topK, filter ?? null);
475
527
  return raw;
476
528
  },
477
- subscribe: (filter, callback) => makePoller(async () => col.find(filter ?? null), callback)
529
+ subscribe: (filter, callback, onError) => makePoller(async () => col.find(filter ?? null), callback, onError)
478
530
  };
479
531
  return opts ? applySchema(wrapped, opts) : wrapped;
480
532
  }
@@ -512,6 +564,8 @@ async function createNativeDB(_dbName) {
512
564
  aggregate: async (pipeline) => native.aggregate(name, pipeline),
513
565
  createIndex: async (field) => native.createIndex(name, field),
514
566
  dropIndex: async (field) => native.dropIndex(name, field),
567
+ createCompoundIndex: async (fields) => native.createCompoundIndex(name, fields),
568
+ dropCompoundIndex: async (fields) => native.dropCompoundIndex(name, fields),
515
569
  createFtsIndex: async (field) => native.createFtsIndex(name, field),
516
570
  dropFtsIndex: async (field) => native.dropFtsIndex(name, field),
517
571
  createVectorIndex: async (field, options) => {
@@ -531,18 +585,35 @@ async function createNativeDB(_dbName) {
531
585
  const raw = native.findNearest(name, field, vector, topK, filter ?? null);
532
586
  return raw;
533
587
  },
534
- subscribe: (filter, callback) => makePoller(async () => native.find(name, filter ?? {}), callback)
588
+ subscribe: (filter, callback, onError) => makePoller(async () => native.find(name, filter ?? {}), callback, onError)
535
589
  };
536
590
  return opts ? applySchema(wrapped, opts) : wrapped;
537
591
  }
592
+ const syncSurface = typeof native.exportChanges === "function" && typeof native.importChanges === "function" && typeof native.listCollectionNames === "function" ? (() => {
593
+ const handle = {
594
+ collection: (name, opts) => wrapCollection(name, opts),
595
+ exportChanges: async (collections, sinceMs) => native.exportChanges(collections, sinceMs),
596
+ importChanges: async (changeset) => native.importChanges(changeset),
597
+ listCollectionNames: async () => native.listCollectionNames(),
598
+ sync: (adapter, options) => runSync(handle, adapter, options)
599
+ };
600
+ return {
601
+ exportChanges: handle.exportChanges,
602
+ importChanges: handle.importChanges,
603
+ sync: handle.sync
604
+ };
605
+ })() : unsupportedSync("react-native");
538
606
  return {
539
607
  collection: (name, opts) => wrapCollection(name, opts),
540
608
  compact: async () => native.compact(),
541
609
  close: async () => native.close(),
542
- ...unsupportedSync("react-native")
610
+ ...syncSurface
543
611
  };
544
612
  }
545
613
  async function openDB(dbName = "taladb.db", options) {
614
+ if (options?.passphrase !== void 0 && options.passphrase.length === 0) {
615
+ throw new Error("TalaDB encryption passphrase must not be empty");
616
+ }
546
617
  let resolvedConfig;
547
618
  if (options?.config !== void 0) {
548
619
  validateConfig(options.config);
@@ -553,11 +624,14 @@ async function openDB(dbName = "taladb.db", options) {
553
624
  const platform = detectPlatform();
554
625
  switch (platform) {
555
626
  case "browser":
556
- return createBrowserDB(dbName, resolvedConfig);
627
+ return createBrowserDB(dbName, resolvedConfig, options?.passphrase);
557
628
  case "react-native":
629
+ if (options?.passphrase !== void 0) {
630
+ throw new Error("On React Native, pass the passphrase in the config JSON to TalaDBModule.initialize(); refusing to assume the already-open native database is encrypted");
631
+ }
558
632
  return createNativeDB(dbName);
559
633
  case "node":
560
- return createNodeDB(dbName, resolvedConfig);
634
+ return createNodeDB(dbName, resolvedConfig, options?.passphrase);
561
635
  }
562
636
  }
563
637
  export {