@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
@@ -1,17 +1,18 @@
1
+ import assert from 'node:assert/strict';
1
2
  import crypto from 'node:crypto';
2
- import { executeGet, executeRun, prepareCached, toSafeInteger, withImmediateTransaction, } from './db.js';
3
+ import { throwIfAborted } from './abort.js';
4
+ import { findMemoryIdByHash, sqlGet, sqlRun, toSafeInteger, withImmediateTransaction, withSavepoint, } from './db.js';
3
5
  const MAX_TAGS = 100;
4
6
  const TAG_PATTERN = /^\S+$/;
7
+ const TAG_CASE_FOLD_LOCALE = 'en-US';
8
+ const normalizeTagCase = (tag) => tag.normalize('NFKC').toLocaleLowerCase(TAG_CASE_FOLD_LOCALE);
5
9
  const validateTag = (tag) => {
6
- if (tag.length === 0) {
10
+ if (tag.length === 0)
7
11
  throw new Error('Tag must be at least 1 character');
8
- }
9
- if (tag.length > 50) {
12
+ if (tag.length > 50)
10
13
  throw new Error('Tag exceeds 50 characters');
11
- }
12
- if (!TAG_PATTERN.test(tag)) {
14
+ if (!TAG_PATTERN.test(tag))
13
15
  throw new Error('Tag must not contain whitespace');
14
- }
15
16
  };
