@j0hanz/memdb 1.0.11 → 1.1.1
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 +82 -120
- package/dist/config.d.ts +4 -0
- package/dist/config.js +13 -0
- package/dist/core/database-schema.d.ts +0 -1
- package/dist/core/database-schema.js +0 -1
- package/dist/core/database.d.ts +0 -1
- package/dist/core/database.js +0 -1
- package/dist/core/db.d.ts +16 -0
- package/dist/core/db.js +232 -0
- package/dist/core/memory-create.d.ts +0 -1
- package/dist/core/memory-create.js +13 -7
- package/dist/core/memory-db.d.ts +0 -1
- package/dist/core/memory-db.js +0 -1
- package/dist/core/memory-read.d.ts +2 -2
- package/dist/core/memory-read.js +32 -4
- package/dist/core/memory-relations.d.ts +0 -1
- package/dist/core/memory-relations.js +0 -1
- package/dist/core/memory-search.d.ts +8 -7
- package/dist/core/memory-search.js +15 -9
- package/dist/core/memory-stats.d.ts +0 -1
- package/dist/core/memory-stats.js +0 -1
- package/dist/core/memory-updates.d.ts +0 -1
- package/dist/core/memory-updates.js +1 -2
- package/dist/core/memory-write.d.ts +13 -0
- package/dist/core/memory-write.js +113 -0
- package/dist/core/relation-queries.d.ts +0 -1
- package/dist/core/relation-queries.js +0 -1
- package/dist/core/relations.d.ts +10 -0
- package/dist/core/relations.js +177 -0
- package/dist/core/row-mappers.d.ts +0 -1
- package/dist/core/row-mappers.js +0 -1
- package/dist/core/search-errors.d.ts +0 -1
- package/dist/core/search-errors.js +0 -1
- package/dist/core/search.d.ts +5 -12
- package/dist/core/search.js +77 -56
- package/dist/core/sqlite.d.ts +0 -1
- package/dist/core/sqlite.js +0 -3
- package/dist/core/tags.d.ts +0 -1
- package/dist/core/tags.js +1 -2
- package/dist/index.d.ts +0 -1
- package/dist/index.js +58 -37
- package/dist/lib/errors.d.ts +0 -1
- package/dist/lib/errors.js +0 -1
- package/dist/logger.d.ts +5 -0
- package/dist/logger.js +17 -0
- package/dist/protocol-version-guard.d.ts +17 -0
- package/dist/protocol-version-guard.js +100 -0
- package/dist/schemas/inputs.d.ts +0 -1
- package/dist/schemas/inputs.js +0 -1
- package/dist/schemas/outputs.d.ts +0 -1
- package/dist/schemas/outputs.js +0 -1
- package/dist/schemas.d.ts +28 -0
- package/dist/schemas.js +65 -0
- package/dist/tools/definitions/memory-core.d.ts +0 -1
- package/dist/tools/definitions/memory-core.js +0 -1
- package/dist/tools/definitions/memory-relations.d.ts +0 -1
- package/dist/tools/definitions/memory-relations.js +0 -1
- package/dist/tools/definitions/memory-search.d.ts +0 -1
- package/dist/tools/definitions/memory-search.js +1 -11
- package/dist/tools/definitions/memory-stats.d.ts +0 -1
- package/dist/tools/definitions/memory-stats.js +0 -1
- package/dist/tools/index.d.ts +0 -1
- package/dist/tools/index.js +0 -1
- package/dist/tools/tool-handlers.d.ts +0 -1
- package/dist/tools/tool-handlers.js +0 -1
- package/dist/tools/tool-types.d.ts +0 -1
- package/dist/tools/tool-types.js +0 -1
- package/dist/tools.d.ts +18 -0
- package/dist/tools.js +167 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/types/index.d.ts +0 -1
- package/dist/types/index.js +0 -1
- package/dist/types.d.ts +30 -0
- package/dist/types.js +1 -0
- package/dist/utils/config.d.ts +0 -1
- package/dist/utils/config.js +0 -1
- package/dist/utils/logger.d.ts +0 -1
- package/dist/utils/logger.js +0 -1
- package/dist/utils.d.ts +11 -0
- package/dist/utils.js +118 -0
- package/dist/worker/db-worker-client.d.ts +9 -0
- package/dist/worker/db-worker-client.js +93 -0
- package/dist/worker/db-worker.d.ts +1 -0
- package/dist/worker/db-worker.js +174 -0
- package/dist/worker/protocol.d.ts +9 -0
- package/dist/worker/protocol.js +14 -0
- package/package.json +8 -5
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { Worker } from 'node:worker_threads';
|
|
2
|
+
import { isWorkerResponse } from './protocol.js';
|
|
3
|
+
const resolveWorkerUrl = () => {
|
|
4
|
+
const extension = import.meta.url.endsWith('.ts') ? 'ts' : 'js';
|
|
5
|
+
return new URL(`./db-worker.${extension}`, import.meta.url);
|
|
6
|
+
};
|
|
7
|
+
const WORKER_REQUEST_TIMEOUT_MS = 30000;
|
|
8
|
+
const createWorker = () => {
|
|
9
|
+
const options = { type: 'module' };
|
|
10
|
+
return new Worker(resolveWorkerUrl(), options);
|
|
11
|
+
};
|
|
12
|
+
const rejectAll = (pending, error) => {
|
|
13
|
+
for (const entry of pending.values()) {
|
|
14
|
+
clearTimeout(entry.timeout);
|
|
15
|
+
entry.reject(error);
|
|
16
|
+
}
|
|
17
|
+
pending.clear();
|
|
18
|
+
};
|
|
19
|
+
const onWorkerMessage = (pending, value) => {
|
|
20
|
+
if (!isWorkerResponse(value))
|
|
21
|
+
return;
|
|
22
|
+
const entry = pending.get(value.id);
|
|
23
|
+
if (!entry)
|
|
24
|
+
return;
|
|
25
|
+
pending.delete(value.id);
|
|
26
|
+
clearTimeout(entry.timeout);
|
|
27
|
+
if (!value.ok) {
|
|
28
|
+
entry.reject(new Error(value.error ?? 'Worker error'));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
entry.resolve(value.result);
|
|
32
|
+
};
|
|
33
|
+
const createRequest = (worker, pending, action, params, id) => {
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
const timeout = setTimeout(() => {
|
|
36
|
+
pending.delete(id);
|
|
37
|
+
reject(new Error('Worker request timed out'));
|
|
38
|
+
}, WORKER_REQUEST_TIMEOUT_MS);
|
|
39
|
+
pending.set(id, { resolve, reject, timeout });
|
|
40
|
+
try {
|
|
41
|
+
worker.postMessage({ id, action, params });
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
pending.delete(id);
|
|
45
|
+
clearTimeout(timeout);
|
|
46
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
export const createDbWorkerClient = () => {
|
|
51
|
+
const worker = createWorker();
|
|
52
|
+
let nextId = 1;
|
|
53
|
+
const pending = new Map();
|
|
54
|
+
worker.on('message', (value) => {
|
|
55
|
+
onWorkerMessage(pending, value);
|
|
56
|
+
});
|
|
57
|
+
worker.on('error', (error) => {
|
|
58
|
+
const resolved = error instanceof Error ? error : new Error(String(error));
|
|
59
|
+
rejectAll(pending, resolved);
|
|
60
|
+
});
|
|
61
|
+
worker.on('exit', (code) => {
|
|
62
|
+
if (code !== 0) {
|
|
63
|
+
rejectAll(pending, new Error(`Worker exited with code ${String(code)}`));
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
const request = async (action, params) => {
|
|
67
|
+
const id = nextId;
|
|
68
|
+
nextId += 1;
|
|
69
|
+
const result = await createRequest(worker, pending, action, params, id);
|
|
70
|
+
return result;
|
|
71
|
+
};
|
|
72
|
+
const close = async () => {
|
|
73
|
+
rejectAll(pending, new Error('Worker closed'));
|
|
74
|
+
await worker.terminate();
|
|
75
|
+
};
|
|
76
|
+
return { request, close };
|
|
77
|
+
};
|
|
78
|
+
export const createWorkerToolDependencies = (client) => {
|
|
79
|
+
return {
|
|
80
|
+
createMemory: (input) => client.request('store_memory', input),
|
|
81
|
+
updateMemory: (hash, options) => client.request('update_memory', [hash, options]),
|
|
82
|
+
getMemory: (hash) => client.request('get_memory', hash),
|
|
83
|
+
deleteMemory: (hash) => client.request('delete_memory', hash),
|
|
84
|
+
searchMemories: (input) => client.request('search_memories', input),
|
|
85
|
+
linkMemories: (fromHash, toHash, relationType) => client.request('link_memories', {
|
|
86
|
+
fromHash,
|
|
87
|
+
toHash,
|
|
88
|
+
relationType,
|
|
89
|
+
}),
|
|
90
|
+
getRelated: (input) => client.request('get_related', input),
|
|
91
|
+
getStats: () => client.request('memory_stats', null),
|
|
92
|
+
};
|
|
93
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { parentPort } from 'node:worker_threads';
|
|
2
|
+
import { closeDb } from '../core/db.js';
|
|
3
|
+
import { deleteMemory, getMemory, getStats } from '../core/memory-read.js';
|
|
4
|
+
import { createMemory, updateMemory } from '../core/memory-write.js';
|
|
5
|
+
import { getRelated, linkMemories } from '../core/relations.js';
|
|
6
|
+
import { searchMemories } from '../core/search.js';
|
|
7
|
+
import { GetMemoryInputSchema, GetRelatedInputSchema, LinkMemoriesInputSchema, MemoryStatsInputSchema, SearchMemoriesInputSchema, StoreMemoryInputSchema, UpdateMemoryInputSchema, } from '../schemas.js';
|
|
8
|
+
const port = parentPort;
|
|
9
|
+
if (!port) {
|
|
10
|
+
throw new Error('Missing parentPort');
|
|
11
|
+
}
|
|
12
|
+
const toErrorMessage = (error) => {
|
|
13
|
+
if (error instanceof Error)
|
|
14
|
+
return error.message;
|
|
15
|
+
if (typeof error === 'string' && error.length > 0)
|
|
16
|
+
return error;
|
|
17
|
+
return 'Unknown error';
|
|
18
|
+
};
|
|
19
|
+
const isRecord = (value) => {
|
|
20
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
21
|
+
};
|
|
22
|
+
const isTwoItemArray = (value) => {
|
|
23
|
+
return Array.isArray(value) && value.length === 2;
|
|
24
|
+
};
|
|
25
|
+
const parseStoreMemoryInput = (params) => {
|
|
26
|
+
const parsed = StoreMemoryInputSchema.safeParse(params);
|
|
27
|
+
if (!parsed.success) {
|
|
28
|
+
throw new Error(parsed.error.message);
|
|
29
|
+
}
|
|
30
|
+
const { content, tags, importance, memoryType } = parsed.data;
|
|
31
|
+
const result = { content };
|
|
32
|
+
if (tags !== undefined)
|
|
33
|
+
result.tags = tags;
|
|
34
|
+
if (importance !== undefined)
|
|
35
|
+
result.importance = importance;
|
|
36
|
+
if (memoryType !== undefined)
|
|
37
|
+
result.memoryType = memoryType;
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
const parseHashParam = (params, action) => {
|
|
41
|
+
const parsed = GetMemoryInputSchema.safeParse({ hash: params });
|
|
42
|
+
if (!parsed.success) {
|
|
43
|
+
throw new Error(`${action}: ${parsed.error.message}`);
|
|
44
|
+
}
|
|
45
|
+
return parsed.data.hash;
|
|
46
|
+
};
|
|
47
|
+
const parseSearchInput = (params) => {
|
|
48
|
+
const parsed = SearchMemoriesInputSchema.safeParse(params);
|
|
49
|
+
if (!parsed.success) {
|
|
50
|
+
throw new Error(parsed.error.message);
|
|
51
|
+
}
|
|
52
|
+
return parsed.data;
|
|
53
|
+
};
|
|
54
|
+
const parseUpdateMemoryArgs = (params) => {
|
|
55
|
+
if (!isTwoItemArray(params)) {
|
|
56
|
+
throw new Error('update_memory: expected [hash, options]');
|
|
57
|
+
}
|
|
58
|
+
const hash = params[0];
|
|
59
|
+
const options = params[1];
|
|
60
|
+
if (typeof hash !== 'string') {
|
|
61
|
+
throw new Error('update_memory: hash must be a string');
|
|
62
|
+
}
|
|
63
|
+
if (!isRecord(options)) {
|
|
64
|
+
throw new Error('update_memory: options must be an object');
|
|
65
|
+
}
|
|
66
|
+
const parsed = UpdateMemoryInputSchema.safeParse({ hash, ...options });
|
|
67
|
+
if (!parsed.success) {
|
|
68
|
+
throw new Error(parsed.error.message);
|
|
69
|
+
}
|
|
70
|
+
const { hash: parsedHash, ...parsedOptions } = parsed.data;
|
|
71
|
+
return [parsedHash, parsedOptions];
|
|
72
|
+
};
|
|
73
|
+
const parseLinkParams = (params) => {
|
|
74
|
+
const parsed = LinkMemoriesInputSchema.safeParse(params);
|
|
75
|
+
if (!parsed.success) {
|
|
76
|
+
throw new Error(parsed.error.message);
|
|
77
|
+
}
|
|
78
|
+
return parsed.data;
|
|
79
|
+
};
|
|
80
|
+
const parseGetRelatedInput = (params) => {
|
|
81
|
+
const parsed = GetRelatedInputSchema.safeParse(params);
|
|
82
|
+
if (!parsed.success) {
|
|
83
|
+
throw new Error(parsed.error.message);
|
|
84
|
+
}
|
|
85
|
+
const { hash, relationType, depth, direction } = parsed.data;
|
|
86
|
+
const result = { hash };
|
|
87
|
+
if (relationType !== undefined)
|
|
88
|
+
result.relationType = relationType;
|
|
89
|
+
if (depth !== undefined)
|
|
90
|
+
result.depth = depth;
|
|
91
|
+
if (direction !== undefined)
|
|
92
|
+
result.direction = direction;
|
|
93
|
+
return result;
|
|
94
|
+
};
|
|
95
|
+
const parseStatsParams = (params) => {
|
|
96
|
+
if (params == null)
|
|
97
|
+
return;
|
|
98
|
+
const parsed = MemoryStatsInputSchema.safeParse(params);
|
|
99
|
+
if (!parsed.success) {
|
|
100
|
+
throw new Error(parsed.error.message);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
const handlers = new Map([
|
|
104
|
+
['store_memory', (params) => createMemory(parseStoreMemoryInput(params))],
|
|
105
|
+
['get_memory', (params) => getMemory(parseHashParam(params, 'get_memory'))],
|
|
106
|
+
[
|
|
107
|
+
'delete_memory',
|
|
108
|
+
(params) => deleteMemory(parseHashParam(params, 'delete_memory')),
|
|
109
|
+
],
|
|
110
|
+
[
|
|
111
|
+
'update_memory',
|
|
112
|
+
(params) => {
|
|
113
|
+
const [hash, options] = parseUpdateMemoryArgs(params);
|
|
114
|
+
return updateMemory(hash, options);
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
['search_memories', (params) => searchMemories(parseSearchInput(params))],
|
|
118
|
+
[
|
|
119
|
+
'link_memories',
|
|
120
|
+
(params) => {
|
|
121
|
+
const input = parseLinkParams(params);
|
|
122
|
+
return linkMemories(input.fromHash, input.toHash, input.relationType);
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
['get_related', (params) => getRelated(parseGetRelatedInput(params))],
|
|
126
|
+
[
|
|
127
|
+
'memory_stats',
|
|
128
|
+
(params) => {
|
|
129
|
+
parseStatsParams(params);
|
|
130
|
+
return getStats();
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
]);
|
|
134
|
+
const isWorkerAction = (value) => {
|
|
135
|
+
if (typeof value !== 'string')
|
|
136
|
+
return false;
|
|
137
|
+
return handlers.has(value);
|
|
138
|
+
};
|
|
139
|
+
const isWorkerRequest = (value) => {
|
|
140
|
+
if (!isRecord(value))
|
|
141
|
+
return false;
|
|
142
|
+
if (typeof value.id !== 'number')
|
|
143
|
+
return false;
|
|
144
|
+
if (!isWorkerAction(value.action))
|
|
145
|
+
return false;
|
|
146
|
+
return 'params' in value;
|
|
147
|
+
};
|
|
148
|
+
const getHandler = (action) => {
|
|
149
|
+
const handler = handlers.get(action);
|
|
150
|
+
if (!handler) {
|
|
151
|
+
throw new Error(`Unsupported action: ${action}`);
|
|
152
|
+
}
|
|
153
|
+
return handler;
|
|
154
|
+
};
|
|
155
|
+
const handleRequest = (request) => {
|
|
156
|
+
try {
|
|
157
|
+
const handler = getHandler(request.action);
|
|
158
|
+
const result = handler(request.params);
|
|
159
|
+
return { id: request.id, ok: true, result };
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
return { id: request.id, ok: false, error: toErrorMessage(error) };
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
const onMessage = (value) => {
|
|
166
|
+
if (!isWorkerRequest(value))
|
|
167
|
+
return;
|
|
168
|
+
const response = handleRequest(value);
|
|
169
|
+
port.postMessage(response);
|
|
170
|
+
};
|
|
171
|
+
port.on('message', onMessage);
|
|
172
|
+
process.on('exit', () => {
|
|
173
|
+
closeDb();
|
|
174
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type WorkerAction = 'store_memory' | 'get_memory' | 'delete_memory' | 'update_memory' | 'search_memories' | 'link_memories' | 'get_related' | 'memory_stats';
|
|
2
|
+
interface WorkerResponse {
|
|
3
|
+
id: number;
|
|
4
|
+
ok: boolean;
|
|
5
|
+
result?: unknown;
|
|
6
|
+
error?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare const isWorkerResponse: (value: unknown) => value is WorkerResponse;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const isRecord = (value) => {
|
|
2
|
+
return typeof value === 'object' && value !== null;
|
|
3
|
+
};
|
|
4
|
+
export const isWorkerResponse = (value) => {
|
|
5
|
+
if (!isRecord(value))
|
|
6
|
+
return false;
|
|
7
|
+
if (typeof value.id !== 'number')
|
|
8
|
+
return false;
|
|
9
|
+
if (typeof value.ok !== 'boolean')
|
|
10
|
+
return false;
|
|
11
|
+
if (!value.ok && typeof value.error !== 'string')
|
|
12
|
+
return false;
|
|
13
|
+
return true;
|
|
14
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@j0hanz/memdb",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"mcpName": "io.github.j0hanz/memdb",
|
|
5
5
|
"description": "A memory-based MCP server using SQLite in-memory database.",
|
|
6
6
|
"type": "module",
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
],
|
|
20
20
|
"scripts": {
|
|
21
21
|
"clean": "node -e \"require('fs').rmSync('dist', {recursive: true, force: true})\"",
|
|
22
|
-
"build": "tsc",
|
|
22
|
+
"build": "tsc -p tsconfig.build.json && node -e \"require('fs').chmodSync('dist/index.js', '755')\"",
|
|
23
|
+
"prepare": "npm run build",
|
|
23
24
|
"dev": "tsx watch src/index.ts",
|
|
24
25
|
"start": "node dist/index.js",
|
|
25
26
|
"test": "node --import tsx/esm --test tests/*.test.ts",
|
|
@@ -48,15 +49,17 @@
|
|
|
48
49
|
"homepage": "https://github.com/j0hanz/memdb-mcp-server#readme",
|
|
49
50
|
"license": "MIT",
|
|
50
51
|
"dependencies": {
|
|
51
|
-
"@modelcontextprotocol/sdk": "^1.25.
|
|
52
|
+
"@modelcontextprotocol/sdk": "^1.25.2",
|
|
52
53
|
"zod": "^4.3.5"
|
|
53
54
|
},
|
|
54
55
|
"devDependencies": {
|
|
55
56
|
"@eslint/js": "^9.39.2",
|
|
56
|
-
"@trivago/prettier-plugin-sort-imports": "^6.0.
|
|
57
|
-
"@types/node": "^
|
|
57
|
+
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
|
|
58
|
+
"@types/node": "^25.0.6",
|
|
58
59
|
"eslint": "^9.39.2",
|
|
59
60
|
"eslint-config-prettier": "^10.1.8",
|
|
61
|
+
"eslint-plugin-de-morgan": "^2.0.0",
|
|
62
|
+
"eslint-plugin-depend": "^1.4.0",
|
|
60
63
|
"eslint-plugin-sonarjs": "^3.0.5",
|
|
61
64
|
"eslint-plugin-unused-imports": "^4.3.0",
|
|
62
65
|
"prettier": "^3.7.4",
|