mnemocyte 0.4.0 → 0.5.1

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,483 +1,6 @@
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, eq, inArray, isNotNull, isNull, lt, or, sql } from "drizzle-orm";
5
- import { PgDialect, 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.toString();
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
- embeddingModel: text("embedding_model")
77
- });
78
- //#endregion
79
- //#region src/db/index.ts
80
- function parseSslFromUrl(url) {
81
- const mode = url.searchParams.get("sslmode");
82
- if (!mode || mode === "disable") return void 0;
83
- if (mode === "require" || mode === "prefer" || mode === "allow") return { rejectUnauthorized: false };
84
- if (mode === "verify-full") return "verify-full";
85
- }
86
- function createDatabase(databaseUrl) {
87
- let url;
88
- try {
89
- url = new URL(databaseUrl);
90
- } catch (error) {
91
- throw new MnemocyteError("databaseUrl must be a valid Postgres connection URL.", "CONFIG", error);
92
- }
93
- if (url.protocol !== "postgres:" && url.protocol !== "postgresql:") throw new MnemocyteError("databaseUrl must use the postgres: or postgresql: protocol.", "CONFIG");
94
- const ssl = parseSslFromUrl(url);
95
- const isPooler = url.port === "6543" || url.searchParams.get("pgbouncer") === "true";
96
- const client = postgres(databaseUrl, {
97
- max: 10,
98
- idle_timeout: 20,
99
- connect_timeout: 10,
100
- ...ssl ? { ssl } : {},
101
- ...isPooler ? { prepare: false } : {}
102
- });
103
- return {
104
- db: drizzle(client, { schema: schema_exports }),
105
- async close() {
106
- await client.end();
107
- }
108
- };
109
- }
110
- //#endregion
111
- //#region src/resilience.ts
112
- /**
113
- * Default per-provider-call timeout. Disabled by default so that callers
114
- * who do not configure {@link ProviderResilienceConfig.timeoutMs} are
115
- * unaffected.
116
- */
117
- const DEFAULT_TIMEOUT_MS = 0;
118
- /** Default number of retry attempts on transient provider failures. */
119
- const DEFAULT_RETRIES = 0;
120
- /** Default base backoff delay used between retry attempts. */
121
- const DEFAULT_BASE_DELAY_MS = 100;
122
- /** Default ceiling for retry backoff delay. */
123
- const DEFAULT_MAX_DELAY_MS = 2e3;
124
- /**
125
- * Throw a {@link MnemocyteError} with code `"ABORTED"` if `signal` is
126
- * already aborted. Safe to call with `undefined`.
127
- */
128
- function throwIfAborted(signal) {
129
- if (signal?.aborted) throw new MnemocyteError("Operation was aborted.", "ABORTED", signal.reason);
130
- }
131
- /**
132
- * Heuristic default for whether an error should be retried.
133
- *
134
- * Conservative by design: only retries on common transient failure
135
- * indicators (network errors, 5xx-ish messages, `ECONN*` etc.). Never
136
- * retries on validation, configuration, or {@link MnemocyteError} of
137
- * code `"VALIDATION"` / `"CONFIG"` / `"CONFLICT"` / `"ABORTED"`.
138
- */
139
- function defaultShouldRetry(error) {
140
- if (error instanceof MnemocyteError) {
141
- if (error.code === "VALIDATION" || error.code === "CONFIG" || error.code === "CONFLICT" || error.code === "ABORTED") return false;
142
- return true;
143
- }
144
- if (typeof error === "object" && error !== null && "status" in error && typeof error.status === "number" && [
145
- 429,
146
- 500,
147
- 502,
148
- 503,
149
- 504
150
- ].includes(error.status)) return true;
151
- if (error instanceof Error) {
152
- const message = error.message.toLowerCase();
153
- 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;
154
- }
155
- return false;
156
- }
157
- /**
158
- * Compute the backoff delay for the given retry attempt, using exponential
159
- * growth capped at {@link ProviderResilienceConfig.maxDelayMs}.
160
- *
161
- * `attempt` is 1-indexed: the delay before the first retry uses
162
- * `attempt = 1`, before the second retry `attempt = 2`, etc.
163
- */
164
- function computeDelayMs(attempt, baseDelayMs, maxDelayMs) {
165
- const exponential = baseDelayMs * 2 ** (attempt - 1);
166
- return Math.max(0, Math.min(maxDelayMs, exponential));
167
- }
168
- /**
169
- * Promise-based `setTimeout` that rejects with an `"ABORTED"`
170
- * {@link MnemocyteError} if `signal` aborts while waiting.
171
- */
172
- function delay(ms, signal) {
173
- if (ms <= 0) {
174
- throwIfAborted(signal);
175
- return Promise.resolve();
176
- }
177
- return new Promise((resolve, reject) => {
178
- const timer = setTimeout(() => {
179
- signal?.removeEventListener("abort", onAbort);
180
- resolve();
181
- }, ms);
182
- const onAbort = () => {
183
- clearTimeout(timer);
184
- reject(new MnemocyteError("Operation was aborted.", "ABORTED", signal?.reason));
185
- };
186
- if (signal) {
187
- if (signal.aborted) {
188
- clearTimeout(timer);
189
- reject(new MnemocyteError("Operation was aborted.", "ABORTED", signal.reason));
190
- return;
191
- }
192
- signal.addEventListener("abort", onAbort, { once: true });
193
- }
194
- });
195
- }
196
- function timeoutError(timeoutMs) {
197
- return new MnemocyteError(`Provider call timed out after ${timeoutMs}ms.`, "TIMEOUT");
198
- }
199
- /**
200
- * Run one provider attempt with a signal linked to the caller's signal and
201
- * Mnemocyte's configured timeout. Timeout aborts are actively propagated to
202
- * providers that honor `AbortSignal`.
203
- */
204
- function runAttempt(action, timeoutMs, signal) {
205
- if (timeoutMs <= 0 && !signal) return action(void 0);
206
- return new Promise((resolve, reject) => {
207
- let settled = false;
208
- const controller = new AbortController();
209
- const timer = timeoutMs > 0 ? setTimeout(() => {
210
- if (settled) return;
211
- settled = true;
212
- signal?.removeEventListener("abort", onAbort);
213
- controller.abort(timeoutError(timeoutMs));
214
- reject(timeoutError(timeoutMs));
215
- }, timeoutMs) : void 0;
216
- const onAbort = () => {
217
- if (settled) return;
218
- settled = true;
219
- if (timer) clearTimeout(timer);
220
- controller.abort(signal?.reason);
221
- reject(new MnemocyteError("Operation was aborted.", "ABORTED", signal?.reason));
222
- };
223
- if (signal) {
224
- if (signal.aborted) {
225
- if (timer) clearTimeout(timer);
226
- settled = true;
227
- controller.abort(signal.reason);
228
- reject(new MnemocyteError("Operation was aborted.", "ABORTED", signal.reason));
229
- return;
230
- }
231
- signal.addEventListener("abort", onAbort, { once: true });
232
- }
233
- action(controller.signal).then((value) => {
234
- if (settled) return;
235
- settled = true;
236
- if (timer) clearTimeout(timer);
237
- signal?.removeEventListener("abort", onAbort);
238
- resolve(value);
239
- }, (error) => {
240
- if (settled) return;
241
- settled = true;
242
- if (timer) clearTimeout(timer);
243
- signal?.removeEventListener("abort", onAbort);
244
- reject(error);
245
- });
246
- });
247
- }
248
- /**
249
- * Run `action` with timeout, retry, and abort handling derived from
250
- * `options.resilience` and `options.signal`. Each retry attempt receives
251
- * a fresh `AbortSignal` linked to the caller's signal — so callers may
252
- * abort mid-retry and the in-flight attempt will cancel promptly if the
253
- * underlying provider honours signals.
254
- *
255
- * Errors from {@link MnemocyteError} with code `"VALIDATION"` /
256
- * `"CONFIG"` / `"CONFLICT"` / `"ABORTED"` are never retried.
257
- */
258
- async function withResilience(action, options = {}) {
259
- const config = options.resilience ?? {};
260
- const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
261
- const retries = Math.max(0, config.retries ?? DEFAULT_RETRIES);
262
- const baseDelayMs = Math.max(0, config.baseDelayMs ?? DEFAULT_BASE_DELAY_MS);
263
- const maxDelayMs = Math.max(baseDelayMs, config.maxDelayMs ?? DEFAULT_MAX_DELAY_MS);
264
- const shouldRetry = config.shouldRetry ?? defaultShouldRetry;
265
- const externalSignal = options.signal;
266
- throwIfAborted(externalSignal);
267
- const maxAttempts = retries + 1;
268
- let lastError;
269
- for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
270
- throwIfAborted(externalSignal);
271
- try {
272
- return await runAttempt(action, timeoutMs, externalSignal);
273
- } catch (error) {
274
- lastError = error;
275
- if (error instanceof MnemocyteError && error.code === "ABORTED") throw error;
276
- if (attempt >= maxAttempts) throw error;
277
- if (!shouldRetry(error, attempt)) throw error;
278
- await delay(computeDelayMs(attempt, baseDelayMs, maxDelayMs), externalSignal);
279
- }
280
- }
281
- throw lastError;
282
- }
283
- //#endregion
284
- //#region src/memory/json.ts
285
- function invalidJsonValue(path, cause) {
286
- return new MnemocyteError(`${path} must contain only JSON-compatible value data.`, "VALIDATION", cause);
287
- }
288
- function cloneJsonValue(value, path, ancestors) {
289
- if (value === null || typeof value === "string" || typeof value === "boolean") return value;
290
- if (typeof value === "number") {
291
- if (!Number.isFinite(value)) throw invalidJsonValue(path);
292
- return Object.is(value, -0) ? 0 : value;
293
- }
294
- if (typeof value !== "object") throw invalidJsonValue(path);
295
- if (ancestors.has(value)) throw invalidJsonValue(path);
296
- ancestors.add(value);
297
- try {
298
- if (Array.isArray(value)) {
299
- if (Object.getOwnPropertySymbols(value).length > 0 || Object.keys(value).some((key) => !/^(0|[1-9]\d*)$/.test(key))) throw invalidJsonValue(path);
300
- return Array.from({ length: value.length }, (_, index) => {
301
- if (!Object.hasOwn(value, index)) throw invalidJsonValue(`${path}[${index}]`);
302
- return cloneJsonValue(value[index], `${path}[${index}]`, ancestors);
303
- });
304
- }
305
- const prototype = Object.getPrototypeOf(value);
306
- if (prototype !== Object.prototype && prototype !== null) throw invalidJsonValue(path);
307
- const descriptors = Object.getOwnPropertyDescriptors(value);
308
- const entries = [];
309
- for (const key of Reflect.ownKeys(descriptors)) {
310
- if (typeof key !== "string") throw invalidJsonValue(path);
311
- const descriptor = descriptors[key];
312
- if (descriptor === void 0 || descriptor.enumerable !== true || !("value" in descriptor)) throw invalidJsonValue(`${path}.${key}`);
313
- entries.push([key, cloneJsonValue(descriptor.value, `${path}.${key}`, ancestors)]);
314
- }
315
- return Object.fromEntries(entries);
316
- } finally {
317
- ancestors.delete(value);
318
- }
319
- }
320
- /** Validate and deep-clone a JSON-compatible object. */
321
- function cloneJsonObject(value, path = "metadata") {
322
- try {
323
- if (value === null || typeof value !== "object" || Array.isArray(value)) throw invalidJsonValue(path);
324
- return cloneJsonValue(value, path, /* @__PURE__ */ new Set());
325
- } catch (error) {
326
- if (error instanceof MnemocyteError) throw error;
327
- throw invalidJsonValue(path, error);
328
- }
329
- }
330
- //#endregion
331
- //#region src/memory/records.ts
332
- function normalizeTags(tags) {
333
- if (!tags) return [];
334
- return [...new Set(tags.map((tag) => tag.trim()).filter(Boolean))];
335
- }
336
- function createId() {
337
- return `mem_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
338
- }
339
- /** Generate a unique audit-event id (`evt_*`). */
340
- function createEventId() {
341
- return `evt_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
342
- }
343
- function cloneMemory(memory) {
344
- return {
345
- id: memory.id,
346
- entityId: memory.entityId,
347
- content: memory.content,
348
- type: memory.type,
349
- importance: memory.importance,
350
- tags: [...memory.tags],
351
- source: memory.source,
352
- metadata: cloneJsonObject(memory.metadata),
353
- confidence: memory.confidence,
354
- embeddingModel: memory.embeddingModel,
355
- embeddingDimensions: memory.embeddingDimensions,
356
- supersededBy: memory.supersededBy,
357
- supersededAt: memory.supersededAt ? new Date(memory.supersededAt) : null,
358
- expiresAt: memory.expiresAt ? new Date(memory.expiresAt) : null,
359
- lastAccessedAt: memory.lastAccessedAt ? new Date(memory.lastAccessedAt) : null,
360
- accessCount: memory.accessCount,
361
- createdAt: new Date(memory.createdAt),
362
- updatedAt: new Date(memory.updatedAt)
363
- };
364
- }
365
- //#endregion
366
- //#region src/retrieval/scorer.ts
367
- const IMPORTANCE_SCORES = {
368
- low: 0,
369
- normal: .5,
370
- high: .8,
371
- critical: 1
372
- };
373
- const DEFAULT_RETRIEVAL_WEIGHTS = {
374
- vector: .55,
375
- lexical: .2,
376
- recency: .1,
377
- confidence: .05,
378
- access: .05,
379
- importance: .05
380
- };
381
- const DEFAULT_RECENCY_HALF_LIFE_DAYS = 90;
382
- const DEFAULT_ACCESS_SATURATION = 10;
383
- function cosineSimilarity(a, b) {
384
- let dot = 0;
385
- let aMagnitude = 0;
386
- let bMagnitude = 0;
387
- for (let i = 0; i < a.length; i += 1) {
388
- const av = a[i] ?? 0;
389
- const bv = b[i] ?? 0;
390
- dot += av * bv;
391
- aMagnitude += av * av;
392
- bMagnitude += bv * bv;
393
- }
394
- if (aMagnitude === 0 || bMagnitude === 0) return 0;
395
- return dot / (Math.sqrt(aMagnitude) * Math.sqrt(bMagnitude));
396
- }
397
- function tokenizeQuery(query) {
398
- return query.toLowerCase().split(/\s+/).filter(Boolean);
399
- }
400
- function lexicalScoreWithTerms(content, terms) {
401
- if (terms.length === 0) return 0;
402
- const lowerContent = content.toLowerCase();
403
- return terms.filter((term) => lowerContent.includes(term)).length / terms.length;
404
- }
405
- function createLexicalScorer(query) {
406
- const terms = tokenizeQuery(query);
407
- return (content) => lexicalScoreWithTerms(content, terms);
408
- }
409
- function clampScore(value) {
410
- if (!Number.isFinite(value)) return 0;
411
- return Math.max(0, Math.min(1, value));
412
- }
413
- function toVectorScore(cosineSimilarity) {
414
- return clampScore(cosineSimilarity);
415
- }
416
- function normalizeWeights(weights) {
417
- const merged = {
418
- ...DEFAULT_RETRIEVAL_WEIGHTS,
419
- ...weights
420
- };
421
- const total = merged.vector + merged.lexical + merged.recency + merged.confidence + merged.access + merged.importance;
422
- if (total <= 0) return { ...DEFAULT_RETRIEVAL_WEIGHTS };
423
- return {
424
- vector: merged.vector / total,
425
- lexical: merged.lexical / total,
426
- recency: merged.recency / total,
427
- confidence: merged.confidence / total,
428
- access: merged.access / total,
429
- importance: merged.importance / total
430
- };
431
- }
432
- function createScoringConfig(config) {
433
- return {
434
- weights: normalizeWeights(config?.weights),
435
- recencyHalfLifeDays: config?.recencyHalfLifeDays ?? DEFAULT_RECENCY_HALF_LIFE_DAYS,
436
- accessSaturation: config?.accessSaturation ?? DEFAULT_ACCESS_SATURATION
437
- };
438
- }
439
- function recencyScore(createdAt, halfLifeDays) {
440
- const ageMs = Date.now() - createdAt.getTime();
441
- const ageDays = Math.max(0, ageMs / 864e5);
442
- return Math.exp(-Math.LN2 * ageDays / halfLifeDays);
443
- }
444
- function accessScore(accessCount, saturation) {
445
- if (accessCount <= 0) return 0;
446
- return Math.min(1, Math.log1p(accessCount) / Math.log1p(saturation));
447
- }
448
- function toScoredMemoryWithConfig(memory, vector, lexical, input, config) {
449
- const { weights } = config;
450
- const recency = recencyScore(memory.createdAt, config.recencyHalfLifeDays);
451
- const confidence = clampScore(memory.confidence);
452
- const access = accessScore(memory.accessCount, config.accessSaturation);
453
- const importance = IMPORTANCE_SCORES[memory.importance];
454
- 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));
455
- return {
456
- ...cloneMemory(memory),
457
- score,
458
- scores: {
459
- vector: clampScore(vector),
460
- lexical: clampScore(lexical),
461
- recency,
462
- confidence,
463
- access,
464
- importance
465
- },
466
- explanation: input.explain ? {
467
- vectorScore: clampScore(vector),
468
- lexicalScore: clampScore(lexical),
469
- recencyScore: recency,
470
- confidenceScore: confidence,
471
- accessScore: access,
472
- importanceScore: importance,
473
- importanceBoost: importance * weights.importance,
474
- weights,
475
- finalScore: score
476
- } : null
477
- };
478
- }
479
- //#endregion
480
- //#region src/context/formatter.ts
1
+ import { n as isMnemocyteError, t as MnemocyteError } from "./errors-Dgt2nAs1.mjs";
2
+ import { c as IMPORTANCE_RANK, d as createId, f as normalizeTags, g as parsePostgresDatabaseUrl, h as withResilience, i as assertPruneFilterHasSelector, l as cloneMemory, m as throwIfAborted, n as unwrapMnemocyteStoreConfig, p as cloneJsonObject, r as memoryHasDependentsError, u as createEventId } from "./store-config-BZ4J1hrM.mjs";
3
+ // src/context/formatter.ts
481
4
  function escapeXml(value) {
482
5
  return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;").replaceAll("'", "&apos;");
483
6
  }
