mumpix 1.0.19 → 1.0.29

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.
@@ -1,329 +1,249 @@
1
- 'use strict';
2
-
3
- /**
4
- * MumpixDB main database class
5
- *
6
- * const { Mumpix } = require('mumpix')
7
- * const db = await Mumpix.open('./agent.mumpix', { consistency: 'strict' })
8
- *
9
- * await db.remember('User prefers TypeScript')
10
- * const ans = await db.recall('what language?')
11
- * const all = await db.list()
12
- * await db.clear()
13
- * await db.close()
14
- */
15
-
16
- const { MumpixStore } = require('./store');
17
- const { MumpixAudit } = require('./audit');
18
- const { recall, recallMany } = require('./recall');
19
- const { LicenseManager } = require('./license');
20
- const { getStoredLicenseKey } = require('./auth');
21
- const { answerTemporalQuery, materializeEvents, materializeEventIndex, extractEventsFromRecord } = require('../temporal/engine');
22
- const { appendEventIndex } = require('../temporal/indexes');
23
-
24
- const VALID_MODES = ['eventual', 'strict', 'verified'];
1
+ "use strict";
2
+
3
+ const { MumpixStore } = require("./store");
4
+ const { MumpixAudit } = require("./audit");
5
+ const { recall, recallMany: scoreRecallMany } = require("./recall");
6
+ const { LicenseManager } = require("./license");
7
+ const { getStoredLicenseKey } = require("./auth");
8
+ const {
9
+ answerTemporalQuery,
10
+ materializeEvents,
11
+ materializeEventIndex,
12
+ extractEventsFromRecord,
13
+ } = require("../temporal/engine");
14
+ const { appendEventIndex } = require("../temporal/indexes");
15
+
16
+ const VALID_CONSISTENCY = ["eventual", "strict", "verified"];
17
+
18
+ function envFlag(name) {
19
+ const value = process.env[name];
20
+ return value != null && value !== "" && /^(1|true|yes|on)$/i.test(String(value).trim());
21
+ }
22
+
23
+ function telemetryEnabled(opts = {}) {
24
+ return typeof opts.telemetry === "boolean" ? opts.telemetry : envFlag("MUMPIX_TELEMETRY");
25
+ }
26
+
27
+ function storeOpenOptions(consistency, opts) {
28
+ return {
29
+ consistency,
30
+ lock: opts.lock,
31
+ lockStaleMs: opts.lockStaleMs,
32
+ walFlushIntervalMs: opts.walFlushIntervalMs,
33
+ walMaxPendingBytes: opts.walMaxPendingBytes,
34
+ recallIndex: opts.recallIndex,
35
+ };
36
+ }
25
37
 
