n8n-nodes-engram 0.2.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 (154) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +334 -0
  3. package/dist/community/CommunityDetector.d.ts +11 -0
  4. package/dist/community/CommunityDetector.js +126 -0
  5. package/dist/community/CommunityDetector.js.map +1 -0
  6. package/dist/community/CommunitySummarizer.d.ts +8 -0
  7. package/dist/community/CommunitySummarizer.js +56 -0
  8. package/dist/community/CommunitySummarizer.js.map +1 -0
  9. package/dist/community/index.d.ts +2 -0
  10. package/dist/community/index.js +8 -0
  11. package/dist/community/index.js.map +1 -0
  12. package/dist/credentials/EngramExtractionApi.credentials.d.ts +8 -0
  13. package/dist/credentials/EngramExtractionApi.credentials.js +41 -0
  14. package/dist/credentials/EngramExtractionApi.credentials.js.map +1 -0
  15. package/dist/credentials/EngramNeo4jApi.credentials.d.ts +8 -0
  16. package/dist/credentials/EngramNeo4jApi.credentials.js +59 -0
  17. package/dist/credentials/EngramNeo4jApi.credentials.js.map +1 -0
  18. package/dist/descriptions.d.ts +4 -0
  19. package/dist/descriptions.js +41 -0
  20. package/dist/descriptions.js.map +1 -0
  21. package/dist/embeddings/EmbeddingService.d.ts +24 -0
  22. package/dist/embeddings/EmbeddingService.js +64 -0
  23. package/dist/embeddings/EmbeddingService.js.map +1 -0
  24. package/dist/embeddings/cosine.d.ts +1 -0
  25. package/dist/embeddings/cosine.js +21 -0
  26. package/dist/embeddings/cosine.js.map +1 -0
  27. package/dist/embeddings/index.d.ts +2 -0
  28. package/dist/embeddings/index.js +8 -0
  29. package/dist/embeddings/index.js.map +1 -0
  30. package/dist/extraction/ContradictionDetector.d.ts +11 -0
  31. package/dist/extraction/ContradictionDetector.js +35 -0
  32. package/dist/extraction/ContradictionDetector.js.map +1 -0
  33. package/dist/extraction/EntityDeduplicator.d.ts +17 -0
  34. package/dist/extraction/EntityDeduplicator.js +39 -0
  35. package/dist/extraction/EntityDeduplicator.js.map +1 -0
  36. package/dist/extraction/EntityExtractor.d.ts +11 -0
  37. package/dist/extraction/EntityExtractor.js +33 -0
  38. package/dist/extraction/EntityExtractor.js.map +1 -0
  39. package/dist/extraction/ExtractionPipeline.d.ts +24 -0
  40. package/dist/extraction/ExtractionPipeline.js +126 -0
  41. package/dist/extraction/ExtractionPipeline.js.map +1 -0
  42. package/dist/extraction/LlmClient.d.ts +36 -0
  43. package/dist/extraction/LlmClient.js +73 -0
  44. package/dist/extraction/LlmClient.js.map +1 -0
  45. package/dist/extraction/RelationshipExtractor.d.ts +12 -0
  46. package/dist/extraction/RelationshipExtractor.js +38 -0
  47. package/dist/extraction/RelationshipExtractor.js.map +1 -0
  48. package/dist/extraction/index.d.ts +6 -0
  49. package/dist/extraction/index.js +16 -0
  50. package/dist/extraction/index.js.map +1 -0
  51. package/dist/extraction/prompts.d.ts +16 -0
  52. package/dist/extraction/prompts.js +101 -0
  53. package/dist/extraction/prompts.js.map +1 -0
  54. package/dist/memory/EngramChatMemory.d.ts +42 -0
  55. package/dist/memory/EngramChatMemory.js +162 -0
  56. package/dist/memory/EngramChatMemory.js.map +1 -0
  57. package/dist/memory/EngramChatMessageHistory.d.ts +22 -0
  58. package/dist/memory/EngramChatMessageHistory.js +72 -0
  59. package/dist/memory/EngramChatMessageHistory.js.map +1 -0
  60. package/dist/memory/index.d.ts +2 -0
  61. package/dist/memory/index.js +8 -0
  62. package/dist/memory/index.js.map +1 -0
  63. package/dist/nodes/EngramAdmin/EngramAdmin.node.d.ts +5 -0
  64. package/dist/nodes/EngramAdmin/EngramAdmin.node.js +798 -0
  65. package/dist/nodes/EngramAdmin/EngramAdmin.node.js.map +1 -0
  66. package/dist/nodes/EngramAdmin/engram-admin.png +0 -0
  67. package/dist/nodes/EngramExplorer/EngramExplorer.node.d.ts +5 -0
  68. package/dist/nodes/EngramExplorer/EngramExplorer.node.js +932 -0
  69. package/dist/nodes/EngramExplorer/EngramExplorer.node.js.map +1 -0
  70. package/dist/nodes/EngramExplorer/engram-explorer.png +0 -0
  71. package/dist/nodes/EngramMemory/EngramMemory.node.d.ts +10 -0
  72. package/dist/nodes/EngramMemory/EngramMemory.node.js +462 -0
  73. package/dist/nodes/EngramMemory/EngramMemory.node.js.map +1 -0
  74. package/dist/nodes/EngramMemory/engram.png +0 -0
  75. package/dist/nodes/EngramTrigger/EngramTrigger.node.d.ts +5 -0
  76. package/dist/nodes/EngramTrigger/EngramTrigger.node.js +146 -0
  77. package/dist/nodes/EngramTrigger/EngramTrigger.node.js.map +1 -0
  78. package/dist/nodes/EngramTrigger/engram-trigger.png +0 -0
  79. package/dist/schemas/Community.schema.d.ts +656 -0
  80. package/dist/schemas/Community.schema.js +26 -0
  81. package/dist/schemas/Community.schema.js.map +1 -0
  82. package/dist/schemas/EntityEdge.schema.d.ts +86 -0
  83. package/dist/schemas/EntityEdge.schema.js +34 -0
  84. package/dist/schemas/EntityEdge.schema.js.map +1 -0
  85. package/dist/schemas/EntityNode.schema.d.ts +56 -0
  86. package/dist/schemas/EntityNode.schema.js +24 -0
  87. package/dist/schemas/EntityNode.schema.js.map +1 -0
  88. package/dist/schemas/EpisodicNode.schema.d.ts +53 -0
  89. package/dist/schemas/EpisodicNode.schema.js +23 -0
  90. package/dist/schemas/EpisodicNode.schema.js.map +1 -0
  91. package/dist/schemas/GraphData.schema.d.ts +220 -0
  92. package/dist/schemas/GraphData.schema.js +25 -0
  93. package/dist/schemas/GraphData.schema.js.map +1 -0
  94. package/dist/schemas/index.d.ts +5 -0
  95. package/dist/schemas/index.js +20 -0
  96. package/dist/schemas/index.js.map +1 -0
  97. package/dist/search/HybridSearchEngine.d.ts +31 -0
  98. package/dist/search/HybridSearchEngine.js +140 -0
  99. package/dist/search/HybridSearchEngine.js.map +1 -0
  100. package/dist/search/MinisearchProvider.d.ts +20 -0
  101. package/dist/search/MinisearchProvider.js +77 -0
  102. package/dist/search/MinisearchProvider.js.map +1 -0
  103. package/dist/search/TextSearchProvider.d.ts +20 -0
  104. package/dist/search/TextSearchProvider.js +3 -0
  105. package/dist/search/TextSearchProvider.js.map +1 -0
  106. package/dist/search/index.d.ts +3 -0
  107. package/dist/search/index.js +8 -0
  108. package/dist/search/index.js.map +1 -0
  109. package/dist/storage/GraphologyStorage.d.ts +42 -0
  110. package/dist/storage/GraphologyStorage.js +665 -0
  111. package/dist/storage/GraphologyStorage.js.map +1 -0
  112. package/dist/storage/IGraphStorage.d.ts +64 -0
  113. package/dist/storage/IGraphStorage.js +3 -0
  114. package/dist/storage/IGraphStorage.js.map +1 -0
  115. package/dist/storage/Neo4jStorage.d.ts +45 -0
  116. package/dist/storage/Neo4jStorage.js +949 -0
  117. package/dist/storage/Neo4jStorage.js.map +1 -0
  118. package/dist/storage/StorageFactory.d.ts +14 -0
  119. package/dist/storage/StorageFactory.js +26 -0
  120. package/dist/storage/StorageFactory.js.map +1 -0
  121. package/dist/storage/index.d.ts +3 -0
  122. package/dist/storage/index.js +8 -0
  123. package/dist/storage/index.js.map +1 -0
  124. package/dist/traversal/EpisodeTraverser.d.ts +10 -0
  125. package/dist/traversal/EpisodeTraverser.js +46 -0
  126. package/dist/traversal/EpisodeTraverser.js.map +1 -0
  127. package/dist/traversal/GraphTraverser.d.ts +24 -0
  128. package/dist/traversal/GraphTraverser.js +89 -0
  129. package/dist/traversal/GraphTraverser.js.map +1 -0
  130. package/dist/traversal/index.d.ts +2 -0
  131. package/dist/traversal/index.js +8 -0
  132. package/dist/traversal/index.js.map +1 -0
  133. package/dist/utils/descriptions.d.ts +6 -0
  134. package/dist/utils/descriptions.js +95 -0
  135. package/dist/utils/descriptions.js.map +1 -0
  136. package/dist/utils/helpers.d.ts +23 -0
  137. package/dist/utils/helpers.js +146 -0
  138. package/dist/utils/helpers.js.map +1 -0
  139. package/dist/utils/logWrapper.d.ts +26 -0
  140. package/dist/utils/logWrapper.js +300 -0
  141. package/dist/utils/logWrapper.js.map +1 -0
  142. package/dist/utils/sharedFields.d.ts +6 -0
  143. package/dist/utils/sharedFields.js +121 -0
  144. package/dist/utils/sharedFields.js.map +1 -0
  145. package/dist/utils/temporal.d.ts +22 -0
  146. package/dist/utils/temporal.js +44 -0
  147. package/dist/utils/temporal.js.map +1 -0
  148. package/dist/utils/tracing.d.ts +7 -0
  149. package/dist/utils/tracing.js +20 -0
  150. package/dist/utils/tracing.js.map +1 -0
  151. package/dist/utils/uuid.d.ts +2 -0
  152. package/dist/utils/uuid.js +13 -0
  153. package/dist/utils/uuid.js.map +1 -0
  154. package/package.json +108 -0
