@j0hanz/memdb 1.2.4 → 1.2.6

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
@@ -161,7 +161,7 @@ Search memories by content and tags.
161
161
  | :-------- | :----- | :------- | :------ | :---------------------------------------- |
162
162
  | `query` | string | Yes | - | Search query (1-1000 chars, max 50 terms) |
163
163
 
164
- **Returns:** Array of search results (`Memory` + `relevance`).
164
+ **Returns:** Array of search results (`Memory` + `relevance`, includes `tags`).
165
165
 
166
166
  Notes:
167
167
 
@@ -177,7 +177,11 @@ Retrieve a specific memory by its hash.
177
177
  | :-------- | :----- | :------- | :------ | :------------------ |
178
178
  | `hash` | string | Yes | - | MD5 hash (32 chars) |
179
179
 
180
- **Returns:** `Memory`.
180
+ **Returns:** `Memory` (includes `tags`).
181
+
182
+ Notes:
183
+
184
+ - Updates `accessed_at` on read.
181
185
 
182
186
  ### `delete_memory`
183
187
 
@@ -242,6 +246,7 @@ All memory-shaped responses include:
242
246
  - `id`: integer ID
243
247
  - `content`: original content string
244
248
  - `summary`: optional summary (currently unset by tools)
249
+ - `tags`: string array
245
250
  - `created_at`: timestamp string
246
251
  - `accessed_at`: timestamp string
247
252
  - `hash`: MD5 hash
package/dist/core/db.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { DatabaseSync, type StatementSync } from 'node:sqlite';
2
- import { type Memory, type SearchResult } from '../types.js';
2
+ import { type Memory, type Relationship, type SearchResult } from '../types.js';
3
3
  export type DbRow = Record<string, unknown>;
4
4
  export declare const db: DatabaseSync;
5
5
  export declare const closeDb: () => void;
@@ -12,5 +12,7 @@ export declare const executeRun: (stmt: StatementSync, ...params: SqlParam[]) =>
12
12
  };
13
13
  export declare const withImmediateTransaction: <T>(operation: () => T) => T;
14
14
  export declare const toSafeInteger: (value: unknown, field: string) => number;
15
- export declare const mapRowToMemory: (row: DbRow) => Memory;
16
- export declare const mapRowToSearchResult: (row: DbRow) => SearchResult;
15
+ export declare const mapRowToMemory: (row: DbRow, tags?: string[]) => Memory;
16
+ export declare const mapRowToSearchResult: (row: DbRow, tags?: string[]) => SearchResult;
17
+ export declare const mapRowToRelationship: (row: DbRow) => Relationship;
18
+ export declare const loadTagsForMemoryIds: (memoryIds: readonly number[]) => Map<number, string[]>;
package/dist/core/db.js CHANGED
@@ -67,23 +67,18 @@ const FTS_SYNC_SQL = `
67
67
  WHERE id NOT IN (SELECT rowid FROM memories_fts);
68
68
  `;
69
69
  const withTimeout = async (promise, ms, message) => {
70
- const controller = new AbortController();
71
- const timeout = setTimeout(() => {
72
- controller.abort();
73
- }, ms);
70
+ let timeout;
71
+ const timeoutPromise = new Promise((_, reject) => {
72
+ timeout = setTimeout(() => {
73
+ reject(new Error(message));
74
+ }, ms);
75
+ });
74
76
  try {
75
- const result = await Promise.race([
76
- promise,
77
- new Promise((_, reject) => {
78
- controller.signal.addEventListener('abort', () => {
79
- reject(new Error(message));
80
- });
81
- }),
82
- ]);
83
- return result;
77
+ return await Promise.race([promise, timeoutPromise]);
84
78
  }
85
79
  finally {
86
- clearTimeout(timeout);
80
+ if (timeout)
81
+ clearTimeout(timeout);
87
82
  }
88
83
  };
