mnemocyte 0.3.0 → 0.5.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.mjs CHANGED
@@ -1,1088 +1,11 @@
1
- import { n as isMnemocyteError, t as MnemocyteError } from "./errors-B_7ZdjEO.mjs";
2
- import { drizzle } from "drizzle-orm/postgres-js";
3
- import postgres from "postgres";
4
- import { and, count, countDistinct, desc, eq, gt, inArray, isNotNull, isNull, lt, or, sql } from "drizzle-orm";
5
- import { customType, index, integer, jsonb, pgTable, real, text, timestamp } from "drizzle-orm/pg-core";
6
- //#region \0rolldown/runtime.js
7
- var __defProp = Object.defineProperty;
8
- var __exportAll = (all, no_symbols) => {
9
- let target = {};
10
- for (var name in all) __defProp(target, name, {
11
- get: all[name],
12
- enumerable: true
13
- });
14
- if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
15
- return target;
16
- };
17
- //#endregion
18
- //#region src/db/vector.ts
19
- function formatVectorComponent(value) {
20
- if (!Number.isFinite(value)) throw new Error("Vector values must be finite numbers.");
21
- return Object.is(value, -0) ? "0" : value.toFixed(17);
22
- }
23
- //#endregion
24
- //#region src/db/schema.ts
25
- var schema_exports = /* @__PURE__ */ __exportAll({
26
- eventsTable: () => eventsTable,
27
- memoriesTable: () => memoriesTable,
28
- metaTable: () => metaTable
29
- });
30
- const vector = customType({
31
- dataType(config) {
32
- return `vector(${config?.dimensions ?? 1536})`;
33
- },
34
- toDriver(value) {
35
- return `[${value.map(formatVectorComponent).join(",")}]`;
36
- },
37
- fromDriver(value) {
38
- return value.slice(1, -1).split(",").filter(Boolean).map(Number);
39
- }
40
- });
41
- const memoriesTable = pgTable("mnemocyte_memories", {
42
- id: text("id").primaryKey(),
43
- entityId: text("entity_id").notNull(),
44
- content: text("content").notNull(),
45
- type: text("type").notNull().default("fact"),
46
- importance: text("importance").notNull().default("normal"),
47
- tags: text("tags").array().notNull().default(sql`'{}'::text[]`),
48
- source: text("source"),
49
- metadata: jsonb("metadata").$type().notNull().default({}),
50
- confidence: real("confidence").notNull().default(1),
51
- embedding: vector("embedding", { dimensions: 1536 }),
52
- embeddingModel: text("embedding_model").notNull(),
53
- embeddingDimensions: integer("embedding_dimensions").notNull(),
54
- supersededBy: text("superseded_by").references(() => memoriesTable.id),
55
- supersededAt: timestamp("superseded_at", { withTimezone: true }),
56
- expiresAt: timestamp("expires_at", { withTimezone: true }),
57
- lastAccessedAt: timestamp("last_accessed_at", { withTimezone: true }),
58
- accessCount: integer("access_count").notNull().default(0),
59
- createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
60
- updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
61
- }, (table) => [
62
- index("mnemocyte_memories_entity_idx").on(table.entityId),
63
- index("mnemocyte_memories_entity_type_idx").on(table.entityId, table.type),
64
- index("mnemocyte_memories_embedding_hnsw_idx").using("hnsw", table.embedding.op("vector_cosine_ops"))
65
- ]);
66
- const eventsTable = pgTable("mnemocyte_events", {
67
- id: text("id").primaryKey(),
68
- entityId: text("entity_id").notNull(),
69
- description: text("description").notNull(),
70
- metadata: jsonb("metadata").$type().notNull().default({}),
71
- timestamp: timestamp("timestamp", { withTimezone: true }).notNull().defaultNow()
72
- }, (table) => [index("mnemocyte_events_entity_time_idx").on(table.entityId, table.timestamp)]);
73
- const metaTable = pgTable("mnemocyte_meta", {
74
- key: text("key").primaryKey(),
75
- embeddingDimensions: integer("embedding_dimensions").notNull()
76
- });
77
- //#endregion
78
- //#region src/db/index.ts
79
- function parseSslFromUrl(url) {
80
- const mode = url.searchParams.get("sslmode");
81
- if (!mode || mode === "disable") return void 0;
82
- if (mode === "require" || mode === "prefer" || mode === "allow") return { rejectUnauthorized: false };
83
- if (mode === "verify-full") return "verify-full";
84
- }
85
- function createDatabase(databaseUrl) {
86
- let url;
87
- try {
88
- url = new URL(databaseUrl);
89
- } catch (error) {
90
- throw new MnemocyteError("databaseUrl must be a valid Postgres connection URL.", "CONFIG", error);
91
- }
92
- const ssl = parseSslFromUrl(url);
93
- const isPooler = url.port === "6543" || url.searchParams.get("pgbouncer") === "true";
94
- const client = postgres(databaseUrl, {
95
- max: 10,
96
- idle_timeout: 20,
97
- connect_timeout: 10,
98
- ...ssl ? { ssl } : {},
99
- ...isPooler ? { prepare: false } : {}
100
- });
101
- return {
102
- db: drizzle(client, { schema: schema_exports }),
103
- async close() {
104
- await client.end();
105
- }
106
- };
107
- }
108
- //#endregion
109
- //#region src/memory/json.ts
110
- function invalidJsonValue(path, cause) {
111
- return new MnemocyteError(`${path} must contain only JSON-compatible value data.`, "VALIDATION", cause);
112
- }
113
- function cloneJsonValue(value, path, ancestors) {
114
- if (value === null || typeof value === "string" || typeof value === "boolean") return value;
115
- if (typeof value === "number") {
116
- if (!Number.isFinite(value)) throw invalidJsonValue(path);
117
- return Object.is(value, -0) ? 0 : value;
118
- }
119
- if (typeof value !== "object") throw invalidJsonValue(path);
120
- if (ancestors.has(value)) throw invalidJsonValue(path);
121
- ancestors.add(value);
122
- try {
123
- if (Array.isArray(value)) {
124
- if (Object.getOwnPropertySymbols(value).length > 0 || Object.keys(value).some((key) => !/^(0|[1-9]\d*)$/.test(key))) throw invalidJsonValue(path);
125
- return Array.from({ length: value.length }, (_, index) => {
126
- if (!Object.hasOwn(value, index)) throw invalidJsonValue(`${path}[${index}]`);
127
- return cloneJsonValue(value[index], `${path}[${index}]`, ancestors);
128
- });
129
- }
130
- const prototype = Object.getPrototypeOf(value);
131
- if (prototype !== Object.prototype && prototype !== null) throw invalidJsonValue(path);
132
- const descriptors = Object.getOwnPropertyDescriptors(value);
133
- const entries = [];
134
- for (const key of Reflect.ownKeys(descriptors)) {
135
- if (typeof key !== "string") throw invalidJsonValue(path);
136
- const descriptor = descriptors[key];
137
- if (descriptor === void 0 || descriptor.enumerable !== true || !("value" in descriptor)) throw invalidJsonValue(`${path}.${key}`);
138
- entries.push([key, cloneJsonValue(descriptor.value, `${path}.${key}`, ancestors)]);
139
- }
140
- return Object.fromEntries(entries);
141
- } finally {
142
- ancestors.delete(value);
143
- }
144
- }
145
- /** Validate and deep-clone a JSON-compatible object. */
146
- function cloneJsonObject(value, path = "metadata") {
147
- try {
148
- if (value === null || typeof value !== "object" || Array.isArray(value)) throw invalidJsonValue(path);
149
- return cloneJsonValue(value, path, /* @__PURE__ */ new Set());
150
- } catch (error) {
151
- if (error instanceof MnemocyteError) throw error;
152
- throw invalidJsonValue(path, error);
153
- }
154
- }
155
- /** Assert that a runtime value is a JSON-compatible object. */
156
- function validateJsonObject(value, path = "metadata") {
157
- cloneJsonObject(value, path);
158
- }
159
- //#endregion
160
- //#region src/memory/records.ts
161
- function normalizeTags(tags) {
162
- if (!tags) return [];
163
- return [...new Set(tags.map((tag) => tag.trim()).filter(Boolean))];
164
- }
165
- function createId() {
166
- return `mem_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
167
- }
168
- /** Generate a unique audit-event id (`evt_*`). */
169
- function createEventId() {
170
- return `evt_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
171
- }
172
- function cloneMemory(memory) {
173
- return {
174
- id: memory.id,
175
- entityId: memory.entityId,
176
- content: memory.content,
177
- type: memory.type,
178
- importance: memory.importance,
179
- tags: [...memory.tags],
180
- source: memory.source,
181
- metadata: cloneJsonObject(memory.metadata),
182
- confidence: memory.confidence,
183
- embeddingModel: memory.embeddingModel,
184
- embeddingDimensions: memory.embeddingDimensions,
185
- supersededBy: memory.supersededBy,
186
- supersededAt: memory.supersededAt ? new Date(memory.supersededAt) : null,
187
- expiresAt: memory.expiresAt ? new Date(memory.expiresAt) : null,
188
- lastAccessedAt: memory.lastAccessedAt ? new Date(memory.lastAccessedAt) : null,
189
- accessCount: memory.accessCount,
190
- createdAt: new Date(memory.createdAt),
191
- updatedAt: new Date(memory.updatedAt)
192
- };
193
- }
194
- //#endregion
195
- //#region src/retrieval/scorer.ts
196
- const IMPORTANCE_SCORES = {
197
- low: 0,
198
- normal: .5,
199
- high: .8,
200
- critical: 1
201
- };
202
- const DEFAULT_RETRIEVAL_WEIGHTS = {
203
- vector: .55,
204
- lexical: .2,
205
- recency: .1,
206
- confidence: .05,
207
- access: .05,
208
- importance: .05
209
- };
210
- const DEFAULT_RECENCY_HALF_LIFE_DAYS = 90;
211
- const DEFAULT_ACCESS_SATURATION = 10;
212
- function cosineSimilarity(a, b) {
213
- let dot = 0;
214
- let aMagnitude = 0;
215
- let bMagnitude = 0;
216
- for (let i = 0; i < a.length; i += 1) {
217
- const av = a[i] ?? 0;
218
- const bv = b[i] ?? 0;
219
- dot += av * bv;
220
- aMagnitude += av * av;
221
- bMagnitude += bv * bv;
222
- }
223
- if (aMagnitude === 0 || bMagnitude === 0) return 0;
224
- return dot / (Math.sqrt(aMagnitude) * Math.sqrt(bMagnitude));
225
- }
226
- function tokenizeQuery(query) {
227
- return query.toLowerCase().split(/\s+/).filter(Boolean);
228
- }
229
- function lexicalScoreWithTerms(content, terms) {
230
- if (terms.length === 0) return 0;
231
- const lowerContent = content.toLowerCase();
232
- return terms.filter((term) => lowerContent.includes(term)).length / terms.length;
233
- }
234
- function createLexicalScorer(query) {
235
- const terms = tokenizeQuery(query);
236
- return (content) => lexicalScoreWithTerms(content, terms);
237
- }
238
- function clampScore(value) {
239
- if (!Number.isFinite(value)) return 0;
240
- return Math.max(0, Math.min(1, value));
241
- }
242
- function normalizeWeights(weights) {
243
- const merged = {
244
- ...DEFAULT_RETRIEVAL_WEIGHTS,
245
- ...weights
246
- };
247
- const total = merged.vector + merged.lexical + merged.recency + merged.confidence + merged.access + merged.importance;
248
- if (total <= 0) return { ...DEFAULT_RETRIEVAL_WEIGHTS };
249
- return {
250
- vector: merged.vector / total,
251
- lexical: merged.lexical / total,
252
- recency: merged.recency / total,
253
- confidence: merged.confidence / total,
254
- access: merged.access / total,
255
- importance: merged.importance / total
256
- };
257
- }
258
- function createScoringConfig(config) {
259
- return {
260
- weights: normalizeWeights(config?.weights),
261
- recencyHalfLifeDays: config?.recencyHalfLifeDays ?? DEFAULT_RECENCY_HALF_LIFE_DAYS,
262
- accessSaturation: config?.accessSaturation ?? DEFAULT_ACCESS_SATURATION
263
- };
264
- }
265
- function recencyScore(createdAt, halfLifeDays) {
266
- const ageMs = Date.now() - createdAt.getTime();
267
- const ageDays = Math.max(0, ageMs / 864e5);
268
- return Math.exp(-Math.LN2 * ageDays / halfLifeDays);
269
- }
270
- function accessScore(accessCount, saturation) {
271
- if (accessCount <= 0) return 0;
272
- return Math.min(1, Math.log1p(accessCount) / Math.log1p(saturation));
273
- }
274
- function toScoredMemoryWithConfig(memory, vector, lexical, input, config) {
275
- const { weights } = config;
276
- const recency = recencyScore(memory.createdAt, config.recencyHalfLifeDays);
277
- const confidence = clampScore(memory.confidence);
278
- const access = accessScore(memory.accessCount, config.accessSaturation);
279
- const importance = IMPORTANCE_SCORES[memory.importance];
280
- const score = Math.max(0, Math.min(1, clampScore(vector) * weights.vector + clampScore(lexical) * weights.lexical + recency * weights.recency + confidence * weights.confidence + access * weights.access + importance * weights.importance));
281
- return {
282
- ...cloneMemory(memory),
283
- score,
284
- scores: {
285
- vector: clampScore(vector),
286
- lexical: clampScore(lexical),
287
- recency,
288
- confidence,
289
- access,
290
- importance
291
- },
292
- explanation: input.explain ? {
293
- vectorScore: clampScore(vector),
294
- lexicalScore: clampScore(lexical),
295
- recencyScore: recency,
296
- confidenceScore: confidence,
297
- accessScore: access,
298
- importanceScore: importance,
299
- importanceBoost: importance * weights.importance,
300
- weights,
301
- finalScore: score
302
- } : null
303
- };
304
- }
305
- //#endregion
306
- //#region src/context/formatter.ts
307
- function escapeXml(value) {
308
- return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;").replaceAll("'", "&apos;");
309
- }
310
- function formatMetadata(memory) {
311
- const parts = [
312
- `type=${memory.type}`,
313
- `importance=${memory.importance}`,
314
- `score=${memory.score.toFixed(3)}`
315
- ];
316
- if (memory.source) parts.push(`source=${memory.source}`);
317
- if (memory.tags.length > 0) parts.push(`tags=${memory.tags.join(",")}`);
318
- return parts.join("; ");
319
- }
320
- function markdownFence(value) {
321
- const longestBacktickRun = Math.max(0, ...Array.from(value.matchAll(/`+/g), (match) => match[0].length));
322
- return "`".repeat(Math.max(3, longestBacktickRun + 1));
323
- }
324
- function formatMarkdown(memories, query, omittedCount) {
325
- const lines = [
326
- "# Memory Context",
327
- "",
328
- `Query: ${query}`,
329
- "",
330
- "## Relevant Memories"
331
- ];
332
- for (const memory of memories) {
333
- const fence = markdownFence(memory.content);
334
- lines.push("", `- ${formatMetadata(memory)}`, ` ${fence}text`, ...memory.content.split("\n").map((line) => ` ${line}`), ` ${fence}`);
335
- }
336
- if (omittedCount > 0) lines.push("", `[${omittedCount} memories omitted to fit token budget]`);
337
- return lines.join("\n");
338
- }
339
- function formatPlain(memories, query, omittedCount) {
340
- const lines = [
341
- "MEMORY CONTEXT",
342
- "",
343
- `QUERY: ${query}`,
344
- "",
345
- "RELEVANT MEMORIES"
346
- ];
347
- for (const [index, memory] of memories.entries()) lines.push("", `--- MEMORY ${index + 1} START ---`, formatMetadata(memory), memory.content, `--- MEMORY ${index + 1} END ---`);
348
- if (omittedCount > 0) lines.push("", `[${omittedCount} memories omitted to fit token budget]`);
349
- return lines.join("\n");
350
- }
351
- function formatXml(memories, query, omittedCount) {
352
- const lines = [`<memory_context query="${escapeXml(query)}">`];
353
- for (const memory of memories) lines.push(` <memory id="${escapeXml(memory.id)}" type="${escapeXml(memory.type)}" importance="${escapeXml(memory.importance)}" score="${memory.score.toFixed(3)}">`, ` <content>${escapeXml(memory.content)}</content>`, ` <metadata>${escapeXml(formatMetadata(memory))}</metadata>`, " </memory>");
354
- if (omittedCount > 0) lines.push(` <omitted count="${omittedCount}" reason="token_budget" />`);
355
- lines.push("</memory_context>");
356
- return lines.join("\n");
357
- }
358
- function formatContext(memories, query, format, omittedCount = 0) {
359
- switch (format) {
360
- case "markdown": return formatMarkdown(memories, query, omittedCount);
361
- case "plain": return formatPlain(memories, query, omittedCount);
362
- case "xml": return formatXml(memories, query, omittedCount);
363
- }
364
- }
365
- //#endregion
366
- //#region src/context/tokens.ts
367
- const heuristicTokenCounter = { count(text) {
368
- return Math.ceil(text.length / 4);
369
- } };
370
- function trimToTokenBudget(text, maxTokens, tokenCounter) {
371
- if (maxTokens < 1 || tokenCounter.count(text) <= maxTokens) return text;
372
- let low = 0;
373
- let high = text.length;
374
- let best = "";
375
- while (low <= high) {
376
- const mid = Math.floor((low + high) / 2);
377
- const output = `${text.slice(0, mid).trimEnd()}\n[truncated to fit token budget]`;
378
- if (tokenCounter.count(output) <= maxTokens) {
379
- best = output;
380
- low = mid + 1;
381
- } else high = mid - 1;
382
- }
383
- return best || "[truncated to fit token budget]";
384
- }
385
- //#endregion
386
- //#region src/context/builder.ts
387
- async function buildContext(options) {
388
- const format = options.input.format ?? "markdown";
389
- const maxTokens = options.input.maxTokens ?? 1200;
390
- const tokenCounter = options.input.tokenCounter ?? heuristicTokenCounter;
391
- const memories = await options.recall(options.input);
392
- let low = 0;
393
- let high = memories.length;
394
- let bestContext = "";
395
- while (low <= high) {
396
- const mid = Math.floor((low + high) / 2);
397
- const omittedCount = memories.length - mid;
398
- const context = formatContext(memories.slice(0, mid), options.input.query, format, omittedCount);
399
- if (tokenCounter.count(context) <= maxTokens) {
400
- bestContext = context;
401
- low = mid + 1;
402
- } else high = mid - 1;
403
- }
404
- if (bestContext || memories.length === 0) return bestContext;
405
- return trimToTokenBudget(formatContext([], options.input.query, format, memories.length), maxTokens, tokenCounter);
406
- }
407
- //#endregion
408
- //#region src/observability.ts
409
- function withMetadata(event, metadata) {
410
- return {
411
- ...event,
412
- ...metadata.entityId === void 0 ? {} : { entityId: metadata.entityId },
413
- ...metadata.memoryId === void 0 ? {} : { memoryId: metadata.memoryId },
414
- ...metadata.count === void 0 ? {} : { count: metadata.count }
415
- };
416
- }
417
- async function emitObservation(config, event) {
418
- try {
419
- await config.observability?.onEvent?.(event);
420
- } catch {
421
- return;
422
- }
423
- }
424
- async function observe(config, backend, operation, metadata, action, successMetadata) {
425
- const startedAt = Date.now();
426
- await emitObservation(config, withMetadata({
427
- operation,
428
- phase: "start",
429
- backend,
430
- timestamp: new Date(startedAt)
431
- }, metadata));
432
- try {
433
- const result = await action();
434
- await emitObservation(config, withMetadata({
435
- operation,
436
- phase: "success",
437
- backend,
438
- timestamp: /* @__PURE__ */ new Date(),
439
- durationMs: Date.now() - startedAt
440
- }, {
441
- ...metadata,
442
- ...successMetadata?.(result)
443
- }));
444
- return result;
445
- } catch (error) {
446
- await emitObservation(config, withMetadata({
447
- operation,
448
- phase: "error",
449
- backend,
450
- timestamp: /* @__PURE__ */ new Date(),
451
- durationMs: Date.now() - startedAt,
452
- error
453
- }, metadata));
454
- throw error;
455
- }
456
- }
457
- /**
458
- * Ordering of {@link ImportanceLevel} from least to most important. Used
459
- * by `prune({ maxImportance })` to compare a memory's importance against
460
- * the caller-supplied ceiling.
461
- */
462
- const IMPORTANCE_RANK = {
463
- low: 0,
464
- normal: 1,
465
- high: 2,
466
- critical: 3
467
- };
468
- //#endregion
469
- //#region src/resilience.ts
470
- /**
471
- * Default per-provider-call timeout. Disabled by default so that callers
472
- * who do not configure {@link ProviderResilienceConfig.timeoutMs} are
473
- * unaffected.
474
- */
475
- const DEFAULT_TIMEOUT_MS = 0;
476
- /** Default number of retry attempts on transient provider failures. */
477
- const DEFAULT_RETRIES = 0;
478
- /** Default base backoff delay used between retry attempts. */
479
- const DEFAULT_BASE_DELAY_MS = 100;
480
- /** Default ceiling for retry backoff delay. */
481
- const DEFAULT_MAX_DELAY_MS = 2e3;
482
- /**
483
- * Throw a {@link MnemocyteError} with code `"ABORTED"` if `signal` is
484
- * already aborted. Safe to call with `undefined`.
485
- */
486
- function throwIfAborted(signal) {
487
- if (signal?.aborted) throw new MnemocyteError("Operation was aborted.", "ABORTED", signal.reason);
488
- }
489
- /**
490
- * Heuristic default for whether an error should be retried.
491
- *
492
- * Conservative by design: only retries on common transient failure
493
- * indicators (network errors, 5xx-ish messages, `ECONN*` etc.). Never
494
- * retries on validation, configuration, or {@link MnemocyteError} of
495
- * code `"VALIDATION"` / `"CONFIG"` / `"ABORTED"`.
496
- */
497
- function defaultShouldRetry(error) {
498
- if (error instanceof MnemocyteError) {
499
- if (error.code === "VALIDATION" || error.code === "CONFIG" || error.code === "ABORTED") return false;
500
- return true;
501
- }
502
- if (typeof error === "object" && error !== null && "status" in error && typeof error.status === "number" && [
503
- 429,
504
- 500,
505
- 502,
506
- 503,
507
- 504
508
- ].includes(error.status)) return true;
509
- if (error instanceof Error) {
510
- const message = error.message.toLowerCase();
511
- if (message.includes("econn") || message.includes("etimedout") || message.includes("network") || message.includes("timeout") || message.includes("temporarily") || message.includes("rate limit") || message.includes("503") || message.includes("502") || message.includes("504") || message.includes("500")) return true;
512
- }
513
- return false;
514
- }
515
- /**
516
- * Compute the backoff delay for the given retry attempt, using exponential
517
- * growth capped at {@link ProviderResilienceConfig.maxDelayMs}.
518
- *
519
- * `attempt` is 1-indexed: the delay before the first retry uses
520
- * `attempt = 1`, before the second retry `attempt = 2`, etc.
521
- */
522
- function computeDelayMs(attempt, baseDelayMs, maxDelayMs) {
523
- const exponential = baseDelayMs * 2 ** (attempt - 1);
524
- return Math.max(0, Math.min(maxDelayMs, exponential));
525
- }
526
- /**
527
- * Promise-based `setTimeout` that rejects with an `"ABORTED"`
528
- * {@link MnemocyteError} if `signal` aborts while waiting.
529
- */
530
- function delay(ms, signal) {
531
- if (ms <= 0) {
532
- throwIfAborted(signal);
533
- return Promise.resolve();
534
- }
535
- return new Promise((resolve, reject) => {
536
- const timer = setTimeout(() => {
537
- signal?.removeEventListener("abort", onAbort);
538
- resolve();
539
- }, ms);
540
- const onAbort = () => {
541
- clearTimeout(timer);
542
- reject(new MnemocyteError("Operation was aborted.", "ABORTED", signal?.reason));
543
- };
544
- if (signal) {
545
- if (signal.aborted) {
546
- clearTimeout(timer);
547
- reject(new MnemocyteError("Operation was aborted.", "ABORTED", signal.reason));
548
- return;
549
- }
550
- signal.addEventListener("abort", onAbort, { once: true });
551
- }
552
- });
553
- }
554
- function timeoutError(timeoutMs) {
555
- return new MnemocyteError(`Provider call timed out after ${timeoutMs}ms.`, "TIMEOUT");
556
- }
557
- /**
558
- * Run one provider attempt with a signal linked to the caller's signal and
559
- * Mnemocyte's configured timeout. Timeout aborts are actively propagated to
560
- * providers that honor `AbortSignal`.
561
- */
562
- function runAttempt(action, timeoutMs, signal) {
563
- if (timeoutMs <= 0 && !signal) return action(void 0);
564
- return new Promise((resolve, reject) => {
565
- let settled = false;
566
- const controller = new AbortController();
567
- const timer = timeoutMs > 0 ? setTimeout(() => {
568
- if (settled) return;
569
- settled = true;
570
- signal?.removeEventListener("abort", onAbort);
571
- controller.abort(timeoutError(timeoutMs));
572
- reject(timeoutError(timeoutMs));
573
- }, timeoutMs) : void 0;
574
- const onAbort = () => {
575
- if (settled) return;
576
- settled = true;
577
- if (timer) clearTimeout(timer);
578
- controller.abort(signal?.reason);
579
- reject(new MnemocyteError("Operation was aborted.", "ABORTED", signal?.reason));
580
- };
581
- if (signal) {
582
- if (signal.aborted) {
583
- if (timer) clearTimeout(timer);
584
- settled = true;
585
- controller.abort(signal.reason);
586
- reject(new MnemocyteError("Operation was aborted.", "ABORTED", signal.reason));
587
- return;
588
- }
589
- signal.addEventListener("abort", onAbort, { once: true });
590
- }
591
- action(controller.signal).then((value) => {
592
- if (settled) return;
593
- settled = true;
594
- if (timer) clearTimeout(timer);
595
- signal?.removeEventListener("abort", onAbort);
596
- resolve(value);
597
- }, (error) => {
598
- if (settled) return;
599
- settled = true;
600
- if (timer) clearTimeout(timer);
601
- signal?.removeEventListener("abort", onAbort);
602
- reject(error);
603
- });
604
- });
605
- }
606
- /**
607
- * Run `action` with timeout, retry, and abort handling derived from
608
- * `options.resilience` and `options.signal`. Each retry attempt receives
609
- * a fresh `AbortSignal` linked to the caller's signal — so callers may
610
- * abort mid-retry and the in-flight attempt will cancel promptly if the
611
- * underlying provider honours signals.
612
- *
613
- * Errors from {@link MnemocyteError} with code `"VALIDATION"` /
614
- * `"CONFIG"` / `"ABORTED"` are never retried.
615
- */
616
- async function withResilience(action, options = {}) {
617
- const config = options.resilience ?? {};
618
- const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
619
- const retries = Math.max(0, config.retries ?? DEFAULT_RETRIES);
620
- const baseDelayMs = Math.max(0, config.baseDelayMs ?? DEFAULT_BASE_DELAY_MS);
621
- const maxDelayMs = Math.max(baseDelayMs, config.maxDelayMs ?? DEFAULT_MAX_DELAY_MS);
622
- const shouldRetry = config.shouldRetry ?? defaultShouldRetry;
623
- const externalSignal = options.signal;
624
- throwIfAborted(externalSignal);
625
- const maxAttempts = retries + 1;
626
- let lastError;
627
- for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
628
- throwIfAborted(externalSignal);
629
- try {
630
- return await runAttempt(action, timeoutMs, externalSignal);
631
- } catch (error) {
632
- lastError = error;
633
- if (error instanceof MnemocyteError && error.code === "ABORTED") throw error;
634
- if (attempt >= maxAttempts) throw error;
635
- if (!shouldRetry(error, attempt)) throw error;
636
- await delay(computeDelayMs(attempt, baseDelayMs, maxDelayMs), externalSignal);
637
- }
638
- }
639
- throw lastError;
640
- }
641
- //#endregion
642
- //#region src/memory/embeddings.ts
643
- function validateEmbedding(embedding, dimensions) {
644
- if (embedding.length !== dimensions) throw new MnemocyteError("Embedder returned an embedding with unexpected dimensions.", "EMBEDDING");
645
- if (embedding.some((component) => !Number.isFinite(component))) throw new MnemocyteError("Embedder returned an embedding with non-finite values.", "EMBEDDING");
646
- }
647
- async function embedOne(embedder, text, options = {}) {
648
- let embeddings;
649
- try {
650
- embeddings = await withResilience((signal) => signal === void 0 ? embedder.embed([text]) : embedder.embed([text], { signal }), {
651
- ...options.signal === void 0 ? {} : { signal: options.signal },
652
- ...options.resilience === void 0 ? {} : { resilience: options.resilience }
653
- });
654
- } catch (error) {
655
- if (error instanceof MnemocyteError && (error.code === "TIMEOUT" || error.code === "ABORTED")) throw error;
656
- throw new MnemocyteError("Failed to embed text.", "EMBEDDING", error);
657
- }
658
- const embedding = embeddings[0];
659
- if (!embedding) throw new MnemocyteError("Embedder returned no embedding.", "EMBEDDING");
660
- validateEmbedding(embedding, embedder.dimensions);
661
- return embedding;
662
- }
663
- async function embedMany(embedder, texts, options = {}) {
664
- if (texts.length === 0) return [];
665
- let embeddings;
666
- try {
667
- embeddings = await withResilience((signal) => signal === void 0 ? embedder.embed(texts) : embedder.embed(texts, { signal }), {
668
- ...options.signal === void 0 ? {} : { signal: options.signal },
669
- ...options.resilience === void 0 ? {} : { resilience: options.resilience }
670
- });
671
- } catch (error) {
672
- if (error instanceof MnemocyteError && (error.code === "TIMEOUT" || error.code === "ABORTED")) throw error;
673
- throw new MnemocyteError("Failed to embed texts.", "EMBEDDING", error);
674
- }
675
- if (embeddings.length !== texts.length) throw new MnemocyteError(`Embedder returned ${embeddings.length} embeddings for ${texts.length} texts.`, "EMBEDDING");
676
- for (const embedding of embeddings) validateEmbedding(embedding, embedder.dimensions);
677
- return embeddings;
678
- }
679
- //#endregion
680
- //#region src/memory/validation.ts
681
- function assertNonEmptyString(value, field, code = "VALIDATION") {
682
- if (typeof value !== "string" || value.trim().length === 0) throw new MnemocyteError(`${field} must be a non-empty string.`, code);
683
- }
684
- function assertLimit(value) {
685
- if (!Number.isInteger(value) || value < 1) throw new MnemocyteError("limit must be a positive integer.", "VALIDATION");
686
- }
687
- function assertMinScore(value) {
688
- if (!Number.isFinite(value) || value < 0 || value > 1) throw new MnemocyteError("minScore must be a number between 0 and 1.", "VALIDATION");
689
- }
690
- function assertEmbedder(embedder) {
691
- if (!embedder || typeof embedder.embed !== "function") throw new MnemocyteError("embedder with an embed function is required.", "CONFIG");
692
- assertNonEmptyString(embedder.model, "embedder.model", "CONFIG");
693
- if (!Number.isInteger(embedder.dimensions) || embedder.dimensions < 1) throw new MnemocyteError("embedder.dimensions must be a positive integer.", "CONFIG");
694
- }
695
- const RETRIEVAL_WEIGHT_KEYS = [
696
- "vector",
697
- "lexical",
698
- "recency",
699
- "confidence",
700
- "access",
701
- "importance"
702
- ];
703
- function assertPositiveFiniteConfigValue(value, field) {
704
- if (!Number.isFinite(value) || value <= 0) throw new MnemocyteError(`${field} must be a positive finite number.`, "CONFIG");
705
- }
706
- function validateRetrievalConfig(config) {
707
- if (config === void 0) return;
708
- let weightTotal = 0;
709
- for (const key of RETRIEVAL_WEIGHT_KEYS) {
710
- const weight = config.weights?.[key] ?? DEFAULT_RETRIEVAL_WEIGHTS[key];
711
- if (!Number.isFinite(weight) || weight < 0) throw new MnemocyteError(`retrieval.weights.${key} must be a non-negative finite number.`, "CONFIG");
712
- weightTotal += weight;
713
- }
714
- if (weightTotal === 0) throw new MnemocyteError("retrieval.weights must have an effective total greater than zero.", "CONFIG");
715
- if (config.recencyHalfLifeDays !== void 0) assertPositiveFiniteConfigValue(config.recencyHalfLifeDays, "retrieval.recencyHalfLifeDays");
716
- if (config.accessSaturation !== void 0) assertPositiveFiniteConfigValue(config.accessSaturation, "retrieval.accessSaturation");
717
- if (config.candidateMultiplier !== void 0 && (!Number.isInteger(config.candidateMultiplier) || config.candidateMultiplier < 1)) throw new MnemocyteError("retrieval.candidateMultiplier must be an integer greater than or equal to 1.", "CONFIG");
718
- }
719
- function validateBuildContextInput(input) {
720
- if (input.maxTokens !== void 0 && (!Number.isInteger(input.maxTokens) || input.maxTokens < 1)) throw new MnemocyteError("maxTokens must be a positive integer when supplied.", "VALIDATION");
721
- }
722
- function validateRememberInput(input) {
723
- assertNonEmptyString(input.entityId, "entityId");
724
- assertNonEmptyString(input.content, "content");
725
- if (input.metadata !== void 0) validateJsonObject(input.metadata);
726
- if (input.confidence !== void 0 && (!Number.isFinite(input.confidence) || input.confidence < 0 || input.confidence > 1)) throw new MnemocyteError("confidence must be a number between 0 and 1.", "VALIDATION");
727
- }
728
- function validateRecallInput(input) {
729
- assertNonEmptyString(input.entityId, "entityId");
730
- assertNonEmptyString(input.query, "query");
731
- if (input.limit !== void 0) assertLimit(input.limit);
732
- if (input.minScore !== void 0) assertMinScore(input.minScore);
733
- }
734
- function contextInputToRecallInput(input) {
735
- return {
736
- entityId: input.entityId,
737
- query: input.query,
738
- ...input.limit === void 0 ? {} : { limit: input.limit },
739
- ...input.minScore === void 0 ? {} : { minScore: input.minScore },
740
- ...input.types === void 0 ? {} : { types: input.types },
741
- ...input.tags === void 0 ? {} : { tags: input.tags },
742
- ...input.includeSuperseded === void 0 ? {} : { includeSuperseded: input.includeSuperseded },
743
- ...input.includeExpired === void 0 ? {} : { includeExpired: input.includeExpired }
744
- };
745
- }
746
- /**
747
- * Validate a {@link PruneInput} and throw a `"VALIDATION"`
748
- * {@link MnemocyteError} when no selector is supplied. Mnemocyte
749
- * deliberately rejects `prune({})` to avoid surprise full-table deletes.
750
- */
751
- function validatePruneInput(input) {
752
- if (input.entityId !== void 0) assertNonEmptyString(input.entityId, "entityId");
753
- if (!(input.entityId !== void 0 || input.expired === true || input.superseded === true || input.createdBefore !== void 0 || input.notAccessedSince !== void 0 || input.types !== void 0 && input.types.length > 0 || input.tags !== void 0 && input.tags.length > 0 || input.maxImportance !== void 0)) throw new MnemocyteError("prune requires at least one selector (entityId, expired, superseded, createdBefore, notAccessedSince, types, tags, or maxImportance).", "VALIDATION");
754
- }
755
- /**
756
- * Validate a {@link FindDuplicatesInput} and throw a `"VALIDATION"`
757
- * {@link MnemocyteError} for malformed `threshold` / `limit` values.
758
- */
759
- function validateFindDuplicatesInput(input) {
760
- assertNonEmptyString(input.entityId, "entityId");
761
- if (input.threshold !== void 0) {
762
- if (!Number.isFinite(input.threshold) || input.threshold < 0 || input.threshold > 1) throw new MnemocyteError("threshold must be a number between 0 and 1.", "VALIDATION");
763
- }
764
- if (input.limit !== void 0) assertLimit(input.limit);
765
- }
766
- /**
767
- * Validate a {@link ConsolidateInput} and throw a `"VALIDATION"`
768
- * {@link MnemocyteError} for malformed inputs (empty `supersededIds`
769
- * or a `survivorId` appearing in `supersededIds`).
770
- */
771
- function validateConsolidateInput(input) {
772
- assertNonEmptyString(input.entityId, "entityId");
773
- assertNonEmptyString(input.survivorId, "survivorId");
774
- if (input.supersededIds.length === 0) throw new MnemocyteError("supersededIds must contain at least one memory id.", "VALIDATION");
775
- if (input.supersededIds.includes(input.survivorId)) throw new MnemocyteError("survivorId must not appear in supersededIds.", "VALIDATION");
776
- for (const id of input.supersededIds) assertNonEmptyString(id, "supersededIds[*]");
777
- }
778
- /**
779
- * Validate a {@link ListAuditLogInput} and throw a `"VALIDATION"`
780
- * {@link MnemocyteError} when malformed.
781
- */
782
- function validateListAuditLogInput(input) {
783
- assertNonEmptyString(input.entityId, "entityId");
784
- if (input.limit !== void 0) assertLimit(input.limit);
785
- }
786
- //#endregion
787
- //#region src/memory/client-core.ts
788
- function isPositionalRememberManyInput(input) {
789
- return Array.isArray(input);
790
- }
791
- function providerOptions(config, signal) {
792
- return {
793
- ...signal === void 0 ? {} : { signal },
794
- ...config.provider === void 0 ? {} : { resilience: config.provider }
795
- };
796
- }
797
- function createStoredMemory(config, input, embedding, now) {
798
- return {
799
- id: createId(),
800
- entityId: input.entityId,
801
- content: input.content,
802
- type: input.type ?? "fact",
803
- importance: input.importance ?? "normal",
804
- tags: normalizeTags(input.tags),
805
- source: input.source ?? null,
806
- metadata: cloneJsonObject(input.metadata ?? {}),
807
- confidence: input.confidence ?? 1,
808
- embeddingModel: config.embedder.model,
809
- embeddingDimensions: config.embedder.dimensions,
810
- supersededBy: null,
811
- supersededAt: null,
812
- expiresAt: input.expiresAt ?? null,
813
- lastAccessedAt: null,
814
- accessCount: 0,
815
- createdAt: now,
816
- updatedAt: now,
817
- embedding
818
- };
819
- }
820
- function auditEvent(entityId, description, metadata, timestamp = /* @__PURE__ */ new Date()) {
821
- return {
822
- id: createEventId(),
823
- entityId,
824
- description,
825
- metadata: cloneJsonObject(metadata),
826
- timestamp
827
- };
828
- }
829
- function toDuplicatePair(pair) {
830
- return {
831
- a: cloneMemory(pair.a),
832
- b: cloneMemory(pair.b),
833
- similarity: Math.max(0, Math.min(1, pair.similarity))
834
- };
835
- }
836
- function createMemoryClient(config, store) {
837
- let closed = false;
838
- function assertOpen() {
839
- if (closed) throw new MnemocyteError("Mnemocyte client is closed.", "DB");
840
- }
841
- async function ensureSchema() {
842
- await store.ensureSchema();
843
- }
844
- async function ensureEmbeddingCompatibility() {
845
- await store.ensureEmbeddingCompatibility(config.embedder);
846
- }
847
- async function recordAudit(events) {
848
- if (events.length === 0 || config.audit?.enabled !== true) return;
849
- try {
850
- await store.addAuditEvents(events);
851
- } catch {}
852
- }
853
- async function remember(input) {
854
- const preparedInput = {
855
- ...input,
856
- metadata: cloneJsonObject(input.metadata ?? {})
857
- };
858
- return observe(config, store.backend, "remember", { entityId: preparedInput.entityId }, async () => {
859
- assertOpen();
860
- validateRememberInput(preparedInput);
861
- await ensureEmbeddingCompatibility();
862
- const embedding = await embedOne(config.embedder, preparedInput.content, providerOptions(config, preparedInput.signal));
863
- const [memory] = await store.insertMemories([createStoredMemory(config, preparedInput, embedding, /* @__PURE__ */ new Date())]);
864
- if (!memory) throw new MnemocyteError("Memory insert returned no rows.", "DB");
865
- await recordAudit([auditEvent(memory.entityId, "memory.created", {
866
- memoryId: memory.id,
867
- type: memory.type,
868
- importance: memory.importance
869
- })]);
870
- return cloneMemory(memory);
871
- }, (memory) => ({
872
- memoryId: memory.id,
873
- count: 1
874
- }));
875
- }
876
- async function rememberMany(input) {
877
- const positional = isPositionalRememberManyInput(input);
878
- const inputs = positional ? input : input.inputs;
879
- const signal = positional ? input[0]?.signal : input.signal;
880
- const preparedInputs = inputs.map((item) => ({
881
- ...item,
882
- metadata: cloneJsonObject(item.metadata ?? {})
883
- }));
884
- return observe(config, store.backend, "rememberMany", { count: preparedInputs.length }, async () => {
885
- assertOpen();
886
- if (preparedInputs.length === 0) return [];
887
- for (const item of preparedInputs) validateRememberInput(item);
888
- await ensureEmbeddingCompatibility();
889
- const embeddings = await embedMany(config.embedder, preparedInputs.map((item) => item.content), providerOptions(config, signal));
890
- const now = /* @__PURE__ */ new Date();
891
- const stored = preparedInputs.map((item, idx) => createStoredMemory(config, item, embeddings[idx], now));
892
- const memories = await store.insertMemories(stored);
893
- await recordAudit(memories.map((memory) => auditEvent(memory.entityId, "memory.created", {
894
- memoryId: memory.id,
895
- type: memory.type,
896
- importance: memory.importance
897
- })));
898
- return memories.map(cloneMemory);
899
- }, (result) => ({ count: result.length }));
900
- }
901
- const client = {
902
- remember,
903
- rememberMany,
904
- async recall(input) {
905
- return observe(config, store.backend, "recall", { entityId: input.entityId }, async () => {
906
- assertOpen();
907
- validateRecallInput(input);
908
- const limit = input.limit ?? config.defaults?.limit ?? 10;
909
- const minScore = input.minScore ?? config.defaults?.minScore ?? 0;
910
- assertLimit(limit);
911
- assertMinScore(minScore);
912
- await ensureEmbeddingCompatibility();
913
- const queryEmbedding = await embedOne(config.embedder, input.query, providerOptions(config, input.signal));
914
- const candidateMultiplier = config.retrieval?.candidateMultiplier ?? 3;
915
- const candidateLimit = Math.max(limit, limit * candidateMultiplier);
916
- const scoreLexical = createLexicalScorer(input.query);
917
- const scoringConfig = createScoringConfig(config.retrieval);
918
- const [vectorCandidates, lexicalCandidates] = await Promise.all([store.vectorSearch({
919
- ...input,
920
- embedding: queryEmbedding,
921
- limit: candidateLimit,
922
- minScore: 0
923
- }), store.lexicalSearch({
924
- ...input,
925
- limit: candidateLimit
926
- })]);
927
- const merged = /* @__PURE__ */ new Map();
928
- for (const candidate of vectorCandidates) merged.set(candidate.memory.id, {
929
- memory: candidate.memory,
930
- vectorScore: candidate.vectorScore,
931
- lexicalScore: scoreLexical(candidate.memory.content)
932
- });
933
- const lexicalOnly = lexicalCandidates.filter((candidate) => !merged.has(candidate.memory.id));
934
- const lexicalOnlyEmbeddings = await store.getMemoryEmbeddings(lexicalOnly.map((candidate) => candidate.memory.id));
935
- for (const candidate of lexicalCandidates) {
936
- const existing = merged.get(candidate.memory.id);
937
- if (existing) {
938
- existing.lexicalScore = candidate.lexicalScore;
939
- continue;
940
- }
941
- const embedding = lexicalOnlyEmbeddings.get(candidate.memory.id);
942
- const similarity = embedding ? cosineSimilarity(embedding, queryEmbedding) : 0;
943
- merged.set(candidate.memory.id, {
944
- memory: candidate.memory,
945
- vectorScore: Math.max(0, similarity),
946
- lexicalScore: candidate.lexicalScore
947
- });
948
- }
949
- const scored = Array.from(merged.values()).map((entry) => toScoredMemoryWithConfig(entry.memory, Math.max(0, entry.vectorScore), Math.max(0, entry.lexicalScore), input, scoringConfig)).filter((memory) => memory.score >= minScore).sort((a, b) => b.score - a.score).slice(0, limit);
950
- await store.markMemoriesAccessed(scored.map((memory) => memory.id));
951
- return scored;
952
- }, (result) => ({ count: result.length }));
953
- },
954
- async buildContext(input) {
955
- return observe(config, store.backend, "buildContext", { entityId: input.entityId }, async () => {
956
- assertOpen();
957
- validateBuildContextInput(input);
958
- return buildContext({
959
- input,
960
- recall: (contextInput) => {
961
- const recallInput = contextInputToRecallInput(contextInput);
962
- return client.recall(input.signal === void 0 ? recallInput : {
963
- ...recallInput,
964
- signal: input.signal
965
- });
966
- }
967
- });
968
- });
969
- },
970
- async forget(input) {
971
- return observe(config, store.backend, "forget", {
972
- entityId: input.entityId,
973
- memoryId: input.memoryId
974
- }, async () => {
975
- assertOpen();
976
- assertNonEmptyString(input.entityId, "entityId");
977
- assertNonEmptyString(input.memoryId, "memoryId");
978
- await ensureSchema();
979
- if (!await store.deleteMemory(input.entityId, input.memoryId)) throw new MnemocyteError("Memory was not found.", "NOT_FOUND");
980
- await recordAudit([auditEvent(input.entityId, "memory.deleted", { memoryId: input.memoryId })]);
981
- });
982
- },
983
- async forgetAll(input) {
984
- return observe(config, store.backend, "forgetAll", { entityId: input.entityId }, async () => {
985
- assertOpen();
986
- assertNonEmptyString(input.entityId, "entityId");
987
- await ensureSchema();
988
- const deletedCount = await store.deleteMemoriesForEntity(input.entityId);
989
- await recordAudit([auditEvent(input.entityId, "entity.cleared", { count: deletedCount })]);
990
- return deletedCount;
991
- }, (count) => ({ count })).then(() => void 0);
992
- },
993
- async prune(input) {
994
- return observe(config, store.backend, "prune", input.entityId === void 0 ? {} : { entityId: input.entityId }, async () => {
995
- assertOpen();
996
- validatePruneInput(input);
997
- await ensureSchema();
998
- const result = await store.prune(input);
999
- if (result.deletedCount > 0 && input.entityId !== void 0 && result.dryRun === false) await recordAudit([auditEvent(input.entityId, "memory.pruned", { count: result.deletedCount })]);
1000
- return result;
1001
- }, (result) => ({ count: result.deletedCount }));
1002
- },
1003
- async findDuplicates(input) {
1004
- return observe(config, store.backend, "findDuplicates", { entityId: input.entityId }, async () => {
1005
- assertOpen();
1006
- validateFindDuplicatesInput(input);
1007
- await ensureEmbeddingCompatibility();
1008
- return (await store.findDuplicatePairs(input)).map(toDuplicatePair);
1009
- }, (result) => ({ count: result.length }));
1010
- },
1011
- async listAuditLog(input) {
1012
- return observe(config, store.backend, "listAuditLog", { entityId: input.entityId }, async () => {
1013
- assertOpen();
1014
- validateListAuditLogInput(input);
1015
- await ensureSchema();
1016
- return (await store.listAuditLog(input)).map((event) => ({
1017
- id: event.id,
1018
- entityId: event.entityId,
1019
- description: event.description,
1020
- metadata: cloneJsonObject(event.metadata),
1021
- timestamp: new Date(event.timestamp)
1022
- }));
1023
- }, (result) => ({ count: result.length }));
1024
- },
1025
- async stats(input) {
1026
- return observe(config, store.backend, "stats", input?.entityId ? { entityId: input.entityId } : {}, async () => {
1027
- assertOpen();
1028
- await ensureSchema();
1029
- return store.stats(input, /* @__PURE__ */ new Date());
1030
- });
1031
- },
1032
- experimental: { async consolidate(input) {
1033
- return observe(config, store.backend, "consolidate", {
1034
- entityId: input.entityId,
1035
- memoryId: input.survivorId
1036
- }, async () => {
1037
- assertOpen();
1038
- validateConsolidateInput(input);
1039
- await ensureSchema();
1040
- const survivor = await store.getMemory(input.entityId, input.survivorId);
1041
- if (!survivor) throw new MnemocyteError("Survivor memory was not found.", "NOT_FOUND");
1042
- if (survivor.supersededBy !== null) throw new MnemocyteError("Survivor memory is itself superseded.", "VALIDATION");
1043
- const targets = await store.loadConsolidationTargets(input.entityId, input.supersededIds);
1044
- const foundIds = new Set(targets.map((target) => target.id));
1045
- for (const id of input.supersededIds) if (!foundIds.has(id)) throw new MnemocyteError("Superseded memory was not found.", "NOT_FOUND");
1046
- const losers = targets.filter((target) => target.supersededBy === null);
1047
- if (losers.length === 0) return {
1048
- survivorId: survivor.id,
1049
- supersededCount: 0,
1050
- supersededIds: []
1051
- };
1052
- const result = await store.consolidate({
1053
- entityId: input.entityId,
1054
- survivorId: survivor.id,
1055
- survivorTags: survivor.tags,
1056
- supersededIds: losers.map((loser) => loser.id),
1057
- mergeTags: input.mergeTags !== false,
1058
- now: /* @__PURE__ */ new Date(),
1059
- auditEnabled: config.audit?.enabled === true
1060
- });
1061
- return {
1062
- survivorId: survivor.id,
1063
- supersededCount: result.supersededIds.length,
1064
- supersededIds: result.supersededIds
1065
- };
1066
- }, (result) => ({ count: result.supersededCount }));
1067
- } },
1068
- async close() {
1069
- return observe(config, store.backend, "close", {}, async () => {
1070
- if (!closed) {
1071
- closed = true;
1072
- await store.close();
1073
- }
1074
- });
1075
- }
1076
- };
1077
- return client;
1078
- }
1079
- //#endregion
1
+ import { n as isMnemocyteError, t as MnemocyteError } from "./errors-DIPysVWl.mjs";
2
+ import { _ as cloneJsonObject, a as assertEmbedder, c as validateRetrievalConfig, d as IMPORTANCE_RANK, f as cosineSimilarity, g as createEventId, h as cloneMemory, i as createMemoryClient, l as assertPruneFilterHasSelector, m as toVectorScore, n as unwrapMnemocyteStoreConfig, o as assertNonEmptyString, p as createLexicalScorer, r as memoryHasDependentsError, s as validateProviderResilienceConfig, v as throwIfAborted, y as parsePostgresDatabaseUrl } from "./store-config-BFFTm1sj.mjs";
1080
3
  //#region src/memory/filters.ts
1081
4
  function isExpired(memory, now) {
1082
5
  return memory.expiresAt !== null && memory.expiresAt.getTime() <= now.getTime();
1083
6
  }
1084
7
  /**
1085
- * Test whether `memory` matches a {@link PruneInput}. All specified
8
+ * Test whether `memory` matches a validated prune filter. All specified
1086
9
  * selectors are AND-combined; unspecified selectors do not restrict.
1087
10
  *
1088
11
  * `now` is supplied by the caller so callers can share a consistent
@@ -1126,12 +49,6 @@ function matchesRecallFilter(memory, input, now) {
1126
49
  }
1127
50
  //#endregion
1128
51
  //#region src/memory/in-memory.ts
1129
- function cloneStoredMemory(memory) {
1130
- return {
1131
- ...cloneMemory(memory),
1132
- embedding: [...memory.embedding]
1133
- };
1134
- }
1135
52
  function cloneAuditEvent(event) {
1136
53
  return {
1137
54
  id: event.id,
@@ -1141,6 +58,15 @@ function cloneAuditEvent(event) {
1141
58
  timestamp: new Date(event.timestamp)
1142
59
  };
1143
60
  }
61
+ function compareAuditPositions(a, b) {
62
+ const timestampDifference = a.timestamp.getTime() - b.timestamp.getTime();
63
+ if (timestampDifference !== 0) return timestampDifference;
64
+ if (a.id === b.id) return 0;
65
+ return a.id < b.id ? -1 : 1;
66
+ }
67
+ function assertNoDependentMemories(memories, targetIds) {
68
+ for (const memory of memories.values()) if (memory.supersededBy !== null && targetIds.has(memory.supersededBy)) throw memoryHasDependentsError();
69
+ }
1144
70
  function createInMemoryStore() {
1145
71
  const memories = /* @__PURE__ */ new Map();
1146
72
  const auditLog = [];
@@ -1151,19 +77,18 @@ function createInMemoryStore() {
1151
77
  async insertMemories(rows) {
1152
78
  const inserted = [];
1153
79
  for (const row of rows) {
1154
- const stored = cloneStoredMemory(row);
1155
- memories.set(stored.id, stored);
1156
- inserted.push(cloneMemory(stored));
80
+ memories.set(row.id, row);
81
+ inserted.push(cloneMemory(row));
1157
82
  }
1158
83
  return inserted;
1159
84
  },
1160
85
  async vectorSearch(input) {
1161
86
  const now = /* @__PURE__ */ new Date();
1162
- const minScore = input.minScore ?? 0;
87
+ const minVectorScore = input.minVectorScore ?? 0;
1163
88
  return Array.from(memories.values()).filter((memory) => matchesRecallFilter(memory, input, now)).map((memory) => ({
1164
89
  memory: cloneMemory(memory),
1165
- vectorScore: Math.max(0, cosineSimilarity(memory.embedding, input.embedding))
1166
- })).filter((candidate) => candidate.vectorScore >= minScore).sort((a, b) => b.vectorScore - a.vectorScore).slice(0, input.limit);
90
+ vectorScore: toVectorScore(cosineSimilarity(memory.embedding, input.embedding))
91
+ })).filter((candidate) => candidate.vectorScore >= minVectorScore).sort((a, b) => b.vectorScore - a.vectorScore).slice(0, input.limit);
1167
92
  },
