@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
|
@@ -2,20 +2,23 @@
|
|
|
2
2
|
* Entity Manager
|
|
3
3
|
*
|
|
4
4
|
* Handles CRUD operations for entities in the knowledge graph.
|
|
5
|
+
* Focused on core entity and tag operations only (Phase 4: Consolidate God Objects).
|
|
5
6
|
*
|
|
6
7
|
* @module core/EntityManager
|
|
7
8
|
*/
|
|
8
9
|
import { EntityNotFoundError, InvalidImportanceError, ValidationError } from '../utils/errors.js';
|
|
9
|
-
import { BatchCreateEntitiesSchema, UpdateEntitySchema, EntityNamesSchema } from '../utils/index.js';
|
|
10
|
+
import { BatchCreateEntitiesSchema, UpdateEntitySchema, EntityNamesSchema, checkCancellation, createProgressReporter, createProgress, } from '../utils/index.js';
|
|
10
11
|
import { GRAPH_LIMITS } from '../utils/constants.js';
|
|
11
12
|
/**
|
|
12
13
|
* Minimum importance value (least important).
|
|
14
|
+
* Note: Use IMPORTANCE_RANGE from constants.ts for external access.
|
|
13
15
|
*/
|
|
14
|
-
|
|
16
|
+
const MIN_IMPORTANCE = 0;
|
|
15
17
|
/**
|
|
16
18
|
* Maximum importance value (most important).
|
|
19
|
+
* Note: Use IMPORTANCE_RANGE from constants.ts for external access.
|
|
17
20
|
*/
|
|
18
|
-
|
|
21
|
+
const MAX_IMPORTANCE = 10;
|
|
19
22
|
/**
|
|
20
23
|
* Manages entity operations with automatic timestamp handling.
|
|
21
24
|
*/
|
|
@@ -33,9 +36,13 @@ export class EntityManager {
|
|
|
33
36
|
* - Normalizes all tags to lowercase for consistent searching
|
|
34
37
|
* - Validates importance values (must be between 0-10)
|
|
35
38
|
*
|
|
39
|
+
* Phase 9B: Supports progress tracking and cancellation via LongRunningOperationOptions.
|
|
40
|
+
*
|
|
36
41
|
* @param entities - Array of entities to create. Each entity must have a unique name.
|
|
42
|
+
* @param options - Optional progress/cancellation options (Phase 9B)
|
|
37
43
|
* @returns Promise resolving to array of newly created entities (excludes duplicates)
|
|
38
44
|
* @throws {InvalidImportanceError} If any entity has importance outside the valid range [0-10]
|
|
45
|
+
* @throws {OperationCancelledError} If operation is cancelled via signal (Phase 9B)
|
|
39
46
|
*
|
|
40
47
|
* @example
|
|
41
48
|
* ```typescript
|
|
@@ -55,24 +62,43 @@ export class EntityManager {
|
|
|
55
62
|
* { name: 'Bob', entityType: 'person', observations: [] },
|
|
56
63
|
* { name: 'Charlie', entityType: 'person', observations: [] }
|
|
57
64
|
* ]);
|
|
65
|
+
*
|
|
66
|
+
* // With progress tracking and cancellation (Phase 9B)
|
|
67
|
+
* const controller = new AbortController();
|
|
68
|
+
* const results = await manager.createEntities(largeEntityArray, {
|
|
69
|
+
* signal: controller.signal,
|
|
70
|
+
* onProgress: (p) => console.log(`${p.percentage}% complete`),
|
|
71
|
+
* });
|
|
58
72
|
* ```
|
|
59
73
|
*/
|
|
60
|
-
async createEntities(entities) {
|
|
74
|
+
async createEntities(entities, options) {
|
|
75
|
+
// Check for early cancellation
|
|
76
|
+
checkCancellation(options?.signal, 'createEntities');
|
|
61
77
|
// Validate input
|
|
62
78
|
const validation = BatchCreateEntitiesSchema.safeParse(entities);
|
|
63
79
|
if (!validation.success) {
|
|
64
80
|
const errors = validation.error.issues.map((e) => `${e.path.join('.')}: ${e.message}`);
|
|
65
81
|
throw new ValidationError('Invalid entity data', errors);
|
|
66
82
|
}
|
|
67
|
-
|
|
83
|
+
// Setup progress reporter
|
|
84
|
+
const reportProgress = createProgressReporter(options?.onProgress);
|
|
85
|
+
const total = entities.length;
|
|
86
|
+
reportProgress?.(createProgress(0, total, 'createEntities'));
|
|
87
|
+
// Use read-only graph for checking existing entities
|
|
88
|
+
const readGraph = await this.storage.loadGraph();
|
|
68
89
|
const timestamp = new Date().toISOString();
|
|
69
90
|
// Check graph size limits
|
|
70
|
-
const entitiesToAdd = entities.filter(e => !
|
|
71
|
-
if (
|
|
91
|
+
const entitiesToAdd = entities.filter(e => !readGraph.entities.some(existing => existing.name === e.name));
|
|
92
|
+
if (readGraph.entities.length + entitiesToAdd.length > GRAPH_LIMITS.MAX_ENTITIES) {
|
|
72
93
|
throw new ValidationError('Graph size limit exceeded', [`Adding ${entitiesToAdd.length} entities would exceed maximum of ${GRAPH_LIMITS.MAX_ENTITIES} entities`]);
|
|
73
94
|
}
|
|
74
|
-
|
|
75
|
-
|
|
95
|
+
// Check for cancellation before processing
|
|
96
|
+
checkCancellation(options?.signal, 'createEntities');
|
|
97
|
+
const newEntities = [];
|
|
98
|
+
let processed = 0;
|
|
99
|
+
for (const e of entitiesToAdd) {
|
|
100
|
+
// Check for cancellation periodically
|
|
101
|
+
checkCancellation(options?.signal, 'createEntities');
|
|
76
102
|
const entity = {
|
|
77
103
|
...e,
|
|
78
104
|
createdAt: e.createdAt || timestamp,
|
|
@@ -89,10 +115,22 @@ export class EntityManager {
|
|
|
89
115
|
}
|
|
90
116
|
entity.importance = e.importance;
|
|
91
117
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
118
|
+
newEntities.push(entity);
|
|
119
|
+
processed++;
|
|
120
|
+
reportProgress?.(createProgress(processed, entitiesToAdd.length, 'createEntities'));
|
|
121
|
+
}
|
|
122
|
+
// OPTIMIZED: Use append for single entity, bulk save for multiple
|
|
123
|
+
// (N individual appends is slower than one bulk write)
|
|
124
|
+
if (newEntities.length === 1) {
|
|
125
|
+
await this.storage.appendEntity(newEntities[0]);
|
|
126
|
+
}
|
|
127
|
+
else if (newEntities.length > 1) {
|
|
128
|
+
const graph = await this.storage.getGraphForMutation();
|
|
129
|
+
graph.entities.push(...newEntities);
|
|
130
|
+
await this.storage.saveGraph(graph);
|
|
131
|
+
}
|
|
132
|
+
// Report completion
|
|
133
|
+
reportProgress?.(createProgress(entitiesToAdd.length, entitiesToAdd.length, 'createEntities'));
|
|
96
134
|
return newEntities;
|
|
97
135
|
}
|
|
98
136
|
/**
|
|
@@ -127,9 +165,11 @@ export class EntityManager {
|
|
|
127
165
|
const errors = validation.error.issues.map((e) => `${e.path.join('.')}: ${e.message}`);
|
|
128
166
|
throw new ValidationError('Invalid entity names', errors);
|
|
129
167
|
}
|
|
130
|
-
const graph = await this.storage.
|
|
131
|
-
|
|
132
|
-
|
|
168
|
+
const graph = await this.storage.getGraphForMutation();
|
|
169
|
+
// OPTIMIZED: Use Set for O(1) lookups instead of O(n) includes()
|
|
170
|
+
const namesToDelete = new Set(entityNames);
|
|
171
|
+
graph.entities = graph.entities.filter(e => !namesToDelete.has(e.name));
|
|
172
|
+
graph.relations = graph.relations.filter(r => !namesToDelete.has(r.from) && !namesToDelete.has(r.to));
|
|
133
173
|
await this.storage.saveGraph(graph);
|
|
134
174
|
}
|
|
135
175
|
/**
|
|
@@ -205,7 +245,7 @@ export class EntityManager {
|
|
|
205
245
|
const errors = validation.error.issues.map((e) => `${e.path.join('.')}: ${e.message}`);
|
|
206
246
|
throw new ValidationError('Invalid update data', errors);
|
|
207
247
|
}
|
|
208
|
-
const graph = await this.storage.
|
|
248
|
+
const graph = await this.storage.getGraphForMutation();
|
|
209
249
|
const entity = graph.entities.find(e => e.name === name);
|
|
210
250
|
if (!entity) {
|
|
211
251
|
throw new EntityNotFoundError(name);
|
|
@@ -258,14 +298,18 @@ export class EntityManager {
|
|
|
258
298
|
throw new ValidationError('Invalid update data', errors);
|
|
259
299
|
}
|
|
260
300
|
}
|
|
261
|
-
const graph = await this.storage.
|
|
301
|
+
const graph = await this.storage.getGraphForMutation();
|
|
262
302
|
const timestamp = new Date().toISOString();
|
|
263
303
|
const updatedEntities = [];
|
|
304
|
+
// OPTIMIZED: Build Map for O(1) lookups instead of O(n) find() per update
|
|
305
|
+
const entityIndex = new Map();
|
|
306
|
+
graph.entities.forEach((e, i) => entityIndex.set(e.name, i));
|
|
264
307
|
for (const { name, updates: updateData } of updates) {
|
|
265
|
-
const
|
|
266
|
-
if (
|
|
308
|
+
const idx = entityIndex.get(name);
|
|
309
|
+
if (idx === undefined) {
|
|
267
310
|
throw new EntityNotFoundError(name);
|
|
268
311
|
}
|
|
312
|
+
const entity = graph.entities[idx];
|
|
269
313
|
// Apply updates
|
|
270
314
|
Object.assign(entity, updateData);
|
|
271
315
|
entity.lastModified = timestamp;
|
|
@@ -274,96 +318,9 @@ export class EntityManager {
|
|
|
274
318
|
await this.storage.saveGraph(graph);
|
|
275
319
|
return updatedEntities;
|
|
276
320
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
* This method performs the following operations:
|
|
281
|
-
* - Adds new observations to specified entities
|
|
282
|
-
* - Filters out duplicate observations (already present)
|
|
283
|
-
* - Updates lastModified timestamp only if new observations were added
|
|
284
|
-
*
|
|
285
|
-
* @param observations - Array of entity names and observations to add
|
|
286
|
-
* @returns Promise resolving to array of results showing which observations were added
|
|
287
|
-
* @throws {EntityNotFoundError} If any entity is not found
|
|
288
|
-
*
|
|
289
|
-
* @example
|
|
290
|
-
* ```typescript
|
|
291
|
-
* const manager = new EntityManager(storage);
|
|
292
|
-
*
|
|
293
|
-
* // Add observations to multiple entities
|
|
294
|
-
* const results = await manager.addObservations([
|
|
295
|
-
* { entityName: 'Alice', contents: ['Completed project X', 'Started project Y'] },
|
|
296
|
-
* { entityName: 'Bob', contents: ['Joined team meeting'] }
|
|
297
|
-
* ]);
|
|
298
|
-
*
|
|
299
|
-
* // Check what was added (duplicates are filtered out)
|
|
300
|
-
* results.forEach(r => {
|
|
301
|
-
* console.log(`${r.entityName}: added ${r.addedObservations.length} new observations`);
|
|
302
|
-
* });
|
|
303
|
-
* ```
|
|
304
|
-
*/
|
|
305
|
-
async addObservations(observations) {
|
|
306
|
-
const graph = await this.storage.loadGraph();
|
|
307
|
-
const timestamp = new Date().toISOString();
|
|
308
|
-
const results = observations.map(o => {
|
|
309
|
-
const entity = graph.entities.find(e => e.name === o.entityName);
|
|
310
|
-
if (!entity) {
|
|
311
|
-
throw new EntityNotFoundError(o.entityName);
|
|
312
|
-
}
|
|
313
|
-
const newObservations = o.contents.filter(content => !entity.observations.includes(content));
|
|
314
|
-
entity.observations.push(...newObservations);
|
|
315
|
-
// Update lastModified timestamp if observations were added
|
|
316
|
-
if (newObservations.length > 0) {
|
|
317
|
-
entity.lastModified = timestamp;
|
|
318
|
-
}
|
|
319
|
-
return { entityName: o.entityName, addedObservations: newObservations };
|
|
320
|
-
});
|
|
321
|
-
await this.storage.saveGraph(graph);
|
|
322
|
-
return results;
|
|
323
|
-
}
|
|
324
|
-
/**
|
|
325
|
-
* Delete observations from multiple entities in a single batch operation.
|
|
326
|
-
*
|
|
327
|
-
* This method performs the following operations:
|
|
328
|
-
* - Removes specified observations from entities
|
|
329
|
-
* - Updates lastModified timestamp only if observations were deleted
|
|
330
|
-
* - Silently ignores entities that don't exist (no error thrown)
|
|
331
|
-
*
|
|
332
|
-
* @param deletions - Array of entity names and observations to delete
|
|
333
|
-
* @returns Promise that resolves when deletion is complete
|
|
334
|
-
*
|
|
335
|
-
* @example
|
|
336
|
-
* ```typescript
|
|
337
|
-
* const manager = new EntityManager(storage);
|
|
338
|
-
*
|
|
339
|
-
* // Delete observations from multiple entities
|
|
340
|
-
* await manager.deleteObservations([
|
|
341
|
-
* { entityName: 'Alice', observations: ['Old observation 1', 'Old observation 2'] },
|
|
342
|
-
* { entityName: 'Bob', observations: ['Outdated info'] }
|
|
343
|
-
* ]);
|
|
344
|
-
*
|
|
345
|
-
* // Safe to delete from non-existent entities (no error)
|
|
346
|
-
* await manager.deleteObservations([
|
|
347
|
-
* { entityName: 'NonExistent', observations: ['Some text'] }
|
|
348
|
-
* ]); // No error thrown
|
|
349
|
-
* ```
|
|
350
|
-
*/
|
|
351
|
-
async deleteObservations(deletions) {
|
|
352
|
-
const graph = await this.storage.loadGraph();
|
|
353
|
-
const timestamp = new Date().toISOString();
|
|
354
|
-
deletions.forEach(d => {
|
|
355
|
-
const entity = graph.entities.find(e => e.name === d.entityName);
|
|
356
|
-
if (entity) {
|
|
357
|
-
const originalLength = entity.observations.length;
|
|
358
|
-
entity.observations = entity.observations.filter(o => !d.observations.includes(o));
|
|
359
|
-
// Update lastModified timestamp if observations were deleted
|
|
360
|
-
if (entity.observations.length < originalLength) {
|
|
361
|
-
entity.lastModified = timestamp;
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
});
|
|
365
|
-
await this.storage.saveGraph(graph);
|
|
366
|
-
}
|
|
321
|
+
// ============================================================
|
|
322
|
+
// TAG OPERATIONS
|
|
323
|
+
// ============================================================
|
|
367
324
|
/**
|
|
368
325
|
* Add tags to an entity.
|
|
369
326
|
*
|
|
@@ -375,25 +332,20 @@ export class EntityManager {
|
|
|
375
332
|
* @throws {EntityNotFoundError} If entity is not found
|
|
376
333
|
*/
|
|
377
334
|
async addTags(entityName, tags) {
|
|
378
|
-
|
|
379
|
-
const
|
|
380
|
-
const entity = graph.entities.find(e => e.name === entityName);
|
|
335
|
+
// OPTIMIZED: Use O(1) NameIndex lookup instead of loadGraph() + O(n) find()
|
|
336
|
+
const entity = this.storage.getEntityByName(entityName);
|
|
381
337
|
if (!entity) {
|
|
382
338
|
throw new EntityNotFoundError(entityName);
|
|
383
339
|
}
|
|
384
340
|
// Initialize tags array if it doesn't exist
|
|
385
|
-
|
|
386
|
-
entity.tags = [];
|
|
387
|
-
}
|
|
341
|
+
const existingTags = entity.tags || [];
|
|
388
342
|
// Normalize tags to lowercase and filter out duplicates
|
|
389
343
|
const normalizedTags = tags.map(tag => tag.toLowerCase());
|
|
390
|
-
const newTags = normalizedTags.filter(tag => !
|
|
391
|
-
entity.tags.push(...newTags);
|
|
392
|
-
// Update lastModified timestamp if tags were added
|
|
344
|
+
const newTags = normalizedTags.filter(tag => !existingTags.includes(tag));
|
|
393
345
|
if (newTags.length > 0) {
|
|
394
|
-
|
|
346
|
+
// OPTIMIZED: Use updateEntity for in-place update + append
|
|
347
|
+
await this.storage.updateEntity(entityName, { tags: [...existingTags, ...newTags] });
|
|
395
348
|
}
|
|
396
|
-
await this.storage.saveGraph(graph);
|
|
397
349
|
return { entityName, addedTags: newTags };
|
|
398
350
|
}
|
|
399
351
|
/**
|
|
@@ -405,7 +357,7 @@ export class EntityManager {
|
|
|
405
357
|
* @throws {EntityNotFoundError} If entity is not found
|
|
406
358
|
*/
|
|
407
359
|
async removeTags(entityName, tags) {
|
|
408
|
-
const graph = await this.storage.
|
|
360
|
+
const graph = await this.storage.getGraphForMutation();
|
|
409
361
|
const timestamp = new Date().toISOString();
|
|
410
362
|
const entity = graph.entities.find(e => e.name === entityName);
|
|
411
363
|
if (!entity) {
|
|
@@ -417,10 +369,12 @@ export class EntityManager {
|
|
|
417
369
|
// Normalize tags to lowercase
|
|
418
370
|
const normalizedTags = tags.map(tag => tag.toLowerCase());
|
|
419
371
|
const originalLength = entity.tags.length;
|
|
372
|
+
// Capture existing tags (lowercase) BEFORE filtering to accurately track removals
|
|
373
|
+
const existingTagsLower = entity.tags.map(t => t.toLowerCase());
|
|
420
374
|
// Filter out the tags to remove
|
|
421
375
|
entity.tags = entity.tags.filter(tag => !normalizedTags.includes(tag.toLowerCase()));
|
|
422
|
-
|
|
423
|
-
|
|
376
|
+
// A tag was removed if it existed in the original tags
|
|
377
|
+
const removedTags = normalizedTags.filter(tag => existingTagsLower.includes(tag));
|
|
424
378
|
// Update lastModified timestamp if tags were removed
|
|
425
379
|
if (entity.tags.length < originalLength) {
|
|
426
380
|
entity.lastModified = timestamp;
|
|
@@ -438,19 +392,17 @@ export class EntityManager {
|
|
|
438
392
|
* @throws {Error} If importance is out of range
|
|
439
393
|
*/
|
|
440
394
|
async setImportance(entityName, importance) {
|
|
441
|
-
const graph = await this.storage.loadGraph();
|
|
442
|
-
const timestamp = new Date().toISOString();
|
|
443
395
|
// Validate importance range (0-10)
|
|
444
396
|
if (importance < 0 || importance > 10) {
|
|
445
397
|
throw new Error(`Importance must be between 0 and 10, got ${importance}`);
|
|
446
398
|
}
|
|
447
|
-
|
|
399
|
+
// OPTIMIZED: Use O(1) NameIndex lookup instead of loadGraph() + O(n) find()
|
|
400
|
+
const entity = this.storage.getEntityByName(entityName);
|
|
448
401
|
if (!entity) {
|
|
449
402
|
throw new EntityNotFoundError(entityName);
|
|
450
403
|
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
await this.storage.saveGraph(graph);
|
|
404
|
+
// Use updateEntity for in-place update + append
|
|
405
|
+
await this.storage.updateEntity(entityName, { importance });
|
|
454
406
|
return { entityName, importance };
|
|
455
407
|
}
|
|
456
408
|
/**
|
|
@@ -461,7 +413,7 @@ export class EntityManager {
|
|
|
461
413
|
* @returns Array of results showing which tags were added to each entity
|
|
462
414
|
*/
|
|
463
415
|
async addTagsToMultipleEntities(entityNames, tags) {
|
|
464
|
-
const graph = await this.storage.
|
|
416
|
+
const graph = await this.storage.getGraphForMutation();
|
|
465
417
|
const timestamp = new Date().toISOString();
|
|
466
418
|
const normalizedTags = tags.map(tag => tag.toLowerCase());
|
|
467
419
|
const results = [];
|
|
@@ -494,7 +446,7 @@ export class EntityManager {
|
|
|
494
446
|
* @returns Result with affected entities and count
|
|
495
447
|
*/
|
|
496
448
|
async replaceTag(oldTag, newTag) {
|
|
497
|
-
const graph = await this.storage.
|
|
449
|
+
const graph = await this.storage.getGraphForMutation();
|
|
498
450
|
const timestamp = new Date().toISOString();
|
|
499
451
|
const normalizedOldTag = oldTag.toLowerCase();
|
|
500
452
|
const normalizedNewTag = newTag.toLowerCase();
|
|
@@ -524,7 +476,7 @@ export class EntityManager {
|
|
|
524
476
|
* @returns Object with affected entity names and count
|
|
525
477
|
*/
|
|
526
478
|
async mergeTags(tag1, tag2, targetTag) {
|
|
527
|
-
const graph = await this.storage.
|
|
479
|
+
const graph = await this.storage.getGraphForMutation();
|
|
528
480
|
const timestamp = new Date().toISOString();
|
|
529
481
|
const normalizedTag1 = tag1.toLowerCase();
|
|
530
482
|
const normalizedTag2 = tag2.toLowerCase();
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Graph Event Emitter
|
|
3
|
+
*
|
|
4
|
+
* Phase 10 Sprint 2: Provides event-based notifications for graph changes.
|
|
5
|
+
* Enables loose coupling between graph operations and dependent systems
|
|
6
|
+
* like search indexes, analytics, and external integrations.
|
|
7
|
+
*
|
|
8
|
+
* @module core/GraphEventEmitter
|
|
9
|
+
*/
|
|
10
|
+
import type { GraphEventType, GraphEvent, GraphEventListener, GraphEventMap, Entity, Relation } from '../types/index.js';
|
|
11
|
+
/**
|
|
12
|
+
* Phase 10 Sprint 2: Event emitter for graph change notifications.
|
|
13
|
+
*
|
|
14
|
+
* Provides a type-safe event system for subscribing to and emitting
|
|
15
|
+
* graph change events. Supports wildcard listeners for all events.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* const emitter = new GraphEventEmitter();
|
|
20
|
+
*
|
|
21
|
+
* // Listen to specific event types
|
|
22
|
+
* emitter.on('entity:created', (event) => {
|
|
23
|
+
* console.log(`Entity ${event.entity.name} created`);
|
|
24
|
+
* });
|
|
25
|
+
*
|
|
26
|
+
* // Listen to all events
|
|
27
|
+
* emitter.onAny((event) => {
|
|
28
|
+
* console.log(`Event: ${event.type}`);
|
|
29
|
+
* });
|
|
30
|
+
*
|
|
31
|
+
* // Emit an event
|
|
32
|
+
* emitter.emitEntityCreated(entity);
|
|
33
|
+
*
|
|
34
|
+
* // Remove listener
|
|
35
|
+
* const unsubscribe = emitter.on('entity:deleted', handler);
|
|
36
|
+
* unsubscribe();
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export declare class GraphEventEmitter {
|
|
40
|
+
/**
|
|
41
|
+
* Map of event types to their registered listeners.
|
|
42
|
+
*/
|
|
43
|
+
private listeners;
|
|
44
|
+
/**
|
|
45
|
+
* Listeners that receive all events regardless of type.
|
|
46
|
+
*/
|
|
47
|
+
private wildcardListeners;
|
|
48
|
+
/**
|
|
49
|
+
* Whether to suppress errors from listeners (default: true).
|
|
50
|
+
* When true, listener errors are logged but don't stop event propagation.
|
|
51
|
+
*/
|
|
52
|
+
private suppressListenerErrors;
|
|
53
|
+
/**
|
|
54
|
+
* Create a new GraphEventEmitter instance.
|
|
55
|
+
*
|
|
56
|
+
* @param options - Optional configuration
|
|
57
|
+
*/
|
|
58
|
+
constructor(options?: {
|
|
59
|
+
suppressListenerErrors?: boolean;
|
|
60
|
+
});
|
|
61
|
+
/**
|
|
62
|
+
* Subscribe to a specific event type.
|
|
63
|
+
*
|
|
64
|
+
* @template K - The event type key
|
|
65
|
+
* @param eventType - The event type to listen for
|
|
66
|
+
* @param listener - Callback function to invoke when event occurs
|
|
67
|
+
* @returns Unsubscribe function to remove the listener
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```typescript
|
|
71
|
+
* const unsubscribe = emitter.on('entity:created', (event) => {
|
|
72
|
+
* console.log(`Created: ${event.entity.name}`);
|
|
73
|
+
* });
|
|
74
|
+
*
|
|
75
|
+
* // Later: unsubscribe();
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
on<K extends GraphEventType>(eventType: K, listener: GraphEventListener<GraphEventMap[K]>): () => void;
|
|
79
|
+
/**
|
|
80
|
+
* Unsubscribe from a specific event type.
|
|
81
|
+
*
|
|
82
|
+
* @template K - The event type key
|
|
83
|
+
* @param eventType - The event type to unsubscribe from
|
|
84
|
+
* @param listener - The listener function to remove
|
|
85
|
+
*/
|
|
86
|
+
off<K extends GraphEventType>(eventType: K, listener: GraphEventListener<GraphEventMap[K]>): void;
|
|
87
|
+
/**
|
|
88
|
+
* Subscribe to all event types.
|
|
89
|
+
*
|
|
90
|
+
* @param listener - Callback function to invoke for any event
|
|
91
|
+
* @returns Unsubscribe function to remove the listener
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```typescript
|
|
95
|
+
* emitter.onAny((event) => {
|
|
96
|
+
* console.log(`Event: ${event.type} at ${event.timestamp}`);
|
|
97
|
+
* });
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
onAny(listener: GraphEventListener<GraphEvent>): () => void;
|
|
101
|
+
/**
|
|
102
|
+
* Unsubscribe from all events.
|
|
103
|
+
*
|
|
104
|
+
* @param listener - The listener function to remove
|
|
105
|
+
*/
|
|
106
|
+
offAny(listener: GraphEventListener<GraphEvent>): void;
|
|
107
|
+
/**
|
|
108
|
+
* Subscribe to an event type, but only receive the first occurrence.
|
|
109
|
+
*
|
|
110
|
+
* @template K - The event type key
|
|
111
|
+
* @param eventType - The event type to listen for once
|
|
112
|
+
* @param listener - Callback function to invoke once
|
|
113
|
+
* @returns Unsubscribe function to cancel before event occurs
|
|
114
|
+
*/
|
|
115
|
+
once<K extends GraphEventType>(eventType: K, listener: GraphEventListener<GraphEventMap[K]>): () => void;
|
|
116
|
+
/**
|
|
117
|
+
* Remove all listeners for all event types.
|
|
118
|
+
*/
|
|
119
|
+
removeAllListeners(): void;
|
|
120
|
+
/**
|
|
121
|
+
* Get the count of listeners for a specific event type.
|
|
122
|
+
*
|
|
123
|
+
* @param eventType - The event type to count listeners for
|
|
124
|
+
* @returns Number of listeners registered
|
|
125
|
+
*/
|
|
126
|
+
listenerCount(eventType?: GraphEventType): number;
|
|
127
|
+
/**
|
|
128
|
+
* Emit an event to all registered listeners.
|
|
129
|
+
*
|
|
130
|
+
* @param event - The event to emit
|
|
131
|
+
*/
|
|
132
|
+
emit(event: GraphEvent): void;
|
|
133
|
+
/**
|
|
134
|
+
* Emit an entity:created event.
|
|
135
|
+
*
|
|
136
|
+
* @param entity - The entity that was created
|
|
137
|
+
*/
|
|
138
|
+
emitEntityCreated(entity: Entity): void;
|
|
139
|
+
/**
|
|
140
|
+
* Emit an entity:updated event.
|
|
141
|
+
*
|
|
142
|
+
* @param entityName - Name of the updated entity
|
|
143
|
+
* @param changes - The changes that were applied
|
|
144
|
+
* @param previousValues - Optional previous values before update
|
|
145
|
+
*/
|
|
146
|
+
emitEntityUpdated(entityName: string, changes: Partial<Entity>, previousValues?: Partial<Entity>): void;
|
|
147
|
+
/**
|
|
148
|
+
* Emit an entity:deleted event.
|
|
149
|
+
*
|
|
150
|
+
* @param entityName - Name of the deleted entity
|
|
151
|
+
* @param entity - Optional entity data before deletion
|
|
152
|
+
*/
|
|
153
|
+
emitEntityDeleted(entityName: string, entity?: Entity): void;
|
|
154
|
+
/**
|
|
155
|
+
* Emit a relation:created event.
|
|
156
|
+
*
|
|
157
|
+
* @param relation - The relation that was created
|
|
158
|
+
*/
|
|
159
|
+
emitRelationCreated(relation: Relation): void;
|
|
160
|
+
/**
|
|
161
|
+
* Emit a relation:deleted event.
|
|
162
|
+
*
|
|
163
|
+
* @param from - Source entity name
|
|
164
|
+
* @param to - Target entity name
|
|
165
|
+
* @param relationType - Type of the deleted relation
|
|
166
|
+
*/
|
|
167
|
+
emitRelationDeleted(from: string, to: string, relationType: string): void;
|
|
168
|
+
/**
|
|
169
|
+
* Emit an observation:added event.
|
|
170
|
+
*
|
|
171
|
+
* @param entityName - Name of the entity
|
|
172
|
+
* @param observations - Observations that were added
|
|
173
|
+
*/
|
|
174
|
+
emitObservationAdded(entityName: string, observations: string[]): void;
|
|
175
|
+
/**
|
|
176
|
+
* Emit an observation:deleted event.
|
|
177
|
+
*
|
|
178
|
+
* @param entityName - Name of the entity
|
|
179
|
+
* @param observations - Observations that were deleted
|
|
180
|
+
*/
|
|
181
|
+
emitObservationDeleted(entityName: string, observations: string[]): void;
|
|
182
|
+
/**
|
|
183
|
+
* Emit a graph:saved event.
|
|
184
|
+
*
|
|
185
|
+
* @param entityCount - Number of entities in the saved graph
|
|
186
|
+
* @param relationCount - Number of relations in the saved graph
|
|
187
|
+
*/
|
|
188
|
+
emitGraphSaved(entityCount: number, relationCount: number): void;
|
|
189
|
+
/**
|
|
190
|
+
* Emit a graph:loaded event.
|
|
191
|
+
*
|
|
192
|
+
* @param entityCount - Number of entities in the loaded graph
|
|
193
|
+
* @param relationCount - Number of relations in the loaded graph
|
|
194
|
+
*/
|
|
195
|
+
emitGraphLoaded(entityCount: number, relationCount: number): void;
|
|
196
|
+
/**
|
|
197
|
+
* Safely invoke a listener, optionally catching errors.
|
|
198
|
+
* @private
|
|
199
|
+
*/
|
|
200
|
+
private invokeListener;
|
|
201
|
+
}
|
|
202
|
+
//# sourceMappingURL=GraphEventEmitter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GraphEventEmitter.d.ts","sourceRoot":"","sources":["../../src/core/GraphEventEmitter.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EACV,cAAc,EACd,UAAU,EACV,kBAAkB,EAClB,aAAa,EACb,MAAM,EACN,QAAQ,EAUT,MAAM,mBAAmB,CAAC;AAE3B;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,qBAAa,iBAAiB;IAC5B;;OAEG;IACH,OAAO,CAAC,SAAS,CAAgE;IAEjF;;OAEG;IACH,OAAO,CAAC,iBAAiB,CAAkD;IAE3E;;;OAGG;IACH,OAAO,CAAC,sBAAsB,CAAiB;IAE/C;;;;OAIG;gBACS,OAAO,CAAC,EAAE;QAAE,sBAAsB,CAAC,EAAE,OAAO,CAAA;KAAE;IAQ1D;;;;;;;;;;;;;;;;OAgBG;IACH,EAAE,CAAC,CAAC,SAAS,cAAc,EACzB,SAAS,EAAE,CAAC,EACZ,QAAQ,EAAE,kBAAkB,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,GAC7C,MAAM,IAAI;IAYb;;;;;;OAMG;IACH,GAAG,CAAC,CAAC,SAAS,cAAc,EAC1B,SAAS,EAAE,CAAC,EACZ,QAAQ,EAAE,kBAAkB,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,GAC7C,IAAI;IAOP;;;;;;;;;;;;OAYG;IACH,KAAK,CAAC,QAAQ,EAAE,kBAAkB,CAAC,UAAU,CAAC,GAAG,MAAM,IAAI;IAO3D;;;;OAIG;IACH,MAAM,CAAC,QAAQ,EAAE,kBAAkB,CAAC,UAAU,CAAC,GAAG,IAAI;IAItD;;;;;;;OAOG;IACH,IAAI,CAAC,CAAC,SAAS,cAAc,EAC3B,SAAS,EAAE,CAAC,EACZ,QAAQ,EAAE,kBAAkB,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,GAC7C,MAAM,IAAI;IASb;;OAEG;IACH,kBAAkB,IAAI,IAAI;IAK1B;;;;;OAKG;IACH,aAAa,CAAC,SAAS,CAAC,EAAE,cAAc,GAAG,MAAM;IAcjD;;;;OAIG;IACH,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;IAe7B;;;;OAIG;IACH,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IASvC;;;;;;OAMG;IACH,iBAAiB,CACf,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,EACxB,cAAc,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,GAC/B,IAAI;IAWP;;;;;OAKG;IACH,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI;IAU5D;;;;OAIG;IACH,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI;IAS7C;;;;;;OAMG;IACH,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI;IAWzE;;;;;OAKG;IACH,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,IAAI;IAYtE;;;;;OAKG;IACH,sBAAsB,CAAC,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,IAAI;IAYxE;;;;;OAKG;IACH,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,IAAI;IAUhE;;;;;OAKG;IACH,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,IAAI;IAYjE;;;OAGG;IACH,OAAO,CAAC,cAAc;CAYvB"}
|