@danielsimonjr/memory-mcp 0.47.1 → 9.8.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/LICENSE +22 -0
- package/README.md +2000 -194
- package/dist/__tests__/file-path.test.js +5 -5
- package/dist/__tests__/knowledge-graph.test.js +3 -8
- package/dist/core/EntityManager.d.ts +266 -0
- package/dist/core/EntityManager.d.ts.map +1 -0
- package/dist/core/EntityManager.js +85 -133
- package/dist/core/GraphEventEmitter.d.ts +202 -0
- package/dist/core/GraphEventEmitter.d.ts.map +1 -0
- package/dist/core/GraphEventEmitter.js +346 -0
- package/dist/core/GraphStorage.d.ts +395 -0
- package/dist/core/GraphStorage.d.ts.map +1 -0
- package/dist/core/GraphStorage.js +643 -31
- package/dist/core/GraphTraversal.d.ts +141 -0
- package/dist/core/GraphTraversal.d.ts.map +1 -0
- package/dist/core/GraphTraversal.js +573 -0
- package/dist/core/HierarchyManager.d.ts +111 -0
- package/dist/core/HierarchyManager.d.ts.map +1 -0
- package/dist/{features → core}/HierarchyManager.js +14 -9
- package/dist/core/ManagerContext.d.ts +72 -0
- package/dist/core/ManagerContext.d.ts.map +1 -0
- package/dist/core/ManagerContext.js +118 -0
- package/dist/core/ObservationManager.d.ts +85 -0
- package/dist/core/ObservationManager.d.ts.map +1 -0
- package/dist/core/ObservationManager.js +51 -57
- package/dist/core/RelationManager.d.ts +131 -0
- package/dist/core/RelationManager.d.ts.map +1 -0
- package/dist/core/RelationManager.js +31 -7
- package/dist/core/SQLiteStorage.d.ts +354 -0
- package/dist/core/SQLiteStorage.d.ts.map +1 -0
- package/dist/core/SQLiteStorage.js +917 -0
- package/dist/core/StorageFactory.d.ts +45 -0
- package/dist/core/StorageFactory.d.ts.map +1 -0
- package/dist/core/StorageFactory.js +64 -0
- package/dist/core/TransactionManager.d.ts +464 -0
- package/dist/core/TransactionManager.d.ts.map +1 -0
- package/dist/core/TransactionManager.js +490 -13
- package/dist/core/index.d.ts +17 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +12 -2
- package/dist/features/AnalyticsManager.d.ts +44 -0
- package/dist/features/AnalyticsManager.d.ts.map +1 -0
- package/dist/features/AnalyticsManager.js +14 -13
- package/dist/features/ArchiveManager.d.ts +133 -0
- package/dist/features/ArchiveManager.d.ts.map +1 -0
- package/dist/features/ArchiveManager.js +221 -14
- package/dist/features/CompressionManager.d.ts +117 -0
- package/dist/features/CompressionManager.d.ts.map +1 -0
- package/dist/features/CompressionManager.js +189 -20
- package/dist/features/IOManager.d.ts +225 -0
- package/dist/features/IOManager.d.ts.map +1 -0
- package/dist/features/IOManager.js +1041 -0
- package/dist/features/StreamingExporter.d.ts +123 -0
- package/dist/features/StreamingExporter.d.ts.map +1 -0
- package/dist/features/StreamingExporter.js +203 -0
- package/dist/features/TagManager.d.ts +147 -0
- package/dist/features/TagManager.d.ts.map +1 -0
- package/dist/features/index.d.ts +12 -0
- package/dist/features/index.d.ts.map +1 -0
- package/dist/features/index.js +5 -6
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -45
- package/dist/memory.jsonl +1 -18
- package/dist/search/BasicSearch.d.ts +51 -0
- package/dist/search/BasicSearch.d.ts.map +1 -0
- package/dist/search/BasicSearch.js +9 -3
- package/dist/search/BooleanSearch.d.ts +98 -0
- package/dist/search/BooleanSearch.d.ts.map +1 -0
- package/dist/search/BooleanSearch.js +156 -9
- package/dist/search/EmbeddingService.d.ts +178 -0
- package/dist/search/EmbeddingService.d.ts.map +1 -0
- package/dist/search/EmbeddingService.js +358 -0
- package/dist/search/FuzzySearch.d.ts +118 -0
- package/dist/search/FuzzySearch.d.ts.map +1 -0
- package/dist/search/FuzzySearch.js +241 -25
- package/dist/search/QueryCostEstimator.d.ts +111 -0
- package/dist/search/QueryCostEstimator.d.ts.map +1 -0
- package/dist/search/QueryCostEstimator.js +355 -0
- package/dist/search/RankedSearch.d.ts +71 -0
- package/dist/search/RankedSearch.d.ts.map +1 -0
- package/dist/search/RankedSearch.js +54 -6
- package/dist/search/SavedSearchManager.d.ts +79 -0
- package/dist/search/SavedSearchManager.d.ts.map +1 -0
- package/dist/search/SearchFilterChain.d.ts +120 -0
- package/dist/search/SearchFilterChain.d.ts.map +1 -0
- package/dist/search/SearchFilterChain.js +2 -4
- package/dist/search/SearchManager.d.ts +326 -0
- package/dist/search/SearchManager.d.ts.map +1 -0
- package/dist/search/SearchManager.js +148 -0
- package/dist/search/SearchSuggestions.d.ts +27 -0
- package/dist/search/SearchSuggestions.d.ts.map +1 -0
- package/dist/search/SearchSuggestions.js +1 -1
- package/dist/search/SemanticSearch.d.ts +149 -0
- package/dist/search/SemanticSearch.d.ts.map +1 -0
- package/dist/search/SemanticSearch.js +323 -0
- package/dist/search/TFIDFEventSync.d.ts +85 -0
- package/dist/search/TFIDFEventSync.d.ts.map +1 -0
- package/dist/search/TFIDFEventSync.js +133 -0
- package/dist/search/TFIDFIndexManager.d.ts +151 -0
- package/dist/search/TFIDFIndexManager.d.ts.map +1 -0
- package/dist/search/TFIDFIndexManager.js +232 -17
- package/dist/search/VectorStore.d.ts +235 -0
- package/dist/search/VectorStore.d.ts.map +1 -0
- package/dist/search/VectorStore.js +311 -0
- package/dist/search/index.d.ts +21 -0
- package/dist/search/index.d.ts.map +1 -0
- package/dist/search/index.js +12 -0
- package/dist/server/MCPServer.d.ts +21 -0
- package/dist/server/MCPServer.d.ts.map +1 -0
- package/dist/server/MCPServer.js +4 -4
- package/dist/server/responseCompressor.d.ts +94 -0
- package/dist/server/responseCompressor.d.ts.map +1 -0
- package/dist/server/responseCompressor.js +127 -0
- package/dist/server/toolDefinitions.d.ts +27 -0
- package/dist/server/toolDefinitions.d.ts.map +1 -0
- package/dist/server/toolDefinitions.js +189 -18
- package/dist/server/toolHandlers.d.ts +41 -0
- package/dist/server/toolHandlers.d.ts.map +1 -0
- package/dist/server/toolHandlers.js +467 -75
- package/dist/types/index.d.ts +13 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +1 -1
- package/dist/types/types.d.ts +1654 -0
- package/dist/types/types.d.ts.map +1 -0
- package/dist/types/types.js +9 -0
- package/dist/utils/compressedCache.d.ts +192 -0
- package/dist/utils/compressedCache.d.ts.map +1 -0
- package/dist/utils/compressedCache.js +309 -0
- package/dist/utils/compressionUtil.d.ts +214 -0
- package/dist/utils/compressionUtil.d.ts.map +1 -0
- package/dist/utils/compressionUtil.js +247 -0
- package/dist/utils/constants.d.ts +245 -0
- package/dist/utils/constants.d.ts.map +1 -0
- package/dist/utils/constants.js +124 -0
- package/dist/utils/entityUtils.d.ts +321 -0
- package/dist/utils/entityUtils.d.ts.map +1 -0
- package/dist/utils/entityUtils.js +434 -4
- package/dist/utils/errors.d.ts +95 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +24 -0
- package/dist/utils/formatters.d.ts +145 -0
- package/dist/utils/formatters.d.ts.map +1 -0
- package/dist/utils/{paginationUtils.js → formatters.js} +54 -3
- package/dist/utils/index.d.ts +23 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +69 -31
- package/dist/utils/indexes.d.ts +270 -0
- package/dist/utils/indexes.d.ts.map +1 -0
- package/dist/utils/indexes.js +526 -0
- package/dist/utils/logger.d.ts +24 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/operationUtils.d.ts +124 -0
- package/dist/utils/operationUtils.d.ts.map +1 -0
- package/dist/utils/operationUtils.js +175 -0
- package/dist/utils/parallelUtils.d.ts +72 -0
- package/dist/utils/parallelUtils.d.ts.map +1 -0
- package/dist/utils/parallelUtils.js +169 -0
- package/dist/utils/schemas.d.ts +374 -0
- package/dist/utils/schemas.d.ts.map +1 -0
- package/dist/utils/schemas.js +302 -2
- package/dist/utils/searchAlgorithms.d.ts +99 -0
- package/dist/utils/searchAlgorithms.d.ts.map +1 -0
- package/dist/utils/searchAlgorithms.js +167 -0
- package/dist/utils/searchCache.d.ts +108 -0
- package/dist/utils/searchCache.d.ts.map +1 -0
- package/dist/utils/taskScheduler.d.ts +290 -0
- package/dist/utils/taskScheduler.d.ts.map +1 -0
- package/dist/utils/taskScheduler.js +466 -0
- package/dist/workers/index.d.ts +12 -0
- package/dist/workers/index.d.ts.map +1 -0
- package/dist/workers/index.js +9 -0
- package/dist/workers/levenshteinWorker.d.ts +60 -0
- package/dist/workers/levenshteinWorker.d.ts.map +1 -0
- package/dist/workers/levenshteinWorker.js +98 -0
- package/package.json +17 -4
- package/dist/__tests__/edge-cases/edge-cases.test.js +0 -406
- package/dist/__tests__/integration/workflows.test.js +0 -449
- package/dist/__tests__/performance/benchmarks.test.js +0 -413
- package/dist/__tests__/unit/core/EntityManager.test.js +0 -334
- package/dist/__tests__/unit/core/GraphStorage.test.js +0 -205
- package/dist/__tests__/unit/core/RelationManager.test.js +0 -274
- package/dist/__tests__/unit/features/CompressionManager.test.js +0 -350
- package/dist/__tests__/unit/search/BasicSearch.test.js +0 -311
- package/dist/__tests__/unit/search/BooleanSearch.test.js +0 -432
- package/dist/__tests__/unit/search/FuzzySearch.test.js +0 -448
- package/dist/__tests__/unit/search/RankedSearch.test.js +0 -379
- package/dist/__tests__/unit/utils/levenshtein.test.js +0 -77
- package/dist/core/KnowledgeGraphManager.js +0 -423
- package/dist/features/BackupManager.js +0 -311
- package/dist/features/ExportManager.js +0 -305
- package/dist/features/ImportExportManager.js +0 -50
- package/dist/features/ImportManager.js +0 -328
- package/dist/types/analytics.types.js +0 -6
- package/dist/types/entity.types.js +0 -7
- package/dist/types/import-export.types.js +0 -7
- package/dist/types/search.types.js +0 -7
- package/dist/types/tag.types.js +0 -6
- package/dist/utils/dateUtils.js +0 -89
- package/dist/utils/filterUtils.js +0 -155
- package/dist/utils/levenshtein.js +0 -62
- package/dist/utils/pathUtils.js +0 -115
- package/dist/utils/responseFormatter.js +0 -55
- package/dist/utils/tagUtils.js +0 -107
- package/dist/utils/tfidf.js +0 -90
- package/dist/utils/validationHelper.js +0 -99
- package/dist/utils/validationUtils.js +0 -109
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task Scheduler
|
|
3
|
+
*
|
|
4
|
+
* Advanced task scheduling utilities using workerpool.
|
|
5
|
+
* Phase 8 Sprint 4: Priority queues, concurrency control, progress tracking.
|
|
6
|
+
*
|
|
7
|
+
* @module utils/taskScheduler
|
|
8
|
+
*/
|
|
9
|
+
import workerpool from '@danielsimonjr/workerpool';
|
|
10
|
+
// ==================== Types ====================
|
|
11
|
+
/**
|
|
12
|
+
* Task priority levels.
|
|
13
|
+
* Higher priority tasks are executed first.
|
|
14
|
+
*/
|
|
15
|
+
export var TaskPriority;
|
|
16
|
+
(function (TaskPriority) {
|
|
17
|
+
TaskPriority[TaskPriority["LOW"] = 0] = "LOW";
|
|
18
|
+
TaskPriority[TaskPriority["NORMAL"] = 1] = "NORMAL";
|
|
19
|
+
TaskPriority[TaskPriority["HIGH"] = 2] = "HIGH";
|
|
20
|
+
TaskPriority[TaskPriority["CRITICAL"] = 3] = "CRITICAL";
|
|
21
|
+
})(TaskPriority || (TaskPriority = {}));
|
|
22
|
+
/**
|
|
23
|
+
* Task status in the queue.
|
|
24
|
+
*/
|
|
25
|
+
export var TaskStatus;
|
|
26
|
+
(function (TaskStatus) {
|
|
27
|
+
TaskStatus["PENDING"] = "pending";
|
|
28
|
+
TaskStatus["RUNNING"] = "running";
|
|
29
|
+
TaskStatus["COMPLETED"] = "completed";
|
|
30
|
+
TaskStatus["FAILED"] = "failed";
|
|
31
|
+
TaskStatus["CANCELLED"] = "cancelled";
|
|
32
|
+
})(TaskStatus || (TaskStatus = {}));
|
|
33
|
+
/**
|
|
34
|
+
* Priority Task Queue with advanced scheduling.
|
|
35
|
+
*
|
|
36
|
+
* Features:
|
|
37
|
+
* - Priority-based execution (CRITICAL > HIGH > NORMAL > LOW)
|
|
38
|
+
* - Configurable concurrency limits
|
|
39
|
+
* - Progress tracking
|
|
40
|
+
* - Graceful error handling
|
|
41
|
+
* - Task cancellation
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```typescript
|
|
45
|
+
* const queue = new TaskQueue({ concurrency: 4 });
|
|
46
|
+
*
|
|
47
|
+
* // Add tasks with different priorities
|
|
48
|
+
* queue.enqueue({
|
|
49
|
+
* id: 'task1',
|
|
50
|
+
* priority: TaskPriority.HIGH,
|
|
51
|
+
* fn: (x: number) => x * 2,
|
|
52
|
+
* input: 5,
|
|
53
|
+
* });
|
|
54
|
+
*
|
|
55
|
+
* // Process all tasks
|
|
56
|
+
* const results = await queue.processAll();
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export class TaskQueue {
|
|
60
|
+
queue = [];
|
|
61
|
+
running = new Map();
|
|
62
|
+
completed = [];
|
|
63
|
+
pool = null;
|
|
64
|
+
concurrency;
|
|
65
|
+
defaultTimeout;
|
|
66
|
+
isProcessing = false;
|
|
67
|
+
totalExecutionTime = 0;
|
|
68
|
+
totalProcessed = 0;
|
|
69
|
+
useWorkerPool;
|
|
70
|
+
constructor(options = {}) {
|
|
71
|
+
this.concurrency = options.concurrency ?? Math.max(1, workerpool.cpus - 1);
|
|
72
|
+
this.defaultTimeout = options.timeout ?? 30000;
|
|
73
|
+
this.useWorkerPool = options.useWorkerPool ?? true;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Get or create the worker pool.
|
|
77
|
+
*/
|
|
78
|
+
getPool() {
|
|
79
|
+
if (!this.pool) {
|
|
80
|
+
this.pool = workerpool.pool({
|
|
81
|
+
maxWorkers: this.concurrency,
|
|
82
|
+
workerType: 'thread',
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return this.pool;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Add a task to the queue.
|
|
89
|
+
*
|
|
90
|
+
* @param task - Task to add
|
|
91
|
+
* @returns Promise that resolves when the task completes
|
|
92
|
+
*/
|
|
93
|
+
enqueue(task) {
|
|
94
|
+
return new Promise((resolve, reject) => {
|
|
95
|
+
const queuedTask = {
|
|
96
|
+
...task,
|
|
97
|
+
status: TaskStatus.PENDING,
|
|
98
|
+
addedAt: Date.now(),
|
|
99
|
+
resolve: resolve,
|
|
100
|
+
reject,
|
|
101
|
+
};
|
|
102
|
+
// Insert based on priority (higher priority first)
|
|
103
|
+
const insertIndex = this.queue.findIndex(t => t.priority < task.priority);
|
|
104
|
+
if (insertIndex === -1) {
|
|
105
|
+
this.queue.push(queuedTask);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
this.queue.splice(insertIndex, 0, queuedTask);
|
|
109
|
+
}
|
|
110
|
+
// Start processing if not already running
|
|
111
|
+
if (!this.isProcessing) {
|
|
112
|
+
this.processNext();
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Process the next task in the queue.
|
|
118
|
+
*/
|
|
119
|
+
async processNext() {
|
|
120
|
+
if (this.running.size >= this.concurrency || this.queue.length === 0) {
|
|
121
|
+
if (this.running.size === 0 && this.queue.length === 0) {
|
|
122
|
+
this.isProcessing = false;
|
|
123
|
+
}
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
this.isProcessing = true;
|
|
127
|
+
const task = this.queue.shift();
|
|
128
|
+
if (!task)
|
|
129
|
+
return;
|
|
130
|
+
task.status = TaskStatus.RUNNING;
|
|
131
|
+
this.running.set(task.id, task);
|
|
132
|
+
const startTime = Date.now();
|
|
133
|
+
try {
|
|
134
|
+
// Execute task - try worker pool first, fall back to direct execution
|
|
135
|
+
let result;
|
|
136
|
+
if (this.useWorkerPool) {
|
|
137
|
+
try {
|
|
138
|
+
const pool = this.getPool();
|
|
139
|
+
const fnString = task.fn.toString();
|
|
140
|
+
const timeout = task.timeout ?? this.defaultTimeout;
|
|
141
|
+
result = await pool
|
|
142
|
+
.exec((input, fnStr) => {
|
|
143
|
+
// eslint-disable-next-line no-new-func
|
|
144
|
+
const fn = new Function('return ' + fnStr)();
|
|
145
|
+
return fn(input);
|
|
146
|
+
}, [task.input, fnString])
|
|
147
|
+
.timeout(timeout);
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
// Fall back to direct execution
|
|
151
|
+
result = await Promise.resolve(task.fn(task.input));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
// Direct execution without worker pool
|
|
156
|
+
result = await Promise.resolve(task.fn(task.input));
|
|
157
|
+
}
|
|
158
|
+
const endTime = Date.now();
|
|
159
|
+
const duration = endTime - startTime;
|
|
160
|
+
const taskResult = {
|
|
161
|
+
id: task.id,
|
|
162
|
+
status: TaskStatus.COMPLETED,
|
|
163
|
+
result,
|
|
164
|
+
duration,
|
|
165
|
+
startedAt: startTime,
|
|
166
|
+
completedAt: endTime,
|
|
167
|
+
};
|
|
168
|
+
this.totalExecutionTime += duration;
|
|
169
|
+
this.totalProcessed++;
|
|
170
|
+
this.completed.push(taskResult);
|
|
171
|
+
this.running.delete(task.id);
|
|
172
|
+
task.resolve(taskResult);
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
const endTime = Date.now();
|
|
176
|
+
const duration = endTime - startTime;
|
|
177
|
+
const taskResult = {
|
|
178
|
+
id: task.id,
|
|
179
|
+
status: TaskStatus.FAILED,
|
|
180
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
181
|
+
duration,
|
|
182
|
+
startedAt: startTime,
|
|
183
|
+
completedAt: endTime,
|
|
184
|
+
};
|
|
185
|
+
this.totalProcessed++;
|
|
186
|
+
this.completed.push(taskResult);
|
|
187
|
+
this.running.delete(task.id);
|
|
188
|
+
task.resolve(taskResult);
|
|
189
|
+
}
|
|
190
|
+
// Process next task
|
|
191
|
+
this.processNext();
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Cancel a pending task.
|
|
195
|
+
*
|
|
196
|
+
* @param taskId - ID of the task to cancel
|
|
197
|
+
* @returns True if task was cancelled, false if not found or already running
|
|
198
|
+
*/
|
|
199
|
+
cancel(taskId) {
|
|
200
|
+
const index = this.queue.findIndex(t => t.id === taskId);
|
|
201
|
+
if (index === -1)
|
|
202
|
+
return false;
|
|
203
|
+
const task = this.queue.splice(index, 1)[0];
|
|
204
|
+
task.status = TaskStatus.CANCELLED;
|
|
205
|
+
const result = {
|
|
206
|
+
id: task.id,
|
|
207
|
+
status: TaskStatus.CANCELLED,
|
|
208
|
+
duration: 0,
|
|
209
|
+
startedAt: Date.now(),
|
|
210
|
+
completedAt: Date.now(),
|
|
211
|
+
};
|
|
212
|
+
task.resolve(result);
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Wait for all tasks to complete.
|
|
217
|
+
*
|
|
218
|
+
* @returns Array of all task results
|
|
219
|
+
*/
|
|
220
|
+
async drain() {
|
|
221
|
+
// Wait for queue to empty and all running tasks to complete
|
|
222
|
+
while (this.queue.length > 0 || this.running.size > 0) {
|
|
223
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
224
|
+
}
|
|
225
|
+
return [...this.completed];
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Get queue statistics.
|
|
229
|
+
*/
|
|
230
|
+
getStats() {
|
|
231
|
+
return {
|
|
232
|
+
pending: this.queue.length,
|
|
233
|
+
running: this.running.size,
|
|
234
|
+
completed: this.completed.filter(r => r.status === TaskStatus.COMPLETED).length,
|
|
235
|
+
failed: this.completed.filter(r => r.status === TaskStatus.FAILED).length,
|
|
236
|
+
averageExecutionTime: this.totalProcessed > 0 ? this.totalExecutionTime / this.totalProcessed : 0,
|
|
237
|
+
totalProcessed: this.totalProcessed,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Clear all completed results.
|
|
242
|
+
*/
|
|
243
|
+
clearCompleted() {
|
|
244
|
+
this.completed = [];
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Shutdown the task queue and release resources.
|
|
248
|
+
*/
|
|
249
|
+
async shutdown() {
|
|
250
|
+
// Cancel all pending tasks
|
|
251
|
+
for (const task of this.queue) {
|
|
252
|
+
task.status = TaskStatus.CANCELLED;
|
|
253
|
+
task.resolve({
|
|
254
|
+
id: task.id,
|
|
255
|
+
status: TaskStatus.CANCELLED,
|
|
256
|
+
duration: 0,
|
|
257
|
+
startedAt: Date.now(),
|
|
258
|
+
completedAt: Date.now(),
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
this.queue = [];
|
|
262
|
+
// Terminate worker pool
|
|
263
|
+
if (this.pool) {
|
|
264
|
+
await this.pool.terminate();
|
|
265
|
+
this.pool = null;
|
|
266
|
+
}
|
|
267
|
+
this.isProcessing = false;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
// ==================== Batch Processing Utilities ====================
|
|
271
|
+
/**
|
|
272
|
+
* Process items in parallel batches with progress tracking.
|
|
273
|
+
*
|
|
274
|
+
* Unlike parallelMap, this provides:
|
|
275
|
+
* - Progress callbacks
|
|
276
|
+
* - Configurable concurrency
|
|
277
|
+
* - Error handling options
|
|
278
|
+
* - Task-level timeouts
|
|
279
|
+
*
|
|
280
|
+
* @template T - Input item type
|
|
281
|
+
* @template R - Output item type
|
|
282
|
+
* @param items - Array of items to process
|
|
283
|
+
* @param fn - Processing function (must be serializable)
|
|
284
|
+
* @param options - Batch processing options
|
|
285
|
+
* @returns Array of results (or errors if stopOnError is false)
|
|
286
|
+
*
|
|
287
|
+
* @example
|
|
288
|
+
* ```typescript
|
|
289
|
+
* const results = await batchProcess(
|
|
290
|
+
* urls,
|
|
291
|
+
* (url: string) => fetch(url).then(r => r.json()),
|
|
292
|
+
* {
|
|
293
|
+
* concurrency: 5,
|
|
294
|
+
* timeout: 10000,
|
|
295
|
+
* onProgress: ({ completed, total, percentage }) => {
|
|
296
|
+
* console.log(`Progress: ${percentage.toFixed(1)}%`);
|
|
297
|
+
* },
|
|
298
|
+
* }
|
|
299
|
+
* );
|
|
300
|
+
* ```
|
|
301
|
+
*/
|
|
302
|
+
export async function batchProcess(items, fn, options = {}) {
|
|
303
|
+
const { concurrency = Math.max(1, workerpool.cpus - 1), timeout = 30000, onProgress, stopOnError = false, } = options;
|
|
304
|
+
const results = [];
|
|
305
|
+
let completed = 0;
|
|
306
|
+
const total = items.length;
|
|
307
|
+
// Process in batches respecting concurrency
|
|
308
|
+
for (let i = 0; i < items.length; i += concurrency) {
|
|
309
|
+
const batch = items.slice(i, i + concurrency);
|
|
310
|
+
const batchPromises = batch.map(async (item, batchIndex) => {
|
|
311
|
+
const itemIndex = i + batchIndex;
|
|
312
|
+
try {
|
|
313
|
+
// Execute with timeout
|
|
314
|
+
const result = await Promise.race([
|
|
315
|
+
Promise.resolve(fn(item)),
|
|
316
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('Task timeout')), timeout)),
|
|
317
|
+
]);
|
|
318
|
+
results[itemIndex] = { success: true, result };
|
|
319
|
+
}
|
|
320
|
+
catch (error) {
|
|
321
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
322
|
+
results[itemIndex] = { success: false, error: err };
|
|
323
|
+
if (stopOnError) {
|
|
324
|
+
throw err;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
finally {
|
|
328
|
+
completed++;
|
|
329
|
+
if (onProgress) {
|
|
330
|
+
onProgress({
|
|
331
|
+
completed,
|
|
332
|
+
total,
|
|
333
|
+
percentage: (completed / total) * 100,
|
|
334
|
+
currentTaskId: `item-${itemIndex}`,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
await Promise.all(batchPromises);
|
|
340
|
+
}
|
|
341
|
+
return results;
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Execute tasks with rate limiting.
|
|
345
|
+
*
|
|
346
|
+
* Ensures tasks don't exceed a specified rate (tasks per second).
|
|
347
|
+
*
|
|
348
|
+
* @template T - Input item type
|
|
349
|
+
* @template R - Output item type
|
|
350
|
+
* @param items - Items to process
|
|
351
|
+
* @param fn - Processing function
|
|
352
|
+
* @param rateLimit - Maximum tasks per second
|
|
353
|
+
* @returns Array of results
|
|
354
|
+
*
|
|
355
|
+
* @example
|
|
356
|
+
* ```typescript
|
|
357
|
+
* // Process max 10 items per second
|
|
358
|
+
* const results = await rateLimitedProcess(
|
|
359
|
+
* items,
|
|
360
|
+
* (item) => processItem(item),
|
|
361
|
+
* 10
|
|
362
|
+
* );
|
|
363
|
+
* ```
|
|
364
|
+
*/
|
|
365
|
+
export async function rateLimitedProcess(items, fn, rateLimit) {
|
|
366
|
+
const results = [];
|
|
367
|
+
const minInterval = 1000 / rateLimit;
|
|
368
|
+
let lastExecutionTime = 0;
|
|
369
|
+
for (const item of items) {
|
|
370
|
+
// Calculate wait time
|
|
371
|
+
const now = Date.now();
|
|
372
|
+
const timeSinceLast = now - lastExecutionTime;
|
|
373
|
+
if (timeSinceLast < minInterval) {
|
|
374
|
+
await new Promise(resolve => setTimeout(resolve, minInterval - timeSinceLast));
|
|
375
|
+
}
|
|
376
|
+
lastExecutionTime = Date.now();
|
|
377
|
+
const result = await fn(item);
|
|
378
|
+
results.push(result);
|
|
379
|
+
}
|
|
380
|
+
return results;
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Retry a function with exponential backoff.
|
|
384
|
+
*
|
|
385
|
+
* @template T - Return type
|
|
386
|
+
* @param fn - Function to retry
|
|
387
|
+
* @param options - Retry options
|
|
388
|
+
* @returns Result of the function
|
|
389
|
+
*
|
|
390
|
+
* @example
|
|
391
|
+
* ```typescript
|
|
392
|
+
* const result = await withRetry(
|
|
393
|
+
* () => fetchData(),
|
|
394
|
+
* { maxRetries: 3, baseDelay: 1000 }
|
|
395
|
+
* );
|
|
396
|
+
* ```
|
|
397
|
+
*/
|
|
398
|
+
export async function withRetry(fn, options = {}) {
|
|
399
|
+
const { maxRetries = 3, baseDelay = 1000, maxDelay = 30000, onRetry } = options;
|
|
400
|
+
let lastError;
|
|
401
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
402
|
+
try {
|
|
403
|
+
return await fn();
|
|
404
|
+
}
|
|
405
|
+
catch (error) {
|
|
406
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
407
|
+
if (attempt < maxRetries) {
|
|
408
|
+
const delay = Math.min(baseDelay * Math.pow(2, attempt), maxDelay);
|
|
409
|
+
if (onRetry) {
|
|
410
|
+
onRetry(lastError, attempt + 1);
|
|
411
|
+
}
|
|
412
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
throw lastError;
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Create a debounced version of a function.
|
|
420
|
+
*
|
|
421
|
+
* @template T - Function arguments type
|
|
422
|
+
* @template R - Return type
|
|
423
|
+
* @param fn - Function to debounce
|
|
424
|
+
* @param delay - Delay in milliseconds
|
|
425
|
+
* @returns Debounced function
|
|
426
|
+
*/
|
|
427
|
+
export function debounce(fn, delay) {
|
|
428
|
+
let timeoutId = null;
|
|
429
|
+
let pendingResolve = null;
|
|
430
|
+
return (...args) => {
|
|
431
|
+
return new Promise(resolve => {
|
|
432
|
+
if (timeoutId) {
|
|
433
|
+
clearTimeout(timeoutId);
|
|
434
|
+
}
|
|
435
|
+
pendingResolve = resolve;
|
|
436
|
+
timeoutId = setTimeout(() => {
|
|
437
|
+
const result = fn(...args);
|
|
438
|
+
if (pendingResolve) {
|
|
439
|
+
pendingResolve(result);
|
|
440
|
+
}
|
|
441
|
+
timeoutId = null;
|
|
442
|
+
pendingResolve = null;
|
|
443
|
+
}, delay);
|
|
444
|
+
});
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Create a throttled version of a function.
|
|
449
|
+
*
|
|
450
|
+
* @template T - Function arguments type
|
|
451
|
+
* @template R - Return type
|
|
452
|
+
* @param fn - Function to throttle
|
|
453
|
+
* @param limit - Minimum time between calls in milliseconds
|
|
454
|
+
* @returns Throttled function
|
|
455
|
+
*/
|
|
456
|
+
export function throttle(fn, limit) {
|
|
457
|
+
let lastCall = 0;
|
|
458
|
+
return (...args) => {
|
|
459
|
+
const now = Date.now();
|
|
460
|
+
if (now - lastCall >= limit) {
|
|
461
|
+
lastCall = now;
|
|
462
|
+
return fn(...args);
|
|
463
|
+
}
|
|
464
|
+
return undefined;
|
|
465
|
+
};
|
|
466
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workers Module
|
|
3
|
+
*
|
|
4
|
+
* Worker thread utilities for parallel processing.
|
|
5
|
+
* Phase 8: Uses workerpool library for worker management.
|
|
6
|
+
*
|
|
7
|
+
* @module workers
|
|
8
|
+
*/
|
|
9
|
+
export type { Pool, PoolStats } from '@danielsimonjr/workerpool';
|
|
10
|
+
export type { WorkerInput, MatchResult } from './levenshteinWorker.js';
|
|
11
|
+
export { levenshteinDistance, similarity, searchEntities } from './levenshteinWorker.js';
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/workers/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,YAAY,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAGjE,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACvE,OAAO,EAAE,mBAAmB,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Levenshtein Worker
|
|
3
|
+
*
|
|
4
|
+
* Worker thread for calculating Levenshtein distances in parallel.
|
|
5
|
+
* Uses workerpool for worker management.
|
|
6
|
+
*
|
|
7
|
+
* @module workers/levenshteinWorker
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Input data structure for the worker.
|
|
11
|
+
*/
|
|
12
|
+
export interface WorkerInput {
|
|
13
|
+
/** Search query string */
|
|
14
|
+
query: string;
|
|
15
|
+
/** Array of entities to search */
|
|
16
|
+
entities: Array<{
|
|
17
|
+
name: string;
|
|
18
|
+
nameLower: string;
|
|
19
|
+
observations: string[];
|
|
20
|
+
}>;
|
|
21
|
+
/** Similarity threshold (0.0 to 1.0) */
|
|
22
|
+
threshold: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Match result returned by the worker.
|
|
26
|
+
*/
|
|
27
|
+
export interface MatchResult {
|
|
28
|
+
/** Entity name that matched */
|
|
29
|
+
name: string;
|
|
30
|
+
/** Similarity score (0.0 to 1.0) */
|
|
31
|
+
score: number;
|
|
32
|
+
/** Where the match occurred */
|
|
33
|
+
matchedIn: 'name' | 'observation';
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Calculate Levenshtein distance between two strings.
|
|
37
|
+
*
|
|
38
|
+
* Uses dynamic programming matrix for efficient computation.
|
|
39
|
+
*
|
|
40
|
+
* @param s1 - First string
|
|
41
|
+
* @param s2 - Second string
|
|
42
|
+
* @returns Levenshtein distance (number of edits)
|
|
43
|
+
*/
|
|
44
|
+
export declare function levenshteinDistance(s1: string, s2: string): number;
|
|
45
|
+
/**
|
|
46
|
+
* Calculate similarity score between two strings.
|
|
47
|
+
*
|
|
48
|
+
* @param s1 - First string
|
|
49
|
+
* @param s2 - Second string
|
|
50
|
+
* @returns Similarity score (0.0 to 1.0, where 1.0 is identical)
|
|
51
|
+
*/
|
|
52
|
+
export declare function similarity(s1: string, s2: string): number;
|
|
53
|
+
/**
|
|
54
|
+
* Search entities for fuzzy matches.
|
|
55
|
+
*
|
|
56
|
+
* @param data - Worker input containing query, entities, and threshold
|
|
57
|
+
* @returns Array of match results
|
|
58
|
+
*/
|
|
59
|
+
export declare function searchEntities(data: WorkerInput): MatchResult[];
|
|
60
|
+
//# sourceMappingURL=levenshteinWorker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"levenshteinWorker.d.ts","sourceRoot":"","sources":["../../src/workers/levenshteinWorker.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,0BAA0B;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,kCAAkC;IAClC,QAAQ,EAAE,KAAK,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;QAClB,YAAY,EAAE,MAAM,EAAE,CAAC;KACxB,CAAC,CAAC;IACH,wCAAwC;IACxC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,+BAA+B;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,oCAAoC;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,+BAA+B;IAC/B,SAAS,EAAE,MAAM,GAAG,aAAa,CAAC;CACnC;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAgClE;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAWzD;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,WAAW,GAAG,WAAW,EAAE,CAwB/D"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Levenshtein Worker
|
|
3
|
+
*
|
|
4
|
+
* Worker thread for calculating Levenshtein distances in parallel.
|
|
5
|
+
* Uses workerpool for worker management.
|
|
6
|
+
*
|
|
7
|
+
* @module workers/levenshteinWorker
|
|
8
|
+
*/
|
|
9
|
+
import workerpool from '@danielsimonjr/workerpool';
|
|
10
|
+
/**
|
|
11
|
+
* Calculate Levenshtein distance between two strings.
|
|
12
|
+
*
|
|
13
|
+
* Uses dynamic programming matrix for efficient computation.
|
|
14
|
+
*
|
|
15
|
+
* @param s1 - First string
|
|
16
|
+
* @param s2 - Second string
|
|
17
|
+
* @returns Levenshtein distance (number of edits)
|
|
18
|
+
*/
|
|
19
|
+
export function levenshteinDistance(s1, s2) {
|
|
20
|
+
const len1 = s1.length;
|
|
21
|
+
const len2 = s2.length;
|
|
22
|
+
if (len1 === 0)
|
|
23
|
+
return len2;
|
|
24
|
+
if (len2 === 0)
|
|
25
|
+
return len1;
|
|
26
|
+
const matrix = [];
|
|
27
|
+
// Initialize first column
|
|
28
|
+
for (let i = 0; i <= len1; i++) {
|
|
29
|
+
matrix[i] = [i];
|
|
30
|
+
}
|
|
31
|
+
// Initialize first row
|
|
32
|
+
for (let j = 0; j <= len2; j++) {
|
|
33
|
+
matrix[0][j] = j;
|
|
34
|
+
}
|
|
35
|
+
// Fill matrix
|
|
36
|
+
for (let i = 1; i <= len1; i++) {
|
|
37
|
+
for (let j = 1; j <= len2; j++) {
|
|
38
|
+
const cost = s1[i - 1] === s2[j - 1] ? 0 : 1;
|
|
39
|
+
matrix[i][j] = Math.min(matrix[i - 1][j] + 1, // deletion
|
|
40
|
+
matrix[i][j - 1] + 1, // insertion
|
|
41
|
+
matrix[i - 1][j - 1] + cost // substitution
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return matrix[len1][len2];
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Calculate similarity score between two strings.
|
|
49
|
+
*
|
|
50
|
+
* @param s1 - First string
|
|
51
|
+
* @param s2 - Second string
|
|
52
|
+
* @returns Similarity score (0.0 to 1.0, where 1.0 is identical)
|
|
53
|
+
*/
|
|
54
|
+
export function similarity(s1, s2) {
|
|
55
|
+
// Exact match
|
|
56
|
+
if (s1 === s2)
|
|
57
|
+
return 1.0;
|
|
58
|
+
// One contains the other
|
|
59
|
+
if (s1.includes(s2) || s2.includes(s1))
|
|
60
|
+
return 1.0;
|
|
61
|
+
// Calculate Levenshtein-based similarity
|
|
62
|
+
const distance = levenshteinDistance(s1, s2);
|
|
63
|
+
const maxLength = Math.max(s1.length, s2.length);
|
|
64
|
+
return 1 - distance / maxLength;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Search entities for fuzzy matches.
|
|
68
|
+
*
|
|
69
|
+
* @param data - Worker input containing query, entities, and threshold
|
|
70
|
+
* @returns Array of match results
|
|
71
|
+
*/
|
|
72
|
+
export function searchEntities(data) {
|
|
73
|
+
const { query, entities, threshold } = data;
|
|
74
|
+
const queryLower = query.toLowerCase();
|
|
75
|
+
const results = [];
|
|
76
|
+
for (const entity of entities) {
|
|
77
|
+
// Check name similarity
|
|
78
|
+
const nameScore = similarity(queryLower, entity.nameLower);
|
|
79
|
+
if (nameScore >= threshold) {
|
|
80
|
+
results.push({ name: entity.name, score: nameScore, matchedIn: 'name' });
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
// Check observations
|
|
84
|
+
for (const obs of entity.observations) {
|
|
85
|
+
const obsScore = similarity(queryLower, obs);
|
|
86
|
+
if (obsScore >= threshold) {
|
|
87
|
+
results.push({ name: entity.name, score: obsScore, matchedIn: 'observation' });
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return results;
|
|
93
|
+
}
|
|
94
|
+
// Register worker methods with workerpool
|
|
95
|
+
// Cast to satisfy workerpool's generic type signature
|
|
96
|
+
workerpool.worker({
|
|
97
|
+
searchEntities: searchEntities,
|
|
98
|
+
});
|
package/package.json
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@danielsimonjr/memory-mcp",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Enhanced MCP memory server with hierarchies, compression, archiving, and
|
|
3
|
+
"version": "9.8.0",
|
|
4
|
+
"description": "Enhanced MCP memory server with hierarchies, compression, archiving, graph algorithms, semantic search, and 55 advanced tools",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=18.0.0"
|
|
8
|
+
},
|
|
6
9
|
"author": "Daniel Simon Jr. (https://github.com/danielsimonjr)",
|
|
7
10
|
"homepage": "https://github.com/danielsimonjr/memory-mcp",
|
|
8
11
|
"bugs": "https://github.com/danielsimonjr/memory-mcp/issues",
|
|
@@ -20,7 +23,9 @@
|
|
|
20
23
|
"tags",
|
|
21
24
|
"export",
|
|
22
25
|
"graphml",
|
|
23
|
-
"analytics"
|
|
26
|
+
"analytics",
|
|
27
|
+
"semantic-search",
|
|
28
|
+
"embeddings"
|
|
24
29
|
],
|
|
25
30
|
"type": "module",
|
|
26
31
|
"main": "./dist/index.js",
|
|
@@ -66,15 +71,23 @@
|
|
|
66
71
|
"prepare": "npm run build",
|
|
67
72
|
"watch": "tsc --watch",
|
|
68
73
|
"test": "vitest run --coverage",
|
|
69
|
-
"typecheck": "tsc --noEmit --noUnusedLocals --noUnusedParameters --strict"
|
|
74
|
+
"typecheck": "tsc --noEmit --noUnusedLocals --noUnusedParameters --strict",
|
|
75
|
+
"clean": "shx rm -rf dist",
|
|
76
|
+
"docs:deps": "npx tsx tools/create-dependency-graph/create-dependency-graph.ts"
|
|
70
77
|
},
|
|
71
78
|
"dependencies": {
|
|
79
|
+
"@danielsimonjr/workerpool": "^10.0.1",
|
|
72
80
|
"@modelcontextprotocol/sdk": "^1.21.1",
|
|
81
|
+
"async-mutex": "^0.5.0",
|
|
82
|
+
"better-sqlite3": "^11.7.0",
|
|
73
83
|
"zod": "^4.1.13"
|
|
74
84
|
},
|
|
75
85
|
"devDependencies": {
|
|
86
|
+
"@types/better-sqlite3": "^7.6.12",
|
|
87
|
+
"@types/js-yaml": "^4.0.9",
|
|
76
88
|
"@types/node": "^22",
|
|
77
89
|
"@vitest/coverage-v8": "^4.0.13",
|
|
90
|
+
"js-yaml": "^4.1.1",
|
|
78
91
|
"shx": "^0.4.0",
|
|
79
92
|
"typescript": "^5.6.2",
|
|
80
93
|
"vitest": "^4.0.13"
|