@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.
- package/README.md +663 -382
- package/dist/assets/logo.svg +12 -0
- package/dist/async-context.d.ts +6 -0
- package/dist/async-context.js +4 -0
- package/dist/config.d.ts +0 -1
- package/dist/config.js +10 -5
- package/dist/core/abort.d.ts +1 -0
- package/dist/core/abort.js +3 -0
- package/dist/core/db.d.ts +9 -5
- package/dist/core/db.js +205 -129
- package/dist/core/memory-read.d.ts +1 -2
- package/dist/core/memory-read.js +18 -21
- package/dist/core/memory-write.d.ts +1 -2
- package/dist/core/memory-write.js +69 -85
- package/dist/core/relationships.d.ts +0 -1
- package/dist/core/relationships.js +56 -59
- package/dist/core/search.d.ts +0 -1
- package/dist/core/search.js +141 -85
- package/dist/error-utils.d.ts +1 -0
- package/dist/error-utils.js +28 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +63 -16
- package/dist/instructions.md +38 -34
- package/dist/logger.d.ts +0 -1
- package/dist/logger.js +6 -3
- package/dist/protocol-version-guard.d.ts +0 -1
- package/dist/protocol-version-guard.js +17 -2
- 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 +6 -16
- package/dist/tools.d.ts +1 -2
- package/dist/tools.js +222 -222
- package/dist/types.d.ts +0 -1
- package/dist/types.js +0 -1
- package/package.json +20 -18
- 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
|
@@ -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>
|
package/dist/config.d.ts
CHANGED
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.
|
|
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
|
-
|
|
40
|
+
const normalizedEnvPath = envPath.trim();
|
|
41
|
+
if (normalizedEnvPath === ':memory:')
|
|
41
42
|
return ':memory:';
|
|
42
|
-
if (
|
|
43
|
-
|
|
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;
|
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:
|
|
10
|
-
export declare const
|
|
11
|
-
export declare const
|
|
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
|
-
|
|
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
|
|
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(
|
|
74
|
-
},
|
|
80
|
+
reject(new Error('Database directory creation timed out'));
|
|
81
|
+
}, timeoutMs);
|
|
75
82
|
});
|
|
76
83
|
try {
|
|
77
|
-
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
140
|
-
|
|
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
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
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
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
|
237
|
-
const
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
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
|
-
|
|
308
|
+
if (!Number.isFinite(relevance))
|
|
309
|
+
throw new Error('Invalid relevance');
|
|
310
|
+
return {
|
|
311
|
+
...memory,
|
|
312
|
+
relevance,
|
|
313
|
+
};
|
|
243
314
|
};
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
|
250
|
-
|
|
336
|
+
export const findMemoryIdByHash = (hash) => {
|
|
337
|
+
const row = sqlGet `SELECT id FROM memories WHERE hash = ${hash}`;
|
|
338
|
+
if (!row)
|
|
251
339
|
return undefined;
|
|
252
|
-
|
|
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
|
|
255
|
-
id
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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 =
|
|
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
|
|
300
|
-
|
|
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
|
|
304
|
-
const
|
|
305
|
-
|
|
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
|
package/dist/core/memory-read.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import {
|
|
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
|
|
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
|
|
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
|
-
|
|
25
|
-
|
|
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
|
|
61
|
-
const
|
|
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
|
|
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
|