@coreidentitylabs/open-graph-memory-mcp 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/.agents/skills/mcp-builder/LICENSE.txt +202 -0
  2. package/.agents/skills/mcp-builder/SKILL.md +236 -0
  3. package/.agents/skills/mcp-builder/reference/evaluation.md +602 -0
  4. package/.agents/skills/mcp-builder/reference/mcp_best_practices.md +249 -0
  5. package/.agents/skills/mcp-builder/reference/node_mcp_server.md +970 -0
  6. package/.agents/skills/mcp-builder/reference/python_mcp_server.md +719 -0
  7. package/.agents/skills/mcp-builder/scripts/connections.py +151 -0
  8. package/.agents/skills/mcp-builder/scripts/evaluation.py +373 -0
  9. package/.agents/skills/mcp-builder/scripts/example_evaluation.xml +22 -0
  10. package/.agents/skills/mcp-builder/scripts/requirements.txt +2 -0
  11. package/.env.example +26 -0
  12. package/Implementation Plan.md +358 -0
  13. package/README.md +187 -0
  14. package/dist/constants.d.ts +34 -0
  15. package/dist/constants.d.ts.map +1 -0
  16. package/dist/constants.js +40 -0
  17. package/dist/constants.js.map +1 -0
  18. package/dist/encoding/embedder.d.ts +12 -0
  19. package/dist/encoding/embedder.d.ts.map +1 -0
  20. package/dist/encoding/embedder.js +85 -0
  21. package/dist/encoding/embedder.js.map +1 -0
  22. package/dist/encoding/pipeline.d.ts +28 -0
  23. package/dist/encoding/pipeline.d.ts.map +1 -0
  24. package/dist/encoding/pipeline.js +146 -0
  25. package/dist/encoding/pipeline.js.map +1 -0
  26. package/dist/evolution/consolidator.d.ts +12 -0
  27. package/dist/evolution/consolidator.d.ts.map +1 -0
  28. package/dist/evolution/consolidator.js +212 -0
  29. package/dist/evolution/consolidator.js.map +1 -0
  30. package/dist/index.d.ts +3 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +53 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/llm/openai-provider.d.ts +23 -0
  35. package/dist/llm/openai-provider.d.ts.map +1 -0
  36. package/dist/llm/openai-provider.js +141 -0
  37. package/dist/llm/openai-provider.js.map +1 -0
  38. package/dist/llm/prompts.d.ts +10 -0
  39. package/dist/llm/prompts.d.ts.map +1 -0
  40. package/dist/llm/prompts.js +63 -0
  41. package/dist/llm/prompts.js.map +1 -0
  42. package/dist/llm/provider.d.ts +7 -0
  43. package/dist/llm/provider.d.ts.map +1 -0
  44. package/dist/llm/provider.js +25 -0
  45. package/dist/llm/provider.js.map +1 -0
  46. package/dist/resources/context-resource.d.ts +8 -0
  47. package/dist/resources/context-resource.d.ts.map +1 -0
  48. package/dist/resources/context-resource.js +51 -0
  49. package/dist/resources/context-resource.js.map +1 -0
  50. package/dist/retrieval/search.d.ts +24 -0
  51. package/dist/retrieval/search.d.ts.map +1 -0
  52. package/dist/retrieval/search.js +143 -0
  53. package/dist/retrieval/search.js.map +1 -0
  54. package/dist/storage/factory.d.ts +10 -0
  55. package/dist/storage/factory.d.ts.map +1 -0
  56. package/dist/storage/factory.js +35 -0
  57. package/dist/storage/factory.js.map +1 -0
  58. package/dist/storage/json-store.d.ts +34 -0
  59. package/dist/storage/json-store.d.ts.map +1 -0
  60. package/dist/storage/json-store.js +248 -0
  61. package/dist/storage/json-store.js.map +1 -0
  62. package/dist/storage/neo4j-store.d.ts +31 -0
  63. package/dist/storage/neo4j-store.d.ts.map +1 -0
  64. package/dist/storage/neo4j-store.js +440 -0
  65. package/dist/storage/neo4j-store.js.map +1 -0
  66. package/dist/tools/memory-tools.d.ts +4 -0
  67. package/dist/tools/memory-tools.d.ts.map +1 -0
  68. package/dist/tools/memory-tools.js +873 -0
  69. package/dist/tools/memory-tools.js.map +1 -0
  70. package/dist/types.d.ts +129 -0
  71. package/dist/types.d.ts.map +1 -0
  72. package/dist/types.js +5 -0
  73. package/dist/types.js.map +1 -0
  74. package/implementation_plan.md.resolved.md +322 -0
  75. package/package.json +43 -0
  76. package/src/constants.ts +52 -0
  77. package/src/encoding/embedder.ts +93 -0
  78. package/src/encoding/pipeline.ts +197 -0
  79. package/src/evolution/consolidator.ts +281 -0
  80. package/src/index.ts +67 -0
  81. package/src/llm/openai-provider.ts +208 -0
  82. package/src/llm/prompts.ts +66 -0
  83. package/src/llm/provider.ts +37 -0
  84. package/src/resources/context-resource.ts +74 -0
  85. package/src/retrieval/search.ts +203 -0
  86. package/src/storage/factory.ts +48 -0
  87. package/src/storage/json-store.ts +325 -0
  88. package/src/storage/neo4j-store.ts +564 -0
  89. package/src/tools/memory-tools.ts +1067 -0
  90. package/src/types.ts +207 -0
  91. package/tsconfig.json +21 -0