89
84
  const ensureDbDirectory = async (dbPath) => {
@@ -146,26 +141,22 @@ const enforceStatementCacheLimit = () => {
146
141
  const isDbRow = (value) => {
147
142
  return typeof value === 'object' && value !== null;
148
143
  };
144
+ const assertDbRow = (value) => {
145
+ if (!isDbRow(value)) {
146
+ throw new Error('Invalid row');
147
+ }
148
+ return value;
149
+ };
149
150
  const toDbRowArray = (value) => {
150
151
  if (!Array.isArray(value)) {
151
152
  throw new Error('Expected rows array');
152
153
  }
153
- const rows = [];
154
- for (const row of value) {
155
- if (!isDbRow(row)) {
156
- throw new Error('Invalid row');
157
- }
158
- rows.push(row);
159
- }
160
- return rows;
154
+ return value.map(assertDbRow);
161
155
  };
162
156
  const toDbRowOrUndefined = (value) => {
163
157
  if (value === undefined)
164
158
  return undefined;
165
- if (!isDbRow(value)) {
166
- throw new Error('Invalid row');
167
- }
168
- return value;
159
+ return assertDbRow(value);
169
160
  };
170
161
  const toRunResult = (value) => {
171
162
  if (typeof value !== 'object' || value === null) {
@@ -206,13 +197,18 @@ export const withImmediateTransaction = (operation) => {
206
197
  }
207
198
  };
208
199
  const createFieldError = (field) => new Error(`Invalid ${field}`);
200
+ const assertFiniteNumber = (value, field) => {
201
+ if (!Number.isFinite(value)) {
202
+ throw createFieldError(field);
203
+ }
204
+ return value;
205
+ };
209
206
  const toNumber = (value, field) => {
210
- if (typeof value === 'number' && Number.isFinite(value))
211
- return value;
207
+ if (typeof value === 'number') {
208
+ return assertFiniteNumber(value, field);
209
+ }
212
210
  if (typeof value === 'bigint') {
213
- const numeric = Number(value);
214
- if (Number.isFinite(numeric))
215
- return numeric;
211
+ return assertFiniteNumber(Number(value), field);
216
212
  }
217
213
  throw createFieldError(field);
218
214
  };
@@ -246,17 +242,68 @@ const toOptionalNumber = (value, field) => {
246
242
  return undefined;
247
243
  return toNumber(value, field);
248
244
  };
249
- export const mapRowToMemory = (row) => ({
245
+ export const mapRowToMemory = (row, tags = []) => ({
250
246
  id: toSafeInteger(row.id, 'id'),
251
247
  content: toString(row.content, 'content'),
252
248
  summary: toOptionalString(row.summary, 'summary'),
249
+ tags,
253
250
  importance: toSafeInteger(row.importance ?? 0, 'importance'),
254
251
  memory_type: toMemoryType(row.memory_type ?? 'general', 'memory_type'),
255
252
  created_at: toString(row.created_at, 'created_at'),
256
253
  accessed_at: toString(row.accessed_at, 'accessed_at'),
257
254
  hash: toString(row.hash, 'hash'),
258
255
  });
259
- export const mapRowToSearchResult = (row) => ({
260
- ...mapRowToMemory(row),
256
+ export const mapRowToSearchResult = (row, tags = []) => ({
257
+ ...mapRowToMemory(row, tags),
261
258
  relevance: toOptionalNumber(row.relevance, 'relevance') ?? 0,
262
259
  });
260
+ export const mapRowToRelationship = (row) => ({
261
+ id: toSafeInteger(row.id, 'id'),
262
+ from_hash: toString(row.from_hash, 'from_hash'),
263
+ to_hash: toString(row.to_hash, 'to_hash'),
264
+ relation_type: toString(row.relation_type, 'relation_type'),
265
+ created_at: toString(row.created_at, 'created_at'),
266
+ });
267
+ const tagsSelectStatements = [];
268
+ const getSelectTagsStatement = (idCount) => {
269
+ const cached = tagsSelectStatements[idCount];
270
+ if (cached)
271
+ return cached;
272
+ const placeholders = Array.from({ length: idCount }, () => '?').join(', ');
273
+ const stmt = db.prepare(`SELECT memory_id, tag FROM tags WHERE memory_id IN (${placeholders}) ORDER BY memory_id, tag`);
274
+ tagsSelectStatements[idCount] = stmt;
275
+ return stmt;
276
+ };
277
+ const dedupeIds = (ids) => {
278
+ const seen = new Set();
279
+ const unique = [];
280
+ for (const id of ids) {
281
+ if (seen.has(id))
282
+ continue;
283
+ seen.add(id);
284
+ unique.push(id);
285
+ }
286
+ return unique;
287
+ };
288
+ const pushToMapArray = (map, key, value) => {
289
+ const existing = map.get(key);
290
+ if (existing) {
291
+ existing.push(value);
292
+ return;
293
+ }
294
+ map.set(key, [value]);
295
+ };
296
+ export const loadTagsForMemoryIds = (memoryIds) => {
297
+ const uniqueIds = dedupeIds(memoryIds);
298
+ if (uniqueIds.length === 0)
299
+ return new Map();
300
+ const stmt = getSelectTagsStatement(uniqueIds.length);
301
+ const rows = executeAll(stmt, ...uniqueIds);
302
+ const tagsById = new Map();
303
+ for (const row of rows) {
304
+ const memoryId = toSafeInteger(row.memory_id, 'memory_id');
305
+ const tag = toString(row.tag, 'tag');
306
+ pushToMapArray(tagsById, memoryId, tag);
307
+ }
308
+ return tagsById;
309
+ };
@@ -1,36 +1,48 @@
1
- import { db, executeGet, executeRun, mapRowToMemory, toSafeInteger, withImmediateTransaction, } from './db.js';
1
+ import { db, executeGet, executeRun, loadTagsForMemoryIds, mapRowToMemory, toSafeInteger, withImmediateTransaction, } from './db.js';
2
2
  const stmtGetMemoryByHash = db.prepare('SELECT * FROM memories WHERE hash = ?');
3
+ const stmtTouchMemoryByHash = db.prepare('UPDATE memories SET accessed_at = CURRENT_TIMESTAMP WHERE hash = ?');
3
4
  const stmtDeleteMemoryByHash = db.prepare('DELETE FROM memories WHERE hash = ?');
4
5
  export const getMemory = (hash) => {
5
- const row = executeGet(stmtGetMemoryByHash, hash);
6
- return row ? mapRowToMemory(row) : undefined;
6
+ return withImmediateTransaction(() => {
7
+ executeRun(stmtTouchMemoryByHash, hash);
8
+ const row = executeGet(stmtGetMemoryByHash, hash);
9
+ if (!row)
10
+ return undefined;
11
+ const id = toSafeInteger(row.id, 'id');
12
+ const tags = loadTagsForMemoryIds([id]).get(id) ?? [];
13
+ return mapRowToMemory(row, tags);
14
+ });
7
15
  };
8
16
  export const deleteMemory = (hash) => {
9
17
  const result = executeRun(stmtDeleteMemoryByHash, hash);
10
18
  return { changes: toSafeInteger(result.changes, 'changes') };
11
19
  };
20
+ const deleteMemoryForBatch = (hash) => {
21
+ try {
22
+ const result = deleteMemory(hash);
23
+ const deleted = result.changes > 0;
24
+ return deleted
25
+ ? { item: { hash, deleted: true }, succeeded: true }
26
+ : {
27
+ item: { hash, deleted: false, error: 'Memory not found' },
28
+ succeeded: false,
29
+ };
30
+ }
31
+ catch (err) {
32
+ const message = err instanceof Error ? err.message : 'Unknown error';
33
+ return { item: { hash, deleted: false, error: message }, succeeded: false };
34
+ }
35
+ };
12
36
  export const deleteMemories = (hashes) => {
13
37
  const results = [];
14
38
  let succeeded = 0;
15
39
  let failed = 0;
16
40
  return withImmediateTransaction(() => {
17
41
  for (const hash of hashes) {
18
- try {
19
- const result = deleteMemory(hash);
20
- if (result.changes > 0) {
21
- results.push({ hash, deleted: true });
22
- succeeded++;
23
- }
24
- else {
25
- results.push({ hash, deleted: false, error: 'Memory not found' });
26
- failed++;
27
- }
28
- }
29
- catch (err) {
30
- const message = err instanceof Error ? err.message : 'Unknown error';
31
- results.push({ hash, deleted: false, error: message });
32
- failed++;
33
- }
42
+ const outcome = deleteMemoryForBatch(hash);
43
+ results.push(outcome.item);
44
+ succeeded += outcome.succeeded ? 1 : 0;
45
+ failed += outcome.succeeded ? 0 : 1;
34
46
  }
35
47
  return { results, succeeded, failed };
36
48
  });
@@ -89,31 +89,55 @@ const createMemoryInTransaction = (input) => {
89
89
  return { id, hash, isNew };
90
90
  };
91
91
  export const createMemories = (items) => {
92
+ return withImmediateTransaction(() => createMemoriesInTransaction(items));
93
+ };
94
+ const withSavepoint = (name, fn) => {
95
+ db.exec(`SAVEPOINT ${name}`);
96
+ try {
97
+ const result = fn();
98
+ db.exec(`RELEASE ${name}`);
99
+ return result;
100
+ }
101
+ catch (err) {
102
+ db.exec(`ROLLBACK TO ${name}`);
103
+ db.exec(`RELEASE ${name}`);
104
+ throw err;
105
+ }
106
+ };
107
+ const createMemoriesInTransaction = (items) => {
92
108
  const results = [];
93
109
  let succeeded = 0;
94
110
  let failed = 0;
95
- return withImmediateTransaction(() => {
96
- for (let i = 0; i < items.length; i++) {
97
- const item = items[i];
98
- if (!item)
99
- continue;
100
- db.exec('SAVEPOINT mem_item');
101
- try {
102
- const { hash, isNew } = createMemoryInTransaction(item);
103
- results.push({ ok: true, index: i, hash, isNew });
104
- succeeded++;
105
- db.exec('RELEASE mem_item');
106
- }
107
- catch (err) {
108
- db.exec('ROLLBACK TO mem_item');
109
- db.exec('RELEASE mem_item');
110
- const message = err instanceof Error ? err.message : 'Unknown error';
111
- results.push({ ok: false, index: i, error: message });
112
- failed++;
113
- }
111
+ for (let i = 0; i < items.length; i++) {
112
+ const item = items[i];
113
+ if (!item)
114
+ continue;
115
+ const result = processCreateMemoriesItem(i, item);
116
+ results.push(result);
117
+ if (result.ok) {
118
+ succeeded++;
114
119
  }
115
- return { results, succeeded, failed };
116
- });
120
+ else {
121
+ failed++;
122
+ }
123
+ }
124
+ return { results, succeeded, failed };
125
+ };
126
+ const processCreateMemoriesItem = (index, item) => {
127
+ const savepointName = `mem_item_${index}`;
128
+ try {
129
+ const created = withSavepoint(savepointName, () => createMemoryInTransaction(item));
130
+ return {
131
+ ok: true,
132
+ index,
133
+ hash: created.hash,
134
+ isNew: created.isNew,
135
+ };
136
+ }
137
+ catch (err) {
138
+ const message = err instanceof Error ? err.message : 'Unknown error';
139
+ return { ok: false, index, error: message };
140
+ }
117
141
  };
118
142
  const stmtDeleteTagsForMemory = db.prepare('DELETE FROM tags WHERE memory_id = ?');
119
143
  const stmtUpdateContent = db.prepare('UPDATE memories SET content = ?, hash = ? WHERE id = ?');
@@ -121,6 +145,14 @@ const replaceTags = (memoryId, tags) => {
121
145
  executeRun(stmtDeleteTagsForMemory, memoryId);
122
146
  insertTags(memoryId, normalizeTags(tags, MAX_TAGS));
123
147
  };
148
+ const assertNoDuplicateOnUpdate = (oldHash, newHash) => {
149
+ if (newHash === oldHash)
150
+ return;
151
+ const existingId = findMemoryIdByHash(newHash);
152
+ if (existingId !== undefined) {
153
+ throw new Error('Content already exists as another memory');
154
+ }
155
+ };
124
156
  export const updateMemory = (hash, options) => {
125
157
  const memoryId = findMemoryIdByHash(hash);
126
158
  if (memoryId === undefined)
@@ -128,12 +160,7 @@ export const updateMemory = (hash, options) => {
128
160
  return withImmediateTransaction(() => {
129
161
  const newHash = buildHash(options.content);
130
162
  // Check if new content would create a duplicate
131
- if (newHash !== hash) {
132
- const existingId = findMemoryIdByHash(newHash);
133
- if (existingId !== undefined) {
134
- throw new Error('Content already exists as another memory');
135
- }
136
- }
163
+ assertNoDuplicateOnUpdate(hash, newHash);
137
164
  // Update content and hash
138
165
  executeRun(stmtUpdateContent, options.content, newHash, memoryId);
139
166
  // Update tags if provided, otherwise preserve existing tags
@@ -1,4 +1,4 @@
1
- import { db, executeAll, executeGet, executeRun, toSafeInteger, withImmediateTransaction, } from './db.js';
1
+ import { db, executeAll, executeGet, executeRun, mapRowToRelationship, toSafeInteger, withImmediateTransaction, } from './db.js';
2
2
  const stmtFindMemoryIdByHash = db.prepare('SELECT id FROM memories WHERE hash = ?');
3
3
  const findMemoryIdByHash = (hash) => {
4
4
  const row = executeGet(stmtFindMemoryIdByHash, hash);
@@ -13,18 +13,6 @@ const requireMemoryId = (hash) => {
13
13
  }
14
14
  return id;
15
15
  };
16
- const toString = (value, field) => {
17
- if (typeof value === 'string')
18
- return value;
19
- throw new Error(`Invalid ${field}`);
20
- };
21
- const mapRowToRelationship = (row) => ({
22
- id: toSafeInteger(row.id, 'id'),
23
- from_hash: toString(row.from_hash, 'from_hash'),
24
- to_hash: toString(row.to_hash, 'to_hash'),
25
- relation_type: toString(row.relation_type, 'relation_type'),
26
- created_at: toString(row.created_at, 'created_at'),
27
- });
28
16
  const stmtInsertRelationship = db.prepare(`
29
17
  INSERT OR IGNORE INTO relationships (from_memory_id, to_memory_id, relation_type)
30
18
  VALUES (?, ?, ?)
@@ -60,13 +48,14 @@ const buildGetRelationshipsQuery = (direction) => {
60
48
  JOIN memories mf ON r.from_memory_id = mf.id
61
49
  JOIN memories mt ON r.to_memory_id = mt.id
62
50
  `;
51
+ const orderBy = ' ORDER BY r.relation_type, mf.hash, mt.hash, r.created_at, r.id';
63
52
  switch (direction) {
64
53
  case 'outgoing':
65
- return `${baseSelect} WHERE mf.hash = ?`;
54
+ return `${baseSelect} WHERE mf.hash = ?${orderBy}`;
66
55
  case 'incoming':
67
- return `${baseSelect} WHERE mt.hash = ?`;
56
+ return `${baseSelect} WHERE mt.hash = ?${orderBy}`;
68
57
  case 'both':
69
- return `${baseSelect} WHERE mf.hash = ? OR mt.hash = ?`;
58
+ return `${baseSelect} WHERE mf.hash = ? OR mt.hash = ?${orderBy}`;
70
59
  }
71
60
  };
72
61
  const stmtGetRelationships = {
@@ -1,4 +1,4 @@
1
- import { db, executeAll, mapRowToSearchResult, prepareCached, toSafeInteger, } from './db.js';
1
+ import { db, executeAll, loadTagsForMemoryIds, mapRowToRelationship, mapRowToSearchResult, prepareCached, toSafeInteger, } from './db.js';
2
2
  const MAX_QUERY_TOKENS = 50;
3
3
  const DEFAULT_LIMIT = 100;
4
4
  const RECENCY_DECAY_DAYS = 7;
@@ -65,25 +65,16 @@ const QUERY_INVALID_TOKENS = ['fts5', 'syntax error'];
65
65
  const isSearchIndexMissing = (message) => INDEX_MISSING_TOKENS.some((token) => message.includes(token));
66
66
  const isSearchQueryInvalid = (message) => QUERY_INVALID_TOKENS.some((token) => message.includes(token));
67
67
  const getErrorMessage = (err) => err instanceof Error ? err.message : String(err);
68
- const SEARCH_ERROR_MAP = [
69
- {
70
- matches: isSearchIndexMissing,
71
- build: () => new Error('Search index unavailable. Ensure FTS5 is enabled and the index is ' +
72
- 'initialized.'),
73
- },
74
- {
75
- matches: isSearchQueryInvalid,
76
- build: (message) => new Error('Invalid search query syntax. Check for unbalanced quotes or special ' +
77
- 'characters. ' +
78
- `Details: ${message}`),
79
- },
80
- ];
81
68
  const toSearchError = (err) => {
82
69
  const message = getErrorMessage(err);
83
- for (const mapping of SEARCH_ERROR_MAP) {
84
- if (mapping.matches(message)) {
85
- return mapping.build(message);
86
- }
70
+ if (isSearchIndexMissing(message)) {
71
+ return new Error('Search index unavailable. Ensure FTS5 is enabled and the index is ' +
72
+ 'initialized.');
73
+ }
74
+ if (isSearchQueryInvalid(message)) {
75
+ return new Error('Invalid search query syntax. Check for unbalanced quotes or special ' +
76
+ 'characters. ' +
77
+ `Details: ${message}`);
87
78
  }
88
79
  return undefined;
89
80
  };
@@ -93,13 +84,17 @@ const executeSearch = (sql, params) => {
93
84
  return executeAll(stmt, ...params);
94
85
  }
95
86
  catch (err) {
96
- const mappedError = toSearchError(err);
97
- if (mappedError) {
98
- throw mappedError;
99
- }
100
- throw err;
87
+ throw toSearchError(err) ?? err;
101
88
  }
102
89
  };
90
+ const mapRowsToSearchResultsWithTags = (rows) => {
91
+ const ids = rows.map((row) => toSafeInteger(row.id, 'id'));
92
+ const tagsById = loadTagsForMemoryIds(ids);
93
+ return rows.map((row) => {
94
+ const id = toSafeInteger(row.id, 'id');
95
+ return mapRowToSearchResult(row, tagsById.get(id) ?? []);
96
+ });
97
+ };
103
98
  export const searchMemories = (input) => {
104
99
  const tokens = tokenizeQuery(input.query);
105
100
  if (tokens.length === 0) {
@@ -107,22 +102,10 @@ export const searchMemories = (input) => {
107
102
  }
108
103
  const { sql, params } = buildSearchQuery(tokens);
109
104
  const rows = executeSearch(sql, params);
110
- return rows.map((row) => mapRowToSearchResult(row));
105
+ return mapRowsToSearchResultsWithTags(rows);
111
106
  };
112
107
  const MAX_RECALL_DEPTH = 3;
113
108
  const MAX_RECALL_MEMORIES = 50;
114
- const toString = (value, field) => {
115
- if (typeof value === 'string')
116
- return value;
117
- throw new Error(`Invalid ${field}`);
118
- };
119
- const mapRowToRelationship = (row) => ({
120
- id: toSafeInteger(row.id, 'id'),
121
- from_hash: toString(row.from_hash, 'from_hash'),
122
- to_hash: toString(row.to_hash, 'to_hash'),
123
- relation_type: toString(row.relation_type, 'relation_type'),
124
- created_at: toString(row.created_at, 'created_at'),
125
- });
126
109
  const buildRecallQuery = (seedCount, depth) => {
127
110
  const seedPlaceholders = Array.from({ length: seedCount }, () => '?').join(', ');
128
111
  const safeDepth = Math.min(Math.max(0, depth), MAX_RECALL_DEPTH);
@@ -166,30 +149,53 @@ const buildRelationshipsQuery = (memoryCount) => {
166
149
  JOIN memories mt ON r.to_memory_id = mt.id
167
150
  WHERE r.from_memory_id IN (${placeholders})
168
151
  AND r.to_memory_id IN (${placeholders})
152
+ ORDER BY r.relation_type, mf.hash, mt.hash, r.created_at, r.id
169
153
  `;
170
154
  return { sql };
171
155
  };
156
+ const executeWithSql = (sql, params) => {
157
+ const stmt = db.prepare(sql);
158
+ return executeAll(stmt, ...params);
159
+ };
160
+ const executeRecall = (seedIds, depth) => {
161
+ const { sql } = buildRecallQuery(seedIds.length, depth);
162
+ return executeWithSql(sql, seedIds);
163
+ };
164
+ const loadRelationshipsForMemoryIds = (memoryIds) => {
165
+ const { sql } = buildRelationshipsQuery(memoryIds.length);
166
+ const rows = executeWithSql(sql, [...memoryIds, ...memoryIds]);
167
+ return rows.map(mapRowToRelationship);
168
+ };
169
+ const getRecallDepth = (depth) => depth ?? 1;
170
+ const emptyRecallResult = (depth) => ({
171
+ memories: [],
172
+ relationships: [],
173
+ depth,
174
+ });
175
+ const recallAtDepthZero = (searchResults, depth) => {
176
+ if (depth !== 0)
177
+ return undefined;
178
+ return { memories: searchResults, relationships: [], depth };
179
+ };
180
+ const recallAtPositiveDepth = (searchResults, depth) => {
181
+ const seedIds = searchResults.map((m) => m.id);
182
+ const recallRows = executeRecall(seedIds, depth);
183
+ const memories = mapRowsToSearchResultsWithTags(recallRows);
184
+ if (memories.length === 0) {
185
+ return emptyRecallResult(depth);
186
+ }
187
+ const relationships = loadRelationshipsForMemoryIds(memories.map((m) => m.id));
188
+ return { memories, relationships, depth };
189
+ };
172
190
  export const recallMemories = (input) => {
173
- const depth = input.depth ?? 1;
174
191
  const searchResults = searchMemories({ query: input.query });
175
192
  if (searchResults.length === 0) {
176
- return { memories: [], relationships: [], depth };
177
- }
178
- if (depth === 0) {
179
- return { memories: searchResults, relationships: [], depth };
193
+ return emptyRecallResult(getRecallDepth(input.depth));
180
194
  }
181
- const seedIds = searchResults.map((m) => m.id);
182
- const { sql: recallSql } = buildRecallQuery(seedIds.length, depth);
183
- const recallStmt = db.prepare(recallSql);
184
- const recallRows = executeAll(recallStmt, ...seedIds);
185
- const memories = recallRows.map(mapRowToSearchResult);
186
- const allMemoryIds = memories.map((m) => m.id);
187
- if (allMemoryIds.length === 0) {
188
- return { memories: [], relationships: [], depth };
195
+ const depth = getRecallDepth(input.depth);
196
+ const depthZeroResult = recallAtDepthZero(searchResults, depth);
197
+ if (depthZeroResult) {
198
+ return depthZeroResult;
189
199
  }
190
- const { sql: relSql } = buildRelationshipsQuery(allMemoryIds.length);
191
- const relStmt = db.prepare(relSql);
192
- const relRows = executeAll(relStmt, ...allMemoryIds, ...allMemoryIds);
193
- const relationships = relRows.map(mapRowToRelationship);
194
- return { memories, relationships, depth };
200
+ return recallAtPositiveDepth(searchResults, depth);
195
201
  };