@j0hanz/memdb 1.2.9 → 1.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.
Files changed (62) hide show
  1. package/README.md +663 -382
  2. package/dist/assets/logo.svg +12 -0
  3. package/dist/async-context.d.ts +6 -0
  4. package/dist/async-context.js +4 -0
  5. package/dist/config.d.ts +0 -1
  6. package/dist/config.js +10 -5
  7. package/dist/core/abort.d.ts +1 -0
  8. package/dist/core/abort.js +3 -0
  9. package/dist/core/db.d.ts +9 -5
  10. package/dist/core/db.js +205 -129
  11. package/dist/core/memory-read.d.ts +1 -2
  12. package/dist/core/memory-read.js +18 -21
  13. package/dist/core/memory-write.d.ts +1 -2
  14. package/dist/core/memory-write.js +69 -85
  15. package/dist/core/relationships.d.ts +0 -1
  16. package/dist/core/relationships.js +56 -59
  17. package/dist/core/search.d.ts +0 -1
  18. package/dist/core/search.js +141 -85
  19. package/dist/error-utils.d.ts +1 -0
  20. package/dist/error-utils.js +28 -0
  21. package/dist/index.d.ts +0 -1
  22. package/dist/index.js +63 -16
  23. package/dist/instructions.md +38 -34
  24. package/dist/logger.d.ts +0 -1
  25. package/dist/logger.js +6 -3
  26. package/dist/protocol-version-guard.d.ts +0 -1
  27. package/dist/protocol-version-guard.js +17 -2
  28. package/dist/schemas.d.ts +0 -1
  29. package/dist/schemas.js +12 -9
  30. package/dist/stdio-transport.d.ts +0 -1
  31. package/dist/stdio-transport.js +6 -16
  32. package/dist/tools.d.ts +1 -2
  33. package/dist/tools.js +222 -222
  34. package/dist/types.d.ts +0 -1
  35. package/dist/types.js +0 -1
  36. package/package.json +20 -18
  37. package/dist/config.d.ts.map +0 -1
  38. package/dist/config.js.map +0 -1
  39. package/dist/core/db.d.ts.map +0 -1
  40. package/dist/core/db.js.map +0 -1
  41. package/dist/core/memory-read.d.ts.map +0 -1
  42. package/dist/core/memory-read.js.map +0 -1
  43. package/dist/core/memory-write.d.ts.map +0 -1
  44. package/dist/core/memory-write.js.map +0 -1
  45. package/dist/core/relationships.d.ts.map +0 -1
  46. package/dist/core/relationships.js.map +0 -1
  47. package/dist/core/search.d.ts.map +0 -1
  48. package/dist/core/search.js.map +0 -1
  49. package/dist/index.d.ts.map +0 -1
  50. package/dist/index.js.map +0 -1
  51. package/dist/logger.d.ts.map +0 -1
  52. package/dist/logger.js.map +0 -1
  53. package/dist/protocol-version-guard.d.ts.map +0 -1
  54. package/dist/protocol-version-guard.js.map +0 -1
  55. package/dist/schemas.d.ts.map +0 -1
  56. package/dist/schemas.js.map +0 -1
  57. package/dist/stdio-transport.d.ts.map +0 -1
  58. package/dist/stdio-transport.js.map +0 -1
  59. package/dist/tools.d.ts.map +0 -1
  60. package/dist/tools.js.map +0 -1
  61. package/dist/types.d.ts.map +0 -1
  62. package/dist/types.js.map +0 -1
