@lucern/graph-sync 0.3.0-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/README.md +121 -0
- package/dist/index.d.ts +207 -0
- package/dist/index.js +3249 -0
- package/dist/index.js.map +1 -0
- package/dist/neo4jDriver.d.ts +95 -0
- package/dist/neo4jDriver.js +343 -0
- package/dist/neo4jDriver.js.map +1 -0
- package/dist/neo4jEdgeAPI.d.ts +10 -0
- package/dist/neo4jEdgeAPI.js +523 -0
- package/dist/neo4jEdgeAPI.js.map +1 -0
- package/dist/neo4jQueries-eF7YNiqS.d.ts +297 -0
- package/dist/neo4jQueries.d.ts +1 -0
- package/dist/neo4jQueries.js +1327 -0
- package/dist/neo4jQueries.js.map +1 -0
- package/dist/neo4jQueryRoute.d.ts +20 -0
- package/dist/neo4jQueryRoute.js +187 -0
- package/dist/neo4jQueryRoute.js.map +1 -0
- package/dist/neo4jSync.d.ts +29 -0
- package/dist/neo4jSync.js +1000 -0
- package/dist/neo4jSync.js.map +1 -0
- package/dist/neo4jSyncHelpers-vxe1-Gvw.d.ts +58 -0
- package/dist/neo4jSyncHelpers.d.ts +1 -0
- package/dist/neo4jSyncHelpers.js +332 -0
- package/dist/neo4jSyncHelpers.js.map +1 -0
- package/package.json +62 -0
|
@@ -0,0 +1,1000 @@
|
|
|
1
|
+
"use node";
|
|
2
|
+
import { v } from 'convex/values';
|
|
3
|
+
import { NODE_TYPE_TO_LABEL, EDGE_TYPE_TO_REL } from '@lucern/graph-primitives/graphTypes';
|
|
4
|
+
import { permissiveReturn } from '@lucern/contracts/schema-helpers/validators';
|
|
5
|
+
import { componentsGeneric, anyApi, internalActionGeneric } from 'convex/server';
|
|
6
|
+
import neo4j from 'neo4j-driver';
|
|
7
|
+
|
|
8
|
+
componentsGeneric();
|
|
9
|
+
var internal = anyApi;
|
|
10
|
+
var internalAction = internalActionGeneric;
|
|
11
|
+
var VALID_NODE_LABELS = /* @__PURE__ */ new Set([
|
|
12
|
+
// Ontological
|
|
13
|
+
"ValueChain",
|
|
14
|
+
"Function",
|
|
15
|
+
"FinSector",
|
|
16
|
+
"Company",
|
|
17
|
+
"Person",
|
|
18
|
+
"Investor",
|
|
19
|
+
// Epistemic
|
|
20
|
+
"Theme",
|
|
21
|
+
"Belief",
|
|
22
|
+
"Question",
|
|
23
|
+
"Evidence",
|
|
24
|
+
"Source",
|
|
25
|
+
"Decision",
|
|
26
|
+
"Sprint",
|
|
27
|
+
"Claim",
|
|
28
|
+
"Synthesis",
|
|
29
|
+
"Answer"
|
|
30
|
+
]);
|
|
31
|
+
var VALID_RELATIONSHIP_TYPES = /* @__PURE__ */ new Set([
|
|
32
|
+
// Cross-layer edges
|
|
33
|
+
"EXTRACTED_FROM",
|
|
34
|
+
"ANSWERS",
|
|
35
|
+
"RESPONDS_TO",
|
|
36
|
+
"INFORMS",
|
|
37
|
+
"QUALIFIES",
|
|
38
|
+
"TESTS",
|
|
39
|
+
"EXPLORES",
|
|
40
|
+
"BASED_ON",
|
|
41
|
+
"RELATES_TO_THESIS",
|
|
42
|
+
"BELONGS_TO",
|
|
43
|
+
"PLAYS_THEME",
|
|
44
|
+
// Same-layer edges
|
|
45
|
+
"SUPERSEDES",
|
|
46
|
+
"SAME_AS",
|
|
47
|
+
"DEPENDS_ON",
|
|
48
|
+
"REINFORCES",
|
|
49
|
+
"PARENT_OF",
|
|
50
|
+
"CHILD_OF",
|
|
51
|
+
"FALSIFIED_BY",
|
|
52
|
+
"EXCLUSIVE_WITH",
|
|
53
|
+
"COLLAPSES_IF",
|
|
54
|
+
"CASCADE_FROM",
|
|
55
|
+
"STRENGTHENED_BY",
|
|
56
|
+
"WEAKENED_BY",
|
|
57
|
+
"ALTERNATIVE_TO",
|
|
58
|
+
"SUBSUMES",
|
|
59
|
+
"VALIDATED_BY",
|
|
60
|
+
"REQUIRED_FOR",
|
|
61
|
+
"PREREQUISITE_FOR",
|
|
62
|
+
"PARALLEL_TO",
|
|
63
|
+
"CORROBORATES",
|
|
64
|
+
"EXTENDS",
|
|
65
|
+
"SAME_SOURCE_AS",
|
|
66
|
+
"SAME_THEME_AS",
|
|
67
|
+
// Ontological
|
|
68
|
+
"EVALUATES",
|
|
69
|
+
"PERSPECTIVE_ON",
|
|
70
|
+
"WORKS_AT",
|
|
71
|
+
"PARTICIPATES_IN",
|
|
72
|
+
"PERFORMS",
|
|
73
|
+
"FUNCTION_IN",
|
|
74
|
+
"IMPACTS",
|
|
75
|
+
"INVESTED_IN",
|
|
76
|
+
"RAISED_FROM",
|
|
77
|
+
"BASED_ON_BELIEF",
|
|
78
|
+
"BASED_ON_QUESTION",
|
|
79
|
+
"BLOCKED_BY_CONTRADICTION",
|
|
80
|
+
"INFORMED_BY_THEME"
|
|
81
|
+
]);
|
|
82
|
+
function validateLabel(label) {
|
|
83
|
+
if (!VALID_NODE_LABELS.has(label)) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
`[Neo4j Security] Invalid node label: ${label}. Must be one of: ${Array.from(VALID_NODE_LABELS).join(", ")}`
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function validateRelType(relType) {
|
|
90
|
+
if (!VALID_RELATIONSHIP_TYPES.has(relType)) {
|
|
91
|
+
throw new Error(
|
|
92
|
+
`[Neo4j Security] Invalid relationship type: ${relType}. Must be one of: ${Array.from(VALID_RELATIONSHIP_TYPES).join(", ")}`
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
var driver = null;
|
|
97
|
+
function getDriver() {
|
|
98
|
+
if (!driver) {
|
|
99
|
+
const uri = process.env.NEO4J_URI;
|
|
100
|
+
const user = process.env.NEO4J_USER;
|
|
101
|
+
const password = process.env.NEO4J_PASSWORD;
|
|
102
|
+
if (!uri || !user || !password) {
|
|
103
|
+
throw new Error(
|
|
104
|
+
"[Neo4j Driver] Missing credentials. Set NEO4J_URI, NEO4J_USER, NEO4J_PASSWORD via `npx convex env set`"
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
driver = neo4j.driver(uri, neo4j.auth.basic(user, password), {
|
|
108
|
+
// Connection pool settings
|
|
109
|
+
maxConnectionPoolSize: 50,
|
|
110
|
+
connectionAcquisitionTimeout: 3e4,
|
|
111
|
+
// Logging
|
|
112
|
+
logging: {
|
|
113
|
+
level: "warn",
|
|
114
|
+
logger: (level, message) => console.log(`[Neo4j ${level}] ${message}`)
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
return driver;
|
|
119
|
+
}
|
|
120
|
+
var DEFAULT_QUERY_TIMEOUT_MS = 3e4;
|
|
121
|
+
var COMPLEX_QUERY_TIMEOUT_MS = 6e4;
|
|
122
|
+
function toNeo4jParams(params) {
|
|
123
|
+
const result = {};
|
|
124
|
+
for (const [key, value] of Object.entries(params)) {
|
|
125
|
+
if (typeof value === "number" && Number.isInteger(value)) {
|
|
126
|
+
result[key] = neo4j.int(value);
|
|
127
|
+
} else if (Array.isArray(value)) {
|
|
128
|
+
result[key] = value.map(
|
|
129
|
+
(v2) => typeof v2 === "number" && Number.isInteger(v2) ? neo4j.int(v2) : v2
|
|
130
|
+
);
|
|
131
|
+
} else {
|
|
132
|
+
result[key] = value;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return result;
|
|
136
|
+
}
|
|
137
|
+
async function runCypher(query, params = {}, timeoutMs = DEFAULT_QUERY_TIMEOUT_MS) {
|
|
138
|
+
const neo4jDriver = getDriver();
|
|
139
|
+
const session = neo4jDriver.session();
|
|
140
|
+
try {
|
|
141
|
+
const neo4jParams = toNeo4jParams(params);
|
|
142
|
+
const result = await session.run(query, neo4jParams, {
|
|
143
|
+
timeout: neo4j.int(timeoutMs)
|
|
144
|
+
});
|
|
145
|
+
return result.records.map((record) => {
|
|
146
|
+
const obj = {};
|
|
147
|
+
for (const key of record.keys) {
|
|
148
|
+
const field = String(key);
|
|
149
|
+
obj[field] = convertNeo4jValue(record.get(field));
|
|
150
|
+
}
|
|
151
|
+
return obj;
|
|
152
|
+
});
|
|
153
|
+
} finally {
|
|
154
|
+
await session.close();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
async function runWriteTransaction(query, params = {}, timeoutMs = DEFAULT_QUERY_TIMEOUT_MS) {
|
|
158
|
+
const neo4jDriver = getDriver();
|
|
159
|
+
const session = neo4jDriver.session();
|
|
160
|
+
try {
|
|
161
|
+
const neo4jParams = toNeo4jParams(params);
|
|
162
|
+
const result = await session.executeWrite(
|
|
163
|
+
async (tx) => {
|
|
164
|
+
return await tx.run(query, neo4jParams);
|
|
165
|
+
},
|
|
166
|
+
{ timeout: timeoutMs }
|
|
167
|
+
);
|
|
168
|
+
return result.records.map((record) => {
|
|
169
|
+
const obj = {};
|
|
170
|
+
for (const key of record.keys) {
|
|
171
|
+
const field = String(key);
|
|
172
|
+
obj[field] = convertNeo4jValue(record.get(field));
|
|
173
|
+
}
|
|
174
|
+
return obj;
|
|
175
|
+
});
|
|
176
|
+
} finally {
|
|
177
|
+
await session.close();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
async function runBatchTransaction(queries, timeoutMs = COMPLEX_QUERY_TIMEOUT_MS) {
|
|
181
|
+
const neo4jDriver = getDriver();
|
|
182
|
+
const session = neo4jDriver.session();
|
|
183
|
+
try {
|
|
184
|
+
await session.executeWrite(
|
|
185
|
+
async (tx) => {
|
|
186
|
+
for (const { query, params } of queries) {
|
|
187
|
+
await tx.run(query, params);
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
{ timeout: timeoutMs }
|
|
191
|
+
);
|
|
192
|
+
} finally {
|
|
193
|
+
await session.close();
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
async function upsertNode(label, globalId, properties) {
|
|
197
|
+
validateLabel(label);
|
|
198
|
+
await runWriteTransaction(
|
|
199
|
+
`
|
|
200
|
+
MERGE (n:${label} {globalId: $globalId})
|
|
201
|
+
SET n += $properties
|
|
202
|
+
SET n.updatedAt = timestamp()
|
|
203
|
+
`,
|
|
204
|
+
{ globalId, properties }
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
async function deleteNode(globalId) {
|
|
208
|
+
await runWriteTransaction(
|
|
209
|
+
`
|
|
210
|
+
MATCH (n {globalId: $globalId})
|
|
211
|
+
DETACH DELETE n
|
|
212
|
+
`,
|
|
213
|
+
{ globalId }
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
async function batchUpsertNodes(label, nodes) {
|
|
217
|
+
if (nodes.length === 0) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
validateLabel(label);
|
|
221
|
+
await runWriteTransaction(
|
|
222
|
+
`
|
|
223
|
+
UNWIND $nodes as node
|
|
224
|
+
MERGE (n:${label} {globalId: node.globalId})
|
|
225
|
+
SET n += node.properties
|
|
226
|
+
SET n.updatedAt = timestamp()
|
|
227
|
+
`,
|
|
228
|
+
{ nodes }
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
async function upsertEdge(relType, globalId, fromGlobalId, toGlobalId, properties = {}) {
|
|
232
|
+
validateRelType(relType);
|
|
233
|
+
await runWriteTransaction(
|
|
234
|
+
`
|
|
235
|
+
MATCH (from {globalId: $fromGlobalId})
|
|
236
|
+
MATCH (to {globalId: $toGlobalId})
|
|
237
|
+
MERGE (from)-[r:${relType} {globalId: $globalId}]->(to)
|
|
238
|
+
SET r += $properties
|
|
239
|
+
SET r.updatedAt = timestamp()
|
|
240
|
+
`,
|
|
241
|
+
{ globalId, fromGlobalId, toGlobalId, properties }
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
async function deleteEdge(globalId) {
|
|
245
|
+
await runWriteTransaction(
|
|
246
|
+
`
|
|
247
|
+
MATCH ()-[r {globalId: $globalId}]->()
|
|
248
|
+
DELETE r
|
|
249
|
+
`,
|
|
250
|
+
{ globalId }
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
async function batchUpsertEdges(edges) {
|
|
254
|
+
if (edges.length === 0) {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const byType = /* @__PURE__ */ new Map();
|
|
258
|
+
for (const edge of edges) {
|
|
259
|
+
const existing = byType.get(edge.relType) || [];
|
|
260
|
+
existing.push(edge);
|
|
261
|
+
byType.set(edge.relType, existing);
|
|
262
|
+
}
|
|
263
|
+
const queries = [];
|
|
264
|
+
for (const [relType, typeEdges] of byType) {
|
|
265
|
+
queries.push({
|
|
266
|
+
query: `
|
|
267
|
+
UNWIND $edges as edge
|
|
268
|
+
MATCH (from {globalId: edge.fromGlobalId})
|
|
269
|
+
MATCH (to {globalId: edge.toGlobalId})
|
|
270
|
+
MERGE (from)-[r:${relType} {globalId: edge.globalId}]->(to)
|
|
271
|
+
SET r += edge.properties
|
|
272
|
+
SET r.updatedAt = timestamp()
|
|
273
|
+
`,
|
|
274
|
+
params: {
|
|
275
|
+
edges: typeEdges.map((e) => ({
|
|
276
|
+
globalId: e.globalId,
|
|
277
|
+
fromGlobalId: e.fromGlobalId,
|
|
278
|
+
toGlobalId: e.toGlobalId,
|
|
279
|
+
properties: e.properties || {}
|
|
280
|
+
}))
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
await runBatchTransaction(queries);
|
|
285
|
+
}
|
|
286
|
+
async function healthCheck() {
|
|
287
|
+
try {
|
|
288
|
+
const result = await runCypher(
|
|
289
|
+
"MATCH (n) RETURN count(n) as count LIMIT 1"
|
|
290
|
+
);
|
|
291
|
+
return {
|
|
292
|
+
healthy: true,
|
|
293
|
+
nodeCount: result[0]?.count || 0
|
|
294
|
+
};
|
|
295
|
+
} catch (error) {
|
|
296
|
+
return {
|
|
297
|
+
healthy: false,
|
|
298
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
function getConnectionInfo() {
|
|
303
|
+
return {
|
|
304
|
+
uri: process.env.NEO4J_URI,
|
|
305
|
+
user: process.env.NEO4J_USER,
|
|
306
|
+
configured: Boolean(
|
|
307
|
+
process.env.NEO4J_URI && process.env.NEO4J_USER && process.env.NEO4J_PASSWORD
|
|
308
|
+
)
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
function convertNeo4jValue(value) {
|
|
312
|
+
if (value === null || value === void 0) {
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
if (neo4j.isInt(value)) {
|
|
316
|
+
return neo4j.integer.toNumber(value);
|
|
317
|
+
}
|
|
318
|
+
if (neo4j.isDate(value) || neo4j.isDateTime(value) || neo4j.isTime(value)) {
|
|
319
|
+
return value.toString();
|
|
320
|
+
}
|
|
321
|
+
if (Array.isArray(value)) {
|
|
322
|
+
return value.map(convertNeo4jValue);
|
|
323
|
+
}
|
|
324
|
+
if (value && typeof value === "object" && "properties" in value) {
|
|
325
|
+
const nodeObj = value;
|
|
326
|
+
const result = {};
|
|
327
|
+
for (const [k, v2] of Object.entries(nodeObj.properties)) {
|
|
328
|
+
result[k] = convertNeo4jValue(v2);
|
|
329
|
+
}
|
|
330
|
+
return result;
|
|
331
|
+
}
|
|
332
|
+
if (typeof value === "object") {
|
|
333
|
+
const result = {};
|
|
334
|
+
for (const [k, v2] of Object.entries(value)) {
|
|
335
|
+
result[k] = convertNeo4jValue(v2);
|
|
336
|
+
}
|
|
337
|
+
return result;
|
|
338
|
+
}
|
|
339
|
+
return value;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// src/neo4jSync.ts
|
|
343
|
+
function buildSyncResponse(entityType, operation, fields) {
|
|
344
|
+
return {
|
|
345
|
+
entityType,
|
|
346
|
+
operation,
|
|
347
|
+
...fields
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
function buildSyncSuccess(entityType, operation, fields) {
|
|
351
|
+
return buildSyncResponse(entityType, operation, {
|
|
352
|
+
success: true,
|
|
353
|
+
...fields
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
function buildSyncFailure(entityType, operation, error, fields) {
|
|
357
|
+
return buildSyncResponse(entityType, operation, {
|
|
358
|
+
success: false,
|
|
359
|
+
error,
|
|
360
|
+
...fields
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
function buildNodeProperties(node) {
|
|
364
|
+
const metadata = node.metadata || {};
|
|
365
|
+
const pillar = metadata.pillar || metadata.topic || "";
|
|
366
|
+
const stage = metadata.stage || metadata.beliefStage || "";
|
|
367
|
+
const criticality = metadata.criticality || "";
|
|
368
|
+
const synthesizedFrom = metadata.synthesizedFrom || [];
|
|
369
|
+
const epistemicStatus = node.epistemicStatus || "";
|
|
370
|
+
const methodology = node.methodology || "";
|
|
371
|
+
const informationAsymmetry = node.informationAsymmetry || "";
|
|
372
|
+
const questionType = node.questionType || "";
|
|
373
|
+
const questionPriority = node.questionPriority || "";
|
|
374
|
+
const answerQuality = node.answerQuality || "";
|
|
375
|
+
const reversibility = node.reversibility || "";
|
|
376
|
+
const predictionMeta = node.predictionMeta;
|
|
377
|
+
return {
|
|
378
|
+
convexId: node._id,
|
|
379
|
+
canonicalText: node.canonicalText || "",
|
|
380
|
+
title: node.title || "",
|
|
381
|
+
status: node.status || "active",
|
|
382
|
+
subtype: node.subtype || "",
|
|
383
|
+
domain: node.domain || "",
|
|
384
|
+
confidence: node.confidence || 0,
|
|
385
|
+
verificationStatus: node.verificationStatus || "unverified",
|
|
386
|
+
sourceType: node.sourceType || "unknown",
|
|
387
|
+
createdAt: node.createdAt,
|
|
388
|
+
updatedAt: node.updatedAt || Date.now(),
|
|
389
|
+
createdBy: node.createdBy || "",
|
|
390
|
+
nodeType: node.nodeType,
|
|
391
|
+
epistemicLayer: node.epistemicLayer || "",
|
|
392
|
+
// Project and metadata fields
|
|
393
|
+
projectId: node.projectId || "",
|
|
394
|
+
tenantId: node.tenantId || "",
|
|
395
|
+
workspaceId: node.workspaceId || "",
|
|
396
|
+
pillar,
|
|
397
|
+
stage,
|
|
398
|
+
criticality,
|
|
399
|
+
synthesizedFromCount: synthesizedFrom.length,
|
|
400
|
+
// Classification fields (Logic Machine)
|
|
401
|
+
epistemicStatus,
|
|
402
|
+
methodology,
|
|
403
|
+
informationAsymmetry,
|
|
404
|
+
questionType,
|
|
405
|
+
questionPriority,
|
|
406
|
+
answerQuality,
|
|
407
|
+
reversibility,
|
|
408
|
+
isPrediction: predictionMeta?.isPrediction ? "true" : "false",
|
|
409
|
+
expectedBy: predictionMeta?.expectedBy ? String(predictionMeta.expectedBy) : ""
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
function buildEdgeProperties(edge) {
|
|
413
|
+
return {
|
|
414
|
+
convexId: edge._id,
|
|
415
|
+
weight: edge.weight || 1,
|
|
416
|
+
confidence: edge.confidence || 0,
|
|
417
|
+
context: edge.context || "",
|
|
418
|
+
derivationType: edge.derivationType || "",
|
|
419
|
+
createdAt: edge.createdAt,
|
|
420
|
+
createdBy: edge.createdBy || "",
|
|
421
|
+
edgeType: edge.edgeType,
|
|
422
|
+
fromLayer: edge.fromLayer || "",
|
|
423
|
+
toLayer: edge.toLayer || "",
|
|
424
|
+
projectId: edge.projectId || "",
|
|
425
|
+
tenantId: edge.tenantId || "",
|
|
426
|
+
workspaceId: edge.workspaceId || "",
|
|
427
|
+
// Classification fields (Logic Machine)
|
|
428
|
+
reasoningMethod: edge.reasoningMethod || "",
|
|
429
|
+
logicalRole: edge.logicalRole || "",
|
|
430
|
+
temporalClass: edge.temporalClass || ""
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
var syncNodeToNeo4j = internalAction({
|
|
434
|
+
args: {
|
|
435
|
+
nodeId: v.id("epistemicNodes"),
|
|
436
|
+
operation: v.union(v.literal("upsert"), v.literal("delete"))
|
|
437
|
+
},
|
|
438
|
+
returns: permissiveReturn,
|
|
439
|
+
handler: async (ctx, args) => {
|
|
440
|
+
const connInfo = getConnectionInfo();
|
|
441
|
+
if (!connInfo.configured) {
|
|
442
|
+
console.warn(
|
|
443
|
+
"[Neo4j Sync] Missing Neo4j credentials, skipping sync. Set via `npx convex env set`"
|
|
444
|
+
);
|
|
445
|
+
return buildSyncFailure("node", args.operation, "Missing credentials", {
|
|
446
|
+
skippedReason: "credentials_missing"
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
const node = await ctx.runQuery(internal.neo4jSyncHelpers.getNodeForSync, {
|
|
450
|
+
nodeId: args.nodeId
|
|
451
|
+
});
|
|
452
|
+
if (!node) {
|
|
453
|
+
if (args.operation === "upsert") {
|
|
454
|
+
return buildSyncFailure("node", args.operation, "Node not found", {
|
|
455
|
+
skippedReason: "source_node_missing"
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
console.log("[Neo4j Sync] Node not found for delete, skipping");
|
|
459
|
+
return buildSyncSuccess("node", args.operation, {
|
|
460
|
+
skipped: true,
|
|
461
|
+
skippedReason: "source_node_missing"
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
const label = NODE_TYPE_TO_LABEL[node.nodeType] || "Node";
|
|
465
|
+
try {
|
|
466
|
+
if (args.operation === "delete") {
|
|
467
|
+
await deleteNode(node.globalId);
|
|
468
|
+
console.log(`[Neo4j Sync] Deleted node ${node.globalId}`);
|
|
469
|
+
} else {
|
|
470
|
+
const props = buildNodeProperties(node);
|
|
471
|
+
const embedding = await ctx.runQuery(
|
|
472
|
+
internal.neo4jSyncHelpers.getEmbeddingForSync,
|
|
473
|
+
{ nodeId: args.nodeId }
|
|
474
|
+
);
|
|
475
|
+
if (embedding) {
|
|
476
|
+
props.embedding = embedding;
|
|
477
|
+
}
|
|
478
|
+
await upsertNode(label, node.globalId, props);
|
|
479
|
+
console.log(
|
|
480
|
+
`[Neo4j Sync] Upserted node ${node.globalId} as ${label} with projectId=${node.projectId}` + (embedding ? ` (with ${embedding.length}-dim embedding)` : "")
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
await ctx.runMutation(internal.neo4jSyncHelpers.logSyncEvent, {
|
|
484
|
+
eventType: args.operation === "delete" ? "node_deleted" : node ? "node_updated" : "node_created",
|
|
485
|
+
entityId: args.nodeId,
|
|
486
|
+
entityType: node.nodeType,
|
|
487
|
+
status: "success"
|
|
488
|
+
});
|
|
489
|
+
return buildSyncSuccess("node", args.operation);
|
|
490
|
+
} catch (error) {
|
|
491
|
+
const errorMsg = error instanceof Error ? error.message : "Unknown error";
|
|
492
|
+
console.error("[Neo4j Sync] Node sync error:", errorMsg);
|
|
493
|
+
await ctx.runMutation(internal.neo4jSyncHelpers.logSyncEvent, {
|
|
494
|
+
eventType: args.operation === "delete" ? "node_deleted" : "node_updated",
|
|
495
|
+
entityId: args.nodeId,
|
|
496
|
+
entityType: node.nodeType,
|
|
497
|
+
status: "failed",
|
|
498
|
+
error: errorMsg
|
|
499
|
+
});
|
|
500
|
+
await ctx.runMutation(internal.neo4jSyncHelpers.queueForRetry, {
|
|
501
|
+
entityType: "node",
|
|
502
|
+
entityId: args.nodeId,
|
|
503
|
+
operation: args.operation,
|
|
504
|
+
error: errorMsg
|
|
505
|
+
});
|
|
506
|
+
return buildSyncFailure("node", args.operation, errorMsg);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
});
|
|
510
|
+
var syncEdgeToNeo4j = internalAction({
|
|
511
|
+
args: {
|
|
512
|
+
edgeId: v.id("epistemicEdges"),
|
|
513
|
+
operation: v.union(v.literal("upsert"), v.literal("delete"))
|
|
514
|
+
},
|
|
515
|
+
returns: permissiveReturn,
|
|
516
|
+
handler: async (ctx, args) => {
|
|
517
|
+
const connInfo = getConnectionInfo();
|
|
518
|
+
if (!connInfo.configured) {
|
|
519
|
+
console.warn(
|
|
520
|
+
"[Neo4j Sync] Missing Neo4j credentials, skipping sync. Set via `npx convex env set`"
|
|
521
|
+
);
|
|
522
|
+
return buildSyncFailure("edge", args.operation, "Missing credentials", {
|
|
523
|
+
skippedReason: "credentials_missing"
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
const edge = await ctx.runQuery(internal.neo4jSyncHelpers.getEdgeForSync, {
|
|
527
|
+
edgeId: args.edgeId
|
|
528
|
+
});
|
|
529
|
+
if (!edge) {
|
|
530
|
+
if (args.operation === "upsert") {
|
|
531
|
+
return buildSyncFailure("edge", args.operation, "Edge not found", {
|
|
532
|
+
skippedReason: "source_edge_missing"
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
console.log("[Neo4j Sync] Edge not found for delete, skipping");
|
|
536
|
+
return buildSyncSuccess("edge", args.operation, {
|
|
537
|
+
skipped: true,
|
|
538
|
+
skippedReason: "source_edge_missing"
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
if (!edge.fromGlobalId || !edge.toGlobalId) {
|
|
542
|
+
console.warn(
|
|
543
|
+
"[Neo4j Sync] Edge missing fromGlobalId or toGlobalId, skipping"
|
|
544
|
+
);
|
|
545
|
+
return buildSyncFailure(
|
|
546
|
+
"edge",
|
|
547
|
+
args.operation,
|
|
548
|
+
"Edge missing connected node globalIds",
|
|
549
|
+
{
|
|
550
|
+
skippedReason: "edge_endpoint_missing"
|
|
551
|
+
}
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
const relType = EDGE_TYPE_TO_REL[edge.edgeType] || edge.edgeType.toUpperCase();
|
|
555
|
+
try {
|
|
556
|
+
if (args.operation === "delete") {
|
|
557
|
+
await deleteEdge(edge.globalId);
|
|
558
|
+
console.log(`[Neo4j Sync] Deleted edge ${edge.globalId}`);
|
|
559
|
+
} else {
|
|
560
|
+
await upsertEdge(
|
|
561
|
+
relType,
|
|
562
|
+
edge.globalId,
|
|
563
|
+
edge.fromGlobalId,
|
|
564
|
+
edge.toGlobalId,
|
|
565
|
+
{
|
|
566
|
+
convexId: args.edgeId,
|
|
567
|
+
weight: edge.weight || 1,
|
|
568
|
+
confidence: edge.confidence || 0,
|
|
569
|
+
context: edge.context || "",
|
|
570
|
+
derivationType: edge.derivationType || "",
|
|
571
|
+
createdAt: edge.createdAt,
|
|
572
|
+
createdBy: edge.createdBy || "",
|
|
573
|
+
edgeType: edge.edgeType,
|
|
574
|
+
fromLayer: edge.fromLayer || "",
|
|
575
|
+
toLayer: edge.toLayer || "",
|
|
576
|
+
// Classification fields (Logic Machine)
|
|
577
|
+
reasoningMethod: edge.reasoningMethod || "",
|
|
578
|
+
logicalRole: edge.logicalRole || "",
|
|
579
|
+
temporalClass: edge.temporalClass || ""
|
|
580
|
+
}
|
|
581
|
+
);
|
|
582
|
+
console.log(
|
|
583
|
+
`[Neo4j Sync] Upserted edge ${edge.globalId} as ${relType}`
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
await ctx.runMutation(internal.neo4jSyncHelpers.logSyncEvent, {
|
|
587
|
+
eventType: args.operation === "delete" ? "edge_deleted" : "edge_created",
|
|
588
|
+
entityId: args.edgeId,
|
|
589
|
+
entityType: edge.edgeType,
|
|
590
|
+
status: "success"
|
|
591
|
+
});
|
|
592
|
+
return buildSyncSuccess("edge", args.operation);
|
|
593
|
+
} catch (error) {
|
|
594
|
+
const errorMsg = error instanceof Error ? error.message : "Unknown error";
|
|
595
|
+
console.error("[Neo4j Sync] Edge sync error:", errorMsg);
|
|
596
|
+
await ctx.runMutation(internal.neo4jSyncHelpers.logSyncEvent, {
|
|
597
|
+
eventType: args.operation === "delete" ? "edge_deleted" : "edge_created",
|
|
598
|
+
entityId: args.edgeId,
|
|
599
|
+
entityType: edge.edgeType,
|
|
600
|
+
status: "failed",
|
|
601
|
+
error: errorMsg
|
|
602
|
+
});
|
|
603
|
+
await ctx.runMutation(internal.neo4jSyncHelpers.queueForRetry, {
|
|
604
|
+
entityType: "edge",
|
|
605
|
+
entityId: args.edgeId,
|
|
606
|
+
operation: args.operation,
|
|
607
|
+
error: errorMsg
|
|
608
|
+
});
|
|
609
|
+
return buildSyncFailure("edge", args.operation, errorMsg);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
});
|
|
613
|
+
var syncAllNodesToNeo4j = internalAction({
|
|
614
|
+
args: {
|
|
615
|
+
batchSize: v.optional(v.number()),
|
|
616
|
+
cursor: v.optional(v.string())
|
|
617
|
+
},
|
|
618
|
+
returns: permissiveReturn,
|
|
619
|
+
handler: async (ctx, args) => {
|
|
620
|
+
const batchSize = args.batchSize ?? 100;
|
|
621
|
+
const result = await ctx.runQuery(
|
|
622
|
+
internal.neo4jSyncHelpers.getNodeBatchForSync,
|
|
623
|
+
{
|
|
624
|
+
limit: batchSize,
|
|
625
|
+
cursor: args.cursor
|
|
626
|
+
}
|
|
627
|
+
);
|
|
628
|
+
if (result.nodes.length === 0) {
|
|
629
|
+
return { synced: 0, failed: 0, hasMore: false };
|
|
630
|
+
}
|
|
631
|
+
const nodesByLabel = /* @__PURE__ */ new Map();
|
|
632
|
+
for (const node of result.nodes) {
|
|
633
|
+
const label = NODE_TYPE_TO_LABEL[node.nodeType] || "Node";
|
|
634
|
+
if (!nodesByLabel.has(label)) {
|
|
635
|
+
nodesByLabel.set(label, []);
|
|
636
|
+
}
|
|
637
|
+
nodesByLabel.get(label)?.push({
|
|
638
|
+
globalId: node.globalId,
|
|
639
|
+
properties: buildNodeProperties(node)
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
let synced = 0;
|
|
643
|
+
let failed = 0;
|
|
644
|
+
for (const [label, nodes] of nodesByLabel) {
|
|
645
|
+
try {
|
|
646
|
+
await batchUpsertNodes(label, nodes);
|
|
647
|
+
synced += nodes.length;
|
|
648
|
+
console.log(
|
|
649
|
+
`[Neo4j Sync] Batch upserted ${nodes.length} ${label} nodes`
|
|
650
|
+
);
|
|
651
|
+
} catch (error) {
|
|
652
|
+
console.error(`[Neo4j Sync] Batch upsert failed for ${label}:`, error);
|
|
653
|
+
failed += nodes.length;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
return {
|
|
657
|
+
synced,
|
|
658
|
+
failed,
|
|
659
|
+
hasMore: result.hasMore,
|
|
660
|
+
nextCursor: result.nextCursor
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
});
|
|
664
|
+
var syncAllEdgesToNeo4j = internalAction({
|
|
665
|
+
args: {
|
|
666
|
+
batchSize: v.optional(v.number()),
|
|
667
|
+
cursor: v.optional(v.string())
|
|
668
|
+
},
|
|
669
|
+
returns: permissiveReturn,
|
|
670
|
+
handler: async (ctx, args) => {
|
|
671
|
+
const batchSize = args.batchSize ?? 100;
|
|
672
|
+
const result = await ctx.runQuery(
|
|
673
|
+
internal.neo4jSyncHelpers.getEdgeBatchForSync,
|
|
674
|
+
{
|
|
675
|
+
limit: batchSize,
|
|
676
|
+
cursor: args.cursor
|
|
677
|
+
}
|
|
678
|
+
);
|
|
679
|
+
if (result.edges.length === 0) {
|
|
680
|
+
return { synced: 0, failed: 0, hasMore: false };
|
|
681
|
+
}
|
|
682
|
+
const edgesToSync = [];
|
|
683
|
+
for (const edge of result.edges) {
|
|
684
|
+
if (!edge.fromGlobalId || !edge.toGlobalId) {
|
|
685
|
+
console.warn(
|
|
686
|
+
`[Neo4j Sync] Skipping edge ${edge.globalId} - missing globalIds`
|
|
687
|
+
);
|
|
688
|
+
continue;
|
|
689
|
+
}
|
|
690
|
+
const relType = EDGE_TYPE_TO_REL[edge.edgeType] || edge.edgeType.toUpperCase();
|
|
691
|
+
edgesToSync.push({
|
|
692
|
+
relType,
|
|
693
|
+
globalId: edge.globalId,
|
|
694
|
+
fromGlobalId: edge.fromGlobalId,
|
|
695
|
+
toGlobalId: edge.toGlobalId,
|
|
696
|
+
properties: buildEdgeProperties(edge)
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
let synced = 0;
|
|
700
|
+
let failed = 0;
|
|
701
|
+
try {
|
|
702
|
+
await batchUpsertEdges(edgesToSync);
|
|
703
|
+
synced = edgesToSync.length;
|
|
704
|
+
console.log(`[Neo4j Sync] Batch upserted ${synced} edges`);
|
|
705
|
+
} catch (error) {
|
|
706
|
+
console.error("[Neo4j Sync] Batch edge upsert failed:", error);
|
|
707
|
+
failed = edgesToSync.length;
|
|
708
|
+
}
|
|
709
|
+
return {
|
|
710
|
+
synced,
|
|
711
|
+
failed,
|
|
712
|
+
hasMore: result.hasMore,
|
|
713
|
+
nextCursor: result.nextCursor
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
});
|
|
717
|
+
var backfillAllToNeo4j = internalAction({
|
|
718
|
+
args: {
|
|
719
|
+
batchSize: v.optional(v.number())
|
|
720
|
+
},
|
|
721
|
+
returns: permissiveReturn,
|
|
722
|
+
handler: async (ctx, args) => {
|
|
723
|
+
const batchSize = args.batchSize ?? 100;
|
|
724
|
+
let totalNodes = 0;
|
|
725
|
+
let totalEdges = 0;
|
|
726
|
+
let totalFailed = 0;
|
|
727
|
+
console.log("[Neo4j Sync] Starting full backfill...");
|
|
728
|
+
let nodeCursor;
|
|
729
|
+
do {
|
|
730
|
+
const result = await ctx.runAction(
|
|
731
|
+
internal.neo4jSync.syncAllNodesToNeo4j,
|
|
732
|
+
{
|
|
733
|
+
batchSize,
|
|
734
|
+
cursor: nodeCursor
|
|
735
|
+
}
|
|
736
|
+
);
|
|
737
|
+
totalNodes += result.synced;
|
|
738
|
+
totalFailed += result.failed;
|
|
739
|
+
nodeCursor = result.hasMore ? result.nextCursor : void 0;
|
|
740
|
+
console.log(
|
|
741
|
+
`[Neo4j Sync] Nodes progress: ${totalNodes} synced, ${totalFailed} failed`
|
|
742
|
+
);
|
|
743
|
+
} while (nodeCursor);
|
|
744
|
+
console.log(`[Neo4j Sync] Finished nodes: ${totalNodes} synced`);
|
|
745
|
+
let edgeCursor;
|
|
746
|
+
do {
|
|
747
|
+
const result = await ctx.runAction(
|
|
748
|
+
internal.neo4jSync.syncAllEdgesToNeo4j,
|
|
749
|
+
{
|
|
750
|
+
batchSize,
|
|
751
|
+
cursor: edgeCursor
|
|
752
|
+
}
|
|
753
|
+
);
|
|
754
|
+
totalEdges += result.synced;
|
|
755
|
+
totalFailed += result.failed;
|
|
756
|
+
edgeCursor = result.hasMore ? result.nextCursor : void 0;
|
|
757
|
+
console.log(
|
|
758
|
+
`[Neo4j Sync] Edges progress: ${totalEdges} synced, ${totalFailed} failed`
|
|
759
|
+
);
|
|
760
|
+
} while (edgeCursor);
|
|
761
|
+
console.log(
|
|
762
|
+
`[Neo4j Sync] Backfill complete: ${totalNodes} nodes, ${totalEdges} edges, ${totalFailed} failed`
|
|
763
|
+
);
|
|
764
|
+
return {
|
|
765
|
+
totalNodes,
|
|
766
|
+
totalEdges,
|
|
767
|
+
totalFailed
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
});
|
|
771
|
+
var processRetryQueue = internalAction({
|
|
772
|
+
args: {
|
|
773
|
+
limit: v.optional(v.number())
|
|
774
|
+
},
|
|
775
|
+
returns: permissiveReturn,
|
|
776
|
+
handler: async (ctx, args) => {
|
|
777
|
+
const limit = args.limit ?? 10;
|
|
778
|
+
const pendingItems = await ctx.runQuery(
|
|
779
|
+
internal.neo4jSyncHelpers.getPendingRetries,
|
|
780
|
+
{ limit }
|
|
781
|
+
);
|
|
782
|
+
if (pendingItems.length === 0) {
|
|
783
|
+
return { processed: 0, succeeded: 0, failed: 0 };
|
|
784
|
+
}
|
|
785
|
+
let succeeded = 0;
|
|
786
|
+
let failed = 0;
|
|
787
|
+
for (const item of pendingItems) {
|
|
788
|
+
await ctx.runMutation(internal.neo4jSyncHelpers.updateQueueStatus, {
|
|
789
|
+
queueId: item._id,
|
|
790
|
+
status: "in_progress"
|
|
791
|
+
});
|
|
792
|
+
let result;
|
|
793
|
+
if (item.entityType === "node") {
|
|
794
|
+
result = await ctx.runAction(internal.neo4jSync.syncNodeToNeo4j, {
|
|
795
|
+
nodeId: item.entityId,
|
|
796
|
+
operation: item.operation
|
|
797
|
+
});
|
|
798
|
+
} else {
|
|
799
|
+
const resolved = await ctx.runQuery(
|
|
800
|
+
internal.neo4jSyncHelpers.resolveEdgeRetryTarget,
|
|
801
|
+
{
|
|
802
|
+
entityId: item.entityId
|
|
803
|
+
}
|
|
804
|
+
);
|
|
805
|
+
if (resolved.mode === "convex_id" || resolved.mode === "global_id_in_convex") {
|
|
806
|
+
result = await ctx.runAction(internal.neo4jSync.syncEdgeToNeo4j, {
|
|
807
|
+
edgeId: resolved.edgeId,
|
|
808
|
+
operation: item.operation
|
|
809
|
+
});
|
|
810
|
+
} else {
|
|
811
|
+
result = await ctx.runAction(
|
|
812
|
+
internal.neo4jEdgeAPI.retryProjectionByGlobalId,
|
|
813
|
+
{
|
|
814
|
+
globalId: resolved.edgeGlobalId
|
|
815
|
+
}
|
|
816
|
+
);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
if (result.success) {
|
|
820
|
+
await ctx.runMutation(internal.neo4jSyncHelpers.updateQueueStatus, {
|
|
821
|
+
queueId: item._id,
|
|
822
|
+
status: "succeeded"
|
|
823
|
+
});
|
|
824
|
+
succeeded++;
|
|
825
|
+
} else {
|
|
826
|
+
const updated = await ctx.runMutation(
|
|
827
|
+
internal.neo4jSyncHelpers.incrementAttempts,
|
|
828
|
+
{
|
|
829
|
+
queueId: item._id,
|
|
830
|
+
error: result.error || "Unknown error"
|
|
831
|
+
}
|
|
832
|
+
);
|
|
833
|
+
if (updated.failed) {
|
|
834
|
+
failed++;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
return { processed: pendingItems.length, succeeded, failed };
|
|
839
|
+
}
|
|
840
|
+
});
|
|
841
|
+
var syncEmbeddingToNeo4j = internalAction({
|
|
842
|
+
args: {
|
|
843
|
+
nodeId: v.id("epistemicNodes")
|
|
844
|
+
},
|
|
845
|
+
returns: permissiveReturn,
|
|
846
|
+
handler: async (ctx, args) => {
|
|
847
|
+
const connInfo = getConnectionInfo();
|
|
848
|
+
if (!connInfo.configured) {
|
|
849
|
+
return buildSyncFailure("embedding", "sync", "Missing credentials", {
|
|
850
|
+
skippedReason: "credentials_missing"
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
const node = await ctx.runQuery(internal.neo4jSyncHelpers.getNodeForSync, {
|
|
854
|
+
nodeId: args.nodeId
|
|
855
|
+
});
|
|
856
|
+
if (!node?.globalId) {
|
|
857
|
+
return buildSyncFailure("embedding", "sync", "Node not found", {
|
|
858
|
+
skippedReason: "source_node_missing"
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
const embedding = await ctx.runQuery(
|
|
862
|
+
internal.neo4jSyncHelpers.getEmbeddingForSync,
|
|
863
|
+
{ nodeId: args.nodeId }
|
|
864
|
+
);
|
|
865
|
+
if (!embedding) {
|
|
866
|
+
return buildSyncFailure("embedding", "sync", "Embedding not found", {
|
|
867
|
+
skippedReason: "embedding_missing"
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
try {
|
|
871
|
+
await runWriteTransaction(
|
|
872
|
+
"MATCH (n {globalId: $globalId}) SET n.embedding = $embedding",
|
|
873
|
+
{ globalId: node.globalId, embedding }
|
|
874
|
+
);
|
|
875
|
+
console.log(
|
|
876
|
+
`[Neo4j Sync] Synced ${embedding.length}-dim embedding for node ${node.globalId}`
|
|
877
|
+
);
|
|
878
|
+
return buildSyncSuccess("embedding", "sync");
|
|
879
|
+
} catch (error) {
|
|
880
|
+
const errorMsg = error instanceof Error ? error.message : "Unknown error";
|
|
881
|
+
console.error("[Neo4j Sync] Embedding sync error:", errorMsg);
|
|
882
|
+
return buildSyncFailure("embedding", "sync", errorMsg);
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
});
|
|
886
|
+
var checkNeo4jHealth = internalAction({
|
|
887
|
+
args: {},
|
|
888
|
+
returns: permissiveReturn,
|
|
889
|
+
handler: async () => {
|
|
890
|
+
const connInfo = getConnectionInfo();
|
|
891
|
+
if (!connInfo.configured) {
|
|
892
|
+
return {
|
|
893
|
+
healthy: false,
|
|
894
|
+
error: "Neo4j not configured",
|
|
895
|
+
uri: connInfo.uri
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
const health = await healthCheck();
|
|
899
|
+
return {
|
|
900
|
+
...health,
|
|
901
|
+
uri: connInfo.uri
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
});
|
|
905
|
+
var resyncAllNodes = internalAction({
|
|
906
|
+
args: {
|
|
907
|
+
batchSize: v.optional(v.number()),
|
|
908
|
+
nodeType: v.optional(v.string()),
|
|
909
|
+
cursor: v.optional(v.string())
|
|
910
|
+
},
|
|
911
|
+
returns: permissiveReturn,
|
|
912
|
+
handler: async (ctx, args) => {
|
|
913
|
+
const batchSize = args.batchSize ?? 50;
|
|
914
|
+
const result = await ctx.runQuery(
|
|
915
|
+
internal.neo4jSyncHelpers.getAllNodesForResync,
|
|
916
|
+
{
|
|
917
|
+
nodeType: args.nodeType,
|
|
918
|
+
limit: batchSize,
|
|
919
|
+
cursor: args.cursor
|
|
920
|
+
}
|
|
921
|
+
);
|
|
922
|
+
let synced = 0;
|
|
923
|
+
let failed = 0;
|
|
924
|
+
for (const node of result.nodes) {
|
|
925
|
+
try {
|
|
926
|
+
const syncResult = await ctx.runAction(
|
|
927
|
+
internal.neo4jSync.syncNodeToNeo4j,
|
|
928
|
+
{
|
|
929
|
+
nodeId: node._id,
|
|
930
|
+
operation: "upsert"
|
|
931
|
+
}
|
|
932
|
+
);
|
|
933
|
+
if (syncResult.success) {
|
|
934
|
+
synced++;
|
|
935
|
+
} else {
|
|
936
|
+
failed++;
|
|
937
|
+
}
|
|
938
|
+
} catch (error) {
|
|
939
|
+
console.error(`[Neo4j Resync] Failed to sync node ${node._id}:`, error);
|
|
940
|
+
failed++;
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
return {
|
|
944
|
+
synced,
|
|
945
|
+
failed,
|
|
946
|
+
total: result.nodes.length,
|
|
947
|
+
hasMore: result.hasMore,
|
|
948
|
+
nextCursor: result.nextCursor
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
});
|
|
952
|
+
var resyncAllEdges = internalAction({
|
|
953
|
+
args: {
|
|
954
|
+
batchSize: v.optional(v.number()),
|
|
955
|
+
cursor: v.optional(v.string())
|
|
956
|
+
},
|
|
957
|
+
returns: permissiveReturn,
|
|
958
|
+
handler: async (ctx, args) => {
|
|
959
|
+
const batchSize = args.batchSize ?? 50;
|
|
960
|
+
const result = await ctx.runQuery(
|
|
961
|
+
internal.neo4jSyncHelpers.getAllEdgesForResync,
|
|
962
|
+
{
|
|
963
|
+
limit: batchSize,
|
|
964
|
+
cursor: args.cursor
|
|
965
|
+
}
|
|
966
|
+
);
|
|
967
|
+
let synced = 0;
|
|
968
|
+
let failed = 0;
|
|
969
|
+
for (const edge of result.edges) {
|
|
970
|
+
try {
|
|
971
|
+
const syncResult = await ctx.runAction(
|
|
972
|
+
internal.neo4jSync.syncEdgeToNeo4j,
|
|
973
|
+
{
|
|
974
|
+
edgeId: edge._id,
|
|
975
|
+
operation: "upsert"
|
|
976
|
+
}
|
|
977
|
+
);
|
|
978
|
+
if (syncResult.success) {
|
|
979
|
+
synced++;
|
|
980
|
+
} else {
|
|
981
|
+
failed++;
|
|
982
|
+
}
|
|
983
|
+
} catch (error) {
|
|
984
|
+
console.error(`[Neo4j Resync] Failed to sync edge ${edge._id}:`, error);
|
|
985
|
+
failed++;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
return {
|
|
989
|
+
synced,
|
|
990
|
+
failed,
|
|
991
|
+
total: result.edges.length,
|
|
992
|
+
hasMore: result.hasMore,
|
|
993
|
+
nextCursor: result.nextCursor
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
});
|
|
997
|
+
|
|
998
|
+
export { backfillAllToNeo4j, checkNeo4jHealth, processRetryQueue, resyncAllEdges, resyncAllNodes, syncAllEdgesToNeo4j, syncAllNodesToNeo4j, syncEdgeToNeo4j, syncEmbeddingToNeo4j, syncNodeToNeo4j };
|
|
999
|
+
//# sourceMappingURL=neo4jSync.js.map
|
|
1000
|
+
//# sourceMappingURL=neo4jSync.js.map
|