@@ -545,8 +68,7 @@ function formatContext(memories, query, format, omittedCount = 0) {
545
68
  case "xml": return formatXml(memories, query, omittedCount);
546
69
  }
547
70
  }
548
- //#endregion
549
- //#region src/context/tokens.ts
71
+ // src/context/tokens.ts
550
72
  const TRUNCATION_MARKER = "[truncated to fit token budget]";
551
73
  const heuristicTokenCounter = { count(text) {
552
74
  return Math.ceil(text.length / 4);
@@ -571,8 +93,7 @@ function trimToTokenBudget(text, maxTokens, tokenCounter) {
571
93
  }
572
94
  return "";
573
95
  }
574
- //#endregion
575
- //#region src/context/builder.ts
96
+ // src/context/builder.ts
576
97
  async function buildContext(options) {
577
98
  const format = options.input.format ?? "markdown";
578
99
  const maxTokens = options.input.maxTokens ?? 1200;
@@ -593,8 +114,7 @@ async function buildContext(options) {
593
114
  if (bestContext || memories.length === 0) return bestContext;
594
115
  return trimToTokenBudget(formatContext([], options.input.query, format, memories.length), maxTokens, tokenCounter);
595
116
  }
596
- //#endregion
597
- //#region src/observability.ts
117
+ // src/observability.ts
598
118
  function withMetadata(event, metadata) {
599
119
  return {
600
120
  ...event,
@@ -656,19 +176,120 @@ function observePrepared(config, backend, operation, metadata, prepare, action,
656
176
  });
657
177
  }
658
178
  }
659
- /**
660
- * Ordering of {@link ImportanceLevel} from least to most important. Used
661
- * by `prune({ maxImportance })` to compare a memory's importance against
662
- * the caller-supplied ceiling.
663
- */
664
- const IMPORTANCE_RANK = {
179
+ // src/retrieval/scorer.ts
180
+ const IMPORTANCE_SCORES = {
665
181
  low: 0,
666
- normal: 1,
667
- high: 2,
668
- critical: 3
182
+ normal: .5,
183
+ high: .8,
184
+ critical: 1
669
185
  };
670
- //#endregion
671
- //#region src/memory/embeddings.ts
186
+ const DEFAULT_RETRIEVAL_WEIGHTS = {
187
+ vector: .55,
188
+ lexical: .2,
189
+ recency: .1,
190
+ confidence: .05,
191
+ access: .05,
192
+ importance: .05
193
+ };
194
+ const DEFAULT_RECENCY_HALF_LIFE_DAYS = 90;
195
+ const DEFAULT_ACCESS_SATURATION = 10;
196
+ function cosineSimilarity(a, b) {
197
+ let dot = 0;
198
+ let aMagnitude = 0;
199
+ let bMagnitude = 0;
200
+ for (let i = 0; i < a.length; i += 1) {
201
+ const av = a[i] ?? 0;
202
+ const bv = b[i] ?? 0;
203
+ dot += av * bv;
204
+ aMagnitude += av * av;
205
+ bMagnitude += bv * bv;
206
+ }
207
+ if (aMagnitude === 0 || bMagnitude === 0) return 0;
208
+ return dot / (Math.sqrt(aMagnitude) * Math.sqrt(bMagnitude));
209
+ }
210
+ function tokenizeQuery(query) {
211
+ return query.toLowerCase().split(/\s+/).filter(Boolean);
212
+ }
213
+ function lexicalScoreWithTerms(content, terms) {
214
+ if (terms.length === 0) return 0;
215
+ const lowerContent = content.toLowerCase();
216
+ return terms.filter((term) => lowerContent.includes(term)).length / terms.length;
217
+ }
218
+ function createLexicalScorer(query) {
219
+ const terms = tokenizeQuery(query);
220
+ return (content) => lexicalScoreWithTerms(content, terms);
221
+ }
222
+ function clampScore(value) {
223
+ if (!Number.isFinite(value)) return 0;
224
+ return Math.max(0, Math.min(1, value));
225
+ }
226
+ function toVectorScore(cosineSimilarity) {
227
+ return clampScore(cosineSimilarity);
228
+ }
229
+ function normalizeWeights(weights) {
230
+ const merged = {
231
+ ...DEFAULT_RETRIEVAL_WEIGHTS,
232
+ ...weights
233
+ };
234
+ const total = merged.vector + merged.lexical + merged.recency + merged.confidence + merged.access + merged.importance;
235
+ if (total <= 0) return { ...DEFAULT_RETRIEVAL_WEIGHTS };
236
+ return {
237
+ vector: merged.vector / total,
238
+ lexical: merged.lexical / total,
239
+ recency: merged.recency / total,
240
+ confidence: merged.confidence / total,
241
+ access: merged.access / total,
242
+ importance: merged.importance / total
243
+ };
244
+ }
245
+ function createScoringConfig(config) {
246
+ return {
247
+ weights: normalizeWeights(config?.weights),
248
+ recencyHalfLifeDays: config?.recencyHalfLifeDays ?? DEFAULT_RECENCY_HALF_LIFE_DAYS,
249
+ accessSaturation: config?.accessSaturation ?? DEFAULT_ACCESS_SATURATION
250
+ };
251
+ }
252
+ function recencyScore(createdAt, halfLifeDays) {
253
+ const ageMs = Date.now() - createdAt.getTime();
254
+ const ageDays = Math.max(0, ageMs / 864e5);
255
+ return Math.exp(-Math.LN2 * ageDays / halfLifeDays);
256
+ }
257
+ function accessScore(accessCount, saturation) {
258
+ if (accessCount <= 0) return 0;
259
+ return Math.min(1, Math.log1p(accessCount) / Math.log1p(saturation));
260
+ }
261
+ function toScoredMemoryWithConfig(memory, vector, lexical, input, config) {
262
+ const { weights } = config;
263
+ const recency = recencyScore(memory.createdAt, config.recencyHalfLifeDays);
264
+ const confidence = clampScore(memory.confidence);
265
+ const access = accessScore(memory.accessCount, config.accessSaturation);
266
+ const importance = IMPORTANCE_SCORES[memory.importance];
267
+ 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));
268
+ return {
269
+ ...cloneMemory(memory),
270
+ score,
271
+ scores: {
272
+ vector: clampScore(vector),
273
+ lexical: clampScore(lexical),
274
+ recency,
275
+ confidence,
276
+ access,
277
+ importance
278
+ },
279
+ explanation: input.explain ? {
280
+ vectorScore: clampScore(vector),
281
+ lexicalScore: clampScore(lexical),
282
+ recencyScore: recency,
283
+ confidenceScore: confidence,
284
+ accessScore: access,
285
+ importanceScore: importance,
286
+ importanceBoost: importance * weights.importance,
287
+ weights,
288
+ finalScore: score
289
+ } : null
290
+ };
291
+ }
292
+ // src/memory/embeddings.ts
672
293
  function validateEmbedding(embedding, dimensions) {
673
294
  if (embedding.length !== dimensions) throw new MnemocyteError("Embedder returned an embedding with unexpected dimensions.", "EMBEDDING");
674
295
  if (embedding.some((component) => !Number.isFinite(component))) throw new MnemocyteError("Embedder returned an embedding with non-finite values.", "EMBEDDING");
@@ -706,16 +327,7 @@ async function embedMany(embedder, texts, options = {}) {
706
327
  for (const embedding of embeddings) validateEmbedding(embedding, embedder.dimensions);
707
328
  return embeddings;
708
329
  }
709
- //#endregion
710
- //#region src/memory/store.ts
711
- function hasPruneSelector(filter) {
712
- return filter.entityId !== void 0 || filter.expired === true || filter.superseded === true || filter.createdBefore !== void 0 || filter.notAccessedSince !== void 0 || filter.types !== void 0 && filter.types.length > 0 || filter.tags !== void 0 && filter.tags.length > 0 || filter.maxImportance !== void 0;
713
- }
714
- function assertPruneFilterHasSelector(filter) {
715
- if (!hasPruneSelector(filter)) throw new MnemocyteError("prune requires at least one selector (entityId, expired, superseded, createdBefore, notAccessedSince, types, tags, or maxImportance).", "VALIDATION");
716
- }
717
- //#endregion
718
- //#region src/memory/validation.ts
330
+ // src/memory/validation.ts
719
331
  const MEMORY_TYPES = [
720
332
  "fact",
721
333
  "preference",
@@ -724,7 +336,7 @@ const MEMORY_TYPES = [
724
336
  "episode",
725
337
  "session"
726
338
  ];
727
- const IMPORTANCE_LEVELS$1 = [
339
+ const IMPORTANCE_LEVELS = [
728
340
  "low",
729
341
  "normal",
730
342
  "high",
@@ -734,7 +346,7 @@ function isMemoryType(value) {
734
346
  return typeof value === "string" && MEMORY_TYPES.includes(value);
735
347
  }
736
348
  function isImportanceLevel(value) {
737
- return typeof value === "string" && IMPORTANCE_LEVELS$1.includes(value);
349
+ return typeof value === "string" && IMPORTANCE_LEVELS.includes(value);
738
350
  }
739
351
  function assertMemoryType(value, field) {
740
352
  if (!isMemoryType(value)) throw new MnemocyteError(`${field} must be a known memory type.`, "VALIDATION");
@@ -893,14 +505,19 @@ function validateFindDuplicatesInput(input) {
893
505
  /**
894
506
  * Validate a {@link ConsolidateInput} and throw a `"VALIDATION"`
895
507
  * {@link MnemocyteError} for malformed inputs (empty `supersededIds`
896
- * or a `survivorId` appearing in `supersededIds`).
508
+ * a `survivorId` appearing in `supersededIds`, or duplicate loser IDs).
897
509
  */
898
510
  function validateConsolidateInput(input) {
899
511
  assertNonEmptyString(input.entityId, "entityId");
900
512
  assertNonEmptyString(input.survivorId, "survivorId");
901
513
  if (input.supersededIds.length === 0) throw new MnemocyteError("supersededIds must contain at least one memory id.", "VALIDATION");
902
514
  if (input.supersededIds.includes(input.survivorId)) throw new MnemocyteError("survivorId must not appear in supersededIds.", "VALIDATION");
903
- for (const id of input.supersededIds) assertNonEmptyString(id, "supersededIds[*]");
515
+ const seenIds = /* @__PURE__ */ new Set();
516
+ for (const id of input.supersededIds) {
517
+ assertNonEmptyString(id, "supersededIds[*]");
518
+ if (seenIds.has(id)) throw new MnemocyteError("supersededIds must not contain duplicate memory ids.", "VALIDATION");
519
+ seenIds.add(id);
520
+ }
904
521
  }
905
522
  /**
906
523
  * Validate a {@link ListAuditLogInput} and throw a `"VALIDATION"`
@@ -920,8 +537,7 @@ function assertAuditLogCursor(value, field) {
920
537
  assertValidDate(cursor.timestamp, `${field}.timestamp`);
921
538
  assertNonEmptyString(cursor.id, `${field}.id`);
922
539
  }
923
- //#endregion
924
- //#region src/memory/client-core.ts
540
+ // src/memory/client-core.ts
925
541
  function isPositionalRememberManyInput(input) {
926
542
  return Array.isArray(input);
927
543
  }
@@ -1293,7 +909,6 @@ function createMemoryClient(config, store) {
1293
909
  const result = await store.consolidate({
1294
910
  entityId: input.entityId,
1295
911
  survivorId: survivor.id,
1296
- survivorTags: survivor.tags,
1297
912
  supersededIds: input.supersededIds,
1298
913
  mergeTags: input.mergeTags !== false,
1299
914
  now: /* @__PURE__ */ new Date(),
@@ -1324,13 +939,7 @@ function createMemoryClient(config, store) {
1324
939
  }
1325
940
  };
1326
941
  }
1327
- //#endregion
1328
- //#region src/memory/deletion.ts
1329
- function memoryHasDependentsError(cause) {
1330
- return new MnemocyteError("Memory cannot be deleted while other memories reference it as their consolidation survivor.", "CONFLICT", cause);
1331
- }
1332
- //#endregion
1333
- //#region src/memory/filters.ts
942
+ // src/memory/filters.ts
1334
943
  function isExpired(memory, now) {
1335
944
  return memory.expiresAt !== null && memory.expiresAt.getTime() <= now.getTime();
1336
945
  }
@@ -1377,8 +986,7 @@ function matchesRecallFilter(memory, input, now) {
1377
986
  if (input.after && memory.createdAt <= input.after) return false;
1378
987
  return true;
1379
988
  }
1380
- //#endregion
1381
- //#region src/memory/in-memory.ts
989
+ // src/memory/in-memory.ts
1382
990
  function cloneAuditEvent(event) {
1383
991
  return {
1384
992
  id: event.id,
@@ -1458,7 +1066,7 @@ function createInMemoryStore() {
1458
1066
  async deleteMemory(entityId, memoryId) {
1459
1067
  const memory = memories.get(memoryId);
1460
1068
  if (!memory || memory.entityId !== entityId) return false;
1461
- assertNoDependentMemories(memories, new Set([memoryId]));
1069
+ assertNoDependentMemories(memories, /* @__PURE__ */ new Set([memoryId]));
1462
1070
  return memories.delete(memoryId);
1463
1071
  },
1464
1072
  async deleteMemoriesForEntity(entityId) {
@@ -1565,6 +1173,7 @@ function createInMemoryStore() {
1565
1173
  async consolidate(input, options) {
1566
1174
  throwIfAborted(options?.signal);
1567
1175
  const survivor = memories.get(input.survivorId);
1176
+ if (!survivor || survivor.entityId !== input.entityId || survivor.supersededBy !== null) throw new MnemocyteError("Consolidation survivor changed before mutation.", "CONFLICT");
1568
1177
  const newlySuperseded = [];
1569
1178
  const targets = [];
1570
1179
  for (const id of input.supersededIds) {
@@ -1576,8 +1185,8 @@ function createInMemoryStore() {
1576
1185
  if (targets.some((memory) => memory.supersededBy !== null && memory.supersededBy !== input.survivorId)) throw new MnemocyteError("Superseded memory already belongs to a different survivor.", "CONFLICT");
1577
1186
  for (const memory of targets) if (memory.supersededBy === null) newlySuperseded.push(memory);
1578
1187
  let mergedSurvivorTags;
1579
- if (survivor && input.mergeTags && newlySuperseded.length > 0) {
1580
- const mergedTags = new Set(input.survivorTags);
1188
+ if (input.mergeTags && newlySuperseded.length > 0) {
1189
+ const mergedTags = new Set(survivor.tags);
1581
1190
  for (const memory of newlySuperseded) {
1582
1191
  throwIfAborted(options?.signal);
1583
1192
  for (const tag of memory.tags) mergedTags.add(tag);
@@ -1600,7 +1209,7 @@ function createInMemoryStore() {
1600
1209
  memory.supersededAt = input.now;
1601
1210
  memory.updatedAt = input.now;
1602
1211
  }
1603
- if (survivor && mergedSurvivorTags) {
1212
+ if (mergedSurvivorTags) {
1604
1213
  survivor.tags = mergedSurvivorTags;
1605
1214
  survivor.updatedAt = input.now;
1606
1215
  }
@@ -1637,825 +1246,92 @@ function createInMemoryStore() {
1637
1246
  function createInMemoryClient(config) {
1638
1247
  return createMemoryClient(config, createInMemoryStore());
1639
1248
  }
1640
- //#endregion
1641
- //#region src/db/cancellation.ts
1642
- const postgresDialect = new PgDialect();
1643
- async function awaitCancelableQuery(query, signal) {
1644
- throwIfAborted(signal);
1645
- if (!signal) return await query;
1646
- const onAbort = () => {
1647
- query.cancel();
1648
- };
1649
- signal.addEventListener("abort", onAbort, { once: true });
1650
- try {
1651
- return await query;
1652
- } catch (error) {
1653
- if (signal.aborted) throw new MnemocyteError("Operation was aborted.", "ABORTED", signal.reason);
1654
- throw error;
1655
- } finally {
1656
- signal.removeEventListener("abort", onAbort);
1657
- }
1658
- }
1659
- async function executeCancelableSql(db, statement, signal) {
1660
- const query = postgresDialect.sqlToQuery(statement);
1661
- const client = db.$client;
1662
- if (!client) throw new MnemocyteError("Cancelable Postgres query requires the postgres.js client.", "DB");
1663
- return await awaitCancelableQuery(client.unsafe(query.sql, query.params), signal);
1664
- }
1665
- //#endregion
1666
- //#region src/db/queries/events.ts
1667
- function timestampParam$1(value) {
1668
- return sql`${value.toISOString()}::timestamptz`;
1669
- }
1670
- async function insertEvent(db, row) {
1671
- const inserted = (await db.insert(eventsTable).values(row).returning())[0];
1672
- if (!inserted) throw new MnemocyteError("Event insert returned no rows.", "DB");
1673
- return inserted;
1674
- }
1675
- async function listEvents(db, filter, signal) {
1676
- return executeCancelableSql(db, sql`
1677
- SELECT
1678
- id,
1679
- entity_id AS "entityId",
1680
- description,
1681
- metadata,
1682
- timestamp
1683
- FROM mnemocyte_events
1684
- WHERE entity_id = ${filter.entityId}
1685
- ${filter.before ? sql`AND timestamp < ${timestampParam$1(filter.before)}` : sql``}
1686
- ${filter.after ? sql`AND timestamp > ${timestampParam$1(filter.after)}` : sql``}
1687
- ${filter.beforeCursor ? sql`AND (timestamp, id) < (${timestampParam$1(filter.beforeCursor.timestamp)}, ${filter.beforeCursor.id})` : sql``}
1688
- ${filter.afterCursor ? sql`AND (timestamp, id) > (${timestampParam$1(filter.afterCursor.timestamp)}, ${filter.afterCursor.id})` : sql``}
1689
- ORDER BY timestamp DESC, id DESC
1690
- LIMIT ${filter.limit ?? 50}
1691
- `, signal);
1692
- }
1693
- //#endregion
1694
- //#region src/db/queries/memories.ts
1695
- function vectorLiteral(embedding) {
1696
- return `[${embedding.map(formatVectorComponent).join(",")}]`;
1697
- }
1698
- function clampedVectorScore(embedding) {
1699
- const rawScore = sql`1 - (embedding <=> ${embedding}::vector)`;
1700
- return sql`
1701
- CASE
1702
- WHEN ${rawScore} = 'NaN'::double precision
1703
- OR ${rawScore} = 'Infinity'::double precision
1704
- OR ${rawScore} = '-Infinity'::double precision
1705
- THEN 0::double precision
1706
- ELSE GREATEST(
1707
- 0::double precision,
1708
- LEAST(1::double precision, ${rawScore})
1709
- )
1710
- END
1711
- `;
1712
- }
1713
- function timestampParam(value) {
1714
- return sql`${value.toISOString()}::timestamptz`;
1715
- }
1716
- function textArrayLiteral(values) {
1717
- return sql`ARRAY[${sql.join(values.map((value) => sql`${value}`), sql`, `)}]::text[]`;
1718
- }
1719
- function tagsContainAll(tags) {
1720
- return tags && tags.length > 0 ? sql`${memoriesTable.tags} @> ${textArrayLiteral(tags)}` : void 0;
1721
- }
1722
- function rawTagsFilter(tags) {
1723
- return tags && tags.length > 0 ? sql`AND tags @> ${textArrayLiteral(tags)}` : sql``;
1724
- }
1725
- function duplicateTagsFilter(tags) {
1726
- return tags && tags.length > 0 ? sql`AND a.tags @> ${textArrayLiteral(tags)} AND b.tags @> ${textArrayLiteral(tags)}` : sql``;
1727
- }
1728
- function memoryStatsCountFields(now) {
1729
- return {
1730
- memoryCount: count(),
1731
- activeMemoryCount: sql`
1732
- (count(*) FILTER (
1733
- WHERE ${memoriesTable.supersededBy} IS NULL
1734
- AND (${memoriesTable.expiresAt} IS NULL OR ${memoriesTable.expiresAt} > ${timestampParam(now)})
1735
- ))::int
1736
- `.mapWith(Number),
1737
- expiredMemoryCount: sql`
1738
- (count(*) FILTER (
1739
- WHERE ${memoriesTable.expiresAt} IS NOT NULL
1740
- AND ${memoriesTable.expiresAt} <= ${timestampParam(now)}
1741
- ))::int
1742
- `.mapWith(Number),
1743
- supersededMemoryCount: sql`(count(*) FILTER (WHERE ${memoriesTable.supersededBy} IS NOT NULL))::int`.mapWith(Number)
1744
- };
1745
- }
1746
- function emptyMemoryStatsCounts() {
1747
- return {
1748
- memoryCount: 0,
1749
- activeMemoryCount: 0,
1750
- expiredMemoryCount: 0,
1751
- supersededMemoryCount: 0
1752
- };
1753
- }
1754
- async function insertMemories(db, rows) {
1755
- if (rows.length === 0) return [];
1756
- return db.insert(memoriesTable).values(rows).returning();
1757
- }
1758
- async function getMemoryById(db, entityId, memoryId) {
1759
- return (await db.select().from(memoriesTable).where(and(eq(memoriesTable.entityId, entityId), eq(memoriesTable.id, memoryId))).limit(1))[0] ?? null;
1760
- }
1761
- async function getEntityMemoryStatsCounts(db, entityId, now) {
1762
- return (await db.select(memoryStatsCountFields(now)).from(memoriesTable).where(eq(memoriesTable.entityId, entityId)))[0] ?? emptyMemoryStatsCounts();
1763
- }
1764
- async function getGlobalMemoryStatsCounts(db, now) {
1765
- return (await db.select({
1766
- entityCount: countDistinct(memoriesTable.entityId),
1767
- ...memoryStatsCountFields(now)
1768
- }).from(memoriesTable))[0] ?? {
1769
- entityCount: 0,
1770
- ...emptyMemoryStatsCounts()
1771
- };
1772
- }
1773
- async function deleteMemory(db, entityId, memoryId) {
1774
- return deleteMemoriesWithDependentGuard(db, and(eq(memoriesTable.entityId, entityId), eq(memoriesTable.id, memoryId)));
1775
- }
1776
- async function deleteMemoriesForEntity(db, entityId) {
1777
- return deleteMemoriesWithDependentGuard(db, eq(memoriesTable.entityId, entityId));
1778
- }
1779
- function pruneConditions(filter) {
1780
- 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);
1781
- }
1782
- async function countPruneMatches(db, filter, signal) {
1783
- const conditions = pruneConditions(filter);
1784
- const rows = await executeCancelableSql(db, sql`
1785
- SELECT count(*)::int AS count
1786
- FROM ${memoriesTable}
1787
- ${conditions ? sql`WHERE ${conditions}` : sql``}
1788
- `, signal);
1789
- return Number(rows[0]?.count ?? 0);
1790
- }
1791
- async function pruneMemories(db, filter, signal) {
1792
- return deleteMemoriesWithDependentGuard(db, pruneConditions(filter), signal);
1793
- }
1794
- async function deleteMemoriesWithDependentGuard(db, conditions, signal) {
1795
- const rows = await executeCancelableSql(db, sql`
1796
- WITH candidates AS MATERIALIZED (
1797
- SELECT id, entity_id
1798
- FROM ${memoriesTable}
1799
- ${conditions ? sql`WHERE ${conditions}` : sql``}
1800
- ),
1801
- dependents AS MATERIALIZED (
1802
- SELECT 1
1803
- FROM ${memoriesTable} AS dependent
1804
- INNER JOIN candidates AS target
1805
- ON dependent.superseded_by = target.id
1806
- LIMIT 1
1807
- ),
1808
- deleted AS (
1809
- DELETE FROM ${memoriesTable}
1810
- WHERE ${memoriesTable.id} IN (SELECT id FROM candidates)
1811
- AND NOT EXISTS (SELECT 1 FROM dependents)
1812
- RETURNING id, entity_id
1813
- ),
1814
- deleted_counts AS (
1815
- SELECT entity_id, count(*)::int AS deleted_count
1816
- FROM deleted
1817
- GROUP BY entity_id
1818
- ),
1819
- summary AS (
1820
- SELECT
1821
- (SELECT count(*)::int FROM candidates) AS matched_count,
1822
- (SELECT count(*)::int FROM deleted) AS deleted_count,
1823
- EXISTS (SELECT 1 FROM dependents) AS has_dependents
1824
- )
1825
- SELECT
1826
- summary.matched_count AS "matchedCount",
1827
- summary.deleted_count AS "deletedCount",
1828
- summary.has_dependents AS "hasDependents",
1829
- deleted_counts.entity_id AS "entityId",
1830
- deleted_counts.deleted_count AS "entityDeletedCount"
1831
- FROM summary
1832
- LEFT JOIN deleted_counts ON true
1833
- ORDER BY deleted_counts.entity_id
1834
- `, signal);
1835
- const row = rows[0];
1836
- return {
1837
- matchedCount: Number(row?.matchedCount ?? 0),
1838
- deletedCount: Number(row?.deletedCount ?? 0),
1839
- hasDependents: row?.hasDependents === true,
1840
- deletedByEntity: rows.flatMap((detail) => detail.entityId === null ? [] : [{
1841
- entityId: detail.entityId,
1842
- deletedCount: Number(detail.entityDeletedCount ?? 0)
1843
- }])
1844
- };
1845
- }
1846
- async function findDuplicatePairs(db, input, signal) {
1847
- return (await executeCancelableSql(db, sql`
1848
- SELECT
1849
- a.id AS "aId",
1850
- a.entity_id AS "aEntityId",
1851
- a.content AS "aContent",
1852
- a.type AS "aType",
1853
- a.importance AS "aImportance",
1854
- a.tags AS "aTags",
1855
- a.source AS "aSource",
1856
- a.metadata AS "aMetadata",
1857
- a.confidence AS "aConfidence",
1858
- a.embedding AS "aEmbedding",
1859
- a.embedding_model AS "aEmbeddingModel",
1860
- a.embedding_dimensions AS "aEmbeddingDimensions",
1861
- a.superseded_by AS "aSupersededBy",
1862
- a.superseded_at AS "aSupersededAt",
1863
- a.expires_at AS "aExpiresAt",
1864
- a.last_accessed_at AS "aLastAccessedAt",
1865
- a.access_count AS "aAccessCount",
1866
- a.created_at AS "aCreatedAt",
1867
- a.updated_at AS "aUpdatedAt",
1868
- b.id AS "bId",
1869
- b.entity_id AS "bEntityId",
1870
- b.content AS "bContent",
1871
- b.type AS "bType",
1872
- b.importance AS "bImportance",
1873
- b.tags AS "bTags",
1874
- b.source AS "bSource",
1875
- b.metadata AS "bMetadata",
1876
- b.confidence AS "bConfidence",
1877
- b.embedding AS "bEmbedding",
1878
- b.embedding_model AS "bEmbeddingModel",
1879
- b.embedding_dimensions AS "bEmbeddingDimensions",
1880
- b.superseded_by AS "bSupersededBy",
1881
- b.superseded_at AS "bSupersededAt",
1882
- b.expires_at AS "bExpiresAt",
1883
- b.last_accessed_at AS "bLastAccessedAt",
1884
- b.access_count AS "bAccessCount",
1885
- b.created_at AS "bCreatedAt",
1886
- b.updated_at AS "bUpdatedAt",
1887
- 1 - (a.embedding <=> b.embedding) AS "similarity"
1888
- FROM mnemocyte_memories a
1889
- JOIN mnemocyte_memories b
1890
- ON b.entity_id = a.entity_id
1891
- AND a.id < b.id
1892
- AND a.embedding IS NOT NULL
1893
- AND b.embedding IS NOT NULL
1894
- WHERE
1895
- a.entity_id = ${input.entityId}
1896
- ${input.includeSuperseded ? sql`` : sql`AND a.superseded_by IS NULL AND b.superseded_by IS NULL`}
1897
- ${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())`}
1898
- ${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``}
1899
- ${duplicateTagsFilter(input.tags)}
1900
- AND 1 - (a.embedding <=> b.embedding) >= ${input.threshold}
1901
- ORDER BY "similarity" DESC
1902
- LIMIT ${input.limit}
1903
- `, signal)).map((row) => {
1904
- return {
1905
- a: {
1906
- id: row.aId,
1907
- entityId: row.aEntityId,
1908
- content: row.aContent,
1909
- type: row.aType,
1910
- importance: row.aImportance,
1911
- tags: row.aTags,
1912
- source: row.aSource,
1913
- metadata: row.aMetadata,
1914
- confidence: row.aConfidence,
1915
- embedding: row.aEmbedding,
1916
- embeddingModel: row.aEmbeddingModel,
1917
- embeddingDimensions: row.aEmbeddingDimensions,
1918
- supersededBy: row.aSupersededBy,
1919
- supersededAt: row.aSupersededAt,
1920
- expiresAt: row.aExpiresAt,
1921
- lastAccessedAt: row.aLastAccessedAt,
1922
- accessCount: row.aAccessCount,
1923
- createdAt: row.aCreatedAt,
1924
- updatedAt: row.aUpdatedAt
1925
- },
1926
- b: {
1927
- id: row.bId,
1928
- entityId: row.bEntityId,
1929
- content: row.bContent,
1930
- type: row.bType,
1931
- importance: row.bImportance,
1932
- tags: row.bTags,
1933
- source: row.bSource,
1934
- metadata: row.bMetadata,
1935
- confidence: row.bConfidence,
1936
- embedding: row.bEmbedding,
1937
- embeddingModel: row.bEmbeddingModel,
1938
- embeddingDimensions: row.bEmbeddingDimensions,
1939
- supersededBy: row.bSupersededBy,
1940
- supersededAt: row.bSupersededAt,
1941
- expiresAt: row.bExpiresAt,
1942
- lastAccessedAt: row.bLastAccessedAt,
1943
- accessCount: row.bAccessCount,
1944
- createdAt: row.bCreatedAt,
1945
- updatedAt: row.bUpdatedAt
1946
- },
1947
- similarity: Number(row.similarity) || 0
1948
- };
1949
- });
1950
- }
1951
- async function loadConsolidationTargets(db, entityId, ids) {
1952
- if (ids.length === 0) return [];
1953
- return await db.select({
1954
- id: memoriesTable.id,
1955
- tags: memoriesTable.tags,
1956
- supersededBy: memoriesTable.supersededBy
1957
- }).from(memoriesTable).where(and(eq(memoriesTable.entityId, entityId), inArray(memoriesTable.id, [...ids])));
1958
- }
1959
- async function lockConsolidationTargets(db, entityId, ids) {
1960
- if (ids.length === 0) return [];
1961
- return db.select({
1962
- id: memoriesTable.id,
1963
- tags: memoriesTable.tags,
1964
- supersededBy: memoriesTable.supersededBy
1965
- }).from(memoriesTable).where(and(eq(memoriesTable.entityId, entityId), inArray(memoriesTable.id, [...ids]))).orderBy(memoriesTable.id).for("update");
1966
- }
1967
- async function markMemoriesSuperseded(db, params) {
1968
- if (params.ids.length === 0) return [];
1969
- return await db.update(memoriesTable).set({
1970
- supersededBy: params.survivorId,
1971
- supersededAt: params.now,
1972
- updatedAt: params.now
1973
- }).where(and(eq(memoriesTable.entityId, params.entityId), inArray(memoriesTable.id, [...params.ids]), isNull(memoriesTable.supersededBy))).returning({
1974
- id: memoriesTable.id,
1975
- tags: memoriesTable.tags
1976
- });
1977
- }
1978
- async function setMemoryTags(db, params) {
1979
- await db.update(memoriesTable).set({
1980
- tags: [...params.tags],
1981
- updatedAt: params.now
1982
- }).where(and(eq(memoriesTable.entityId, params.entityId), eq(memoriesTable.id, params.memoryId)));
1983
- }
1984
- async function markMemoriesAccessed(db, memoryIds) {
1985
- const now = /* @__PURE__ */ new Date();
1986
- if (memoryIds.length === 0) return [];
1987
- return db.update(memoriesTable).set({
1988
- lastAccessedAt: now,
1989
- accessCount: sql`${memoriesTable.accessCount} + 1`,
1990
- updatedAt: now
1991
- }).where(inArray(memoriesTable.id, [...memoryIds])).returning({
1992
- id: memoriesTable.id,
1993
- lastAccessedAt: memoriesTable.lastAccessedAt,
1994
- accessCount: memoriesTable.accessCount,
1995
- updatedAt: memoriesTable.updatedAt
1996
- });
1997
- }
1998
- async function getMemoryEmbeddings(db, memoryIds) {
1999
- if (memoryIds.length === 0) return /* @__PURE__ */ new Map();
2000
- const rows = await db.select({
2001
- id: memoriesTable.id,
2002
- embedding: memoriesTable.embedding
2003
- }).from(memoriesTable).where(inArray(memoriesTable.id, [...memoryIds]));
2004
- return new Map(rows.flatMap((row) => row.embedding ? [[row.id, row.embedding]] : []));
2005
- }
2006
- async function vectorSearch(db, input) {
2007
- const embedding = vectorLiteral(input.embedding);
2008
- const vectorScore = clampedVectorScore(embedding);
2009
- const minVectorScore = input.minVectorScore ?? 0;
2010
- return await db.execute(sql`
2011
- SELECT
2012
- id,
2013
- entity_id AS "entityId",
2014
- content,
2015
- type,
2016
- importance,
2017
- tags,
2018
- source,
2019
- metadata,
2020
- confidence,
2021
- embedding_model AS "embeddingModel",
2022
- embedding_dimensions AS "embeddingDimensions",
2023
- superseded_by AS "supersededBy",
2024
- superseded_at AS "supersededAt",
2025
- expires_at AS "expiresAt",
2026
- last_accessed_at AS "lastAccessedAt",
2027
- access_count AS "accessCount",
2028
- created_at AS "createdAt",
2029
- updated_at AS "updatedAt",
2030
- ${vectorScore} AS "vectorScore"
2031
- FROM mnemocyte_memories
2032
- WHERE
2033
- entity_id = ${input.entityId}
2034
- AND embedding IS NOT NULL
2035
- ${input.includeSuperseded ? sql`` : sql`AND superseded_by IS NULL`}
2036
- ${input.includeExpired ? sql`` : sql`AND (expires_at IS NULL OR expires_at > now())`}
2037
- ${input.types && input.types.length > 0 ? sql`AND type IN (${sql.join(input.types.map((type) => sql`${type}`), sql`, `)})` : sql``}
2038
- ${rawTagsFilter(input.tags)}
2039
- ${input.before ? sql`AND created_at < ${timestampParam(input.before)}` : sql``}
2040
- ${input.after ? sql`AND created_at > ${timestampParam(input.after)}` : sql``}
2041
- AND ${vectorScore} >= ${minVectorScore}
2042
- ORDER BY embedding <=> ${embedding}::vector
2043
- LIMIT ${input.limit}
2044
- `);
2045
- }
2046
- async function lexicalSearch(db, input) {
2047
- return await db.execute(sql`
2048
- SELECT
2049
- id,
2050
- entity_id AS "entityId",
2051
- content,
2052
- type,
2053
- importance,
2054
- tags,
2055
- source,
2056
- metadata,
2057
- confidence,
2058
- embedding_model AS "embeddingModel",
2059
- embedding_dimensions AS "embeddingDimensions",
2060
- superseded_by AS "supersededBy",
2061
- superseded_at AS "supersededAt",
2062
- expires_at AS "expiresAt",
2063
- last_accessed_at AS "lastAccessedAt",
2064
- access_count AS "accessCount",
2065
- created_at AS "createdAt",
2066
- updated_at AS "updatedAt",
2067
- ts_rank(to_tsvector('english', content), websearch_to_tsquery('english', ${input.query})) AS "lexicalScore"
2068
- FROM mnemocyte_memories
2069
- WHERE
2070
- entity_id = ${input.entityId}
2071
- ${input.includeSuperseded ? sql`` : sql`AND superseded_by IS NULL`}
2072
- ${input.includeExpired ? sql`` : sql`AND (expires_at IS NULL OR expires_at > now())`}
2073
- ${input.types && input.types.length > 0 ? sql`AND type IN (${sql.join(input.types.map((type) => sql`${type}`), sql`, `)})` : sql``}
2074
- ${rawTagsFilter(input.tags)}
2075
- ${input.before ? sql`AND created_at < ${timestampParam(input.before)}` : sql``}
2076
- ${input.after ? sql`AND created_at > ${timestampParam(input.after)}` : sql``}
2077
- AND to_tsvector('english', content) @@ websearch_to_tsquery('english', ${input.query})
2078
- ORDER BY "lexicalScore" DESC
2079
- LIMIT ${input.limit}
2080
- `);
2081
- }
2082
- async function getInstallationMeta(db) {
2083
- return (await db.select().from(metaTable).where(eq(metaTable.key, "installation")).limit(1))[0] ?? null;
2084
- }
2085
- async function getStoredEmbeddingModels(db) {
2086
- return (await db.selectDistinct({ embeddingModel: memoriesTable.embeddingModel }).from(memoriesTable).limit(2)).map((row) => row.embeddingModel);
2087
- }
2088
- async function recordInstallationEmbeddingModel(db, embeddingModel) {
2089
- return (await db.update(metaTable).set({ embeddingModel }).where(and(eq(metaTable.key, "installation"), isNull(metaTable.embeddingModel))).returning())[0] ?? null;
2090
- }
2091
- //#endregion
2092
- //#region src/memory/postgres-records.ts
2093
- function toDate(value) {
2094
- return value instanceof Date ? value : new Date(value);
2095
- }
2096
- function toNullableDate(value) {
2097
- return value === null ? null : toDate(value);
2098
- }
2099
- function rowToMemory(row) {
2100
- return {
2101
- id: row.id,
2102
- entityId: row.entityId,
2103
- content: row.content,
2104
- type: row.type,
2105
- importance: row.importance,
2106
- tags: row.tags,
2107
- source: row.source,
2108
- metadata: cloneJsonObject(row.metadata),
2109
- confidence: row.confidence,
2110
- embeddingModel: row.embeddingModel,
2111
- embeddingDimensions: row.embeddingDimensions,
2112
- supersededBy: row.supersededBy,
2113
- supersededAt: toNullableDate(row.supersededAt),
2114
- expiresAt: toNullableDate(row.expiresAt),
2115
- lastAccessedAt: toNullableDate(row.lastAccessedAt),
2116
- accessCount: row.accessCount,
2117
- createdAt: toDate(row.createdAt),
2118
- updatedAt: toDate(row.updatedAt)
2119
- };
2120
- }
2121
- //#endregion
2122
- //#region src/memory/postgres.ts
2123
- const IMPORTANCE_LEVELS = [
2124
- "low",
2125
- "normal",
2126
- "high",
2127
- "critical"
2128
- ];
2129
- const MIGRATION_ERROR_CODES = new Set([
2130
- "42P01",
2131
- "42703",
2132
- "42704",
2133
- "42883"
2134
- ]);
2135
- const CONSOLIDATION_SURVIVOR_FOREIGN_KEY = "mnemocyte_memories_superseded_by_mnemocyte_memories_id_fk";
2136
- function rowToAuditEvent(row) {
2137
- return {
2138
- id: row.id,
2139
- entityId: row.entityId,
2140
- description: row.description,
2141
- metadata: cloneJsonObject(row.metadata),
2142
- timestamp: new Date(row.timestamp)
2143
- };
2144
- }
2145
- function importanceCeilingLevels(max) {
2146
- return IMPORTANCE_LEVELS.filter((level) => IMPORTANCE_RANK[level] <= IMPORTANCE_RANK[max]);
2147
- }
2148
- function hasPostgresErrorCode(error, code) {
2149
- return typeof error === "object" && error !== null && "code" in error && error.code === code;
2150
- }
2151
- function hasPostgresConstraintName(error, name) {
2152
- return typeof error === "object" && error !== null && "constraint_name" in error && error.constraint_name === name;
2153
- }
2154
- function getPostgresErrorCode(error) {
2155
- return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
2156
- }
2157
- function normalizePostgresError(error) {
2158
- if (error instanceof MnemocyteError) return error;
2159
- const code = getPostgresErrorCode(error);
2160
- 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);
2161
- return new MnemocyteError("Postgres operation failed.", "DB", error);
2162
- }
2163
- async function runPostgresOperation(operation) {
2164
- try {
2165
- return await operation();
2166
- } catch (error) {
2167
- throw normalizePostgresError(error);
2168
- }
2169
- }
2170
- async function runPostgresDeleteOperation(operation) {
2171
- try {
2172
- return await operation();
2173
- } catch (error) {
2174
- if (error instanceof MnemocyteError) throw error;
2175
- if (hasPostgresErrorCode(error, "23503") && hasPostgresConstraintName(error, CONSOLIDATION_SURVIVOR_FOREIGN_KEY)) throw memoryHasDependentsError(error);
2176
- throw normalizePostgresError(error);
2177
- }
2178
- }
2179
- function toPruneFilter(input) {
2180
- const filter = {};
2181
- if (input.entityId !== void 0) filter.entityId = input.entityId;
2182
- if (input.expired === true) filter.expired = true;
2183
- if (input.superseded === true) filter.superseded = true;
2184
- if (input.createdBefore !== void 0) filter.createdBefore = input.createdBefore;
2185
- if (input.notAccessedSince !== void 0) filter.notAccessedSince = input.notAccessedSince;
2186
- if (input.types !== void 0 && input.types.length > 0) filter.types = input.types;
2187
- if (input.tags !== void 0 && input.tags.length > 0) filter.tags = input.tags;
2188
- if (input.maxImportance !== void 0) filter.maxImportanceLevels = importanceCeilingLevels(input.maxImportance);
2189
- return filter;
2190
- }
2191
- function toMemoryRow(memory) {
2192
- return {
2193
- ...memory,
2194
- tags: [...memory.tags],
2195
- embedding: [...memory.embedding]
2196
- };
2197
- }
2198
- function createPostgresStore(handle) {
2199
- let schemaValidated = false;
2200
- let schemaValidationPromise;
2201
- async function ensureEmbeddingCompatibility(embedder) {
2202
- if (schemaValidated) return;
2203
- if (!schemaValidationPromise) schemaValidationPromise = validateEmbeddingCompatibility(embedder).catch((error) => {
2204
- schemaValidationPromise = void 0;
2205
- throw error;
2206
- });
2207
- await schemaValidationPromise;
2208
- }
2209
- async function validateEmbeddingCompatibility(embedder) {
2210
- let meta;
2211
- try {
2212
- meta = await getInstallationMeta(handle.db);
2213
- } catch (error) {
2214
- if (hasPostgresErrorCode(error, "42P01") || hasPostgresErrorCode(error, "42703")) throw new MnemocyteError("Postgres embedding metadata is missing or outdated. Apply the bundled migrations through 0002_add_embedding_model.sql, or render a fresh dimension-specific initial migration, before using embedding-dependent operations.", "MIGRATION", error);
2215
- throw normalizePostgresError(error);
2216
- }
2217
- if (!meta) throw new MnemocyteError("mnemocyte_meta is missing the \"installation\" row. Apply the bundled migrations, or render a fresh dimension-specific initial migration, before using embedding-dependent operations.", "MIGRATION");
2218
- 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");
2219
- let installationModel = meta.embeddingModel;
2220
- if (installationModel === null) {
2221
- try {
2222
- const storedModels = await getStoredEmbeddingModels(handle.db);
2223
- if (storedModels.length > 1) throw new MnemocyteError("mnemocyte_meta.embedding_model is unset, but stored memories contain multiple embedding_model values. Re-embed or remove the mixed historical rows, then explicitly record the installation model before using embedding-dependent operations.", "MIGRATION");
2224
- const inferredModel = storedModels[0] ?? embedder.model;
2225
- const recorded = await recordInstallationEmbeddingModel(handle.db, inferredModel);
2226
- if (recorded) installationModel = recorded.embeddingModel;
2227
- else installationModel = (await getInstallationMeta(handle.db))?.embeddingModel ?? null;
2228
- } catch (error) {
2229
- throw normalizePostgresError(error);
2230
- }
2231
- if (installationModel === null) throw new MnemocyteError("mnemocyte_meta.embedding_model could not be recorded. Verify the installation metadata row and retry.", "MIGRATION");
2232
- }
2233
- if (installationModel !== embedder.model) throw new MnemocyteError(`embedder.model ("${embedder.model}") must match mnemocyte_meta.embedding_model ("${installationModel}"). Configure the recorded model, or explicitly re-embed existing memories and update the installation metadata.`, "CONFIG");
2234
- schemaValidated = true;
1249
+ // src/memory/lazy-postgres.ts
1250
+ function createLazyPostgresStore(databaseUrl) {
1251
+ let storePromise;
1252
+ function loadStore() {
1253
+ storePromise ??= import("./postgres-runtime-51fQVwqa.mjs").then(({ createPostgresStoreFromUrl }) => createPostgresStoreFromUrl(databaseUrl));
1254
+ return storePromise;
2235
1255
  }
2236
1256
  return {
2237
1257
  backend: "postgres",
2238
- async ensureSchema() {},
2239
- ensureEmbeddingCompatibility,
1258
+ async ensureSchema() {
1259
+ return (await loadStore()).ensureSchema();
1260
+ },
1261
+ async ensureEmbeddingCompatibility(embedder) {
1262
+ return (await loadStore()).ensureEmbeddingCompatibility(embedder);
1263
+ },
2240
1264
  async insertMemories(memories) {
2241
- return runPostgresOperation(async () => {
2242
- return (await insertMemories(handle.db, memories.map(toMemoryRow))).map(rowToMemory);
2243
- });
1265
+ return (await loadStore()).insertMemories(memories);
2244
1266
  },
2245
1267
  async vectorSearch(input) {
2246
- return runPostgresOperation(async () => {
2247
- return (await vectorSearch(handle.db, input)).map((row) => ({
2248
- memory: rowToMemory(row),
2249
- vectorScore: row.vectorScore
2250
- }));
2251
- });
1268
+ return (await loadStore()).vectorSearch(input);
2252
1269
  },
2253
1270
  async lexicalSearch(input) {
2254
- return runPostgresOperation(async () => {
2255
- return (await lexicalSearch(handle.db, input)).map((row) => ({
2256
- memory: rowToMemory(row),
2257
- lexicalScore: row.lexicalScore
2258
- }));
2259
- });
1271
+ return (await loadStore()).lexicalSearch(input);
2260
1272
  },
2261
1273
  async getMemoryEmbeddings(memoryIds) {
2262
- return runPostgresOperation(() => getMemoryEmbeddings(handle.db, memoryIds));
1274
+ return (await loadStore()).getMemoryEmbeddings(memoryIds);
2263
1275
  },
2264
1276
  async markMemoriesAccessed(memoryIds) {
2265
- return runPostgresOperation(async () => {
2266
- return (await markMemoriesAccessed(handle.db, memoryIds)).map((row) => {
2267
- if (row.lastAccessedAt === null) throw new MnemocyteError("Postgres access update returned no timestamp.", "DB");
2268
- return {
2269
- ...row,
2270
- lastAccessedAt: row.lastAccessedAt
2271
- };
2272
- });
2273
- });
1277
+ return (await loadStore()).markMemoriesAccessed(memoryIds);
2274
1278
  },
2275
1279
  async deleteMemory(entityId, memoryId) {
2276
- return runPostgresDeleteOperation(async () => {
2277
- const result = await deleteMemory(handle.db, entityId, memoryId);
2278
- if (result.hasDependents) throw memoryHasDependentsError();
2279
- return result.deletedCount > 0;
2280
- });
1280
+ return (await loadStore()).deleteMemory(entityId, memoryId);
2281
1281
  },
2282
1282
  async deleteMemoriesForEntity(entityId) {
2283
- return runPostgresDeleteOperation(async () => {
2284
- const result = await deleteMemoriesForEntity(handle.db, entityId);
2285
- if (result.hasDependents) throw memoryHasDependentsError();
2286
- return result.deletedCount;
2287
- });
1283
+ return (await loadStore()).deleteMemoriesForEntity(entityId);
2288
1284
  },
2289
1285
  async prune(input, options) {
2290
- return runPostgresDeleteOperation(async () => {
2291
- throwIfAborted(options?.signal);
2292
- assertPruneFilterHasSelector(input);
2293
- const filter = toPruneFilter(input);
2294
- if (input.dryRun === true) return {
2295
- matchedCount: await countPruneMatches(handle.db, filter, options?.signal),
2296
- deletedCount: 0,
2297
- dryRun: true,
2298
- deletedByEntity: []
2299
- };
2300
- const result = await pruneMemories(handle.db, filter, options?.signal);
2301
- if (result.hasDependents) throw memoryHasDependentsError();
2302
- return {
2303
- matchedCount: result.matchedCount,
2304
- deletedCount: result.deletedCount,
2305
- dryRun: false,
2306
- deletedByEntity: result.deletedByEntity
2307
- };
2308
- });
1286
+ return (await loadStore()).prune(input, options);
2309
1287
  },
2310
1288
  async findDuplicatePairs(input, options) {
2311
- return runPostgresOperation(async () => {
2312
- return (await findDuplicatePairs(handle.db, {
2313
- entityId: input.entityId,
2314
- threshold: input.threshold ?? .95,
2315
- limit: input.limit ?? 100,
2316
- ...input.types === void 0 ? {} : { types: input.types },
2317
- ...input.tags === void 0 ? {} : { tags: input.tags },
2318
- ...input.includeSuperseded === void 0 ? {} : { includeSuperseded: input.includeSuperseded },
2319
- ...input.includeExpired === void 0 ? {} : { includeExpired: input.includeExpired }
2320
- }, options?.signal)).map((row) => ({
2321
- a: rowToMemory(row.a),
2322
- b: rowToMemory(row.b),
2323
- similarity: Math.max(0, Math.min(1, row.similarity))
2324
- }));
2325
- });
1289
+ return (await loadStore()).findDuplicatePairs(input, options);
2326
1290
  },
2327
1291
  async addAuditEvents(events) {
2328
- return runPostgresOperation(async () => {
2329
- for (const event of events) await insertEvent(handle.db, {
2330
- id: event.id,
2331
- entityId: event.entityId,
2332
- description: event.description,
2333
- metadata: cloneJsonObject(event.metadata),
2334
- timestamp: event.timestamp
2335
- });
2336
- });
1292
+ return (await loadStore()).addAuditEvents(events);
2337
1293
  },
2338
1294
  async listAuditLog(input, options) {
2339
- return runPostgresOperation(async () => {
2340
- return (await listEvents(handle.db, {
2341
- entityId: input.entityId,
2342
- limit: input.limit ?? 50,
2343
- ...input.before === void 0 ? {} : { before: input.before },
2344
- ...input.after === void 0 ? {} : { after: input.after },
2345
- ...input.beforeCursor === void 0 ? {} : { beforeCursor: input.beforeCursor },
2346
- ...input.afterCursor === void 0 ? {} : { afterCursor: input.afterCursor }
2347
- }, options?.signal)).map(rowToAuditEvent);
2348
- });
1295
+ return (await loadStore()).listAuditLog(input, options);
2349
1296
  },
2350
1297
  async getMemory(entityId, memoryId, options) {
2351
- return runPostgresOperation(async () => {
2352
- throwIfAborted(options?.signal);
2353
- const row = await getMemoryById(handle.db, entityId, memoryId);
2354
- throwIfAborted(options?.signal);
2355
- return row ? rowToMemory(row) : null;
2356
- });
1298
+ return (await loadStore()).getMemory(entityId, memoryId, options);
2357
1299
  },
2358
1300
  async loadConsolidationTargets(entityId, ids, options) {
2359
- return runPostgresOperation(async () => {
2360
- throwIfAborted(options?.signal);
2361
- const targets = await loadConsolidationTargets(handle.db, entityId, ids);
2362
- throwIfAborted(options?.signal);
2363
- return targets;
2364
- });
1301
+ return (await loadStore()).loadConsolidationTargets(entityId, ids, options);
2365
1302
  },
2366
1303
  async consolidate(input, options) {
2367
- return runPostgresOperation(async () => {
2368
- throwIfAborted(options?.signal);
2369
- return { supersededIds: await handle.db.transaction(async (tx) => {
2370
- throwIfAborted(options?.signal);
2371
- const targets = await lockConsolidationTargets(tx, input.entityId, input.supersededIds);
2372
- throwIfAborted(options?.signal);
2373
- if (targets.some((target) => target.supersededBy !== null && target.supersededBy !== input.survivorId)) throw new MnemocyteError("Superseded memory already belongs to a different survivor.", "CONFLICT");
2374
- const activeIds = targets.filter((target) => target.supersededBy === null).map((target) => target.id);
2375
- const updated = await markMemoriesSuperseded(tx, {
2376
- survivorId: input.survivorId,
2377
- entityId: input.entityId,
2378
- ids: activeIds,
2379
- now: input.now
2380
- });
2381
- throwIfAborted(options?.signal);
2382
- const ids = updated.map((row) => row.id);
2383
- if (input.auditEnabled) for (const id of ids) {
2384
- throwIfAborted(options?.signal);
2385
- await insertEvent(tx, {
2386
- id: createEventId(),
2387
- entityId: input.entityId,
2388
- description: "memory.superseded",
2389
- metadata: {
2390
- memoryId: id,
2391
- supersededBy: input.survivorId
2392
- },
2393
- timestamp: input.now
2394
- });
2395
- throwIfAborted(options?.signal);
2396
- }
2397
- if (input.mergeTags && updated.length > 0) {
2398
- const mergedTags = new Set(input.survivorTags);
2399
- for (const row of updated) {
2400
- throwIfAborted(options?.signal);
2401
- for (const tag of row.tags) mergedTags.add(tag);
2402
- }
2403
- if (mergedTags.size !== input.survivorTags.length) {
2404
- throwIfAborted(options?.signal);
2405
- await setMemoryTags(tx, {
2406
- entityId: input.entityId,
2407
- memoryId: input.survivorId,
2408
- tags: [...mergedTags],
2409
- now: input.now
2410
- });
2411
- throwIfAborted(options?.signal);
2412
- }
2413
- }
2414
- throwIfAborted(options?.signal);
2415
- return ids;
2416
- }) };
2417
- });
1304
+ return (await loadStore()).consolidate(input, options);
2418
1305
  },
2419
1306
  async stats(input, now) {
2420
- return runPostgresOperation(async () => {
2421
- if (input?.entityId) {
2422
- const counts = await getEntityMemoryStatsCounts(handle.db, input.entityId, now);
2423
- return {
2424
- entityId: input.entityId,
2425
- ...counts
2426
- };
2427
- }
2428
- return await getGlobalMemoryStatsCounts(handle.db, now);
2429
- });
1307
+ return (await loadStore()).stats(input, now);
2430
1308
  },
2431
1309
  async close() {
2432
- return runPostgresOperation(() => handle.close());
1310
+ return (await loadStore()).close();
2433
1311
  }
2434
1312
  };
2435
1313
  }
2436
- function createPostgresClient(config, handle) {
2437
- return createMemoryClient(config, createPostgresStore(handle));
2438
- }
2439
- //#endregion
2440
- //#region src/client.ts
1314
+ // src/client.ts
2441
1315
  /**
2442
1316
  * Create a Mnemocyte client.
2443
1317
  *
2444
- * When {@link MnemocyteConfig.databaseUrl} is provided, a Postgres-backed
2445
- * client is returned (requires the `pgvector` extension and the bundled
2446
- * migration applied). Otherwise an in-memory client is returned, intended
2447
- * for tests, demos, and short-lived processes.
1318
+ * When {@link MnemocyteConfig.store} is provided, that adapter is used. When
1319
+ * {@link MnemocyteConfig.databaseUrl} is provided, a URL-owned Postgres client
1320
+ * is used (requires the `pgvector` extension and the bundled migrations).
1321
+ * When neither is supplied, an in-memory client is returned for tests, demos,
1322
+ * and short-lived processes.
2448
1323
  *
2449
- * The returned client owns any underlying resources (e.g. the Postgres
2450
- * connection pool). Call {@link MnemocyteClient.close} when finished to
2451
- * release them.
1324
+ * Call {@link MnemocyteClient.close} when finished. URL-created database
1325
+ * resources are owned and closed by Mnemocyte; resources behind a supplied
1326
+ * store retain the ownership policy documented by that adapter.
2452
1327
  *
2453
1328
  * @param config - Client configuration. {@link MnemocyteConfig.embedder} is required.
2454
1329
  * @returns A {@link MnemocyteClient} instance.
2455
1330
  * @throws {MnemocyteError} With code `"CONFIG"` if `embedder`, retrieval
2456
1331
  * tuning, provider resilience, or the database URL is malformed or does not
2457
- * use the `postgres:` / `postgresql:` protocol, or `"VALIDATION"` if
2458
- * `databaseUrl` is explicitly empty.
1332
+ * use the `postgres:` / `postgresql:` protocol, or if `databaseUrl` and
1333
+ * `store` are both provided; or `"VALIDATION"` if `databaseUrl` is explicitly
1334
+ * empty.
2459
1335
  *
2460
1336
  * @example Postgres-backed client
2461
1337
  * ```ts
@@ -2469,13 +1345,15 @@ function createMnemocyte(config) {
2469
1345
  assertEmbedder(config.embedder);
2470
1346
  validateRetrievalConfig(config.retrieval);
2471
1347
  validateProviderResilienceConfig(config.provider);
1348
+ if (config.databaseUrl !== void 0 && config.store !== void 0) throw new MnemocyteError("databaseUrl and store cannot be provided together.", "CONFIG");
2472
1349
  if (config.databaseUrl !== void 0) {
2473
1350
  assertNonEmptyString(config.databaseUrl, "databaseUrl");
2474
- return createPostgresClient(config, createDatabase(config.databaseUrl));
1351
+ parsePostgresDatabaseUrl(config.databaseUrl);
1352
+ return createMemoryClient(config, createLazyPostgresStore(config.databaseUrl));
2475
1353
  }
1354
+ if (config.store !== void 0) return createMemoryClient(config, unwrapMnemocyteStoreConfig(config.store));
2476
1355
  return createInMemoryClient(config);
2477
1356
  }
2478
- //#endregion
2479
1357
  export { MnemocyteError, createMnemocyte, isMnemocyteError };
2480
1358
 
2481
1359
  //# sourceMappingURL=index.mjs.map