@@ -0,0 +1,12 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF"
2
+ stroke-width="1" stroke-linecap="round" stroke-linejoin="round">
3
+ <path d="M3 7c0-1.7 2.7-3 6-3s6 1.3 6 3-2.7 3-6 3-6-1.3-6-3Z" />
4
+ <path d="M3 7v10c0 1.7 2.7 3 6 3s6-1.3 6-3V7" />
5
+ <path d="M3 12c0 1.7 2.7 3 6 3s6-1.3 6-3" />
6
+ <line x1="15" y1="12" x2="16" y2="12" />
7
+ <rect x="16" y="9" width="6" height="6" rx="1" />
8
+ <line x1="17.5" y1="9" x2="17.5" y2="8" />
9
+ <line x1="20.5" y1="9" x2="20.5" y2="8" />
10
+ <line x1="17.5" y1="15" x2="17.5" y2="16" />
11
+ <line x1="20.5" y1="15" x2="20.5" y2="16" />
12
+ </svg>
@@ -0,0 +1,6 @@
1
+ export interface ToolContextStore {
2
+ toolName: string;
3
+ startTime: number;
4
+ }
5
+ export declare const runWithToolContext: <T>(store: ToolContextStore, fn: () => T) => T;
6
+ export declare const getToolContext: () => ToolContextStore | undefined;
@@ -0,0 +1,4 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ const toolContextStorage = new AsyncLocalStorage();
3
+ export const runWithToolContext = (store, fn) => toolContextStorage.run(store, fn);
4
+ export const getToolContext = () => toolContextStorage.getStore();
package/dist/config.d.ts CHANGED
@@ -5,4 +5,3 @@ export declare const config: {
5
5
  toolTimeoutMs: number;
6
6
  };
7
7
  export {};
8
- //# sourceMappingURL=config.d.ts.map
package/dist/config.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import path from 'node:path';
2
2
  import process from 'node:process';
3
- const DEFAULT_DB_PATH = path.join(process.cwd(), '.memdb', 'memory.db');
3
+ const DEFAULT_DB_PATH = path.resolve('.memdb', 'memory.db');
4
4
  const DEFAULT_LOG_LEVEL = 'info';
5
5
  const DEFAULT_TOOL_TIMEOUT_MS = 15000;
6
6
  const hasNullByte = (value) => value.includes('\0');
@@ -37,10 +37,16 @@ const resolveDbPath = (env) => {
37
37
  if (hasNullByte(envPath)) {
38
38
  throw new Error('Invalid MEMDB_PATH: null byte detected');
39
39
  }
40
- if (envPath === ':memory:')
40
+ const normalizedEnvPath = envPath.trim();
41
+ if (normalizedEnvPath === ':memory:')
41
42
  return ':memory:';
42
- if (envPath.length > 0)
43
- return path.resolve(envPath);
43
+ if (normalizedEnvPath.length > 0) {
44
+ if (process.platform !== 'win32' &&
45
+ path.win32.isAbsolute(normalizedEnvPath)) {
46
+ return path.win32.normalize(normalizedEnvPath);
47
+ }
48
+ return path.resolve(normalizedEnvPath);
49
+ }
44
50
  }
45
51
  return path.resolve(DEFAULT_DB_PATH);
46
52
  };
@@ -49,4 +55,3 @@ export const config = {
49
55
  logLevel: parseLogLevel(process.env.MEMDB_LOG_LEVEL) ?? DEFAULT_LOG_LEVEL,
50
56
  toolTimeoutMs: parseTimeoutMs(process.env.MEMDB_TOOL_TIMEOUT_MS, 'MEMDB_TOOL_TIMEOUT_MS') ?? DEFAULT_TOOL_TIMEOUT_MS,
51
57
  };
52
- //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ export declare const throwIfAborted: (signal?: AbortSignal) => void;
@@ -0,0 +1,3 @@
1
+ export const throwIfAborted = (signal) => {
2
+ signal?.throwIfAborted();
3
+ };
package/dist/core/db.d.ts CHANGED
@@ -1,20 +1,24 @@
1
1
  import { DatabaseSync, type StatementSync } from 'node:sqlite';
2
2
  import { type Memory, type Relationship, type SearchResult } from '../types.js';
3
3
  export type DbRow = Record<string, unknown>;
4
+ type SqlParam = string | number | bigint | null | Uint8Array;
4
5
  export declare const initDb: () => Promise<void>;
5
6
  export declare const getDb: () => DatabaseSync;
6
7
  export declare const closeDb: () => void;
7
- export type SqlParam = string | number | bigint | null | Uint8Array;
8
8
  export declare const prepareCached: (sql: string) => StatementSync;
