mnemocyte 0.1.2 → 0.1.4

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
@@ -1,20 +1,20 @@
1
1
  # Mnemocyte
2
2
 
3
- Persistent memory for TypeScript AI applications.
3
+ Infrastructure-native memory for TypeScript AI applications.
4
4
 
5
- Mnemocyte stores small, durable memories for users, agents, sessions, or
6
- other entities. It provides hybrid retrieval, context formatting, lifecycle
7
- operations, audit logging, and duplicate-consolidation tools without requiring
8
- a separate vector database.
5
+ Mnemocyte stores durable memories for users, agents, sessions, and other
6
+ entities. It gives your app hybrid recall, prompt-ready context, pruning, audit
7
+ logs, and duplicate-consolidation tools on top of your existing infrastructure.
9
8
 
10
9
  > Mnemocyte is early software. APIs may change before v1.0.
11
10
 
12
11
  ## Features
13
12
 
14
- - In-memory backend for tests, demos, and short-lived processes.
13
+ - In-memory backend for tests and demos.
15
14
  - Postgres + pgvector backend for persistent storage.
16
- - Hybrid recall using vector similarity, lexical matching, recency,
17
- confidence, access count, and importance.
15
+ - User-supplied `Embedder` interface.
16
+ - Hybrid recall with vector similarity, lexical matching, recency, confidence,
17
+ access count, and importance.
18
18
  - Prompt-ready context output in Markdown, plain text, or XML.
19
19
  - Provider timeouts, retries, and `AbortSignal` cancellation.
20
20
  - Pruning, duplicate detection, audit logs, and experimental consolidation.
@@ -55,92 +55,145 @@ const memories = await client.recall({
55
55
  explain: true,
56
56
  });
57
57
 
58
+ const context = await client.buildContext({
59
+ entityId: "user_123",
60
+ query: "How should I respond?",
61
+ format: "markdown",
62
+ maxTokens: 500,
63
+ });
64
+
58
65
  await client.close();
59
66
  ```
60
67
 
61
68
  When `databaseUrl` is omitted, Mnemocyte uses the in-memory backend.
62
69
 
63
- ## Postgres
70
+ ## Embedder
64
71
 
65
- Apply the bundled migration to a Postgres database with pgvector enabled, then
66
- pass `databaseUrl`:
72
+ Mnemocyte accepts any embedder that returns one vector per input text.
67
73
 
68
74
  ```ts
69
75
  const client = createMnemocyte({
70
- databaseUrl: process.env.DATABASE_URL,
71
- embedder, // embedder.dimensions must be 1536 for the Postgres backend.
76
+ embedder: {
77
+ model: "text-embedding-3-small",
78
+ dimensions: 1536,
79
+ async embed(texts, options) {
80
+ return embedWithYourProvider(texts, {
81
+ signal: options?.signal,
82
+ });
83
+ },
84
+ },
72
85
  });
73
86
  ```
74
87
 
75
- The published package includes `migrations/0000_initial.sql`.
88
+ You can also use the optional OpenAI helper:
76
89
 
77
- The current Postgres schema uses `embedding vector(1536)`. If
78
- `embedder.dimensions` is anything else, `createMnemocyte` throws a
79
- `MnemocyteError` with code `"CONFIG"` before opening the connection pool. The
80
- in-memory backend does not enforce this constraint.
90
+ ```ts
91
+ import { openaiEmbedder } from "mnemocyte/embedders/openai";
81
92
 
82
- ## Provider Resilience
93
+ const embedder = openaiEmbedder({
94
+ model: "text-embedding-3-small",
95
+ });
96
+ ```
83
97
 
84
- Mnemocyte can apply timeouts and retries to embedder calls, and forwards
85
- `AbortSignal` cancellation to in-flight attempts. Retries and timeouts are
86
- disabled by default.
98
+ `openaiEmbedder()` reads `OPENAI_API_KEY` by default. Pass `apiKey`
99
+ explicitly when you use a different key source. The helper uses `fetch`
100
+ directly and does not depend on the OpenAI SDK; plain `import "mnemocyte"` also
101
+ stays provider-free.
102
+
103
+ ## Postgres
104
+
105
+ For persistent storage, apply the bundled migration to a Postgres database with
106
+ pgvector enabled, then pass `databaseUrl`.
87
107
 
88
108
  ```ts
