mumpix 1.0.19 → 1.1.3

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.
Files changed (38) hide show
  1. package/CHANGELOG.md +82 -11
  2. package/README.md +278 -8
  3. package/bin/mumpix.js +1 -405
  4. package/examples/agent-memory.js +1 -1
  5. package/examples/basic.js +1 -1
  6. package/examples/behavioral-primitives.js +50 -0
  7. package/examples/recall-quality-suite.js +156 -0
  8. package/examples/verified-mode.js +1 -1
  9. package/package.json +24 -13
  10. package/scripts/bench-semantic.cjs +76 -0
  11. package/scripts/test-license-modes.cjs +87 -0
  12. package/scripts/test-phase0.cjs +137 -0
  13. package/scripts/test-semantic.cjs +411 -0
  14. package/src/brp/index.js +1 -0
  15. package/src/collapse/index.js +1 -0
  16. package/src/core/MumpixDB.js +264 -323
  17. package/src/core/audit.js +1 -173
  18. package/src/core/auth.js +1 -232
  19. package/src/core/inverted-index.js +249 -0
  20. package/src/core/license.js +1 -267
  21. package/src/core/ml-dsa.mjs +1 -25
  22. package/src/core/ml-kem.mjs +1 -32
  23. package/src/core/recall.js +226 -112
  24. package/src/core/semantic-sidecar.js +312 -0
  25. package/src/core/store.js +365 -288
  26. package/src/core/wal-writer.js +83 -0
  27. package/src/index.js +20 -34
  28. package/src/integrations/developer-sdk.js +1 -165
  29. package/src/integrations/langchain-official.js +1 -0
  30. package/src/integrations/langchain.js +1 -131
  31. package/src/integrations/llamaindex-official.js +1 -0
  32. package/src/integrations/llamaindex.js +1 -86
  33. package/src/integrations/vector-sidecar.js +325 -0
  34. package/src/rlp/index.js +1 -0
  35. package/src/temporal/engine.js +1 -1894
  36. package/src/temporal/indexes.js +1 -178
  37. package/src/temporal/operators.js +1 -186
  38. package/scripts/postinstall-auth.js +0 -101
