@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.
Files changed (207) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +2000 -194
  3. package/dist/__tests__/file-path.test.js +5 -5
  4. package/dist/__tests__/knowledge-graph.test.js +3 -8
  5. package/dist/core/EntityManager.d.ts +266 -0
  6. package/dist/core/EntityManager.d.ts.map +1 -0
  7. package/dist/core/EntityManager.js +85 -133
  8. package/dist/core/GraphEventEmitter.d.ts +202 -0
  9. package/dist/core/GraphEventEmitter.d.ts.map +1 -0
  10. package/dist/core/GraphEventEmitter.js +346 -0
  11. package/dist/core/GraphStorage.d.ts +395 -0
  12. package/dist/core/GraphStorage.d.ts.map +1 -0
  13. package/dist/core/GraphStorage.js +643 -31
  14. package/dist/core/GraphTraversal.d.ts +141 -0
  15. package/dist/core/GraphTraversal.d.ts.map +1 -0
  16. package/dist/core/GraphTraversal.js +573 -0
  17. package/dist/core/HierarchyManager.d.ts +111 -0
  18. package/dist/core/HierarchyManager.d.ts.map +1 -0
  19. package/dist/{features → core}/HierarchyManager.js +14 -9
  20. package/dist/core/ManagerContext.d.ts +72 -0
  21. package/dist/core/ManagerContext.d.ts.map +1 -0
  22. package/dist/core/ManagerContext.js +118 -0
  23. package/dist/core/ObservationManager.d.ts +85 -0
  24. package/dist/core/ObservationManager.d.ts.map +1 -0
  25. package/dist/core/ObservationManager.js +51 -57
  26. package/dist/core/RelationManager.d.ts +131 -0
  27. package/dist/core/RelationManager.d.ts.map +1 -0
  28. package/dist/core/RelationManager.js +31 -7
  29. package/dist/core/SQLiteStorage.d.ts +354 -0
  30. package/dist/core/SQLiteStorage.d.ts.map +1 -0
  31. package/dist/core/SQLiteStorage.js +917 -0
  32. package/dist/core/StorageFactory.d.ts +45 -0
  33. package/dist/core/StorageFactory.d.ts.map +1 -0
  34. package/dist/core/StorageFactory.js +64 -0
  35. package/dist/core/TransactionManager.d.ts +464 -0
  36. package/dist/core/TransactionManager.d.ts.map +1 -0
  37. package/dist/core/TransactionManager.js +490 -13
  38. package/dist/core/index.d.ts +17 -0
  39. package/dist/core/index.d.ts.map +1 -0
  40. package/dist/core/index.js +12 -2
  41. package/dist/features/AnalyticsManager.d.ts +44 -0
  42. package/dist/features/AnalyticsManager.d.ts.map +1 -0
  43. package/dist/features/AnalyticsManager.js +14 -13
  44. package/dist/features/ArchiveManager.d.ts +133 -0
  45. package/dist/features/ArchiveManager.d.ts.map +1 -0
  46. package/dist/features/ArchiveManager.js +221 -14
  47. package/dist/features/CompressionManager.d.ts +117 -0
  48. package/dist/features/CompressionManager.d.ts.map +1 -0
  49. package/dist/features/CompressionManager.js +189 -20
  50. package/dist/features/IOManager.d.ts +225 -0
  51. package/dist/features/IOManager.d.ts.map +1 -0
  52. package/dist/features/IOManager.js +1041 -0
  53. package/dist/features/StreamingExporter.d.ts +123 -0
  54. package/dist/features/StreamingExporter.d.ts.map +1 -0
  55. package/dist/features/StreamingExporter.js +203 -0
  56. package/dist/features/TagManager.d.ts +147 -0
  57. package/dist/features/TagManager.d.ts.map +1 -0
  58. package/dist/features/index.d.ts +12 -0
  59. package/dist/features/index.d.ts.map +1 -0
  60. package/dist/features/index.js +5 -6
  61. package/dist/index.d.ts +9 -0
  62. package/dist/index.d.ts.map +1 -0
  63. package/dist/index.js +12 -45
  64. package/dist/memory.jsonl +1 -18
  65. package/dist/search/BasicSearch.d.ts +51 -0
  66. package/dist/search/BasicSearch.d.ts.map +1 -0
  67. package/dist/search/BasicSearch.js +9 -3
  68. package/dist/search/BooleanSearch.d.ts +98 -0
  69. package/dist/search/BooleanSearch.d.ts.map +1 -0
  70. package/dist/search/BooleanSearch.js +156 -9
  71. package/dist/search/EmbeddingService.d.ts +178 -0
  72. package/dist/search/EmbeddingService.d.ts.map +1 -0
  73. package/dist/search/EmbeddingService.js +358 -0
  74. package/dist/search/FuzzySearch.d.ts +118 -0
  75. package/dist/search/FuzzySearch.d.ts.map +1 -0
  76. package/dist/search/FuzzySearch.js +241 -25
  77. package/dist/search/QueryCostEstimator.d.ts +111 -0
  78. package/dist/search/QueryCostEstimator.d.ts.map +1 -0
  79. package/dist/search/QueryCostEstimator.js +355 -0
  80. package/dist/search/RankedSearch.d.ts +71 -0
  81. package/dist/search/RankedSearch.d.ts.map +1 -0
  82. package/dist/search/RankedSearch.js +54 -6
  83. package/dist/search/SavedSearchManager.d.ts +79 -0
  84. package/dist/search/SavedSearchManager.d.ts.map +1 -0
  85. package/dist/search/SearchFilterChain.d.ts +120 -0
  86. package/dist/search/SearchFilterChain.d.ts.map +1 -0
  87. package/dist/search/SearchFilterChain.js +2 -4
  88. package/dist/search/SearchManager.d.ts +326 -0
  89. package/dist/search/SearchManager.d.ts.map +1 -0
  90. package/dist/search/SearchManager.js +148 -0
  91. package/dist/search/SearchSuggestions.d.ts +27 -0
  92. package/dist/search/SearchSuggestions.d.ts.map +1 -0
  93. package/dist/search/SearchSuggestions.js +1 -1
  94. package/dist/search/SemanticSearch.d.ts +149 -0
  95. package/dist/search/SemanticSearch.d.ts.map +1 -0
  96. package/dist/search/SemanticSearch.js +323 -0
  97. package/dist/search/TFIDFEventSync.d.ts +85 -0
  98. package/dist/search/TFIDFEventSync.d.ts.map +1 -0
  99. package/dist/search/TFIDFEventSync.js +133 -0
  100. package/dist/search/TFIDFIndexManager.d.ts +151 -0
  101. package/dist/search/TFIDFIndexManager.d.ts.map +1 -0
  102. package/dist/search/TFIDFIndexManager.js +232 -17
  103. package/dist/search/VectorStore.d.ts +235 -0
  104. package/dist/search/VectorStore.d.ts.map +1 -0
  105. package/dist/search/VectorStore.js +311 -0
  106. package/dist/search/index.d.ts +21 -0
  107. package/dist/search/index.d.ts.map +1 -0
  108. package/dist/search/index.js +12 -0
  109. package/dist/server/MCPServer.d.ts +21 -0
  110. package/dist/server/MCPServer.d.ts.map +1 -0
  111. package/dist/server/MCPServer.js +4 -4
  112. package/dist/server/responseCompressor.d.ts +94 -0
  113. package/dist/server/responseCompressor.d.ts.map +1 -0
  114. package/dist/server/responseCompressor.js +127 -0
  115. package/dist/server/toolDefinitions.d.ts +27 -0
  116. package/dist/server/toolDefinitions.d.ts.map +1 -0
  117. package/dist/server/toolDefinitions.js +189 -18
  118. package/dist/server/toolHandlers.d.ts +41 -0
  119. package/dist/server/toolHandlers.d.ts.map +1 -0
  120. package/dist/server/toolHandlers.js +467 -75
  121. package/dist/types/index.d.ts +13 -0
  122. package/dist/types/index.d.ts.map +1 -0
  123. package/dist/types/index.js +1 -1
  124. package/dist/types/types.d.ts +1654 -0
  125. package/dist/types/types.d.ts.map +1 -0
  126. package/dist/types/types.js +9 -0
  127. package/dist/utils/compressedCache.d.ts +192 -0
  128. package/dist/utils/compressedCache.d.ts.map +1 -0
  129. package/dist/utils/compressedCache.js +309 -0
  130. package/dist/utils/compressionUtil.d.ts +214 -0
  131. package/dist/utils/compressionUtil.d.ts.map +1 -0
  132. package/dist/utils/compressionUtil.js +247 -0
  133. package/dist/utils/constants.d.ts +245 -0
  134. package/dist/utils/constants.d.ts.map +1 -0
  135. package/dist/utils/constants.js +124 -0
  136. package/dist/utils/entityUtils.d.ts +321 -0
  137. package/dist/utils/entityUtils.d.ts.map +1 -0
  138. package/dist/utils/entityUtils.js +434 -4
  139. package/dist/utils/errors.d.ts +95 -0
  140. package/dist/utils/errors.d.ts.map +1 -0
  141. package/dist/utils/errors.js +24 -0
  142. package/dist/utils/formatters.d.ts +145 -0
  143. package/dist/utils/formatters.d.ts.map +1 -0
  144. package/dist/utils/{paginationUtils.js → formatters.js} +54 -3
  145. package/dist/utils/index.d.ts +23 -0
  146. package/dist/utils/index.d.ts.map +1 -0
  147. package/dist/utils/index.js +69 -31
  148. package/dist/utils/indexes.d.ts +270 -0
  149. package/dist/utils/indexes.d.ts.map +1 -0
  150. package/dist/utils/indexes.js +526 -0
  151. package/dist/utils/logger.d.ts +24 -0
  152. package/dist/utils/logger.d.ts.map +1 -0
  153. package/dist/utils/operationUtils.d.ts +124 -0
  154. package/dist/utils/operationUtils.d.ts.map +1 -0
  155. package/dist/utils/operationUtils.js +175 -0
  156. package/dist/utils/parallelUtils.d.ts +72 -0
  157. package/dist/utils/parallelUtils.d.ts.map +1 -0
  158. package/dist/utils/parallelUtils.js +169 -0
  159. package/dist/utils/schemas.d.ts +374 -0
  160. package/dist/utils/schemas.d.ts.map +1 -0
  161. package/dist/utils/schemas.js +302 -2
  162. package/dist/utils/searchAlgorithms.d.ts +99 -0
  163. package/dist/utils/searchAlgorithms.d.ts.map +1 -0
  164. package/dist/utils/searchAlgorithms.js +167 -0
  165. package/dist/utils/searchCache.d.ts +108 -0
  166. package/dist/utils/searchCache.d.ts.map +1 -0
  167. package/dist/utils/taskScheduler.d.ts +290 -0
  168. package/dist/utils/taskScheduler.d.ts.map +1 -0
  169. package/dist/utils/taskScheduler.js +466 -0
  170. package/dist/workers/index.d.ts +12 -0
  171. package/dist/workers/index.d.ts.map +1 -0
  172. package/dist/workers/index.js +9 -0
  173. package/dist/workers/levenshteinWorker.d.ts +60 -0
  174. package/dist/workers/levenshteinWorker.d.ts.map +1 -0
  175. package/dist/workers/levenshteinWorker.js +98 -0
  176. package/package.json +17 -4
  177. package/dist/__tests__/edge-cases/edge-cases.test.js +0 -406
  178. package/dist/__tests__/integration/workflows.test.js +0 -449
  179. package/dist/__tests__/performance/benchmarks.test.js +0 -413
  180. package/dist/__tests__/unit/core/EntityManager.test.js +0 -334
  181. package/dist/__tests__/unit/core/GraphStorage.test.js +0 -205
  182. package/dist/__tests__/unit/core/RelationManager.test.js +0 -274
  183. package/dist/__tests__/unit/features/CompressionManager.test.js +0 -350
  184. package/dist/__tests__/unit/search/BasicSearch.test.js +0 -311
  185. package/dist/__tests__/unit/search/BooleanSearch.test.js +0 -432
  186. package/dist/__tests__/unit/search/FuzzySearch.test.js +0 -448
  187. package/dist/__tests__/unit/search/RankedSearch.test.js +0 -379
  188. package/dist/__tests__/unit/utils/levenshtein.test.js +0 -77
  189. package/dist/core/KnowledgeGraphManager.js +0 -423
  190. package/dist/features/BackupManager.js +0 -311
  191. package/dist/features/ExportManager.js +0 -305
  192. package/dist/features/ImportExportManager.js +0 -50
  193. package/dist/features/ImportManager.js +0 -328
  194. package/dist/types/analytics.types.js +0 -6
  195. package/dist/types/entity.types.js +0 -7
  196. package/dist/types/import-export.types.js +0 -7
  197. package/dist/types/search.types.js +0 -7
  198. package/dist/types/tag.types.js +0 -6
  199. package/dist/utils/dateUtils.js +0 -89
  200. package/dist/utils/filterUtils.js +0 -155
  201. package/dist/utils/levenshtein.js +0 -62
  202. package/dist/utils/pathUtils.js +0 -115
  203. package/dist/utils/responseFormatter.js +0 -55
  204. package/dist/utils/tagUtils.js +0 -107
  205. package/dist/utils/tfidf.js +0 -90
  206. package/dist/utils/validationHelper.js +0 -99
  207. package/dist/utils/validationUtils.js +0 -109
