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.browser.mjs
CHANGED
|
@@ -39,15 +39,13 @@ function unsupportedSync(runtime) {
|
|
|
39
39
|
};
|
|
40
40
|
}
|
|
41
41
|
async function readCursor(cursorCol, target) {
|
|
42
|
-
const doc = await cursorCol.findOne({
|
|
43
|
-
return doc?.
|
|
42
|
+
const doc = await cursorCol.findOne({ target });
|
|
43
|
+
return { pushMs: doc?.pushMs ?? 0, pullMs: doc?.pullMs ?? 0 };
|
|
44
44
|
}
|
|
45
|
-
async function writeCursor(cursorCol, target,
|
|
46
|
-
const
|
|
47
|
-
if (
|
|
48
|
-
await cursorCol.
|
|
49
|
-
} else {
|
|
50
|
-
await cursorCol.insert({ _id: target, sinceMs });
|
|
45
|
+
async function writeCursor(cursorCol, target, cursor) {
|
|
46
|
+
const updated = await cursorCol.updateOne({ target }, { $set: { ...cursor } });
|
|
47
|
+
if (!updated) {
|
|
48
|
+
await cursorCol.insert({ target, ...cursor });
|
|
51
49
|
}
|
|
52
50
|
}
|
|
53
51
|
async function runSync(handle, adapter, options) {
|
|
@@ -63,12 +61,11 @@ async function runSync(handle, adapter, options) {
|
|
|
63
61
|
}
|
|
64
62
|
const collections = await resolveCollections(handle, options);
|
|
65
63
|
const cursorCol = handle.collection(CURSOR_COLLECTION);
|
|
66
|
-
const
|
|
67
|
-
const
|
|
68
|
-
const local = doPush ? await handle.exportChanges(collections, sinceMs) : "[]";
|
|
64
|
+
const cursor = await readCursor(cursorCol, target);
|
|
65
|
+
const local = doPush ? await handle.exportChanges(collections, 0) : "[]";
|
|
69
66
|
let pulled = 0;
|
|
70
67
|
if (doPull) {
|
|
71
|
-
const remote = await adapter.pull(
|
|
68
|
+
const remote = await adapter.pull(0);
|
|
72
69
|
if (remote && remote !== "[]") {
|
|
73
70
|
pulled = await handle.importChanges(remote);
|
|
74
71
|
}
|
|
@@ -78,8 +75,11 @@ async function runSync(handle, adapter, options) {
|
|
|
78
75
|
pushed = JSON.parse(local).length;
|
|
79
76
|
await adapter.push(local);
|
|
80
77
|
}
|
|
81
|
-
await writeCursor(cursorCol, target,
|
|
82
|
-
|
|
78
|
+
await writeCursor(cursorCol, target, {
|
|
79
|
+
pushMs: cursor.pushMs,
|
|
80
|
+
pullMs: cursor.pullMs
|
|
81
|
+
});
|
|
82
|
+
return { pushed, pulled, cursor: 0 };
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
// src/http-adapter.ts
|
|
@@ -87,7 +87,7 @@ var HttpSyncAdapter = class {
|
|
|
87
87
|
constructor(options) {
|
|
88
88
|
this.endpoint = options.endpoint.replace(/\/$/, "");
|
|
89
89
|
this.headers = options.headers ?? {};
|
|
90
|
-
const f = options.fetch ?? globalThis.fetch;
|
|
90
|
+
const f = options.fetch ?? globalThis.fetch?.bind(globalThis);
|
|
91
91
|
if (!f) {
|
|
92
92
|
throw new Error(
|
|
93
93
|
"HttpSyncAdapter: no fetch available. Pass options.fetch on runtimes without a global fetch."
|
|
@@ -213,28 +213,44 @@ var WorkerProxy = class {
|
|
|
213
213
|
this.pending.clear();
|
|
214
214
|
}
|
|
215
215
|
};
|
|
216
|
-
function makePoller(findFn, callback) {
|
|
216
|
+
function makePoller(findFn, callback, onError) {
|
|
217
217
|
let active = true;
|
|
218
218
|
let lastJson = "";
|
|
219
|
+
let running = false;
|
|
220
|
+
let rerun = false;
|
|
219
221
|
const poll = async () => {
|
|
220
222
|
if (!active) return;
|
|
223
|
+
if (running) {
|
|
224
|
+
rerun = true;
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
running = true;
|
|
221
228
|
try {
|
|
222
229
|
const docs = await findFn();
|
|
230
|
+
if (!active) return;
|
|
223
231
|
const json = JSON.stringify(docs);
|
|
224
232
|
if (json !== lastJson) {
|
|
225
233
|
lastJson = json;
|
|
226
234
|
callback(docs);
|
|
227
235
|
}
|
|
228
|
-
} catch {
|
|
236
|
+
} catch (error) {
|
|
237
|
+
if (active) onError?.(error);
|
|
238
|
+
} finally {
|
|
239
|
+
running = false;
|
|
240
|
+
if (active) {
|
|
241
|
+
if (rerun) {
|
|
242
|
+
rerun = false;
|
|
243
|
+
void poll();
|
|
244
|
+
} else setTimeout(poll, 300);
|
|
245
|
+
}
|
|
229
246
|
}
|
|
230
|
-
if (active) setTimeout(poll, 300);
|
|
231
247
|
};
|
|
232
248
|
poll();
|
|
233
249
|
return () => {
|
|
234
250
|
active = false;
|
|
235
251
|
};
|
|
236
252
|
}
|
|
237
|
-
async function createBrowserDB(dbName, config) {
|
|
253
|
+
async function createBrowserDB(dbName, config, passphrase) {
|
|
238
254
|
const workerUrl = new URL("@taladb/web/worker/taladb.worker.js", import.meta.url);
|
|
239
255
|
const worker = new Worker(workerUrl, { type: "module", name: "taladb" });
|
|
240
256
|
const proxy = new WorkerProxy(worker);
|
|
@@ -242,7 +258,13 @@ async function createBrowserDB(dbName, config) {
|
|
|
242
258
|
proxy.abort(new Error(`taladb worker error: ${e.message ?? "unknown"}`));
|
|
243
259
|
};
|
|
244
260
|
const configJson = config !== void 0 ? JSON.stringify(config) : void 0;
|
|
245
|
-
|
|
261
|
+
try {
|
|
262
|
+
await proxy.send("init", { dbName, configJson, passphrase });
|
|
263
|
+
} catch (e) {
|
|
264
|
+
proxy.abort(e instanceof Error ? e : new Error(String(e)));
|
|
265
|
+
worker.terminate();
|
|
266
|
+
throw e;
|
|
267
|
+
}
|
|
246
268
|
const nudgeCallbacks = /* @__PURE__ */ new Set();
|
|
247
269
|
let channel = null;
|
|
248
270
|
if (typeof BroadcastChannel !== "undefined") {
|
|
@@ -303,6 +325,8 @@ async function createBrowserDB(dbName, config) {
|
|
|
303
325
|
},
|
|
304
326
|
createIndex: (field) => proxy.send("createIndex", { collection: name, field }),
|
|
305
327
|
dropIndex: (field) => proxy.send("dropIndex", { collection: name, field }),
|
|
328
|
+
createCompoundIndex: (fields) => proxy.send("createCompoundIndex", { collection: name, fieldsJson: JSON.stringify(fields) }),
|
|
329
|
+
dropCompoundIndex: (fields) => proxy.send("dropCompoundIndex", { collection: name, fieldsJson: JSON.stringify(fields) }),
|
|
306
330
|
createFtsIndex: (field) => proxy.send("createFtsIndex", { collection: name, field }),
|
|
307
331
|
dropFtsIndex: (field) => proxy.send("dropFtsIndex", { collection: name, field }),
|
|
308
332
|
createVectorIndex: (field, options) => {
|
|
@@ -333,12 +357,19 @@ async function createBrowserDB(dbName, config) {
|
|
|
333
357
|
});
|
|
334
358
|
return JSON.parse(json);
|
|
335
359
|
},
|
|
336
|
-
subscribe: (filter, callback) => {
|
|
360
|
+
subscribe: (filter, callback, onError) => {
|
|
337
361
|
let active = true;
|
|
338
362
|
let lastJson = "";
|
|
339
363
|
let timer = null;
|
|
364
|
+
let running = false;
|
|
365
|
+
let rerun = false;
|
|
340
366
|
const poll = async () => {
|
|
341
367
|
if (!active) return;
|
|
368
|
+
if (running) {
|
|
369
|
+
rerun = true;
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
running = true;
|
|
342
373
|
if (timer !== null) {
|
|
343
374
|
clearTimeout(timer);
|
|
344
375
|
timer = null;
|
|
@@ -348,13 +379,22 @@ async function createBrowserDB(dbName, config) {
|
|
|
348
379
|
collection: name,
|
|
349
380
|
filterJson: filter ? s(filter) : "null"
|
|
350
381
|
});
|
|
382
|
+
if (!active) return;
|
|
351
383
|
if (json !== lastJson) {
|
|
352
384
|
lastJson = json;
|
|
353
385
|
callback(JSON.parse(json));
|
|
354
386
|
}
|
|
355
|
-
} catch {
|
|
387
|
+
} catch (error) {
|
|
388
|
+
if (active) onError?.(error);
|
|
389
|
+
} finally {
|
|
390
|
+
running = false;
|
|
391
|
+
}
|
|
392
|
+
if (active) {
|
|
393
|
+
if (rerun) {
|
|
394
|
+
rerun = false;
|
|
395
|
+
void poll();
|
|
396
|
+
} else timer = setTimeout(poll, 300);
|
|
356
397
|
}
|
|
357
|
-
if (active) timer = setTimeout(poll, 300);
|
|
358
398
|
};
|
|
359
399
|
nudgeCallbacks.add(poll);
|
|
360
400
|
poll();
|
|
@@ -370,9 +410,11 @@ async function createBrowserDB(dbName, config) {
|
|
|
370
410
|
};
|
|
371
411
|
return opts ? applySchema(wrapped, opts) : wrapped;
|
|
372
412
|
}
|
|
373
|
-
|
|
413
|
+
const handle = {
|
|
374
414
|
collection: (name, opts) => wrapCollection(name, opts),
|
|
375
415
|
compact: () => proxy.send("compact"),
|
|
416
|
+
syncStatus: async () => JSON.parse(await proxy.send("syncStatus")),
|
|
417
|
+
flushSync: (timeoutMs = 5e3) => proxy.send("flushSync", { timeoutMs }),
|
|
376
418
|
close: async () => {
|
|
377
419
|
channel?.close();
|
|
378
420
|
try {
|
|
@@ -382,13 +424,21 @@ async function createBrowserDB(dbName, config) {
|
|
|
382
424
|
proxy.abort(new Error("taladb worker closed"));
|
|
383
425
|
}
|
|
384
426
|
},
|
|
385
|
-
|
|
427
|
+
// All engine work (export scan, LWW merge) runs inside the worker, off the
|
|
428
|
+
// main thread — a sync pass never blocks rendering, whatever its size.
|
|
429
|
+
exportChanges: (collections, sinceMs) => proxy.send("exportChangeset", { collectionsJson: JSON.stringify(collections), sinceMs }),
|
|
430
|
+
importChanges: (changeset) => proxy.send("importChangeset", { changesetJson: changeset }),
|
|
431
|
+
listCollectionNames: async () => JSON.parse(await proxy.send("listCollections")),
|
|
432
|
+
sync: (adapter, options) => runSync(handle, adapter, options)
|
|
386
433
|
};
|
|
434
|
+
return handle;
|
|
387
435
|
}
|
|
388
|
-
async function createNodeDB(dbName, config) {
|
|
389
|
-
const
|
|
436
|
+
async function createNodeDB(dbName, config, passphrase) {
|
|
437
|
+
const native = await import("./node-A4LKRSW5.mjs");
|
|
438
|
+
const TalaDBNode = native.TalaDbNode ?? native.TalaDBNode;
|
|
439
|
+
if (!TalaDBNode) throw new Error("@taladb/node loaded but exports no TalaDbNode class \u2014 rebuild the native module");
|
|
390
440
|
const configJson = config !== void 0 ? JSON.stringify(config) : null;
|
|
391
|
-
const db = TalaDBNode.open(dbName, configJson);
|
|
441
|
+
const db = TalaDBNode.open(dbName, configJson, passphrase ?? null);
|
|
392
442
|
function wrapCollection(name, opts) {
|
|
393
443
|
const col = db.collection(name);
|
|
394
444
|
const wrapped = {
|
|
@@ -404,6 +454,8 @@ async function createNodeDB(dbName, config) {
|
|
|
404
454
|
aggregate: async (pipeline) => col.aggregate(pipeline),
|
|
405
455
|
createIndex: async (field) => col.createIndex(field),
|
|
406
456
|
dropIndex: async (field) => col.dropIndex(field),
|
|
457
|
+
createCompoundIndex: async (fields) => col.createCompoundIndex(fields),
|
|
458
|
+
dropCompoundIndex: async (fields) => col.dropCompoundIndex(fields),
|
|
407
459
|
createFtsIndex: async (field) => col.createFtsIndex(field),
|
|
408
460
|
dropFtsIndex: async (field) => col.dropFtsIndex(field),
|
|
409
461
|
createVectorIndex: async (field, options) => col.createVectorIndex(field, options.dimensions, options.metric ?? null, options.indexType ?? null, options.hnswM ?? null, options.hnswEfConstruction ?? null),
|
|
@@ -417,7 +469,7 @@ async function createNodeDB(dbName, config) {
|
|
|
417
469
|
const raw = await col.findNearest(field, vector, topK, filter ?? null);
|
|
418
470
|
return raw;
|
|
419
471
|
},
|
|
420
|
-
subscribe: (filter, callback) => makePoller(async () => col.find(filter ?? null), callback)
|
|
472
|
+
subscribe: (filter, callback, onError) => makePoller(async () => col.find(filter ?? null), callback, onError)
|
|
421
473
|
};
|
|
422
474
|
return opts ? applySchema(wrapped, opts) : wrapped;
|
|
423
475
|
}
|
|
@@ -455,6 +507,8 @@ async function createNativeDB(_dbName) {
|
|
|
455
507
|
aggregate: async (pipeline) => native.aggregate(name, pipeline),
|
|
456
508
|
createIndex: async (field) => native.createIndex(name, field),
|
|
457
509
|
dropIndex: async (field) => native.dropIndex(name, field),
|
|
510
|
+
createCompoundIndex: async (fields) => native.createCompoundIndex(name, fields),
|
|
511
|
+
dropCompoundIndex: async (fields) => native.dropCompoundIndex(name, fields),
|
|
458
512
|
createFtsIndex: async (field) => native.createFtsIndex(name, field),
|
|
459
513
|
dropFtsIndex: async (field) => native.dropFtsIndex(name, field),
|
|
460
514
|
createVectorIndex: async (field, options) => {
|
|
@@ -474,18 +528,35 @@ async function createNativeDB(_dbName) {
|
|
|
474
528
|
const raw = native.findNearest(name, field, vector, topK, filter ?? null);
|
|
475
529
|
return raw;
|
|
476
530
|
},
|
|
477
|
-
subscribe: (filter, callback) => makePoller(async () => native.find(name, filter ?? {}), callback)
|
|
531
|
+
subscribe: (filter, callback, onError) => makePoller(async () => native.find(name, filter ?? {}), callback, onError)
|
|
478
532
|
};
|
|
479
533
|
return opts ? applySchema(wrapped, opts) : wrapped;
|
|
480
534
|
}
|
|
535
|
+
const syncSurface = typeof native.exportChanges === "function" && typeof native.importChanges === "function" && typeof native.listCollectionNames === "function" ? (() => {
|
|
536
|
+
const handle = {
|
|
537
|
+
collection: (name, opts) => wrapCollection(name, opts),
|
|
538
|
+
exportChanges: async (collections, sinceMs) => native.exportChanges(collections, sinceMs),
|
|
539
|
+
importChanges: async (changeset) => native.importChanges(changeset),
|
|
540
|
+
listCollectionNames: async () => native.listCollectionNames(),
|
|
541
|
+
sync: (adapter, options) => runSync(handle, adapter, options)
|
|
542
|
+
};
|
|
543
|
+
return {
|
|
544
|
+
exportChanges: handle.exportChanges,
|
|
545
|
+
importChanges: handle.importChanges,
|
|
546
|
+
sync: handle.sync
|
|
547
|
+
};
|
|
548
|
+
})() : unsupportedSync("react-native");
|
|
481
549
|
return {
|
|
482
550
|
collection: (name, opts) => wrapCollection(name, opts),
|
|
483
551
|
compact: async () => native.compact(),
|
|
484
552
|
close: async () => native.close(),
|
|
485
|
-
...
|
|
553
|
+
...syncSurface
|
|
486
554
|
};
|
|
487
555
|
}
|
|
488
556
|
async function openDB(dbName = "taladb.db", options) {
|
|
557
|
+
if (options?.passphrase !== void 0 && options.passphrase.length === 0) {
|
|
558
|
+
throw new Error("TalaDB encryption passphrase must not be empty");
|
|
559
|
+
}
|
|
489
560
|
let resolvedConfig;
|
|
490
561
|
if (options?.config !== void 0) {
|
|
491
562
|
validateConfig(options.config);
|
|
@@ -496,11 +567,14 @@ async function openDB(dbName = "taladb.db", options) {
|
|
|
496
567
|
const platform = detectPlatform();
|
|
497
568
|
switch (platform) {
|
|
498
569
|
case "browser":
|
|
499
|
-
return createBrowserDB(dbName, resolvedConfig);
|
|
570
|
+
return createBrowserDB(dbName, resolvedConfig, options?.passphrase);
|
|
500
571
|
case "react-native":
|
|
572
|
+
if (options?.passphrase !== void 0) {
|
|
573
|
+
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");
|
|
574
|
+
}
|
|
501
575
|
return createNativeDB(dbName);
|
|
502
576
|
case "node":
|
|
503
|
-
return createNodeDB(dbName, resolvedConfig);
|
|
577
|
+
return createNodeDB(dbName, resolvedConfig, options?.passphrase);
|
|
504
578
|
}
|
|
505
579
|
}
|
|
506
580
|
export {
|
package/dist/index.d.mts
CHANGED
|
@@ -57,6 +57,7 @@ type FieldOps<T> = T extends null | undefined ? {
|
|
|
57
57
|
$exists?: boolean;
|
|
58
58
|
/** Full-text search: matches documents where this string field contains the given token. */
|
|
59
59
|
$contains?: string;
|
|
60
|
+
$regex?: string;
|
|
60
61
|
};
|
|
61
62
|
type Filter<T extends Document = Document> = {
|
|
62
63
|
[K in keyof T]?: T[K] | FieldOps<T[K]>;
|
|
@@ -157,6 +158,21 @@ interface Collection<T extends Document = Document> {
|
|
|
157
158
|
aggregate<R extends Document = Document>(pipeline: AggregatePipeline<T>): Promise<R[]>;
|
|
158
159
|
createIndex(field: keyof Omit<T, '_id'> & string): Promise<void>;
|
|
159
160
|
dropIndex(field: keyof Omit<T, '_id'> & string): Promise<void>;
|
|
161
|
+
/**
|
|
162
|
+
* Create a compound (multi-field) index over an ordered list of fields.
|
|
163
|
+
*
|
|
164
|
+
* The query planner uses it to accelerate an `$and` where **every** field of
|
|
165
|
+
* the index is constrained by equality — e.g. an index on
|
|
166
|
+
* `['userId', 'status']` serves `find({ userId, status })` with a single
|
|
167
|
+
* index scan instead of a full-collection scan. Fields are ascending; a
|
|
168
|
+
* partial-prefix or trailing-range match is not used yet (planned).
|
|
169
|
+
*
|
|
170
|
+
* @example
|
|
171
|
+
* await orders.createCompoundIndex(['userId', 'status'])
|
|
172
|
+
*/
|
|
173
|
+
createCompoundIndex(fields: (keyof Omit<T, '_id'> & string)[]): Promise<void>;
|
|
174
|
+
/** Drop a compound index by its ordered field list. */
|
|
175
|
+
dropCompoundIndex(fields: (keyof Omit<T, '_id'> & string)[]): Promise<void>;
|
|
160
176
|
/**
|
|
161
177
|
* Create a full-text search index on a string field.
|
|
162
178
|
*
|
|
@@ -228,7 +244,7 @@ interface Collection<T extends Document = Document> {
|
|
|
228
244
|
* // later…
|
|
229
245
|
* unsub();
|
|
230
246
|
*/
|
|
231
|
-
subscribe(filter: Filter<T>, callback: (docs: T[]) => void): () => void;
|
|
247
|
+
subscribe(filter: Filter<T>, callback: (docs: T[]) => void, onError?: (error: unknown) => void): () => void;
|
|
232
248
|
}
|
|
233
249
|
/**
|
|
234
250
|
* A JSON-encoded changeset — the opaque payload exchanged between peers. Produced
|
|
@@ -269,8 +285,9 @@ interface SyncOptions {
|
|
|
269
285
|
/** Direction of the pass. Default `'both'` (bidirectional). */
|
|
270
286
|
direction?: SyncDirection;
|
|
271
287
|
/**
|
|
272
|
-
* Names this sync target
|
|
273
|
-
*
|
|
288
|
+
* Names this sync target. Reserved cursor state remains isolated per target
|
|
289
|
+
* for forward compatibility with monotonic server cursors. Default
|
|
290
|
+
* `'default'`.
|
|
274
291
|
*/
|
|
275
292
|
target?: string;
|
|
276
293
|
}
|
|
@@ -279,7 +296,7 @@ interface SyncResult {
|
|
|
279
296
|
pushed: number;
|
|
280
297
|
/** Number of documents changed locally by the pulled remote changeset. */
|
|
281
298
|
pulled: number;
|
|
282
|
-
/**
|
|
299
|
+
/** Active sync cursor. Currently `0` because timestamp adapters replay safely. */
|
|
283
300
|
cursor: number;
|
|
284
301
|
}
|
|
285
302
|
interface TalaDB {
|
|
@@ -317,6 +334,14 @@ interface TalaDB {
|
|
|
317
334
|
* await db.compact();
|
|
318
335
|
*/
|
|
319
336
|
compact(): Promise<void>;
|
|
337
|
+
/** Browser HTTP-push queue health, when supported by the active binding. */
|
|
338
|
+
syncStatus?(): Promise<{
|
|
339
|
+
pending: number;
|
|
340
|
+
dropped: number;
|
|
341
|
+
failed: number;
|
|
342
|
+
}>;
|
|
343
|
+
/** Wait for accepted browser HTTP-push events, returning false on timeout. */
|
|
344
|
+
flushSync?(timeoutMs?: number): Promise<boolean>;
|
|
320
345
|
close(): Promise<void>;
|
|
321
346
|
}
|
|
322
347
|
|
|
@@ -408,6 +433,8 @@ declare class TalaDbValidationError extends Error {
|
|
|
408
433
|
|
|
409
434
|
/** Options for `openDB`. */
|
|
410
435
|
interface OpenDBOptions {
|
|
436
|
+
/** Encrypt native database values at rest. Never hard-code this value. */
|
|
437
|
+
passphrase?: string;
|
|
411
438
|
/**
|
|
412
439
|
* Explicit path to a `taladb.config.yml` / `taladb.config.json` file.
|
|
413
440
|
* If omitted, TalaDB auto-discovers the file from `process.cwd()` on Node.js.
|
package/dist/index.d.ts
CHANGED
|
@@ -57,6 +57,7 @@ type FieldOps<T> = T extends null | undefined ? {
|
|
|
57
57
|
$exists?: boolean;
|
|
58
58
|
/** Full-text search: matches documents where this string field contains the given token. */
|
|
59
59
|
$contains?: string;
|
|
60
|
+
$regex?: string;
|
|
60
61
|
};
|
|
61
62
|
type Filter<T extends Document = Document> = {
|
|
62
63
|
[K in keyof T]?: T[K] | FieldOps<T[K]>;
|
|
@@ -157,6 +158,21 @@ interface Collection<T extends Document = Document> {
|
|
|
157
158
|
aggregate<R extends Document = Document>(pipeline: AggregatePipeline<T>): Promise<R[]>;
|
|
158
159
|
createIndex(field: keyof Omit<T, '_id'> & string): Promise<void>;
|
|
159
160
|
dropIndex(field: keyof Omit<T, '_id'> & string): Promise<void>;
|
|
161
|
+
/**
|
|
162
|
+
* Create a compound (multi-field) index over an ordered list of fields.
|
|
163
|
+
*
|
|
164
|
+
* The query planner uses it to accelerate an `$and` where **every** field of
|
|
165
|
+
* the index is constrained by equality — e.g. an index on
|
|
166
|
+
* `['userId', 'status']` serves `find({ userId, status })` with a single
|
|
167
|
+
* index scan instead of a full-collection scan. Fields are ascending; a
|
|
168
|
+
* partial-prefix or trailing-range match is not used yet (planned).
|
|
169
|
+
*
|
|
170
|
+
* @example
|
|
171
|
+
* await orders.createCompoundIndex(['userId', 'status'])
|
|
172
|
+
*/
|
|
173
|
+
createCompoundIndex(fields: (keyof Omit<T, '_id'> & string)[]): Promise<void>;
|
|
174
|
+
/** Drop a compound index by its ordered field list. */
|
|
175
|
+
dropCompoundIndex(fields: (keyof Omit<T, '_id'> & string)[]): Promise<void>;
|
|
160
176
|
/**
|
|
161
177
|
* Create a full-text search index on a string field.
|
|
162
178
|
*
|
|
@@ -228,7 +244,7 @@ interface Collection<T extends Document = Document> {
|
|
|
228
244
|
* // later…
|
|
229
245
|
* unsub();
|
|
230
246
|
*/
|
|
231
|
-
subscribe(filter: Filter<T>, callback: (docs: T[]) => void): () => void;
|
|
247
|
+
subscribe(filter: Filter<T>, callback: (docs: T[]) => void, onError?: (error: unknown) => void): () => void;
|
|
232
248
|
}
|
|
233
249
|
/**
|
|
234
250
|
* A JSON-encoded changeset — the opaque payload exchanged between peers. Produced
|
|
@@ -269,8 +285,9 @@ interface SyncOptions {
|
|
|
269
285
|
/** Direction of the pass. Default `'both'` (bidirectional). */
|
|
270
286
|
direction?: SyncDirection;
|
|
271
287
|
/**
|
|
272
|
-
* Names this sync target
|
|
273
|
-
*
|
|
288
|
+
* Names this sync target. Reserved cursor state remains isolated per target
|
|
289
|
+
* for forward compatibility with monotonic server cursors. Default
|
|
290
|
+
* `'default'`.
|
|
274
291
|
*/
|
|
275
292
|
target?: string;
|
|
276
293
|
}
|
|
@@ -279,7 +296,7 @@ interface SyncResult {
|
|
|
279
296
|
pushed: number;
|
|
280
297
|
/** Number of documents changed locally by the pulled remote changeset. */
|
|
281
298
|
pulled: number;
|
|
282
|
-
/**
|
|
299
|
+
/** Active sync cursor. Currently `0` because timestamp adapters replay safely. */
|
|
283
300
|
cursor: number;
|
|
284
301
|
}
|
|
285
302
|
interface TalaDB {
|
|
@@ -317,6 +334,14 @@ interface TalaDB {
|
|
|
317
334
|
* await db.compact();
|
|
318
335
|
*/
|
|
319
336
|
compact(): Promise<void>;
|
|
337
|
+
/** Browser HTTP-push queue health, when supported by the active binding. */
|
|
338
|
+
syncStatus?(): Promise<{
|
|
339
|
+
pending: number;
|
|
340
|
+
dropped: number;
|
|
341
|
+
failed: number;
|
|
342
|
+
}>;
|
|
343
|
+
/** Wait for accepted browser HTTP-push events, returning false on timeout. */
|
|
344
|
+
flushSync?(timeoutMs?: number): Promise<boolean>;
|
|
320
345
|
close(): Promise<void>;
|
|
321
346
|
}
|
|
322
347
|
|
|
@@ -408,6 +433,8 @@ declare class TalaDbValidationError extends Error {
|
|
|
408
433
|
|
|
409
434
|
/** Options for `openDB`. */
|
|
410
435
|
interface OpenDBOptions {
|
|
436
|
+
/** Encrypt native database values at rest. Never hard-code this value. */
|
|
437
|
+
passphrase?: string;
|
|
411
438
|
/**
|
|
412
439
|
* Explicit path to a `taladb.config.yml` / `taladb.config.json` file.
|
|
413
440
|
* If omitted, TalaDB auto-discovers the file from `process.cwd()` on Node.js.
|