9
- export declare const executeAll: <T = DbRow>(stmt: StatementSync, ...params: SqlParam[]) => T[];
10
- export declare const executeGet: <T = DbRow>(stmt: StatementSync, ...params: SqlParam[]) => T | undefined;
11
- export declare const executeRun: (stmt: StatementSync, ...params: SqlParam[]) => {
9
+ export declare const executeAll: (stmt: StatementSync, ...params: SqlParam[]) => DbRow[];
10
+ export declare const sqlAll: (strings: TemplateStringsArray, ...params: SqlParam[]) => DbRow[];
11
+ export declare const sqlGet: (strings: TemplateStringsArray, ...params: SqlParam[]) => DbRow | undefined;
12
+ export declare const sqlRun: (strings: TemplateStringsArray, ...params: SqlParam[]) => {
12
13
  changes: number | bigint;
13
14
  };
14
15
  export declare const withImmediateTransaction: <T>(operation: () => T) => T;
16
+ export declare const withSavepoint: <T>(name: string, operation: () => T) => T;
15
17
  export declare const toSafeInteger: (value: unknown, field: string) => number;
16
18
  export declare const mapRowToMemory: (row: DbRow, tags?: string[]) => Memory;
17
19
  export declare const mapRowToSearchResult: (row: DbRow, tags?: string[]) => SearchResult;
18
20
  export declare const mapRowToRelationship: (row: DbRow) => Relationship;
21
+ export declare const findMemoryIdByHash: (hash: string) => number | undefined;
22
+ export declare const requireMemoryIdByHash: (hash: string, message?: string) => number;
19
23
  export declare const loadTagsForMemoryIds: (memoryIds: readonly number[]) => Map<number, string[]>;
20
- //# sourceMappingURL=db.d.ts.map
24
+ export {};
package/dist/core/db.js CHANGED
@@ -1,3 +1,4 @@
1
+ import assert from 'node:assert/strict';
1
2
  import { mkdir } from 'node:fs/promises';
2
3
  import path from 'node:path';
3
4
  import { DatabaseSync } from 'node:sqlite';
@@ -66,35 +67,33 @@ const FTS_SYNC_SQL = `
66
67
  SELECT id, content, summary FROM memories
67
68
  WHERE id NOT IN (SELECT rowid FROM memories_fts);
68
69
  `;
69
- const withTimeout = async (promise, ms, message) => {
70
+ const ensureDbDirectory = async (dbPath) => {
71
+ if (dbPath === ':memory:')
72
+ return;
73
+ const dbDir = path.dirname(dbPath);
74
+ if (dbDir === '.')
75
+ return;
76
+ const timeoutMs = 5000;
70
77
  let timeout;
71
78
  const timeoutPromise = new Promise((_, reject) => {
72
79
  timeout = setTimeout(() => {
73
- reject(new Error(message));
74
- }, ms);
80
+ reject(new Error('Database directory creation timed out'));
81
+ }, timeoutMs);
75
82
  });
76
83
  try {
77
- return await Promise.race([promise, timeoutPromise]);
84
+ await Promise.race([mkdir(dbDir, { recursive: true }), timeoutPromise]);
78
85
  }
79
86
  finally {
80
87
  if (timeout)
81
88
  clearTimeout(timeout);
82
89
  }
83
90
  };
84
- const ensureDbDirectory = async (dbPath) => {
85
- if (dbPath === ':memory:')
86
- return;
87
- await withTimeout(mkdir(path.dirname(dbPath), { recursive: true }), 5000, 'Database directory creation timed out');
88
- };
89
91
  const enableDefensiveMode = (database) => {
90
92
  const extended = database;
91
93
  if (typeof extended.enableDefensive === 'function') {
92
94
  extended.enableDefensive(true);
93
95
  }
94
96
  };
95
- const isInTransaction = (database) => {
96
- return database.isTransaction;
97
- };
98
97
  const initializeSchema = (database) => {
99
98
  database.exec(SCHEMA_SQL);
100
99
  database.exec(FTS_SYNC_SQL);
@@ -129,71 +128,79 @@ export const getDb = () => {
129
128
  }
130
129
  return dbInstance;
131
130
  };
