@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,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* neo4jDriver module implementation.
|
|
3
|
+
*/
|
|
4
|
+
declare function validateLabel(label: string): void;
|
|
5
|
+
declare function validateRelType(relType: string): void;
|
|
6
|
+
/**
|
|
7
|
+
* Default query timeout in milliseconds.
|
|
8
|
+
* Prevents expensive graph traversals from hanging indefinitely.
|
|
9
|
+
*/
|
|
10
|
+
declare const DEFAULT_QUERY_TIMEOUT_MS = 30000;
|
|
11
|
+
/**
|
|
12
|
+
* Timeout for complex graph queries (cascade simulation, lineage traversal)
|
|
13
|
+
*/
|
|
14
|
+
declare const COMPLEX_QUERY_TIMEOUT_MS = 60000;
|
|
15
|
+
/**
|
|
16
|
+
* Execute a Cypher query and return results as typed objects
|
|
17
|
+
*
|
|
18
|
+
* @param query - Cypher query string
|
|
19
|
+
* @param params - Query parameters
|
|
20
|
+
* @param timeoutMs - Query timeout in milliseconds (default: 30s)
|
|
21
|
+
*/
|
|
22
|
+
declare function runCypher<T = Record<string, unknown>>(query: string, params?: Record<string, unknown>, timeoutMs?: number): Promise<T[]>;
|
|
23
|
+
/**
|
|
24
|
+
* Execute a write transaction (for mutations)
|
|
25
|
+
*
|
|
26
|
+
* @param query - Cypher query string
|
|
27
|
+
* @param params - Query parameters
|
|
28
|
+
* @param timeoutMs - Transaction timeout in milliseconds (default: 30s)
|
|
29
|
+
*/
|
|
30
|
+
declare function runWriteTransaction<T = Record<string, unknown>>(query: string, params?: Record<string, unknown>, timeoutMs?: number): Promise<T[]>;
|
|
31
|
+
/**
|
|
32
|
+
* Execute multiple queries in a single transaction
|
|
33
|
+
*
|
|
34
|
+
* @param queries - Array of queries with parameters
|
|
35
|
+
* @param timeoutMs - Transaction timeout in milliseconds (default: 60s for batch)
|
|
36
|
+
*/
|
|
37
|
+
declare function runBatchTransaction(queries: Array<{
|
|
38
|
+
query: string;
|
|
39
|
+
params: Record<string, unknown>;
|
|
40
|
+
}>, timeoutMs?: number): Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* Upsert a node by globalId
|
|
43
|
+
*/
|
|
44
|
+
declare function upsertNode(label: string, globalId: string, properties: Record<string, unknown>): Promise<void>;
|
|
45
|
+
/**
|
|
46
|
+
* Delete a node by globalId
|
|
47
|
+
*/
|
|
48
|
+
declare function deleteNode(globalId: string): Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* Batch upsert nodes
|
|
51
|
+
*/
|
|
52
|
+
declare function batchUpsertNodes(label: string, nodes: Array<{
|
|
53
|
+
globalId: string;
|
|
54
|
+
properties: Record<string, unknown>;
|
|
55
|
+
}>): Promise<void>;
|
|
56
|
+
/**
|
|
57
|
+
* Upsert an edge by globalId
|
|
58
|
+
*/
|
|
59
|
+
declare function upsertEdge(relType: string, globalId: string, fromGlobalId: string, toGlobalId: string, properties?: Record<string, unknown>): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* Delete an edge by globalId
|
|
62
|
+
*/
|
|
63
|
+
declare function deleteEdge(globalId: string): Promise<void>;
|
|
64
|
+
/**
|
|
65
|
+
* Batch upsert edges
|
|
66
|
+
*/
|
|
67
|
+
declare function batchUpsertEdges(edges: Array<{
|
|
68
|
+
relType: string;
|
|
69
|
+
globalId: string;
|
|
70
|
+
fromGlobalId: string;
|
|
71
|
+
toGlobalId: string;
|
|
72
|
+
properties?: Record<string, unknown>;
|
|
73
|
+
}>): Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* Check if Neo4j connection is healthy
|
|
76
|
+
*/
|
|
77
|
+
declare function healthCheck(): Promise<{
|
|
78
|
+
healthy: boolean;
|
|
79
|
+
nodeCount?: number;
|
|
80
|
+
error?: string;
|
|
81
|
+
}>;
|
|
82
|
+
/**
|
|
83
|
+
* Get connection info (for debugging)
|
|
84
|
+
*/
|
|
85
|
+
declare function getConnectionInfo(): {
|
|
86
|
+
uri: string | undefined;
|
|
87
|
+
user: string | undefined;
|
|
88
|
+
configured: boolean;
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Close the driver connection (for graceful shutdown)
|
|
92
|
+
*/
|
|
93
|
+
declare function closeDriver(): Promise<void>;
|
|
94
|
+
|
|
95
|
+
export { COMPLEX_QUERY_TIMEOUT_MS, DEFAULT_QUERY_TIMEOUT_MS, batchUpsertEdges, batchUpsertNodes, closeDriver, deleteEdge, deleteNode, getConnectionInfo, healthCheck, runBatchTransaction, runCypher, runWriteTransaction, upsertEdge, upsertNode, validateLabel, validateRelType };
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
"use node";
|
|
2
|
+
import neo4j from 'neo4j-driver';
|
|
3
|
+
|
|
4
|
+
var VALID_NODE_LABELS = /* @__PURE__ */ new Set([
|
|
5
|
+
// Ontological
|
|
6
|
+
"ValueChain",
|
|
7
|
+
"Function",
|
|
8
|
+
"FinSector",
|
|
9
|
+
"Company",
|
|
10
|
+
"Person",
|
|
11
|
+
"Investor",
|
|
12
|
+
// Epistemic
|
|
13
|
+
"Theme",
|
|
14
|
+
"Belief",
|
|
15
|
+
"Question",
|
|
16
|
+
"Evidence",
|
|
17
|
+
"Source",
|
|
18
|
+
"Decision",
|
|
19
|
+
"Sprint",
|
|
20
|
+
"Claim",
|
|
21
|
+
"Synthesis",
|
|
22
|
+
"Answer"
|
|
23
|
+
]);
|
|
24
|
+
var VALID_RELATIONSHIP_TYPES = /* @__PURE__ */ new Set([
|
|
25
|
+
// Cross-layer edges
|
|
26
|
+
"EXTRACTED_FROM",
|
|
27
|
+
"ANSWERS",
|
|
28
|
+
"RESPONDS_TO",
|
|
29
|
+
"INFORMS",
|
|
30
|
+
"QUALIFIES",
|
|
31
|
+
"TESTS",
|
|
32
|
+
"EXPLORES",
|
|
33
|
+
"BASED_ON",
|
|
34
|
+
"RELATES_TO_THESIS",
|
|
35
|
+
"BELONGS_TO",
|
|
36
|
+
"PLAYS_THEME",
|
|
37
|
+
// Same-layer edges
|
|
38
|
+
"SUPERSEDES",
|
|
39
|
+
"SAME_AS",
|
|
40
|
+
"DEPENDS_ON",
|
|
41
|
+
"REINFORCES",
|
|
42
|
+
"PARENT_OF",
|
|
43
|
+
"CHILD_OF",
|
|
44
|
+
"FALSIFIED_BY",
|
|
45
|
+
"EXCLUSIVE_WITH",
|
|
46
|
+
"COLLAPSES_IF",
|
|
47
|
+
"CASCADE_FROM",
|
|
48
|
+
"STRENGTHENED_BY",
|
|
49
|
+
"WEAKENED_BY",
|
|
50
|
+
"ALTERNATIVE_TO",
|
|
51
|
+
"SUBSUMES",
|
|
52
|
+
"VALIDATED_BY",
|
|
53
|
+
"REQUIRED_FOR",
|
|
54
|
+
"PREREQUISITE_FOR",
|
|
55
|
+
"PARALLEL_TO",
|
|
56
|
+
"CORROBORATES",
|
|
57
|
+
"EXTENDS",
|
|
58
|
+
"SAME_SOURCE_AS",
|
|
59
|
+
"SAME_THEME_AS",
|
|
60
|
+
// Ontological
|
|
61
|
+
"EVALUATES",
|
|
62
|
+
"PERSPECTIVE_ON",
|
|
63
|
+
"WORKS_AT",
|
|
64
|
+
"PARTICIPATES_IN",
|
|
65
|
+
"PERFORMS",
|
|
66
|
+
"FUNCTION_IN",
|
|
67
|
+
"IMPACTS",
|
|
68
|
+
"INVESTED_IN",
|
|
69
|
+
"RAISED_FROM",
|
|
70
|
+
"BASED_ON_BELIEF",
|
|
71
|
+
"BASED_ON_QUESTION",
|
|
72
|
+
"BLOCKED_BY_CONTRADICTION",
|
|
73
|
+
"INFORMED_BY_THEME"
|
|
74
|
+
]);
|
|
75
|
+
function validateLabel(label) {
|
|
76
|
+
if (!VALID_NODE_LABELS.has(label)) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
`[Neo4j Security] Invalid node label: ${label}. Must be one of: ${Array.from(VALID_NODE_LABELS).join(", ")}`
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function validateRelType(relType) {
|
|
83
|
+
if (!VALID_RELATIONSHIP_TYPES.has(relType)) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
`[Neo4j Security] Invalid relationship type: ${relType}. Must be one of: ${Array.from(VALID_RELATIONSHIP_TYPES).join(", ")}`
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
var driver = null;
|
|
90
|
+
function getDriver() {
|
|
91
|
+
if (!driver) {
|
|
92
|
+
const uri = process.env.NEO4J_URI;
|
|
93
|
+
const user = process.env.NEO4J_USER;
|
|
94
|
+
const password = process.env.NEO4J_PASSWORD;
|
|
95
|
+
if (!uri || !user || !password) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
"[Neo4j Driver] Missing credentials. Set NEO4J_URI, NEO4J_USER, NEO4J_PASSWORD via `npx convex env set`"
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
driver = neo4j.driver(uri, neo4j.auth.basic(user, password), {
|
|
101
|
+
// Connection pool settings
|
|
102
|
+
maxConnectionPoolSize: 50,
|
|
103
|
+
connectionAcquisitionTimeout: 3e4,
|
|
104
|
+
// Logging
|
|
105
|
+
logging: {
|
|
106
|
+
level: "warn",
|
|
107
|
+
logger: (level, message) => console.log(`[Neo4j ${level}] ${message}`)
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
return driver;
|
|
112
|
+
}
|
|
113
|
+
var DEFAULT_QUERY_TIMEOUT_MS = 3e4;
|
|
114
|
+
var COMPLEX_QUERY_TIMEOUT_MS = 6e4;
|
|
115
|
+
function toNeo4jParams(params) {
|
|
116
|
+
const result = {};
|
|
117
|
+
for (const [key, value] of Object.entries(params)) {
|
|
118
|
+
if (typeof value === "number" && Number.isInteger(value)) {
|
|
119
|
+
result[key] = neo4j.int(value);
|
|
120
|
+
} else if (Array.isArray(value)) {
|
|
121
|
+
result[key] = value.map(
|
|
122
|
+
(v) => typeof v === "number" && Number.isInteger(v) ? neo4j.int(v) : v
|
|
123
|
+
);
|
|
124
|
+
} else {
|
|
125
|
+
result[key] = value;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return result;
|
|
129
|
+
}
|
|
130
|
+
async function runCypher(query, params = {}, timeoutMs = DEFAULT_QUERY_TIMEOUT_MS) {
|
|
131
|
+
const neo4jDriver = getDriver();
|
|
132
|
+
const session = neo4jDriver.session();
|
|
133
|
+
try {
|
|
134
|
+
const neo4jParams = toNeo4jParams(params);
|
|
135
|
+
const result = await session.run(query, neo4jParams, {
|
|
136
|
+
timeout: neo4j.int(timeoutMs)
|
|
137
|
+
});
|
|
138
|
+
return result.records.map((record) => {
|
|
139
|
+
const obj = {};
|
|
140
|
+
for (const key of record.keys) {
|
|
141
|
+
const field = String(key);
|
|
142
|
+
obj[field] = convertNeo4jValue(record.get(field));
|
|
143
|
+
}
|
|
144
|
+
return obj;
|
|
145
|
+
});
|
|
146
|
+
} finally {
|
|
147
|
+
await session.close();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
async function runWriteTransaction(query, params = {}, timeoutMs = DEFAULT_QUERY_TIMEOUT_MS) {
|
|
151
|
+
const neo4jDriver = getDriver();
|
|
152
|
+
const session = neo4jDriver.session();
|
|
153
|
+
try {
|
|
154
|
+
const neo4jParams = toNeo4jParams(params);
|
|
155
|
+
const result = await session.executeWrite(
|
|
156
|
+
async (tx) => {
|
|
157
|
+
return await tx.run(query, neo4jParams);
|
|
158
|
+
},
|
|
159
|
+
{ timeout: timeoutMs }
|
|
160
|
+
);
|
|
161
|
+
return result.records.map((record) => {
|
|
162
|
+
const obj = {};
|
|
163
|
+
for (const key of record.keys) {
|
|
164
|
+
const field = String(key);
|
|
165
|
+
obj[field] = convertNeo4jValue(record.get(field));
|
|
166
|
+
}
|
|
167
|
+
return obj;
|
|
168
|
+
});
|
|
169
|
+
} finally {
|
|
170
|
+
await session.close();
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
async function runBatchTransaction(queries, timeoutMs = COMPLEX_QUERY_TIMEOUT_MS) {
|
|
174
|
+
const neo4jDriver = getDriver();
|
|
175
|
+
const session = neo4jDriver.session();
|
|
176
|
+
try {
|
|
177
|
+
await session.executeWrite(
|
|
178
|
+
async (tx) => {
|
|
179
|
+
for (const { query, params } of queries) {
|
|
180
|
+
await tx.run(query, params);
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
{ timeout: timeoutMs }
|
|
184
|
+
);
|
|
185
|
+
} finally {
|
|
186
|
+
await session.close();
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
async function upsertNode(label, globalId, properties) {
|
|
190
|
+
validateLabel(label);
|
|
191
|
+
await runWriteTransaction(
|
|
192
|
+
`
|
|
193
|
+
MERGE (n:${label} {globalId: $globalId})
|
|
194
|
+
SET n += $properties
|
|
195
|
+
SET n.updatedAt = timestamp()
|
|
196
|
+
`,
|
|
197
|
+
{ globalId, properties }
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
async function deleteNode(globalId) {
|
|
201
|
+
await runWriteTransaction(
|
|
202
|
+
`
|
|
203
|
+
MATCH (n {globalId: $globalId})
|
|
204
|
+
DETACH DELETE n
|
|
205
|
+
`,
|
|
206
|
+
{ globalId }
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
async function batchUpsertNodes(label, nodes) {
|
|
210
|
+
if (nodes.length === 0) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
validateLabel(label);
|
|
214
|
+
await runWriteTransaction(
|
|
215
|
+
`
|
|
216
|
+
UNWIND $nodes as node
|
|
217
|
+
MERGE (n:${label} {globalId: node.globalId})
|
|
218
|
+
SET n += node.properties
|
|
219
|
+
SET n.updatedAt = timestamp()
|
|
220
|
+
`,
|
|
221
|
+
{ nodes }
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
async function upsertEdge(relType, globalId, fromGlobalId, toGlobalId, properties = {}) {
|
|
225
|
+
validateRelType(relType);
|
|
226
|
+
await runWriteTransaction(
|
|
227
|
+
`
|
|
228
|
+
MATCH (from {globalId: $fromGlobalId})
|
|
229
|
+
MATCH (to {globalId: $toGlobalId})
|
|
230
|
+
MERGE (from)-[r:${relType} {globalId: $globalId}]->(to)
|
|
231
|
+
SET r += $properties
|
|
232
|
+
SET r.updatedAt = timestamp()
|
|
233
|
+
`,
|
|
234
|
+
{ globalId, fromGlobalId, toGlobalId, properties }
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
async function deleteEdge(globalId) {
|
|
238
|
+
await runWriteTransaction(
|
|
239
|
+
`
|
|
240
|
+
MATCH ()-[r {globalId: $globalId}]->()
|
|
241
|
+
DELETE r
|
|
242
|
+
`,
|
|
243
|
+
{ globalId }
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
async function batchUpsertEdges(edges) {
|
|
247
|
+
if (edges.length === 0) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
const byType = /* @__PURE__ */ new Map();
|
|
251
|
+
for (const edge of edges) {
|
|
252
|
+
const existing = byType.get(edge.relType) || [];
|
|
253
|
+
existing.push(edge);
|
|
254
|
+
byType.set(edge.relType, existing);
|
|
255
|
+
}
|
|
256
|
+
const queries = [];
|
|
257
|
+
for (const [relType, typeEdges] of byType) {
|
|
258
|
+
queries.push({
|
|
259
|
+
query: `
|
|
260
|
+
UNWIND $edges as edge
|
|
261
|
+
MATCH (from {globalId: edge.fromGlobalId})
|
|
262
|
+
MATCH (to {globalId: edge.toGlobalId})
|
|
263
|
+
MERGE (from)-[r:${relType} {globalId: edge.globalId}]->(to)
|
|
264
|
+
SET r += edge.properties
|
|
265
|
+
SET r.updatedAt = timestamp()
|
|
266
|
+
`,
|
|
267
|
+
params: {
|
|
268
|
+
edges: typeEdges.map((e) => ({
|
|
269
|
+
globalId: e.globalId,
|
|
270
|
+
fromGlobalId: e.fromGlobalId,
|
|
271
|
+
toGlobalId: e.toGlobalId,
|
|
272
|
+
properties: e.properties || {}
|
|
273
|
+
}))
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
await runBatchTransaction(queries);
|
|
278
|
+
}
|
|
279
|
+
async function healthCheck() {
|
|
280
|
+
try {
|
|
281
|
+
const result = await runCypher(
|
|
282
|
+
"MATCH (n) RETURN count(n) as count LIMIT 1"
|
|
283
|
+
);
|
|
284
|
+
return {
|
|
285
|
+
healthy: true,
|
|
286
|
+
nodeCount: result[0]?.count || 0
|
|
287
|
+
};
|
|
288
|
+
} catch (error) {
|
|
289
|
+
return {
|
|
290
|
+
healthy: false,
|
|
291
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
function getConnectionInfo() {
|
|
296
|
+
return {
|
|
297
|
+
uri: process.env.NEO4J_URI,
|
|
298
|
+
user: process.env.NEO4J_USER,
|
|
299
|
+
configured: Boolean(
|
|
300
|
+
process.env.NEO4J_URI && process.env.NEO4J_USER && process.env.NEO4J_PASSWORD
|
|
301
|
+
)
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
function convertNeo4jValue(value) {
|
|
305
|
+
if (value === null || value === void 0) {
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
if (neo4j.isInt(value)) {
|
|
309
|
+
return neo4j.integer.toNumber(value);
|
|
310
|
+
}
|
|
311
|
+
if (neo4j.isDate(value) || neo4j.isDateTime(value) || neo4j.isTime(value)) {
|
|
312
|
+
return value.toString();
|
|
313
|
+
}
|
|
314
|
+
if (Array.isArray(value)) {
|
|
315
|
+
return value.map(convertNeo4jValue);
|
|
316
|
+
}
|
|
317
|
+
if (value && typeof value === "object" && "properties" in value) {
|
|
318
|
+
const nodeObj = value;
|
|
319
|
+
const result = {};
|
|
320
|
+
for (const [k, v] of Object.entries(nodeObj.properties)) {
|
|
321
|
+
result[k] = convertNeo4jValue(v);
|
|
322
|
+
}
|
|
323
|
+
return result;
|
|
324
|
+
}
|
|
325
|
+
if (typeof value === "object") {
|
|
326
|
+
const result = {};
|
|
327
|
+
for (const [k, v] of Object.entries(value)) {
|
|
328
|
+
result[k] = convertNeo4jValue(v);
|
|
329
|
+
}
|
|
330
|
+
return result;
|
|
331
|
+
}
|
|
332
|
+
return value;
|
|
333
|
+
}
|
|
334
|
+
async function closeDriver() {
|
|
335
|
+
if (driver) {
|
|
336
|
+
await driver.close();
|
|
337
|
+
driver = null;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export { COMPLEX_QUERY_TIMEOUT_MS, DEFAULT_QUERY_TIMEOUT_MS, batchUpsertEdges, batchUpsertNodes, closeDriver, deleteEdge, deleteNode, getConnectionInfo, healthCheck, runBatchTransaction, runCypher, runWriteTransaction, upsertEdge, upsertNode, validateLabel, validateRelType };
|
|
342
|
+
//# sourceMappingURL=neo4jDriver.js.map
|
|
343
|
+
//# sourceMappingURL=neo4jDriver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/neo4jDriver.ts"],"names":[],"mappings":"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","file":"neo4jDriver.js","sourcesContent":["/**\n * neo4jDriver module implementation.\n */\n\n\"use node\";\n/**\n * Direct Neo4j Driver for Convex\n *\n * Uses the \"use node\" directive to enable Node.js runtime, allowing\n * direct use of the neo4j-driver package instead of HTTP proxies.\n *\n * Environment Variables (set per deployment via `npx convex env set`):\n * - NEO4J_URI: neo4j+s://xxx.databases.neo4j.io\n * - NEO4J_USER: neo4j\n * - NEO4J_PASSWORD: your-password\n *\n * @see /docs/architecture/UNIFIED_GRAPH_ARCHITECTURE.md\n */\n\nimport neo4j, { type Driver } from \"neo4j-driver\";\n\n// =============================================================================\n// VALID LABELS AND RELATIONSHIP TYPES (Security: Prevent Cypher Injection)\n// =============================================================================\n\nconst VALID_NODE_LABELS = new Set([\n // Ontological\n \"ValueChain\",\n \"Function\",\n \"FinSector\",\n \"Company\",\n \"Person\",\n \"Investor\",\n // Epistemic\n \"Theme\",\n \"Belief\",\n \"Question\",\n \"Evidence\",\n \"Source\",\n \"Decision\",\n \"Sprint\",\n \"Claim\",\n \"Synthesis\",\n \"Answer\",\n]);\n\nconst VALID_RELATIONSHIP_TYPES = new Set([\n // Cross-layer edges\n \"EXTRACTED_FROM\",\n \"ANSWERS\",\n \"RESPONDS_TO\",\n \"INFORMS\",\n \"QUALIFIES\",\n \"TESTS\",\n \"EXPLORES\",\n \"BASED_ON\",\n \"RELATES_TO_THESIS\",\n \"BELONGS_TO\",\n \"PLAYS_THEME\",\n // Same-layer edges\n \"SUPERSEDES\",\n \"SAME_AS\",\n \"DEPENDS_ON\",\n \"REINFORCES\",\n \"PARENT_OF\",\n \"CHILD_OF\",\n \"FALSIFIED_BY\",\n \"EXCLUSIVE_WITH\",\n \"COLLAPSES_IF\",\n \"CASCADE_FROM\",\n \"STRENGTHENED_BY\",\n \"WEAKENED_BY\",\n \"ALTERNATIVE_TO\",\n \"SUBSUMES\",\n \"VALIDATED_BY\",\n \"REQUIRED_FOR\",\n \"PREREQUISITE_FOR\",\n \"PARALLEL_TO\",\n \"CORROBORATES\",\n \"EXTENDS\",\n \"SAME_SOURCE_AS\",\n \"SAME_THEME_AS\",\n // Ontological\n \"EVALUATES\",\n \"PERSPECTIVE_ON\",\n \"WORKS_AT\",\n \"PARTICIPATES_IN\",\n \"PERFORMS\",\n \"FUNCTION_IN\",\n \"IMPACTS\",\n \"INVESTED_IN\",\n \"RAISED_FROM\",\n \"BASED_ON_BELIEF\",\n \"BASED_ON_QUESTION\",\n \"BLOCKED_BY_CONTRADICTION\",\n \"INFORMED_BY_THEME\",\n]);\n\nexport function validateLabel(label: string): void {\n if (!VALID_NODE_LABELS.has(label)) {\n throw new Error(\n `[Neo4j Security] Invalid node label: ${label}. Must be one of: ${Array.from(VALID_NODE_LABELS).join(\", \")}`\n );\n }\n}\n\nexport function validateRelType(relType: string): void {\n if (!VALID_RELATIONSHIP_TYPES.has(relType)) {\n throw new Error(\n `[Neo4j Security] Invalid relationship type: ${relType}. Must be one of: ${Array.from(VALID_RELATIONSHIP_TYPES).join(\", \")}`\n );\n }\n}\n\n// =============================================================================\n// DRIVER SINGLETON\n// =============================================================================\n\nlet driver: Driver | null = null;\n\nfunction getDriver(): Driver {\n if (!driver) {\n const uri = process.env.NEO4J_URI;\n const user = process.env.NEO4J_USER;\n const password = process.env.NEO4J_PASSWORD;\n\n if (!uri || !user || !password) {\n throw new Error(\n \"[Neo4j Driver] Missing credentials. Set NEO4J_URI, NEO4J_USER, NEO4J_PASSWORD via `npx convex env set`\"\n );\n }\n\n driver = neo4j.driver(uri, neo4j.auth.basic(user, password), {\n // Connection pool settings\n maxConnectionPoolSize: 50,\n connectionAcquisitionTimeout: 30_000,\n // Logging\n logging: {\n level: \"warn\",\n logger: (level, message) => console.log(`[Neo4j ${level}] ${message}`),\n },\n });\n }\n return driver;\n}\n\n// =============================================================================\n// QUERY CONFIGURATION\n// =============================================================================\n\n/**\n * Default query timeout in milliseconds.\n * Prevents expensive graph traversals from hanging indefinitely.\n */\nexport const DEFAULT_QUERY_TIMEOUT_MS = 30_000; // 30 seconds\n\n/**\n * Timeout for complex graph queries (cascade simulation, lineage traversal)\n */\nexport const COMPLEX_QUERY_TIMEOUT_MS = 60_000; // 60 seconds\n\n// =============================================================================\n// QUERY EXECUTION\n// =============================================================================\n\n/**\n * Convert JavaScript values to Neo4j-compatible types\n * Neo4j requires explicit integers, not floats\n */\nfunction toNeo4jParams(\n params: Record<string, unknown>\n): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(params)) {\n if (typeof value === \"number\" && Number.isInteger(value)) {\n // Convert JavaScript integers to Neo4j integers\n result[key] = neo4j.int(value);\n } else if (Array.isArray(value)) {\n result[key] = value.map((v) =>\n typeof v === \"number\" && Number.isInteger(v) ? neo4j.int(v) : v\n );\n } else {\n result[key] = value;\n }\n }\n return result;\n}\n\n/**\n * Execute a Cypher query and return results as typed objects\n *\n * @param query - Cypher query string\n * @param params - Query parameters\n * @param timeoutMs - Query timeout in milliseconds (default: 30s)\n */\nexport async function runCypher<T = Record<string, unknown>>(\n query: string,\n params: Record<string, unknown> = {},\n timeoutMs: number = DEFAULT_QUERY_TIMEOUT_MS\n): Promise<T[]> {\n const neo4jDriver = getDriver();\n const session = neo4jDriver.session();\n\n try {\n const neo4jParams = toNeo4jParams(params);\n const result = await session.run(query, neo4jParams, {\n timeout: neo4j.int(timeoutMs),\n });\n return result.records.map((record) => {\n const obj: Record<string, unknown> = {};\n for (const key of record.keys) {\n const field = String(key);\n obj[field] = convertNeo4jValue(record.get(field));\n }\n return obj as T;\n });\n } finally {\n await session.close();\n }\n}\n\n/**\n * Execute a write transaction (for mutations)\n *\n * @param query - Cypher query string\n * @param params - Query parameters\n * @param timeoutMs - Transaction timeout in milliseconds (default: 30s)\n */\nexport async function runWriteTransaction<T = Record<string, unknown>>(\n query: string,\n params: Record<string, unknown> = {},\n timeoutMs: number = DEFAULT_QUERY_TIMEOUT_MS\n): Promise<T[]> {\n const neo4jDriver = getDriver();\n const session = neo4jDriver.session();\n\n try {\n const neo4jParams = toNeo4jParams(params);\n const result = await session.executeWrite(\n async (tx) => {\n return await tx.run(query, neo4jParams);\n },\n { timeout: timeoutMs }\n );\n return result.records.map((record) => {\n const obj: Record<string, unknown> = {};\n for (const key of record.keys) {\n const field = String(key);\n obj[field] = convertNeo4jValue(record.get(field));\n }\n return obj as T;\n });\n } finally {\n await session.close();\n }\n}\n\n/**\n * Execute multiple queries in a single transaction\n *\n * @param queries - Array of queries with parameters\n * @param timeoutMs - Transaction timeout in milliseconds (default: 60s for batch)\n */\nexport async function runBatchTransaction(\n queries: Array<{ query: string; params: Record<string, unknown> }>,\n timeoutMs: number = COMPLEX_QUERY_TIMEOUT_MS\n): Promise<void> {\n const neo4jDriver = getDriver();\n const session = neo4jDriver.session();\n\n try {\n await session.executeWrite(\n async (tx) => {\n for (const { query, params } of queries) {\n await tx.run(query, params);\n }\n },\n { timeout: timeoutMs }\n );\n } finally {\n await session.close();\n }\n}\n\n// =============================================================================\n// NODE OPERATIONS\n// =============================================================================\n\n/**\n * Upsert a node by globalId\n */\nexport async function upsertNode(\n label: string,\n globalId: string,\n properties: Record<string, unknown>\n): Promise<void> {\n validateLabel(label); // Security: prevent Cypher injection\n await runWriteTransaction(\n `\n MERGE (n:${label} {globalId: $globalId})\n SET n += $properties\n SET n.updatedAt = timestamp()\n `,\n { globalId, properties }\n );\n}\n\n/**\n * Delete a node by globalId\n */\nexport async function deleteNode(globalId: string): Promise<void> {\n await runWriteTransaction(\n `\n MATCH (n {globalId: $globalId})\n DETACH DELETE n\n `,\n { globalId }\n );\n}\n\n/**\n * Batch upsert nodes\n */\nexport async function batchUpsertNodes(\n label: string,\n nodes: Array<{ globalId: string; properties: Record<string, unknown> }>\n): Promise<void> {\n if (nodes.length === 0) {\n return;\n }\n\n validateLabel(label); // Security: prevent Cypher injection\n await runWriteTransaction(\n `\n UNWIND $nodes as node\n MERGE (n:${label} {globalId: node.globalId})\n SET n += node.properties\n SET n.updatedAt = timestamp()\n `,\n { nodes }\n );\n}\n\n// =============================================================================\n// EDGE OPERATIONS\n// =============================================================================\n\n/**\n * Upsert an edge by globalId\n */\nexport async function upsertEdge(\n relType: string,\n globalId: string,\n fromGlobalId: string,\n toGlobalId: string,\n properties: Record<string, unknown> = {}\n): Promise<void> {\n validateRelType(relType); // Security: prevent Cypher injection\n await runWriteTransaction(\n `\n MATCH (from {globalId: $fromGlobalId})\n MATCH (to {globalId: $toGlobalId})\n MERGE (from)-[r:${relType} {globalId: $globalId}]->(to)\n SET r += $properties\n SET r.updatedAt = timestamp()\n `,\n { globalId, fromGlobalId, toGlobalId, properties }\n );\n}\n\n/**\n * Delete an edge by globalId\n */\nexport async function deleteEdge(globalId: string): Promise<void> {\n await runWriteTransaction(\n `\n MATCH ()-[r {globalId: $globalId}]->()\n DELETE r\n `,\n { globalId }\n );\n}\n\n/**\n * Batch upsert edges\n */\nexport async function batchUpsertEdges(\n edges: Array<{\n relType: string;\n globalId: string;\n fromGlobalId: string;\n toGlobalId: string;\n properties?: Record<string, unknown>;\n }>\n): Promise<void> {\n if (edges.length === 0) {\n return;\n }\n\n // Group by relationship type for efficient batching\n const byType = new Map<string, typeof edges>();\n for (const edge of edges) {\n const existing = byType.get(edge.relType) || [];\n existing.push(edge);\n byType.set(edge.relType, existing);\n }\n\n const queries: Array<{ query: string; params: Record<string, unknown> }> = [];\n for (const [relType, typeEdges] of byType) {\n queries.push({\n query: `\n UNWIND $edges as edge\n MATCH (from {globalId: edge.fromGlobalId})\n MATCH (to {globalId: edge.toGlobalId})\n MERGE (from)-[r:${relType} {globalId: edge.globalId}]->(to)\n SET r += edge.properties\n SET r.updatedAt = timestamp()\n `,\n params: {\n edges: typeEdges.map((e) => ({\n globalId: e.globalId,\n fromGlobalId: e.fromGlobalId,\n toGlobalId: e.toGlobalId,\n properties: e.properties || {},\n })),\n },\n });\n }\n\n await runBatchTransaction(queries);\n}\n\n// =============================================================================\n// HEALTH CHECK\n// =============================================================================\n\n/**\n * Check if Neo4j connection is healthy\n */\nexport async function healthCheck(): Promise<{\n healthy: boolean;\n nodeCount?: number;\n error?: string;\n}> {\n try {\n const result = await runCypher<{ count: number }>(\n \"MATCH (n) RETURN count(n) as count LIMIT 1\"\n );\n return {\n healthy: true,\n nodeCount: result[0]?.count || 0,\n };\n } catch (error) {\n return {\n healthy: false,\n error: error instanceof Error ? error.message : \"Unknown error\",\n };\n }\n}\n\n/**\n * Get connection info (for debugging)\n */\nexport function getConnectionInfo(): {\n uri: string | undefined;\n user: string | undefined;\n configured: boolean;\n} {\n return {\n uri: process.env.NEO4J_URI,\n user: process.env.NEO4J_USER,\n configured: Boolean(\n process.env.NEO4J_URI &&\n process.env.NEO4J_USER &&\n process.env.NEO4J_PASSWORD\n ),\n };\n}\n\n// =============================================================================\n// VALUE CONVERSION\n// =============================================================================\n\n/**\n * Convert Neo4j types to plain JavaScript\n */\nfunction convertNeo4jValue(value: unknown): unknown {\n if (value === null || value === undefined) {\n return null;\n }\n\n // Handle Neo4j Integer\n if (neo4j.isInt(value)) {\n return neo4j.integer.toNumber(value);\n }\n\n // Handle Neo4j Date/Time types\n if (neo4j.isDate(value) || neo4j.isDateTime(value) || neo4j.isTime(value)) {\n return value.toString();\n }\n\n // Handle arrays\n if (Array.isArray(value)) {\n return value.map(convertNeo4jValue);\n }\n\n // Handle Node objects\n if (value && typeof value === \"object\" && \"properties\" in value) {\n const nodeObj = value as { properties: Record<string, unknown> };\n const result: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(nodeObj.properties)) {\n result[k] = convertNeo4jValue(v);\n }\n return result;\n }\n\n // Handle plain objects\n if (typeof value === \"object\") {\n const result: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n result[k] = convertNeo4jValue(v);\n }\n return result;\n }\n\n return value;\n}\n\n// =============================================================================\n// CLEANUP\n// =============================================================================\n\n/**\n * Close the driver connection (for graceful shutdown)\n */\nexport async function closeDriver(): Promise<void> {\n if (driver) {\n await driver.close();\n driver = null;\n }\n}\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
declare const DUAL_WRITE_EDGE_TYPES: readonly ["supports", "informs", "tests", "depends_on", "derived_from", "contains", "supersedes", "extracted_from", "responds_to", "based_on", "answers", "belongs_to", "relates_to_thesis", "corroborates", "extends", "same_source_as", "same_theme_as", "plays_theme", "impacts", "evaluates", "mentioned_in", "perspective_on"];
|
|
2
|
+
type DualWriteEdgeType = (typeof DUAL_WRITE_EDGE_TYPES)[number];
|
|
3
|
+
declare function needsDualWrite(edgeType: string): boolean;
|
|
4
|
+
declare const createEdge: any;
|
|
5
|
+
declare const deleteEdge: any;
|
|
6
|
+
declare const updateEdge: any;
|
|
7
|
+
declare const getEdge: any;
|
|
8
|
+
declare const retryProjectionByGlobalId: any;
|
|
9
|
+
|
|
10
|
+
export { DUAL_WRITE_EDGE_TYPES, type DualWriteEdgeType, createEdge, deleteEdge, getEdge, needsDualWrite, retryProjectionByGlobalId, updateEdge };
|