@@ -0,0 +1,312 @@
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+
6
+ function vectorPathForDb(dbPath) {
7
+ return `${dbPath}.vectors.json`;
8
+ }
9
+
10
+ function vectorLogPathForSnapshot(vectorPath) {
11
+ return `${vectorPath}l`;
12
+ }
13
+
14
+ function emptySidecar(embedder) {
15
+ return {
16
+ version: 1,
17
+ embedder_id: embedder.id,
18
+ dim: embedder.dim,
19
+ entries: [],
20
+ };
21
+ }
22
+
23
+ function normalizeVector(vector, dim) {
24
+ if (!vector || typeof vector.length !== "number") return null;
25
+ if (Number.isFinite(dim) && vector.length !== dim) return null;
26
+ const out = Array.from(vector, Number);
27
+ if (out.some((value) => !Number.isFinite(value))) return null;
28
+ return out;
29
+ }
30
+
31
+ function dot(left, right) {
32
+ const length = Math.min(left.length, right.length);
33
+ let score = 0;
34
+ for (let i = 0; i < length; i += 1) score += left[i] * right[i];
35
+ return score;
36
+ }
37
+
38
+ function validateEmbedder(embedder) {
39
+ if (!embedder || typeof embedder !== "object") {
40
+ throw new Error(
41
+ 'Mumpix semantic recall requires an embedder. Pass { embedder } or install optional package "mumpix-embed".'
42
+ );
43
+ }
44
+ if (typeof embedder.id !== "string" || !embedder.id.trim()) {
45
+ throw new Error("Mumpix semantic recall embedder requires a stable string id.");
46
+ }
47
+ if (!Number.isInteger(embedder.dim) || embedder.dim <= 0) {
48
+ throw new Error("Mumpix semantic recall embedder requires a positive integer dim.");
49
+ }
50
+ if (typeof embedder.embed !== "function") {
51
+ throw new Error("Mumpix semantic recall embedder requires embed(texts) => Promise<vector[]>.");
52
+ }
53
+ }
54
+
55
+ function loadDefaultEmbedder() {
56
+ try {
57
+ const mod = require("mumpix-embed");
58
+ const candidate =
59
+ typeof mod.createEmbedder === "function"
60
+ ? mod.createEmbedder()
61
+ : mod.default || mod.embedder || mod;
62
+ return candidate;
63
+ } catch (err) {
64
+ const code = err && err.code;
65
+ if (code === "MODULE_NOT_FOUND") {
66
+ throw new Error(
67
+ 'Mumpix semantic recall requires optional package "mumpix-embed". Install it with: npm i mumpix-embed'
68
+ );
69
+ }
70
+ throw err;
71
+ }
72
+ }
73
+
74
+ class SemanticSidecar {
75
+ constructor(dbPath, embedder, opts = {}) {
76
+ validateEmbedder(embedder);
77
+ this.dbPath = dbPath;
78
+ this.embedder = embedder;
79
+ this.vectorPath = opts.vectorPath || vectorPathForDb(dbPath);
80
+ this.vectorLogPath = opts.vectorLogPath || vectorLogPathForSnapshot(this.vectorPath);
81
+ this._needsPersist = false;
82
+ this._bySeq = new Map();
83
+ this.data = this._load();
84
+ if (this._needsPersist) this.persist();
85
+ }
86
+
87
+ _load() {
88
+ if (!fs.existsSync(this.vectorPath)) {
89
+ this._needsPersist = true;
90
+ const data = emptySidecar(this.embedder);
91
+ this._hydrateMap(data.entries);
92
+ this._loadJournal(data);
93
+ return data;
94
+ }
95
+ try {
96
+ const loaded = JSON.parse(fs.readFileSync(this.vectorPath, "utf8"));
97
+ if (
98
+ !loaded ||
99
+ loaded.embedder_id !== this.embedder.id ||
100
+ loaded.dim !== this.embedder.dim ||
101
+ !Array.isArray(loaded.entries)
102
+ ) {
103
+ this._needsPersist = true;
104
+ this._discardJournal();
105
+ return emptySidecar(this.embedder);
106
+ }
107
+ const data = {
108
+ ...emptySidecar(this.embedder),
109
+ entries: loaded.entries.filter(
110
+ (entry) =>
111
+ entry &&
112
+ Number.isInteger(entry.seq ?? entry.id) &&
113
+ typeof entry.content === "string" &&
114
+ Array.isArray(entry.vec) &&
115
+ entry.vec.length === this.embedder.dim
116
+ ).map((entry) => this._normalizeEntry(entry)),
117
+ };
118
+ this._hydrateMap(data.entries);
119
+ this._loadJournal(data);
120
+ this._hydrateMap(data.entries);
121
+ return data;
122
+ } catch (_) {
123
+ this._discardJournal();
124
+ const data = emptySidecar(this.embedder);
125
+ this._hydrateMap(data.entries);
126
+ return data;
127
+ }
128
+ }
129
+
130
+ persist() {
131
+ fs.mkdirSync(path.dirname(this.vectorPath), { recursive: true });
132
+ const tmp = `${this.vectorPath}.tmp.${process.pid}`;
133
+ fs.writeFileSync(tmp, JSON.stringify(this.data, null, 2), "utf8");
134
+ fs.renameSync(tmp, this.vectorPath);
135
+ }
136
+
137
+ compact() {
138
+ this.persist();
139
+ this._discardJournal();
140
+ }
141
+
142
+ syncRecords(records) {
143
+ const bySeq = new Map(records.map((record) => [record.id, record]));
144
+ const before = this.data.entries.length;
145
+ this.data.entries = this.data.entries.filter((entry) => {
146
+ const record = bySeq.get(entry.seq);
147
+ return record && record.content === entry.content && record.ts === entry.ts;
148
+ });
149
+ this._hydrateMap(this.data.entries);
150
+ if (this.data.entries.length !== before) this.compact();
151
+ }
152
+
153
+ async reconcileRecords(records) {
154
+ const bySeq = new Map(records.map((record) => [record.id, record]));
155
+ const before = this.data.entries.length;
156
+ this.data.entries = this.data.entries.filter((entry) => {
157
+ const record = bySeq.get(entry.seq);
158
+ return record && record.content === entry.content && record.ts === entry.ts;
159
+ });
160
+ this._hydrateMap(this.data.entries);
161
+
162
+ const missing = records.filter((record) => !this._bySeq.has(record.id));
163
+ if (!missing.length) {
164
+ if (this.data.entries.length !== before) this.compact();
165
+ return { pruned: before - this.data.entries.length, embedded: 0 };
166
+ }
167
+
168
+ const vectors = await this.embedder.embed(missing.map((record) => record.content));
169
+ if (!Array.isArray(vectors) || vectors.length !== missing.length) {
170
+ throw new Error("Mumpix semantic recall embedder returned the wrong number of vectors.");
171
+ }
172
+
173
+ for (let i = 0; i < missing.length; i += 1) {
174
+ const record = missing[i];
175
+ const vec = normalizeVector(vectors[i], this.embedder.dim);
176
+ if (!vec) throw new Error("Mumpix semantic recall embedder returned an invalid vector.");
177
+ this._upsert({
178
+ seq: record.id,
179
+ record_id: record.id,
180
+ content: record.content,
181
+ ts: record.ts,
182
+ vec,
183
+ });
184
+ }
185
+
186
+ this.compact();
187
+ return { pruned: before - (this.data.entries.length - missing.length), embedded: missing.length };
188
+ }
189
+
190
+ async addRecord(record) {
191
+ const [vector] = await this.embedder.embed([record.content]);
192
+ const vec = normalizeVector(vector, this.embedder.dim);
193
+ if (!vec) throw new Error("Mumpix semantic recall embedder returned an invalid vector.");
194
+
195
+ const entry = {
196
+ seq: record.id,
197
+ id: record.id,
198
+ record_id: record.id,
199
+ content: record.content,
200
+ ts: record.ts,
201
+ vec,
202
+ };
203
+
204
+ this._upsert(entry);
205
+ this._appendJournal({ op: "upsert", entry });
206
+ }
207
+
208
+ clear() {
209
+ this.data.entries = [];
210
+ this._bySeq.clear();
211
+ this.compact();
212
+ }
213
+
214
+ async query(text, k, floor) {
215
+ if (!this.data.entries.length) return [];
216
+ const [queryVector] = await this.embedder.embed([text]);
217
+ const qVec = normalizeVector(queryVector, this.embedder.dim);
218
+ if (!qVec) throw new Error("Mumpix semantic recall embedder returned an invalid query vector.");
219
+
220
+ return this.data.entries
221
+ .map((entry) => ({ ...entry, _score: dot(qVec, entry.vec) }))
222
+ .filter((entry) => entry._score >= floor)
223
+ .sort((left, right) => {
224
+ if (right._score !== left._score) return right._score - left._score;
225
+ if (right.ts !== left.ts) return right.ts - left.ts;
226
+ return left.id - right.id;
227
+ })
228
+ .slice(0, k);
229
+ }
230
+
231
+ _normalizeEntry(entry) {
232
+ const seq = entry.seq ?? entry.id;
233
+ return {
234
+ seq,
235
+ id: seq,
236
+ record_id: entry.record_id ?? seq,
237
+ content: entry.content,
238
+ ts: entry.ts,
239
+ vec: entry.vec,
240
+ };
241
+ }
242
+
243
+ _hydrateMap(entries) {
244
+ this._bySeq.clear();
245
+ for (const entry of entries) this._bySeq.set(entry.seq, entry);
246
+ }
247
+
248
+ _upsert(entry) {
249
+ const normalized = this._normalizeEntry(entry);
250
+ const existing = this._bySeq.get(normalized.seq);
251
+ if (existing) {
252
+ Object.assign(existing, normalized);
253
+ return;
254
+ }
255
+ this._bySeq.set(normalized.seq, normalized);
256
+ this.data.entries.push(normalized);
257
+ }
258
+
259
+ _loadJournal(data) {
260
+ if (!fs.existsSync(this.vectorLogPath)) return;
261
+ const bySeq = new Map(data.entries.map((entry) => [entry.seq, entry]));
262
+ try {
263
+ const lines = fs.readFileSync(this.vectorLogPath, "utf8").split("\n");
264
+ for (const line of lines) {
265
+ if (!line.trim()) continue;
266
+ const event = JSON.parse(line);
267
+ if (event.op === "clear") {
268
+ data.entries = [];
269
+ bySeq.clear();
270
+ } else if (event.op === "upsert" && event.entry) {
271
+ const entry = this._normalizeEntry(event.entry);
272
+ if (
273
+ Number.isInteger(entry.seq) &&
274
+ typeof entry.content === "string" &&
275
+ Array.isArray(entry.vec) &&
276
+ entry.vec.length === this.embedder.dim
277
+ ) {
278
+ const existing = bySeq.get(entry.seq);
279
+ if (existing) {
280
+ Object.assign(existing, entry);
281
+ } else {
282
+ bySeq.set(entry.seq, entry);
283
+ data.entries.push(entry);
284
+ }
285
+ }
286
+ }
287
+ }
288
+ } catch (_) {
289
+ this._discardJournal();
290
+ data.entries = [...bySeq.values()];
291
+ }
292
+ }
293
+
294
+ _appendJournal(event) {
295
+ fs.mkdirSync(path.dirname(this.vectorLogPath), { recursive: true });
296
+ fs.appendFileSync(this.vectorLogPath, `${JSON.stringify(event)}\n`, "utf8");
297
+ }
298
+
299
+ _discardJournal() {
300
+ try {
301
+ fs.unlinkSync(this.vectorLogPath);
302
+ } catch (_) {}
303
+ }
304
+ }
305
+
306
+ module.exports = {
307
+ SemanticSidecar,
308
+ loadDefaultEmbedder,
309
+ validateEmbedder,
310
+ vectorLogPathForSnapshot,
311
+ vectorPathForDb,
312
+ };