@@ -0,0 +1,949 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.Neo4jStorage = void 0;
7
+ const neo4j_driver_1 = __importDefault(require("neo4j-driver"));
8
+ const uuid_1 = require("../utils/uuid");
9
+ const temporal_1 = require("../utils/temporal");
10
+ const cosine_1 = require("../embeddings/cosine");
11
+ class Neo4jStorage {
12
+ constructor(uri, username, password, database) {
13
+ this.initialized = false;
14
+ this.vectorIndexCreated = false;
15
+ const resolvedUri = uri.replace('://localhost:', '://127.0.0.1:');
16
+ this.driver = neo4j_driver_1.default.driver(resolvedUri, neo4j_driver_1.default.auth.basic(username, password));
17
+ this.database = database !== null && database !== void 0 ? database : 'neo4j';
18
+ }
19
+ async initialize() {
20
+ if (this.initialized)
21
+ return;
22
+ await this.driver.verifyConnectivity();
23
+ const session = this.getSession();
24
+ try {
25
+ await session.executeWrite(async (tx) => {
26
+ await tx.run('CREATE INDEX IF NOT EXISTS FOR (e:Entity) ON (e.uuid)');
27
+ await tx.run('CREATE INDEX IF NOT EXISTS FOR (e:Entity) ON (e.group_id)');
28
+ await tx.run('CREATE INDEX IF NOT EXISTS FOR (e:Entity) ON (e.name)');
29
+ await tx.run('CREATE INDEX IF NOT EXISTS FOR (ep:Episode) ON (ep.uuid)');
30
+ await tx.run('CREATE INDEX IF NOT EXISTS FOR (ep:Episode) ON (ep.group_id)');
31
+ await tx.run('CREATE FULLTEXT INDEX entitySearch IF NOT EXISTS FOR (e:Entity) ON EACH [e.name, e.summary, e.entity_type]');
32
+ await tx.run('CREATE FULLTEXT INDEX edgeSearch IF NOT EXISTS FOR ()-[r:RELATES_TO]-() ON EACH [r.name, r.fact]');
33
+ });
34
+ }
35
+ finally {
36
+ await session.close();
37
+ }
38
+ this.initialized = true;
39
+ }
40
+ async close() {
41
+ await this.driver.close();
42
+ }
43
+ getSession() {
44
+ return this.driver.session({ database: this.database });
45
+ }
46
+ async addEntity(input) {
47
+ var _a, _b, _c, _d;
48
+ const now = (0, temporal_1.nowIso)();
49
+ const entity = {
50
+ uuid: (0, uuid_1.generateUuid)(),
51
+ name: input.name,
52
+ group_id: input.group_id,
53
+ summary: (_a = input.summary) !== null && _a !== void 0 ? _a : '',
54
+ entity_type: (_b = input.entity_type) !== null && _b !== void 0 ? _b : 'unknown',
55
+ name_embedding: (_c = input.name_embedding) !== null && _c !== void 0 ? _c : null,
56
+ attributes: (_d = input.attributes) !== null && _d !== void 0 ? _d : {},
57
+ created_at: now,
58
+ updated_at: now,
59
+ };
60
+ if (entity.name_embedding && entity.name_embedding.length > 0) {
61
+ await this.ensureVectorIndex(entity.name_embedding.length);
62
+ }
63
+ const session = this.getSession();
64
+ try {
65
+ await session.executeWrite(async (tx) => {
66
+ await tx.run(`CREATE (e:Entity {
67
+ uuid: $uuid,
68
+ name: $name,
69
+ group_id: $group_id,
70
+ summary: $summary,
71
+ entity_type: $entity_type,
72
+ name_embedding: $name_embedding,
73
+ attributes: $attributes,
74
+ created_at: $created_at,
75
+ updated_at: $updated_at
76
+ })`, {
77
+ ...entity,
78
+ attributes: JSON.stringify(entity.attributes),
79
+ name_embedding: entity.name_embedding,
80
+ });
81
+ });
82
+ }
83
+ finally {
84
+ await session.close();
85
+ }
86
+ return entity;
87
+ }
88
+ async getEntity(uuid) {
89
+ const session = this.getSession();
90
+ try {
91
+ const result = await session.executeRead(async (tx) => {
92
+ return tx.run('MATCH (e:Entity {uuid: $uuid}) RETURN e', { uuid });
93
+ });
94
+ if (result.records.length === 0)
95
+ return null;
96
+ return this.recordToEntity(result.records[0].get('e').properties);
97
+ }
98
+ finally {
99
+ await session.close();
100
+ }
101
+ }
102
+ async getEntityByName(name, groupId) {
103
+ const session = this.getSession();
104
+ try {
105
+ const result = await session.executeRead(async (tx) => {
106
+ return tx.run('MATCH (e:Entity {group_id: $groupId}) WHERE toLower(e.name) = toLower($name) RETURN e LIMIT 1', { name, groupId });
107
+ });
108
+ if (result.records.length === 0)
109
+ return null;
110
+ return this.recordToEntity(result.records[0].get('e').properties);
111
+ }
112
+ finally {
113
+ await session.close();
114
+ }
115
+ }
116
+ async updateEntity(uuid, updates) {
117
+ const session = this.getSession();
118
+ try {
119
+ const setClauses = ['e.updated_at = $updated_at'];
120
+ const params = {
121
+ uuid,
122
+ updated_at: (0, temporal_1.nowIso)(),
123
+ };
124
+ if (updates.name !== undefined) {
125
+ setClauses.push('e.name = $name');
126
+ params.name = updates.name;
127
+ }
128
+ if (updates.summary !== undefined) {
129
+ setClauses.push('e.summary = $summary');
130
+ params.summary = updates.summary;
131
+ }
132
+ if (updates.entity_type !== undefined) {
133
+ setClauses.push('e.entity_type = $entity_type');
134
+ params.entity_type = updates.entity_type;
135
+ }
136
+ if (updates.attributes !== undefined) {
137
+ setClauses.push('e.attributes = $attributes');
138
+ params.attributes = JSON.stringify(updates.attributes);
139
+ }
140
+ if (updates.name_embedding !== undefined) {
141
+ setClauses.push('e.name_embedding = $name_embedding');
142
+ params.name_embedding = updates.name_embedding;
143
+ if (updates.name_embedding && updates.name_embedding.length > 0) {
144
+ await this.ensureVectorIndex(updates.name_embedding.length);
145
+ }
146
+ }
147
+ const result = await session.executeWrite(async (tx) => {
148
+ return tx.run(`MATCH (e:Entity {uuid: $uuid}) SET ${setClauses.join(', ')} RETURN e`, params);
149
+ });
150
+ if (result.records.length === 0) {
151
+ throw new Error(`Entity not found: ${uuid}`);
152
+ }
153
+ return this.recordToEntity(result.records[0].get('e').properties);
154
+ }
155
+ finally {
156
+ await session.close();
157
+ }
158
+ }
159
+ async deleteEntity(uuid) {
160
+ const session = this.getSession();
161
+ try {
162
+ await session.executeWrite(async (tx) => {
163
+ await tx.run('MATCH (e:Entity {uuid: $uuid}) DETACH DELETE e', { uuid });
164
+ });
165
+ }
166
+ finally {
167
+ await session.close();
168
+ }
169
+ }
170
+ async listEntities(groupId, options) {
171
+ const session = this.getSession();
172
+ try {
173
+ let query = 'MATCH (e:Entity {group_id: $groupId})';
174
+ const params = { groupId };
175
+ if (options === null || options === void 0 ? void 0 : options.entity_type) {
176
+ query += ' WHERE e.entity_type = $entity_type';
177
+ params.entity_type = options.entity_type;
178
+ }
179
+ query += ' RETURN e ORDER BY e.created_at DESC';
180
+ if (options === null || options === void 0 ? void 0 : options.offset) {
181
+ query += ' SKIP $offset';
182
+ params.offset = neo4j_driver_1.default.int(options.offset);
183
+ }
184
+ if (options === null || options === void 0 ? void 0 : options.limit) {
185
+ query += ' LIMIT $limit';
186
+ params.limit = neo4j_driver_1.default.int(options.limit);
187
+ }
188
+ const result = await session.executeRead(async (tx) => {
189
+ return tx.run(query, params);
190
+ });
191
+ return result.records.map((r) => this.recordToEntity(r.get('e').properties));
192
+ }
193
+ finally {
194
+ await session.close();
195
+ }
196
+ }
197
+ async addEdge(input) {
198
+ var _a, _b, _c, _d, _e, _f;
199
+ const now = (0, temporal_1.nowIso)();
200
+ const edge = {
201
+ uuid: (0, uuid_1.generateUuid)(),
202
+ group_id: input.group_id,
203
+ source_node_uuid: input.source_node_uuid,
204
+ target_node_uuid: input.target_node_uuid,
205
+ name: input.name,
206
+ fact: input.fact,
207
+ fact_embedding: (_a = input.fact_embedding) !== null && _a !== void 0 ? _a : null,
208
+ episodes: (_b = input.episodes) !== null && _b !== void 0 ? _b : [],
209
+ valid_at: (_c = input.valid_at) !== null && _c !== void 0 ? _c : null,
210
+ invalid_at: (_d = input.invalid_at) !== null && _d !== void 0 ? _d : null,
211
+ expired_at: (_e = input.expired_at) !== null && _e !== void 0 ? _e : null,
212
+ attributes: (_f = input.attributes) !== null && _f !== void 0 ? _f : {},
213
+ created_at: now,
214
+ updated_at: now,
215
+ };
216
+ if (edge.fact_embedding && edge.fact_embedding.length > 0) {
217
+ await this.ensureVectorIndex(edge.fact_embedding.length);
218
+ }
219
+ const session = this.getSession();
220
+ try {
221
+ const result = await session.executeWrite(async (tx) => {
222
+ return tx.run(`MATCH (source:Entity {uuid: $source_node_uuid})
223
+ MATCH (target:Entity {uuid: $target_node_uuid})
224
+ CREATE (source)-[r:RELATES_TO {
225
+ uuid: $uuid,
226
+ group_id: $group_id,
227
+ source_node_uuid: $source_node_uuid,
228
+ target_node_uuid: $target_node_uuid,
229
+ name: $name,
230
+ fact: $fact,
231
+ fact_embedding: $fact_embedding,
232
+ episodes: $episodes,
233
+ valid_at: $valid_at,
234
+ invalid_at: $invalid_at,
235
+ expired_at: $expired_at,
236
+ attributes: $attributes,
237
+ created_at: $created_at,
238
+ updated_at: $updated_at
239
+ }]->(target)
240
+ RETURN r`, {
241
+ ...edge,
242
+ attributes: JSON.stringify(edge.attributes),
243
+ });
244
+ });
245
+ if (result.records.length === 0) {
246
+ throw new Error(`Cannot create edge: source entity (${input.source_node_uuid}) or target entity (${input.target_node_uuid}) not found`);
247
+ }
248
+ }
249
+ finally {
250
+ await session.close();
251
+ }
252
+ return edge;
253
+ }
254
+ async getEdge(uuid) {
255
+ const session = this.getSession();
256
+ try {
257
+ const result = await session.executeRead(async (tx) => {
258
+ return tx.run('MATCH ()-[r:RELATES_TO {uuid: $uuid}]->() RETURN r', { uuid });
259
+ });
260
+ if (result.records.length === 0)
261
+ return null;
262
+ return this.recordToEdge(result.records[0].get('r').properties);
263
+ }
264
+ finally {
265
+ await session.close();
266
+ }
267
+ }
268
+ async getEdgesBetween(sourceUuid, targetUuid) {
269
+ const session = this.getSession();
270
+ try {
271
+ const result = await session.executeRead(async (tx) => {
272
+ return tx.run(`MATCH (s:Entity {uuid: $sourceUuid})-[r:RELATES_TO]->(t:Entity {uuid: $targetUuid})
273
+ RETURN r
274
+ UNION
275
+ MATCH (s:Entity {uuid: $targetUuid})-[r:RELATES_TO]->(t:Entity {uuid: $sourceUuid})
276
+ RETURN r`, { sourceUuid, targetUuid });
277
+ });
278
+ return result.records.map((rec) => this.recordToEdge(rec.get('r').properties));
279
+ }
280
+ finally {
281
+ await session.close();
282
+ }
283
+ }
284
+ async getEdgesForEntity(entityUuid) {
285
+ const session = this.getSession();
286
+ try {
287
+ const result = await session.executeRead(async (tx) => {
288
+ return tx.run(`MATCH (e:Entity {uuid: $entityUuid})-[r:RELATES_TO]->()
289
+ RETURN r
290
+ UNION
291
+ MATCH (e:Entity {uuid: $entityUuid})<-[r:RELATES_TO]-()
292
+ RETURN r`, { entityUuid });
293
+ });
294
+ return result.records.map((rec) => this.recordToEdge(rec.get('r').properties));
295
+ }
296
+ finally {
297
+ await session.close();
298
+ }
299
+ }
300
+ async updateEdge(uuid, updates) {
301
+ const session = this.getSession();
302
+ try {
303
+ const setClauses = ['r.updated_at = $updated_at'];
304
+ const params = {
305
+ uuid,
306
+ updated_at: (0, temporal_1.nowIso)(),
307
+ };
308
+ if (updates.name !== undefined) {
309
+ setClauses.push('r.name = $name');
310
+ params.name = updates.name;
311
+ }
312
+ if (updates.fact !== undefined) {
313
+ setClauses.push('r.fact = $fact');
314
+ params.fact = updates.fact;
315
+ }
316
+ if (updates.valid_at !== undefined) {
317
+ setClauses.push('r.valid_at = $valid_at');
318
+ params.valid_at = updates.valid_at;
319
+ }
320
+ if (updates.invalid_at !== undefined) {
321
+ setClauses.push('r.invalid_at = $invalid_at');
322
+ params.invalid_at = updates.invalid_at;
323
+ }
324
+ if (updates.expired_at !== undefined) {
325
+ setClauses.push('r.expired_at = $expired_at');
326
+ params.expired_at = updates.expired_at;
327
+ }
328
+ if (updates.episodes !== undefined) {
329
+ setClauses.push('r.episodes = $episodes');
330
+ params.episodes = updates.episodes;
331
+ }
332
+ if (updates.attributes !== undefined) {
333
+ setClauses.push('r.attributes = $attributes');
334
+ params.attributes = JSON.stringify(updates.attributes);
335
+ }
336
+ if (updates.fact_embedding !== undefined) {
337
+ setClauses.push('r.fact_embedding = $fact_embedding');
338
+ params.fact_embedding = updates.fact_embedding;
339
+ if (updates.fact_embedding && updates.fact_embedding.length > 0) {
340
+ await this.ensureVectorIndex(updates.fact_embedding.length);
341
+ }
342
+ }
343
+ const result = await session.executeWrite(async (tx) => {
344
+ return tx.run(`MATCH ()-[r:RELATES_TO {uuid: $uuid}]->()
345
+ SET ${setClauses.join(', ')}
346
+ RETURN r`, params);
347
+ });
348
+ if (result.records.length === 0) {
349
+ throw new Error(`Edge not found: ${uuid}`);
350
+ }
351
+ return this.recordToEdge(result.records[0].get('r').properties);
352
+ }
353
+ finally {
354
+ await session.close();
355
+ }
356
+ }
357
+ async deleteEdge(uuid) {
358
+ const session = this.getSession();
359
+ try {
360
+ await session.executeWrite(async (tx) => {
361
+ await tx.run('MATCH ()-[r:RELATES_TO {uuid: $uuid}]->() DELETE r', { uuid });
362
+ });
363
+ }
364
+ finally {
365
+ await session.close();
366
+ }
367
+ }
368
+ async addEpisode(input) {
369
+ var _a, _b;
370
+ const episode = {
371
+ uuid: (0, uuid_1.generateUuid)(),
372
+ group_id: input.group_id,
373
+ content: input.content,
374
+ role: input.role,
375
+ source_type: (_a = input.source_type) !== null && _a !== void 0 ? _a : 'message',
376
+ reference_time: input.reference_time,
377
+ previous_episode_uuid: (_b = input.previous_episode_uuid) !== null && _b !== void 0 ? _b : null,
378
+ created_at: (0, temporal_1.nowIso)(),
379
+ };
380
+ const session = this.getSession();
381
+ try {
382
+ await session.executeWrite(async (tx) => {
383
+ await tx.run(`CREATE (ep:Episode {
384
+ uuid: $uuid,
385
+ group_id: $group_id,
386
+ content: $content,
387
+ role: $role,
388
+ source_type: $source_type,
389
+ reference_time: $reference_time,
390
+ previous_episode_uuid: $previous_episode_uuid,
391
+ created_at: $created_at
392
+ })`, episode);
393
+ if (episode.previous_episode_uuid) {
394
+ await tx.run(`MATCH (prev:Episode {uuid: $prevUuid})
395
+ MATCH (curr:Episode {uuid: $currUuid})
396
+ CREATE (prev)-[:NEXT_EPISODE]->(curr)`, {
397
+ prevUuid: episode.previous_episode_uuid,
398
+ currUuid: episode.uuid,
399
+ });
400
+ }
401
+ });
402
+ }
403
+ finally {
404
+ await session.close();
405
+ }
406
+ return episode;
407
+ }
408
+ async getEpisode(uuid) {
409
+ const session = this.getSession();
410
+ try {
411
+ const result = await session.executeRead(async (tx) => {
412
+ return tx.run('MATCH (ep:Episode {uuid: $uuid}) RETURN ep', { uuid });
413
+ });
414
+ if (result.records.length === 0)
415
+ return null;
416
+ return this.recordToEpisode(result.records[0].get('ep').properties);
417
+ }
418
+ finally {
419
+ await session.close();
420
+ }
421
+ }
422
+ async getRecentEpisodes(groupId, limit) {
423
+ const session = this.getSession();
424
+ try {
425
+ const result = await session.executeRead(async (tx) => {
426
+ return tx.run(`MATCH (ep:Episode {group_id: $groupId})
427
+ RETURN ep
428
+ ORDER BY ep.created_at DESC
429
+ LIMIT $limit`, { groupId, limit: neo4j_driver_1.default.int(limit) });
430
+ });
431
+ const episodes = result.records.map((r) => this.recordToEpisode(r.get('ep').properties));
432
+ return episodes.reverse();
433
+ }
434
+ finally {
435
+ await session.close();
436
+ }
437
+ }
438
+ async getEpisodeCount(groupId) {
439
+ var _a, _b;
440
+ const session = this.getSession();
441
+ try {
442
+ const result = await session.executeRead(async (tx) => {
443
+ return tx.run('MATCH (ep:Episode {group_id: $groupId}) RETURN count(ep) as cnt', {
444
+ groupId,
445
+ });
446
+ });
447
+ return (_b = (_a = result.records[0]) === null || _a === void 0 ? void 0 : _a.get('cnt').toNumber()) !== null && _b !== void 0 ? _b : 0;
448
+ }
449
+ finally {
450
+ await session.close();
451
+ }
452
+ }
453
+ sanitizeLuceneQuery(query) {
454
+ const sanitized = query.replace(/[+\-&|!(){}[\]^"~*?:\\/]/g, (ch) => `\\${ch}`);
455
+ return sanitized.trim();
456
+ }
457
+ async searchEntities(query, groupId, options) {
458
+ var _a;
459
+ const safeQuery = this.sanitizeLuceneQuery(query);
460
+ if (!safeQuery)
461
+ return [];
462
+ const limit = (_a = options === null || options === void 0 ? void 0 : options.limit) !== null && _a !== void 0 ? _a : 10;
463
+ const session = this.getSession();
464
+ try {
465
+ let cypher = `CALL db.index.fulltext.queryNodes('entitySearch', $query)
466
+ YIELD node, score
467
+ WHERE node.group_id = $groupId`;
468
+ const params = { query: safeQuery, groupId };
469
+ if (options === null || options === void 0 ? void 0 : options.entity_type) {
470
+ cypher += ' AND node.entity_type = $entityType';
471
+ params.entityType = options.entity_type;
472
+ }
473
+ cypher += ' RETURN node, score ORDER BY score DESC LIMIT $limit';
474
+ params.limit = neo4j_driver_1.default.int(limit);
475
+ const result = await session.executeRead(async (tx) => {
476
+ return tx.run(cypher, params);
477
+ });
478
+ const maxScore = result.records.length > 0 ? result.records[0].get('score') : 1;
479
+ return result.records
480
+ .filter((r) => {
481
+ var _a;
482
+ const score = r.get('score') / (maxScore || 1);
483
+ return score >= ((_a = options === null || options === void 0 ? void 0 : options.min_score) !== null && _a !== void 0 ? _a : 0);
484
+ })
485
+ .map((r) => ({
486
+ entity: this.recordToEntity(r.get('node').properties),
487
+ score: r.get('score') / (maxScore || 1),
488
+ }));
489
+ }
490
+ finally {
491
+ await session.close();
492
+ }
493
+ }
494
+ async searchEdges(query, groupId, options) {
495
+ var _a, _b;
496
+ const safeQuery = this.sanitizeLuceneQuery(query);
497
+ if (!safeQuery)
498
+ return [];
499
+ const limit = (_a = options === null || options === void 0 ? void 0 : options.limit) !== null && _a !== void 0 ? _a : 10;
500
+ const includeExpired = (_b = options === null || options === void 0 ? void 0 : options.include_expired) !== null && _b !== void 0 ? _b : false;
501
+ const session = this.getSession();
502
+ try {
503
+ let cypher = `CALL db.index.fulltext.queryRelationships('edgeSearch', $query)
504
+ YIELD relationship, score
505
+ WHERE relationship.group_id = $groupId`;
506
+ if (!includeExpired) {
507
+ cypher += ' AND relationship.expired_at IS NULL';
508
+ }
509
+ cypher += ` WITH relationship, score
510
+ ORDER BY score DESC LIMIT $limit
511
+ MATCH (source:Entity {uuid: relationship.source_node_uuid})
512
+ MATCH (target:Entity {uuid: relationship.target_node_uuid})
513
+ RETURN relationship, source, target, score`;
514
+ const params = {
515
+ query: safeQuery,
516
+ groupId,
517
+ limit: neo4j_driver_1.default.int(limit),
518
+ };
519
+ const result = await session.executeRead(async (tx) => {
520
+ return tx.run(cypher, params);
521
+ });
522
+ const maxScore = result.records.length > 0 ? result.records[0].get('score') : 1;
523
+ return result.records
524
+ .filter((r) => {
525
+ var _a;
526
+ const score = r.get('score') / (maxScore || 1);
527
+ return score >= ((_a = options === null || options === void 0 ? void 0 : options.min_score) !== null && _a !== void 0 ? _a : 0);
528
+ })
529
+ .map((r) => ({
530
+ edge: this.recordToEdge(r.get('relationship').properties),
531
+ sourceEntity: this.recordToEntity(r.get('source').properties),
532
+ targetEntity: this.recordToEntity(r.get('target').properties),
533
+ score: r.get('score') / (maxScore || 1),
534
+ }));
535
+ }
536
+ finally {
537
+ await session.close();
538
+ }
539
+ }
540
+ async ensureVectorIndex(dimensions) {
541
+ if (this.vectorIndexCreated)
542
+ return;
543
+ const session = this.getSession();
544
+ try {
545
+ await session.executeWrite(async (tx) => {
546
+ await tx.run(`CREATE VECTOR INDEX entityNameEmbedding IF NOT EXISTS
547
+ FOR (e:Entity)
548
+ ON (e.name_embedding)
549
+ OPTIONS {indexConfig: {
550
+ \`vector.dimensions\`: $dimensions,
551
+ \`vector.similarity_function\`: 'cosine'
552
+ }}`, { dimensions: neo4j_driver_1.default.int(dimensions) });
553
+ });
554
+ this.vectorIndexCreated = true;
555
+ }
556
+ catch (error) {
557
+ console.warn('Engram: Could not create vector indexes (Neo4j may not support them):', error.message);
558
+ }
559
+ finally {
560
+ await session.close();
561
+ }
562
+ }
563
+ async searchEntitiesByVector(vector, groupId, options) {
564
+ var _a, _b;
565
+ const limit = (_a = options === null || options === void 0 ? void 0 : options.limit) !== null && _a !== void 0 ? _a : 10;
566
+ const minScore = (_b = options === null || options === void 0 ? void 0 : options.min_score) !== null && _b !== void 0 ? _b : 0;
567
+ const session = this.getSession();
568
+ try {
569
+ const result = await session.executeRead(async (tx) => {
570
+ return tx.run(`CALL db.index.vector.queryNodes('entityNameEmbedding', $topK, $vector)
571
+ YIELD node, score
572
+ WHERE node.group_id = $groupId AND score >= $minScore
573
+ RETURN node, score
574
+ ORDER BY score DESC
575
+ LIMIT $limit`, {
576
+ vector,
577
+ groupId,
578
+ topK: neo4j_driver_1.default.int(limit * 2),
579
+ minScore,
580
+ limit: neo4j_driver_1.default.int(limit),
581
+ });
582
+ });
583
+ return result.records.map((r) => ({
584
+ entity: this.recordToEntity(r.get('node').properties),
585
+ score: r.get('score'),
586
+ }));
587
+ }
588
+ catch {
589
+ return this.bruteForceEntityVectorSearch(vector, groupId, options);
590
+ }
591
+ finally {
592
+ await session.close();
593
+ }
594
+ }
595
+ async searchEdgesByVector(vector, groupId, options) {
596
+ var _a, _b;
597
+ const limit = (_a = options === null || options === void 0 ? void 0 : options.limit) !== null && _a !== void 0 ? _a : 10;
598
+ const minScore = (_b = options === null || options === void 0 ? void 0 : options.min_score) !== null && _b !== void 0 ? _b : 0;
599
+ const session = this.getSession();
600
+ try {
601
+ const result = await session.executeRead(async (tx) => {
602
+ return tx.run(`MATCH (source:Entity)-[r:RELATES_TO]->(target:Entity)
603
+ WHERE r.group_id = $groupId
604
+ AND r.fact_embedding IS NOT NULL
605
+ AND r.expired_at IS NULL
606
+ RETURN r, source, target`, { groupId });
607
+ });
608
+ const scored = [];
609
+ for (const rec of result.records) {
610
+ const edgeProps = rec.get('r').properties;
611
+ const embedding = edgeProps.fact_embedding;
612
+ if (!embedding || embedding.length !== vector.length)
613
+ continue;
614
+ try {
615
+ const score = (0, cosine_1.cosineSimilarity)(vector, embedding);
616
+ if (score < minScore)
617
+ continue;
618
+ scored.push({
619
+ edge: this.recordToEdge(edgeProps),
620
+ sourceEntity: this.recordToEntity(rec.get('source').properties),
621
+ targetEntity: this.recordToEntity(rec.get('target').properties),
622
+ score,
623
+ });
624
+ }
625
+ catch {
626
+ }
627
+ }
628
+ scored.sort((a, b) => b.score - a.score);
629
+ return scored.slice(0, limit);
630
+ }
631
+ finally {
632
+ await session.close();
633
+ }
634
+ }
635
+ async bruteForceEntityVectorSearch(vector, groupId, options) {
636
+ var _a, _b;
637
+ const limit = (_a = options === null || options === void 0 ? void 0 : options.limit) !== null && _a !== void 0 ? _a : 10;
638
+ const minScore = (_b = options === null || options === void 0 ? void 0 : options.min_score) !== null && _b !== void 0 ? _b : 0;
639
+ const session = this.getSession();
640
+ try {
641
+ const result = await session.executeRead(async (tx) => {
642
+ return tx.run(`MATCH (e:Entity {group_id: $groupId})
643
+ WHERE e.name_embedding IS NOT NULL
644
+ RETURN e`, { groupId });
645
+ });
646
+ const scored = [];
647
+ for (const rec of result.records) {
648
+ const entity = this.recordToEntity(rec.get('e').properties);
649
+ if (!entity.name_embedding || entity.name_embedding.length !== vector.length)
650
+ continue;
651
+ try {
652
+ const score = (0, cosine_1.cosineSimilarity)(vector, entity.name_embedding);
653
+ if (score >= minScore) {
654
+ scored.push({ entity, score });
655
+ }
656
+ }
657
+ catch {
658
+ }
659
+ }
660
+ scored.sort((a, b) => b.score - a.score);
661
+ return scored.slice(0, limit);
662
+ }
663
+ finally {
664
+ await session.close();
665
+ }
666
+ }
667
+ async clearGroup(groupId) {
668
+ const session = this.getSession();
669
+ try {
670
+ await session.executeWrite(async (tx) => {
671
+ await tx.run(`MATCH ()-[r:RELATES_TO {group_id: $groupId}]->()
672
+ DELETE r`, { groupId });
673
+ await tx.run(`MATCH (n)
674
+ WHERE n.group_id = $groupId AND (n:Entity OR n:Episode)
675
+ DETACH DELETE n`, { groupId });
676
+ });
677
+ }
678
+ finally {
679
+ await session.close();
680
+ }
681
+ }
682
+ async clearAll() {
683
+ const session = this.getSession();
684
+ try {
685
+ await session.executeWrite(async (tx) => {
686
+ await tx.run('MATCH (n) WHERE n:Entity OR n:Episode DETACH DELETE n');
687
+ });
688
+ }
689
+ finally {
690
+ await session.close();
691
+ }
692
+ }
693
+ async exportGraph(groupId) {
694
+ const entities = [];
695
+ const edges = [];
696
+ const episodes = [];
697
+ const session = this.getSession();
698
+ try {
699
+ const entityQuery = groupId
700
+ ? 'MATCH (e:Entity {group_id: $groupId}) RETURN e'
701
+ : 'MATCH (e:Entity) RETURN e';
702
+ const entityResult = await session.executeRead(async (tx) => {
703
+ return tx.run(entityQuery, groupId ? { groupId } : {});
704
+ });
705
+ for (const r of entityResult.records) {
706
+ entities.push(this.recordToEntity(r.get('e').properties));
707
+ }
708
+ const edgeQuery = groupId
709
+ ? 'MATCH ()-[r:RELATES_TO {group_id: $groupId}]->() RETURN r'
710
+ : 'MATCH ()-[r:RELATES_TO]->() RETURN r';
711
+ const edgeResult = await session.executeRead(async (tx) => {
712
+ return tx.run(edgeQuery, groupId ? { groupId } : {});
713
+ });
714
+ for (const r of edgeResult.records) {
715
+ edges.push(this.recordToEdge(r.get('r').properties));
716
+ }
717
+ const episodeQuery = groupId
718
+ ? 'MATCH (ep:Episode {group_id: $groupId}) RETURN ep'
719
+ : 'MATCH (ep:Episode) RETURN ep';
720
+ const episodeResult = await session.executeRead(async (tx) => {
721
+ return tx.run(episodeQuery, groupId ? { groupId } : {});
722
+ });
723
+ for (const r of episodeResult.records) {
724
+ episodes.push(this.recordToEpisode(r.get('ep').properties));
725
+ }
726
+ }
727
+ finally {
728
+ await session.close();
729
+ }
730
+ return {
731
+ version: '1.0',
732
+ exported_at: (0, temporal_1.nowIso)(),
733
+ group_id: groupId,
734
+ entities,
735
+ edges,
736
+ episodes,
737
+ };
738
+ }
739
+ async importGraph(data) {
740
+ const session = this.getSession();
741
+ try {
742
+ await session.executeWrite(async (tx) => {
743
+ for (const entity of data.entities) {
744
+ await tx.run(`MERGE (e:Entity {uuid: $uuid})
745
+ SET e += {
746
+ name: $name,
747
+ group_id: $group_id,
748
+ summary: $summary,
749
+ entity_type: $entity_type,
750
+ name_embedding: $name_embedding,
751
+ attributes: $attributes,
752
+ created_at: $created_at,
753
+ updated_at: $updated_at
754
+ }`, {
755
+ ...entity,
756
+ attributes: JSON.stringify(entity.attributes),
757
+ });
758
+ }
759
+ for (const episode of data.episodes) {
760
+ await tx.run(`MERGE (ep:Episode {uuid: $uuid})
761
+ SET ep += {
762
+ group_id: $group_id,
763
+ content: $content,
764
+ role: $role,
765
+ source_type: $source_type,
766
+ reference_time: $reference_time,
767
+ previous_episode_uuid: $previous_episode_uuid,
768
+ created_at: $created_at
769
+ }`, episode);
770
+ }
771
+ for (const edge of data.edges) {
772
+ await tx.run(`MATCH (source:Entity {uuid: $source_node_uuid})
773
+ MATCH (target:Entity {uuid: $target_node_uuid})
774
+ MERGE (source)-[r:RELATES_TO {uuid: $uuid}]->(target)
775
+ SET r += {
776
+ group_id: $group_id,
777
+ source_node_uuid: $source_node_uuid,
778
+ target_node_uuid: $target_node_uuid,
779
+ name: $name,
780
+ fact: $fact,
781
+ fact_embedding: $fact_embedding,
782
+ episodes: $episodes,
783
+ valid_at: $valid_at,
784
+ invalid_at: $invalid_at,
785
+ expired_at: $expired_at,
786
+ attributes: $attributes,
787
+ created_at: $created_at,
788
+ updated_at: $updated_at
789
+ }`, {
790
+ ...edge,
791
+ attributes: JSON.stringify(edge.attributes),
792
+ });
793
+ }
794
+ for (const episode of data.episodes) {
795
+ if (episode.previous_episode_uuid) {
796
+ await tx.run(`MATCH (prev:Episode {uuid: $prevUuid})
797
+ MATCH (curr:Episode {uuid: $currUuid})
798
+ MERGE (prev)-[:NEXT_EPISODE]->(curr)`, {
799
+ prevUuid: episode.previous_episode_uuid,
800
+ currUuid: episode.uuid,
801
+ });
802
+ }
803
+ }
804
+ });
805
+ }
806
+ finally {
807
+ await session.close();
808
+ }
809
+ }
810
+ async getStats(groupId) {
811
+ var _a, _b, _c;
812
+ const session = this.getSession();
813
+ try {
814
+ const where = groupId ? ' WHERE n.group_id = $groupId' : '';
815
+ const params = groupId ? { groupId } : {};
816
+ const result = await session.executeRead(async (tx) => {
817
+ return tx.run(`MATCH (n)
818
+ ${where}
819
+ WITH n
820
+ WHERE n:Entity OR n:Episode
821
+ RETURN
822
+ sum(CASE WHEN n:Entity THEN 1 ELSE 0 END) as entityCount,
823
+ sum(CASE WHEN n:Episode THEN 1 ELSE 0 END) as episodeCount,
824
+ collect(DISTINCT n.group_id) as groupIds,
825
+ collect(CASE WHEN n:Entity THEN n.entity_type ELSE null END) as entityTypes,
826
+ min(CASE WHEN n:Episode THEN n.created_at ELSE null END) as oldestEpisode,
827
+ max(CASE WHEN n:Episode THEN n.created_at ELSE null END) as newestEpisode`, params);
828
+ });
829
+ const edgeResult = await session.executeRead(async (tx) => {
830
+ const edgeWhere = groupId ? 'WHERE r.group_id = $groupId' : '';
831
+ return tx.run(`MATCH ()-[r:RELATES_TO]->() ${edgeWhere} RETURN count(r) as edgeCount`, params);
832
+ });
833
+ const record = result.records[0];
834
+ const entityTypesList = record.get('entityTypes').filter(Boolean);
835
+ const entityTypes = {};
836
+ for (const t of entityTypesList) {
837
+ entityTypes[t] = ((_a = entityTypes[t]) !== null && _a !== void 0 ? _a : 0) + 1;
838
+ }
839
+ return {
840
+ entity_count: record.get('entityCount').toNumber(),
841
+ edge_count: edgeResult.records[0].get('edgeCount').toNumber(),
842
+ episode_count: record.get('episodeCount').toNumber(),
843
+ group_ids: record.get('groupIds'),
844
+ entity_types: entityTypes,
845
+ oldest_episode: (_b = record.get('oldestEpisode')) !== null && _b !== void 0 ? _b : null,
846
+ newest_episode: (_c = record.get('newestEpisode')) !== null && _c !== void 0 ? _c : null,
847
+ };
848
+ }
849
+ finally {
850
+ await session.close();
851
+ }
852
+ }
853
+ async applyRetention(groupId, policy) {
854
+ var _a, _b, _c, _d, _e, _f;
855
+ if (policy.type === 'forever')
856
+ return 0;
857
+ const session = this.getSession();
858
+ try {
859
+ if (policy.type === 'days' && policy.value) {
860
+ const cutoffDate = new Date(Date.now() - policy.value * 24 * 60 * 60 * 1000).toISOString();
861
+ const result = await session.executeWrite(async (tx) => {
862
+ return tx.run(`MATCH (ep:Episode {group_id: $groupId})
863
+ WHERE ep.created_at < $cutoffDate
864
+ DETACH DELETE ep
865
+ RETURN count(*) as removed`, { groupId, cutoffDate });
866
+ });
867
+ return (_c = (_b = (_a = result.records[0]) === null || _a === void 0 ? void 0 : _a.get('removed')) === null || _b === void 0 ? void 0 : _b.toNumber()) !== null && _c !== void 0 ? _c : 0;
868
+ }
869
+ if (policy.type === 'max_episodes' && policy.value) {
870
+ const result = await session.executeWrite(async (tx) => {
871
+ return tx.run(`MATCH (ep:Episode {group_id: $groupId})
872
+ WITH ep ORDER BY ep.created_at DESC
873
+ SKIP $maxEpisodes
874
+ WITH collect(ep) as toDelete, count(ep) as cnt
875
+ UNWIND toDelete as ep
876
+ DETACH DELETE ep
877
+ RETURN cnt as removed`, { groupId, maxEpisodes: neo4j_driver_1.default.int(policy.value) });
878
+ });
879
+ return (_f = (_e = (_d = result.records[0]) === null || _d === void 0 ? void 0 : _d.get('removed')) === null || _e === void 0 ? void 0 : _e.toNumber()) !== null && _f !== void 0 ? _f : 0;
880
+ }
881
+ return 0;
882
+ }
883
+ finally {
884
+ await session.close();
885
+ }
886
+ }
887
+ recordToEntity(props) {
888
+ var _a, _b, _c;
889
+ return {
890
+ uuid: props.uuid,
891
+ name: props.name,
892
+ group_id: props.group_id,
893
+ summary: (_a = props.summary) !== null && _a !== void 0 ? _a : '',
894
+ entity_type: (_b = props.entity_type) !== null && _b !== void 0 ? _b : 'unknown',
895
+ name_embedding: (_c = props.name_embedding) !== null && _c !== void 0 ? _c : null,
896
+ attributes: this.parseJsonField(props.attributes, {}),
897
+ created_at: props.created_at,
898
+ updated_at: props.updated_at,
899
+ };
900
+ }
901
+ recordToEdge(props) {
902
+ var _a, _b, _c, _d, _e;
903
+ return {
904
+ uuid: props.uuid,
905
+ group_id: props.group_id,
906
+ source_node_uuid: props.source_node_uuid,
907
+ target_node_uuid: props.target_node_uuid,
908
+ name: props.name,
909
+ fact: props.fact,
910
+ fact_embedding: (_a = props.fact_embedding) !== null && _a !== void 0 ? _a : null,
911
+ episodes: (_b = props.episodes) !== null && _b !== void 0 ? _b : [],
912
+ valid_at: (_c = props.valid_at) !== null && _c !== void 0 ? _c : null,
913
+ invalid_at: (_d = props.invalid_at) !== null && _d !== void 0 ? _d : null,
914
+ expired_at: (_e = props.expired_at) !== null && _e !== void 0 ? _e : null,
915
+ attributes: this.parseJsonField(props.attributes, {}),
916
+ created_at: props.created_at,
917
+ updated_at: props.updated_at,
918
+ };
919
+ }
920
+ recordToEpisode(props) {
921
+ var _a, _b;
922
+ return {
923
+ uuid: props.uuid,
924
+ group_id: props.group_id,
925
+ content: props.content,
926
+ role: props.role,
927
+ source_type: (_a = props.source_type) !== null && _a !== void 0 ? _a : 'message',
928
+ reference_time: props.reference_time,
929
+ previous_episode_uuid: (_b = props.previous_episode_uuid) !== null && _b !== void 0 ? _b : null,
930
+ created_at: props.created_at,
931
+ };
932
+ }
933
+ parseJsonField(value, fallback) {
934
+ if (typeof value === 'string') {
935
+ try {
936
+ return JSON.parse(value);
937
+ }
938
+ catch {
939
+ return fallback;
940
+ }
941
+ }
942
+ if (typeof value === 'object' && value !== null) {
943
+ return value;
944
+ }
945
+ return fallback;
946
+ }
947
+ }
948
+ exports.Neo4jStorage = Neo4jStorage;
949
+ //# sourceMappingURL=Neo4jStorage.js.map