mnemocyte 0.0.6 → 0.0.8
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 +63 -1
- package/dist/index.d.mts +518 -4
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +621 -165
- package/dist/index.mjs.map +1 -1
- package/package.json +11 -15
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { drizzle } from "drizzle-orm/postgres-js";
|
|
2
2
|
import postgres from "postgres";
|
|
3
|
-
import { and, eq, gt, inArray, isNull, lt, sql } from "drizzle-orm";
|
|
3
|
+
import { and, eq, gt, inArray, isNotNull, isNull, lt, or, sql } from "drizzle-orm";
|
|
4
4
|
import { customType, index, integer, jsonb, pgTable, real, text, timestamp } from "drizzle-orm/pg-core";
|
|
5
5
|
//#region \0rolldown/runtime.js
|
|
6
6
|
var __defProp = Object.defineProperty;
|
|
@@ -153,9 +153,35 @@ async function buildContext(options) {
|
|
|
153
153
|
}
|
|
154
154
|
//#endregion
|
|
155
155
|
//#region src/errors.ts
|
|
156
|
+
/**
|
|
157
|
+
* Error type thrown by every public Mnemocyte API.
|
|
158
|
+
*
|
|
159
|
+
* Use the {@link MnemocyteError.code} property — not the message — to branch
|
|
160
|
+
* on failure modes, or call {@link isMnemocyteError} as a type guard when
|
|
161
|
+
* working with `unknown` errors.
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* ```ts
|
|
165
|
+
* try {
|
|
166
|
+
* await client.recall({ entityId: "u1", query: "hi" });
|
|
167
|
+
* } catch (err) {
|
|
168
|
+
* if (isMnemocyteError(err) && err.code === "DB") {
|
|
169
|
+
* // handle database failure
|
|
170
|
+
* }
|
|
171
|
+
* throw err;
|
|
172
|
+
* }
|
|
173
|
+
* ```
|
|
174
|
+
*/
|
|
156
175
|
var MnemocyteError = class extends Error {
|
|
176
|
+
/** Stable, machine-readable failure code. See {@link MnemocyteErrorCode}. */
|
|
157
177
|
code;
|
|
178
|
+
/** Underlying cause, if any (e.g. an original driver or embedder error). */
|
|
158
179
|
cause;
|
|
180
|
+
/**
|
|
181
|
+
* @param message - Human-readable error message.
|
|
182
|
+
* @param code - Stable failure code; see {@link MnemocyteErrorCode}.
|
|
183
|
+
* @param cause - Optional underlying cause (driver error, network error, etc.).
|
|
184
|
+
*/
|
|
159
185
|
constructor(message, code, cause) {
|
|
160
186
|
super(message);
|
|
161
187
|
this.name = "MnemocyteError";
|
|
@@ -163,6 +189,238 @@ var MnemocyteError = class extends Error {
|
|
|
163
189
|
this.cause = cause;
|
|
164
190
|
}
|
|
165
191
|
};
|
|
192
|
+
/**
|
|
193
|
+
* Type guard for {@link MnemocyteError}.
|
|
194
|
+
*
|
|
195
|
+
* Prefer this over `instanceof` when working across module boundaries or
|
|
196
|
+
* bundlers where multiple copies of the class may exist.
|
|
197
|
+
*
|
|
198
|
+
* @param error - Value to test (typically a caught `unknown`).
|
|
199
|
+
* @returns `true` if `error` is a {@link MnemocyteError}.
|
|
200
|
+
*/
|
|
201
|
+
function isMnemocyteError(error) {
|
|
202
|
+
return error instanceof MnemocyteError;
|
|
203
|
+
}
|
|
204
|
+
//#endregion
|
|
205
|
+
//#region src/observability.ts
|
|
206
|
+
function withMetadata(event, metadata) {
|
|
207
|
+
return {
|
|
208
|
+
...event,
|
|
209
|
+
...metadata.entityId === void 0 ? {} : { entityId: metadata.entityId },
|
|
210
|
+
...metadata.memoryId === void 0 ? {} : { memoryId: metadata.memoryId },
|
|
211
|
+
...metadata.count === void 0 ? {} : { count: metadata.count }
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
async function emitObservation(config, event) {
|
|
215
|
+
try {
|
|
216
|
+
await config.observability?.onEvent?.(event);
|
|
217
|
+
} catch {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
async function observe(config, backend, operation, metadata, action, successMetadata) {
|
|
222
|
+
const startedAt = Date.now();
|
|
223
|
+
await emitObservation(config, withMetadata({
|
|
224
|
+
operation,
|
|
225
|
+
phase: "start",
|
|
226
|
+
backend,
|
|
227
|
+
timestamp: new Date(startedAt)
|
|
228
|
+
}, metadata));
|
|
229
|
+
try {
|
|
230
|
+
const result = await action();
|
|
231
|
+
await emitObservation(config, withMetadata({
|
|
232
|
+
operation,
|
|
233
|
+
phase: "success",
|
|
234
|
+
backend,
|
|
235
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
236
|
+
durationMs: Date.now() - startedAt
|
|
237
|
+
}, {
|
|
238
|
+
...metadata,
|
|
239
|
+
...successMetadata?.(result)
|
|
240
|
+
}));
|
|
241
|
+
return result;
|
|
242
|
+
} catch (error) {
|
|
243
|
+
await emitObservation(config, withMetadata({
|
|
244
|
+
operation,
|
|
245
|
+
phase: "error",
|
|
246
|
+
backend,
|
|
247
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
248
|
+
durationMs: Date.now() - startedAt,
|
|
249
|
+
error
|
|
250
|
+
}, metadata));
|
|
251
|
+
throw error;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
//#endregion
|
|
255
|
+
//#region src/resilience.ts
|
|
256
|
+
/**
|
|
257
|
+
* Default per-provider-call timeout. Disabled by default so that callers
|
|
258
|
+
* who do not configure {@link ProviderResilienceConfig.timeoutMs} are
|
|
259
|
+
* unaffected.
|
|
260
|
+
*/
|
|
261
|
+
const DEFAULT_TIMEOUT_MS = 0;
|
|
262
|
+
/** Default number of retry attempts on transient provider failures. */
|
|
263
|
+
const DEFAULT_RETRIES = 0;
|
|
264
|
+
/** Default base backoff delay used between retry attempts. */
|
|
265
|
+
const DEFAULT_BASE_DELAY_MS = 100;
|
|
266
|
+
/** Default ceiling for retry backoff delay. */
|
|
267
|
+
const DEFAULT_MAX_DELAY_MS = 2e3;
|
|
268
|
+
/**
|
|
269
|
+
* Throw a {@link MnemocyteError} with code `"ABORTED"` if `signal` is
|
|
270
|
+
* already aborted. Safe to call with `undefined`.
|
|
271
|
+
*/
|
|
272
|
+
function throwIfAborted(signal) {
|
|
273
|
+
if (signal?.aborted) throw new MnemocyteError("Operation was aborted.", "ABORTED", signal.reason);
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Heuristic default for whether an error should be retried.
|
|
277
|
+
*
|
|
278
|
+
* Conservative by design: only retries on common transient failure
|
|
279
|
+
* indicators (network errors, 5xx-ish messages, `ECONN*` etc.). Never
|
|
280
|
+
* retries on validation, configuration, or {@link MnemocyteError} of
|
|
281
|
+
* code `"VALIDATION"` / `"CONFIG"` / `"ABORTED"`.
|
|
282
|
+
*/
|
|
283
|
+
function defaultShouldRetry(error) {
|
|
284
|
+
if (error instanceof MnemocyteError) {
|
|
285
|
+
if (error.code === "VALIDATION" || error.code === "CONFIG" || error.code === "ABORTED") return false;
|
|
286
|
+
return true;
|
|
287
|
+
}
|
|
288
|
+
if (error instanceof Error) {
|
|
289
|
+
const message = error.message.toLowerCase();
|
|
290
|
+
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;
|
|
291
|
+
}
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Compute the backoff delay for the given retry attempt, using exponential
|
|
296
|
+
* growth capped at {@link ProviderResilienceConfig.maxDelayMs}.
|
|
297
|
+
*
|
|
298
|
+
* `attempt` is 1-indexed: the delay before the first retry uses
|
|
299
|
+
* `attempt = 1`, before the second retry `attempt = 2`, etc.
|
|
300
|
+
*/
|
|
301
|
+
function computeDelayMs(attempt, baseDelayMs, maxDelayMs) {
|
|
302
|
+
const exponential = baseDelayMs * 2 ** (attempt - 1);
|
|
303
|
+
return Math.max(0, Math.min(maxDelayMs, exponential));
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Promise-based `setTimeout` that rejects with an `"ABORTED"`
|
|
307
|
+
* {@link MnemocyteError} if `signal` aborts while waiting.
|
|
308
|
+
*/
|
|
309
|
+
function delay(ms, signal) {
|
|
310
|
+
if (ms <= 0) {
|
|
311
|
+
throwIfAborted(signal);
|
|
312
|
+
return Promise.resolve();
|
|
313
|
+
}
|
|
314
|
+
return new Promise((resolve, reject) => {
|
|
315
|
+
const timer = setTimeout(() => {
|
|
316
|
+
signal?.removeEventListener("abort", onAbort);
|
|
317
|
+
resolve();
|
|
318
|
+
}, ms);
|
|
319
|
+
const onAbort = () => {
|
|
320
|
+
clearTimeout(timer);
|
|
321
|
+
reject(new MnemocyteError("Operation was aborted.", "ABORTED", signal?.reason));
|
|
322
|
+
};
|
|
323
|
+
if (signal) {
|
|
324
|
+
if (signal.aborted) {
|
|
325
|
+
clearTimeout(timer);
|
|
326
|
+
reject(new MnemocyteError("Operation was aborted.", "ABORTED", signal.reason));
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Race `promise` against the configured `timeoutMs` and the caller's
|
|
335
|
+
* `signal`. Rejects with `"TIMEOUT"` or `"ABORTED"` respectively. Resolves
|
|
336
|
+
* with the original promise value if it settles first.
|
|
337
|
+
*/
|
|
338
|
+
function withTimeout(promise, timeoutMs, signal) {
|
|
339
|
+
if (timeoutMs <= 0 && !signal) return promise;
|
|
340
|
+
return new Promise((resolve, reject) => {
|
|
341
|
+
let settled = false;
|
|
342
|
+
const timer = timeoutMs > 0 ? setTimeout(() => {
|
|
343
|
+
if (settled) return;
|
|
344
|
+
settled = true;
|
|
345
|
+
signal?.removeEventListener("abort", onAbort);
|
|
346
|
+
reject(new MnemocyteError(`Provider call timed out after ${timeoutMs}ms.`, "TIMEOUT"));
|
|
347
|
+
}, timeoutMs) : void 0;
|
|
348
|
+
const onAbort = () => {
|
|
349
|
+
if (settled) return;
|
|
350
|
+
settled = true;
|
|
351
|
+
if (timer) clearTimeout(timer);
|
|
352
|
+
reject(new MnemocyteError("Operation was aborted.", "ABORTED", signal?.reason));
|
|
353
|
+
};
|
|
354
|
+
if (signal) {
|
|
355
|
+
if (signal.aborted) {
|
|
356
|
+
if (timer) clearTimeout(timer);
|
|
357
|
+
settled = true;
|
|
358
|
+
reject(new MnemocyteError("Operation was aborted.", "ABORTED", signal.reason));
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
362
|
+
}
|
|
363
|
+
promise.then((value) => {
|
|
364
|
+
if (settled) return;
|
|
365
|
+
settled = true;
|
|
366
|
+
if (timer) clearTimeout(timer);
|
|
367
|
+
signal?.removeEventListener("abort", onAbort);
|
|
368
|
+
resolve(value);
|
|
369
|
+
}, (error) => {
|
|
370
|
+
if (settled) return;
|
|
371
|
+
settled = true;
|
|
372
|
+
if (timer) clearTimeout(timer);
|
|
373
|
+
signal?.removeEventListener("abort", onAbort);
|
|
374
|
+
reject(error);
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Run `action` with timeout, retry, and abort handling derived from
|
|
380
|
+
* `options.resilience` and `options.signal`. Each retry attempt receives
|
|
381
|
+
* a fresh `AbortSignal` linked to the caller's signal — so callers may
|
|
382
|
+
* abort mid-retry and the in-flight attempt will cancel promptly if the
|
|
383
|
+
* underlying provider honours signals.
|
|
384
|
+
*
|
|
385
|
+
* Errors from {@link MnemocyteError} with code `"VALIDATION"` /
|
|
386
|
+
* `"CONFIG"` / `"ABORTED"` are never retried.
|
|
387
|
+
*/
|
|
388
|
+
async function withResilience(action, options = {}) {
|
|
389
|
+
const config = options.resilience ?? {};
|
|
390
|
+
const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
391
|
+
const retries = Math.max(0, config.retries ?? DEFAULT_RETRIES);
|
|
392
|
+
const baseDelayMs = Math.max(0, config.baseDelayMs ?? DEFAULT_BASE_DELAY_MS);
|
|
393
|
+
const maxDelayMs = Math.max(baseDelayMs, config.maxDelayMs ?? DEFAULT_MAX_DELAY_MS);
|
|
394
|
+
const shouldRetry = config.shouldRetry ?? defaultShouldRetry;
|
|
395
|
+
const externalSignal = options.signal;
|
|
396
|
+
throwIfAborted(externalSignal);
|
|
397
|
+
const maxAttempts = retries + 1;
|
|
398
|
+
let lastError;
|
|
399
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
400
|
+
throwIfAborted(externalSignal);
|
|
401
|
+
try {
|
|
402
|
+
return await withTimeout(action(externalSignal), timeoutMs, externalSignal);
|
|
403
|
+
} catch (error) {
|
|
404
|
+
lastError = error;
|
|
405
|
+
if (error instanceof MnemocyteError && error.code === "ABORTED") throw error;
|
|
406
|
+
if (attempt >= maxAttempts) throw error;
|
|
407
|
+
if (!shouldRetry(error, attempt)) throw error;
|
|
408
|
+
await delay(computeDelayMs(attempt, baseDelayMs, maxDelayMs), externalSignal);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
throw lastError;
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Ordering of {@link ImportanceLevel} from least to most important. Used
|
|
415
|
+
* by `prune({ maxImportance })` to compare a memory's importance against
|
|
416
|
+
* the caller-supplied ceiling.
|
|
417
|
+
*/
|
|
418
|
+
const IMPORTANCE_RANK = {
|
|
419
|
+
low: 0,
|
|
420
|
+
normal: 1,
|
|
421
|
+
high: 2,
|
|
422
|
+
critical: 3
|
|
423
|
+
};
|
|
166
424
|
function assertNonEmptyString(value, field) {
|
|
167
425
|
if (value.trim().length === 0) throw new MnemocyteError(`${field} must be a non-empty string.`, "VALIDATION");
|
|
168
426
|
}
|
|
@@ -219,11 +477,15 @@ function rowToMemory(row) {
|
|
|
219
477
|
updatedAt: row.updatedAt
|
|
220
478
|
};
|
|
221
479
|
}
|
|
222
|
-
async function embedOne(embedder, text) {
|
|
480
|
+
async function embedOne(embedder, text, options = {}) {
|
|
223
481
|
let embeddings;
|
|
224
482
|
try {
|
|
225
|
-
embeddings = await embedder.embed([text])
|
|
483
|
+
embeddings = await withResilience((signal) => signal === void 0 ? embedder.embed([text]) : embedder.embed([text], { signal }), {
|
|
484
|
+
...options.signal === void 0 ? {} : { signal: options.signal },
|
|
485
|
+
...options.resilience === void 0 ? {} : { resilience: options.resilience }
|
|
486
|
+
});
|
|
226
487
|
} catch (error) {
|
|
488
|
+
if (error instanceof MnemocyteError && (error.code === "TIMEOUT" || error.code === "ABORTED")) throw error;
|
|
227
489
|
throw new MnemocyteError("Failed to embed text.", "EMBEDDING", error);
|
|
228
490
|
}
|
|
229
491
|
const embedding = embeddings[0];
|
|
@@ -254,6 +516,36 @@ function contextInputToRecallInput(input) {
|
|
|
254
516
|
...input.includeExpired === void 0 ? {} : { includeExpired: input.includeExpired }
|
|
255
517
|
};
|
|
256
518
|
}
|
|
519
|
+
/**
|
|
520
|
+
* Validate a {@link PruneInput} and throw a `"VALIDATION"`
|
|
521
|
+
* {@link MnemocyteError} when no selector is supplied. Mnemocyte
|
|
522
|
+
* deliberately rejects `prune({})` to avoid surprise full-table deletes.
|
|
523
|
+
*/
|
|
524
|
+
function validatePruneInput(input) {
|
|
525
|
+
if (input.entityId !== void 0) assertNonEmptyString(input.entityId, "entityId");
|
|
526
|
+
if (!(input.entityId !== void 0 || input.expired === true || input.superseded === true || input.createdBefore !== void 0 || input.notAccessedSince !== void 0 || input.types !== void 0 && input.types.length > 0 || input.tags !== void 0 && input.tags.length > 0 || input.maxImportance !== void 0)) throw new MnemocyteError("prune requires at least one selector (entityId, expired, superseded, createdBefore, notAccessedSince, types, tags, or maxImportance).", "VALIDATION");
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Test whether `memory` matches a {@link PruneInput}. All specified
|
|
530
|
+
* selectors are AND-combined; unspecified selectors do not restrict.
|
|
531
|
+
*
|
|
532
|
+
* `now` is supplied by the caller so callers can share a consistent
|
|
533
|
+
* timestamp across a batch.
|
|
534
|
+
*/
|
|
535
|
+
function matchesPruneFilter(memory, input, now) {
|
|
536
|
+
if (input.entityId !== void 0 && memory.entityId !== input.entityId) return false;
|
|
537
|
+
if (input.expired === true && !isExpired(memory, now)) return false;
|
|
538
|
+
if (input.superseded === true && memory.supersededBy === null) return false;
|
|
539
|
+
if (input.createdBefore !== void 0 && memory.createdAt.getTime() >= input.createdBefore.getTime()) return false;
|
|
540
|
+
if (input.notAccessedSince !== void 0) {
|
|
541
|
+
const last = memory.lastAccessedAt;
|
|
542
|
+
if (last !== null && last.getTime() >= input.notAccessedSince.getTime()) return false;
|
|
543
|
+
}
|
|
544
|
+
if (input.types !== void 0 && !input.types.includes(memory.type)) return false;
|
|
545
|
+
if (input.tags !== void 0 && input.tags.length > 0 && !input.tags.every((tag) => memory.tags.includes(tag))) return false;
|
|
546
|
+
if (input.maxImportance !== void 0 && IMPORTANCE_RANK[memory.importance] > IMPORTANCE_RANK[input.maxImportance]) return false;
|
|
547
|
+
return true;
|
|
548
|
+
}
|
|
257
549
|
function matchesRecallFilter(memory, input, now) {
|
|
258
550
|
if (memory.entityId !== input.entityId) return false;
|
|
259
551
|
if (!input.includeSuperseded && memory.supersededBy !== null) return false;
|
|
@@ -371,107 +663,162 @@ function createInMemoryClient(config) {
|
|
|
371
663
|
if (closed) throw new MnemocyteError("Mnemocyte client is closed.", "DB");
|
|
372
664
|
}
|
|
373
665
|
async function remember(input) {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
666
|
+
return observe(config, "in-memory", "remember", { entityId: input.entityId }, async () => {
|
|
667
|
+
assertOpen();
|
|
668
|
+
validateRememberInput(input);
|
|
669
|
+
const embedding = await embedOne(config.embedder, input.content, {
|
|
670
|
+
...input.signal === void 0 ? {} : { signal: input.signal },
|
|
671
|
+
...config.provider === void 0 ? {} : { resilience: config.provider }
|
|
672
|
+
});
|
|
673
|
+
const now = /* @__PURE__ */ new Date();
|
|
674
|
+
const memory = {
|
|
675
|
+
id: createId(),
|
|
676
|
+
entityId: input.entityId,
|
|
677
|
+
content: input.content,
|
|
678
|
+
type: input.type ?? "fact",
|
|
679
|
+
importance: input.importance ?? "normal",
|
|
680
|
+
tags: normalizeTags(input.tags),
|
|
681
|
+
source: input.source ?? null,
|
|
682
|
+
metadata: input.metadata ?? {},
|
|
683
|
+
confidence: input.confidence ?? 1,
|
|
684
|
+
embeddingModel: config.embedder.model,
|
|
685
|
+
embeddingDimensions: config.embedder.dimensions,
|
|
686
|
+
supersededBy: null,
|
|
687
|
+
expiresAt: input.expiresAt ?? null,
|
|
688
|
+
lastAccessedAt: null,
|
|
689
|
+
accessCount: 0,
|
|
690
|
+
createdAt: now,
|
|
691
|
+
updatedAt: now,
|
|
692
|
+
embedding
|
|
693
|
+
};
|
|
694
|
+
memories.set(memory.id, memory);
|
|
695
|
+
return cloneMemory(memory);
|
|
696
|
+
}, (memory) => ({
|
|
697
|
+
memoryId: memory.id,
|
|
698
|
+
count: 1
|
|
699
|
+
}));
|
|
400
700
|
}
|
|
401
701
|
return {
|
|
402
702
|
remember,
|
|
403
703
|
async rememberMany(inputs) {
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
704
|
+
return observe(config, "in-memory", "rememberMany", { count: inputs.length }, async () => {
|
|
705
|
+
assertOpen();
|
|
706
|
+
const result = [];
|
|
707
|
+
for (const input of inputs) result.push(await remember(input));
|
|
708
|
+
return result;
|
|
709
|
+
}, (result) => ({ count: result.length }));
|
|
408
710
|
},
|
|
409
711
|
async recall(input) {
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
stored
|
|
712
|
+
return observe(config, "in-memory", "recall", { entityId: input.entityId }, async () => {
|
|
713
|
+
assertOpen();
|
|
714
|
+
validateRecallInput(input);
|
|
715
|
+
const limit = input.limit ?? config.defaults?.limit ?? 10;
|
|
716
|
+
const minScore = input.minScore ?? config.defaults?.minScore ?? 0;
|
|
717
|
+
assertLimit(limit);
|
|
718
|
+
assertMinScore(minScore);
|
|
719
|
+
const queryEmbedding = await embedOne(config.embedder, input.query, {
|
|
720
|
+
...input.signal === void 0 ? {} : { signal: input.signal },
|
|
721
|
+
...config.provider === void 0 ? {} : { resilience: config.provider }
|
|
722
|
+
});
|
|
723
|
+
const now = /* @__PURE__ */ new Date();
|
|
724
|
+
const scored = Array.from(memories.values()).filter((memory) => matchesRecallFilter(memory, input, now)).map((memory) => toScoredMemory(memory, Math.max(0, cosineSimilarity(memory.embedding, queryEmbedding)), lexicalScore(memory.content, input.query), input, config.retrieval)).filter((memory) => memory.score >= minScore).sort((a, b) => b.score - a.score).slice(0, limit);
|
|
725
|
+
for (const memory of scored) {
|
|
726
|
+
const stored = memories.get(memory.id);
|
|
727
|
+
if (stored) {
|
|
728
|
+
stored.lastAccessedAt = now;
|
|
729
|
+
stored.accessCount += 1;
|
|
730
|
+
stored.updatedAt = now;
|
|
731
|
+
}
|
|
425
732
|
}
|
|
426
|
-
|
|
427
|
-
|
|
733
|
+
return scored;
|
|
734
|
+
}, (result) => ({ count: result.length }));
|
|
428
735
|
},
|
|
429
736
|
async buildContext(input) {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
737
|
+
return observe(config, "in-memory", "buildContext", { entityId: input.entityId }, async () => {
|
|
738
|
+
assertOpen();
|
|
739
|
+
return buildContext({
|
|
740
|
+
input,
|
|
741
|
+
recall: (contextInput) => {
|
|
742
|
+
const recallInput = contextInputToRecallInput(contextInput);
|
|
743
|
+
return this.recall(input.signal === void 0 ? recallInput : {
|
|
744
|
+
...recallInput,
|
|
745
|
+
signal: input.signal
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
});
|
|
434
749
|
});
|
|
435
750
|
},
|
|
436
751
|
async forget(input) {
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
752
|
+
return observe(config, "in-memory", "forget", {
|
|
753
|
+
entityId: input.entityId,
|
|
754
|
+
memoryId: input.memoryId
|
|
755
|
+
}, async () => {
|
|
756
|
+
assertOpen();
|
|
757
|
+
assertNonEmptyString(input.entityId, "entityId");
|
|
758
|
+
assertNonEmptyString(input.memoryId, "memoryId");
|
|
759
|
+
const memory = memories.get(input.memoryId);
|
|
760
|
+
if (!memory || memory.entityId !== input.entityId) throw new MnemocyteError("Memory was not found.", "NOT_FOUND");
|
|
761
|
+
memories.delete(input.memoryId);
|
|
762
|
+
});
|
|
443
763
|
},
|
|
444
764
|
async forgetAll(input) {
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
765
|
+
return observe(config, "in-memory", "forgetAll", { entityId: input.entityId }, async () => {
|
|
766
|
+
assertOpen();
|
|
767
|
+
assertNonEmptyString(input.entityId, "entityId");
|
|
768
|
+
let deleted = 0;
|
|
769
|
+
for (const [id, memory] of memories) if (memory.entityId === input.entityId) {
|
|
770
|
+
memories.delete(id);
|
|
771
|
+
deleted += 1;
|
|
772
|
+
}
|
|
773
|
+
return deleted;
|
|
774
|
+
}, (count) => ({ count })).then(() => void 0);
|
|
775
|
+
},
|
|
776
|
+
async prune(input) {
|
|
777
|
+
return observe(config, "in-memory", "prune", input.entityId === void 0 ? {} : { entityId: input.entityId }, async () => {
|
|
778
|
+
assertOpen();
|
|
779
|
+
validatePruneInput(input);
|
|
780
|
+
const now = /* @__PURE__ */ new Date();
|
|
781
|
+
const matched = [];
|
|
782
|
+
for (const memory of memories.values()) if (matchesPruneFilter(memory, input, now)) matched.push(memory.id);
|
|
783
|
+
const dryRun = input.dryRun === true;
|
|
784
|
+
if (!dryRun) for (const id of matched) memories.delete(id);
|
|
785
|
+
return {
|
|
786
|
+
matchedCount: matched.length,
|
|
787
|
+
deletedCount: dryRun ? 0 : matched.length,
|
|
788
|
+
dryRun
|
|
789
|
+
};
|
|
790
|
+
}, (result) => ({ count: result.deletedCount }));
|
|
448
791
|
},
|
|
449
792
|
async stats(input) {
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
793
|
+
return observe(config, "in-memory", "stats", input?.entityId ? { entityId: input.entityId } : {}, async () => {
|
|
794
|
+
assertOpen();
|
|
795
|
+
const now = /* @__PURE__ */ new Date();
|
|
796
|
+
const allMemories = Array.from(memories.values());
|
|
797
|
+
const selected = input?.entityId ? allMemories.filter((memory) => memory.entityId === input.entityId) : allMemories;
|
|
798
|
+
const activeMemoryCount = selected.filter((memory) => !isExpired(memory, now) && memory.supersededBy === null).length;
|
|
799
|
+
const expiredMemoryCount = selected.filter((memory) => isExpired(memory, now)).length;
|
|
800
|
+
const supersededMemoryCount = selected.filter((memory) => memory.supersededBy !== null).length;
|
|
801
|
+
if (input?.entityId) return {
|
|
802
|
+
entityId: input.entityId,
|
|
803
|
+
memoryCount: selected.length,
|
|
804
|
+
activeMemoryCount,
|
|
805
|
+
expiredMemoryCount,
|
|
806
|
+
supersededMemoryCount
|
|
807
|
+
};
|
|
808
|
+
return {
|
|
809
|
+
entityCount: new Set(allMemories.map((memory) => memory.entityId)).size,
|
|
810
|
+
memoryCount: selected.length,
|
|
811
|
+
activeMemoryCount,
|
|
812
|
+
expiredMemoryCount,
|
|
813
|
+
supersededMemoryCount
|
|
814
|
+
};
|
|
815
|
+
});
|
|
471
816
|
},
|
|
472
817
|
async close() {
|
|
473
|
-
|
|
474
|
-
|
|
818
|
+
return observe(config, "in-memory", "close", {}, async () => {
|
|
819
|
+
closed = true;
|
|
820
|
+
memories.clear();
|
|
821
|
+
});
|
|
475
822
|
}
|
|
476
823
|
};
|
|
477
824
|
}
|
|
@@ -502,6 +849,15 @@ async function deleteMemory(db, entityId, memoryId) {
|
|
|
502
849
|
async function deleteMemoriesForEntity(db, entityId) {
|
|
503
850
|
return (await db.delete(memoriesTable).where(eq(memoriesTable.entityId, entityId)).returning({ id: memoriesTable.id })).length;
|
|
504
851
|
}
|
|
852
|
+
function pruneConditions(filter) {
|
|
853
|
+
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, filter.tags && filter.tags.length > 0 ? sql`${memoriesTable.tags} @> ${filter.tags}` : void 0, filter.maxImportanceLevels && filter.maxImportanceLevels.length > 0 ? inArray(memoriesTable.importance, [...filter.maxImportanceLevels]) : void 0);
|
|
854
|
+
}
|
|
855
|
+
async function countPruneMatches(db, filter) {
|
|
856
|
+
return (await db.select({ count: sql`count(*)::int` }).from(memoriesTable).where(pruneConditions(filter)))[0]?.count ?? 0;
|
|
857
|
+
}
|
|
858
|
+
async function pruneMemories(db, filter) {
|
|
859
|
+
return (await db.delete(memoriesTable).where(pruneConditions(filter)).returning({ id: memoriesTable.id })).length;
|
|
860
|
+
}
|
|
505
861
|
async function markMemoryAccessed(db, memoryIds) {
|
|
506
862
|
if (memoryIds.length === 0) return;
|
|
507
863
|
await db.update(memoriesTable).set({
|
|
@@ -590,7 +946,10 @@ async function lexicalSearch(db, input) {
|
|
|
590
946
|
//#endregion
|
|
591
947
|
//#region src/retrieval/index.ts
|
|
592
948
|
async function hybridRecall(input) {
|
|
593
|
-
const queryEmbedding = await embedOne(input.embedder, input.input.query
|
|
949
|
+
const queryEmbedding = await embedOne(input.embedder, input.input.query, {
|
|
950
|
+
...input.signal === void 0 ? {} : { signal: input.signal },
|
|
951
|
+
...input.resilience === void 0 ? {} : { resilience: input.resilience }
|
|
952
|
+
});
|
|
594
953
|
const candidateMultiplier = input.retrieval?.candidateMultiplier ?? 3;
|
|
595
954
|
const candidateLimit = Math.max(input.limit, input.limit * candidateMultiplier);
|
|
596
955
|
const [vectorRows, lexicalRows] = await Promise.all([vectorSearch(input.db, {
|
|
@@ -623,114 +982,211 @@ async function hybridRecall(input) {
|
|
|
623
982
|
}
|
|
624
983
|
//#endregion
|
|
625
984
|
//#region src/memory/postgres.ts
|
|
985
|
+
const IMPORTANCE_LEVELS = [
|
|
986
|
+
"low",
|
|
987
|
+
"normal",
|
|
988
|
+
"high",
|
|
989
|
+
"critical"
|
|
990
|
+
];
|
|
991
|
+
function importanceCeilingLevels(max) {
|
|
992
|
+
return IMPORTANCE_LEVELS.filter((level) => IMPORTANCE_RANK[level] <= IMPORTANCE_RANK[max]);
|
|
993
|
+
}
|
|
994
|
+
function toPruneFilter(input) {
|
|
995
|
+
const filter = {};
|
|
996
|
+
if (input.entityId !== void 0) filter.entityId = input.entityId;
|
|
997
|
+
if (input.expired === true) filter.expired = true;
|
|
998
|
+
if (input.superseded === true) filter.superseded = true;
|
|
999
|
+
if (input.createdBefore !== void 0) filter.createdBefore = input.createdBefore;
|
|
1000
|
+
if (input.notAccessedSince !== void 0) filter.notAccessedSince = input.notAccessedSince;
|
|
1001
|
+
if (input.types !== void 0 && input.types.length > 0) filter.types = input.types;
|
|
1002
|
+
if (input.tags !== void 0 && input.tags.length > 0) filter.tags = input.tags;
|
|
1003
|
+
if (input.maxImportance !== void 0) filter.maxImportanceLevels = importanceCeilingLevels(input.maxImportance);
|
|
1004
|
+
return filter;
|
|
1005
|
+
}
|
|
626
1006
|
function createPostgresClient(config, handle) {
|
|
627
1007
|
let closed = false;
|
|
628
1008
|
function assertOpen() {
|
|
629
1009
|
if (closed) throw new MnemocyteError("Mnemocyte client is closed.", "DB");
|
|
630
1010
|
}
|
|
631
1011
|
async function remember(input) {
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
1012
|
+
return observe(config, "postgres", "remember", { entityId: input.entityId }, async () => {
|
|
1013
|
+
assertOpen();
|
|
1014
|
+
validateRememberInput(input);
|
|
1015
|
+
const embedding = await embedOne(config.embedder, input.content, {
|
|
1016
|
+
...input.signal === void 0 ? {} : { signal: input.signal },
|
|
1017
|
+
...config.provider === void 0 ? {} : { resilience: config.provider }
|
|
1018
|
+
});
|
|
1019
|
+
const now = /* @__PURE__ */ new Date();
|
|
1020
|
+
return rowToMemory(await insertMemory(handle.db, {
|
|
1021
|
+
id: createId(),
|
|
1022
|
+
entityId: input.entityId,
|
|
1023
|
+
content: input.content,
|
|
1024
|
+
type: input.type ?? "fact",
|
|
1025
|
+
importance: input.importance ?? "normal",
|
|
1026
|
+
tags: normalizeTags(input.tags),
|
|
1027
|
+
source: input.source ?? null,
|
|
1028
|
+
metadata: input.metadata ?? {},
|
|
1029
|
+
confidence: input.confidence ?? 1,
|
|
1030
|
+
embedding,
|
|
1031
|
+
embeddingModel: config.embedder.model,
|
|
1032
|
+
embeddingDimensions: config.embedder.dimensions,
|
|
1033
|
+
supersededBy: null,
|
|
1034
|
+
supersededAt: null,
|
|
1035
|
+
expiresAt: input.expiresAt ?? null,
|
|
1036
|
+
lastAccessedAt: null,
|
|
1037
|
+
accessCount: 0,
|
|
1038
|
+
createdAt: now,
|
|
1039
|
+
updatedAt: now
|
|
1040
|
+
}));
|
|
1041
|
+
}, (memory) => ({
|
|
1042
|
+
memoryId: memory.id,
|
|
1043
|
+
count: 1
|
|
656
1044
|
}));
|
|
657
1045
|
}
|
|
658
1046
|
return {
|
|
659
1047
|
remember,
|
|
660
1048
|
async rememberMany(inputs) {
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
1049
|
+
return observe(config, "postgres", "rememberMany", { count: inputs.length }, async () => {
|
|
1050
|
+
assertOpen();
|
|
1051
|
+
const result = [];
|
|
1052
|
+
for (const input of inputs) result.push(await remember(input));
|
|
1053
|
+
return result;
|
|
1054
|
+
}, (result) => ({ count: result.length }));
|
|
665
1055
|
},
|
|
666
1056
|
async recall(input) {
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
1057
|
+
return observe(config, "postgres", "recall", { entityId: input.entityId }, async () => {
|
|
1058
|
+
assertOpen();
|
|
1059
|
+
validateRecallInput(input);
|
|
1060
|
+
const limit = input.limit ?? config.defaults?.limit ?? 10;
|
|
1061
|
+
const minScore = input.minScore ?? config.defaults?.minScore ?? 0;
|
|
1062
|
+
assertLimit(limit);
|
|
1063
|
+
assertMinScore(minScore);
|
|
1064
|
+
return hybridRecall({
|
|
1065
|
+
db: handle.db,
|
|
1066
|
+
embedder: config.embedder,
|
|
1067
|
+
input,
|
|
1068
|
+
limit,
|
|
1069
|
+
minScore,
|
|
1070
|
+
retrieval: config.retrieval,
|
|
1071
|
+
...input.signal === void 0 ? {} : { signal: input.signal },
|
|
1072
|
+
...config.provider === void 0 ? {} : { resilience: config.provider }
|
|
1073
|
+
});
|
|
1074
|
+
}, (result) => ({ count: result.length }));
|
|
681
1075
|
},
|
|
682
1076
|
async buildContext(input) {
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
1077
|
+
return observe(config, "postgres", "buildContext", { entityId: input.entityId }, async () => {
|
|
1078
|
+
assertOpen();
|
|
1079
|
+
return buildContext({
|
|
1080
|
+
input,
|
|
1081
|
+
recall: (contextInput) => {
|
|
1082
|
+
const recallInput = contextInputToRecallInput(contextInput);
|
|
1083
|
+
return this.recall(input.signal === void 0 ? recallInput : {
|
|
1084
|
+
...recallInput,
|
|
1085
|
+
signal: input.signal
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1088
|
+
});
|
|
687
1089
|
});
|
|
688
1090
|
},
|
|
689
1091
|
async forget(input) {
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
1092
|
+
return observe(config, "postgres", "forget", {
|
|
1093
|
+
entityId: input.entityId,
|
|
1094
|
+
memoryId: input.memoryId
|
|
1095
|
+
}, async () => {
|
|
1096
|
+
assertOpen();
|
|
1097
|
+
assertNonEmptyString(input.entityId, "entityId");
|
|
1098
|
+
assertNonEmptyString(input.memoryId, "memoryId");
|
|
1099
|
+
if (!await deleteMemory(handle.db, input.entityId, input.memoryId)) throw new MnemocyteError("Memory was not found.", "NOT_FOUND");
|
|
1100
|
+
});
|
|
694
1101
|
},
|
|
695
1102
|
async forgetAll(input) {
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
1103
|
+
return observe(config, "postgres", "forgetAll", { entityId: input.entityId }, async () => {
|
|
1104
|
+
assertOpen();
|
|
1105
|
+
assertNonEmptyString(input.entityId, "entityId");
|
|
1106
|
+
await Promise.all([deleteMemoriesForEntity(handle.db, input.entityId), deleteEventsForEntity(handle.db, input.entityId)]);
|
|
1107
|
+
});
|
|
1108
|
+
},
|
|
1109
|
+
async prune(input) {
|
|
1110
|
+
return observe(config, "postgres", "prune", input.entityId === void 0 ? {} : { entityId: input.entityId }, async () => {
|
|
1111
|
+
assertOpen();
|
|
1112
|
+
validatePruneInput(input);
|
|
1113
|
+
const filter = toPruneFilter(input);
|
|
1114
|
+
if (input.dryRun === true) return {
|
|
1115
|
+
matchedCount: await countPruneMatches(handle.db, filter),
|
|
1116
|
+
deletedCount: 0,
|
|
1117
|
+
dryRun: true
|
|
1118
|
+
};
|
|
1119
|
+
const deletedCount = await pruneMemories(handle.db, filter);
|
|
1120
|
+
return {
|
|
1121
|
+
matchedCount: deletedCount,
|
|
1122
|
+
deletedCount,
|
|
1123
|
+
dryRun: false
|
|
1124
|
+
};
|
|
1125
|
+
}, (result) => ({ count: result.deletedCount }));
|
|
699
1126
|
},
|
|
700
1127
|
async stats(input) {
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
1128
|
+
return observe(config, "postgres", "stats", input?.entityId ? { entityId: input.entityId } : {}, async () => {
|
|
1129
|
+
assertOpen();
|
|
1130
|
+
const now = /* @__PURE__ */ new Date();
|
|
1131
|
+
if (input?.entityId) {
|
|
1132
|
+
const selected = await listMemories(handle.db, {
|
|
1133
|
+
entityId: input.entityId,
|
|
1134
|
+
includeExpired: true,
|
|
1135
|
+
includeSuperseded: true
|
|
1136
|
+
});
|
|
1137
|
+
return {
|
|
1138
|
+
entityId: input.entityId,
|
|
1139
|
+
memoryCount: selected.length,
|
|
1140
|
+
activeMemoryCount: selected.filter((memory) => !isExpired(rowToMemory(memory), now) && memory.supersededBy === null).length,
|
|
1141
|
+
expiredMemoryCount: selected.filter((memory) => isExpired(rowToMemory(memory), now)).length,
|
|
1142
|
+
supersededMemoryCount: selected.filter((memory) => memory.supersededBy !== null).length
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
const allMemories = await handle.db.select().from(memoriesTable);
|
|
709
1146
|
return {
|
|
710
|
-
|
|
711
|
-
memoryCount:
|
|
712
|
-
activeMemoryCount:
|
|
713
|
-
expiredMemoryCount:
|
|
714
|
-
supersededMemoryCount:
|
|
1147
|
+
entityCount: new Set(allMemories.map((memory) => memory.entityId)).size,
|
|
1148
|
+
memoryCount: allMemories.length,
|
|
1149
|
+
activeMemoryCount: allMemories.filter((memory) => !isExpired(rowToMemory(memory), now) && memory.supersededBy === null).length,
|
|
1150
|
+
expiredMemoryCount: allMemories.filter((memory) => isExpired(rowToMemory(memory), now)).length,
|
|
1151
|
+
supersededMemoryCount: allMemories.filter((memory) => memory.supersededBy !== null).length
|
|
715
1152
|
};
|
|
716
|
-
}
|
|
717
|
-
const allMemories = await handle.db.select().from(memoriesTable);
|
|
718
|
-
return {
|
|
719
|
-
entityCount: new Set(allMemories.map((memory) => memory.entityId)).size,
|
|
720
|
-
memoryCount: allMemories.length,
|
|
721
|
-
activeMemoryCount: allMemories.filter((memory) => !isExpired(rowToMemory(memory), now) && memory.supersededBy === null).length,
|
|
722
|
-
expiredMemoryCount: allMemories.filter((memory) => isExpired(rowToMemory(memory), now)).length,
|
|
723
|
-
supersededMemoryCount: allMemories.filter((memory) => memory.supersededBy !== null).length
|
|
724
|
-
};
|
|
1153
|
+
});
|
|
725
1154
|
},
|
|
726
1155
|
async close() {
|
|
727
|
-
|
|
728
|
-
|
|
1156
|
+
return observe(config, "postgres", "close", {}, async () => {
|
|
1157
|
+
closed = true;
|
|
1158
|
+
await handle.close();
|
|
1159
|
+
});
|
|
729
1160
|
}
|
|
730
1161
|
};
|
|
731
1162
|
}
|
|
732
1163
|
//#endregion
|
|
733
1164
|
//#region src/client.ts
|
|
1165
|
+
/**
|
|
1166
|
+
* Create a Mnemocyte client.
|
|
1167
|
+
*
|
|
1168
|
+
* When {@link MnemocyteConfig.databaseUrl} is provided, a Postgres-backed
|
|
1169
|
+
* client is returned (requires the `pgvector` extension and the bundled
|
|
1170
|
+
* migration applied). Otherwise an in-memory client is returned, intended
|
|
1171
|
+
* for tests, demos, and short-lived processes.
|
|
1172
|
+
*
|
|
1173
|
+
* The returned client owns any underlying resources (e.g. the Postgres
|
|
1174
|
+
* connection pool). Call {@link MnemocyteClient.close} when finished to
|
|
1175
|
+
* release them.
|
|
1176
|
+
*
|
|
1177
|
+
* @param config - Client configuration. {@link MnemocyteConfig.embedder} is required.
|
|
1178
|
+
* @returns A {@link MnemocyteClient} instance.
|
|
1179
|
+
* @throws {MnemocyteError} With code `"CONFIG"` if `embedder` is missing or invalid,
|
|
1180
|
+
* or `"VALIDATION"` if `databaseUrl` is provided but empty.
|
|
1181
|
+
*
|
|
1182
|
+
* @example Postgres-backed client
|
|
1183
|
+
* ```ts
|
|
1184
|
+
* const client = createMnemocyte({
|
|
1185
|
+
* databaseUrl: process.env.DATABASE_URL,
|
|
1186
|
+
* embedder,
|
|
1187
|
+
* });
|
|
1188
|
+
* ```
|
|
1189
|
+
*/
|
|
734
1190
|
function createMnemocyte(config) {
|
|
735
1191
|
assertEmbedder(config.embedder);
|
|
736
1192
|
if (config.databaseUrl) {
|
|
@@ -740,6 +1196,6 @@ function createMnemocyte(config) {
|
|
|
740
1196
|
return createInMemoryClient(config);
|
|
741
1197
|
}
|
|
742
1198
|
//#endregion
|
|
743
|
-
export { MnemocyteError, createMnemocyte };
|
|
1199
|
+
export { MnemocyteError, createMnemocyte, isMnemocyteError };
|
|
744
1200
|
|
|
745
1201
|
//# sourceMappingURL=index.mjs.map
|