89
109
  const client = createMnemocyte({
110
+ databaseUrl: process.env.DATABASE_URL!,
90
111
  embedder,
91
- provider: {
92
- timeoutMs: 5_000,
93
- retries: 2,
94
- baseDelayMs: 200,
95
- maxDelayMs: 2_000,
96
- },
97
112
  });
113
+ ```
98
114
 
99
- const controller = new AbortController();
100
- setTimeout(() => controller.abort(), 1_000);
115
+ The package includes:
101
116
 
117
+ ```txt
118
+ migrations/0000_initial.sql
119
+ ```
120
+
121
+ The current Postgres schema uses `embedding vector(1536)`, so the Postgres
122
+ backend currently requires a 1536-dimensional embedder.
123
+
124
+ If dimensions do not match, `createMnemocyte` throws a `MnemocyteError` with
125
+ code `"CONFIG"` before opening the connection pool.
126
+
127
+ The migration creates the bundled HNSW pgvector index
128
+ `mnemocyte_memories_embedding_hnsw_idx` for cosine search. HNSW is approximate:
129
+ it is fast for nearest-neighbor recall, but recall quality, index build memory,
130
+ and write overhead should be benchmarked against your data volume and write
131
+ rate. Postgres still applies ordinary filters such as `entity_id`, `type`, and
132
+ tags around vector search, so highly selective filters may need query tuning or
133
+ a workload-specific index strategy.
134
+
135
+ Large production tables should benchmark alternate indexes or custom
136
+ migrations with representative data before changing the default. IVFFlat can be
137
+ a good fit for some large, steady-state tables, but it needs tuning after the
138
+ table has representative vectors. The bundled migration intentionally does not
139
+ add full-text or tag-specific GIN indexes yet; add and benchmark expression
140
+ indexes such as `to_tsvector('english', content)` or tag-oriented indexes only
141
+ when your workload needs them.
142
+
143
+ Configurable embedding dimensions are planned for `0.2.0`.
144
+
145
+ ## API
146
+
147
+ ### `remember`
148
+
149
+ ```ts
102
150
  await client.remember({
103
151
  entityId: "user_123",
104
- content: "Prefers short answers.",
105
- signal: controller.signal,
152
+ content: "Likes concise answers.",
153
+ type: "preference",
154
+ importance: "high",
155
+ tags: ["communication"],
106
156
  });
107
157
  ```
108
158
 
109
- Provider failures surface as `MnemocyteError` values with stable `code`s:
110
-
111
- - `"TIMEOUT"`: a provider attempt exceeded `timeoutMs`.
112
- - `"ABORTED"`: the operation was cancelled via `signal` and is not retried.
113
- - `"EMBEDDING"`: the embedder failed after retries were exhausted.
159
+ ### `recall`
114
160
 
115
- ## Pruning
161
+ ```ts
162
+ const memories = await client.recall({
163
+ entityId: "user_123",
164
+ query: "How should I respond to this user?",
165
+ limit: 5,
166
+ explain: true,
167
+ });
168
+ ```
116
169
 
117
- `prune` deletes memories matching a filter. At least one selector is required;
118
- `prune({})` is rejected to avoid accidental full deletion.
170
+ ### `buildContext`
119
171
 
120
172
  ```ts