132
- export const closeDb = () => {
133
- if (dbInstance?.isOpen) {
134
- dbInstance.close();
135
- dbInstance = undefined;
136
- statementCache.clear();
131
+ const MAX_CACHED_STATEMENTS = 200;
132
+ let sqlTagStore;
133
+ const getSqlTagStore = () => {
134
+ const db = getDb();
135
+ sqlTagStore ??= db.createTagStore(MAX_CACHED_STATEMENTS);
136
+ return sqlTagStore;
137
+ };
138
+ const resetSqlTagStore = () => {
139
+ if (!sqlTagStore)
140
+ return;
141
+ if ('reset' in sqlTagStore && typeof sqlTagStore.reset === 'function') {
142
+ sqlTagStore.reset();
143
+ return;
144
+ }
145
+ if ('clear' in sqlTagStore && typeof sqlTagStore.clear === 'function') {
146
+ sqlTagStore.clear();
137
147
  }
138
148
  };
139
- const MAX_CACHED_STATEMENTS = 200;
140
- const statementCache = new Map();
149
+ class LruStatementCache {
150
+ maxSize;
151
+ cache = new Map();
152
+ constructor(maxSize) {
153
+ this.maxSize = maxSize;
154
+ }
155
+ get(sql) {
156
+ const hit = this.cache.get(sql);
157
+ if (!hit)
158
+ return undefined;
159
+ // Refresh LRU order
160
+ this.cache.delete(sql);
161
+ this.cache.set(sql, hit);
162
+ return hit;
163
+ }
164
+ set(sql, stmt) {
165
+ this.cache.set(sql, stmt);
166
+ if (this.cache.size <= this.maxSize)
167
+ return;
168
+ const oldestKey = this.cache.keys().next().value;
169
+ if (oldestKey)
170
+ this.cache.delete(oldestKey);
171
+ }
172
+ clear() {
173
+ this.cache.clear();
174
+ }
175
+ }
176
+ const statementCache = new LruStatementCache(MAX_CACHED_STATEMENTS);
177
+ export const closeDb = () => {
178
+ if (!dbInstance?.isOpen)
179
+ return;
180
+ dbInstance.close();
181
+ dbInstance = undefined;
182
+ resetSqlTagStore();
183
+ sqlTagStore = undefined;
184
+ statementCache.clear();
185
+ };
141
186
  export const prepareCached = (sql) => {
142
187
  const cached = statementCache.get(sql);
143
- if (cached) {
144
- statementCache.delete(sql);
145
- statementCache.set(sql, cached);
188
+ if (cached)
146
189
  return cached;
147
- }
148
190
  const stmt = getDb().prepare(sql);
149
191
  statementCache.set(sql, stmt);
150
- if (statementCache.size > MAX_CACHED_STATEMENTS) {
151
- const oldestKey = statementCache.keys().next().value;
152
- if (oldestKey)
153
- statementCache.delete(oldestKey);
154
- }
155
192
  return stmt;
156
193
  };
157
- const isDbRow = (value) => {
158
- return typeof value === 'object' && value !== null;
159
- };
160
- const assertDbRow = (value) => {
161
- if (!isDbRow(value)) {
162
- throw new Error('Invalid row');
163
- }
164
- return value;
165
- };
166
- const toDbRowArray = (value) => {
167
- if (!Array.isArray(value)) {
168
- throw new Error('Expected rows array');
169
- }
170
- return value.map(assertDbRow);
171
- };
172
- const toDbRowOrUndefined = (value) => {
173
- if (value === undefined)
174
- return undefined;
175
- return assertDbRow(value);
176
- };
177
- const toRunResult = (value) => {
178
- if (typeof value !== 'object' || value === null) {
179
- throw new Error('Invalid run result');
180
- }
181
- const result = value;
182
- const { changes } = result;
183
- if (typeof changes !== 'number' && typeof changes !== 'bigint') {
184
- throw new Error('Invalid run result');
185
- }
186
- return { changes };
194
+ export const executeAll = (stmt, ...params) => stmt.all(...params);
195
+ export const sqlAll = (strings, ...params) => getSqlTagStore().all(strings, ...params);
196
+ export const sqlGet = (strings, ...params) => getSqlTagStore().get(strings, ...params);
197
+ export const sqlRun = (strings, ...params) => {
198
+ const res = getSqlTagStore().run(strings, ...params);
199
+ return { changes: res.changes };
187
200
  };
188
- export const executeAll = (stmt, ...params) => toDbRowArray(stmt.all(...params));
189
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
190
- export const executeGet = (stmt, ...params) => toDbRowOrUndefined(stmt.get(...params));
191
- export const executeRun = (stmt, ...params) => toRunResult(stmt.run(...params));
192
201
  export const withImmediateTransaction = (operation) => {
193
202
  const db = getDb();
194
- if (isInTransaction(db)) {
195
- throw new Error('Cannot start nested transaction');
196
- }
203
+ assert.ok(!db.isTransaction, 'Cannot start nested transaction');
197
204
  db.exec('BEGIN IMMEDIATE');
198
205
  try {
199
206
  const result = operation();
@@ -205,6 +212,22 @@ export const withImmediateTransaction = (operation) => {
205
212
  throw err;
206
213
  }
207
214
  };
215
+ const SAVEPOINT_NAME_PATTERN = /^[A-Za-z_]\w*$/;
216
+ export const withSavepoint = (name, operation) => {
217
+ assert.ok(SAVEPOINT_NAME_PATTERN.test(name), `Invalid savepoint name: ${name}`);
218
+ const db = getDb();
219
+ db.exec(`SAVEPOINT ${name}`);
220
+ try {
221
+ const result = operation();
222
+ db.exec(`RELEASE ${name}`);
223
+ return result;
224
+ }
225
+ catch (err) {
226
+ db.exec(`ROLLBACK TO ${name}`);
227
+ db.exec(`RELEASE ${name}`);
228
+ throw err;
229
+ }
230
+ };
208
231
  const createFieldError = (field) => new Error(`Invalid ${field}`);
209
232
  const assertFiniteNumber = (value, field) => {
210
233
  if (!Number.isFinite(value)) {
@@ -228,82 +251,135 @@ export const toSafeInteger = (value, field) => {
228
251
  }
229
252
  return numeric;
230
253
  };
231
- const toString = (value, field) => {
232
- if (typeof value === 'string')
233
- return value;
234
- throw createFieldError(field);
254
+ export const mapRowToMemory = (row, tags = []) => {
255
+ const { id: idRaw, content, summary: summaryRaw, importance: impRaw = 0, memory_type: typeRaw = 'general', created_at: created, accessed_at: accessed, hash, } = row;
256
+ const id = typeof idRaw === 'bigint' ? Number(idRaw) : idRaw;
257
+ if (!Number.isSafeInteger(id))
258
+ throw new Error('Invalid id');
259
+ if (typeof content !== 'string')
260
+ throw new Error('Invalid content');
261
+ let summary;
262
+ if (summaryRaw === null || summaryRaw === undefined) {
263
+ summary = undefined;
264
+ }
265
+ else if (typeof summaryRaw === 'string') {
266
+ summary = summaryRaw;
267
+ }
268
+ else {
269
+ throw new Error('Invalid summary');
270
+ }
271
+ const importance = typeof impRaw === 'bigint' ? Number(impRaw) : impRaw;
272
+ if (!Number.isSafeInteger(importance))
273
+ throw new Error('Invalid importance');
274
+ if (typeof typeRaw !== 'string' ||
275
+ !MEMORY_TYPES.includes(typeRaw)) {
276
+ throw new Error('Invalid memory_type');
277
+ }
278
+ if (typeof created !== 'string')
279
+ throw new Error('Invalid created_at');
280
+ if (typeof accessed !== 'string')
281
+ throw new Error('Invalid accessed_at');
282
+ if (typeof hash !== 'string')
283
+ throw new Error('Invalid hash');
284
+ return {
285
+ id,
286
+ content,
287
+ summary,
288
+ tags,
289
+ importance,
290
+ memory_type: typeRaw,
291
+ created_at: created,
292
+ accessed_at: accessed,
293
+ hash,
294
+ };
235
295
  };
236
- const isMemoryType = (value) => MEMORY_TYPES.includes(value);
237
- const toMemoryType = (value, field) => {
238
- const str = toString(value, field);
239
- if (!isMemoryType(str)) {
240
- throw createFieldError(field);
296
+ export const mapRowToSearchResult = (row, tags = []) => {
297
+ const memory = mapRowToMemory(row, tags);
298
+ const relRaw = row.relevance;
299
+ let relevance = 0;
300
+ if (relRaw !== null && relRaw !== undefined) {
301
+ if (typeof relRaw === 'bigint') {
302
+ relevance = Number(relRaw);
303
+ }
304
+ else {
305
+ relevance = relRaw;
306
+ }
241
307
  }
242
- return str;
308
+ if (!Number.isFinite(relevance))
309
+ throw new Error('Invalid relevance');
310
+ return {
311
+ ...memory,
312
+ relevance,
313
+ };
243
314
  };
244
- const toOptionalString = (value, field) => {
245
- if (value === null || value === undefined)
246
- return undefined;
247
- return toString(value, field);
315
+ export const mapRowToRelationship = (row) => {
316
+ const { id: idRaw, from_hash: fromHash, to_hash: toHash, relation_type: relationType, created_at: created, } = row;
317
+ const id = typeof idRaw === 'bigint' ? Number(idRaw) : idRaw;
318
+ if (!Number.isSafeInteger(id))
319
+ throw new Error('Invalid id');
320
+ if (typeof fromHash !== 'string')
321
+ throw new Error('Invalid from_hash');
322
+ if (typeof toHash !== 'string')
323
+ throw new Error('Invalid to_hash');
324
+ if (typeof relationType !== 'string')
325
+ throw new Error('Invalid relation_type');
326
+ if (typeof created !== 'string')
327
+ throw new Error('Invalid created_at');
328
+ return {
329
+ id,
330
+ from_hash: fromHash,
331
+ to_hash: toHash,
332
+ relation_type: relationType,
333
+ created_at: created,
334
+ };
248
335
  };
249
- const toOptionalNumber = (value, field) => {
250
- if (value === null || value === undefined)
336
+ export const findMemoryIdByHash = (hash) => {
337
+ const row = sqlGet `SELECT id FROM memories WHERE hash = ${hash}`;
338
+ if (!row)
251
339
  return undefined;
252
- return toNumber(value, field);
340
+ const idRaw = row.id;
341
+ const id = typeof idRaw === 'bigint' ? Number(idRaw) : idRaw;
342
+ if (!Number.isSafeInteger(id))
343
+ throw new Error('Invalid id');
344
+ return id;
253
345
  };
254
- export const mapRowToMemory = (row, tags = []) => ({
255
- id: toSafeInteger(row.id, 'id'),
256
- content: toString(row.content, 'content'),
257
- summary: toOptionalString(row.summary, 'summary'),
258
- tags,
259
- importance: toSafeInteger(row.importance ?? 0, 'importance'),
260
- memory_type: toMemoryType(row.memory_type ?? 'general', 'memory_type'),
261
- created_at: toString(row.created_at, 'created_at'),
262
- accessed_at: toString(row.accessed_at, 'accessed_at'),
263
- hash: toString(row.hash, 'hash'),
264
- });
265
- export const mapRowToSearchResult = (row, tags = []) => ({
266
- ...mapRowToMemory(row, tags),
267
- relevance: toOptionalNumber(row.relevance, 'relevance') ?? 0,
268
- });
269
- export const mapRowToRelationship = (row) => ({
270
- id: toSafeInteger(row.id, 'id'),
271
- from_hash: toString(row.from_hash, 'from_hash'),
272
- to_hash: toString(row.to_hash, 'to_hash'),
273
- relation_type: toString(row.relation_type, 'relation_type'),
274
- created_at: toString(row.created_at, 'created_at'),
275
- });
276
- const dedupeIds = (ids) => {
277
- const seen = new Set();
278
- const unique = [];
279
- for (const id of ids) {
280
- if (seen.has(id))
281
- continue;
282
- seen.add(id);
283
- unique.push(id);
284
- }
285
- return unique;
286
- };
287
- const pushToMapArray = (map, key, value) => {
288
- const existing = map.get(key);
289
- if (existing) {
290
- existing.push(value);
291
- return;
292
- }
293
- map.set(key, [value]);
346
+ export const requireMemoryIdByHash = (hash, message = `Memory not found: ${hash}`) => {
347
+ const id = findMemoryIdByHash(hash);
348
+ if (id === undefined)
349
+ throw new Error(message);
350
+ return id;
294
351
  };
295
352
  export const loadTagsForMemoryIds = (memoryIds) => {
296
- const uniqueIds = dedupeIds(memoryIds);
353
+ const uniqueIds = [];
354
+ const seen = new Set();
355
+ for (const id of memoryIds) {
356
+ if (!seen.has(id)) {
357
+ seen.add(id);
358
+ uniqueIds.push(id);
359
+ }
360
+ }
297
361
  if (uniqueIds.length === 0)
298
362
  return new Map();
299
- const stmtSelectTags = prepareCached('SELECT memory_id, tag FROM tags WHERE memory_id IN (SELECT value FROM json_each(?)) ORDER BY memory_id, tag');
300
- const rows = executeAll(stmtSelectTags, JSON.stringify(uniqueIds));
363
+ const rows = sqlAll `
364
+ SELECT memory_id, tag
365
+ FROM tags
366
+ WHERE memory_id IN (SELECT value FROM json_each(${JSON.stringify(uniqueIds)}))
367
+ ORDER BY memory_id, tag
368
+ `;
301
369
  const tagsById = new Map();
302
370
  for (const row of rows) {
303
- const memoryId = toSafeInteger(row.memory_id, 'memory_id');
304
- const tag = toString(row.tag, 'tag');
305
- pushToMapArray(tagsById, memoryId, tag);
371
+ const memoryIdRaw = row.memory_id;
372
+ const memoryId = typeof memoryIdRaw === 'bigint'
373
+ ? Number(memoryIdRaw)
374
+ : memoryIdRaw;
375
+ const tag = row.tag;
376
+ const list = tagsById.get(memoryId);
377
+ if (list) {
378
+ list.push(tag);
379
+ }
380
+ else {
381
+ tagsById.set(memoryId, [tag]);
382
+ }
306
383
  }
307
384
  return tagsById;
308
385
  };
309
- //# sourceMappingURL=db.js.map
@@ -1,6 +1,5 @@
1
1
  import type { BatchDeleteResult, Memory, MemoryStats, StatementResult } from '../types.js';
2
2
  export declare const getMemory: (hash: string) => Memory | undefined;
3
3
  export declare const deleteMemory: (hash: string) => StatementResult;
4
- export declare const deleteMemories: (hashes: string[]) => BatchDeleteResult;
4
+ export declare const deleteMemories: (hashes: string[], signal?: AbortSignal) => BatchDeleteResult;
5
5
  export declare const getStats: () => MemoryStats;
6
- //# sourceMappingURL=memory-read.d.ts.map
@@ -1,10 +1,9 @@
1
- import { executeGet, executeRun, loadTagsForMemoryIds, mapRowToMemory, prepareCached, toSafeInteger, withImmediateTransaction, } from './db.js';
1
+ import { throwIfAborted } from './abort.js';
2
+ import { loadTagsForMemoryIds, mapRowToMemory, sqlGet, sqlRun, toSafeInteger, withImmediateTransaction, } from './db.js';
3
+ const loadMemoryRowByHash = (hash) => sqlGet `SELECT * FROM memories WHERE hash = ${hash}`;
2
4
  export const getMemory = (hash) => {
3
5
  return withImmediateTransaction(() => {
4
- const stmtTouchMemoryByHash = prepareCached('UPDATE memories SET accessed_at = CURRENT_TIMESTAMP WHERE hash = ?');
5
- executeRun(stmtTouchMemoryByHash, hash);
6
- const stmtGetMemoryByHash = prepareCached('SELECT * FROM memories WHERE hash = ?');
7
- const row = executeGet(stmtGetMemoryByHash, hash);
6
+ const row = loadMemoryRowByHash(hash);
8
7
  if (!row)
9
8
  return undefined;
10
9
  const id = toSafeInteger(row.id, 'id');
@@ -13,32 +12,34 @@ export const getMemory = (hash) => {
13
12
  });
14
13
  };
15
14
  export const deleteMemory = (hash) => {
16
- const stmtDeleteMemoryByHash = prepareCached('DELETE FROM memories WHERE hash = ?');
17
- const result = executeRun(stmtDeleteMemoryByHash, hash);
15
+ const result = sqlRun `DELETE FROM memories WHERE hash = ${hash}`;
18
16
  return { changes: toSafeInteger(result.changes, 'changes') };
19
17
  };
20
18
  const deleteMemoryForBatch = (hash) => {
21
19
  try {
22
20
  const result = deleteMemory(hash);
23
21
  const deleted = result.changes > 0;
24
- return deleted
25
- ? { item: { hash, deleted: true }, succeeded: true }
26
- : {
22
+ if (!deleted) {
23
+ return {
27
24
  item: { hash, deleted: false, error: 'Memory not found' },
28
25
  succeeded: false,
29
26
  };
27
+ }
28
+ return { item: { hash, deleted: true }, succeeded: true };
30
29
  }
31
30
  catch (err) {
32
31
  const message = err instanceof Error ? err.message : 'Unknown error';
33
32
  return { item: { hash, deleted: false, error: message }, succeeded: false };
34
33
  }
35
34
  };
36
- export const deleteMemories = (hashes) => {
37
- const results = [];
38
- let succeeded = 0;
39
- let failed = 0;
35
+ export const deleteMemories = (hashes, signal) => {
40
36
  return withImmediateTransaction(() => {
37
+ const results = [];
38
+ let succeeded = 0;
39
+ let failed = 0;
40
+ throwIfAborted(signal);
41
41
  for (const hash of hashes) {
42
+ throwIfAborted(signal);
42
43
  const outcome = deleteMemoryForBatch(hash);
43
44
  results.push(outcome.item);
44
45
  succeeded += outcome.succeeded ? 1 : 0;
@@ -57,10 +58,8 @@ const toDateString = (value) => {
57
58
  return null;
58
59
  };
59
60
  const queryCounts = () => {
60
- const stmtMemoryCount = prepareCached('SELECT COUNT(*) as count FROM memories');
61
- const memoryRow = executeGet(stmtMemoryCount);
62
- const stmtTagCount = prepareCached('SELECT COUNT(DISTINCT tag) as count FROM tags');
63
- const tagRow = executeGet(stmtTagCount);
61
+ const memoryRow = sqlGet `SELECT COUNT(*) as count FROM memories`;
62
+ const tagRow = sqlGet `SELECT COUNT(DISTINCT tag) as count FROM tags`;
64
63
  if (!memoryRow)
65
64
  throw new Error('Failed to load memory stats');
66
65
  if (!tagRow)
@@ -69,8 +68,7 @@ const queryCounts = () => {
69
68
  };
70
69
  export const getStats = () => {
71
70
  const { memoryRow, tagRow } = queryCounts();
72
- const stmtDateRange = prepareCached('SELECT MIN(created_at) as oldest, MAX(created_at) as newest FROM memories');
73
- const dateRow = executeGet(stmtDateRange);
71
+ const dateRow = sqlGet `SELECT MIN(created_at) as oldest, MAX(created_at) as newest FROM memories`;
74
72
  return {
75
73
  memoryCount: toSafeInteger(memoryRow.count, 'memoryCount'),
76
74
  tagCount: toSafeInteger(tagRow.count, 'tagCount'),
@@ -78,4 +76,3 @@ export const getStats = () => {
78
76
  newestMemory: toDateString(dateRow?.newest),
79
77
  };
80
78
  };
81
- //# sourceMappingURL=memory-read.js.map
@@ -10,11 +10,10 @@ export declare const createMemories: (items: {
10
10
  tags?: readonly string[];
11
11
  importance?: number;
12
12
  memory_type?: MemoryType;
13
- }[]) => BatchStoreResult;
13
+ }[], signal?: AbortSignal) => BatchStoreResult;
14
14
  interface UpdateMemoryOptions {
15
15
  content: string;
16
16
  tags?: readonly string[] | undefined;
17
17
  }
18
18
  export declare const updateMemory: (hash: string, options: UpdateMemoryOptions) => MemoryUpdateResult;
19
19
  export {};
20
- //# sourceMappingURL=memory-write.d.ts.map