@@ -1,18 +1,21 @@
1
1
  /**
2
- * Validation Schemas
2
+ * Validation Schemas and Helpers
3
3
  *
4
- * Zod schemas for input validation across the memory system.
4
+ * Consolidated module for Zod schemas and validation utilities.
5
5
  * Provides runtime type safety and data validation.
6
6
  *
7
7
  * @module utils/schemas
8
8
  */
9
9
  import { z } from 'zod';
10
10
  import { IMPORTANCE_RANGE } from './constants.js';
11
+ import { ValidationError } from './errors.js';
12
+ // ==================== Constants ====================
11
13
  /**
12
14
  * Importance range constants (imported from centralized constants).
13
15
  */
14
16
  const MIN_IMPORTANCE = IMPORTANCE_RANGE.MIN;
15
17
  const MAX_IMPORTANCE = IMPORTANCE_RANGE.MAX;
18
+ // ==================== Base Schema Components ====================
16
19
  /**
17
20
  * ISO 8601 date string validation.
18
21
  * Accepts standard ISO format: YYYY-MM-DDTHH:mm:ss.sssZ
@@ -66,6 +69,7 @@ const relationTypeSchema = z.string()
66
69
  .min(1, 'Relation type cannot be empty')
67
70
  .max(100, 'Relation type cannot exceed 100 characters')
68
71
  .trim();
72
+ // ==================== Entity Schemas ====================
69
73
  /**
70
74
  * Complete Entity schema with all fields.
71
75
  * Used for validating full entity objects including timestamps.
@@ -107,6 +111,7 @@ export const UpdateEntitySchema = z.object({
107
111
  importance: importanceSchema.optional(),
108
112
  parentId: entityNameSchema.optional(),
109
113
  });
114
+ // ==================== Relation Schemas ====================
110
115
  /**
111
116
  * Complete Relation schema with all fields.
112
117
  * Used for validating full relation objects including timestamps.
@@ -130,6 +135,7 @@ export const CreateRelationSchema = z.object({
130
135
  createdAt: isoDateSchema.optional(),
131
136
  lastModified: isoDateSchema.optional(),
132
137
  });
138
+ // ==================== Search Schemas ====================
133
139
  /**
134
140
  * Search query validation.
135
141
  * Validates text search queries with reasonable length constraints.
@@ -145,6 +151,7 @@ export const DateRangeSchema = z.object({
145
151
  start: isoDateSchema,
146
152
  end: isoDateSchema,
147
153
  }).refine((data) => new Date(data.start) <= new Date(data.end), { message: 'Start date must be before or equal to end date' });
154
+ // ==================== Tag Schemas ====================
148
155
  /**
149
156
  * Tag alias validation for TagManager.
150
157
  */