26
38
  class MumpixDB {
27
- /**
28
- * @param {MumpixStore} store
29
- * @param {MumpixAudit|null} audit
30
- * @param {LicenseManager} license
31
- * @param {object} opts
32
- */
33
39
  constructor(store, audit, license, opts = {}) {
34
40
  this._store = store;
35
- this._audit = audit; // non-null only in 'verified' mode
41
+ this._audit = audit;
36
42
  this._opts = opts;
37
43
  this._closed = false;
38
44
  this._license = license;
39
45
  this._temporalCache = null;
40
46
  }
41
47
 
42
- // ─────────────────────────────────────────
43
- // Factory
44
- // ─────────────────────────────────────────
45
-
46
- /**
47
- * Open (or create) a Mumpix database.
48
- *
49
- * @param {string} filePath Path to .mumpix file
50
- * @param {object} [opts]
51
- * @param {string} [opts.consistency] 'eventual' | 'strict' | 'verified' (default: 'eventual')
52
- * @param {Function} [opts.embedFn] async (texts: string[]) => number[][] — optional custom embeddings
53
- * @param {string} [opts.licenseKey] Commercial license key for Pro/Enterprise features
54
- * @returns {Promise<MumpixDB>}
55
- */
56
48
  static async open(filePath, opts = {}) {
57
- if (!filePath || typeof filePath !== 'string') {
58
- throw new TypeError('Mumpix.open() requires a file path string');
49
+ if (!filePath || typeof filePath !== "string") {
50
+ throw new TypeError("Mumpix.open() requires a file path string");
59
51
  }
60
52
 
61
- const requestedConsistency = opts.consistency || 'eventual';
62
- if (!VALID_MODES.includes(requestedConsistency)) {
63
- throw new Error(`Invalid consistency mode "${requestedConsistency}". Valid: ${VALID_MODES.join(', ')}`);
53
+ const requestedConsistency = opts.consistency || "eventual";
54
+ if (!VALID_CONSISTENCY.includes(requestedConsistency)) {
55
+ throw new Error(
56
+ `Invalid consistency mode "${requestedConsistency}". Valid: ${VALID_CONSISTENCY.join(", ")}`
57
+ );
64
58
  }
65
59
 
66
60
  const store = new MumpixStore(filePath);
67
- let auditLog = null;
61
+ let audit = null;
62
+
68
63
  try {
69
- await store.open({ consistency: requestedConsistency });
64
+ store.open(storeOpenOptions(requestedConsistency, opts));
70
65
 
71
- // License initialization & check.
72
- // Allow dependency injection for local verification and integration tests.
73
- const resolvedLicenseKey = opts.licenseKey || getStoredLicenseKey() || null;
74
- const license = new LicenseManager(resolvedLicenseKey);
66
+ const licenseKey = opts.licenseKey || getStoredLicenseKey() || null;
67
+ const license =
68
+ opts.licenseManager instanceof LicenseManager
69
+ ? opts.licenseManager
70
+ : new LicenseManager(licenseKey);
75
71
  license.setFileContext(store.header && store.header.fileId ? store.header.fileId : null);
76
- const licenseReady = await license.init();
77
- if (resolvedLicenseKey && !licenseReady) {
78
- console.warn(`[DEBUG] License rejected: ${license.validationError}`);
79
- }
72
+ await license.init();
80
73
 
81
- // Capability-gated mode check with graceful downgrade policy:
82
- // if a paid mode (strict/verified) is requested with expired/invalid license,
83
- // force eventual mode until the license is renewed.
84
- let effectiveConsistency = requestedConsistency;
85
- if (requestedConsistency !== 'eventual') {
74
+ let consistency = requestedConsistency;
75
+ if (requestedConsistency !== "eventual") {
86
76
  try {
87
- license.checkLimit('mode', requestedConsistency);
77
+ license.checkLimit("mode", requestedConsistency);
88
78
  license.assertActive(requestedConsistency);
89
- } catch (modeErr) {
90
- const msg = String(modeErr && modeErr.message ? modeErr.message : modeErr).toLowerCase();
91
- console.warn(`[DEBUG] Mode check failed: ${msg}`);
92
- const downgradeable =
93
- msg.includes('expired') ||
94
- msg.includes('invalid for mode') ||
95
- msg.includes('license_validation_failed') ||
96
- msg.includes('quantum signature verification failed') ||
97
- msg.includes('lease window') ||
98
- msg.includes('too far in the future');
99
- if (!downgradeable) throw modeErr;
100
- effectiveConsistency = 'eventual';
79
+ } catch (err) {
80
+ const message = String((err && err.message) || err).toLowerCase();
81
+ const canDowngrade =
82
+ message.includes("expired") ||
83
+ message.includes("invalid for mode") ||
84
+ message.includes("license_validation_failed") ||
85
+ message.includes("quantum signature verification failed") ||
86
+ message.includes("lease window") ||
87
+ message.includes("too far in the future");
88
+ if (!canDowngrade) throw err;
89
+ consistency = "eventual";
101
90
  }
102
91
  }
103
92
 
104
- if (effectiveConsistency !== requestedConsistency) {
105
- console.warn(`[DEBUG] Downgrading consistency to ${effectiveConsistency}`);
106
- // Re-open with downgraded mode after releasing current FD/lock first.
93
+ if (consistency !== requestedConsistency) {
107
94
  store.close();
108
- await store.open({ consistency: effectiveConsistency });
95
+ store.open(storeOpenOptions(consistency, opts));
109
96
  }
110
97
 
111
- if (effectiveConsistency === 'verified') {
112
- auditLog = new MumpixAudit(filePath);
113
- auditLog.open();
98
+ if (consistency === "verified") {
99
+ audit = new MumpixAudit(filePath);
100
+ audit.open();
114
101
  }
115
102
 
116
- return new MumpixDB(store, auditLog, license, {
103
+ return new MumpixDB(store, audit, license, {
117
104
  ...opts,
118
- consistency: effectiveConsistency,
105
+ telemetry: telemetryEnabled(opts),
106
+ consistency,
119
107
  requestedConsistency,
120
- downgradedConsistency: effectiveConsistency !== requestedConsistency
121
- ? { from: requestedConsistency, to: effectiveConsistency, reason: 'license_not_active' }
122
- : null,
108
+ downgradedConsistency:
109
+ consistency !== requestedConsistency
110
+ ? { from: requestedConsistency, to: consistency, reason: "license_not_active" }
111
+ : null,
123
112
  });
124
- } catch (error) {
125
- if (auditLog) {
126
- try { auditLog.close(); } catch (_) {}
113
+ } catch (err) {
114
+ if (audit) {
115
+ try {
116
+ audit.close();
117
+ } catch (_) {}
127
118
  }
128
- try { store.close(); } catch (_) {}
129
- throw error;
119
+ try {
120
+ store.close();
121
+ } catch (_) {}
122
+ throw err;
130
123
  }
131
124
  }
132
125
 
133
- // ─────────────────────────────────────────
134
- // Core API
135
- // ─────────────────────────────────────────
136
-
137
- /**
138
- * Store a memory.
139
- *
140
- * @param {string} content
141
- * @returns {{ id: number, written: boolean, consistency: string }}
142
- */
143
- async remember(content, opts = {}) {
126
+ async remember(input, meta = {}) {
144
127
  this._assertOpen();
145
- let text = content;
146
- let meta = opts;
147
128
 
148
- if (content && typeof content === 'object' && !Array.isArray(content)) {
149
- text = content.content;
150
- meta = {
151
- ...opts,
152
- workspace: content.workspace ?? opts.workspace,
153
- repo: content.repo ?? opts.repo,
154
- source: content.source ?? opts.source,
155
- ts: content.ts ?? opts.ts,
129
+ let content = input;
130
+ let cleanMeta = meta;
131
+ if (input && typeof input === "object" && !Array.isArray(input)) {
132
+ content = input.content;
133
+ cleanMeta = {
134
+ ...meta,
135
+ workspace: input.workspace ?? meta.workspace,
136
+ repo: input.repo ?? meta.repo,
137
+ source: input.source ?? meta.source,
138
+ ts: input.ts ?? meta.ts,
156
139
  };
157
140
  }
158
141
 
159
- if (typeof text !== 'string' || !text.trim()) {
160
- throw new TypeError('remember() requires a non-empty string or { content } object');
142
+ if (typeof content !== "string" || !content.trim()) {
143
+ throw new TypeError("remember() requires a non-empty string or { content } object");
161
144
  }
162
145
 
163
- // License Check for Record Limit
164
- this._license.checkLimit('records', this._store.records.length);
165
-
166
- const record = this._store.write(text, meta);
146
+ this._license.checkLimit("records", this._store.records.length);
147
+ const record = this._store.write(content, cleanMeta);
167
148
  this._appendTemporalRecord(record);
149
+ if (this._audit) this._audit.logWrite(record);
150
+ return { written: true, id: record.id, consistency: this._store.header.consistency };
151
+ }
168
152
 
169
- if (this._audit) {
170
- this._audit.logWrite(record);
171
- }
153
+ async rememberAll(items) {
154
+ this._assertOpen();
155
+ if (!Array.isArray(items)) throw new TypeError("rememberAll() expects an array");
156
+ this._license.checkLimit("records", this._store.records.length + items.length);
172
157
 
173
- return {
158
+ const normalized = items.map((item) => {
159
+ if (item && typeof item === "object" && !Array.isArray(item)) {
160
+ return item;
161
+ }
162
+ return { content: item };
163
+ });
164
+
165
+ const records = this._store.writeBatch(normalized);
166
+ for (const record of records) {
167
+ this._appendTemporalRecord(record);
168
+ if (this._audit) this._audit.logWrite(record);
169
+ }
170
+ return records.map((record) => ({
174
171
  written: true,
175
172
  id: record.id,
176
173
  consistency: this._store.header.consistency,
177
- };
174
+ }));
178
175
  }
179
176
 
180
- /**
181
- * Recall the most semantically relevant memory for a query.
182
- *
183
- * @param {string} query
184
- * @param {object} [opts]
185
- * @param {object} [opts.filter] Pre-filter records: fn(record) → bool
186
- * @param {number} [opts.since] Only consider records newer than this timestamp
187
- * @param {string} [opts.mode] 'hybrid' | 'exact' | 'semantic'
188
- * @returns {string|null}
189
- */
190
177
  async recall(query, opts = {}) {
191
178
  this._assertOpen();
192
- if (typeof query !== 'string' || !query.trim()) {
193
- throw new TypeError('recall() requires a non-empty string');
179
+ if (typeof query !== "string" || !query.trim()) {
180
+ throw new TypeError("recall() requires a non-empty string");
194
181
  }
195
182
 
196
- const records = this._store.all();
197
- if (!records.length) return null;
198
-
199
- const t0 = Date.now();
200
- const result = await recall(query, records, {
201
- embedFn: this._opts.embedFn,
202
- ...opts,
203
- });
204
- const latencyMs = Date.now() - t0;
205
-
206
- if (this._audit) {
207
- this._audit.logRecall(query, result, latencyMs);
208
- }
209
-
210
- return result ? result.content : null;
183
+ const start = Date.now();
184
+ const hits = await this._rank(query, 1, opts);
185
+ const hit = hits[0] || null;
186
+ const latency = Date.now() - start;
187
+ if (this._audit) this._audit.logRecall(query, hit, latency);
188
+ return hit ? hit.content : null;
211
189
  }
212
190
 
213
- /**
214
- * Return the top-k most relevant memories.
215
- *
216
- * @param {string} query
217
- * @param {number} [k=5]
218
- * @param {object} [opts] Same opts as recall()
219
- * @returns {Array<{ id, content, ts, score }>}
220
- */
221
191
  async recallMany(query, k = 5, opts = {}) {
222
192
  this._assertOpen();
223
- if (typeof query !== 'string' || !query.trim()) {
224
- throw new TypeError('recallMany() requires a non-empty string');
193
+ if (typeof query !== "string" || !query.trim()) {
194
+ throw new TypeError("recallMany() requires a non-empty string");
225
195
  }
226
196
 
227
- const records = this._store.all();
228
- if (!records.length) return [];
229
-
230
- const results = await recallMany(query, records, {
231
- embedFn: this._opts.embedFn,
232
- k,
233
- ...opts,
234
- });
235
-
236
- return results.map(r => ({
237
- id: r.id,
238
- content: r.content,
239
- ts: r.ts,
240
- score: r._score,
197
+ return (await this._rank(query, k, opts)).map((record) => ({
198
+ id: record.id,
199
+ content: record.content,
200
+ ts: record.ts,
201
+ score: record._score,
241
202
  }));
242
203
  }
243
204
 
244
- /**
245
- * List all stored memories.
246
- *
247
- * @returns {Array<{ id, content, ts }>}
248
- */
249
205
  async list() {
250
206
  this._assertOpen();
251
- return this._store.all().map(r => ({
252
- id: r.id,
253
- content: r.content,
254
- ts: r.ts,
207
+ return this._store.all().map((record) => ({
208
+ id: record.id,
209
+ content: record.content,
210
+ ts: record.ts,
255
211
  }));
256
212
  }
257
213
 
258
- /**
259
- * Materialize normalized temporal events from WAL-backed records.
260
- *
261
- * @param {object} [opts]
262
- * @param {string} [opts.query] Optional query to shortlist candidate records first
263
- * @param {number} [opts.topK=50]
264
- * @returns {Array<object>}
265
- */
266
214
  async events(opts = {}) {
267
215
  this._assertOpen();
268
216
  const records = this._store.all();
269
217
  if (!records.length) return [];
270
-
271
- if (!opts.query) {
272
- return this._getTemporalCache().events;
273
- }
274
-
275
- if (opts.query && typeof opts.query === 'string' && opts.query.trim()) {
218
+ if (!opts.query) return this._getTemporalCache().events;
219
+ if (opts.query && typeof opts.query === "string" && opts.query.trim()) {
276
220
  const topK = Number.isFinite(opts.topK) ? Number(opts.topK) : 50;
277
- const shortlisted = await recallMany(opts.query, records, {
221
+ const hits = await scoreRecallMany(opts.query, records, {
278
222
  embedFn: this._opts.embedFn,
279
223
  k: topK,
280
224
  });
281
- return materializeEvents(shortlisted);
225
+ return materializeEvents(hits);
282
226
  }
283
-
284
227
  return this._getTemporalCache().events;
285
228
  }
286
229
 
287
- /**
288
- * Return derived event indexes built from WAL-backed records.
289
- *
290
- * @param {object} [opts]
291
- * @param {string} [opts.query]
292
- * @param {number} [opts.topK=50]
293
- * @returns {object}
294
- */
295
230
  async eventIndex(opts = {}) {
296
231
  this._assertOpen();
297
232
  const records = this._store.all();
298
- if (!records.length) {
299
- return materializeEventIndex([]);
300
- }
301
-
302
- if (!opts.query) {
303
- return this._getTemporalCache();
304
- }
305
-
233
+ if (!records.length) return materializeEventIndex([]);
234
+ if (!opts.query) return this._getTemporalCache();
306
235
  const topK = Number.isFinite(opts.topK) ? Number(opts.topK) : 50;
307
- const shortlisted = await recallMany(opts.query, records, {
236
+ const hits = await scoreRecallMany(opts.query, records, {
308
237
  embedFn: this._opts.embedFn,
309
238
  k: topK,
310
239
  });
311
- return materializeEventIndex(shortlisted);
240
+ return materializeEventIndex(hits);
312
241
  }
313
242
 
314
- /**
315
- * Run a deterministic temporal query over WAL-backed records.
316
- *
317
- * @param {string} query
318
- * @param {object} [opts]
319
- * @param {number} [opts.topK=50]
320
- * @param {Date|string|number} [opts.now]
321
- * @returns {object}
322
- */
323
243
  async temporalQuery(query, opts = {}) {
324
244
  this._assertOpen();
325
- if (typeof query !== 'string' || !query.trim()) {
326
- throw new TypeError('temporalQuery() requires a non-empty string');
245
+ if (typeof query !== "string" || !query.trim()) {
246
+ throw new TypeError("temporalQuery() requires a non-empty string");
327
247
  }
328
248
 
329
249
  const records = this._store.all();
@@ -333,157 +253,126 @@ class MumpixDB {
333
253
  kind: null,
334
254
  value: null,
335
255
  evidence: [],
336
- warnings: ['No records available.'],
256
+ warnings: ["No records available."],
337
257
  parsed_query: null,
338
258
  events: [],
339
259
  };
340
260
  }
341
261
 
342
262
  const topK = Number.isFinite(opts.topK) ? Number(opts.topK) : 50;
343
- const shortlisted = await recallMany(query, records, {
263
+ const hits = await scoreRecallMany(query, records, {
344
264
  embedFn: this._opts.embedFn,
345
265
  k: topK,
346
266
  });
347
-
348
267
  const now =
349
- opts.now instanceof Date ? opts.now :
350
- (typeof opts.now === 'string' || typeof opts.now === 'number') ? new Date(opts.now) :
351
- new Date();
352
-
353
- return answerTemporalQuery(query, shortlisted, { now });
268
+ opts.now instanceof Date
269
+ ? opts.now
270
+ : typeof opts.now === "string" || typeof opts.now === "number"
271
+ ? new Date(opts.now)
272
+ : new Date();
273
+ return answerTemporalQuery(query, hits, { now });
354
274
  }
355
275
 
356
- /**
357
- * Delete all memories.
358
- *
359
- * @returns {{ cleared: boolean, count: number }}
360
- */
361
276
  async clear() {
362
277
  this._assertOpen();
363
278
  const count = this._store.clear();
364
279
  this._invalidateTemporalCache();
365
-
366
- if (this._audit) {
367
- this._audit.logClear(count);
368
- }
369
-
280
+ if (this._audit) this._audit.logClear(count);
370
281
  return { cleared: true, count };
371
282
  }
372
283
 
373
- /**
374
- * Get audit log entries (Verified mode only).
375
- *
376
- * @returns {Array<object>}
377
- */
378
284
  async audit() {
379
285
  this._assertOpen();
380
286
  if (!this._audit) {
381
- throw new Error('audit() requires consistency: "verified". Current mode: ' + this._store.header.consistency);
287
+ throw new Error(
288
+ `audit() requires consistency: "verified". Current mode: ${this._store.header.consistency}`
289
+ );
382
290
  }
383
291
  return this._audit.all();
384
292
  }
385
293
 
386
- /**
387
- * Get audit log summary (Verified mode only).
388
- */
389
294
  async auditSummary() {
390
295
  this._assertOpen();
391
- if (!this._audit) {
392
- throw new Error('auditSummary() requires consistency: "verified"');
393
- }
296
+ if (!this._audit) throw new Error('auditSummary() requires consistency: "verified"');
394
297
  return this._audit.summary();
395
298
  }
396
299
 
397
- /**
398
- * Export audit log as NDJSON string (Verified mode only).
399
- */
400
300
  async exportAudit() {
401
301
  this._assertOpen();
402
- if (!this._audit) {
403
- throw new Error('exportAudit() requires consistency: "verified"');
404
- }
302
+ if (!this._audit) throw new Error('exportAudit() requires consistency: "verified"');
405
303
  return this._audit.export();
406
304
  }
407
305
 
408
- /**
409
- * Database metadata and stats.
410
- */
411
306
  async stats() {
412
307
  this._assertOpen();
413
308
  return this._store.stats();
414
309
  }
415
310
 
416
- /**
417
- * Close the database and release file handles.
418
- */
419
- async close() {
420
- if (this._closed) return;
421
-
422
- // Sync usage metrics in background on close
423
- this._license.syncUsage(this._store.stats()).catch(() => { });
424
-
425
- this._store.close();
426
- if (this._audit) this._audit.close();
427
- this._closed = true;
311
+ async flush() {
312
+ this._assertOpen();
313
+ this._store.flush();
314
+ return { flushed: true };
428
315
  }
429
316
 
430
- // ─────────────────────────────────────────
431
- // Convenience / fluent helpers
432
- // ─────────────────────────────────────────
317
+ async rebuildIndex() {
318
+ this._assertOpen();
319
+ return this._store.rebuildIndex();
320
+ }
433
321
 
434
- /**
435
- * Add multiple memories at once.
436
- *
437
- * @param {string[]} items
438
- */
439
- async rememberAll(items) {
322
+ async indexStats() {
440
323
  this._assertOpen();
441
- if (!Array.isArray(items)) throw new TypeError('rememberAll() expects an array');
442
- const results = [];
443
- for (const item of items) {
444
- results.push(await this.remember(item));
324
+ return this._store.indexStats();
325
+ }
326
+
327
+ async close() {
328
+ if (this._closed) return;
329
+ try {
330
+ if (this._opts.telemetry && this._license.hasVerifiedLicense()) {
331
+ await this._license.syncUsage(this._store.stats());
332
+ }
333
+ } finally {
334
+ this._store.close();
335
+ if (this._audit) this._audit.close();
336
+ this._closed = true;
445
337
  }
446
- return results;
447
338
  }
448
339
 
449
- /**
450
- * Check if any memory semantically matches a query.
451
- *
452
- * @param {string} query
453
- * @returns {boolean}
454
- */
455
340
  async has(query) {
456
- const result = await this.recall(query);
457
- return result !== null;
341
+ return (await this.recall(query)) !== null;
458
342
  }
459
343
 
460
- /**
461
- * Return the number of stored memories.
462
- */
463
344
  get size() {
464
345
  return this._store.records.length;
465
346
  }
466
347
 
467
- /**
468
- * Consistency mode of this instance.
469
- */
470
348
  get consistency() {
471
349
  return this._store.header.consistency;
472
350
  }
473
351
 
474
- /**
475
- * File path of the underlying .mumpix file.
476
- */
477
352
  get path() {
478
353
  return this._store.filePath;
479
354
  }
480
355
 
481
- // ─────────────────────────────────────────
482
- // Private
483
- // ─────────────────────────────────────────
356
+ async _rank(query, k, opts = {}) {
357
+ const allRecords = this._store.all();
358
+ if (!allRecords.length) return [];
359
+
360
+ let candidates = null;
361
+ if (opts.recallIndex !== false && typeof this._store.indexedCandidates === "function") {
362
+ candidates = this._store.indexedCandidates(query, opts.candidateCap || 400);
363
+ }
364
+
365
+ const records = candidates && candidates.length ? candidates : allRecords;
366
+ const ranked = await scoreRecallMany(query, records, {
367
+ embedFn: this._opts.embedFn,
368
+ k,
369
+ ...opts,
370
+ });
371
+ return ranked.filter((record) => this._store._verifyHash(record));
372
+ }
484
373
 
485
374
  _assertOpen() {
486
- if (this._closed) throw new Error('Database is closed. Call Mumpix.open() again.');
375
+ if (this._closed) throw new Error("Database is closed. Call Mumpix.open() again.");
487
376
  this._license.assertActive(this._store.header.consistency);
488
377
  }
489
378
 
@@ -493,8 +382,8 @@ class MumpixDB {
493
382
 
494
383
  _temporalSignature() {
495
384
  const records = this._store.records || [];
496
- const last = records.length ? records[records.length - 1] : null;
497
- return `${records.length}:${last ? last.id : 0}:${last ? last.ts : 0}`;
385
+ const tail = records.length ? records[records.length - 1] : null;
386
+ return `${records.length}:${tail ? tail.id : 0}:${tail ? tail.ts : 0}`;
498
387
  }
499
388
 
500
389
  _getTemporalCache() {
@@ -502,7 +391,6 @@ class MumpixDB {
502
391
  if (this._temporalCache && this._temporalCache.signature === signature) {
503
392
  return this._temporalCache.index;
504
393
  }
505
-
506
394
  const index = materializeEventIndex(this._store.all());
507
395
  this._temporalCache = { signature, index };
508
396
  return index;