mumpix 1.0.20 → 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.
- package/CHANGELOG.md +42 -14
- package/README.md +185 -8
- package/bin/mumpix.js +1 -405
- package/examples/agent-memory.js +1 -1
- package/examples/basic.js +1 -1
- package/examples/behavioral-primitives.js +50 -0
- package/examples/verified-mode.js +1 -1
- package/package.json +17 -13
- package/scripts/test-license-modes.cjs +87 -0
- package/src/brp/index.js +1 -0
- package/src/collapse/index.js +1 -0
- package/src/core/MumpixDB.js +210 -322
- package/src/core/audit.js +1 -173
- package/src/core/auth.js +1 -232
- package/src/core/inverted-index.js +144 -0
- package/src/core/license.js +1 -267
- package/src/core/ml-dsa.mjs +1 -25
- package/src/core/ml-kem.mjs +1 -32
- package/src/core/recall.js +1 -176
- package/src/core/store.js +335 -286
- package/src/core/wal-writer.js +83 -0
- package/src/index.js +20 -34
- package/src/integrations/developer-sdk.js +1 -165
- package/src/integrations/langchain-official.js +1 -0
- package/src/integrations/langchain.js +1 -131
- package/src/integrations/llamaindex-official.js +1 -0
- package/src/integrations/llamaindex.js +1 -86
- package/src/integrations/vector-sidecar.js +325 -0
- package/src/rlp/index.js +1 -0
- package/src/temporal/engine.js +1 -1894
- package/src/temporal/indexes.js +1 -178
- package/src/temporal/operators.js +1 -186
- package/scripts/postinstall-auth.js +0 -101
package/src/core/MumpixDB.js
CHANGED
|
@@ -1,329 +1,249 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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;
|
|
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 !==
|
|
58
|
-
throw new TypeError(
|
|
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 ||
|
|
62
|
-
if (!
|
|
63
|
-
throw new Error(
|
|
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
|
|
61
|
+
let audit = null;
|
|
62
|
+
|
|
68
63
|
try {
|
|
69
|
-
|
|
64
|
+
store.open(storeOpenOptions(requestedConsistency, opts));
|
|
70
65
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
77
|
-
if (resolvedLicenseKey && !licenseReady) {
|
|
78
|
-
console.warn(`[DEBUG] License rejected: ${license.validationError}`);
|
|
79
|
-
}
|
|
72
|
+
await license.init();
|
|
80
73
|
|
|
81
|
-
|
|
82
|
-
|
|
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(
|
|
77
|
+
license.checkLimit("mode", requestedConsistency);
|
|
88
78
|
license.assertActive(requestedConsistency);
|
|
89
|
-
} catch (
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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 (
|
|
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
|
-
|
|
95
|
+
store.open(storeOpenOptions(consistency, opts));
|
|
109
96
|
}
|
|
110
97
|
|
|
111
|
-
if (
|
|
112
|
-
|
|
113
|
-
|
|
98
|
+
if (consistency === "verified") {
|
|
99
|
+
audit = new MumpixAudit(filePath);
|
|
100
|
+
audit.open();
|
|
114
101
|
}
|
|
115
102
|
|
|
116
|
-
return new MumpixDB(store,
|
|
103
|
+
return new MumpixDB(store, audit, license, {
|
|
117
104
|
...opts,
|
|
118
|
-
|
|
105
|
+
telemetry: telemetryEnabled(opts),
|
|
106
|
+
consistency,
|
|
119
107
|
requestedConsistency,
|
|
120
|
-
downgradedConsistency:
|
|
121
|
-
|
|
122
|
-
|
|
108
|
+
downgradedConsistency:
|
|
109
|
+
consistency !== requestedConsistency
|
|
110
|
+
? { from: requestedConsistency, to: consistency, reason: "license_not_active" }
|
|
111
|
+
: null,
|
|
123
112
|
});
|
|
124
|
-
} catch (
|
|
125
|
-
if (
|
|
126
|
-
try {
|
|
113
|
+
} catch (err) {
|
|
114
|
+
if (audit) {
|
|
115
|
+
try {
|
|
116
|
+
audit.close();
|
|
117
|
+
} catch (_) {}
|
|
127
118
|
}
|
|
128
|
-
try {
|
|
129
|
-
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
|
160
|
-
throw new TypeError(
|
|
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
|
-
|
|
164
|
-
this.
|
|
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
|
-
|
|
170
|
-
|
|
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
|
-
|
|
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 !==
|
|
193
|
-
throw new TypeError(
|
|
179
|
+
if (typeof query !== "string" || !query.trim()) {
|
|
180
|
+
throw new TypeError("recall() requires a non-empty string");
|
|
194
181
|
}
|
|
195
182
|
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
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 !==
|
|
224
|
-
throw new TypeError(
|
|
193
|
+
if (typeof query !== "string" || !query.trim()) {
|
|
194
|
+
throw new TypeError("recallMany() requires a non-empty string");
|
|
225
195
|
}
|
|
226
196
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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(
|
|
252
|
-
id:
|
|
253
|
-
content:
|
|
254
|
-
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 (
|
|
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
|
|
221
|
+
const hits = await scoreRecallMany(opts.query, records, {
|
|
278
222
|
embedFn: this._opts.embedFn,
|
|
279
223
|
k: topK,
|
|
280
224
|
});
|
|
281
|
-
return materializeEvents(
|
|
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
|
-
|
|
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
|
|
236
|
+
const hits = await scoreRecallMany(opts.query, records, {
|
|
308
237
|
embedFn: this._opts.embedFn,
|
|
309
238
|
k: topK,
|
|
310
239
|
});
|
|
311
|
-
return materializeEventIndex(
|
|
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 !==
|
|
326
|
-
throw new TypeError(
|
|
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: [
|
|
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
|
|
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
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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(
|
|
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
|
-
|
|
418
|
-
|
|
419
|
-
|
|
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
|
-
|
|
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
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
497
|
-
return `${records.length}:${
|
|
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;
|