121
- await client.prune({ entityId: "user_123", expired: true });
122
-
123
- await client.prune({
124
- notAccessedSince: new Date(Date.now() - 30 * 24 * 3600 * 1000),
125
- maxImportance: "normal",
173
+ const context = await client.buildContext({
174
+ entityId: "user_123",
175
+ query: "User communication preferences",
176
+ format: "markdown",
177
+ maxTokens: 500,
126
178
  });
179
+ ```
180
+
181
+ ### `prune`
127
182
 
183
+ ```ts
128
184
  const preview = await client.prune({
129
185
  entityId: "user_123",
130
186
  superseded: true,
131
187
  dryRun: true,
132
188
  });
133
- console.log(preview.matchedCount);
134
- ```
135
-
136
- Available selectors: `entityId`, `expired`, `superseded`, `createdBefore`,
137
- `notAccessedSince`, `types`, `tags`, `maxImportance`. Selectors are combined
138
- with AND semantics.
139
189
 
140
- ## Duplicate Detection
190
+ await client.prune({
191
+ entityId: "user_123",
192
+ expired: true,
193
+ });
194
+ ```
141
195
 
142
- `findDuplicates` is an experimental, read-only scan that surfaces
143
- near-identical memories for one entity using pairwise cosine similarity.
196
+ ### `findDuplicates`
144
197
 
145
198
  ```ts
146
199
  const pairs = await client.findDuplicates({
@@ -148,20 +201,9 @@ const pairs = await client.findDuplicates({
148
201
  threshold: 0.95,
149
202
  limit: 50,
150
203
  });
151
-
152
- for (const { a, b, similarity } of pairs) {
153
- console.log(similarity.toFixed(3), a.content, "<>", b.content);
154
- }
155
204
  ```
156
205
 
157
- Optional selectors: `types`, `tags`, `includeSuperseded`, `includeExpired`.
158
- `types` and `tags` must match on both memories in each pair. Superseded and
159
- expired memories are excluded by default.
160
-
161
- ## Audit Log
162
-
163
- When `audit.enabled` is `true`, Mnemocyte records an entry for state-changing
164
- operations. Read entries with `client.listAuditLog()`.
206
+ ### `listAuditLog`
165
207
 
166
208
  ```ts
167
209
  const client = createMnemocyte({
@@ -169,53 +211,61 @@ const client = createMnemocyte({
169
211
  audit: { enabled: true },
170
212
  });
171
213
 
172
- await client.remember({ entityId: "user_123", content: "Likes tea." });
173
- await client.prune({ entityId: "user_123", expired: true });
174
-
175
- const log = await client.listAuditLog({
214
+ const events = await client.listAuditLog({
176
215
  entityId: "user_123",
177
216
  limit: 100,
178
217
  });
179
-
180
- for (const event of log) {
181
- console.log(event.timestamp, event.description, event.metadata);
182
- }
183
218
  ```
184
219
 
185
- Recorded `description` slugs:
220
+ ### `experimental.consolidate`
186
221
 
187
- - `"memory.created"`: single insert.
188
- - `"memory.deleted"`: single `forget`.
189
- - `"entity.cleared"`: `forgetAll`.
190
- - `"memory.pruned"`: non-dry-run `prune` with an `entityId` selector.
191
- - `"memory.superseded"`: experimental consolidation.
222
+ ```ts
223
+ await client.experimental.consolidate({
224
+ entityId: "user_123",
225
+ survivorId: "mem_survivor",
226
+ supersededIds: ["mem_duplicate"],
227
+ });
228
+ ```
192
229
 
193
- Audit entries are sticky: `forgetAll` does not erase prior log entries. Disable
194
- audit by leaving `audit.enabled` unset.
230
+ The `experimental` namespace may change before v1.0.
195
231
 
196
- ## Consolidation
232
+ ## Provider Resilience
197
233
 
198
- `client.experimental.consolidate()` marks likely-duplicate memories as
199
- superseded by a survivor. Superseded memories are excluded from `recall` by
200
- default. Tags are unioned onto the survivor unless `mergeTags: false`.
234
+ ```ts
235
+ const client = createMnemocyte({
236
+ embedder,
237
+ provider: {
238
+ timeoutMs: 5_000,
239
+ retries: 2,
240
+ baseDelayMs: 200,
241
+ maxDelayMs: 2_000,
242
+ },
243
+ });
244
+ ```
245
+
246
+ Pass `signal` to cancel an operation:
201
247
 
202
248
  ```ts
203
- const pairs = await client.findDuplicates({
249
+ const controller = new AbortController();
250
+
251
+ await client.remember({
204
252
  entityId: "user_123",
205
- threshold: 0.97,
253
+ content: "Prefers short answers.",
254
+ signal: controller.signal,
206
255
  });
207
-
208
- for (const { a, b } of pairs) {
209
- await client.experimental.consolidate({
210
- entityId: "user_123",
211
- survivorId: a.id,
212
- supersededIds: [b.id],
213
- });
214
- }
215
256
  ```
216
257
 
217
- The `experimental` namespace is intentionally unstable. Members may change or
218
- move before v1.0.
258
+ Common `MnemocyteError` codes:
259
+
260
+ ```txt
261
+ TIMEOUT
262
+ ABORTED
263
+ EMBEDDING
264
+ VALIDATION
265
+ CONFIG
266
+ DB
267
+ NOT_FOUND
268
+ ```
219
269
 
220
270
  ## Retrieval Tuning
221
271
 
@@ -238,19 +288,29 @@ const client = createMnemocyte({
238
288
  });
239
289
  ```
240
290
 
291
+ ## Roadmap
292
+
293
+ Planned larger milestones:
294
+
295
+ - configurable embedding dimensions with `mnemocyte_meta`
296
+ - `MemoryStore` abstraction
297
+ - `drizzleStore(db)` for caller-owned Drizzle clients
298
+ - `@mnemocyte/mcp`
299
+
300
+ See [ROADMAP.md](./ROADMAP.md).
301
+
241
302
  ## Development
242
303
 
243
304
  ```bash
244
305
  pnpm checktypes
245
306
  pnpm lint
246
- pnpm run test:retrieval
307
+ pnpm test
247
308
  pnpm run test:integration
248
309
  pnpm run pack:check
249
310
  ```
250
311
 
251
- `test:integration` skips cleanly when `DATABASE_URL` is not set.
312
+ `test:integration` skips when `DATABASE_URL` is not set.
252
313
 
253
314
  ## Architecture
254
315
 
255
- See [ARCHITECTURE.md](./ARCHITECTURE.md) for implementation details and
256
- [ROADMAP.md](./ROADMAP.md) for planned work.
316
+ See [ARCHITECTURE.md](./ARCHITECTURE.md).
@@ -0,0 +1,31 @@
1
+ import { c as Embedder } from "../types-QK_43oKJ.mjs";
2
+
3
+ //#region src/embedders/openai.d.ts
4
+ interface OpenAIEmbedderOptions {
5
+ /**
6
+ * OpenAI API key. Defaults to `process.env.OPENAI_API_KEY`.
7
+ */
8
+ readonly apiKey?: string;
9
+ /**
10
+ * OpenAI embedding model name.
11
+ */
12
+ readonly model: string;
13
+ /**
14
+ * Output dimensions. Required for unknown/future model names and accepted
15
+ * only for OpenAI models that support configurable dimensions.
16
+ */
17
+ readonly dimensions?: number;
18
+ /**
19
+ * Override the OpenAI API base URL. Intended for tests and compatible
20
+ * deployments; defaults to `https://api.openai.com/v1`.
21
+ */
22
+ readonly baseUrl?: string;
23
+ }
24
+ /**
25
+ * Create an OpenAI-backed {@link Embedder}. This adapter uses `fetch`
26
+ * directly so provider SDKs never become part of Mnemocyte's dependency graph.
27
+ */
28
+ declare function openaiEmbedder(options: OpenAIEmbedderOptions): Embedder;
29
+ //#endregion
30
+ export { OpenAIEmbedderOptions, openaiEmbedder };
31
+ //# sourceMappingURL=openai.d.mts.map
@@ -0,0 +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"}
@@ -0,0 +1,78 @@
1
+ import { t as MnemocyteError } from "../errors-BG3-mNur.mjs";
2
+ //#region src/embedders/openai.ts
3
+ const KNOWN_MODEL_DIMENSIONS = {
4
+ "text-embedding-3-small": 1536,
5
+ "text-embedding-3-large": 3072,
6
+ "text-embedding-ada-002": 1536
7
+ };
8
+ const CONFIGURABLE_DIMENSION_MODELS = new Set(["text-embedding-3-small", "text-embedding-3-large"]);
9
+ function assertDimensions(dimensions) {
10
+ if (!Number.isInteger(dimensions) || dimensions < 1) throw new MnemocyteError("openaiEmbedder dimensions must be a positive integer.", "CONFIG");
11
+ }
12
+ function resolveApiKey(apiKey) {
13
+ const resolved = apiKey ?? process.env.OPENAI_API_KEY;
14
+ if (!resolved || resolved.trim().length === 0) throw new MnemocyteError("openaiEmbedder requires an API key. Pass apiKey or set OPENAI_API_KEY.", "CONFIG");
15
+ return resolved;
16
+ }
17
+ function resolveDimensions(options) {
18
+ const defaultDimensions = KNOWN_MODEL_DIMENSIONS[options.model];
19
+ if (defaultDimensions === void 0) {
20
+ if (options.dimensions === void 0) throw new MnemocyteError(`openaiEmbedder requires dimensions for unknown model "${options.model}".`, "CONFIG");
21
+ assertDimensions(options.dimensions);
22
+ return {
23
+ embedderDimensions: options.dimensions,
24
+ requestDimensions: options.dimensions
25
+ };
26
+ }
27
+ if (options.dimensions !== void 0 && !CONFIGURABLE_DIMENSION_MODELS.has(options.model)) throw new MnemocyteError(`openaiEmbedder does not support a dimensions override for "${options.model}".`, "CONFIG");
28
+ if (options.dimensions !== void 0) {
29
+ assertDimensions(options.dimensions);
30
+ return {
31
+ embedderDimensions: options.dimensions,
32
+ requestDimensions: options.dimensions
33
+ };
34
+ }
35
+ return { embedderDimensions: defaultDimensions };
36
+ }
37
+ /**
38
+ * Create an OpenAI-backed {@link Embedder}. This adapter uses `fetch`
39
+ * directly so provider SDKs never become part of Mnemocyte's dependency graph.
40
+ */
41
+ function openaiEmbedder(options) {
42
+ const apiKey = resolveApiKey(options.apiKey);
43
+ const { embedderDimensions, requestDimensions } = resolveDimensions(options);
44
+ const baseUrl = options.baseUrl ?? "https://api.openai.com/v1";
45
+ return {
46
+ model: options.model,
47
+ dimensions: embedderDimensions,
48
+ async embed(texts, embedOptions) {
49
+ const response = await fetch(`${baseUrl}/embeddings`, {
50
+ method: "POST",
51
+ headers: {
52
+ authorization: `Bearer ${apiKey}`,
53
+ "content-type": "application/json"
54
+ },
55
+ body: JSON.stringify({
56
+ model: options.model,
57
+ input: [...texts],
58
+ encoding_format: "float",
59
+ ...requestDimensions === void 0 ? {} : { dimensions: requestDimensions }
60
+ }),
61
+ ...embedOptions?.signal === void 0 ? {} : { signal: embedOptions.signal }
62
+ });
63
+ 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;
72
+ }
73
+ };
74
+ }
75
+ //#endregion
76
+ export { openaiEmbedder };
77
+
78
+ //# sourceMappingURL=openai.mjs.map
@@ -0,0 +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"}
@@ -0,0 +1,53 @@
1
+ //#region src/errors.ts
2
+ /**
3
+ * Error type thrown by every public Mnemocyte API.
4
+ *
5
+ * Use the {@link MnemocyteError.code} property — not the message — to branch
6
+ * on failure modes, or call {@link isMnemocyteError} as a type guard when
7
+ * working with `unknown` errors.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * try {
12
+ * await client.recall({ entityId: "u1", query: "hi" });
13
+ * } catch (err) {
14
+ * if (isMnemocyteError(err) && err.code === "DB") {
15
+ * // handle database failure
16
+ * }
17
+ * throw err;
18
+ * }
19
+ * ```
20
+ */
21
+ var MnemocyteError = class extends Error {
22
+ /** Stable, machine-readable failure code. See {@link MnemocyteErrorCode}. */
23
+ code;
24
+ /** Underlying cause, if any (e.g. an original driver or embedder error). */
25
+ cause;
26
+ /**
27
+ * @param message - Human-readable error message.
28
+ * @param code - Stable failure code; see {@link MnemocyteErrorCode}.
29
+ * @param cause - Optional underlying cause (driver error, network error, etc.).
30
+ */
31
+ constructor(message, code, cause) {
32
+ super(message);
33
+ this.name = "MnemocyteError";
34
+ this.code = code;
35
+ this.cause = cause;
36
+ }
37
+ };
38
+ /**
39
+ * Type guard for {@link MnemocyteError}.
40
+ *
41
+ * Prefer this over `instanceof` when working across module boundaries or
42
+ * bundlers where multiple copies of the class may exist.
43
+ *
44
+ * @param error - Value to test (typically a caught `unknown`).
45
+ * @returns `true` if `error` is a {@link MnemocyteError}.
46
+ */
47
+ function isMnemocyteError(error) {
48
+ return error instanceof MnemocyteError;
49
+ }
50
+ //#endregion
51
+ export { isMnemocyteError as n, MnemocyteError as t };
52
+
53
+ //# sourceMappingURL=errors-BG3-mNur.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors-BG3-mNur.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 * Prefer this over `instanceof` when working across module boundaries or\n * bundlers where multiple copies of the class may exist.\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;;;;;;;;;;AAWA,SAAgB,iBAAiB,OAAyC;CACzE,OAAO,iBAAiB;AACzB"}