1168
93
  async lexicalSearch(input) {
1169
94
  const now = /* @__PURE__ */ new Date();
@@ -1183,50 +108,82 @@ function createInMemoryStore() {
1183
108
  },
1184
109
  async markMemoriesAccessed(memoryIds) {
1185
110
  const now = /* @__PURE__ */ new Date();
111
+ const updates = [];
1186
112
  for (const id of memoryIds) {
1187
113
  const memory = memories.get(id);
1188
114
  if (memory) {
1189
115
  memory.lastAccessedAt = now;
1190
116
  memory.accessCount += 1;
1191
117
  memory.updatedAt = now;
118
+ updates.push({
119
+ id: memory.id,
120
+ lastAccessedAt: now,
121
+ accessCount: memory.accessCount,
122
+ updatedAt: now
123
+ });
1192
124
  }
1193
125
  }
126
+ return updates;
1194
127
  },
1195
128
  async deleteMemory(entityId, memoryId) {
1196
129
  const memory = memories.get(memoryId);
1197
130
  if (!memory || memory.entityId !== entityId) return false;
131
+ assertNoDependentMemories(memories, new Set([memoryId]));
1198
132
  return memories.delete(memoryId);
1199
133
  },
1200
134
  async deleteMemoriesForEntity(entityId) {
1201
- let deleted = 0;
1202
- for (const [id, memory] of memories) if (memory.entityId === entityId) {
1203
- memories.delete(id);
1204
- deleted += 1;
1205
- }
1206
- return deleted;
1207
- },
1208
- async prune(input) {
135
+ const targetIds = new Set(Array.from(memories.values()).filter((memory) => memory.entityId === entityId).map((memory) => memory.id));
136
+ assertNoDependentMemories(memories, targetIds);
137
+ for (const id of targetIds) memories.delete(id);
138
+ return targetIds.size;
139
+ },
140
+ async prune(input, options) {
141
+ throwIfAborted(options?.signal);
142
+ assertPruneFilterHasSelector(input);
1209
143
  const now = /* @__PURE__ */ new Date();
1210
144
  const matched = [];
1211
- for (const memory of memories.values()) if (matchesPruneFilter(memory, input, now)) matched.push(memory.id);
145
+ for (const memory of memories.values()) {
146
+ throwIfAborted(options?.signal);
147
+ if (matchesPruneFilter(memory, input, now)) matched.push(memory);
148
+ }
1212
149
  const dryRun = input.dryRun === true;
1213
- if (!dryRun) for (const id of matched) memories.delete(id);
150
+ let deletedByEntity = [];
151
+ if (!dryRun) {
152
+ throwIfAborted(options?.signal);
153
+ assertNoDependentMemories(memories, new Set(matched.map((memory) => memory.id)));
154
+ const deletedCounts = /* @__PURE__ */ new Map();
155
+ for (const memory of matched) {
156
+ memories.delete(memory.id);
157
+ deletedCounts.set(memory.entityId, (deletedCounts.get(memory.entityId) ?? 0) + 1);
158
+ }
159
+ deletedByEntity = Array.from(deletedCounts, ([entityId, deletedCount]) => ({
160
+ entityId,
161
+ deletedCount
162
+ })).sort((a, b) => a.entityId.localeCompare(b.entityId));
163
+ }
1214
164
  return {
1215
165
  matchedCount: matched.length,
1216
166
  deletedCount: dryRun ? 0 : matched.length,
1217
- dryRun
167
+ dryRun,
168
+ deletedByEntity
1218
169
  };
1219
170
  },
1220
- async findDuplicatePairs(input) {
171
+ async findDuplicatePairs(input, options) {
172
+ throwIfAborted(options?.signal);
1221
173
  const threshold = input.threshold ?? .95;
1222
174
  const limit = input.limit ?? 100;
1223
175
  const now = /* @__PURE__ */ new Date();
1224
- const candidates = Array.from(memories.values()).filter((memory) => matchesDuplicateFilter(memory, input, now));
176
+ const candidates = Array.from(memories.values()).filter((memory) => {
177
+ throwIfAborted(options?.signal);
178
+ return matchesDuplicateFilter(memory, input, now);
179
+ });
1225
180
  const pairs = [];
1226
181
  for (let i = 0; i < candidates.length; i += 1) {
182
+ throwIfAborted(options?.signal);
1227
183
  const a = candidates[i];
1228
184
  if (a === void 0) continue;
1229
185
  for (let j = i + 1; j < candidates.length; j += 1) {
186
+ throwIfAborted(options?.signal);
1230
187
  const b = candidates[j];
1231
188
  if (b === void 0) continue;
1232
189
  const similarity = Math.max(0, Math.min(1, cosineSimilarity(a.embedding, b.embedding)));
@@ -1243,27 +200,29 @@ function createInMemoryStore() {
1243
200
  async addAuditEvents(events) {
1244
201
  auditLog.push(...events.map(cloneAuditEvent));
1245
202
  },
1246
- async listAuditLog(input) {
203
+ async listAuditLog(input, options) {
204
+ throwIfAborted(options?.signal);
1247
205
  const limit = input.limit ?? 50;
1248
- return auditLog.map((event, idx) => ({
1249
- event,
1250
- idx
1251
- })).filter(({ event }) => {
206
+ return auditLog.filter((event) => {
207
+ throwIfAborted(options?.signal);
1252
208
  if (event.entityId !== input.entityId) return false;
1253
209
  if (input.before !== void 0 && event.timestamp.getTime() >= input.before.getTime()) return false;
1254
210
  if (input.after !== void 0 && event.timestamp.getTime() <= input.after.getTime()) return false;
211
+ if (input.beforeCursor !== void 0 && compareAuditPositions(event, input.beforeCursor) >= 0) return false;
212
+ if (input.afterCursor !== void 0 && compareAuditPositions(event, input.afterCursor) <= 0) return false;
1255
213
  return true;
1256
214
  }).sort((a, b) => {
1257
- const dt = b.event.timestamp.getTime() - a.event.timestamp.getTime();
1258
- return dt === 0 ? b.idx - a.idx : dt;
1259
- }).slice(0, limit).map(({ event }) => cloneAuditEvent(event));
215
+ return compareAuditPositions(b, a);
216
+ }).slice(0, limit).map(cloneAuditEvent);
1260
217
  },
1261
- async getMemory(entityId, memoryId) {
218
+ async getMemory(entityId, memoryId, options) {
219
+ throwIfAborted(options?.signal);
1262
220
  const memory = memories.get(memoryId);
1263
221
  return memory && memory.entityId === entityId ? cloneMemory(memory) : null;
1264
222
  },
1265
- async loadConsolidationTargets(entityId, ids) {
223
+ async loadConsolidationTargets(entityId, ids, options) {
1266
224
  return ids.flatMap((id) => {
225
+ throwIfAborted(options?.signal);
1267
226
  const memory = memories.get(id);
1268
227
  if (!memory || memory.entityId !== entityId) return [];
1269
228
  return [{
@@ -1273,40 +232,51 @@ function createInMemoryStore() {
1273
232
  }];
1274
233
  });
1275
234
  },
1276
- async consolidate(input) {
235
+ async consolidate(input, options) {
236
+ throwIfAborted(options?.signal);
1277
237
  const survivor = memories.get(input.survivorId);
238
+ if (!survivor || survivor.entityId !== input.entityId || survivor.supersededBy !== null) throw new MnemocyteError("Consolidation survivor changed before mutation.", "CONFLICT");
1278
239
  const newlySuperseded = [];
240
+ const targets = [];
1279
241
  for (const id of input.supersededIds) {
242
+ throwIfAborted(options?.signal);
1280
243
  const memory = memories.get(id);
1281
244
  if (!memory || memory.entityId !== input.entityId) continue;
1282
- if (memory.supersededBy !== null) continue;
1283
- memory.supersededBy = input.survivorId;
1284
- memory.supersededAt = input.now;
1285
- memory.updatedAt = input.now;
1286
- newlySuperseded.push(memory.id);
245
+ targets.push(memory);
1287
246
  }
1288
- if (survivor && input.mergeTags && newlySuperseded.length > 0) {
1289
- const mergedTags = new Set(input.survivorTags);
1290
- for (const id of newlySuperseded) {
1291
- const memory = memories.get(id);
1292
- if (memory) for (const tag of memory.tags) mergedTags.add(tag);
1293
- }
1294
- if (mergedTags.size !== survivor.tags.length) {
1295
- survivor.tags = [...mergedTags];
1296
- survivor.updatedAt = input.now;
247
+ if (targets.some((memory) => memory.supersededBy !== null && memory.supersededBy !== input.survivorId)) throw new MnemocyteError("Superseded memory already belongs to a different survivor.", "CONFLICT");
248
+ for (const memory of targets) if (memory.supersededBy === null) newlySuperseded.push(memory);
249
+ let mergedSurvivorTags;
250
+ if (input.mergeTags && newlySuperseded.length > 0) {
251
+ const mergedTags = new Set(survivor.tags);
252
+ for (const memory of newlySuperseded) {
253
+ throwIfAborted(options?.signal);
254
+ for (const tag of memory.tags) mergedTags.add(tag);
1297
255
  }
256
+ if (mergedTags.size !== survivor.tags.length) mergedSurvivorTags = [...mergedTags];
1298
257
  }
1299
- if (input.auditEnabled) for (const id of newlySuperseded) auditLog.push({
258
+ const auditEvents = input.auditEnabled ? newlySuperseded.map((memory) => ({
1300
259
  id: createEventId(),
1301
260
  entityId: input.entityId,
1302
261
  description: "memory.superseded",
1303
262
  metadata: {
1304
- memoryId: id,
263
+ memoryId: memory.id,
1305
264
  supersededBy: input.survivorId
1306
265
  },
1307
266
  timestamp: input.now
1308
- });
1309
- return { supersededIds: newlySuperseded };
267
+ })) : [];
268
+ throwIfAborted(options?.signal);
269
+ for (const memory of newlySuperseded) {
270
+ memory.supersededBy = input.survivorId;
271
+ memory.supersededAt = input.now;
272
+ memory.updatedAt = input.now;
273
+ }
274
+ if (mergedSurvivorTags) {
275
+ survivor.tags = mergedSurvivorTags;
276
+ survivor.updatedAt = input.now;
277
+ }
278
+ if (input.auditEnabled) auditLog.push(...auditEvents);
279
+ return { supersededIds: newlySuperseded.map((memory) => memory.id) };
1310
280
  },
1311
281
  async stats(input, now) {
1312
282
  const allMemories = Array.from(memories.values());
@@ -1339,611 +309,93 @@ function createInMemoryClient(config) {
1339
309
  return createMemoryClient(config, createInMemoryStore());
1340
310
  }
1341
311
  //#endregion
1342
- //#region src/db/queries/events.ts
1343
- async function insertEvent(db, row) {
1344
- const inserted = (await db.insert(eventsTable).values(row).returning())[0];
1345
- if (!inserted) throw new MnemocyteError("Event insert returned no rows.", "DB");
1346
- return inserted;
1347
- }
1348
- async function listEvents(db, filter) {
1349
- return db.select().from(eventsTable).where(and(eq(eventsTable.entityId, filter.entityId), filter.before ? lt(eventsTable.timestamp, filter.before) : void 0, filter.after ? gt(eventsTable.timestamp, filter.after) : void 0)).orderBy(desc(eventsTable.timestamp)).limit(filter.limit ?? 50);
1350
- }
1351
- //#endregion
1352
- //#region src/db/queries/memories.ts
1353
- function vectorLiteral(embedding) {
1354
- return `[${embedding.map(formatVectorComponent).join(",")}]`;
1355
- }
1356
- function timestampParam(value) {
1357
- return sql`${value.toISOString()}::timestamptz`;
1358
- }
1359
- function textArrayLiteral(values) {
1360
- return sql`ARRAY[${sql.join(values.map((value) => sql`${value}`), sql`, `)}]::text[]`;
1361
- }
1362
- function tagsContainAll(tags) {
1363
- return tags && tags.length > 0 ? sql`${memoriesTable.tags} @> ${textArrayLiteral(tags)}` : void 0;
1364
- }
1365
- function rawTagsFilter(tags) {
1366
- return tags && tags.length > 0 ? sql`AND tags @> ${textArrayLiteral(tags)}` : sql``;
1367
- }
1368
- function duplicateTagsFilter(tags) {
1369
- return tags && tags.length > 0 ? sql`AND a.tags @> ${textArrayLiteral(tags)} AND b.tags @> ${textArrayLiteral(tags)}` : sql``;
1370
- }
1371
- function memoryStatsCountFields(now) {
1372
- return {
1373
- memoryCount: count(),
1374
- activeMemoryCount: sql`
1375
- (count(*) FILTER (
1376
- WHERE ${memoriesTable.supersededBy} IS NULL
1377
- AND (${memoriesTable.expiresAt} IS NULL OR ${memoriesTable.expiresAt} > ${timestampParam(now)})
1378
- ))::int
1379
- `.mapWith(Number),
1380
- expiredMemoryCount: sql`
1381
- (count(*) FILTER (
1382
- WHERE ${memoriesTable.expiresAt} IS NOT NULL
1383
- AND ${memoriesTable.expiresAt} <= ${timestampParam(now)}
1384
- ))::int
1385
- `.mapWith(Number),
1386
- supersededMemoryCount: sql`(count(*) FILTER (WHERE ${memoriesTable.supersededBy} IS NOT NULL))::int`.mapWith(Number)
1387
- };
1388
- }
1389
- function emptyMemoryStatsCounts() {
1390
- return {
1391
- memoryCount: 0,
1392
- activeMemoryCount: 0,
1393
- expiredMemoryCount: 0,
1394
- supersededMemoryCount: 0
1395
- };
1396
- }
1397
- async function insertMemories(db, rows) {
1398
- if (rows.length === 0) return [];
1399
- return db.insert(memoriesTable).values(rows).returning();
1400
- }
1401
- async function getMemoryById(db, entityId, memoryId) {
1402
- return (await db.select().from(memoriesTable).where(and(eq(memoriesTable.entityId, entityId), eq(memoriesTable.id, memoryId))).limit(1))[0] ?? null;
1403
- }
1404
- async function getEntityMemoryStatsCounts(db, entityId, now) {
1405
- return (await db.select(memoryStatsCountFields(now)).from(memoriesTable).where(eq(memoriesTable.entityId, entityId)))[0] ?? emptyMemoryStatsCounts();
1406
- }
1407
- async function getGlobalMemoryStatsCounts(db, now) {
1408
- return (await db.select({
1409
- entityCount: countDistinct(memoriesTable.entityId),
1410
- ...memoryStatsCountFields(now)
1411
- }).from(memoriesTable))[0] ?? {
1412
- entityCount: 0,
1413
- ...emptyMemoryStatsCounts()
1414
- };
1415
- }
1416
- async function deleteMemory(db, entityId, memoryId) {
1417
- return (await db.delete(memoriesTable).where(and(eq(memoriesTable.entityId, entityId), eq(memoriesTable.id, memoryId))).returning({ id: memoriesTable.id })).length > 0;
1418
- }
1419
- async function deleteMemoriesForEntity(db, entityId) {
1420
- return (await db.delete(memoriesTable).where(eq(memoriesTable.entityId, entityId)).returning({ id: memoriesTable.id })).length;
1421
- }
1422
- function pruneConditions(filter) {
1423
- return and(filter.entityId !== void 0 ? eq(memoriesTable.entityId, filter.entityId) : void 0, filter.expired ? and(isNotNull(memoriesTable.expiresAt), sql`${memoriesTable.expiresAt} <= now()`) : void 0, filter.superseded ? isNotNull(memoriesTable.supersededBy) : void 0, filter.createdBefore ? lt(memoriesTable.createdAt, filter.createdBefore) : void 0, filter.notAccessedSince ? or(isNull(memoriesTable.lastAccessedAt), lt(memoriesTable.lastAccessedAt, filter.notAccessedSince)) : void 0, filter.types && filter.types.length > 0 ? inArray(memoriesTable.type, [...filter.types]) : void 0, tagsContainAll(filter.tags), filter.maxImportanceLevels && filter.maxImportanceLevels.length > 0 ? inArray(memoriesTable.importance, [...filter.maxImportanceLevels]) : void 0);
1424
- }
1425
- async function countPruneMatches(db, filter) {
1426
- return db.$count(memoriesTable, pruneConditions(filter));
1427
- }
1428
- async function pruneMemories(db, filter) {
1429
- return (await db.delete(memoriesTable).where(pruneConditions(filter)).returning({ id: memoriesTable.id })).length;
1430
- }
1431
- async function findDuplicatePairs(db, input) {
1432
- return (await db.execute(sql`
1433
- SELECT
1434
- a.id AS "aId",
1435
- a.entity_id AS "aEntityId",
1436
- a.content AS "aContent",
1437
- a.type AS "aType",
1438
- a.importance AS "aImportance",
1439
- a.tags AS "aTags",
1440
- a.source AS "aSource",
1441
- a.metadata AS "aMetadata",
1442
- a.confidence AS "aConfidence",
1443
- a.embedding AS "aEmbedding",
1444
- a.embedding_model AS "aEmbeddingModel",
1445
- a.embedding_dimensions AS "aEmbeddingDimensions",
1446
- a.superseded_by AS "aSupersededBy",
1447
- a.superseded_at AS "aSupersededAt",
1448
- a.expires_at AS "aExpiresAt",
1449
- a.last_accessed_at AS "aLastAccessedAt",
1450
- a.access_count AS "aAccessCount",
1451
- a.created_at AS "aCreatedAt",
1452
- a.updated_at AS "aUpdatedAt",
1453
- b.id AS "bId",
1454
- b.entity_id AS "bEntityId",
1455
- b.content AS "bContent",
1456
- b.type AS "bType",
1457
- b.importance AS "bImportance",
1458
- b.tags AS "bTags",
1459
- b.source AS "bSource",
1460
- b.metadata AS "bMetadata",
1461
- b.confidence AS "bConfidence",
1462
- b.embedding AS "bEmbedding",
1463
- b.embedding_model AS "bEmbeddingModel",
1464
- b.embedding_dimensions AS "bEmbeddingDimensions",
1465
- b.superseded_by AS "bSupersededBy",
1466
- b.superseded_at AS "bSupersededAt",
1467
- b.expires_at AS "bExpiresAt",
1468
- b.last_accessed_at AS "bLastAccessedAt",
1469
- b.access_count AS "bAccessCount",
1470
- b.created_at AS "bCreatedAt",
1471
- b.updated_at AS "bUpdatedAt",
1472
- 1 - (a.embedding <=> b.embedding) AS "similarity"
1473
- FROM mnemocyte_memories a
1474
- JOIN mnemocyte_memories b
1475
- ON b.entity_id = a.entity_id
1476
- AND a.id < b.id
1477
- AND a.embedding IS NOT NULL
1478
- AND b.embedding IS NOT NULL
1479
- WHERE
1480
- a.entity_id = ${input.entityId}
1481
- ${input.includeSuperseded ? sql`` : sql`AND a.superseded_by IS NULL AND b.superseded_by IS NULL`}
1482
- ${input.includeExpired ? sql`` : sql`AND (a.expires_at IS NULL OR a.expires_at > now()) AND (b.expires_at IS NULL OR b.expires_at > now())`}
1483
- ${input.types && input.types.length > 0 ? sql`AND a.type IN (${sql.join(input.types.map((type) => sql`${type}`), sql`, `)}) AND b.type IN (${sql.join(input.types.map((type) => sql`${type}`), sql`, `)})` : sql``}
1484
- ${duplicateTagsFilter(input.tags)}
1485
- AND 1 - (a.embedding <=> b.embedding) >= ${input.threshold}
1486
- ORDER BY "similarity" DESC
1487
- LIMIT ${input.limit}
1488
- `)).map((row) => {
1489
- return {
1490
- a: {
1491
- id: row.aId,
1492
- entityId: row.aEntityId,
1493
- content: row.aContent,
1494
- type: row.aType,
1495
- importance: row.aImportance,
1496
- tags: row.aTags,
1497
- source: row.aSource,
1498
- metadata: row.aMetadata,
1499
- confidence: row.aConfidence,
1500
- embedding: row.aEmbedding,
1501
- embeddingModel: row.aEmbeddingModel,
1502
- embeddingDimensions: row.aEmbeddingDimensions,
1503
- supersededBy: row.aSupersededBy,
1504
- supersededAt: row.aSupersededAt,
1505
- expiresAt: row.aExpiresAt,
1506
- lastAccessedAt: row.aLastAccessedAt,
1507
- accessCount: row.aAccessCount,
1508
- createdAt: row.aCreatedAt,
1509
- updatedAt: row.aUpdatedAt
1510
- },
1511
- b: {
1512
- id: row.bId,
1513
- entityId: row.bEntityId,
1514
- content: row.bContent,
1515
- type: row.bType,
1516
- importance: row.bImportance,
1517
- tags: row.bTags,
1518
- source: row.bSource,
1519
- metadata: row.bMetadata,
1520
- confidence: row.bConfidence,
1521
- embedding: row.bEmbedding,
1522
- embeddingModel: row.bEmbeddingModel,
1523
- embeddingDimensions: row.bEmbeddingDimensions,
1524
- supersededBy: row.bSupersededBy,
1525
- supersededAt: row.bSupersededAt,
1526
- expiresAt: row.bExpiresAt,
1527
- lastAccessedAt: row.bLastAccessedAt,
1528
- accessCount: row.bAccessCount,
1529
- createdAt: row.bCreatedAt,
1530
- updatedAt: row.bUpdatedAt
1531
- },
1532
- similarity: Number(row.similarity) || 0
1533
- };
1534
- });
1535
- }
1536
- async function loadConsolidationTargets(db, entityId, ids) {
1537
- if (ids.length === 0) return [];
1538
- return await db.select({
1539
- id: memoriesTable.id,
1540
- tags: memoriesTable.tags,
1541
- supersededBy: memoriesTable.supersededBy
1542
- }).from(memoriesTable).where(and(eq(memoriesTable.entityId, entityId), inArray(memoriesTable.id, [...ids])));
1543
- }
1544
- async function markMemoriesSuperseded(db, params) {
1545
- if (params.ids.length === 0) return [];
1546
- return await db.update(memoriesTable).set({
1547
- supersededBy: params.survivorId,
1548
- supersededAt: params.now,
1549
- updatedAt: params.now
1550
- }).where(and(eq(memoriesTable.entityId, params.entityId), inArray(memoriesTable.id, [...params.ids]), isNull(memoriesTable.supersededBy))).returning({
1551
- id: memoriesTable.id,
1552
- tags: memoriesTable.tags
1553
- });
1554
- }
1555
- async function setMemoryTags(db, params) {
1556
- await db.update(memoriesTable).set({
1557
- tags: [...params.tags],
1558
- updatedAt: params.now
1559
- }).where(and(eq(memoriesTable.entityId, params.entityId), eq(memoriesTable.id, params.memoryId)));
1560
- }
1561
- async function markMemoriesAccessed(db, memoryIds) {
1562
- const now = /* @__PURE__ */ new Date();
1563
- if (memoryIds.length === 0) return;
1564
- await db.update(memoriesTable).set({
1565
- lastAccessedAt: now,
1566
- accessCount: sql`${memoriesTable.accessCount} + 1`,
1567
- updatedAt: now
1568
- }).where(inArray(memoriesTable.id, [...memoryIds]));
1569
- }
1570
- async function getMemoryEmbeddings(db, memoryIds) {
1571
- if (memoryIds.length === 0) return /* @__PURE__ */ new Map();
1572
- const rows = await db.select({
1573
- id: memoriesTable.id,
1574
- embedding: memoriesTable.embedding
1575
- }).from(memoriesTable).where(inArray(memoriesTable.id, [...memoryIds]));
1576
- return new Map(rows.flatMap((row) => row.embedding ? [[row.id, row.embedding]] : []));
1577
- }
1578
- async function vectorSearch(db, input) {
1579
- const embedding = vectorLiteral(input.embedding);
1580
- const minScore = input.minScore ?? 0;
1581
- return await db.execute(sql`
1582
- SELECT
1583
- id,
1584
- entity_id AS "entityId",
1585
- content,
1586
- type,
1587
- importance,
1588
- tags,
1589
- source,
1590
- metadata,
1591
- confidence,
1592
- embedding_model AS "embeddingModel",
1593
- embedding_dimensions AS "embeddingDimensions",
1594
- superseded_by AS "supersededBy",
1595
- superseded_at AS "supersededAt",
1596
- expires_at AS "expiresAt",
1597
- last_accessed_at AS "lastAccessedAt",
1598
- access_count AS "accessCount",
1599
- created_at AS "createdAt",
1600
- updated_at AS "updatedAt",
1601
- 1 - (embedding <=> ${embedding}::vector) AS "vectorScore"
1602
- FROM mnemocyte_memories
1603
- WHERE
1604
- entity_id = ${input.entityId}
1605
- AND embedding IS NOT NULL
1606
- ${input.includeSuperseded ? sql`` : sql`AND superseded_by IS NULL`}
1607
- ${input.includeExpired ? sql`` : sql`AND (expires_at IS NULL OR expires_at > now())`}
1608
- ${input.types && input.types.length > 0 ? sql`AND type IN (${sql.join(input.types.map((type) => sql`${type}`), sql`, `)})` : sql``}
1609
- ${rawTagsFilter(input.tags)}
1610
- ${input.before ? sql`AND created_at < ${timestampParam(input.before)}` : sql``}
1611
- ${input.after ? sql`AND created_at > ${timestampParam(input.after)}` : sql``}
1612
- AND 1 - (embedding <=> ${embedding}::vector) >= ${minScore}
1613
- ORDER BY embedding <=> ${embedding}::vector
1614
- LIMIT ${input.limit}
1615
- `);
1616
- }
1617
- async function lexicalSearch(db, input) {
1618
- return await db.execute(sql`
1619
- SELECT
1620
- id,
1621
- entity_id AS "entityId",
1622
- content,
1623
- type,
1624
- importance,
1625
- tags,
1626
- source,
1627
- metadata,
1628
- confidence,
1629
- embedding_model AS "embeddingModel",
1630
- embedding_dimensions AS "embeddingDimensions",
1631
- superseded_by AS "supersededBy",
1632
- superseded_at AS "supersededAt",
1633
- expires_at AS "expiresAt",
1634
- last_accessed_at AS "lastAccessedAt",
1635
- access_count AS "accessCount",
1636
- created_at AS "createdAt",
1637
- updated_at AS "updatedAt",
1638
- ts_rank(to_tsvector('english', content), websearch_to_tsquery('english', ${input.query})) AS "lexicalScore"
1639
- FROM mnemocyte_memories
1640
- WHERE
1641
- entity_id = ${input.entityId}
1642
- ${input.includeSuperseded ? sql`` : sql`AND superseded_by IS NULL`}
1643
- ${input.includeExpired ? sql`` : sql`AND (expires_at IS NULL OR expires_at > now())`}
1644
- ${input.types && input.types.length > 0 ? sql`AND type IN (${sql.join(input.types.map((type) => sql`${type}`), sql`, `)})` : sql``}
1645
- ${rawTagsFilter(input.tags)}
1646
- ${input.before ? sql`AND created_at < ${timestampParam(input.before)}` : sql``}
1647
- ${input.after ? sql`AND created_at > ${timestampParam(input.after)}` : sql``}
1648
- AND to_tsvector('english', content) @@ websearch_to_tsquery('english', ${input.query})
1649
- ORDER BY "lexicalScore" DESC
1650
- LIMIT ${input.limit}
1651
- `);
1652
- }
1653
- async function getInstallationMeta(db) {
1654
- return (await db.select().from(metaTable).where(eq(metaTable.key, "installation")).limit(1))[0] ?? null;
1655
- }
1656
- //#endregion
1657
- //#region src/memory/postgres-records.ts
1658
- function toDate(value) {
1659
- return value instanceof Date ? value : new Date(value);
1660
- }
1661
- function toNullableDate(value) {
1662
- return value === null ? null : toDate(value);
1663
- }
1664
- function rowToMemory(row) {
1665
- return {
1666
- id: row.id,
1667
- entityId: row.entityId,
1668
- content: row.content,
1669
- type: row.type,
1670
- importance: row.importance,
1671
- tags: row.tags,
1672
- source: row.source,
1673
- metadata: cloneJsonObject(row.metadata),
1674
- confidence: row.confidence,
1675
- embeddingModel: row.embeddingModel,
1676
- embeddingDimensions: row.embeddingDimensions,
1677
- supersededBy: row.supersededBy,
1678
- supersededAt: toNullableDate(row.supersededAt),
1679
- expiresAt: toNullableDate(row.expiresAt),
1680
- lastAccessedAt: toNullableDate(row.lastAccessedAt),
1681
- accessCount: row.accessCount,
1682
- createdAt: toDate(row.createdAt),
1683
- updatedAt: toDate(row.updatedAt)
1684
- };
1685
- }
1686
- //#endregion
1687
- //#region src/memory/postgres.ts
1688
- const IMPORTANCE_LEVELS = [
1689
- "low",
1690
- "normal",
1691
- "high",
1692
- "critical"
1693
- ];
1694
- const MIGRATION_ERROR_CODES = new Set([
1695
- "42P01",
1696
- "42703",
1697
- "42704",
1698
- "42883"
1699
- ]);
1700
- function rowToAuditEvent(row) {
1701
- return {
1702
- id: row.id,
1703
- entityId: row.entityId,
1704
- description: row.description,
1705
- metadata: cloneJsonObject(row.metadata),
1706
- timestamp: row.timestamp
1707
- };
1708
- }
1709
- function importanceCeilingLevels(max) {
1710
- return IMPORTANCE_LEVELS.filter((level) => IMPORTANCE_RANK[level] <= IMPORTANCE_RANK[max]);
1711
- }
1712
- function hasPostgresErrorCode(error, code) {
1713
- return typeof error === "object" && error !== null && "code" in error && error.code === code;
1714
- }
1715
- function getPostgresErrorCode(error) {
1716
- return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
1717
- }
1718
- function normalizePostgresError(error) {
1719
- if (error instanceof MnemocyteError) return error;
1720
- const code = getPostgresErrorCode(error);
1721
- if (code && MIGRATION_ERROR_CODES.has(code)) return new MnemocyteError("Postgres schema is missing or incompatible. Apply the bundled Mnemocyte migrations before using the Postgres backend.", "MIGRATION", error);
1722
- return new MnemocyteError("Postgres operation failed.", "DB", error);
1723
- }
1724
- async function runPostgresOperation(operation) {
1725
- try {
1726
- return await operation();
1727
- } catch (error) {
1728
- throw normalizePostgresError(error);
1729
- }
1730
- }
1731
- function toPruneFilter(input) {
1732
- const filter = {};
1733
- if (input.entityId !== void 0) filter.entityId = input.entityId;
1734
- if (input.expired === true) filter.expired = true;
1735
- if (input.superseded === true) filter.superseded = true;
1736
- if (input.createdBefore !== void 0) filter.createdBefore = input.createdBefore;
1737
- if (input.notAccessedSince !== void 0) filter.notAccessedSince = input.notAccessedSince;
1738
- if (input.types !== void 0 && input.types.length > 0) filter.types = input.types;
1739
- if (input.tags !== void 0 && input.tags.length > 0) filter.tags = input.tags;
1740
- if (input.maxImportance !== void 0) filter.maxImportanceLevels = importanceCeilingLevels(input.maxImportance);
1741
- return filter;
1742
- }
1743
- function toMemoryRow(memory) {
1744
- return {
1745
- ...memory,
1746
- tags: [...memory.tags],
1747
- metadata: cloneJsonObject(memory.metadata),
1748
- embedding: [...memory.embedding]
1749
- };
1750
- }
1751
- function createPostgresStore(handle) {
1752
- let schemaValidated = false;
1753
- let schemaValidationPromise;
1754
- async function ensureEmbeddingCompatibility(embedder) {
1755
- if (schemaValidated) return;
1756
- if (!schemaValidationPromise) schemaValidationPromise = validateEmbeddingCompatibility(embedder).catch((error) => {
1757
- schemaValidationPromise = void 0;
1758
- throw error;
1759
- });
1760
- await schemaValidationPromise;
1761
- }
1762
- async function validateEmbeddingCompatibility(embedder) {
1763
- let meta;
1764
- try {
1765
- meta = await getInstallationMeta(handle.db);
1766
- } catch (error) {
1767
- if (hasPostgresErrorCode(error, "42P01")) throw new MnemocyteError("mnemocyte_meta is missing. Apply the v0.2.0 migration or render a dimension-specific initial migration before using the Postgres backend.", "MIGRATION", error);
1768
- throw normalizePostgresError(error);
1769
- }
1770
- if (!meta) throw new MnemocyteError("mnemocyte_meta is missing the \"installation\" row. Apply the v0.2.0 migration or render a dimension-specific initial migration before using the Postgres backend.", "MIGRATION");
1771
- if (meta.embeddingDimensions !== embedder.dimensions) throw new MnemocyteError(`embedder.dimensions (${embedder.dimensions}) must match mnemocyte_meta.embedding_dimensions (${meta.embeddingDimensions}). Render and apply a migration for the selected embedding dimension, or configure an embedder that matches this installation.`, "CONFIG");
1772
- schemaValidated = true;
312
+ //#region src/memory/lazy-postgres.ts
313
+ function createLazyPostgresStore(databaseUrl) {
314
+ let storePromise;
315
+ function loadStore() {
316
+ storePromise ??= import("./postgres-runtime-DxJxns07.mjs").then(({ createPostgresStoreFromUrl }) => createPostgresStoreFromUrl(databaseUrl));
317
+ return storePromise;
1773
318
  }
1774
319
  return {
1775
320
  backend: "postgres",
1776
- async ensureSchema() {},
1777
- ensureEmbeddingCompatibility,
321
+ async ensureSchema() {
322
+ return (await loadStore()).ensureSchema();
323
+ },
324
+ async ensureEmbeddingCompatibility(embedder) {
325
+ return (await loadStore()).ensureEmbeddingCompatibility(embedder);
326
+ },
1778
327
  async insertMemories(memories) {
1779
- return runPostgresOperation(async () => {
1780
- return (await insertMemories(handle.db, memories.map(toMemoryRow))).map(rowToMemory);
1781
- });
328
+ return (await loadStore()).insertMemories(memories);
1782
329
  },
1783
330
  async vectorSearch(input) {
1784
- return runPostgresOperation(async () => {
1785
- return (await vectorSearch(handle.db, input)).map((row) => ({
1786
- memory: rowToMemory(row),
1787
- vectorScore: row.vectorScore
1788
- }));
1789
- });
331
+ return (await loadStore()).vectorSearch(input);
1790
332
  },
1791
333
  async lexicalSearch(input) {
1792
- return runPostgresOperation(async () => {
1793
- return (await lexicalSearch(handle.db, input)).map((row) => ({
1794
- memory: rowToMemory(row),
1795
- lexicalScore: row.lexicalScore
1796
- }));
1797
- });
334
+ return (await loadStore()).lexicalSearch(input);
1798
335
  },
1799
336
  async getMemoryEmbeddings(memoryIds) {
1800
- return runPostgresOperation(() => getMemoryEmbeddings(handle.db, memoryIds));
337
+ return (await loadStore()).getMemoryEmbeddings(memoryIds);
1801
338
  },
1802
339
  async markMemoriesAccessed(memoryIds) {
1803
- return runPostgresOperation(() => markMemoriesAccessed(handle.db, memoryIds));
340
+ return (await loadStore()).markMemoriesAccessed(memoryIds);
1804
341
  },
1805
342
  async deleteMemory(entityId, memoryId) {
1806
- return runPostgresOperation(() => deleteMemory(handle.db, entityId, memoryId));
343
+ return (await loadStore()).deleteMemory(entityId, memoryId);
1807
344
  },
1808
345
  async deleteMemoriesForEntity(entityId) {
1809
- return runPostgresOperation(() => deleteMemoriesForEntity(handle.db, entityId));
346
+ return (await loadStore()).deleteMemoriesForEntity(entityId);
1810
347
  },
1811
- async prune(input) {
1812
- return runPostgresOperation(async () => {
1813
- const filter = toPruneFilter(input);
1814
- if (input.dryRun === true) return {
1815
- matchedCount: await countPruneMatches(handle.db, filter),
1816
- deletedCount: 0,
1817
- dryRun: true
1818
- };
1819
- const deletedCount = await pruneMemories(handle.db, filter);
1820
- return {
1821
- matchedCount: deletedCount,
1822
- deletedCount,
1823
- dryRun: false
1824
- };
1825
- });
348
+ async prune(input, options) {
349
+ return (await loadStore()).prune(input, options);
1826
350
  },
1827
- async findDuplicatePairs(input) {
1828
- return runPostgresOperation(async () => {
1829
- return (await findDuplicatePairs(handle.db, {
1830
- entityId: input.entityId,
1831
- threshold: input.threshold ?? .95,
1832
- limit: input.limit ?? 100,
1833
- ...input.types === void 0 ? {} : { types: input.types },
1834
- ...input.tags === void 0 ? {} : { tags: input.tags },
1835
- ...input.includeSuperseded === void 0 ? {} : { includeSuperseded: input.includeSuperseded },
1836
- ...input.includeExpired === void 0 ? {} : { includeExpired: input.includeExpired }
1837
- })).map((row) => ({
1838
- a: rowToMemory(row.a),
1839
- b: rowToMemory(row.b),
1840
- similarity: Math.max(0, Math.min(1, row.similarity))
1841
- }));
1842
- });
351
+ async findDuplicatePairs(input, options) {
352
+ return (await loadStore()).findDuplicatePairs(input, options);
1843
353
  },
1844
354
  async addAuditEvents(events) {
1845
- return runPostgresOperation(async () => {
1846
- for (const event of events) await insertEvent(handle.db, {
1847
- id: event.id,
1848
- entityId: event.entityId,
1849
- description: event.description,
1850
- metadata: cloneJsonObject(event.metadata),
1851
- timestamp: event.timestamp
1852
- });
1853
- });
355
+ return (await loadStore()).addAuditEvents(events);
1854
356
  },
1855
- async listAuditLog(input) {
1856
- return runPostgresOperation(async () => {
1857
- return (await listEvents(handle.db, {
1858
- entityId: input.entityId,
1859
- limit: input.limit ?? 50,
1860
- ...input.before === void 0 ? {} : { before: input.before },
1861
- ...input.after === void 0 ? {} : { after: input.after }
1862
- })).map(rowToAuditEvent);
1863
- });
357
+ async listAuditLog(input, options) {
358
+ return (await loadStore()).listAuditLog(input, options);
1864
359
  },
1865
- async getMemory(entityId, memoryId) {
1866
- return runPostgresOperation(async () => {
1867
- const row = await getMemoryById(handle.db, entityId, memoryId);
1868
- return row ? rowToMemory(row) : null;
1869
- });
360
+ async getMemory(entityId, memoryId, options) {
361
+ return (await loadStore()).getMemory(entityId, memoryId, options);
1870
362
  },
1871
- async loadConsolidationTargets(entityId, ids) {
1872
- return runPostgresOperation(() => loadConsolidationTargets(handle.db, entityId, ids));
363
+ async loadConsolidationTargets(entityId, ids, options) {
364
+ return (await loadStore()).loadConsolidationTargets(entityId, ids, options);
1873
365
  },
1874
- async consolidate(input) {
1875
- return runPostgresOperation(async () => {
1876
- return { supersededIds: await handle.db.transaction(async (tx) => {
1877
- const updated = await markMemoriesSuperseded(tx, {
1878
- survivorId: input.survivorId,
1879
- entityId: input.entityId,
1880
- ids: input.supersededIds,
1881
- now: input.now
1882
- });
1883
- const ids = updated.map((row) => row.id);
1884
- if (input.auditEnabled) for (const id of ids) await insertEvent(tx, {
1885
- id: createEventId(),
1886
- entityId: input.entityId,
1887
- description: "memory.superseded",
1888
- metadata: {
1889
- memoryId: id,
1890
- supersededBy: input.survivorId
1891
- },
1892
- timestamp: input.now
1893
- });
1894
- if (input.mergeTags && updated.length > 0) {
1895
- const mergedTags = new Set(input.survivorTags);
1896
- for (const row of updated) for (const tag of row.tags) mergedTags.add(tag);
1897
- if (mergedTags.size !== input.survivorTags.length) await setMemoryTags(tx, {
1898
- entityId: input.entityId,
1899
- memoryId: input.survivorId,
1900
- tags: [...mergedTags],
1901
- now: input.now
1902
- });
1903
- }
1904
- return ids;
1905
- }) };
1906
- });
366
+ async consolidate(input, options) {
367
+ return (await loadStore()).consolidate(input, options);
1907
368
  },
1908
369
  async stats(input, now) {
1909
- return runPostgresOperation(async () => {
1910
- if (input?.entityId) {
1911
- const counts = await getEntityMemoryStatsCounts(handle.db, input.entityId, now);
1912
- return {
1913
- entityId: input.entityId,
1914
- ...counts
1915
- };
1916
- }
1917
- return await getGlobalMemoryStatsCounts(handle.db, now);
1918
- });
370
+ return (await loadStore()).stats(input, now);
1919
371
  },
1920
372
  async close() {
1921
- return runPostgresOperation(() => handle.close());
373
+ return (await loadStore()).close();
1922
374
  }
1923
375
  };
1924
376
  }
1925
- function createPostgresClient(config, handle) {
1926
- return createMemoryClient(config, createPostgresStore(handle));
1927
- }
1928
377
  //#endregion
1929
378
  //#region src/client.ts
1930
379
  /**
1931
380
  * Create a Mnemocyte client.
1932
381
  *
1933
- * When {@link MnemocyteConfig.databaseUrl} is provided, a Postgres-backed
1934
- * client is returned (requires the `pgvector` extension and the bundled
1935
- * migration applied). Otherwise an in-memory client is returned, intended
1936
- * for tests, demos, and short-lived processes.
382
+ * When {@link MnemocyteConfig.store} is provided, that adapter is used. When
383
+ * {@link MnemocyteConfig.databaseUrl} is provided, a URL-owned Postgres client
384
+ * is used (requires the `pgvector` extension and the bundled migrations).
385
+ * When neither is supplied, an in-memory client is returned for tests, demos,
386
+ * and short-lived processes.
1937
387
  *
1938
- * The returned client owns any underlying resources (e.g. the Postgres
1939
- * connection pool). Call {@link MnemocyteClient.close} when finished to
1940
- * release them.
388
+ * Call {@link MnemocyteClient.close} when finished. URL-created database
389
+ * resources are owned and closed by Mnemocyte; resources behind a supplied
390
+ * store retain the ownership policy documented by that adapter.
1941
391
  *
1942
392
  * @param config - Client configuration. {@link MnemocyteConfig.embedder} is required.
1943
393
  * @returns A {@link MnemocyteClient} instance.
1944
394
  * @throws {MnemocyteError} With code `"CONFIG"` if `embedder`, retrieval
1945
- * tuning, or the database URL is malformed, or `"VALIDATION"` if
1946
- * `databaseUrl` is explicitly empty.
395
+ * tuning, provider resilience, or the database URL is malformed or does not
396
+ * use the `postgres:` / `postgresql:` protocol, or if `databaseUrl` and
397
+ * `store` are both provided; or `"VALIDATION"` if `databaseUrl` is explicitly
398
+ * empty.
1947
399
  *
1948
400
  * @example Postgres-backed client
1949
401
  * ```ts
@@ -1956,10 +408,14 @@ function createPostgresClient(config, handle) {
1956
408
  function createMnemocyte(config) {
1957
409
  assertEmbedder(config.embedder);
1958
410
  validateRetrievalConfig(config.retrieval);
411
+ validateProviderResilienceConfig(config.provider);
412
+ if (config.databaseUrl !== void 0 && config.store !== void 0) throw new MnemocyteError("databaseUrl and store cannot be provided together.", "CONFIG");
1959
413
  if (config.databaseUrl !== void 0) {
1960
414
  assertNonEmptyString(config.databaseUrl, "databaseUrl");
1961
- return createPostgresClient(config, createDatabase(config.databaseUrl));
415
+ parsePostgresDatabaseUrl(config.databaseUrl);
416
+ return createMemoryClient(config, createLazyPostgresStore(config.databaseUrl));
1962
417
  }
418
+ if (config.store !== void 0) return createMemoryClient(config, unwrapMnemocyteStoreConfig(config.store));
1963
419
  return createInMemoryClient(config);
1964
420
  }
1965
421
  //#endregion