@j0hanz/memdb 1.2.9 → 1.3.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.
- package/README.md +110 -287
- package/dist/assets/logo.svg +12 -0
- package/dist/config.d.ts +0 -1
- package/dist/config.js +0 -1
- package/dist/core/db.d.ts +6 -4
- package/dist/core/db.js +79 -39
- package/dist/core/memory-read.d.ts +1 -2
- package/dist/core/memory-read.js +28 -14
- package/dist/core/memory-write.d.ts +1 -2
- package/dist/core/memory-write.js +59 -82
- package/dist/core/relationships.d.ts +0 -1
- package/dist/core/relationships.js +4 -20
- package/dist/core/search.d.ts +0 -1
- package/dist/core/search.js +85 -101
- package/dist/index.d.ts +0 -1
- package/dist/index.js +36 -4
- package/dist/instructions.md +35 -27
- package/dist/logger.d.ts +0 -1
- package/dist/logger.js +0 -1
- package/dist/protocol-version-guard.d.ts +0 -1
- package/dist/protocol-version-guard.js +0 -1
- package/dist/schemas.d.ts +0 -1
- package/dist/schemas.js +12 -9
- package/dist/stdio-transport.d.ts +0 -1
- package/dist/stdio-transport.js +0 -1
- package/dist/tools.d.ts +1 -2
- package/dist/tools.js +194 -214
- package/dist/types.d.ts +0 -1
- package/dist/types.js +0 -1
- package/package.json +19 -17
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js.map +0 -1
- package/dist/core/db.d.ts.map +0 -1
- package/dist/core/db.js.map +0 -1
- package/dist/core/memory-read.d.ts.map +0 -1
- package/dist/core/memory-read.js.map +0 -1
- package/dist/core/memory-write.d.ts.map +0 -1
- package/dist/core/memory-write.js.map +0 -1
- package/dist/core/relationships.d.ts.map +0 -1
- package/dist/core/relationships.js.map +0 -1
- package/dist/core/search.d.ts.map +0 -1
- package/dist/core/search.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/logger.d.ts.map +0 -1
- package/dist/logger.js.map +0 -1
- package/dist/protocol-version-guard.d.ts.map +0 -1
- package/dist/protocol-version-guard.js.map +0 -1
- package/dist/schemas.d.ts.map +0 -1
- package/dist/schemas.js.map +0 -1
- package/dist/stdio-transport.d.ts.map +0 -1
- package/dist/stdio-transport.js.map +0 -1
- package/dist/tools.d.ts.map +0 -1
- package/dist/tools.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
package/dist/core/db.js
CHANGED
|
@@ -92,9 +92,6 @@ const enableDefensiveMode = (database) => {
|
|
|
92
92
|
extended.enableDefensive(true);
|
|
93
93
|
}
|
|
94
94
|
};
|
|
95
|
-
const isInTransaction = (database) => {
|
|
96
|
-
return database.isTransaction;
|
|
97
|
-
};
|
|
98
95
|
const initializeSchema = (database) => {
|
|
99
96
|
database.exec(SCHEMA_SQL);
|
|
100
97
|
database.exec(FTS_SYNC_SQL);
|
|
@@ -129,44 +126,59 @@ export const getDb = () => {
|
|
|
129
126
|
}
|
|
130
127
|
return dbInstance;
|
|
131
128
|
};
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
129
|
+
const MAX_CACHED_STATEMENTS = 200;
|
|
130
|
+
class LruStatementCache {
|
|
131
|
+
maxSize;
|
|
132
|
+
cache = new Map();
|
|
133
|
+
constructor(maxSize) {
|
|
134
|
+
this.maxSize = maxSize;
|
|
135
|
+
}
|
|
136
|
+
get(sql) {
|
|
137
|
+
const hit = this.cache.get(sql);
|
|
138
|
+
if (!hit)
|
|
139
|
+
return undefined;
|
|
140
|
+
// Refresh LRU order
|
|
141
|
+
this.cache.delete(sql);
|
|
142
|
+
this.cache.set(sql, hit);
|
|
143
|
+
return hit;
|
|
144
|
+
}
|
|
145
|
+
set(sql, stmt) {
|
|
146
|
+
this.cache.set(sql, stmt);
|
|
147
|
+
if (this.cache.size <= this.maxSize)
|
|
148
|
+
return;
|
|
149
|
+
const oldestKey = this.cache.keys().next().value;
|
|
150
|
+
if (oldestKey)
|
|
151
|
+
this.cache.delete(oldestKey);
|
|
137
152
|
}
|
|
153
|
+
clear() {
|
|
154
|
+
this.cache.clear();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const statementCache = new LruStatementCache(MAX_CACHED_STATEMENTS);
|
|
158
|
+
export const closeDb = () => {
|
|
159
|
+
if (!dbInstance?.isOpen)
|
|
160
|
+
return;
|
|
161
|
+
dbInstance.close();
|
|
162
|
+
dbInstance = undefined;
|
|
163
|
+
statementCache.clear();
|
|
138
164
|
};
|
|
139
|
-
const MAX_CACHED_STATEMENTS = 200;
|
|
140
|
-
const statementCache = new Map();
|
|
141
165
|
export const prepareCached = (sql) => {
|
|
142
166
|
const cached = statementCache.get(sql);
|
|
143
|
-
if (cached)
|
|
144
|
-
statementCache.delete(sql);
|
|
145
|
-
statementCache.set(sql, cached);
|
|
167
|
+
if (cached)
|
|
146
168
|
return cached;
|
|
147
|
-
}
|
|
148
169
|
const stmt = getDb().prepare(sql);
|
|
149
170
|
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
171
|
return stmt;
|
|
156
172
|
};
|
|
157
|
-
const isDbRow = (value) =>
|
|
158
|
-
return typeof value === 'object' && value !== null;
|
|
159
|
-
};
|
|
173
|
+
const isDbRow = (value) => typeof value === 'object' && value !== null;
|
|
160
174
|
const assertDbRow = (value) => {
|
|
161
|
-
if (!isDbRow(value))
|
|
175
|
+
if (!isDbRow(value))
|
|
162
176
|
throw new Error('Invalid row');
|
|
163
|
-
}
|
|
164
177
|
return value;
|
|
165
178
|
};
|
|
166
179
|
const toDbRowArray = (value) => {
|
|
167
|
-
if (!Array.isArray(value))
|
|
180
|
+
if (!Array.isArray(value))
|
|
168
181
|
throw new Error('Expected rows array');
|
|
169
|
-
}
|
|
170
182
|
return value.map(assertDbRow);
|
|
171
183
|
};
|
|
172
184
|
const toDbRowOrUndefined = (value) => {
|
|
@@ -186,12 +198,11 @@ const toRunResult = (value) => {
|
|
|
186
198
|
return { changes };
|
|
187
199
|
};
|
|
188
200
|
export const executeAll = (stmt, ...params) => toDbRowArray(stmt.all(...params));
|
|
189
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
|
|
190
201
|
export const executeGet = (stmt, ...params) => toDbRowOrUndefined(stmt.get(...params));
|
|
191
202
|
export const executeRun = (stmt, ...params) => toRunResult(stmt.run(...params));
|
|
192
203
|
export const withImmediateTransaction = (operation) => {
|
|
193
204
|
const db = getDb();
|
|
194
|
-
if (
|
|
205
|
+
if (db.isTransaction) {
|
|
195
206
|
throw new Error('Cannot start nested transaction');
|
|
196
207
|
}
|
|
197
208
|
db.exec('BEGIN IMMEDIATE');
|
|
@@ -205,6 +216,24 @@ export const withImmediateTransaction = (operation) => {
|
|
|
205
216
|
throw err;
|
|
206
217
|
}
|
|
207
218
|
};
|
|
219
|
+
const SAVEPOINT_NAME_PATTERN = /^[A-Za-z_]\w*$/;
|
|
220
|
+
export const withSavepoint = (name, operation) => {
|
|
221
|
+
if (!SAVEPOINT_NAME_PATTERN.test(name)) {
|
|
222
|
+
throw new Error(`Invalid savepoint name: ${name}`);
|
|
223
|
+
}
|
|
224
|
+
const db = getDb();
|
|
225
|
+
db.exec(`SAVEPOINT ${name}`);
|
|
226
|
+
try {
|
|
227
|
+
const result = operation();
|
|
228
|
+
db.exec(`RELEASE ${name}`);
|
|
229
|
+
return result;
|
|
230
|
+
}
|
|
231
|
+
catch (err) {
|
|
232
|
+
db.exec(`ROLLBACK TO ${name}`);
|
|
233
|
+
db.exec(`RELEASE ${name}`);
|
|
234
|
+
throw err;
|
|
235
|
+
}
|
|
236
|
+
};
|
|
208
237
|
const createFieldError = (field) => new Error(`Invalid ${field}`);
|
|
209
238
|
const assertFiniteNumber = (value, field) => {
|
|
210
239
|
if (!Number.isFinite(value)) {
|
|
@@ -233,14 +262,6 @@ const toString = (value, field) => {
|
|
|
233
262
|
return value;
|
|
234
263
|
throw createFieldError(field);
|
|
235
264
|
};
|
|
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);
|
|
241
|
-
}
|
|
242
|
-
return str;
|
|
243
|
-
};
|
|
244
265
|
const toOptionalString = (value, field) => {
|
|
245
266
|
if (value === null || value === undefined)
|
|
246
267
|
return undefined;
|
|
@@ -251,6 +272,13 @@ const toOptionalNumber = (value, field) => {
|
|
|
251
272
|
return undefined;
|
|
252
273
|
return toNumber(value, field);
|
|
253
274
|
};
|
|
275
|
+
const isMemoryType = (value) => MEMORY_TYPES.includes(value);
|
|
276
|
+
const toMemoryType = (value, field) => {
|
|
277
|
+
const str = toString(value, field);
|
|
278
|
+
if (!isMemoryType(str))
|
|
279
|
+
throw createFieldError(field);
|
|
280
|
+
return str;
|
|
281
|
+
};
|
|
254
282
|
export const mapRowToMemory = (row, tags = []) => ({
|
|
255
283
|
id: toSafeInteger(row.id, 'id'),
|
|
256
284
|
content: toString(row.content, 'content'),
|
|
@@ -273,6 +301,19 @@ export const mapRowToRelationship = (row) => ({
|
|
|
273
301
|
relation_type: toString(row.relation_type, 'relation_type'),
|
|
274
302
|
created_at: toString(row.created_at, 'created_at'),
|
|
275
303
|
});
|
|
304
|
+
export const findMemoryIdByHash = (hash) => {
|
|
305
|
+
const stmt = prepareCached('SELECT id FROM memories WHERE hash = ?');
|
|
306
|
+
const row = executeGet(stmt, hash);
|
|
307
|
+
if (!row)
|
|
308
|
+
return undefined;
|
|
309
|
+
return toSafeInteger(row.id, 'id');
|
|
310
|
+
};
|
|
311
|
+
export const requireMemoryIdByHash = (hash, message = `Memory not found: ${hash}`) => {
|
|
312
|
+
const id = findMemoryIdByHash(hash);
|
|
313
|
+
if (id === undefined)
|
|
314
|
+
throw new Error(message);
|
|
315
|
+
return id;
|
|
316
|
+
};
|
|
276
317
|
const dedupeIds = (ids) => {
|
|
277
318
|
const seen = new Set();
|
|
278
319
|
const unique = [];
|
|
@@ -296,8 +337,8 @@ export const loadTagsForMemoryIds = (memoryIds) => {
|
|
|
296
337
|
const uniqueIds = dedupeIds(memoryIds);
|
|
297
338
|
if (uniqueIds.length === 0)
|
|
298
339
|
return new Map();
|
|
299
|
-
const
|
|
300
|
-
const rows = executeAll(
|
|
340
|
+
const stmt = prepareCached('SELECT memory_id, tag FROM tags WHERE memory_id IN (SELECT value FROM json_each(?)) ORDER BY memory_id, tag');
|
|
341
|
+
const rows = executeAll(stmt, JSON.stringify(uniqueIds));
|
|
301
342
|
const tagsById = new Map();
|
|
302
343
|
for (const row of rows) {
|
|
303
344
|
const memoryId = toSafeInteger(row.memory_id, 'memory_id');
|
|
@@ -306,4 +347,3 @@ export const loadTagsForMemoryIds = (memoryIds) => {
|
|
|
306
347
|
}
|
|
307
348
|
return tagsById;
|
|
308
349
|
};
|
|
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
|
package/dist/core/memory-read.js
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
import { executeGet, executeRun, loadTagsForMemoryIds, mapRowToMemory, prepareCached, toSafeInteger, withImmediateTransaction, } from './db.js';
|
|
2
|
+
const throwIfAborted = (signal) => {
|
|
3
|
+
if (!signal)
|
|
4
|
+
return;
|
|
5
|
+
if (typeof signal.throwIfAborted === 'function') {
|
|
6
|
+
signal.throwIfAborted();
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
if (signal.aborted) {
|
|
10
|
+
throw new Error('Operation aborted');
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
const loadMemoryRowByHashAndTouch = (hash) => {
|
|
14
|
+
const stmt = prepareCached('UPDATE memories SET accessed_at = CURRENT_TIMESTAMP WHERE hash = ? RETURNING *');
|
|
15
|
+
return executeGet(stmt, hash);
|
|
16
|
+
};
|
|
2
17
|
export const getMemory = (hash) => {
|
|
3
18
|
return withImmediateTransaction(() => {
|
|
4
|
-
const
|
|
5
|
-
executeRun(stmtTouchMemoryByHash, hash);
|
|
6
|
-
const stmtGetMemoryByHash = prepareCached('SELECT * FROM memories WHERE hash = ?');
|
|
7
|
-
const row = executeGet(stmtGetMemoryByHash, hash);
|
|
19
|
+
const row = loadMemoryRowByHashAndTouch(hash);
|
|
8
20
|
if (!row)
|
|
9
21
|
return undefined;
|
|
10
22
|
const id = toSafeInteger(row.id, 'id');
|
|
@@ -13,32 +25,35 @@ export const getMemory = (hash) => {
|
|
|
13
25
|
});
|
|
14
26
|
};
|
|
15
27
|
export const deleteMemory = (hash) => {
|
|
16
|
-
const
|
|
17
|
-
const result = executeRun(
|
|
28
|
+
const stmt = prepareCached('DELETE FROM memories WHERE hash = ?');
|
|
29
|
+
const result = executeRun(stmt, hash);
|
|
18
30
|
return { changes: toSafeInteger(result.changes, 'changes') };
|
|
19
31
|
};
|
|
20
32
|
const deleteMemoryForBatch = (hash) => {
|
|
21
33
|
try {
|
|
22
34
|
const result = deleteMemory(hash);
|
|
23
35
|
const deleted = result.changes > 0;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
: {
|
|
36
|
+
if (!deleted) {
|
|
37
|
+
return {
|
|
27
38
|
item: { hash, deleted: false, error: 'Memory not found' },
|
|
28
39
|
succeeded: false,
|
|
29
40
|
};
|
|
41
|
+
}
|
|
42
|
+
return { item: { hash, deleted: true }, succeeded: true };
|
|
30
43
|
}
|
|
31
44
|
catch (err) {
|
|
32
45
|
const message = err instanceof Error ? err.message : 'Unknown error';
|
|
33
46
|
return { item: { hash, deleted: false, error: message }, succeeded: false };
|
|
34
47
|
}
|
|
35
48
|
};
|
|
36
|
-
export const deleteMemories = (hashes) => {
|
|
37
|
-
const results = [];
|
|
38
|
-
let succeeded = 0;
|
|
39
|
-
let failed = 0;
|
|
49
|
+
export const deleteMemories = (hashes, signal) => {
|
|
40
50
|
return withImmediateTransaction(() => {
|
|
51
|
+
const results = [];
|
|
52
|
+
let succeeded = 0;
|
|
53
|
+
let failed = 0;
|
|
54
|
+
throwIfAborted(signal);
|
|
41
55
|
for (const hash of hashes) {
|
|
56
|
+
throwIfAborted(signal);
|
|
42
57
|
const outcome = deleteMemoryForBatch(hash);
|
|
43
58
|
results.push(outcome.item);
|
|
44
59
|
succeeded += outcome.succeeded ? 1 : 0;
|
|
@@ -78,4 +93,3 @@ export const getStats = () => {
|
|
|
78
93
|
newestMemory: toDateString(dateRow?.newest),
|
|
79
94
|
};
|
|
80
95
|
};
|
|
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
|
|
@@ -1,17 +1,25 @@
|
|
|
1
1
|
import crypto from 'node:crypto';
|
|
2
|
-
import { executeGet, executeRun, prepareCached, toSafeInteger, withImmediateTransaction, } from './db.js';
|
|
2
|
+
import { executeGet, executeRun, findMemoryIdByHash, prepareCached, toSafeInteger, withImmediateTransaction, withSavepoint, } from './db.js';
|
|
3
3
|
const MAX_TAGS = 100;
|
|
4
4
|
const TAG_PATTERN = /^\S+$/;
|
|
5
|
+
const throwIfAborted = (signal) => {
|
|
6
|
+
if (!signal)
|
|
7
|
+
return;
|
|
8
|
+
if (typeof signal.throwIfAborted === 'function') {
|
|
9
|
+
signal.throwIfAborted();
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
if (signal.aborted) {
|
|
13
|
+
throw new Error('Operation aborted');
|
|
14
|
+
}
|
|
15
|
+
};
|
|
5
16
|
const validateTag = (tag) => {
|
|
6
|
-
if (tag.length === 0)
|
|
17
|
+
if (tag.length === 0)
|
|
7
18
|
throw new Error('Tag must be at least 1 character');
|
|
8
|
-
|
|
9
|
-
if (tag.length > 50) {
|
|
19
|
+
if (tag.length > 50)
|
|
10
20
|
throw new Error('Tag exceeds 50 characters');
|
|
11
|
-
|
|
12
|
-
if (!TAG_PATTERN.test(tag)) {
|
|
21
|
+
if (!TAG_PATTERN.test(tag))
|
|
13
22
|
throw new Error('Tag must not contain whitespace');
|
|
14
|
-
}
|
|
15
23
|
};
|
|
16
24
|
const validateTagCount = (tags, maxTags) => {
|
|
17
25
|
if (tags.length > maxTags) {
|
|
@@ -32,38 +40,30 @@ const normalizeTags = (tags, maxTags) => {
|
|
|
32
40
|
validateTagCount(tags, maxTags);
|
|
33
41
|
return dedupeTags(tags);
|
|
34
42
|
};
|
|
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
43
|
const insertTags = (memoryId, tags) => {
|
|
43
44
|
if (tags.length === 0)
|
|
44
45
|
return;
|
|
45
|
-
const
|
|
46
|
-
executeRun(
|
|
46
|
+
const stmt = prepareCached('INSERT OR IGNORE INTO tags (memory_id, tag) SELECT ?, value FROM json_each(?)');
|
|
47
|
+
executeRun(stmt, memoryId, JSON.stringify(tags));
|
|
47
48
|
};
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (id === undefined) {
|
|
53
|
-
throw new Error('Failed to resolve memory id');
|
|
54
|
-
}
|
|
55
|
-
return id;
|
|
49
|
+
const replaceTags = (memoryId, tags) => {
|
|
50
|
+
const stmtDelete = prepareCached('DELETE FROM tags WHERE memory_id = ?');
|
|
51
|
+
executeRun(stmtDelete, memoryId);
|
|
52
|
+
insertTags(memoryId, normalizeTags(tags, MAX_TAGS));
|
|
56
53
|
};
|
|
54
|
+
const buildHash = (content) => crypto.createHash('sha256').update(content).digest('hex');
|
|
57
55
|
const resolveMemoryId = (content, hash, importance, memoryType) => {
|
|
58
|
-
const
|
|
59
|
-
const inserted = executeGet(
|
|
56
|
+
const stmtInsert = prepareCached('INSERT OR IGNORE INTO memories (content, hash, importance, memory_type) VALUES (?, ?, ?, ?) RETURNING id');
|
|
57
|
+
const inserted = executeGet(stmtInsert, content, hash, importance, memoryType);
|
|
60
58
|
if (inserted) {
|
|
61
59
|
return { id: toSafeInteger(inserted.id, 'id'), isNew: true };
|
|
62
60
|
}
|
|
63
|
-
const
|
|
64
|
-
|
|
61
|
+
const existingId = findMemoryIdByHash(hash);
|
|
62
|
+
if (existingId === undefined) {
|
|
63
|
+
throw new Error('Failed to resolve memory id');
|
|
64
|
+
}
|
|
65
|
+
return { id: existingId, isNew: false };
|
|
65
66
|
};
|
|
66
|
-
export const createMemory = (input) => withImmediateTransaction(() => createMemoryInTransaction(input));
|
|
67
67
|
const createMemoryInTransaction = (input) => {
|
|
68
68
|
const { content, tags = [], importance = 0, memory_type: memoryType = 'general', } = input;
|
|
69
69
|
const hash = buildHash(content);
|
|
@@ -72,62 +72,44 @@ const createMemoryInTransaction = (input) => {
|
|
|
72
72
|
insertTags(id, normalizedTags);
|
|
73
73
|
return { id, hash, isNew };
|
|
74
74
|
};
|
|
75
|
-
export const
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
const withSavepoint = (name, fn) => {
|
|
79
|
-
executeRun(prepareCached(`SAVEPOINT ${name}`));
|
|
75
|
+
export const createMemory = (input) => withImmediateTransaction(() => createMemoryInTransaction(input));
|
|
76
|
+
const createMemoryWithSavepoint = (index, item, signal) => {
|
|
77
|
+
const savepointName = `mem_item_${index}`;
|
|
80
78
|
try {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
return
|
|
79
|
+
throwIfAborted(signal);
|
|
80
|
+
const created = withSavepoint(savepointName, () => createMemoryInTransaction(item));
|
|
81
|
+
return {
|
|
82
|
+
ok: true,
|
|
83
|
+
index,
|
|
84
|
+
hash: created.hash,
|
|
85
|
+
isNew: created.isNew,
|
|
86
|
+
};
|
|
84
87
|
}
|
|
85
88
|
catch (err) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
throw err;
|
|
89
|
+
const message = err instanceof Error ? err.message : 'Unknown error';
|
|
90
|
+
return { ok: false, index, error: message };
|
|
89
91
|
}
|
|
90
92
|
};
|
|
91
|
-
const createMemoriesInTransaction = (items) => {
|
|
93
|
+
const createMemoriesInTransaction = (items, signal) => {
|
|
92
94
|
const results = [];
|
|
93
95
|
let succeeded = 0;
|
|
94
96
|
let failed = 0;
|
|
97
|
+
throwIfAborted(signal);
|
|
95
98
|
for (let i = 0; i < items.length; i++) {
|
|
99
|
+
throwIfAborted(signal);
|
|
96
100
|
const item = items[i];
|
|
97
101
|
if (!item)
|
|
98
102
|
continue;
|
|
99
|
-
const result = createMemoryWithSavepoint(i, item);
|
|
103
|
+
const result = createMemoryWithSavepoint(i, item, signal);
|
|
100
104
|
results.push(result);
|
|
101
|
-
if (result.ok)
|
|
105
|
+
if (result.ok)
|
|
102
106
|
succeeded++;
|
|
103
|
-
|
|
104
|
-
else {
|
|
107
|
+
else
|
|
105
108
|
failed++;
|
|
106
|
-
}
|
|
107
109
|
}
|
|
108
110
|
return { results, succeeded, failed };
|
|
109
111
|
};
|
|
110
|
-
const
|
|
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
|
-
};
|
|
112
|
+
export const createMemories = (items, signal) => withImmediateTransaction(() => createMemoriesInTransaction(items, signal));
|
|
131
113
|
const assertNoDuplicateOnUpdate = (oldHash, newHash) => {
|
|
132
114
|
if (newHash === oldHash)
|
|
133
115
|
return;
|
|
@@ -136,22 +118,17 @@ const assertNoDuplicateOnUpdate = (oldHash, newHash) => {
|
|
|
136
118
|
throw new Error('Content already exists as another memory');
|
|
137
119
|
}
|
|
138
120
|
};
|
|
139
|
-
|
|
121
|
+
const updateMemoryInTransaction = (hash, options) => {
|
|
140
122
|
const memoryId = findMemoryIdByHash(hash);
|
|
141
123
|
if (memoryId === undefined)
|
|
142
124
|
throw new Error('Memory not found');
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
if (options.tags !== undefined) {
|
|
152
|
-
replaceTags(memoryId, options.tags);
|
|
153
|
-
}
|
|
154
|
-
return { updated: true, oldHash: hash, newHash };
|
|
155
|
-
});
|
|
125
|
+
const newHash = buildHash(options.content);
|
|
126
|
+
assertNoDuplicateOnUpdate(hash, newHash);
|
|
127
|
+
const stmtUpdateContent = prepareCached('UPDATE memories SET content = ?, hash = ? WHERE id = ?');
|
|
128
|
+
executeRun(stmtUpdateContent, options.content, newHash, memoryId);
|
|
129
|
+
if (options.tags !== undefined) {
|
|
130
|
+
replaceTags(memoryId, options.tags);
|
|
131
|
+
}
|
|
132
|
+
return { updated: true, oldHash: hash, newHash };
|
|
156
133
|
};
|
|
157
|
-
|
|
134
|
+
export const updateMemory = (hash, options) => withImmediateTransaction(() => updateMemoryInTransaction(hash, options));
|
|
@@ -1,21 +1,8 @@
|
|
|
1
|
-
import { executeAll, executeGet, executeRun, mapRowToRelationship, prepareCached, toSafeInteger, withImmediateTransaction, } from './db.js';
|
|
2
|
-
const
|
|
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 { executeAll, executeGet, executeRun, mapRowToRelationship, prepareCached, requireMemoryIdByHash, toSafeInteger, withImmediateTransaction, } from './db.js';
|
|
2
|
+
const buildNotFoundMessage = (hash) => `Memory not found: ${hash}`;
|
|
16
3
|
export const createRelationship = (input) => withImmediateTransaction(() => {
|
|
17
|
-
const fromId =
|
|
18
|
-
const toId =
|
|
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
|
}
|
|
@@ -28,7 +15,6 @@ export const createRelationship = (input) => withImmediateTransaction(() => {
|
|
|
28
15
|
if (inserted) {
|
|
29
16
|
return { id: toSafeInteger(inserted.id, 'id'), isNew: true };
|
|
30
17
|
}
|
|
31
|
-
// Relationship already exists, find its ID
|
|
32
18
|
const stmtFindRelationshipId = prepareCached(`
|
|
33
19
|
SELECT id FROM relationships
|
|
34
20
|
WHERE from_memory_id = ? AND to_memory_id = ? AND relation_type = ?
|
|
@@ -39,7 +25,6 @@ export const createRelationship = (input) => withImmediateTransaction(() => {
|
|
|
39
25
|
}
|
|
40
26
|
return { id: toSafeInteger(existing.id, 'id'), isNew: false };
|
|
41
27
|
});
|
|
42
|
-
// Query that joins with memories to get hashes instead of IDs
|
|
43
28
|
const buildGetRelationshipsQuery = (direction) => {
|
|
44
29
|
const baseSelect = `
|
|
45
30
|
SELECT r.id, r.relation_type, r.created_at,
|
|
@@ -75,4 +60,3 @@ export const deleteRelationship = (input) => {
|
|
|
75
60
|
const result = executeRun(stmtDeleteRelationship, input.from_hash, input.to_hash, input.relation_type);
|
|
76
61
|
return { changes: toSafeInteger(result.changes, 'changes') };
|
|
77
62
|
};
|
|
78
|
-
//# sourceMappingURL=relationships.js.map
|
package/dist/core/search.d.ts
CHANGED