16
17
  const validateTagCount = (tags, maxTags) => {
17
18
  if (tags.length > maxTags) {
@@ -21,8 +22,9 @@ const validateTagCount = (tags, maxTags) => {
21
22
  const dedupeTags = (tags) => {
22
23
  const seen = new Set();
23
24
  for (const tag of tags) {
24
- validateTag(tag);
25
- seen.add(tag);
25
+ const normalized = normalizeTagCase(tag);
26
+ validateTag(normalized);
27
+ seen.add(normalized);
26
28
  }
27
29
  return [...seen];
28
30
  };
@@ -32,102 +34,85 @@ const normalizeTags = (tags, maxTags) => {
32
34
  validateTagCount(tags, maxTags);
33
35
  return dedupeTags(tags);
34
36
  };
35
- const findMemoryIdByHash = (hash) => {
36
- const stmtFindMemoryIdByHash = prepareCached('SELECT id FROM memories WHERE hash = ?');
37
- const row = executeGet(stmtFindMemoryIdByHash, hash);
38
- if (!row)
39
- return undefined;
40
- return toSafeInteger(row.id, 'id');
41
- };
42
37
  const insertTags = (memoryId, tags) => {
43
38
  if (tags.length === 0)
44
39
  return;
45
- const stmtInsertTags = prepareCached('INSERT OR IGNORE INTO tags (memory_id, tag) SELECT ?, value FROM json_each(?)');
46
- executeRun(stmtInsertTags, memoryId, JSON.stringify(tags));
47
- };
48
- const buildHash = (content) => {
49
- return crypto.createHash('sha256').update(content).digest('hex');
40
+ const insertResult = sqlRun `
41
+ INSERT OR IGNORE INTO tags (memory_id, tag)
42
+ SELECT ${memoryId}, value FROM json_each(${JSON.stringify(tags)})
43
+ `;
44
+ assert.ok(insertResult.changes >= 0, 'Invalid tag insert result');
50
45
  };
51
- const requireMemoryId = (id) => {
52
- if (id === undefined) {
53
- throw new Error('Failed to resolve memory id');
54
- }
55
- return id;
46
+ const replaceTags = (memoryId, tags) => {
47
+ const deleteResult = sqlRun `DELETE FROM tags WHERE memory_id = ${memoryId}`;
48
+ assert.ok(deleteResult.changes >= 0, 'Invalid tag delete result');
49
+ insertTags(memoryId, normalizeTags(tags, MAX_TAGS));
56
50
  };
51
+ const buildHash = (content) => crypto.createHash('sha256').update(content).digest('hex');
57
52
  const resolveMemoryId = (content, hash, importance, memoryType) => {
58
- const stmtInsertMemory = prepareCached('INSERT OR IGNORE INTO memories (content, hash, importance, memory_type) VALUES (?, ?, ?, ?) RETURNING id');
59
- const inserted = executeGet(stmtInsertMemory, content, hash, importance, memoryType);
53
+ const inserted = sqlGet `
54
+ INSERT OR IGNORE INTO memories (content, hash, importance, memory_type)
55
+ VALUES (${content}, ${hash}, ${importance}, ${memoryType})
56
+ RETURNING id
57
+ `;
60
58
  if (inserted) {
61
59
  return { id: toSafeInteger(inserted.id, 'id'), isNew: true };
62
60
  }
63
- const id = requireMemoryId(findMemoryIdByHash(hash));
64
- return { id, isNew: false };
61
+ const existingId = findMemoryIdByHash(hash);
62
+ assert.ok(existingId !== undefined, 'Failed to resolve memory id');
63
+ return { id: existingId, isNew: false };
65
64
  };
66
- export const createMemory = (input) => withImmediateTransaction(() => createMemoryInTransaction(input));
67
- const createMemoryInTransaction = (input) => {
65
+ const prepareMemory = (input) => {
68
66
  const { content, tags = [], importance = 0, memory_type: memoryType = 'general', } = input;
69
67
  const hash = buildHash(content);
70
68
  const normalizedTags = normalizeTags(tags, MAX_TAGS);
69
+ return { content, hash, normalizedTags, importance, memoryType };
70
+ };
71
+ const insertMemory = (input) => {
72
+ const { content, hash, normalizedTags, importance, memoryType } = input;
71
73
  const { id, isNew } = resolveMemoryId(content, hash, importance, memoryType);
72
74
  insertTags(id, normalizedTags);
73
75
  return { id, hash, isNew };
74
76
  };
75
- export const createMemories = (items) => {
76
- return withImmediateTransaction(() => createMemoriesInTransaction(items));
77
- };
78
- const withSavepoint = (name, fn) => {
79
- executeRun(prepareCached(`SAVEPOINT ${name}`));
77
+ export const createMemory = (input) => withImmediateTransaction(() => insertMemory(prepareMemory(input)));
78
+ const createMemoryWithSavepoint = (index, item, signal) => {
79
+ const savepointName = `mem_item_${index}`;
80
80
  try {
81
- const result = fn();
82
- executeRun(prepareCached(`RELEASE ${name}`));
83
- return result;
81
+ throwIfAborted(signal);
82
+ const prepared = prepareMemory(item);
83
+ const created = withSavepoint(savepointName, () => insertMemory(prepared));
84
+ return {
85
+ ok: true,
86
+ index,
87
+ hash: created.hash,
88
+ isNew: created.isNew,
89
+ };
84
90
  }
85
91
  catch (err) {
86
- executeRun(prepareCached(`ROLLBACK TO ${name}`));
87
- executeRun(prepareCached(`RELEASE ${name}`));
88
- throw err;
92
+ const message = err instanceof Error ? err.message : 'Unknown error';
93
+ return { ok: false, index, error: message };
89
94
  }
90
95
  };
91
- const createMemoriesInTransaction = (items) => {
96
+ const createMemoriesInTransaction = (items, signal) => {
92
97
  const results = [];
93
98
  let succeeded = 0;
94
99
  let failed = 0;
100
+ throwIfAborted(signal);
95
101
  for (let i = 0; i < items.length; i++) {
102
+ throwIfAborted(signal);
96
103
  const item = items[i];
97
104
  if (!item)
98
105
  continue;
99
- const result = createMemoryWithSavepoint(i, item);
106
+ const result = createMemoryWithSavepoint(i, item, signal);
100
107
  results.push(result);
101
- if (result.ok) {
108
+ if (result.ok)
102
109
  succeeded++;
103
- }
104
- else {
110
+ else
105
111
  failed++;
106
- }
107
112
  }
108
113
  return { results, succeeded, failed };
109
114
  };
110
- const createMemoryWithSavepoint = (index, item) => {
111
- const savepointName = `mem_item_${index}`;
112
- try {
113
- const created = withSavepoint(savepointName, () => createMemoryInTransaction(item));
114
- return {
115
- ok: true,
116
- index,
117
- hash: created.hash,
118
- isNew: created.isNew,
119
- };
120
- }
121
- catch (err) {
122
- const message = err instanceof Error ? err.message : 'Unknown error';
123
- return { ok: false, index, error: message };
124
- }
125
- };
126
- const replaceTags = (memoryId, tags) => {
127
- const stmtDeleteTagsForMemory = prepareCached('DELETE FROM tags WHERE memory_id = ?');
128
- executeRun(stmtDeleteTagsForMemory, memoryId);
129
- insertTags(memoryId, normalizeTags(tags, MAX_TAGS));
130
- };
115
+ export const createMemories = (items, signal) => withImmediateTransaction(() => createMemoriesInTransaction(items, signal));
131
116
  const assertNoDuplicateOnUpdate = (oldHash, newHash) => {
132
117
  if (newHash === oldHash)
133
118
  return;
@@ -136,22 +121,21 @@ const assertNoDuplicateOnUpdate = (oldHash, newHash) => {
136
121
  throw new Error('Content already exists as another memory');
137
122
  }
138
123
  };
139
- export const updateMemory = (hash, options) => {
124
+ const updateMemoryInTransaction = (hash, options) => {
140
125
  const memoryId = findMemoryIdByHash(hash);
141
126
  if (memoryId === undefined)
142
127
  throw new Error('Memory not found');
143
- return withImmediateTransaction(() => {
144
- const newHash = buildHash(options.content);
145
- // Check if new content would create a duplicate
146
- assertNoDuplicateOnUpdate(hash, newHash);
147
- // Update content and hash
148
- const stmtUpdateContent = prepareCached('UPDATE memories SET content = ?, hash = ? WHERE id = ?');
149
- executeRun(stmtUpdateContent, options.content, newHash, memoryId);
150
- // Update tags if provided, otherwise preserve existing tags
151
- if (options.tags !== undefined) {
152
- replaceTags(memoryId, options.tags);
153
- }
154
- return { updated: true, oldHash: hash, newHash };
155
- });
128
+ const newHash = buildHash(options.content);
129
+ assertNoDuplicateOnUpdate(hash, newHash);
130
+ const updateResult = sqlRun `
131
+ UPDATE memories
132
+ SET content = ${options.content}, hash = ${newHash}
133
+ WHERE id = ${memoryId}
134
+ `;
135
+ assert.ok(updateResult.changes >= 0, 'Invalid update result');
136
+ if (options.tags !== undefined) {
137
+ replaceTags(memoryId, options.tags);
138
+ }
139
+ return { updated: true, oldHash: hash, newHash };
156
140
  };
157
- //# sourceMappingURL=memory-write.js.map
141
+ export const updateMemory = (hash, options) => withImmediateTransaction(() => updateMemoryInTransaction(hash, options));
@@ -13,4 +13,3 @@ export declare const deleteRelationship: (input: {
13
13
  to_hash: string;
14
14
  relation_type: string;
15
15
  }) => StatementResult;
16
- //# sourceMappingURL=relationships.d.ts.map
@@ -1,78 +1,75 @@
1
- import { executeAll, executeGet, executeRun, mapRowToRelationship, prepareCached, toSafeInteger, withImmediateTransaction, } from './db.js';
2
- const findMemoryIdByHash = (hash) => {
3
- const stmtFindMemoryIdByHash = prepareCached('SELECT id FROM memories WHERE hash = ?');
4
- const row = executeGet(stmtFindMemoryIdByHash, hash);
5
- if (!row)
6
- return undefined;
7
- return toSafeInteger(row.id, 'id');
8
- };
9
- const requireMemoryId = (hash) => {
10
- const id = findMemoryIdByHash(hash);
11
- if (id === undefined) {
12
- throw new Error(`Memory not found: ${hash}`);
13
- }
14
- return id;
15
- };
1
+ import { mapRowToRelationship, requireMemoryIdByHash, sqlAll, sqlGet, sqlRun, toSafeInteger, withImmediateTransaction, } from './db.js';
2
+ const buildNotFoundMessage = (hash) => `Memory not found: ${hash}`;
16
3
  export const createRelationship = (input) => withImmediateTransaction(() => {
17
- const fromId = requireMemoryId(input.from_hash);
18
- const toId = requireMemoryId(input.to_hash);
4
+ const fromId = requireMemoryIdByHash(input.from_hash, buildNotFoundMessage(input.from_hash));
5
+ const toId = requireMemoryIdByHash(input.to_hash, buildNotFoundMessage(input.to_hash));
19
6
  if (fromId === toId) {
20
7
  throw new Error('Cannot create self-referential relationship');
21
8
  }
22
- const stmtInsertRelationship = prepareCached(`
23
- INSERT OR IGNORE INTO relationships (from_memory_id, to_memory_id, relation_type)
24
- VALUES (?, ?, ?)
25
- RETURNING id
26
- `);
27
- const inserted = executeGet(stmtInsertRelationship, fromId, toId, input.relation_type);
9
+ const inserted = sqlGet `
10
+ INSERT OR IGNORE INTO relationships
11
+ (from_memory_id, to_memory_id, relation_type)
12
+ VALUES (${fromId}, ${toId}, ${input.relation_type})
13
+ RETURNING id
14
+ `;
28
15
  if (inserted) {
29
16
  return { id: toSafeInteger(inserted.id, 'id'), isNew: true };
30
17
  }
31
- // Relationship already exists, find its ID
32
- const stmtFindRelationshipId = prepareCached(`
33
- SELECT id FROM relationships
34
- WHERE from_memory_id = ? AND to_memory_id = ? AND relation_type = ?
35
- `);
36
- const existing = executeGet(stmtFindRelationshipId, fromId, toId, input.relation_type);
18
+ const existing = sqlGet `
19
+ SELECT id FROM relationships
20
+ WHERE from_memory_id = ${fromId}
21
+ AND to_memory_id = ${toId}
22
+ AND relation_type = ${input.relation_type}
23
+ `;
37
24
  if (!existing) {
38
25
  throw new Error('Failed to resolve relationship id');
39
26
  }
40
27
  return { id: toSafeInteger(existing.id, 'id'), isNew: false };
41
28
  });
42
- // Query that joins with memories to get hashes instead of IDs
43
- const buildGetRelationshipsQuery = (direction) => {
44
- const baseSelect = `
45
- SELECT r.id, r.relation_type, r.created_at,
46
- mf.hash as from_hash, mt.hash as to_hash
47
- FROM relationships r
48
- JOIN memories mf ON r.from_memory_id = mf.id
49
- JOIN memories mt ON r.to_memory_id = mt.id
50
- `;
51
- const orderBy = ' ORDER BY r.relation_type, mf.hash, mt.hash, r.created_at, r.id';
52
- switch (direction) {
53
- case 'outgoing':
54
- return `${baseSelect} WHERE mf.hash = ?${orderBy}`;
55
- case 'incoming':
56
- return `${baseSelect} WHERE mt.hash = ?${orderBy}`;
57
- case 'both':
58
- return `${baseSelect} WHERE mf.hash = ? OR mt.hash = ?${orderBy}`;
59
- }
60
- };
61
29
  export const getRelationships = (input) => {
62
30
  const direction = input.direction ?? 'both';
63
- const stmt = prepareCached(buildGetRelationshipsQuery(direction));
64
- const params = direction === 'both' ? [input.hash, input.hash] : [input.hash];
65
- const rows = executeAll(stmt, ...params);
31
+ const rows = (() => {
32
+ switch (direction) {
33
+ case 'outgoing':
34
+ return sqlAll `
35
+ SELECT r.id, r.relation_type, r.created_at,
36
+ mf.hash as from_hash, mt.hash as to_hash
37
+ FROM relationships r
38
+ JOIN memories mf ON r.from_memory_id = mf.id
39
+ JOIN memories mt ON r.to_memory_id = mt.id
40
+ WHERE mf.hash = ${input.hash}
41
+ ORDER BY r.relation_type, mf.hash, mt.hash, r.created_at, r.id
42
+ `;
43
+ case 'incoming':
44
+ return sqlAll `
45
+ SELECT r.id, r.relation_type, r.created_at,
46
+ mf.hash as from_hash, mt.hash as to_hash
47
+ FROM relationships r
48
+ JOIN memories mf ON r.from_memory_id = mf.id
49
+ JOIN memories mt ON r.to_memory_id = mt.id
50
+ WHERE mt.hash = ${input.hash}
51
+ ORDER BY r.relation_type, mf.hash, mt.hash, r.created_at, r.id
52
+ `;
53
+ case 'both':
54
+ return sqlAll `
55
+ SELECT r.id, r.relation_type, r.created_at,
56
+ mf.hash as from_hash, mt.hash as to_hash
57
+ FROM relationships r
58
+ JOIN memories mf ON r.from_memory_id = mf.id
59
+ JOIN memories mt ON r.to_memory_id = mt.id
60
+ WHERE mf.hash = ${input.hash} OR mt.hash = ${input.hash}
61
+ ORDER BY r.relation_type, mf.hash, mt.hash, r.created_at, r.id
62
+ `;
63
+ }
64
+ })();
66
65
  return rows.map(mapRowToRelationship);
67
66
  };
68
67
  export const deleteRelationship = (input) => {
69
- const stmtDeleteRelationship = prepareCached(`
70
- DELETE FROM relationships
71
- WHERE from_memory_id = (SELECT id FROM memories WHERE hash = ?)
72
- AND to_memory_id = (SELECT id FROM memories WHERE hash = ?)
73
- AND relation_type = ?
74
- `);
75
- const result = executeRun(stmtDeleteRelationship, input.from_hash, input.to_hash, input.relation_type);
68
+ const result = sqlRun `
69
+ DELETE FROM relationships
70
+ WHERE from_memory_id = (SELECT id FROM memories WHERE hash = ${input.from_hash})
71
+ AND to_memory_id = (SELECT id FROM memories WHERE hash = ${input.to_hash})
72
+ AND relation_type = ${input.relation_type}
73
+ `;
76
74
  return { changes: toSafeInteger(result.changes, 'changes') };
77
75
  };
78
- //# sourceMappingURL=relationships.js.map
@@ -8,4 +8,3 @@ export declare const recallMemories: (input: {
8
8
  depth?: number;
9
9
  }, signal?: AbortSignal) => RecallResult;
10
10
  export {};
11
- //# sourceMappingURL=search.d.ts.map