@@ -0,0 +1,564 @@
1
+ // =============================================================================
2
+ // Neo4j Graph Database Storage Backend
3
+ // =============================================================================
4
+
5
+ import neo4j, { Driver, Session } from "neo4j-driver";
6
+ import type {
7
+ StorageBackend,
8
+ MemoryNode,
9
+ MemoryEdge,
10
+ ScoredNode,
11
+ Subgraph,
12
+ NodeFilter,
13
+ GraphStats,
14
+ } from "../types.js";
15
+ import { cosineSimilarity } from "../encoding/embedder.js";
16
+
17
+ export class Neo4jStore implements StorageBackend {
18
+ private driver: Driver;
19
+ private uri: string;
20
+ private user: string;
21
+ private password: string;
22
+
23
+ constructor(uri: string, user: string, password: string) {
24
+ this.uri = uri;
25
+ this.user = user;
26
+ this.password = password;
27
+ this.driver = neo4j.driver(
28
+ this.uri,
29
+ neo4j.auth.basic(this.user, this.password),
30
+ );
31
+ }
32
+
33
+ private getSession(): Session {
34
+ return this.driver.session();
35
+ }
36
+
37
+ async initialize(): Promise<void> {
38
+ const session = this.getSession();
39
+ try {
40
+ // Create constraints and indexes
41
+ await session.run(
42
+ `CREATE CONSTRAINT memory_node_id IF NOT EXISTS FOR (n:MemoryNode) REQUIRE n.id IS UNIQUE`,
43
+ );
44
+ await session.run(
45
+ `CREATE CONSTRAINT memory_edge_id IF NOT EXISTS FOR ()-[r:MEMORY_EDGE]-() REQUIRE r.id IS UNIQUE`,
46
+ );
47
+ await session.run(
48
+ `CREATE INDEX memory_node_name IF NOT EXISTS FOR (n:MemoryNode) ON (n.name)`,
49
+ );
50
+ await session.run(
51
+ `CREATE INDEX memory_node_type IF NOT EXISTS FOR (n:MemoryNode) ON (n.type)`,
52
+ );
53
+
54
+ // Verify connection
55
+ const result = await session.run(
56
+ `MATCH (n:MemoryNode) RETURN count(n) AS count`,
57
+ );
58
+ const count = result.records[0]?.get("count")?.toNumber() ?? 0;
59
+ console.error(
60
+ `[open-memory] Connected to Neo4j at ${this.uri}. ${count} existing nodes.`,
61
+ );
62
+ } finally {
63
+ await session.close();
64
+ }
65
+ }
66
+
67
+ // -- Node Operations -------------------------------------------------------
68
+
69
+ async addNode(node: MemoryNode): Promise<void> {
70
+ const session = this.getSession();
71
+ try {
72
+ await session.run(
73
+ `CREATE (n:MemoryNode {
74
+ id: $id, name: $name, type: $type, description: $description,
75
+ embedding: $embedding, metadata: $metadata,
76
+ createdAt: $createdAt, updatedAt: $updatedAt,
77
+ validFrom: $validFrom, validUntil: $validUntil,
78
+ source: $source, accessCount: $accessCount,
79
+ lastAccessedAt: $lastAccessedAt
80
+ })`,
81
+ {
82
+ ...node,
83
+ embedding: node.embedding ?? [],
84
+ metadata: JSON.stringify(node.metadata),
85
+ validFrom: node.validFrom ?? null,
86
+ validUntil: node.validUntil ?? null,
87
+ source: node.source ?? null,
88
+ lastAccessedAt: node.lastAccessedAt ?? null,
89
+ },
90
+ );
91
+ } finally {
92
+ await session.close();
93
+ }
94
+ }
95
+
96
+ async updateNode(
97
+ id: string,
98
+ updates: Partial<MemoryNode>,
99
+ ): Promise<MemoryNode | null> {
100
+ const session = this.getSession();
101
+ try {
102
+ const setClause = buildSetClause(updates);
103
+ if (!setClause) return await this.getNode(id);
104
+
105
+ const result = await session.run(
106
+ `MATCH (n:MemoryNode {id: $id})
107
+ SET ${setClause}, n.updatedAt = $updatedAt
108
+ RETURN n`,
109
+ {
110
+ id,
111
+ updatedAt: new Date().toISOString(),
112
+ ...serializeUpdates(updates),
113
+ },
114
+ );
115
+ if (result.records.length === 0) return null;
116
+ return deserializeNode(result.records[0].get("n").properties);
117
+ } finally {
118
+ await session.close();
119
+ }
120
+ }
121
+
122
+ async deleteNode(id: string): Promise<boolean> {
123
+ const session = this.getSession();
124
+ try {
125
+ const result = await session.run(
126
+ `MATCH (n:MemoryNode {id: $id}) DETACH DELETE n RETURN count(n) AS deleted`,
127
+ { id },
128
+ );
129
+ return (result.records[0]?.get("deleted")?.toNumber() ?? 0) > 0;
130
+ } finally {
131
+ await session.close();
132
+ }
133
+ }
134
+
135
+ async getNode(id: string): Promise<MemoryNode | null> {
136
+ const session = this.getSession();
137
+ try {
138
+ const result = await session.run(
139
+ `MATCH (n:MemoryNode {id: $id}) RETURN n`,
140
+ { id },
141
+ );
142
+ if (result.records.length === 0) return null;
143
+ return deserializeNode(result.records[0].get("n").properties);
144
+ } finally {
145
+ await session.close();
146
+ }
147
+ }
148
+
149
+ async getNodeByName(name: string): Promise<MemoryNode | null> {
150
+ const session = this.getSession();
151
+ try {
152
+ const result = await session.run(
153
+ `MATCH (n:MemoryNode) WHERE toLower(n.name) = toLower($name) RETURN n LIMIT 1`,
154
+ { name },
155
+ );
156
+ if (result.records.length === 0) return null;
157
+ return deserializeNode(result.records[0].get("n").properties);
158
+ } finally {
159
+ await session.close();
160
+ }
161
+ }
162
+
163
+ async findNodes(filter: NodeFilter): Promise<MemoryNode[]> {
164
+ const session = this.getSession();
165
+ try {
166
+ const conditions: string[] = [];
167
+ const params: Record<string, unknown> = {};
168
+
169
+ if (filter.type) {
170
+ conditions.push("n.type = $type");
171
+ params.type = filter.type;
172
+ }
173
+ if (filter.source) {
174
+ conditions.push("n.source = $source");
175
+ params.source = filter.source;
176
+ }
177
+ if (filter.nameContains) {
178
+ conditions.push("toLower(n.name) CONTAINS toLower($nameContains)");
179
+ params.nameContains = filter.nameContains;
180
+ }
181
+ if (filter.createdAfter) {
182
+ conditions.push("n.createdAt >= $createdAfter");
183
+ params.createdAfter = filter.createdAfter;
184
+ }
185
+ if (filter.createdBefore) {
186
+ conditions.push("n.createdAt <= $createdBefore");
187
+ params.createdBefore = filter.createdBefore;
188
+ }
189
+
190
+ const whereClause =
191
+ conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
192
+ const result = await session.run(
193
+ `MATCH (n:MemoryNode) ${whereClause} RETURN n ORDER BY n.updatedAt DESC`,
194
+ params,
195
+ );
196
+ return result.records.map((r) => deserializeNode(r.get("n").properties));
197
+ } finally {
198
+ await session.close();
199
+ }
200
+ }
201
+
202
+ async getAllNodes(
203
+ limit: number,
204
+ offset: number,
205
+ ): Promise<{ nodes: MemoryNode[]; total: number }> {
206
+ const session = this.getSession();
207
+ try {
208
+ const countResult = await session.run(
209
+ `MATCH (n:MemoryNode) RETURN count(n) AS total`,
210
+ );
211
+ const total = countResult.records[0]?.get("total")?.toNumber() ?? 0;
212
+
213
+ const result = await session.run(
214
+ `MATCH (n:MemoryNode) RETURN n ORDER BY n.updatedAt DESC SKIP $offset LIMIT $limit`,
215
+ { offset: neo4j.int(offset), limit: neo4j.int(limit) },
216
+ );
217
+ const nodes = result.records.map((r) =>
218
+ deserializeNode(r.get("n").properties),
219
+ );
220
+ return { nodes, total };
221
+ } finally {
222
+ await session.close();
223
+ }
224
+ }
225
+
226
+ async findNodesByEmbedding(
227
+ embedding: number[],
228
+ topK: number,
229
+ ): Promise<ScoredNode[]> {
230
+ // For now, fall back to client-side cosine similarity
231
+ // Neo4j vector index can be used in production for better perf
232
+ const session = this.getSession();
233
+ try {
234
+ const result = await session.run(
235
+ `MATCH (n:MemoryNode) WHERE size(n.embedding) > 0 RETURN n`,
236
+ );
237
+ const scored: ScoredNode[] = [];
238
+ for (const record of result.records) {
239
+ const node = deserializeNode(record.get("n").properties);
240
+ if (node.embedding && node.embedding.length > 0) {
241
+ const score = cosineSimilarity(embedding, node.embedding);
242
+ scored.push({ node, score, matchType: "semantic" });
243
+ }
244
+ }
245
+ scored.sort((a, b) => b.score - a.score);
246
+ return scored.slice(0, topK);
247
+ } finally {
248
+ await session.close();
249
+ }
250
+ }
251
+
252
+ // -- Edge Operations --------------------------------------------------------
253
+
254
+ async addEdge(edge: MemoryEdge): Promise<void> {
255
+ const session = this.getSession();
256
+ try {
257
+ await session.run(
258
+ `MATCH (s:MemoryNode {id: $source})
259
+ MATCH (t:MemoryNode {id: $target})
260
+ CREATE (s)-[r:MEMORY_EDGE {
261
+ id: $id, relation: $relation, description: $description,
262
+ weight: $weight, metadata: $metadata,
263
+ createdAt: $createdAt, updatedAt: $updatedAt
264
+ }]->(t)`,
265
+ {
266
+ ...edge,
267
+ metadata: JSON.stringify(edge.metadata),
268
+ },
269
+ );
270
+ } finally {
271
+ await session.close();
272
+ }
273
+ }
274
+
275
+ async updateEdge(
276
+ id: string,
277
+ updates: Partial<MemoryEdge>,
278
+ ): Promise<MemoryEdge | null> {
279
+ const session = this.getSession();
280
+ try {
281
+ const setClause = buildEdgeSetClause(updates);
282
+ if (!setClause) return await this.getEdge(id);
283
+
284
+ const result = await session.run(
285
+ `MATCH ()-[r:MEMORY_EDGE {id: $id}]-()
286
+ SET ${setClause}, r.updatedAt = $updatedAt
287
+ RETURN r, startNode(r).id AS sourceId, endNode(r).id AS targetId`,
288
+ {
289
+ id,
290
+ updatedAt: new Date().toISOString(),
291
+ ...serializeEdgeUpdates(updates),
292
+ },
293
+ );
294
+ if (result.records.length === 0) return null;
295
+ return deserializeEdge(result.records[0]);
296
+ } finally {
297
+ await session.close();
298
+ }
299
+ }
300
+
301
+ async deleteEdge(id: string): Promise<boolean> {
302
+ const session = this.getSession();
303
+ try {
304
+ const result = await session.run(
305
+ `MATCH ()-[r:MEMORY_EDGE {id: $id}]-() DELETE r RETURN count(r) AS deleted`,
306
+ { id },
307
+ );
308
+ return (result.records[0]?.get("deleted")?.toNumber() ?? 0) > 0;
309
+ } finally {
310
+ await session.close();
311
+ }
312
+ }
313
+
314
+ async getEdge(id: string): Promise<MemoryEdge | null> {
315
+ const session = this.getSession();
316
+ try {
317
+ const result = await session.run(
318
+ `MATCH (s)-[r:MEMORY_EDGE {id: $id}]->(t) RETURN r, s.id AS sourceId, t.id AS targetId`,
319
+ { id },
320
+ );
321
+ if (result.records.length === 0) return null;
322
+ return deserializeEdge(result.records[0]);
323
+ } finally {
324
+ await session.close();
325
+ }
326
+ }
327
+
328
+ async getEdgesForNode(
329
+ nodeId: string,
330
+ direction: "in" | "out" | "both" = "both",
331
+ ): Promise<MemoryEdge[]> {
332
+ const session = this.getSession();
333
+ try {
334
+ let query: string;
335
+ if (direction === "out") {
336
+ query = `MATCH (s:MemoryNode {id: $nodeId})-[r:MEMORY_EDGE]->(t) RETURN r, s.id AS sourceId, t.id AS targetId`;
337
+ } else if (direction === "in") {
338
+ query = `MATCH (s)-[r:MEMORY_EDGE]->(t:MemoryNode {id: $nodeId}) RETURN r, s.id AS sourceId, t.id AS targetId`;
339
+ } else {
340
+ query = `MATCH (n:MemoryNode {id: $nodeId})-[r:MEMORY_EDGE]-(m) RETURN r, startNode(r).id AS sourceId, endNode(r).id AS targetId`;
341
+ }
342
+ const result = await session.run(query, { nodeId });
343
+ return result.records.map((rec) => deserializeEdge(rec));
344
+ } finally {
345
+ await session.close();
346
+ }
347
+ }
348
+
349
+ async getEdgesBetween(
350
+ sourceId: string,
351
+ targetId: string,
352
+ ): Promise<MemoryEdge[]> {
353
+ const session = this.getSession();
354
+ try {
355
+ const result = await session.run(
356
+ `MATCH (s:MemoryNode)-[r:MEMORY_EDGE]-(t:MemoryNode)
357
+ WHERE (s.id = $sourceId AND t.id = $targetId)
358
+ OR (s.id = $targetId AND t.id = $sourceId)
359
+ RETURN r, startNode(r).id AS sourceId, endNode(r).id AS targetId`,
360
+ { sourceId, targetId },
361
+ );
362
+ return result.records.map((rec) => deserializeEdge(rec));
363
+ } finally {
364
+ await session.close();
365
+ }
366
+ }
367
+
368
+ // -- Graph Traversal --------------------------------------------------------
369
+
370
+ async getNeighborhood(nodeId: string, depth: number): Promise<Subgraph> {
371
+ const session = this.getSession();
372
+ try {
373
+ const result = await session.run(
374
+ `MATCH path = (start:MemoryNode {id: $nodeId})-[*0..${depth}]-(neighbor:MemoryNode)
375
+ WITH DISTINCT neighbor, relationships(path) AS rels
376
+ UNWIND rels AS r
377
+ WITH collect(DISTINCT neighbor) AS nodes,
378
+ collect(DISTINCT {rel: r, source: startNode(r).id, target: endNode(r).id}) AS edges
379
+ RETURN nodes, edges`,
380
+ { nodeId },
381
+ );
382
+
383
+ if (result.records.length === 0) {
384
+ // Return just the start node if it exists
385
+ const startNode = await this.getNode(nodeId);
386
+ return {
387
+ nodes: startNode ? [startNode] : [],
388
+ edges: [],
389
+ };
390
+ }
391
+
392
+ const record = result.records[0];
393
+ const rawNodes = record.get("nodes") as unknown[];
394
+ const rawEdges = record.get("edges") as unknown[];
395
+
396
+ const nodes: MemoryNode[] = (rawNodes || []).map((n: unknown) =>
397
+ deserializeNode(
398
+ (n as { properties: Record<string, unknown> }).properties,
399
+ ),
400
+ );
401
+ const edges: MemoryEdge[] = (rawEdges || []).map((e: unknown) => {
402
+ const edge = e as {
403
+ rel: { properties: Record<string, unknown> };
404
+ source: string;
405
+ target: string;
406
+ };
407
+ return {
408
+ id: edge.rel.properties.id as string,
409
+ source: edge.source,
410
+ target: edge.target,
411
+ relation: edge.rel.properties.relation as string,
412
+ description: edge.rel.properties.description as string,
413
+ weight: edge.rel.properties.weight as number,
414
+ metadata: JSON.parse(
415
+ (edge.rel.properties.metadata as string) || "{}",
416
+ ),
417
+ createdAt: edge.rel.properties.createdAt as string,
418
+ updatedAt: edge.rel.properties.updatedAt as string,
419
+ };
420
+ });
421
+
422
+ return { nodes, edges };
423
+ } finally {
424
+ await session.close();
425
+ }
426
+ }
427
+
428
+ // -- Stats & Lifecycle ------------------------------------------------------
429
+
430
+ async getStats(): Promise<GraphStats> {
431
+ const session = this.getSession();
432
+ try {
433
+ const nodesResult = await session.run(
434
+ `MATCH (n:MemoryNode) RETURN n.type AS type, count(n) AS count`,
435
+ );
436
+ const nodesByType: Record<string, number> = {};
437
+ let totalNodes = 0;
438
+ for (const record of nodesResult.records) {
439
+ const type = record.get("type") as string;
440
+ const count = (
441
+ record.get("count") as { toNumber(): number }
442
+ ).toNumber();
443
+ nodesByType[type] = count;
444
+ totalNodes += count;
445
+ }
446
+
447
+ const edgesResult = await session.run(
448
+ `MATCH ()-[r:MEMORY_EDGE]-() RETURN count(r) AS total`,
449
+ );
450
+ const totalEdges =
451
+ (
452
+ edgesResult.records[0]?.get("total") as { toNumber(): number }
453
+ )?.toNumber() ?? 0;
454
+
455
+ return {
456
+ totalNodes,
457
+ totalEdges,
458
+ nodesByType,
459
+ storageBackend: "neo4j",
460
+ };
461
+ } finally {
462
+ await session.close();
463
+ }
464
+ }
465
+
466
+ async close(): Promise<void> {
467
+ await this.driver.close();
468
+ }
469
+ }
470
+
471
+ // =============================================================================
472
+ // Serialization Helpers
473
+ // =============================================================================
474
+
475
+ function deserializeNode(props: Record<string, unknown>): MemoryNode {
476
+ return {
477
+ id: props.id as string,
478
+ name: props.name as string,
479
+ type: props.type as MemoryNode["type"],
480
+ description: props.description as string,
481
+ embedding: (props.embedding as number[]) ?? [],
482
+ metadata:
483
+ typeof props.metadata === "string"
484
+ ? JSON.parse(props.metadata)
485
+ : ((props.metadata as Record<string, unknown>) ?? {}),
486
+ createdAt: props.createdAt as string,
487
+ updatedAt: props.updatedAt as string,
488
+ validFrom: (props.validFrom as string) ?? undefined,
489
+ validUntil: (props.validUntil as string) ?? undefined,
490
+ source: (props.source as string) ?? undefined,
491
+ accessCount:
492
+ typeof props.accessCount === "object" && props.accessCount !== null
493
+ ? (props.accessCount as { toNumber(): number }).toNumber()
494
+ : ((props.accessCount as number) ?? 0),
495
+ lastAccessedAt: (props.lastAccessedAt as string) ?? undefined,
496
+ };
497
+ }
498
+
499
+ function deserializeEdge(record: { get(key: string): unknown }): MemoryEdge {
500
+ const rel = record.get("r") as { properties: Record<string, unknown> };
501
+ const props = rel.properties;
502
+ return {
503
+ id: props.id as string,
504
+ source: record.get("sourceId") as string,
505
+ target: record.get("targetId") as string,
506
+ relation: props.relation as string,
507
+ description: props.description as string,
508
+ weight: props.weight as number,
509
+ metadata:
510
+ typeof props.metadata === "string"
511
+ ? JSON.parse(props.metadata)
512
+ : ((props.metadata as Record<string, unknown>) ?? {}),
513
+ createdAt: props.createdAt as string,
514
+ updatedAt: props.updatedAt as string,
515
+ };
516
+ }
517
+
518
+ function buildSetClause(updates: Partial<MemoryNode>): string {
519
+ const parts: string[] = [];
520
+ if (updates.name !== undefined) parts.push("n.name = $name");
521
+ if (updates.description !== undefined)
522
+ parts.push("n.description = $description");
523
+ if (updates.type !== undefined) parts.push("n.type = $type");
524
+ if (updates.embedding !== undefined) parts.push("n.embedding = $embedding");
525
+ if (updates.metadata !== undefined) parts.push("n.metadata = $metadata");
526
+ if (updates.validFrom !== undefined) parts.push("n.validFrom = $validFrom");
527
+ if (updates.validUntil !== undefined)
528
+ parts.push("n.validUntil = $validUntil");
529
+ if (updates.accessCount !== undefined)
530
+ parts.push("n.accessCount = $accessCount");
531
+ if (updates.lastAccessedAt !== undefined)
532
+ parts.push("n.lastAccessedAt = $lastAccessedAt");
533
+ return parts.join(", ");
534
+ }
535
+
536
+ function buildEdgeSetClause(updates: Partial<MemoryEdge>): string {
537
+ const parts: string[] = [];
538
+ if (updates.relation !== undefined) parts.push("r.relation = $relation");
539
+ if (updates.description !== undefined)
540
+ parts.push("r.description = $description");
541
+ if (updates.weight !== undefined) parts.push("r.weight = $weight");
542
+ if (updates.metadata !== undefined) parts.push("r.metadata = $metadata");
543
+ return parts.join(", ");
544
+ }
545
+
546
+ function serializeUpdates(
547
+ updates: Partial<MemoryNode>,
548
+ ): Record<string, unknown> {
549
+ const result: Record<string, unknown> = { ...updates };
550
+ if (updates.metadata !== undefined) {
551
+ result.metadata = JSON.stringify(updates.metadata);
552
+ }
553
+ return result;
554
+ }
555
+
556
+ function serializeEdgeUpdates(
557
+ updates: Partial<MemoryEdge>,
558
+ ): Record<string, unknown> {
559
+ const result: Record<string, unknown> = { ...updates };
560
+ if (updates.metadata !== undefined) {
561
+ result.metadata = JSON.stringify(updates.metadata);
562
+ }
563
+ return result;
564
+ }