@lucern/graph-sync 1.0.29 → 1.0.30

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.
@@ -92,7 +92,7 @@ function getDriver() {
92
92
  const uri = process.env.NEO4J_URI;
93
93
  const user = process.env.NEO4J_USER;
94
94
  const password = process.env.NEO4J_PASSWORD;
95
- if (!uri || !user || !password) {
95
+ if (!(uri && user && password)) {
96
96
  throw new Error(
97
97
  "[Neo4j Driver] Missing credentials. Set NEO4J_URI, NEO4J_USER, NEO4J_PASSWORD via `npx convex env set`"
98
98
  );
@@ -153,9 +153,7 @@ async function runWriteTransaction(query, params = {}, timeoutMs = DEFAULT_QUERY
153
153
  try {
154
154
  const neo4jParams = toNeo4jParams(params);
155
155
  const result = await session.executeWrite(
156
- async (tx) => {
157
- return await tx.run(query, neo4jParams);
158
- },
156
+ async (tx) => await tx.run(query, neo4jParams),
159
157
  { timeout: timeoutMs }
160
158
  );
161
159
  return result.records.map((record) => {
@@ -1 +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"]}
1
+ {"version":3,"sources":["../src/neo4jDriver.ts"],"names":[],"mappings":"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","file":"neo4jDriver.js","sourcesContent":["// biome-ignore-all lint/style/useFilenamingConvention: Published @lucern/graph-sync/neo4jDriver subpath; rename needs an export-map migration.\n/**\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) => await tx.run(query, neo4jParams),\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"]}
@@ -1,10 +1,98 @@
1
+ import * as convex_server from 'convex/server';
2
+
1
3
  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
4
  type DualWriteEdgeType = (typeof DUAL_WRITE_EDGE_TYPES)[number];
3
5
  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;
6
+ declare const createEdge: convex_server.RegisteredAction<"internal", {
7
+ weight?: number | undefined;
8
+ confidence?: number | undefined;
9
+ context?: string | undefined;
10
+ derivationType?: string | undefined;
11
+ topicId?: string | undefined;
12
+ tenantId?: string | undefined;
13
+ workspaceId?: string | undefined;
14
+ fromLayer?: string | undefined;
15
+ toLayer?: string | undefined;
16
+ fromNodeType?: string | undefined;
17
+ toNodeType?: string | undefined;
18
+ reasoningMethod?: string | undefined;
19
+ logicalRole?: string | undefined;
20
+ temporalClass?: string | undefined;
21
+ validFrom?: number | undefined;
22
+ validUntil?: number | undefined;
23
+ metadata?: any;
24
+ globalId: string;
25
+ edgeType: string;
26
+ createdBy: string;
27
+ fromGlobalId: string;
28
+ toGlobalId: string;
29
+ }, Promise<{
30
+ success: boolean;
31
+ globalId: string;
32
+ edgeType: string;
33
+ queuedForRetry: boolean;
34
+ reason: string;
35
+ } | {
36
+ success: boolean;
37
+ globalId: string;
38
+ edgeType: string;
39
+ dualWritten: boolean;
40
+ }>>;
41
+ declare const deleteEdge: convex_server.RegisteredAction<"internal", {
42
+ globalId: string;
43
+ }, Promise<{
44
+ success: boolean;
45
+ }>>;
46
+ declare const updateEdge: convex_server.RegisteredAction<"internal", {
47
+ weight?: number | undefined;
48
+ confidence?: number | undefined;
49
+ context?: string | undefined;
50
+ derivationType?: string | undefined;
51
+ globalId: string;
52
+ }, Promise<{
53
+ success: boolean;
54
+ error: string;
55
+ globalId?: undefined;
56
+ } | {
57
+ success: boolean;
58
+ globalId: string;
59
+ error?: undefined;
60
+ }>>;
61
+ declare const getEdge: convex_server.RegisteredAction<"internal", {
62
+ globalId: string;
63
+ }, Promise<{
64
+ globalId: string;
65
+ edgeType: string;
66
+ fromGlobalId: string;
67
+ toGlobalId: string;
68
+ properties: Record<string, unknown>;
69
+ } | null>>;
70
+ declare const retryProjectionByGlobalId: convex_server.RegisteredAction<"internal", {
71
+ globalId: string;
72
+ }, Promise<{
73
+ success: boolean;
74
+ error: string;
75
+ skipped?: undefined;
76
+ reason?: undefined;
77
+ edgeType?: undefined;
78
+ globalId?: undefined;
79
+ projected?: undefined;
80
+ } | {
81
+ success: boolean;
82
+ skipped: boolean;
83
+ reason: string;
84
+ edgeType: string;
85
+ error?: undefined;
86
+ globalId?: undefined;
87
+ projected?: undefined;
88
+ } | {
89
+ success: boolean;
90
+ globalId: string;
91
+ projected: boolean;
92
+ error?: undefined;
93
+ skipped?: undefined;
94
+ reason?: undefined;
95
+ edgeType?: undefined;
96
+ }>>;
9
97
 
10
98
  export { DUAL_WRITE_EDGE_TYPES, type DualWriteEdgeType, createEdge, deleteEdge, getEdge, needsDualWrite, retryProjectionByGlobalId, updateEdge };
@@ -1,11 +1,14 @@
1
1
  "use node";
2
- import { v } from 'convex/values';
3
2
  import { permissiveReturn } from '@lucern/contracts/schema-helpers/validators';
4
3
  import { getNeo4jRelType } from '@lucern/graph-primitives/graphTypes';
5
- import { anyApi, internalActionGeneric } from 'convex/server';
4
+ import { v } from 'convex/values';
5
+ import { unsafeConvexAnyApi } from '@lucern/contracts/convex/unsafeAnyApi';
6
+ import { internalActionGeneric } from 'convex/server';
6
7
  import neo4j from 'neo4j-driver';
7
8
 
8
- var internal = anyApi;
9
+ var internal = unsafeConvexAnyApi(
10
+ "graph-sync top-level module bundle lacks a committed Convex _generated/api surface"
11
+ );
9
12
  var internalAction = internalActionGeneric;
10
13
  var driver = null;
11
14
  function getDriver() {
@@ -13,7 +16,7 @@ function getDriver() {
13
16
  const uri = process.env.NEO4J_URI;
14
17
  const user = process.env.NEO4J_USER;
15
18
  const password = process.env.NEO4J_PASSWORD;
16
- if (!uri || !user || !password) {
19
+ if (!(uri && user && password)) {
17
20
  throw new Error(
18
21
  "[Neo4j Driver] Missing credentials. Set NEO4J_URI, NEO4J_USER, NEO4J_PASSWORD via `npx convex env set`"
19
22
  );
@@ -73,9 +76,7 @@ async function runWriteTransaction(query, params = {}, timeoutMs = DEFAULT_QUERY
73
76
  try {
74
77
  const neo4jParams = toNeo4jParams(params);
75
78
  const result = await session.executeWrite(
76
- async (tx) => {
77
- return await tx.run(query, neo4jParams);
78
- },
79
+ async (tx) => await tx.run(query, neo4jParams),
79
80
  { timeout: timeoutMs }
80
81
  );
81
82
  return result.records.map((record) => {
@@ -155,19 +156,21 @@ var DUAL_WRITE_EDGE_TYPES = [
155
156
  "mentioned_in",
156
157
  "perspective_on"
157
158
  ];
159
+ var LOWER_EDGE_TYPE_REGEX = /^[a-z0-9_]+$/u;
160
+ var UPPER_EDGE_TYPE_REGEX = /^[A-Z0-9_]+$/u;
158
161
  function needsDualWrite(edgeType) {
159
162
  return DUAL_WRITE_EDGE_TYPES.includes(edgeType);
160
163
  }
161
164
  function normalizeEdgeType(edgeType) {
162
165
  const normalized = edgeType.trim().toLowerCase();
163
- if (!/^[a-z0-9_]+$/u.test(normalized)) {
166
+ if (!LOWER_EDGE_TYPE_REGEX.test(normalized)) {
164
167
  throw new Error(`[Neo4j Edge API] Invalid edge type: ${edgeType}`);
165
168
  }
166
169
  return normalized;
167
170
  }
168
171
  function resolveRelationshipType(edgeType) {
169
172
  const relType = getNeo4jRelType(edgeType);
170
- if (!/^[A-Z0-9_]+$/u.test(relType)) {
173
+ if (!UPPER_EDGE_TYPE_REGEX.test(relType)) {
171
174
  throw new Error(`[Neo4j Edge API] Invalid relationship type: ${relType}`);
172
175
  }
173
176
  return relType;
@@ -182,10 +185,61 @@ function readNumberProperty(source, key) {
182
185
  }
183
186
  function metadataSummary(metadata) {
184
187
  if (!metadata) {
185
- return void 0;
188
+ return;
186
189
  }
187
190
  return Object.entries(metadata).map(([key, value]) => `${key}=${String(value)}`).slice(0, 8).join(" | ");
188
191
  }
192
+ function readMetadata(metadata) {
193
+ return metadata && typeof metadata === "object" && !Array.isArray(metadata) ? metadata : void 0;
194
+ }
195
+ function neo4jEdgeProperties(args, edgeType, metadata, now) {
196
+ return {
197
+ globalId: args.globalId,
198
+ edgeType,
199
+ weight: args.weight ?? 1,
200
+ confidence: args.confidence ?? readNumberProperty(metadata, "confidence") ?? 1,
201
+ context: args.context ?? metadataSummary(metadata) ?? "",
202
+ derivationType: args.derivationType ?? "",
203
+ createdBy: args.createdBy,
204
+ createdAt: now,
205
+ updatedAt: now,
206
+ topicId: args.topicId ?? readStringProperty(metadata, "topicId") ?? "",
207
+ tenantId: args.tenantId ?? readStringProperty(metadata, "tenantId") ?? "",
208
+ workspaceId: args.workspaceId ?? readStringProperty(metadata, "workspaceId") ?? "",
209
+ fromLayer: args.fromLayer ?? "",
210
+ toLayer: args.toLayer ?? "",
211
+ fromNodeType: args.fromNodeType ?? "",
212
+ toNodeType: args.toNodeType ?? "",
213
+ reasoningMethod: args.reasoningMethod ?? "",
214
+ logicalRole: args.logicalRole ?? "",
215
+ temporalClass: args.temporalClass ?? "structural",
216
+ validFrom: args.validFrom ?? now,
217
+ validUntil: args.validUntil ?? null
218
+ };
219
+ }
220
+ function mirrorInputFromCreateArgs(args, edgeType) {
221
+ return {
222
+ globalId: args.globalId,
223
+ fromGlobalId: args.fromGlobalId,
224
+ toGlobalId: args.toGlobalId,
225
+ edgeType,
226
+ weight: args.weight,
227
+ confidence: args.confidence,
228
+ context: args.context,
229
+ derivationType: args.derivationType,
230
+ createdBy: args.createdBy,
231
+ topicId: args.topicId,
232
+ fromLayer: args.fromLayer,
233
+ toLayer: args.toLayer,
234
+ fromNodeType: args.fromNodeType,
235
+ toNodeType: args.toNodeType,
236
+ reasoningMethod: args.reasoningMethod,
237
+ logicalRole: args.logicalRole,
238
+ temporalClass: args.temporalClass,
239
+ validFrom: args.validFrom,
240
+ validUntil: args.validUntil
241
+ };
242
+ }
189
243
  async function mirrorEdgeToConvex(ctx, args) {
190
244
  await ctx.runMutation(internal.epistemicEdges.mirrorEdgeToConvex, args);
191
245
  }
@@ -197,6 +251,40 @@ async function queueEdgeRetry(ctx, args) {
197
251
  error: args.error
198
252
  });
199
253
  }
254
+ async function mirrorCreatedEdgeIfNeeded(ctx, args, edgeType) {
255
+ if (!needsDualWrite(edgeType)) {
256
+ return;
257
+ }
258
+ try {
259
+ await mirrorEdgeToConvex(ctx, mirrorInputFromCreateArgs(args, edgeType));
260
+ } catch (error) {
261
+ await queueEdgeRetry(ctx, {
262
+ globalId: args.globalId,
263
+ operation: "upsert",
264
+ error: `Convex mirror failed: ${error instanceof Error ? error.message : "Unknown error"}`
265
+ });
266
+ }
267
+ }
268
+ async function handleMissingNeo4jEndpoints(ctx, args, edgeType) {
269
+ await queueEdgeRetry(ctx, {
270
+ globalId: args.globalId,
271
+ operation: "upsert",
272
+ error: `Source or target node not yet synced to Neo4j (from: ${args.fromGlobalId}, to: ${args.toGlobalId})`
273
+ });
274
+ if (needsDualWrite(edgeType)) {
275
+ try {
276
+ await mirrorEdgeToConvex(ctx, mirrorInputFromCreateArgs(args, edgeType));
277
+ } catch {
278
+ }
279
+ }
280
+ return {
281
+ success: false,
282
+ globalId: args.globalId,
283
+ edgeType,
284
+ queuedForRetry: true,
285
+ reason: "nodes_not_synced"
286
+ };
287
+ }
200
288
  var createEdge = internalAction({
201
289
  args: {
202
290
  globalId: v.string(),
@@ -232,31 +320,9 @@ var createEdge = internalAction({
232
320
  }
233
321
  const edgeType = normalizeEdgeType(args.edgeType);
234
322
  const relType = resolveRelationshipType(edgeType);
235
- const metadata = args.metadata && typeof args.metadata === "object" ? args.metadata : void 0;
236
323
  const now = Date.now();
237
- const properties = {
238
- globalId: args.globalId,
239
- edgeType,
240
- weight: args.weight ?? 1,
241
- confidence: args.confidence ?? readNumberProperty(metadata, "confidence") ?? 1,
242
- context: args.context ?? metadataSummary(metadata) ?? "",
243
- derivationType: args.derivationType ?? "",
244
- createdBy: args.createdBy,
245
- createdAt: now,
246
- updatedAt: now,
247
- topicId: args.topicId ?? readStringProperty(metadata, "topicId") ?? "",
248
- tenantId: args.tenantId ?? readStringProperty(metadata, "tenantId") ?? "",
249
- workspaceId: args.workspaceId ?? readStringProperty(metadata, "workspaceId") ?? "",
250
- fromLayer: args.fromLayer ?? "",
251
- toLayer: args.toLayer ?? "",
252
- fromNodeType: args.fromNodeType ?? "",
253
- toNodeType: args.toNodeType ?? "",
254
- reasoningMethod: args.reasoningMethod ?? "",
255
- logicalRole: args.logicalRole ?? "",
256
- temporalClass: args.temporalClass ?? "structural",
257
- validFrom: args.validFrom ?? now,
258
- validUntil: args.validUntil ?? null
259
- };
324
+ const metadata = readMetadata(args.metadata);
325
+ const properties = neo4jEdgeProperties(args, edgeType, metadata, now);
260
326
  const result = await runWriteTransaction(
261
327
  `
262
328
  MATCH (from {globalId: $fromGlobalId})
@@ -273,76 +339,9 @@ var createEdge = internalAction({
273
339
  }
274
340
  );
275
341
  if (result.length === 0) {
276
- await queueEdgeRetry(ctx, {
277
- globalId: args.globalId,
278
- operation: "upsert",
279
- error: `Source or target node not yet synced to Neo4j (from: ${args.fromGlobalId}, to: ${args.toGlobalId})`
280
- });
281
- if (needsDualWrite(edgeType)) {
282
- try {
283
- await mirrorEdgeToConvex(ctx, {
284
- globalId: args.globalId,
285
- fromGlobalId: args.fromGlobalId,
286
- toGlobalId: args.toGlobalId,
287
- edgeType,
288
- weight: args.weight,
289
- confidence: args.confidence,
290
- context: args.context,
291
- derivationType: args.derivationType,
292
- createdBy: args.createdBy,
293
- topicId: args.topicId,
294
- fromLayer: args.fromLayer,
295
- toLayer: args.toLayer,
296
- fromNodeType: args.fromNodeType,
297
- toNodeType: args.toNodeType,
298
- reasoningMethod: args.reasoningMethod,
299
- logicalRole: args.logicalRole,
300
- temporalClass: args.temporalClass,
301
- validFrom: args.validFrom,
302
- validUntil: args.validUntil
303
- });
304
- } catch {
305
- }
306
- }
307
- return {
308
- success: false,
309
- globalId: args.globalId,
310
- edgeType,
311
- queuedForRetry: true,
312
- reason: "nodes_not_synced"
313
- };
314
- }
315
- if (needsDualWrite(edgeType)) {
316
- try {
317
- await mirrorEdgeToConvex(ctx, {
318
- globalId: args.globalId,
319
- fromGlobalId: args.fromGlobalId,
320
- toGlobalId: args.toGlobalId,
321
- edgeType,
322
- weight: args.weight,
323
- confidence: args.confidence,
324
- context: args.context,
325
- derivationType: args.derivationType,
326
- createdBy: args.createdBy,
327
- topicId: args.topicId,
328
- fromLayer: args.fromLayer,
329
- toLayer: args.toLayer,
330
- fromNodeType: args.fromNodeType,
331
- toNodeType: args.toNodeType,
332
- reasoningMethod: args.reasoningMethod,
333
- logicalRole: args.logicalRole,
334
- temporalClass: args.temporalClass,
335
- validFrom: args.validFrom,
336
- validUntil: args.validUntil
337
- });
338
- } catch (error) {
339
- await queueEdgeRetry(ctx, {
340
- globalId: args.globalId,
341
- operation: "upsert",
342
- error: `Convex mirror failed: ${error instanceof Error ? error.message : "Unknown error"}`
343
- });
344
- }
342
+ return handleMissingNeo4jEndpoints(ctx, args, edgeType);
345
343
  }
344
+ await mirrorCreatedEdgeIfNeeded(ctx, args, edgeType);
346
345
  return {
347
346
  success: true,
348
347
  globalId: args.globalId,
@@ -392,9 +391,15 @@ var updateEdge = internalAction({
392
391
  throw new Error("[Neo4j Edge API] Neo4j not configured");
393
392
  }
394
393
  const updates = { updatedAt: Date.now() };
395
- if (args.weight !== void 0) updates.weight = args.weight;
396
- if (args.confidence !== void 0) updates.confidence = args.confidence;
397
- if (args.context !== void 0) updates.context = args.context;
394
+ if (args.weight !== void 0) {
395
+ updates.weight = args.weight;
396
+ }
397
+ if (args.confidence !== void 0) {
398
+ updates.confidence = args.confidence;
399
+ }
400
+ if (args.context !== void 0) {
401
+ updates.context = args.context;
402
+ }
398
403
  if (args.derivationType !== void 0) {
399
404
  updates.derivationType = args.derivationType;
400
405
  }
@@ -482,7 +487,13 @@ var retryProjectionByGlobalId = internalAction({
482
487
  error: `[Neo4j Edge API] Edge not found in Neo4j: ${args.globalId}`
483
488
  };
484
489
  }
485
- const edge = result[0];
490
+ const [edge] = result;
491
+ if (!edge) {
492
+ return {
493
+ success: false,
494
+ error: `[Neo4j Edge API] Edge not found in Neo4j: ${args.globalId}`
495
+ };
496
+ }
486
497
  if (!needsDualWrite(edge.edgeType)) {
487
498
  return {
488
499
  success: true,