taladb 0.8.4 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.browser.mjs +107 -33
- package/dist/index.d.mts +31 -4
- package/dist/index.d.ts +31 -4
- package/dist/index.js +107 -33
- package/dist/index.mjs +107 -33
- package/dist/index.react-native.mjs +107 -33
- package/package.json +1 -1
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({
|
|
138
|
-
return doc?.
|
|
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,
|
|
141
|
-
const
|
|
142
|
-
if (
|
|
143
|
-
await cursorCol.
|
|
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
|
|
162
|
-
const
|
|
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(
|
|
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,
|
|
177
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
...
|
|
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({
|
|
100
|
-
return doc?.
|
|
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,
|
|
103
|
-
const
|
|
104
|
-
if (
|
|
105
|
-
await cursorCol.
|
|
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
|
|
124
|
-
const
|
|
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(
|
|
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,
|
|
139
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
...
|
|
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 {
|