mnemocyte 0.3.0 → 0.4.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/README.md CHANGED
@@ -66,13 +66,16 @@ await client.close();
66
66
  ```
67
67
 
68
68
  When `databaseUrl` is omitted, Mnemocyte uses the in-memory backend.
69
+ That backend is intended for development, tests, and prototyping. For
70
+ persistent or larger workloads, use Postgres.
69
71
 
70
72
  ## Embedder
71
73
 
72
74
  Mnemocyte accepts any embedder that returns one vector per input text.
73
- Each vector must match `embedder.dimensions` and contain only finite numbers;
74
- invalid provider output fails with `MnemocyteError` code `"EMBEDDING"` before
75
- anything is stored.
75
+ Each vector must match `embedder.dimensions`, contain only finite numbers, and
76
+ have at least one nonzero component. Invalid provider output, including an
77
+ exact zero-norm vector, fails with `MnemocyteError` code `"EMBEDDING"` before
78
+ storage or vector comparison. Tiny nonzero vectors remain valid.
76
79
 
77
80
  ```ts
78
81
  const client = createMnemocyte({
@@ -102,7 +105,11 @@ const embedder = openaiEmbedder({
102
105
  explicitly when you use a different key source. The helper uses `fetch`
103
106
  directly and does not depend on the OpenAI SDK; plain `import "mnemocyte"` also
104
107
  stays provider-free. The direct subpath `mnemocyte/embedders/openai` is also
105
- supported for consumers that prefer provider-specific imports.
108
+ supported for consumers that prefer provider-specific imports. Provider
109
+ responses must contain exactly one uniquely indexed array embedding per input;
110
+ malformed response shapes fail with `MnemocyteError` code `"EMBEDDING"`.
111
+ Dimension and numeric-component validation remains at the shared embedder
112
+ boundary.
106
113
 
107
114
  ## Postgres
108
115
 
@@ -116,11 +123,16 @@ const client = createMnemocyte({
116
123
  });
117
124
  ```
118
125
 
126
+ `databaseUrl` must use the `postgres:` or `postgresql:` protocol. Other URL
127
+ protocols reject synchronously with `MnemocyteError` code `"CONFIG"`; detailed
128
+ host, database, and credential validation remains the driver's responsibility.
129
+
119
130
  The package includes:
120
131
 
121
132
  ```txt
122
133
  migrations/0000_initial.sql
123
134
  migrations/0001_add_mnemocyte_meta.sql
135
+ migrations/0002_add_embedding_model.sql
124
136
  migrations/0000_initial.sql.template
125
137
  migrations/render-initial.mjs
126
138
  ```
@@ -131,16 +143,19 @@ order:
131
143
  ```txt
132
144
  migrations/0000_initial.sql
133
145
  migrations/0001_add_mnemocyte_meta.sql
146
+ migrations/0002_add_embedding_model.sql
134
147
  ```
135
148
 
136
149
  For an existing 0.1.x Postgres install, apply
137
- `migrations/0001_add_mnemocyte_meta.sql` to record the current 1536-dimensional
138
- installation metadata.
150
+ `migrations/0001_add_mnemocyte_meta.sql` and then
151
+ `migrations/0002_add_embedding_model.sql`. Existing 0.2.x or 0.3.x installs
152
+ only need `0002_add_embedding_model.sql`.
139
153
 
140
154
  For a different embedding dimension on a fresh installation, render an explicit
141
155
  initial migration from the template and apply that rendered file instead of the
142
156
  default `0000_initial.sql`. The rendered initial migration includes the
143
- matching `mnemocyte_meta` row.
157
+ matching `mnemocyte_meta` dimensions and the installation-model column, so it
158
+ does not also need `0001` or `0002`.
144
159
 
145
160
  ```bash
146
161
  node node_modules/mnemocyte/migrations/render-initial.mjs --dimensions 768 --out migrations/0000_initial.768.sql
@@ -152,14 +167,41 @@ Inside this repository, the equivalent development shortcut is:
152
167
  pnpm migration:render -- --dimensions 768 --out migrations/0000_initial.768.sql
153
168
  ```
154
169
 
155
- The Postgres backend supports one embedding dimension per installation. Before
156
- the first embedding-dependent Postgres operation, Mnemocyte reads
157
- `mnemocyte_meta` and validates it against `embedder.dimensions` before calling
158
- the embedder. A mismatch throws a `MnemocyteError` with code `"CONFIG"`; missing
159
- v0.2.0 metadata throws code `"MIGRATION"`.
170
+ The Postgres backend supports one embedding model and dimension per
171
+ installation. Before writes, recall, or duplicate scans, Mnemocyte reads
172
+ `mnemocyte_meta` and validates both values before calling the embedder or
173
+ comparing vectors. An empty installation atomically records the configured
174
+ model on its first embedding-dependent operation. Migration `0002` records the
175
+ single model already present in historical rows when that value is
176
+ unambiguous.
177
+
178
+ A configured model or dimension mismatch throws `MnemocyteError` code
179
+ `"CONFIG"`. If historical rows contain multiple model values, the installation
180
+ model remains unset and embedding-dependent operations throw `"MIGRATION"`
181
+ until an operator re-embeds or removes the mixed rows and explicitly records
182
+ the intended installation model. Non-embedding recovery operations such as
183
+ `stats`, `forget`, and pruning remain available.
184
+
185
+ Inspect mixed history before repairing it:
186
+
187
+ ```sql
188
+ SELECT embedding_model, count(*)
189
+ FROM mnemocyte_memories
190
+ GROUP BY embedding_model;
191
+ ```
192
+
193
+ Only after every retained row belongs to the intended vector space, record that
194
+ model explicitly:
160
195
 
161
- Use the embedding dimension documented for your chosen model, render or apply a
162
- matching schema, and keep one dimension per Mnemocyte installation.
196
+ ```sql
197
+ UPDATE mnemocyte_meta
198
+ SET embedding_model = 'your-model-id'
199
+ WHERE key = 'installation';
200
+ ```
201
+
202
+ Use the model identifier and embedding dimension documented for your embedder,
203
+ render or apply a matching schema, and keep one vector space per Mnemocyte
204
+ installation.
163
205
 
164
206
  The migration creates the bundled HNSW pgvector index
165
207
  `mnemocyte_memories_embedding_hnsw_idx` for cosine search. HNSW is approximate:
@@ -200,6 +242,19 @@ bigints, non-finite numbers, class instances, or cyclic objects are rejected
200
242
  with `MnemocyteError` code `"VALIDATION"`. Mnemocyte deep-clones metadata when
201
243
  writing and reading, so later mutations do not change stored state.
202
244
 
245
+ JavaScript callers receive the same runtime domain checks as TypeScript
246
+ callers: `type` and `importance` must be known values, tags must be an array of
247
+ strings, `source` must be a string when supplied, and `expiresAt` must be a
248
+ valid `Date`. Invalid values reject with `"VALIDATION"` before embedding or
249
+ storage. Mutable tags, metadata, and expiration dates are snapshotted when
250
+ `remember` or `rememberMany` is invoked, so later caller mutations cannot
251
+ change the pending write.
252
+
253
+ When observability is configured, snapshot and validation failures follow the
254
+ same operation lifecycle as provider or storage failures: one `"start"` event
255
+ and one `"error"` event carrying the thrown value. Snapshotting remains
256
+ synchronous before any awaited `onEvent` hook.
257
+
203
258
  For batches, cancellation belongs to the whole operation:
204
259
 
205
260
  ```ts
@@ -230,6 +285,11 @@ const memories = await client.recall({
230
285
  });
231
286
  ```
232
287
 
288
+ Successful recall results include the access metadata written by that call:
289
+ `accessCount`, `lastAccessedAt`, and `updatedAt` match stored state when the
290
+ promise resolves. Ranking and score explanations use the count from before the
291
+ current recall so the access update cannot change its own ordering.
292
+
233
293
  ### `buildContext`
234
294
 
235
295
  ```ts
@@ -241,8 +301,35 @@ const context = await client.buildContext({
241
301
  });
242
302
  ```
243
303
 
304
+ Plain-text context uses a deterministic `=` fence longer than any `=` run in
305
+ the query, rendered metadata, or included memory content. The resulting
306
+ `MEMORY N START/END` boundaries cannot be reproduced by untrusted content.
307
+ Markdown continues to choose a content-safe backtick fence, and XML continues
308
+ to escape content.
309
+
244
310
  When supplied, `maxTokens` must be a positive integer or `buildContext` rejects
245
- with `"VALIDATION"`. Omitting it keeps the default token-budget path.
311
+ with `"VALIDATION"`. Omitting it keeps the default token-budget path. Returned
312
+ context always fits the supplied counter and budget; when an extremely small
313
+ budget cannot fit the full truncation marker, the result is the longest marker
314
+ fragment that fits or an empty string.
315
+
316
+ ### `forget` and `forgetAll`
317
+
318
+ ```ts
319
+ await client.forget({
320
+ entityId: "user_123",
321
+ memoryId: "mem_old",
322
+ });
323
+
324
+ await client.forgetAll({ entityId: "user_123" });
325
+ ```
326
+
327
+ A memory cannot be deleted while another memory's `supersededBy` still points
328
+ to it as a consolidation survivor. `forget` and `forgetAll` reject with
329
+ `"CONFLICT"` before deleting anything in that case. This also applies when
330
+ `forgetAll` selects both the survivor and its superseded dependent. Delete the
331
+ superseded memories first; deleting a loser, or any memory with no dependents,
332
+ continues to work normally.
246
333
 
247
334
  ### `prune`
248
335
 
@@ -259,6 +346,23 @@ await client.prune({
259
346
  });
260
347
  ```
261
348
 
349
+ Prune selectors are validated and normalized before either backend is called.
350
+ Dates must be valid `Date` instances, enum values must be known, arrays must
351
+ contain valid values, and boolean fields must be actual booleans. `false`
352
+ selector flags and empty selector arrays do not count toward the required
353
+ selector; malformed or selector-free input rejects with `"VALIDATION"` without
354
+ deleting anything. Tag selectors are trimmed and deduplicated.
355
+
356
+ A dry run may preview a survivor among its matches. A non-dry-run prune rejects
357
+ with `"CONFLICT"` if any matching memory still has consolidation dependents,
358
+ and the entire prune batch remains unchanged; unrelated matching rows are not
359
+ partially deleted.
360
+
361
+ When audit logging is enabled, every successful non-dry prune writes one
362
+ best-effort `"memory.pruned"` event per affected entity, including global
363
+ prunes without an `entityId`. Dry runs and zero-deletion runs do not write prune
364
+ events.
365
+
262
366
  ### `findDuplicates`
263
367
 
264
368
  ```ts
@@ -269,6 +373,10 @@ const pairs = await client.findDuplicates({
269
373
  });
270
374
  ```
271
375
 
376
+ The in-memory implementation compares memory pairs quadratically. It degrades
377
+ noticeably once an entity grows beyond roughly a few thousand memories, so use
378
+ the Postgres backend for duplicate detection beyond that scale.
379
+
272
380
  ### `listAuditLog`
273
381
 
274
382
  ```ts
@@ -281,8 +389,24 @@ const events = await client.listAuditLog({
281
389
  entityId: "user_123",
282
390
  limit: 100,
283
391
  });
392
+
393
+ const last = events.at(-1);
394
+ const nextPage = last
395
+ ? await client.listAuditLog({
396
+ entityId: "user_123",
397
+ limit: 100,
398
+ beforeCursor: { timestamp: last.timestamp, id: last.id },
399
+ })
400
+ : [];
284
401
  ```
285
402
 
403
+ Audit entries are ordered newest first by timestamp and event ID. The
404
+ experimental composite `beforeCursor` / `afterCursor` fields provide stable
405
+ pagination when multiple events share a timestamp. The existing `before` and
406
+ `after` fields remain strict timestamp filters; they exclude all events at the
407
+ boundary timestamp and should not be used as the sole cursor when complete
408
+ tie-safe pagination is required.
409
+
286
410
  Ordinary audit writes are best-effort: an audit storage failure does not fail
287
411
  the primary client operation, so `listAuditLog` returns only events that were
288
412
  successfully persisted. Postgres consolidation is the exception; its
@@ -299,6 +423,18 @@ await client.experimental.consolidate({
299
423
  });
300
424
  ```
301
425
 
426
+ Consolidation records each loser's `supersededBy` reference to the survivor.
427
+ Retrying the same loser with that same survivor is an idempotent no-op. If any
428
+ requested loser already points to a different survivor, the call rejects with
429
+ `"CONFLICT"`. Mixed batches are atomic: no newly eligible loser is changed, no
430
+ tags are merged, and no consolidation audit event is written when any item
431
+ conflicts. Concurrent calls follow the same target-specific rule in both
432
+ backends.
433
+
434
+ The survivor cannot later be deleted while any such reference remains:
435
+ `forget`, `forgetAll`, and matching non-dry-run `prune` calls reject with
436
+ `"CONFLICT"`. Deleting a superseded loser remains allowed.
437
+
302
438
  The `experimental` namespace may change before v1.0.
303
439
 
304
440
  ## Provider Resilience
@@ -315,6 +451,12 @@ const client = createMnemocyte({
315
451
  });
316
452
  ```
317
453
 
454
+ Provider resilience values are validated synchronously when the client is
455
+ created. Timeouts and delays must be finite non-negative numbers, retries must
456
+ be a non-negative integer, and `shouldRetry` must be a function when supplied;
457
+ invalid values throw `"CONFIG"`. A `maxDelayMs` below `baseDelayMs` remains
458
+ valid and is normalized up to the base delay.
459
+
318
460
  Pass `signal` to cancel an operation:
319
461
 
320
462
  ```ts
@@ -327,6 +469,17 @@ await client.remember({
327
469
  });
328
470
  ```
329
471
 
472
+ Pre-aborted signals reject with `"ABORTED"` before storage work begins.
473
+ In-memory maintenance scans check cancellation cooperatively. For Postgres,
474
+ standalone prune, duplicate-search, and audit-log queries request postgres.js
475
+ query cancellation while they are in flight.
476
+
477
+ Postgres consolidation has a transaction-specific boundary: cancellation is
478
+ checked between mutation steps and immediately before the transaction callback
479
+ returns. If a statement is already in flight, it may finish before the next
480
+ check throws and rolls the transaction back. An abort after the final check,
481
+ including while commit is in flight, may still leave the mutation committed.
482
+
330
483
  Common `MnemocyteError` codes:
331
484
 
332
485
  ```txt
@@ -337,6 +490,7 @@ VALIDATION
337
490
  CONFIG
338
491
  DB
339
492
  NOT_FOUND
493
+ CONFLICT
340
494
  MIGRATION
341
495
  ```
342
496
 
@@ -398,8 +552,8 @@ Known limitations before v1:
398
552
  - Configured provider timeouts abort the per-attempt `AbortSignal`; the
399
553
  underlying provider request only stops promptly when the embedder honors that
400
554
  signal.
401
- - Postgres dimension metadata is installation-wide. Mixed embedding dimensions
402
- in one database are intentionally out of scope for now.
555
+ - Postgres model and dimension metadata are installation-wide. Mixed embedding
556
+ vector spaces in one database are intentionally unsupported.
403
557
 
404
558
  Planned v1 direction:
405
559
 
@@ -1,4 +1,4 @@
1
- import { c as Embedder } from "../types-gOPLre4V.mjs";
1
+ import { l as Embedder } from "../types-DH0inEay.mjs";
2
2
 
3
3
  //#region src/embedders/openai.d.ts
4
4
  interface OpenAIEmbedderOptions {
@@ -1 +1 @@
1
- {"version":3,"file":"openai.d.mts","names":[],"sources":["../../src/embedders/openai.ts"],"mappings":";;;UAciB,qBAAA;;AAAjB;;WAIU,MAAA;EAJ4B;;;EAAA,SAQ5B,KAAA;EAUA;;AAAO;AA4EjB;EA5EU,SALA,UAAA;;;;;WAKA,OAAA;AAAA;AA4E8D;;;;AAAA,iBAAxD,cAAA,CAAe,OAAA,EAAS,qBAAA,GAAwB,QAAQ"}
1
+ {"version":3,"file":"openai.d.mts","names":[],"sources":["../../src/embedders/openai.ts"],"mappings":";;;UAciB,qBAAA;;AAAjB;;WAIU,MAAA;EAJ4B;;;EAAA,SAQ5B,KAAA;EAUA;;AAAO;AAoIjB;EApIU,SALA,UAAA;;;;;WAKA,OAAA;AAAA;AAoI8D;;;;AAAA,iBAAxD,cAAA,CAAe,OAAA,EAAS,qBAAA,GAAwB,QAAQ"}
@@ -6,6 +6,28 @@ const KNOWN_MODEL_DIMENSIONS = {
6
6
  "text-embedding-ada-002": 1536
7
7
  };
8
8
  const CONFIGURABLE_DIMENSION_MODELS = new Set(["text-embedding-3-small", "text-embedding-3-large"]);
9
+ function isRecord(value) {
10
+ return typeof value === "object" && value !== null && !Array.isArray(value);
11
+ }
12
+ function validateEmbeddingResponse(body, expectedCount) {
13
+ if (!isRecord(body) || !Array.isArray(body.data)) throw new MnemocyteError("OpenAI returned malformed embedding response data.", "EMBEDDING");
14
+ if (body.data.length !== expectedCount) throw new MnemocyteError("OpenAI returned an unexpected number of embeddings.", "EMBEDDING");
15
+ const indices = /* @__PURE__ */ new Set();
16
+ const items = [];
17
+ for (const item of body.data) {
18
+ if (!isRecord(item)) throw new MnemocyteError("OpenAI returned a malformed embedding item.", "EMBEDDING");
19
+ const { embedding, index } = item;
20
+ if (typeof index !== "number" || !Number.isInteger(index) || index < 0 || index >= expectedCount) throw new MnemocyteError("OpenAI returned an embedding with an invalid index.", "EMBEDDING");
21
+ if (indices.has(index)) throw new MnemocyteError("OpenAI returned a duplicate embedding index.", "EMBEDDING");
22
+ if (!Array.isArray(embedding)) throw new MnemocyteError("OpenAI returned an embedding that is not an array.", "EMBEDDING");
23
+ indices.add(index);
24
+ items.push({
25
+ embedding,
26
+ index
27
+ });
28
+ }
29
+ return items;
30
+ }
9
31
  function assertDimensions(dimensions) {
10
32
  if (!Number.isInteger(dimensions) || dimensions < 1) throw new MnemocyteError("openaiEmbedder dimensions must be a positive integer.", "CONFIG");
11
33
  }
@@ -61,14 +83,7 @@ function openaiEmbedder(options) {
61
83
  ...embedOptions?.signal === void 0 ? {} : { signal: embedOptions.signal }
62
84
  });
63
85
  if (!response.ok) throw Object.assign(/* @__PURE__ */ new Error(`OpenAI embeddings request failed with status ${response.status}.`), { status: response.status });
64
- const body = await response.json();
65
- const embeddings = Array.from({ length: texts.length });
66
- for (const item of body.data) {
67
- if (!Number.isInteger(item.index) || item.index < 0 || item.index >= texts.length) throw new MnemocyteError("OpenAI returned an embedding with an invalid index.", "EMBEDDING");
68
- embeddings[item.index] = item.embedding;
69
- }
70
- if (embeddings.some((embedding) => embedding === void 0)) throw new MnemocyteError("OpenAI returned fewer embeddings than requested.", "EMBEDDING");
71
- return embeddings;
86
+ return validateEmbeddingResponse(await response.json(), texts.length).sort((left, right) => left.index - right.index).map((item) => item.embedding);
72
87
  }
73
88
  };
74
89
  }
@@ -1 +1 @@
1
- {"version":3,"file":"openai.mjs","names":[],"sources":["../../src/embedders/openai.ts"],"sourcesContent":["import { MnemocyteError } from \"../errors.js\";\nimport type { Embedder } from \"../types.js\";\n\nconst KNOWN_MODEL_DIMENSIONS: Record<string, number> = {\n\t\"text-embedding-3-small\": 1536,\n\t\"text-embedding-3-large\": 3072,\n\t\"text-embedding-ada-002\": 1536,\n};\n\nconst CONFIGURABLE_DIMENSION_MODELS = new Set([\n\t\"text-embedding-3-small\",\n\t\"text-embedding-3-large\",\n]);\n\nexport interface OpenAIEmbedderOptions {\n\t/**\n\t * OpenAI API key. Defaults to `process.env.OPENAI_API_KEY`.\n\t */\n\treadonly apiKey?: string;\n\t/**\n\t * OpenAI embedding model name.\n\t */\n\treadonly model: string;\n\t/**\n\t * Output dimensions. Required for unknown/future model names and accepted\n\t * only for OpenAI models that support configurable dimensions.\n\t */\n\treadonly dimensions?: number;\n\t/**\n\t * Override the OpenAI API base URL. Intended for tests and compatible\n\t * deployments; defaults to `https://api.openai.com/v1`.\n\t */\n\treadonly baseUrl?: string;\n}\n\ninterface OpenAIEmbeddingItem {\n\treadonly embedding: number[];\n\treadonly index: number;\n}\n\ninterface OpenAIEmbeddingResponse {\n\treadonly data: OpenAIEmbeddingItem[];\n}\n\nfunction assertDimensions(dimensions: number): void {\n\tif (!Number.isInteger(dimensions) || dimensions < 1) {\n\t\tthrow new MnemocyteError(\n\t\t\t\"openaiEmbedder dimensions must be a positive integer.\",\n\t\t\t\"CONFIG\",\n\t\t);\n\t}\n}\n\nfunction resolveApiKey(apiKey: string | undefined): string {\n\tconst resolved = apiKey ?? process.env.OPENAI_API_KEY;\n\tif (!resolved || resolved.trim().length === 0) {\n\t\tthrow new MnemocyteError(\n\t\t\t\"openaiEmbedder requires an API key. Pass apiKey or set OPENAI_API_KEY.\",\n\t\t\t\"CONFIG\",\n\t\t);\n\t}\n\treturn resolved;\n}\n\nfunction resolveDimensions(options: OpenAIEmbedderOptions): {\n\tembedderDimensions: number;\n\trequestDimensions?: number;\n} {\n\tconst defaultDimensions = KNOWN_MODEL_DIMENSIONS[options.model];\n\tif (defaultDimensions === undefined) {\n\t\tif (options.dimensions === undefined) {\n\t\t\tthrow new MnemocyteError(\n\t\t\t\t`openaiEmbedder requires dimensions for unknown model \"${options.model}\".`,\n\t\t\t\t\"CONFIG\",\n\t\t\t);\n\t\t}\n\t\tassertDimensions(options.dimensions);\n\t\treturn {\n\t\t\tembedderDimensions: options.dimensions,\n\t\t\trequestDimensions: options.dimensions,\n\t\t};\n\t}\n\n\tif (\n\t\toptions.dimensions !== undefined &&\n\t\t!CONFIGURABLE_DIMENSION_MODELS.has(options.model)\n\t) {\n\t\tthrow new MnemocyteError(\n\t\t\t`openaiEmbedder does not support a dimensions override for \"${options.model}\".`,\n\t\t\t\"CONFIG\",\n\t\t);\n\t}\n\n\tif (options.dimensions !== undefined) {\n\t\tassertDimensions(options.dimensions);\n\t\treturn {\n\t\t\tembedderDimensions: options.dimensions,\n\t\t\trequestDimensions: options.dimensions,\n\t\t};\n\t}\n\n\treturn { embedderDimensions: defaultDimensions };\n}\n\n/**\n * Create an OpenAI-backed {@link Embedder}. This adapter uses `fetch`\n * directly so provider SDKs never become part of Mnemocyte's dependency graph.\n */\nexport function openaiEmbedder(options: OpenAIEmbedderOptions): Embedder {\n\tconst apiKey = resolveApiKey(options.apiKey);\n\tconst { embedderDimensions, requestDimensions } = resolveDimensions(options);\n\tconst baseUrl = options.baseUrl ?? \"https://api.openai.com/v1\";\n\n\treturn {\n\t\tmodel: options.model,\n\t\tdimensions: embedderDimensions,\n\t\tasync embed(texts, embedOptions) {\n\t\t\tconst response = await fetch(`${baseUrl}/embeddings`, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\tauthorization: `Bearer ${apiKey}`,\n\t\t\t\t\t\"content-type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tmodel: options.model,\n\t\t\t\t\tinput: [...texts],\n\t\t\t\t\tencoding_format: \"float\",\n\t\t\t\t\t...(requestDimensions === undefined\n\t\t\t\t\t\t? {}\n\t\t\t\t\t\t: { dimensions: requestDimensions }),\n\t\t\t\t}),\n\t\t\t\t...(embedOptions?.signal === undefined\n\t\t\t\t\t? {}\n\t\t\t\t\t: { signal: embedOptions.signal }),\n\t\t\t});\n\n\t\t\tif (!response.ok) {\n\t\t\t\tthrow Object.assign(\n\t\t\t\t\tnew Error(\n\t\t\t\t\t\t`OpenAI embeddings request failed with status ${response.status}.`,\n\t\t\t\t\t),\n\t\t\t\t\t{ status: response.status },\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst body = (await response.json()) as OpenAIEmbeddingResponse;\n\n\t\t\tconst embeddings: Array<number[] | undefined> = Array.from({\n\t\t\t\tlength: texts.length,\n\t\t\t});\n\t\t\tfor (const item of body.data) {\n\t\t\t\tif (\n\t\t\t\t\t!Number.isInteger(item.index) ||\n\t\t\t\t\titem.index < 0 ||\n\t\t\t\t\titem.index >= texts.length\n\t\t\t\t) {\n\t\t\t\t\tthrow new MnemocyteError(\n\t\t\t\t\t\t\"OpenAI returned an embedding with an invalid index.\",\n\t\t\t\t\t\t\"EMBEDDING\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tembeddings[item.index] = item.embedding;\n\t\t\t}\n\t\t\tif (embeddings.some((embedding) => embedding === undefined)) {\n\t\t\t\tthrow new MnemocyteError(\n\t\t\t\t\t\"OpenAI returned fewer embeddings than requested.\",\n\t\t\t\t\t\"EMBEDDING\",\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn embeddings as number[][];\n\t\t},\n\t};\n}\n"],"mappings":";;AAGA,MAAM,yBAAiD;CACtD,0BAA0B;CAC1B,0BAA0B;CAC1B,0BAA0B;AAC3B;AAEA,MAAM,gCAAgC,IAAI,IAAI,CAC7C,0BACA,wBACD,CAAC;AAgCD,SAAS,iBAAiB,YAA0B;CACnD,IAAI,CAAC,OAAO,UAAU,UAAU,KAAK,aAAa,GACjD,MAAM,IAAI,eACT,yDACA,QACD;AAEF;AAEA,SAAS,cAAc,QAAoC;CAC1D,MAAM,WAAW,UAAU,QAAQ,IAAI;CACvC,IAAI,CAAC,YAAY,SAAS,KAAK,EAAE,WAAW,GAC3C,MAAM,IAAI,eACT,0EACA,QACD;CAED,OAAO;AACR;AAEA,SAAS,kBAAkB,SAGzB;CACD,MAAM,oBAAoB,uBAAuB,QAAQ;CACzD,IAAI,sBAAsB,KAAA,GAAW;EACpC,IAAI,QAAQ,eAAe,KAAA,GAC1B,MAAM,IAAI,eACT,yDAAyD,QAAQ,MAAM,KACvE,QACD;EAED,iBAAiB,QAAQ,UAAU;EACnC,OAAO;GACN,oBAAoB,QAAQ;GAC5B,mBAAmB,QAAQ;EAC5B;CACD;CAEA,IACC,QAAQ,eAAe,KAAA,KACvB,CAAC,8BAA8B,IAAI,QAAQ,KAAK,GAEhD,MAAM,IAAI,eACT,8DAA8D,QAAQ,MAAM,KAC5E,QACD;CAGD,IAAI,QAAQ,eAAe,KAAA,GAAW;EACrC,iBAAiB,QAAQ,UAAU;EACnC,OAAO;GACN,oBAAoB,QAAQ;GAC5B,mBAAmB,QAAQ;EAC5B;CACD;CAEA,OAAO,EAAE,oBAAoB,kBAAkB;AAChD;;;;;AAMA,SAAgB,eAAe,SAA0C;CACxE,MAAM,SAAS,cAAc,QAAQ,MAAM;CAC3C,MAAM,EAAE,oBAAoB,sBAAsB,kBAAkB,OAAO;CAC3E,MAAM,UAAU,QAAQ,WAAW;CAEnC,OAAO;EACN,OAAO,QAAQ;EACf,YAAY;EACZ,MAAM,MAAM,OAAO,cAAc;GAChC,MAAM,WAAW,MAAM,MAAM,GAAG,QAAQ,cAAc;IACrD,QAAQ;IACR,SAAS;KACR,eAAe,UAAU;KACzB,gBAAgB;IACjB;IACA,MAAM,KAAK,UAAU;KACpB,OAAO,QAAQ;KACf,OAAO,CAAC,GAAG,KAAK;KAChB,iBAAiB;KACjB,GAAI,sBAAsB,KAAA,IACvB,CAAC,IACD,EAAE,YAAY,kBAAkB;IACpC,CAAC;IACD,GAAI,cAAc,WAAW,KAAA,IAC1B,CAAC,IACD,EAAE,QAAQ,aAAa,OAAO;GAClC,CAAC;GAED,IAAI,CAAC,SAAS,IACb,MAAM,OAAO,uBACZ,IAAI,MACH,gDAAgD,SAAS,OAAO,EACjE,GACA,EAAE,QAAQ,SAAS,OAAO,CAC3B;GAGD,MAAM,OAAQ,MAAM,SAAS,KAAK;GAElC,MAAM,aAA0C,MAAM,KAAK,EAC1D,QAAQ,MAAM,OACf,CAAC;GACD,KAAK,MAAM,QAAQ,KAAK,MAAM;IAC7B,IACC,CAAC,OAAO,UAAU,KAAK,KAAK,KAC5B,KAAK,QAAQ,KACb,KAAK,SAAS,MAAM,QAEpB,MAAM,IAAI,eACT,uDACA,WACD;IAED,WAAW,KAAK,SAAS,KAAK;GAC/B;GACA,IAAI,WAAW,MAAM,cAAc,cAAc,KAAA,CAAS,GACzD,MAAM,IAAI,eACT,oDACA,WACD;GAED,OAAO;EACR;CACD;AACD"}
1
+ {"version":3,"file":"openai.mjs","names":[],"sources":["../../src/embedders/openai.ts"],"sourcesContent":["import { MnemocyteError } from \"../errors.js\";\nimport type { Embedder } from \"../types.js\";\n\nconst KNOWN_MODEL_DIMENSIONS: Record<string, number> = {\n\t\"text-embedding-3-small\": 1536,\n\t\"text-embedding-3-large\": 3072,\n\t\"text-embedding-ada-002\": 1536,\n};\n\nconst CONFIGURABLE_DIMENSION_MODELS = new Set([\n\t\"text-embedding-3-small\",\n\t\"text-embedding-3-large\",\n]);\n\nexport interface OpenAIEmbedderOptions {\n\t/**\n\t * OpenAI API key. Defaults to `process.env.OPENAI_API_KEY`.\n\t */\n\treadonly apiKey?: string;\n\t/**\n\t * OpenAI embedding model name.\n\t */\n\treadonly model: string;\n\t/**\n\t * Output dimensions. Required for unknown/future model names and accepted\n\t * only for OpenAI models that support configurable dimensions.\n\t */\n\treadonly dimensions?: number;\n\t/**\n\t * Override the OpenAI API base URL. Intended for tests and compatible\n\t * deployments; defaults to `https://api.openai.com/v1`.\n\t */\n\treadonly baseUrl?: string;\n}\n\ninterface OpenAIEmbeddingItem {\n\treadonly embedding: number[];\n\treadonly index: number;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction validateEmbeddingResponse(\n\tbody: unknown,\n\texpectedCount: number,\n): OpenAIEmbeddingItem[] {\n\tif (!isRecord(body) || !Array.isArray(body.data)) {\n\t\tthrow new MnemocyteError(\n\t\t\t\"OpenAI returned malformed embedding response data.\",\n\t\t\t\"EMBEDDING\",\n\t\t);\n\t}\n\tif (body.data.length !== expectedCount) {\n\t\tthrow new MnemocyteError(\n\t\t\t\"OpenAI returned an unexpected number of embeddings.\",\n\t\t\t\"EMBEDDING\",\n\t\t);\n\t}\n\n\tconst indices = new Set<number>();\n\tconst items: OpenAIEmbeddingItem[] = [];\n\tfor (const item of body.data) {\n\t\tif (!isRecord(item)) {\n\t\t\tthrow new MnemocyteError(\n\t\t\t\t\"OpenAI returned a malformed embedding item.\",\n\t\t\t\t\"EMBEDDING\",\n\t\t\t);\n\t\t}\n\t\tconst { embedding, index } = item;\n\t\tif (\n\t\t\ttypeof index !== \"number\" ||\n\t\t\t!Number.isInteger(index) ||\n\t\t\tindex < 0 ||\n\t\t\tindex >= expectedCount\n\t\t) {\n\t\t\tthrow new MnemocyteError(\n\t\t\t\t\"OpenAI returned an embedding with an invalid index.\",\n\t\t\t\t\"EMBEDDING\",\n\t\t\t);\n\t\t}\n\t\tif (indices.has(index)) {\n\t\t\tthrow new MnemocyteError(\n\t\t\t\t\"OpenAI returned a duplicate embedding index.\",\n\t\t\t\t\"EMBEDDING\",\n\t\t\t);\n\t\t}\n\t\tif (!Array.isArray(embedding)) {\n\t\t\tthrow new MnemocyteError(\n\t\t\t\t\"OpenAI returned an embedding that is not an array.\",\n\t\t\t\t\"EMBEDDING\",\n\t\t\t);\n\t\t}\n\t\tindices.add(index);\n\t\titems.push({ embedding: embedding as number[], index });\n\t}\n\treturn items;\n}\n\nfunction assertDimensions(dimensions: number): void {\n\tif (!Number.isInteger(dimensions) || dimensions < 1) {\n\t\tthrow new MnemocyteError(\n\t\t\t\"openaiEmbedder dimensions must be a positive integer.\",\n\t\t\t\"CONFIG\",\n\t\t);\n\t}\n}\n\nfunction resolveApiKey(apiKey: string | undefined): string {\n\tconst resolved = apiKey ?? process.env.OPENAI_API_KEY;\n\tif (!resolved || resolved.trim().length === 0) {\n\t\tthrow new MnemocyteError(\n\t\t\t\"openaiEmbedder requires an API key. Pass apiKey or set OPENAI_API_KEY.\",\n\t\t\t\"CONFIG\",\n\t\t);\n\t}\n\treturn resolved;\n}\n\nfunction resolveDimensions(options: OpenAIEmbedderOptions): {\n\tembedderDimensions: number;\n\trequestDimensions?: number;\n} {\n\tconst defaultDimensions = KNOWN_MODEL_DIMENSIONS[options.model];\n\tif (defaultDimensions === undefined) {\n\t\tif (options.dimensions === undefined) {\n\t\t\tthrow new MnemocyteError(\n\t\t\t\t`openaiEmbedder requires dimensions for unknown model \"${options.model}\".`,\n\t\t\t\t\"CONFIG\",\n\t\t\t);\n\t\t}\n\t\tassertDimensions(options.dimensions);\n\t\treturn {\n\t\t\tembedderDimensions: options.dimensions,\n\t\t\trequestDimensions: options.dimensions,\n\t\t};\n\t}\n\n\tif (\n\t\toptions.dimensions !== undefined &&\n\t\t!CONFIGURABLE_DIMENSION_MODELS.has(options.model)\n\t) {\n\t\tthrow new MnemocyteError(\n\t\t\t`openaiEmbedder does not support a dimensions override for \"${options.model}\".`,\n\t\t\t\"CONFIG\",\n\t\t);\n\t}\n\n\tif (options.dimensions !== undefined) {\n\t\tassertDimensions(options.dimensions);\n\t\treturn {\n\t\t\tembedderDimensions: options.dimensions,\n\t\t\trequestDimensions: options.dimensions,\n\t\t};\n\t}\n\n\treturn { embedderDimensions: defaultDimensions };\n}\n\n/**\n * Create an OpenAI-backed {@link Embedder}. This adapter uses `fetch`\n * directly so provider SDKs never become part of Mnemocyte's dependency graph.\n */\nexport function openaiEmbedder(options: OpenAIEmbedderOptions): Embedder {\n\tconst apiKey = resolveApiKey(options.apiKey);\n\tconst { embedderDimensions, requestDimensions } = resolveDimensions(options);\n\tconst baseUrl = options.baseUrl ?? \"https://api.openai.com/v1\";\n\n\treturn {\n\t\tmodel: options.model,\n\t\tdimensions: embedderDimensions,\n\t\tasync embed(texts, embedOptions) {\n\t\t\tconst response = await fetch(`${baseUrl}/embeddings`, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\tauthorization: `Bearer ${apiKey}`,\n\t\t\t\t\t\"content-type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tmodel: options.model,\n\t\t\t\t\tinput: [...texts],\n\t\t\t\t\tencoding_format: \"float\",\n\t\t\t\t\t...(requestDimensions === undefined\n\t\t\t\t\t\t? {}\n\t\t\t\t\t\t: { dimensions: requestDimensions }),\n\t\t\t\t}),\n\t\t\t\t...(embedOptions?.signal === undefined\n\t\t\t\t\t? {}\n\t\t\t\t\t: { signal: embedOptions.signal }),\n\t\t\t});\n\n\t\t\tif (!response.ok) {\n\t\t\t\tthrow Object.assign(\n\t\t\t\t\tnew Error(\n\t\t\t\t\t\t`OpenAI embeddings request failed with status ${response.status}.`,\n\t\t\t\t\t),\n\t\t\t\t\t{ status: response.status },\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst items = validateEmbeddingResponse(\n\t\t\t\tawait response.json(),\n\t\t\t\ttexts.length,\n\t\t\t);\n\t\t\treturn items\n\t\t\t\t.sort((left, right) => left.index - right.index)\n\t\t\t\t.map((item) => item.embedding);\n\t\t},\n\t};\n}\n"],"mappings":";;AAGA,MAAM,yBAAiD;CACtD,0BAA0B;CAC1B,0BAA0B;CAC1B,0BAA0B;AAC3B;AAEA,MAAM,gCAAgC,IAAI,IAAI,CAC7C,0BACA,wBACD,CAAC;AA4BD,SAAS,SAAS,OAAkD;CACnE,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC3E;AAEA,SAAS,0BACR,MACA,eACwB;CACxB,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC,MAAM,QAAQ,KAAK,IAAI,GAC9C,MAAM,IAAI,eACT,sDACA,WACD;CAED,IAAI,KAAK,KAAK,WAAW,eACxB,MAAM,IAAI,eACT,uDACA,WACD;CAGD,MAAM,0BAAU,IAAI,IAAY;CAChC,MAAM,QAA+B,CAAC;CACtC,KAAK,MAAM,QAAQ,KAAK,MAAM;EAC7B,IAAI,CAAC,SAAS,IAAI,GACjB,MAAM,IAAI,eACT,+CACA,WACD;EAED,MAAM,EAAE,WAAW,UAAU;EAC7B,IACC,OAAO,UAAU,YACjB,CAAC,OAAO,UAAU,KAAK,KACvB,QAAQ,KACR,SAAS,eAET,MAAM,IAAI,eACT,uDACA,WACD;EAED,IAAI,QAAQ,IAAI,KAAK,GACpB,MAAM,IAAI,eACT,gDACA,WACD;EAED,IAAI,CAAC,MAAM,QAAQ,SAAS,GAC3B,MAAM,IAAI,eACT,sDACA,WACD;EAED,QAAQ,IAAI,KAAK;EACjB,MAAM,KAAK;GAAa;GAAuB;EAAM,CAAC;CACvD;CACA,OAAO;AACR;AAEA,SAAS,iBAAiB,YAA0B;CACnD,IAAI,CAAC,OAAO,UAAU,UAAU,KAAK,aAAa,GACjD,MAAM,IAAI,eACT,yDACA,QACD;AAEF;AAEA,SAAS,cAAc,QAAoC;CAC1D,MAAM,WAAW,UAAU,QAAQ,IAAI;CACvC,IAAI,CAAC,YAAY,SAAS,KAAK,EAAE,WAAW,GAC3C,MAAM,IAAI,eACT,0EACA,QACD;CAED,OAAO;AACR;AAEA,SAAS,kBAAkB,SAGzB;CACD,MAAM,oBAAoB,uBAAuB,QAAQ;CACzD,IAAI,sBAAsB,KAAA,GAAW;EACpC,IAAI,QAAQ,eAAe,KAAA,GAC1B,MAAM,IAAI,eACT,yDAAyD,QAAQ,MAAM,KACvE,QACD;EAED,iBAAiB,QAAQ,UAAU;EACnC,OAAO;GACN,oBAAoB,QAAQ;GAC5B,mBAAmB,QAAQ;EAC5B;CACD;CAEA,IACC,QAAQ,eAAe,KAAA,KACvB,CAAC,8BAA8B,IAAI,QAAQ,KAAK,GAEhD,MAAM,IAAI,eACT,8DAA8D,QAAQ,MAAM,KAC5E,QACD;CAGD,IAAI,QAAQ,eAAe,KAAA,GAAW;EACrC,iBAAiB,QAAQ,UAAU;EACnC,OAAO;GACN,oBAAoB,QAAQ;GAC5B,mBAAmB,QAAQ;EAC5B;CACD;CAEA,OAAO,EAAE,oBAAoB,kBAAkB;AAChD;;;;;AAMA,SAAgB,eAAe,SAA0C;CACxE,MAAM,SAAS,cAAc,QAAQ,MAAM;CAC3C,MAAM,EAAE,oBAAoB,sBAAsB,kBAAkB,OAAO;CAC3E,MAAM,UAAU,QAAQ,WAAW;CAEnC,OAAO;EACN,OAAO,QAAQ;EACf,YAAY;EACZ,MAAM,MAAM,OAAO,cAAc;GAChC,MAAM,WAAW,MAAM,MAAM,GAAG,QAAQ,cAAc;IACrD,QAAQ;IACR,SAAS;KACR,eAAe,UAAU;KACzB,gBAAgB;IACjB;IACA,MAAM,KAAK,UAAU;KACpB,OAAO,QAAQ;KACf,OAAO,CAAC,GAAG,KAAK;KAChB,iBAAiB;KACjB,GAAI,sBAAsB,KAAA,IACvB,CAAC,IACD,EAAE,YAAY,kBAAkB;IACpC,CAAC;IACD,GAAI,cAAc,WAAW,KAAA,IAC1B,CAAC,IACD,EAAE,QAAQ,aAAa,OAAO;GAClC,CAAC;GAED,IAAI,CAAC,SAAS,IACb,MAAM,OAAO,uBACZ,IAAI,MACH,gDAAgD,SAAS,OAAO,EACjE,GACA,EAAE,QAAQ,SAAS,OAAO,CAC3B;GAOD,OAJc,0BACb,MAAM,SAAS,KAAK,GACpB,MAAM,MAEI,EACT,MAAM,MAAM,UAAU,KAAK,QAAQ,MAAM,KAAK,EAC9C,KAAK,SAAS,KAAK,SAAS;EAC/B;CACD;AACD"}
@@ -1 +1 @@
1
- {"version":3,"file":"errors-B_7ZdjEO.mjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/**\n * Stable, machine-readable error code attached to every {@link MnemocyteError}.\n *\n * - `\"CONFIG\"` — invalid client configuration (e.g. missing or malformed embedder).\n * - `\"VALIDATION\"` — invalid argument passed to a client method.\n * - `\"DB\"` — Postgres or driver-level failure.\n * - `\"EMBEDDING\"` — embedder threw or returned an invalid vector.\n * - `\"NOT_FOUND\"` — referenced memory or entity does not exist.\n * - `\"MIGRATION\"` — schema/migration check failed against the target database.\n * - `\"TIMEOUT\"` — a provider call exceeded its configured timeout.\n * - `\"ABORTED\"` — the operation was cancelled via an `AbortSignal`.\n */\nexport type MnemocyteErrorCode =\n\t| \"CONFIG\"\n\t| \"VALIDATION\"\n\t| \"DB\"\n\t| \"EMBEDDING\"\n\t| \"NOT_FOUND\"\n\t| \"MIGRATION\"\n\t| \"TIMEOUT\"\n\t| \"ABORTED\";\n\n/**\n * Error type thrown by every public Mnemocyte API.\n *\n * Use the {@link MnemocyteError.code} property — not the message — to branch\n * on failure modes, or call {@link isMnemocyteError} as a type guard when\n * working with `unknown` errors.\n *\n * @example\n * ```ts\n * try {\n * await client.recall({ entityId: \"u1\", query: \"hi\" });\n * } catch (err) {\n * if (isMnemocyteError(err) && err.code === \"DB\") {\n * // handle database failure\n * }\n * throw err;\n * }\n * ```\n */\nexport class MnemocyteError extends Error {\n\t/** Stable, machine-readable failure code. See {@link MnemocyteErrorCode}. */\n\treadonly code: MnemocyteErrorCode;\n\t/** Underlying cause, if any (e.g. an original driver or embedder error). */\n\toverride readonly cause?: unknown;\n\n\t/**\n\t * @param message - Human-readable error message.\n\t * @param code - Stable failure code; see {@link MnemocyteErrorCode}.\n\t * @param cause - Optional underlying cause (driver error, network error, etc.).\n\t */\n\tconstructor(message: string, code: MnemocyteErrorCode, cause?: unknown) {\n\t\tsuper(message);\n\t\tthis.name = \"MnemocyteError\";\n\t\tthis.code = code;\n\t\tthis.cause = cause;\n\t}\n}\n\n/**\n * Type guard for {@link MnemocyteError}.\n *\n * This guard uses `instanceof` and recognizes errors created by the same loaded\n * Mnemocyte class. It does not recognize structurally similar errors or errors\n * created by another installed copy of the package.\n *\n * @param error - Value to test (typically a caught `unknown`).\n * @returns `true` if `error` is a {@link MnemocyteError}.\n */\nexport function isMnemocyteError(error: unknown): error is MnemocyteError {\n\treturn error instanceof MnemocyteError;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAyCA,IAAa,iBAAb,cAAoC,MAAM;;CAEzC;;CAEA;;;;;;CAOA,YAAY,SAAiB,MAA0B,OAAiB;EACvE,MAAM,OAAO;EACb,KAAK,OAAO;EACZ,KAAK,OAAO;EACZ,KAAK,QAAQ;CACd;AACD;;;;;;;;;;;AAYA,SAAgB,iBAAiB,OAAyC;CACzE,OAAO,iBAAiB;AACzB"}
1
+ {"version":3,"file":"errors-B_7ZdjEO.mjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/**\n * Stable, machine-readable error code attached to every {@link MnemocyteError}.\n *\n * - `\"CONFIG\"` — invalid client configuration (e.g. missing or malformed embedder).\n * - `\"VALIDATION\"` — invalid argument passed to a client method.\n * - `\"DB\"` — Postgres or driver-level failure.\n * - `\"EMBEDDING\"` — embedder threw or returned an invalid vector.\n * - `\"NOT_FOUND\"` — referenced memory or entity does not exist.\n * - `\"CONFLICT\"` — the requested mutation conflicts with stored relationships.\n * - `\"MIGRATION\"` — schema/migration check failed against the target database.\n * - `\"TIMEOUT\"` — a provider call exceeded its configured timeout.\n * - `\"ABORTED\"` — the operation was cancelled via an `AbortSignal`.\n */\nexport type MnemocyteErrorCode =\n\t| \"CONFIG\"\n\t| \"VALIDATION\"\n\t| \"DB\"\n\t| \"EMBEDDING\"\n\t| \"NOT_FOUND\"\n\t| \"CONFLICT\"\n\t| \"MIGRATION\"\n\t| \"TIMEOUT\"\n\t| \"ABORTED\";\n\n/**\n * Error type thrown by every public Mnemocyte API.\n *\n * Use the {@link MnemocyteError.code} property — not the message — to branch\n * on failure modes, or call {@link isMnemocyteError} as a type guard when\n * working with `unknown` errors.\n *\n * @example\n * ```ts\n * try {\n * await client.recall({ entityId: \"u1\", query: \"hi\" });\n * } catch (err) {\n * if (isMnemocyteError(err) && err.code === \"DB\") {\n * // handle database failure\n * }\n * throw err;\n * }\n * ```\n */\nexport class MnemocyteError extends Error {\n\t/** Stable, machine-readable failure code. See {@link MnemocyteErrorCode}. */\n\treadonly code: MnemocyteErrorCode;\n\t/** Underlying cause, if any (e.g. an original driver or embedder error). */\n\toverride readonly cause?: unknown;\n\n\t/**\n\t * @param message - Human-readable error message.\n\t * @param code - Stable failure code; see {@link MnemocyteErrorCode}.\n\t * @param cause - Optional underlying cause (driver error, network error, etc.).\n\t */\n\tconstructor(message: string, code: MnemocyteErrorCode, cause?: unknown) {\n\t\tsuper(message);\n\t\tthis.name = \"MnemocyteError\";\n\t\tthis.code = code;\n\t\tthis.cause = cause;\n\t}\n}\n\n/**\n * Type guard for {@link MnemocyteError}.\n *\n * This guard uses `instanceof` and recognizes errors created by the same loaded\n * Mnemocyte class. It does not recognize structurally similar errors or errors\n * created by another installed copy of the package.\n *\n * @param error - Value to test (typically a caught `unknown`).\n * @returns `true` if `error` is a {@link MnemocyteError}.\n */\nexport function isMnemocyteError(error: unknown): error is MnemocyteError {\n\treturn error instanceof MnemocyteError;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA2CA,IAAa,iBAAb,cAAoC,MAAM;;CAEzC;;CAEA;;;;;;CAOA,YAAY,SAAiB,MAA0B,OAAiB;EACvE,MAAM,OAAO;EACb,KAAK,OAAO;EACZ,KAAK,OAAO;EACZ,KAAK,QAAQ;CACd;AACD;;;;;;;;;;;AAYA,SAAgB,iBAAiB,OAAyC;CACzE,OAAO,iBAAiB;AACzB"}
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { A as RecallInput, C as MnemocyteObservation, D as ProviderResilienceConfig, E as ObservabilityConfig, F as RetrievalScoreWeights, I as RetrievalScores, L as TokenCounter, M as RememberManyInput, N as RetrievalConfig, O as PruneInput, P as RetrievalExplanation, S as MnemocyteConfig, T as MnemocyteOperation, _ as Memory, a as ConsolidateResult, b as MnemocyteBackend, c as Embedder, d as FindDuplicatesInput, f as GlobalStats, g as ListAuditLogInput, h as JsonValue, i as ConsolidateInput, j as RememberInput, k as PruneResult, l as EntityStats, m as JsonObject, n as AuditEvent, o as ContextFormat, p as ImportanceLevel, r as BuildContextInput, s as DuplicatePair, t as AuditConfig, u as ExperimentalMnemocyteClient, v as MemoryType, w as MnemocyteObservationPhase, x as MnemocyteClient, y as MemoryWithScore } from "./types-gOPLre4V.mjs";
1
+ import { A as PruneResult, C as MnemocyteConfig, D as ObservabilityConfig, E as MnemocyteOperation, F as RetrievalExplanation, I as RetrievalScoreWeights, L as RetrievalScores, M as RememberInput, N as RememberManyInput, O as ProviderResilienceConfig, P as RetrievalConfig, R as TokenCounter, S as MnemocyteClient, T as MnemocyteObservationPhase, _ as ListAuditLogInput, a as ConsolidateInput, b as MemoryWithScore, c as DuplicatePair, d as ExperimentalMnemocyteClient, f as FindDuplicatesInput, g as JsonValue, h as JsonObject, i as BuildContextInput, j as RecallInput, k as PruneInput, l as Embedder, m as ImportanceLevel, n as AuditEvent, o as ConsolidateResult, p as GlobalStats, r as AuditLogCursor, s as ContextFormat, t as AuditConfig, u as EntityStats, v as Memory, w as MnemocyteObservation, x as MnemocyteBackend, y as MemoryType } from "./types-DH0inEay.mjs";
2
2
 
3
3
  //#region src/client.d.ts
4
4
  /**
@@ -16,7 +16,8 @@ import { A as RecallInput, C as MnemocyteObservation, D as ProviderResilienceCon
16
16
  * @param config - Client configuration. {@link MnemocyteConfig.embedder} is required.
17
17
  * @returns A {@link MnemocyteClient} instance.
18
18
  * @throws {MnemocyteError} With code `"CONFIG"` if `embedder`, retrieval
19
- * tuning, or the database URL is malformed, or `"VALIDATION"` if
19
+ * tuning, provider resilience, or the database URL is malformed or does not
20
+ * use the `postgres:` / `postgresql:` protocol, or `"VALIDATION"` if
20
21
  * `databaseUrl` is explicitly empty.
21
22
  *
22
23
  * @example Postgres-backed client
@@ -38,11 +39,12 @@ declare function createMnemocyte(config: MnemocyteConfig): MnemocyteClient;
38
39
  * - `"DB"` — Postgres or driver-level failure.
39
40
  * - `"EMBEDDING"` — embedder threw or returned an invalid vector.
40
41
  * - `"NOT_FOUND"` — referenced memory or entity does not exist.
42
+ * - `"CONFLICT"` — the requested mutation conflicts with stored relationships.
41
43
  * - `"MIGRATION"` — schema/migration check failed against the target database.
42
44
  * - `"TIMEOUT"` — a provider call exceeded its configured timeout.
43
45
  * - `"ABORTED"` — the operation was cancelled via an `AbortSignal`.
44
46
  */
45
- type MnemocyteErrorCode = "CONFIG" | "VALIDATION" | "DB" | "EMBEDDING" | "NOT_FOUND" | "MIGRATION" | "TIMEOUT" | "ABORTED";
47
+ type MnemocyteErrorCode = "CONFIG" | "VALIDATION" | "DB" | "EMBEDDING" | "NOT_FOUND" | "CONFLICT" | "MIGRATION" | "TIMEOUT" | "ABORTED";
46
48
  /**
47
49
  * Error type thrown by every public Mnemocyte API.
48
50
  *
@@ -86,5 +88,5 @@ declare class MnemocyteError extends Error {
86
88
  */
87
89
  declare function isMnemocyteError(error: unknown): error is MnemocyteError;
88
90
  //#endregion
89
- export { type AuditConfig, type AuditEvent, type BuildContextInput, type ConsolidateInput, type ConsolidateResult, type ContextFormat, type DuplicatePair, type Embedder, type EntityStats, type ExperimentalMnemocyteClient, type FindDuplicatesInput, type GlobalStats, type ImportanceLevel, type JsonObject, type JsonValue, type ListAuditLogInput, type Memory, type MemoryType, type MemoryWithScore, type MnemocyteBackend, type MnemocyteClient, type MnemocyteConfig, MnemocyteError, type MnemocyteErrorCode, type MnemocyteObservation, type MnemocyteObservationPhase, type MnemocyteOperation, type ObservabilityConfig, type ProviderResilienceConfig, type PruneInput, type PruneResult, type RecallInput, type RememberInput, type RememberManyInput, type RetrievalConfig, type RetrievalExplanation, type RetrievalScoreWeights, type RetrievalScores, type TokenCounter, createMnemocyte, isMnemocyteError };
91
+ export { type AuditConfig, type AuditEvent, type AuditLogCursor, type BuildContextInput, type ConsolidateInput, type ConsolidateResult, type ContextFormat, type DuplicatePair, type Embedder, type EntityStats, type ExperimentalMnemocyteClient, type FindDuplicatesInput, type GlobalStats, type ImportanceLevel, type JsonObject, type JsonValue, type ListAuditLogInput, type Memory, type MemoryType, type MemoryWithScore, type MnemocyteBackend, type MnemocyteClient, type MnemocyteConfig, MnemocyteError, type MnemocyteErrorCode, type MnemocyteObservation, type MnemocyteObservationPhase, type MnemocyteOperation, type ObservabilityConfig, type ProviderResilienceConfig, type PruneInput, type PruneResult, type RecallInput, type RememberInput, type RememberManyInput, type RetrievalConfig, type RetrievalExplanation, type RetrievalScoreWeights, type RetrievalScores, type TokenCounter, createMnemocyte, isMnemocyteError };
90
92
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/client.ts","../src/errors.ts"],"mappings":";;;;;AAoCA;;;;;;;;AAAyE;;;;ACxBzE;;;;AAA8B;AA6B9B;;;;;;;iBDLgB,eAAA,CAAgB,MAAA,EAAQ,eAAA,GAAkB,eAAe;;;;;;AAAzE;;;;;;;;AAAyE;KCxB7D,kBAAA;;;AAAZ;;;;AAA8B;AA6B9B;;;;;;;;;;;;cAAa,cAAA,SAAuB,KAAA;EAWvB;EAAA,SATH,IAAA,EAAM,kBAAA;EASc;EAAA,SAPX,KAAA;EAOoD;AAAA;AAkBvE;;;cAlBa,OAAA,UAAiB,IAAA,EAAM,kBAAA,EAAoB,KAAA;AAAA;;;;AAkBiB;;;;;;;iBAAzD,gBAAA,CAAiB,KAAA,YAAiB,KAAA,IAAS,cAAc"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/client.ts","../src/errors.ts"],"mappings":";;;;;AAsCA;;;;;;;;AAAyE;;;;ACzBzE;;;;AAA8B;AA8B9B;;;;;;;;iBDLgB,eAAA,CAAgB,MAAA,EAAQ,eAAA,GAAkB,eAAe;;;;;;AAAzE;;;;;;;;AAAyE;;KCzB7D,kBAAA;;AAAZ;;;;AAA8B;AA8B9B;;;;;;;;;;;;;cAAa,cAAA,SAAuB,KAAA;EAWA;EAAA,SAT1B,IAAA,EAAM,kBAAA;EASwC;EAAA,SAPrC,KAAA;EAOoD;AAkBvE;;;;cAlBa,OAAA,UAAiB,IAAA,EAAM,kBAAA,EAAoB,KAAA;AAAA;;;AAkBiB;;;;;;;;iBAAzD,gBAAA,CAAiB,KAAA,YAAiB,KAAA,IAAS,cAAc"}