@@ -152,10 +159,12 @@ export const TagAliasSchema = z.object({
152
159
  canonical: tagSchema,
153
160
  aliases: z.array(tagSchema).min(1, 'Must have at least one alias'),
154
161
  });
162
+ // ==================== Export Schemas ====================
155
163
  /**
156
164
  * Export format validation.
157
165
  */
158
166
  export const ExportFormatSchema = z.enum(['json', 'graphml', 'csv']);
167
+ // ==================== Batch Operation Schemas ====================
159
168
  /**
160
169
  * Batch entity creation validation.
161
170
  * Validates array of entities with maximum constraints.
@@ -182,3 +191,294 @@ export const EntityNamesSchema = z.array(entityNameSchema)
182
191
  export const DeleteRelationsSchema = z.array(CreateRelationSchema)
183
192
  .min(1, 'Must specify at least one relation')
184
193
  .max(1000, 'Cannot delete more than 1000 relations in a single batch');
194
+ // ==================== Observation Schemas ====================
195
+ /**
196
+ * Single observation input for add operations.
197
+ * Empty contents array is allowed (no-op).
198
+ */
199
+ export const AddObservationInputSchema = z.object({
200
+ entityName: entityNameSchema,
201
+ contents: z.array(observationSchema),
202
+ });
203
+ /**
204
+ * Batch observation addition validation.
205
+ * Empty array is allowed (no-op).
206
+ */
207
+ export const AddObservationsInputSchema = z.array(AddObservationInputSchema)
208
+ .max(1000, 'Cannot add observations to more than 1000 entities in a single batch');
209
+ /**
210
+ * Single observation deletion input.
211
+ * Empty observations array is allowed (no-op).
212
+ * Non-existent entities are silently skipped by the manager.
213
+ */
214
+ export const DeleteObservationInputSchema = z.object({
215
+ entityName: entityNameSchema,
216
+ observations: z.array(observationSchema),
217
+ });
218
+ /**
219
+ * Batch observation deletion validation.
220
+ * Empty array is allowed (no-op).
221
+ */
222
+ export const DeleteObservationsInputSchema = z.array(DeleteObservationInputSchema)
223
+ .max(1000, 'Cannot delete observations from more than 1000 entities in a single batch');
224
+ // ==================== Archive Schema ====================
225
+ /**
226
+ * Archive criteria validation.
227
+ * All fields are optional - the manager handles the case when no criteria provided.
228
+ */
229
+ export const ArchiveCriteriaSchema = z.object({
230
+ olderThan: z.string().optional(),
231
+ importanceLessThan: z.number().min(0).max(10).optional(),
232
+ tags: z.array(tagSchema).optional(),
233
+ });
234
+ // ==================== Saved Search Schemas ====================
235
+ /**
236
+ * Saved search creation input validation.
237
+ */
238
+ export const SavedSearchInputSchema = z.object({
239
+ name: z.string().min(1, 'Search name cannot be empty').max(200, 'Search name cannot exceed 200 characters').trim(),
240
+ description: z.string().max(1000, 'Description cannot exceed 1000 characters').optional(),
241
+ query: SearchQuerySchema,
242
+ tags: z.array(tagSchema).optional(),
243
+ minImportance: importanceSchema.optional(),
244
+ maxImportance: importanceSchema.optional(),
245
+ entityType: entityTypeSchema.optional(),
246
+ });
247
+ /**
248
+ * Saved search update validation.
249
+ * All fields are optional for partial updates.
250
+ */
251
+ export const SavedSearchUpdateSchema = z.object({
252
+ description: z.string().max(1000, 'Description cannot exceed 1000 characters').optional(),
253
+ query: SearchQuerySchema.optional(),
254
+ tags: z.array(tagSchema).optional(),
255
+ minImportance: importanceSchema.optional(),
256
+ maxImportance: importanceSchema.optional(),
257
+ entityType: entityTypeSchema.optional(),
258
+ });
259
+ // ==================== Import/Export Schemas ====================
260
+ /**
261
+ * Import format validation.
262
+ */
263
+ export const ImportFormatSchema = z.enum(['json', 'csv', 'graphml']);
264
+ /**
265
+ * Export format validation (includes all output formats).
266
+ */
267
+ export const ExtendedExportFormatSchema = z.enum(['json', 'csv', 'graphml', 'gexf', 'dot', 'markdown', 'mermaid']);
268
+ /**
269
+ * Merge strategy validation for imports.
270
+ */
271
+ export const MergeStrategySchema = z.enum(['replace', 'skip', 'merge', 'fail']);
272
+ /**
273
+ * Export filter validation.
274
+ */
275
+ export const ExportFilterSchema = z.object({
276
+ startDate: isoDateSchema.optional(),
277
+ endDate: isoDateSchema.optional(),
278
+ entityType: entityTypeSchema.optional(),
279
+ tags: z.array(tagSchema).optional(),
280
+ });
281
+ // ==================== Search Parameter Schemas ====================
282
+ /**
283
+ * Tags array validation (optional, for search filters).
284
+ */
285
+ export const OptionalTagsSchema = z.array(tagSchema).optional();
286
+ /**
287
+ * Optional entity names array validation.
288
+ */
289
+ export const OptionalEntityNamesSchema = z.array(entityNameSchema).optional();
290
+ // ==================== Zod Validation Helpers ====================
291
+ /**
292
+ * Formats Zod errors into human-readable strings.
293
+ *
294
+ * @param error - Zod error object
295
+ * @returns Array of formatted error messages
296
+ */
297
+ export function formatZodErrors(error) {
298
+ return error.issues.map(issue => {
299
+ const path = issue.path.length > 0 ? `${issue.path.join('.')}: ` : '';
300
+ return `${path}${issue.message}`;
301
+ });
302
+ }
303
+ /**
304
+ * Validates data against a Zod schema and returns the typed result.
305
+ * Throws ValidationError with formatted error messages on failure.
306
+ *
307
+ * @param data - The data to validate
308
+ * @param schema - The Zod schema to validate against
309
+ * @param errorMessage - Custom error message prefix (default: 'Validation failed')
310
+ * @returns The validated and typed data
311
+ * @throws ValidationError if validation fails
312
+ *
313
+ * @example
314
+ * ```typescript
315
+ * const entities = validateWithSchema(
316
+ * input,
317
+ * BatchCreateEntitiesSchema,
318
+ * 'Invalid entity data'
319
+ * );
320
+ * ```
321
+ */
322
+ export function validateWithSchema(data, schema, errorMessage = 'Validation failed') {
323
+ const result = schema.safeParse(data);
324
+ if (!result.success) {
325
+ const errors = formatZodErrors(result.error);
326
+ throw new ValidationError(errorMessage, errors);
327
+ }
328
+ return result.data;
329
+ }
330
+ /**
331
+ * Validates data and returns a result object instead of throwing.
332
+ * Useful when you want to handle validation errors gracefully.
333
+ *
334
+ * @param data - The data to validate
335
+ * @param schema - The Zod schema to validate against
336
+ * @returns Result object with success status and either data or errors
337
+ *
338
+ * @example
339
+ * ```typescript
340
+ * const result = validateSafe(input, EntitySchema);
341
+ * if (result.success) {
342
+ * console.log(result.data);
343
+ * } else {
344
+ * console.error(result.errors);
345
+ * }
346
+ * ```
347
+ */
348
+ export function validateSafe(data, schema) {
349
+ const result = schema.safeParse(data);
350
+ if (result.success) {
351
+ return { success: true, data: result.data };
352
+ }
353
+ return { success: false, errors: formatZodErrors(result.error) };
354
+ }
355
+ /**
356
+ * Validates an array of items against a schema.
357
+ * Returns detailed information about which items failed validation.
358
+ *
359
+ * @param items - Array of items to validate
360
+ * @param schema - Zod schema for individual items
361
+ * @param errorMessage - Custom error message prefix
362
+ * @returns Array of validated items
363
+ * @throws ValidationError if any item fails validation
364
+ */
365
+ export function validateArrayWithSchema(items, schema, errorMessage = 'Array validation failed') {
366
+ const errors = [];
367
+ const validated = [];
368
+ for (let i = 0; i < items.length; i++) {
369
+ const result = schema.safeParse(items[i]);
370
+ if (result.success) {
371
+ validated.push(result.data);
372
+ }
373
+ else {
374
+ const itemErrors = formatZodErrors(result.error);
375
+ errors.push(...itemErrors.map(e => `[${i}] ${e}`));
376
+ }
377
+ }
378
+ if (errors.length > 0) {
379
+ throw new ValidationError(errorMessage, errors);
380
+ }
381
+ return validated;
382
+ }
383
+ // ==================== Manual Validation Functions ====================
384
+ /**
385
+ * Type guard to check if value is a non-null object.
386
+ */
387
+ function isObject(value) {
388
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
389
+ }
390
+ /**
391
+ * Validate an entity object.
392
+ *
393
+ * Checks required fields and data types.
394
+ *
395
+ * @param entity - Entity to validate (unknown type for runtime validation)
396
+ * @returns Validation result
397
+ */
398
+ export function validateEntity(entity) {
399
+ const errors = [];
400
+ if (!isObject(entity)) {
401
+ return { valid: false, errors: ['Entity must be an object'] };
402
+ }
403
+ if (!entity.name || typeof entity.name !== 'string' || entity.name.trim() === '') {
404
+ errors.push('Entity name is required and must be a non-empty string');
405
+ }
406
+ if (!entity.entityType || typeof entity.entityType !== 'string' || entity.entityType.trim() === '') {
407
+ errors.push('Entity type is required and must be a non-empty string');
408
+ }
409
+ if (!Array.isArray(entity.observations)) {
410
+ errors.push('Observations must be an array');
411
+ }
412
+ else if (!entity.observations.every((o) => typeof o === 'string')) {
413
+ errors.push('All observations must be strings');
414
+ }
415
+ if (entity.tags !== undefined) {
416
+ if (!Array.isArray(entity.tags)) {
417
+ errors.push('Tags must be an array');
418
+ }
419
+ else if (!entity.tags.every((t) => typeof t === 'string')) {
420
+ errors.push('All tags must be strings');
421
+ }
422
+ }
423
+ if (entity.importance !== undefined) {
424
+ if (typeof entity.importance !== 'number') {
425
+ errors.push('Importance must be a number');
426
+ }
427
+ else if (!validateImportance(entity.importance)) {
428
+ errors.push('Importance must be between 0 and 10');
429
+ }
430
+ }
431
+ return { valid: errors.length === 0, errors };
432
+ }
433
+ /**
434
+ * Validate a relation object.
435
+ *
436
+ * Checks required fields and data types.
437
+ *
438
+ * @param relation - Relation to validate (unknown type for runtime validation)
439
+ * @returns Validation result
440
+ */
441
+ export function validateRelation(relation) {
442
+ const errors = [];
443
+ if (!isObject(relation)) {
444
+ return { valid: false, errors: ['Relation must be an object'] };
445
+ }
446
+ if (!relation.from || typeof relation.from !== 'string' || relation.from.trim() === '') {
447
+ errors.push('Relation "from" is required and must be a non-empty string');
448
+ }
449
+ if (!relation.to || typeof relation.to !== 'string' || relation.to.trim() === '') {
450
+ errors.push('Relation "to" is required and must be a non-empty string');
451
+ }
452
+ if (!relation.relationType || typeof relation.relationType !== 'string' || relation.relationType.trim() === '') {
453
+ errors.push('Relation type is required and must be a non-empty string');
454
+ }
455
+ return { valid: errors.length === 0, errors };
456
+ }
457
+ /**
458
+ * Validate importance level (must be 0-10).
459
+ *
460
+ * @param importance - Importance value to validate
461
+ * @returns True if valid
462
+ */
463
+ export function validateImportance(importance) {
464
+ return typeof importance === 'number'
465
+ && !isNaN(importance)
466
+ && importance >= IMPORTANCE_RANGE.MIN
467
+ && importance <= IMPORTANCE_RANGE.MAX;
468
+ }
469
+ /**
470
+ * Validate an array of tags.
471
+ *
472
+ * @param tags - Tags array to validate (unknown type for runtime validation)
473
+ * @returns Validation result
474
+ */
475
+ export function validateTags(tags) {
476
+ const errors = [];
477
+ if (!Array.isArray(tags)) {
478
+ return { valid: false, errors: ['Tags must be an array'] };
479
+ }
480
+ if (!tags.every((t) => typeof t === 'string' && t.trim() !== '')) {
481
+ errors.push('All tags must be non-empty strings');
482
+ }
483
+ return { valid: errors.length === 0, errors };
484
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Search Algorithms
3
+ *
4
+ * Algorithms for search operations: Levenshtein distance for fuzzy matching
5
+ * and TF-IDF for relevance scoring.
6
+ *
7
+ * @module utils/searchAlgorithms
8
+ */
9
+ /**
10
+ * Calculate Levenshtein distance between two strings.
11
+ *
12
+ * Returns the minimum number of single-character edits needed to change
13
+ * one word into another.
14
+ *
15
+ * **Algorithm**: Space-optimized dynamic programming using only two rows.
16
+ * Time complexity: O(m*n), Space complexity: O(min(m,n)).
17
+ *
18
+ * This optimization reduces memory usage from O(m*n) to O(min(m,n)) by
19
+ * observing that each row only depends on the previous row.
20
+ *
21
+ * @param str1 - First string to compare
22
+ * @param str2 - Second string to compare
23
+ * @returns Minimum number of edits required (0 = identical strings)
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * levenshteinDistance("kitten", "sitting"); // Returns 3
28
+ * levenshteinDistance("hello", "hello"); // Returns 0
29
+ * levenshteinDistance("abc", ""); // Returns 3
30
+ * ```
31
+ */
32
+ export declare function levenshteinDistance(str1: string, str2: string): number;
33
+ /**
34
+ * Calculate Term Frequency (TF) for a term in a document.
35
+ *
36
+ * TF = (Number of times term appears in document) / (Total terms in document)
37
+ *
38
+ * @param term - The search term
39
+ * @param document - The document text
40
+ * @returns Term frequency (0.0 to 1.0)
41
+ */
42
+ export declare function calculateTF(term: string, document: string): number;
43
+ /**
44
+ * Calculate Inverse Document Frequency (IDF) for a term across documents.
45
+ *
46
+ * IDF = log(Total documents / Documents containing term)
47
+ *
48
+ * Note: For bulk IDF calculation, prefer calculateIDFFromTokenSets which
49
+ * avoids re-tokenizing documents for each term.
50
+ *
51
+ * @param term - The search term
52
+ * @param documents - Array of document texts
53
+ * @returns Inverse document frequency
54
+ */
55
+ export declare function calculateIDF(term: string, documents: string[]): number;
56
+ /**
57
+ * Calculate Inverse Document Frequency (IDF) from pre-tokenized documents.
58
+ *
59
+ * IDF = log(Total documents / Documents containing term)
60
+ *
61
+ * **Optimized**: Avoids re-tokenizing documents for each term. Pre-tokenize
62
+ * documents once and convert to Sets for O(1) lookup per document.
63
+ *
64
+ * @param term - The search term (should already be lowercase)
65
+ * @param tokenSets - Array of Sets, each containing unique tokens for a document
66
+ * @returns Inverse document frequency
67
+ *
68
+ * @example
69
+ * ```typescript
70
+ * const docs = ["hello world", "hello there"];
71
+ * const tokenSets = docs.map(d => new Set(tokenize(d)));
72
+ * calculateIDFFromTokenSets("hello", tokenSets); // Low IDF (common term)
73
+ * calculateIDFFromTokenSets("world", tokenSets); // Higher IDF (less common)
74
+ * ```
75
+ */
76
+ export declare function calculateIDFFromTokenSets(term: string, tokenSets: Set<string>[]): number;
77
+ /**
78
+ * Calculate TF-IDF score for a term in a document.
79
+ *
80
+ * TF-IDF = TF * IDF
81
+ *
82
+ * Higher scores indicate more important/relevant terms.
83
+ *
84
+ * @param term - The search term
85
+ * @param document - The document text
86
+ * @param documents - Array of all documents
87
+ * @returns TF-IDF score
88
+ */
89
+ export declare function calculateTFIDF(term: string, document: string, documents: string[]): number;
90
+ /**
91
+ * Tokenize text into lowercase words.
92
+ *
93
+ * Splits on whitespace and removes punctuation.
94
+ *
95
+ * @param text - Text to tokenize
96
+ * @returns Array of lowercase tokens
97
+ */
98
+ export declare function tokenize(text: string): string[];
99
+ //# sourceMappingURL=searchAlgorithms.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"searchAlgorithms.d.ts","sourceRoot":"","sources":["../../src/utils/searchAlgorithms.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAmCtE;AAID;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAQlE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,MAAM,CAWtE;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,GAAG,MAAM,CAexF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EAAE,GAClB,MAAM,CAIR;AAED;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAM/C"}
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Search Algorithms
3
+ *
4
+ * Algorithms for search operations: Levenshtein distance for fuzzy matching
5
+ * and TF-IDF for relevance scoring.
6
+ *
7
+ * @module utils/searchAlgorithms
8
+ */
9
+ // ==================== Levenshtein Distance ====================
10
+ /**
11
+ * Calculate Levenshtein distance between two strings.
12
+ *
13
+ * Returns the minimum number of single-character edits needed to change
14
+ * one word into another.
15
+ *
16
+ * **Algorithm**: Space-optimized dynamic programming using only two rows.
17
+ * Time complexity: O(m*n), Space complexity: O(min(m,n)).
18
+ *
19
+ * This optimization reduces memory usage from O(m*n) to O(min(m,n)) by
20
+ * observing that each row only depends on the previous row.
21
+ *
22
+ * @param str1 - First string to compare
23
+ * @param str2 - Second string to compare
24
+ * @returns Minimum number of edits required (0 = identical strings)
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * levenshteinDistance("kitten", "sitting"); // Returns 3
29
+ * levenshteinDistance("hello", "hello"); // Returns 0
30
+ * levenshteinDistance("abc", ""); // Returns 3
31
+ * ```
32
+ */
33
+ export function levenshteinDistance(str1, str2) {
34
+ // Ensure str1 is the shorter string for optimal space usage
35
+ if (str1.length > str2.length) {
36
+ [str1, str2] = [str2, str1];
37
+ }
38
+ const m = str1.length;
39
+ const n = str2.length;
40
+ // Use two rows instead of full matrix - O(min(m,n)) space
41
+ let prev = Array.from({ length: m + 1 }, (_, i) => i);
42
+ let curr = new Array(m + 1);
43
+ for (let j = 1; j <= n; j++) {
44
+ curr[0] = j; // Distance from empty string
45
+ for (let i = 1; i <= m; i++) {
46
+ if (str1[i - 1] === str2[j - 1]) {
47
+ // Characters match, no edit needed
48
+ curr[i] = prev[i - 1];
49
+ }
50
+ else {
51
+ // Take minimum of three operations
52
+ curr[i] = 1 + Math.min(prev[i - 1], // substitution
53
+ prev[i], // deletion
54
+ curr[i - 1] // insertion
55
+ );
56
+ }
57
+ }
58
+ // Swap rows for next iteration
59
+ [prev, curr] = [curr, prev];
60
+ }
61
+ return prev[m];
62
+ }
63
+ // ==================== TF-IDF ====================
64
+ /**
65
+ * Calculate Term Frequency (TF) for a term in a document.
66
+ *
67
+ * TF = (Number of times term appears in document) / (Total terms in document)
68
+ *
69
+ * @param term - The search term
70
+ * @param document - The document text
71
+ * @returns Term frequency (0.0 to 1.0)
72
+ */
73
+ export function calculateTF(term, document) {
74
+ const termLower = term.toLowerCase();
75
+ const tokens = tokenize(document);
76
+ if (tokens.length === 0)
77
+ return 0;
78
+ const termCount = tokens.filter(t => t === termLower).length;
79
+ return termCount / tokens.length;
80
+ }
81
+ /**
82
+ * Calculate Inverse Document Frequency (IDF) for a term across documents.
83
+ *
84
+ * IDF = log(Total documents / Documents containing term)
85
+ *
86
+ * Note: For bulk IDF calculation, prefer calculateIDFFromTokenSets which
87
+ * avoids re-tokenizing documents for each term.
88
+ *
89
+ * @param term - The search term
90
+ * @param documents - Array of document texts
91
+ * @returns Inverse document frequency
92
+ */
93
+ export function calculateIDF(term, documents) {
94
+ if (documents.length === 0)
95
+ return 0;
96
+ const termLower = term.toLowerCase();
97
+ const docsWithTerm = documents.filter(doc => tokenize(doc).includes(termLower)).length;
98
+ if (docsWithTerm === 0)
99
+ return 0;
100
+ return Math.log(documents.length / docsWithTerm);
101
+ }
102
+ /**
103
+ * Calculate Inverse Document Frequency (IDF) from pre-tokenized documents.
104
+ *
105
+ * IDF = log(Total documents / Documents containing term)
106
+ *
107
+ * **Optimized**: Avoids re-tokenizing documents for each term. Pre-tokenize
108
+ * documents once and convert to Sets for O(1) lookup per document.
109
+ *
110
+ * @param term - The search term (should already be lowercase)
111
+ * @param tokenSets - Array of Sets, each containing unique tokens for a document
112
+ * @returns Inverse document frequency
113
+ *
114
+ * @example
115
+ * ```typescript
116
+ * const docs = ["hello world", "hello there"];
117
+ * const tokenSets = docs.map(d => new Set(tokenize(d)));
118
+ * calculateIDFFromTokenSets("hello", tokenSets); // Low IDF (common term)
119
+ * calculateIDFFromTokenSets("world", tokenSets); // Higher IDF (less common)
120
+ * ```
121
+ */
122
+ export function calculateIDFFromTokenSets(term, tokenSets) {
123
+ if (tokenSets.length === 0)
124
+ return 0;
125
+ const termLower = term.toLowerCase();
126
+ let docsWithTerm = 0;
127
+ for (const tokenSet of tokenSets) {
128
+ if (tokenSet.has(termLower)) {
129
+ docsWithTerm++;
130
+ }
131
+ }
132
+ if (docsWithTerm === 0)
133
+ return 0;
134
+ return Math.log(tokenSets.length / docsWithTerm);
135
+ }
136
+ /**
137
+ * Calculate TF-IDF score for a term in a document.
138
+ *
139
+ * TF-IDF = TF * IDF
140
+ *
141
+ * Higher scores indicate more important/relevant terms.
142
+ *
143
+ * @param term - The search term
144
+ * @param document - The document text
145
+ * @param documents - Array of all documents
146
+ * @returns TF-IDF score
147
+ */
148
+ export function calculateTFIDF(term, document, documents) {
149
+ const tf = calculateTF(term, document);
150
+ const idf = calculateIDF(term, documents);
151
+ return tf * idf;
152
+ }
153
+ /**
154
+ * Tokenize text into lowercase words.
155
+ *
156
+ * Splits on whitespace and removes punctuation.
157
+ *
158
+ * @param text - Text to tokenize
159
+ * @returns Array of lowercase tokens
160
+ */
161
+ export function tokenize(text) {
162
+ return text
163
+ .toLowerCase()
164
+ .replace(/[^\w\s]/g, ' ')
165
+ .split(/\s+/)
166
+ .filter(token => token.length > 0);
167
+ }