@grafema/util 0.3.0-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (324) hide show
  1. package/LICENSE +190 -0
  2. package/dist/api/GraphAPI.d.ts +87 -0
  3. package/dist/api/GraphAPI.d.ts.map +1 -0
  4. package/dist/api/GraphAPI.js +212 -0
  5. package/dist/api/GraphAPI.js.map +1 -0
  6. package/dist/api/GuaranteeAPI.d.ts +147 -0
  7. package/dist/api/GuaranteeAPI.d.ts.map +1 -0
  8. package/dist/api/GuaranteeAPI.js +290 -0
  9. package/dist/api/GuaranteeAPI.js.map +1 -0
  10. package/dist/config/ConfigLoader.d.ts +214 -0
  11. package/dist/config/ConfigLoader.d.ts.map +1 -0
  12. package/dist/config/ConfigLoader.js +441 -0
  13. package/dist/config/ConfigLoader.js.map +1 -0
  14. package/dist/config/index.d.ts +6 -0
  15. package/dist/config/index.d.ts.map +1 -0
  16. package/dist/config/index.js +5 -0
  17. package/dist/config/index.js.map +1 -0
  18. package/dist/core/CoverageAnalyzer.d.ts +65 -0
  19. package/dist/core/CoverageAnalyzer.d.ts.map +1 -0
  20. package/dist/core/CoverageAnalyzer.js +199 -0
  21. package/dist/core/CoverageAnalyzer.js.map +1 -0
  22. package/dist/core/FileExplainer.d.ts +101 -0
  23. package/dist/core/FileExplainer.d.ts.map +1 -0
  24. package/dist/core/FileExplainer.js +140 -0
  25. package/dist/core/FileExplainer.js.map +1 -0
  26. package/dist/core/FileOverview.d.ts +124 -0
  27. package/dist/core/FileOverview.d.ts.map +1 -0
  28. package/dist/core/FileOverview.js +279 -0
  29. package/dist/core/FileOverview.js.map +1 -0
  30. package/dist/core/GrafemaUri.d.ts +66 -0
  31. package/dist/core/GrafemaUri.d.ts.map +1 -0
  32. package/dist/core/GrafemaUri.js +191 -0
  33. package/dist/core/GrafemaUri.js.map +1 -0
  34. package/dist/core/GraphBackend.d.ts +158 -0
  35. package/dist/core/GraphBackend.d.ts.map +1 -0
  36. package/dist/core/GraphBackend.js +85 -0
  37. package/dist/core/GraphBackend.js.map +1 -0
  38. package/dist/core/GraphFreshnessChecker.d.ts +33 -0
  39. package/dist/core/GraphFreshnessChecker.d.ts.map +1 -0
  40. package/dist/core/GraphFreshnessChecker.js +104 -0
  41. package/dist/core/GraphFreshnessChecker.js.map +1 -0
  42. package/dist/core/GuaranteeManager.d.ts +254 -0
  43. package/dist/core/GuaranteeManager.d.ts.map +1 -0
  44. package/dist/core/GuaranteeManager.js +447 -0
  45. package/dist/core/GuaranteeManager.js.map +1 -0
  46. package/dist/core/HashUtils.d.ts +24 -0
  47. package/dist/core/HashUtils.d.ts.map +1 -0
  48. package/dist/core/HashUtils.js +46 -0
  49. package/dist/core/HashUtils.js.map +1 -0
  50. package/dist/core/IncrementalReanalyzer.d.ts +33 -0
  51. package/dist/core/IncrementalReanalyzer.d.ts.map +1 -0
  52. package/dist/core/IncrementalReanalyzer.js +67 -0
  53. package/dist/core/IncrementalReanalyzer.js.map +1 -0
  54. package/dist/core/ResourceRegistry.d.ts +17 -0
  55. package/dist/core/ResourceRegistry.d.ts.map +1 -0
  56. package/dist/core/ResourceRegistry.js +32 -0
  57. package/dist/core/ResourceRegistry.js.map +1 -0
  58. package/dist/core/SemanticId.d.ts +159 -0
  59. package/dist/core/SemanticId.d.ts.map +1 -0
  60. package/dist/core/SemanticId.js +291 -0
  61. package/dist/core/SemanticId.js.map +1 -0
  62. package/dist/core/VersionManager.d.ts +166 -0
  63. package/dist/core/VersionManager.d.ts.map +1 -0
  64. package/dist/core/VersionManager.js +239 -0
  65. package/dist/core/VersionManager.js.map +1 -0
  66. package/dist/core/brandNodeInternal.d.ts +14 -0
  67. package/dist/core/brandNodeInternal.d.ts.map +1 -0
  68. package/dist/core/brandNodeInternal.js +4 -0
  69. package/dist/core/brandNodeInternal.js.map +1 -0
  70. package/dist/core/nodes/GuaranteeNode.d.ts +76 -0
  71. package/dist/core/nodes/GuaranteeNode.d.ts.map +1 -0
  72. package/dist/core/nodes/GuaranteeNode.js +118 -0
  73. package/dist/core/nodes/GuaranteeNode.js.map +1 -0
  74. package/dist/core/nodes/IssueNode.d.ts +73 -0
  75. package/dist/core/nodes/IssueNode.d.ts.map +1 -0
  76. package/dist/core/nodes/IssueNode.js +130 -0
  77. package/dist/core/nodes/IssueNode.js.map +1 -0
  78. package/dist/core/nodes/NodeKind.d.ts +104 -0
  79. package/dist/core/nodes/NodeKind.d.ts.map +1 -0
  80. package/dist/core/nodes/NodeKind.js +166 -0
  81. package/dist/core/nodes/NodeKind.js.map +1 -0
  82. package/dist/diagnostics/DiagnosticCollector.d.ts +103 -0
  83. package/dist/diagnostics/DiagnosticCollector.d.ts.map +1 -0
  84. package/dist/diagnostics/DiagnosticCollector.js +133 -0
  85. package/dist/diagnostics/DiagnosticCollector.js.map +1 -0
  86. package/dist/diagnostics/DiagnosticReporter.d.ts +122 -0
  87. package/dist/diagnostics/DiagnosticReporter.d.ts.map +1 -0
  88. package/dist/diagnostics/DiagnosticReporter.js +300 -0
  89. package/dist/diagnostics/DiagnosticReporter.js.map +1 -0
  90. package/dist/diagnostics/DiagnosticWriter.d.ts +31 -0
  91. package/dist/diagnostics/DiagnosticWriter.d.ts.map +1 -0
  92. package/dist/diagnostics/DiagnosticWriter.js +44 -0
  93. package/dist/diagnostics/DiagnosticWriter.js.map +1 -0
  94. package/dist/diagnostics/categories.d.ts +57 -0
  95. package/dist/diagnostics/categories.d.ts.map +1 -0
  96. package/dist/diagnostics/categories.js +71 -0
  97. package/dist/diagnostics/categories.js.map +1 -0
  98. package/dist/diagnostics/index.d.ts +17 -0
  99. package/dist/diagnostics/index.d.ts.map +1 -0
  100. package/dist/diagnostics/index.js +15 -0
  101. package/dist/diagnostics/index.js.map +1 -0
  102. package/dist/errors/GrafemaError.d.ts +200 -0
  103. package/dist/errors/GrafemaError.d.ts.map +1 -0
  104. package/dist/errors/GrafemaError.js +209 -0
  105. package/dist/errors/GrafemaError.js.map +1 -0
  106. package/dist/index.d.ts +75 -0
  107. package/dist/index.d.ts.map +1 -0
  108. package/dist/index.js +76 -0
  109. package/dist/index.js.map +1 -0
  110. package/dist/instructions/index.d.ts +8 -0
  111. package/dist/instructions/index.d.ts.map +1 -0
  112. package/dist/instructions/index.js +20 -0
  113. package/dist/instructions/index.js.map +1 -0
  114. package/dist/instructions/onboarding.md +133 -0
  115. package/dist/knowledge/KnowledgeBase.d.ts +113 -0
  116. package/dist/knowledge/KnowledgeBase.d.ts.map +1 -0
  117. package/dist/knowledge/KnowledgeBase.js +420 -0
  118. package/dist/knowledge/KnowledgeBase.js.map +1 -0
  119. package/dist/knowledge/SemanticAddressResolver.d.ts +59 -0
  120. package/dist/knowledge/SemanticAddressResolver.d.ts.map +1 -0
  121. package/dist/knowledge/SemanticAddressResolver.js +160 -0
  122. package/dist/knowledge/SemanticAddressResolver.js.map +1 -0
  123. package/dist/knowledge/git-ingest.d.ts +58 -0
  124. package/dist/knowledge/git-ingest.d.ts.map +1 -0
  125. package/dist/knowledge/git-ingest.js +301 -0
  126. package/dist/knowledge/git-ingest.js.map +1 -0
  127. package/dist/knowledge/git-queries.d.ts +86 -0
  128. package/dist/knowledge/git-queries.d.ts.map +1 -0
  129. package/dist/knowledge/git-queries.js +177 -0
  130. package/dist/knowledge/git-queries.js.map +1 -0
  131. package/dist/knowledge/index.d.ts +14 -0
  132. package/dist/knowledge/index.d.ts.map +1 -0
  133. package/dist/knowledge/index.js +10 -0
  134. package/dist/knowledge/index.js.map +1 -0
  135. package/dist/knowledge/parser.d.ts +39 -0
  136. package/dist/knowledge/parser.d.ts.map +1 -0
  137. package/dist/knowledge/parser.js +292 -0
  138. package/dist/knowledge/parser.js.map +1 -0
  139. package/dist/knowledge/types.d.ts +133 -0
  140. package/dist/knowledge/types.d.ts.map +1 -0
  141. package/dist/knowledge/types.js +8 -0
  142. package/dist/knowledge/types.js.map +1 -0
  143. package/dist/logging/Logger.d.ts +98 -0
  144. package/dist/logging/Logger.d.ts.map +1 -0
  145. package/dist/logging/Logger.js +274 -0
  146. package/dist/logging/Logger.js.map +1 -0
  147. package/dist/notation/archetypes.d.ts +36 -0
  148. package/dist/notation/archetypes.d.ts.map +1 -0
  149. package/dist/notation/archetypes.js +173 -0
  150. package/dist/notation/archetypes.js.map +1 -0
  151. package/dist/notation/fold.d.ts +25 -0
  152. package/dist/notation/fold.d.ts.map +1 -0
  153. package/dist/notation/fold.js +598 -0
  154. package/dist/notation/fold.js.map +1 -0
  155. package/dist/notation/index.d.ts +18 -0
  156. package/dist/notation/index.d.ts.map +1 -0
  157. package/dist/notation/index.js +16 -0
  158. package/dist/notation/index.js.map +1 -0
  159. package/dist/notation/lodExtractor.d.ts +32 -0
  160. package/dist/notation/lodExtractor.d.ts.map +1 -0
  161. package/dist/notation/lodExtractor.js +149 -0
  162. package/dist/notation/lodExtractor.js.map +1 -0
  163. package/dist/notation/nameShortener.d.ts +22 -0
  164. package/dist/notation/nameShortener.d.ts.map +1 -0
  165. package/dist/notation/nameShortener.js +24 -0
  166. package/dist/notation/nameShortener.js.map +1 -0
  167. package/dist/notation/perspectives.d.ts +11 -0
  168. package/dist/notation/perspectives.d.ts.map +1 -0
  169. package/dist/notation/perspectives.js +16 -0
  170. package/dist/notation/perspectives.js.map +1 -0
  171. package/dist/notation/renderer.d.ts +31 -0
  172. package/dist/notation/renderer.d.ts.map +1 -0
  173. package/dist/notation/renderer.js +315 -0
  174. package/dist/notation/renderer.js.map +1 -0
  175. package/dist/notation/traceRenderer.d.ts +39 -0
  176. package/dist/notation/traceRenderer.d.ts.map +1 -0
  177. package/dist/notation/traceRenderer.js +358 -0
  178. package/dist/notation/traceRenderer.js.map +1 -0
  179. package/dist/notation/types.d.ts +66 -0
  180. package/dist/notation/types.d.ts.map +1 -0
  181. package/dist/notation/types.js +10 -0
  182. package/dist/notation/types.js.map +1 -0
  183. package/dist/queries/NodeContext.d.ts +81 -0
  184. package/dist/queries/NodeContext.d.ts.map +1 -0
  185. package/dist/queries/NodeContext.js +196 -0
  186. package/dist/queries/NodeContext.js.map +1 -0
  187. package/dist/queries/findCallsInFunction.d.ts +62 -0
  188. package/dist/queries/findCallsInFunction.d.ts.map +1 -0
  189. package/dist/queries/findCallsInFunction.js +169 -0
  190. package/dist/queries/findCallsInFunction.js.map +1 -0
  191. package/dist/queries/findContainingFunction.d.ts +57 -0
  192. package/dist/queries/findContainingFunction.d.ts.map +1 -0
  193. package/dist/queries/findContainingFunction.js +91 -0
  194. package/dist/queries/findContainingFunction.js.map +1 -0
  195. package/dist/queries/index.d.ts +18 -0
  196. package/dist/queries/index.d.ts.map +1 -0
  197. package/dist/queries/index.js +14 -0
  198. package/dist/queries/index.js.map +1 -0
  199. package/dist/queries/traceDataflow.d.ts +65 -0
  200. package/dist/queries/traceDataflow.d.ts.map +1 -0
  201. package/dist/queries/traceDataflow.js +754 -0
  202. package/dist/queries/traceDataflow.js.map +1 -0
  203. package/dist/queries/traceValues.d.ts +70 -0
  204. package/dist/queries/traceValues.d.ts.map +1 -0
  205. package/dist/queries/traceValues.js +373 -0
  206. package/dist/queries/traceValues.js.map +1 -0
  207. package/dist/queries/types.d.ts +166 -0
  208. package/dist/queries/types.d.ts.map +1 -0
  209. package/dist/queries/types.js +10 -0
  210. package/dist/queries/types.js.map +1 -0
  211. package/dist/schema/GraphSchemaExtractor.d.ts +53 -0
  212. package/dist/schema/GraphSchemaExtractor.d.ts.map +1 -0
  213. package/dist/schema/GraphSchemaExtractor.js +125 -0
  214. package/dist/schema/GraphSchemaExtractor.js.map +1 -0
  215. package/dist/schema/InterfaceSchemaExtractor.d.ts +73 -0
  216. package/dist/schema/InterfaceSchemaExtractor.d.ts.map +1 -0
  217. package/dist/schema/InterfaceSchemaExtractor.js +113 -0
  218. package/dist/schema/InterfaceSchemaExtractor.js.map +1 -0
  219. package/dist/schema/index.d.ts +5 -0
  220. package/dist/schema/index.d.ts.map +1 -0
  221. package/dist/schema/index.js +3 -0
  222. package/dist/schema/index.js.map +1 -0
  223. package/dist/storage/backends/RFDBServerBackend.d.ts +356 -0
  224. package/dist/storage/backends/RFDBServerBackend.d.ts.map +1 -0
  225. package/dist/storage/backends/RFDBServerBackend.js +748 -0
  226. package/dist/storage/backends/RFDBServerBackend.js.map +1 -0
  227. package/dist/storage/backends/typeValidation.d.ts +47 -0
  228. package/dist/storage/backends/typeValidation.d.ts.map +1 -0
  229. package/dist/storage/backends/typeValidation.js +141 -0
  230. package/dist/storage/backends/typeValidation.js.map +1 -0
  231. package/dist/utils/findRfdbBinary.d.ts +67 -0
  232. package/dist/utils/findRfdbBinary.d.ts.map +1 -0
  233. package/dist/utils/findRfdbBinary.js +261 -0
  234. package/dist/utils/findRfdbBinary.js.map +1 -0
  235. package/dist/utils/lazyDownload.d.ts +43 -0
  236. package/dist/utils/lazyDownload.d.ts.map +1 -0
  237. package/dist/utils/lazyDownload.js +175 -0
  238. package/dist/utils/lazyDownload.js.map +1 -0
  239. package/dist/utils/moduleResolution.d.ts +134 -0
  240. package/dist/utils/moduleResolution.d.ts.map +1 -0
  241. package/dist/utils/moduleResolution.js +189 -0
  242. package/dist/utils/moduleResolution.js.map +1 -0
  243. package/dist/utils/resolveNodeFile.d.ts +13 -0
  244. package/dist/utils/resolveNodeFile.d.ts.map +1 -0
  245. package/dist/utils/resolveNodeFile.js +18 -0
  246. package/dist/utils/resolveNodeFile.js.map +1 -0
  247. package/dist/utils/startRfdbServer.d.ts +63 -0
  248. package/dist/utils/startRfdbServer.d.ts.map +1 -0
  249. package/dist/utils/startRfdbServer.js +142 -0
  250. package/dist/utils/startRfdbServer.js.map +1 -0
  251. package/dist/validation/PathValidator.d.ts +80 -0
  252. package/dist/validation/PathValidator.d.ts.map +1 -0
  253. package/dist/validation/PathValidator.js +252 -0
  254. package/dist/validation/PathValidator.js.map +1 -0
  255. package/dist/version.d.ts +11 -0
  256. package/dist/version.d.ts.map +1 -0
  257. package/dist/version.js +26 -0
  258. package/dist/version.js.map +1 -0
  259. package/package.json +50 -0
  260. package/src/api/GraphAPI.ts +307 -0
  261. package/src/api/GuaranteeAPI.ts +402 -0
  262. package/src/config/ConfigLoader.ts +653 -0
  263. package/src/config/index.ts +13 -0
  264. package/src/core/CoverageAnalyzer.ts +243 -0
  265. package/src/core/FileExplainer.ts +179 -0
  266. package/src/core/FileOverview.ts +397 -0
  267. package/src/core/GrafemaUri.ts +216 -0
  268. package/src/core/GraphBackend.ts +266 -0
  269. package/src/core/GraphFreshnessChecker.ts +145 -0
  270. package/src/core/GuaranteeManager.ts +684 -0
  271. package/src/core/HashUtils.ts +48 -0
  272. package/src/core/IncrementalReanalyzer.ts +106 -0
  273. package/src/core/ResourceRegistry.ts +39 -0
  274. package/src/core/SemanticId.ts +423 -0
  275. package/src/core/VersionManager.ts +405 -0
  276. package/src/core/brandNodeInternal.ts +16 -0
  277. package/src/core/nodes/GuaranteeNode.ts +162 -0
  278. package/src/core/nodes/IssueNode.ts +177 -0
  279. package/src/core/nodes/NodeKind.ts +192 -0
  280. package/src/diagnostics/DiagnosticCollector.ts +170 -0
  281. package/src/diagnostics/DiagnosticReporter.ts +395 -0
  282. package/src/diagnostics/DiagnosticWriter.ts +50 -0
  283. package/src/diagnostics/categories.ts +104 -0
  284. package/src/diagnostics/index.ts +30 -0
  285. package/src/errors/GrafemaError.ts +297 -0
  286. package/src/index.ts +261 -0
  287. package/src/instructions/index.ts +21 -0
  288. package/src/instructions/onboarding.md +133 -0
  289. package/src/knowledge/KnowledgeBase.ts +486 -0
  290. package/src/knowledge/SemanticAddressResolver.ts +191 -0
  291. package/src/knowledge/git-ingest.ts +402 -0
  292. package/src/knowledge/git-queries.ts +269 -0
  293. package/src/knowledge/index.ts +29 -0
  294. package/src/knowledge/parser.ts +294 -0
  295. package/src/knowledge/types.ts +146 -0
  296. package/src/logging/Logger.ts +303 -0
  297. package/src/notation/archetypes.ts +189 -0
  298. package/src/notation/fold.ts +696 -0
  299. package/src/notation/index.ts +27 -0
  300. package/src/notation/lodExtractor.ts +177 -0
  301. package/src/notation/nameShortener.ts +24 -0
  302. package/src/notation/perspectives.ts +18 -0
  303. package/src/notation/renderer.ts +394 -0
  304. package/src/notation/traceRenderer.ts +458 -0
  305. package/src/notation/types.ts +79 -0
  306. package/src/queries/NodeContext.ts +280 -0
  307. package/src/queries/findCallsInFunction.ts +249 -0
  308. package/src/queries/findContainingFunction.ts +124 -0
  309. package/src/queries/index.ts +44 -0
  310. package/src/queries/traceDataflow.ts +838 -0
  311. package/src/queries/traceValues.ts +531 -0
  312. package/src/queries/types.ts +191 -0
  313. package/src/schema/GraphSchemaExtractor.ts +177 -0
  314. package/src/schema/InterfaceSchemaExtractor.ts +173 -0
  315. package/src/schema/index.ts +5 -0
  316. package/src/storage/backends/RFDBServerBackend.ts +895 -0
  317. package/src/storage/backends/typeValidation.ts +154 -0
  318. package/src/utils/findRfdbBinary.ts +288 -0
  319. package/src/utils/lazyDownload.ts +206 -0
  320. package/src/utils/moduleResolution.ts +271 -0
  321. package/src/utils/resolveNodeFile.ts +18 -0
  322. package/src/utils/startRfdbServer.ts +197 -0
  323. package/src/validation/PathValidator.ts +334 -0
  324. package/src/version.ts +28 -0
@@ -0,0 +1,895 @@
1
+ /**
2
+ * RFDBServerBackend - Graph backend using RFDB server via Unix socket
3
+ *
4
+ * Replaces ReginaFlowBackend's direct NAPI binding with socket-based
5
+ * communication to a shared RFDB server. This allows multiple processes
6
+ * (MCP server, analysis workers) to share the same graph database.
7
+ *
8
+ * Socket path defaults to `{dbPath}/../rfdb.sock` (e.g., .grafema/rfdb.sock),
9
+ * ensuring each project has its own socket and avoiding conflicts when
10
+ * multiple MCP instances run simultaneously.
11
+ *
12
+ * Usage:
13
+ * const backend = new RFDBServerBackend({
14
+ * dbPath: '/project/.grafema/graph.rfdb' // socket will be /project/.grafema/rfdb.sock
15
+ * });
16
+ * await backend.connect();
17
+ * await backend.addNodes([...]);
18
+ * await backend.flush();
19
+ */
20
+
21
+ import { RFDBClient, type BatchHandle } from '@grafema/rfdb-client';
22
+ import type { ChildProcess } from 'child_process';
23
+ import { join, dirname } from 'path';
24
+
25
+ import type { WireNode, WireEdge, FieldDeclaration, CommitDelta, AttrQuery as RFDBAttrQuery, DatalogExplainResult, ServerStats, CypherResult } from '@grafema/types';
26
+ import type { NodeType, EdgeType } from '@grafema/types';
27
+ import { startRfdbServer } from '../../utils/startRfdbServer.js';
28
+ import { GRAFEMA_VERSION, getSchemaVersion } from '../../version.js';
29
+ import type { BaseNodeRecord, EdgeRecord, AnyBrandedNode } from '@grafema/types';
30
+ import { brandNodeInternal } from '../../core/brandNodeInternal.js';
31
+ import type { AttrQuery, GraphStats, GraphExport } from '../../core/GraphBackend.js';
32
+
33
+ /**
34
+ * Options for RFDBServerBackend
35
+ */
36
+ export interface RFDBServerBackendOptions {
37
+ socketPath?: string;
38
+ dbPath?: string;
39
+ /**
40
+ * If true, automatically start the server if not running.
41
+ * If false, require explicit `grafema server start`.
42
+ * Default: true (for backwards compatibility)
43
+ */
44
+ autoStart?: boolean;
45
+ /**
46
+ * If true, suppress all console output (for clean CLI progress).
47
+ * Default: false
48
+ */
49
+ silent?: boolean;
50
+ /**
51
+ * Name identifying this client in server logs (e.g. 'cli', 'mcp', 'core').
52
+ * Default: 'core'
53
+ */
54
+ clientName?: string;
55
+ }
56
+
57
+ /**
58
+ * Input node format (flexible)
59
+ */
60
+ export interface InputNode {
61
+ id: string;
62
+ type?: string;
63
+ nodeType?: string;
64
+ node_type?: string;
65
+ name?: string;
66
+ file?: string;
67
+ exported?: boolean;
68
+ [key: string]: unknown;
69
+ }
70
+
71
+ /**
72
+ * Input edge format (flexible)
73
+ */
74
+ export interface InputEdge {
75
+ src: string;
76
+ dst: string;
77
+ type?: string;
78
+ edgeType?: string;
79
+ edge_type?: string;
80
+ [key: string]: unknown;
81
+ }
82
+
83
+ /**
84
+ * Query for finding nodes
85
+ */
86
+ export interface NodeQuery {
87
+ nodeType?: NodeType;
88
+ type?: NodeType;
89
+ name?: string;
90
+ file?: string;
91
+ substringMatch?: boolean;
92
+ }
93
+
94
+ /**
95
+ * Backend statistics
96
+ */
97
+ export interface BackendStats extends GraphStats {
98
+ nodesByType: Record<string, number>;
99
+ edgesByType: Record<string, number>;
100
+ }
101
+
102
+ export class RFDBServerBackend {
103
+ readonly socketPath: string;
104
+ readonly dbPath: string | undefined;
105
+ private readonly autoStart: boolean;
106
+ private readonly silent: boolean;
107
+ private readonly _clientName: string;
108
+ private client: RFDBClient | null;
109
+ private serverProcess: ChildProcess | null;
110
+ connected: boolean; // Public for compatibility
111
+ private protocolVersion: number = 2; // Negotiated protocol version
112
+ private edgeTypes: Set<string>;
113
+ private _cachedNodeCounts: Record<string, number> | undefined;
114
+ private _cachedEdgeCounts: Record<string, number> | undefined;
115
+
116
+ constructor(options: RFDBServerBackendOptions = {}) {
117
+ this.dbPath = options.dbPath;
118
+ this.autoStart = options.autoStart ?? true; // Default true for backwards compat
119
+ this.silent = options.silent ?? false;
120
+ this._clientName = options.clientName ?? 'core';
121
+ // Default socket path: next to the database in .grafema folder
122
+ // This ensures each project has its own socket, avoiding conflicts
123
+ if (options.socketPath) {
124
+ this.socketPath = options.socketPath;
125
+ } else if (this.dbPath) {
126
+ this.socketPath = join(dirname(this.dbPath), 'rfdb.sock');
127
+ } else {
128
+ this.socketPath = '/tmp/rfdb.sock'; // fallback, not recommended
129
+ }
130
+ this.client = null;
131
+ this.serverProcess = null;
132
+ this.connected = false;
133
+ this.edgeTypes = new Set();
134
+ }
135
+
136
+ /**
137
+ * Log message if not in silent mode.
138
+ */
139
+ private log(message: string): void {
140
+ if (!this.silent) {
141
+ console.error(message);
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Log error (always shown, even in silent mode).
147
+ */
148
+ private logError(message: string, error?: unknown): void {
149
+ console.error(message, error ?? '');
150
+ }
151
+
152
+ /**
153
+ * Connect to RFDB server.
154
+ * If autoStart is true (default), starts the server if not running.
155
+ * If autoStart is false, requires explicit `grafema server start`.
156
+ */
157
+ async connect(): Promise<void> {
158
+ if (this.connected) return;
159
+
160
+ // Try to connect first
161
+ this.client = new RFDBClient(this.socketPath, this._clientName);
162
+
163
+ // Attach error handler to prevent unhandled 'error' events
164
+ // This is important for stale sockets (socket file exists but server is dead)
165
+ this.client.on('error', (err: Error) => {
166
+ this.logError('[RFDBServerBackend] Client error:', err.message);
167
+ });
168
+
169
+ try {
170
+ await this.client.connect();
171
+ // Verify server is responsive
172
+ await this.client.ping();
173
+ this.connected = true;
174
+ await this._negotiateProtocol();
175
+ this.log(`[RFDBServerBackend] Connected to RFDB server at ${this.socketPath} (protocol v${this.protocolVersion})`);
176
+ return;
177
+ } catch {
178
+ // Server not running or stale socket
179
+ if (!this.autoStart) {
180
+ throw new Error(
181
+ `RFDB server not running at ${this.socketPath}\n` +
182
+ `Start the server first: grafema server start`
183
+ );
184
+ }
185
+ this.log(`[RFDBServerBackend] RFDB server not running, starting...`);
186
+ }
187
+
188
+ // Start the server (only if autoStart is true)
189
+ await this._startServer();
190
+
191
+ // Connect again with fresh client
192
+ this.client = new RFDBClient(this.socketPath, this._clientName);
193
+ this.client.on('error', (err: Error) => {
194
+ this.logError('[RFDBServerBackend] Client error:', err.message);
195
+ });
196
+ await this.client.connect();
197
+ await this.client.ping();
198
+ this.connected = true;
199
+ await this._negotiateProtocol();
200
+ this.log(`[RFDBServerBackend] Connected to RFDB server at ${this.socketPath} (protocol v${this.protocolVersion})`);
201
+ }
202
+
203
+ /**
204
+ * Alias for connect()
205
+ */
206
+ async initialize(): Promise<void> {
207
+ return this.connect();
208
+ }
209
+
210
+ /**
211
+ * Start RFDB server process using shared utility.
212
+ */
213
+ private async _startServer(): Promise<void> {
214
+ if (!this.dbPath) {
215
+ throw new Error('dbPath required to start RFDB server');
216
+ }
217
+
218
+ await startRfdbServer({
219
+ dbPath: this.dbPath,
220
+ socketPath: this.socketPath,
221
+ pidPath: join(dirname(this.socketPath), 'rfdb.pid'),
222
+ waitTimeoutMs: 5000,
223
+ logger: this.silent ? undefined : { debug: (m: string) => this.log(m) },
224
+ });
225
+ }
226
+
227
+ /**
228
+ * Negotiate protocol version with server.
229
+ * Requests v3 (semantic IDs), falls back to v2 if server doesn't support it.
230
+ * Called after ping() confirmed connectivity, so failures here indicate
231
+ * the server doesn't support hello/v3, not network issues.
232
+ */
233
+ private async _negotiateProtocol(): Promise<void> {
234
+ if (!this.client) return;
235
+ try {
236
+ const hello = await this.client.hello(3);
237
+ this.protocolVersion = hello.protocolVersion;
238
+ this._checkServerVersion(hello.serverVersion);
239
+ } catch {
240
+ // Server predates hello command or doesn't support v3 — safe v2 fallback
241
+ this.protocolVersion = 2;
242
+ this.log('[RFDBServerBackend] WARNING: Server does not support version negotiation. Consider updating rfdb-server.');
243
+ }
244
+ }
245
+
246
+ /**
247
+ * Validate server version against expected client version.
248
+ * Warns on mismatch but never fails — version differences are informational.
249
+ */
250
+ private _checkServerVersion(serverVersion: string): void {
251
+ if (!serverVersion) return;
252
+ const expected = getSchemaVersion(GRAFEMA_VERSION);
253
+ const actual = getSchemaVersion(serverVersion);
254
+ if (actual !== expected) {
255
+ this.log(
256
+ `[RFDBServerBackend] WARNING: rfdb-server version mismatch — ` +
257
+ `server v${serverVersion}, expected v${GRAFEMA_VERSION}. ` +
258
+ `Update with: grafema server restart`
259
+ );
260
+ }
261
+ }
262
+
263
+ /**
264
+ * Close client connection. Server continues running to serve other clients.
265
+ */
266
+ async close(): Promise<void> {
267
+ // Request server flush before disconnecting
268
+ if (this.client) {
269
+ try {
270
+ await this.client.flush();
271
+ } catch {
272
+ // Ignore flush errors on close - best effort
273
+ }
274
+ await this.client.close();
275
+ this.client = null;
276
+ }
277
+ this.connected = false;
278
+
279
+ // NOTE: We intentionally do NOT kill the server process.
280
+ // The server continues running to serve other clients (MCP, other CLI invocations).
281
+ // This is by design for multi-client architecture.
282
+ // Server lifecycle is managed separately (system process, or manual grafema server stop).
283
+ this.serverProcess = null;
284
+ }
285
+
286
+ /**
287
+ * Clear the database
288
+ */
289
+ async clear(): Promise<void> {
290
+ if (!this.client) throw new Error('Not connected');
291
+ await this.client.clear();
292
+ }
293
+
294
+ /**
295
+ * Flush data to disk
296
+ */
297
+ async flush(): Promise<void> {
298
+ if (!this.client) throw new Error('Not connected');
299
+ await this.client.flush();
300
+ }
301
+
302
+ /**
303
+ * Declare metadata fields for server-side indexing.
304
+ * Persisted in metadata.json — survives database reopen.
305
+ */
306
+ async declareFields(fields: FieldDeclaration[]): Promise<number> {
307
+ if (!this.client) throw new Error('Not connected');
308
+ return this.client.declareFields(fields);
309
+ }
310
+
311
+ // ===========================================================================
312
+ // Node Operations
313
+ // ===========================================================================
314
+
315
+ /**
316
+ * Add a single node
317
+ */
318
+ async addNode(node: InputNode): Promise<void> {
319
+ return this.addNodes([node]);
320
+ }
321
+
322
+ /**
323
+ * Add multiple nodes
324
+ */
325
+ async addNodes(nodes: InputNode[]): Promise<void> {
326
+ if (!this.client) throw new Error('Not connected');
327
+ if (!nodes.length) return;
328
+
329
+ const useV3 = this.protocolVersion >= 3;
330
+ const wireNodes: WireNode[] = nodes.map(n => {
331
+ // Extract metadata from node
332
+ const { id, type, nodeType, node_type, name, file, exported, ...rest } = n;
333
+
334
+ const wire: WireNode = {
335
+ id: String(id),
336
+ nodeType: (nodeType || node_type || type || 'UNKNOWN') as NodeType,
337
+ name: name || '',
338
+ file: file || '',
339
+ exported: exported || false,
340
+ metadata: useV3
341
+ ? JSON.stringify(rest)
342
+ : JSON.stringify({ originalId: String(id), ...rest }),
343
+ };
344
+ if (useV3) {
345
+ wire.semanticId = String(id);
346
+ }
347
+ return wire;
348
+ });
349
+
350
+ await this.client.addNodes(wireNodes);
351
+ }
352
+
353
+ /**
354
+ * Add a single edge
355
+ */
356
+ async addEdge(edge: InputEdge): Promise<void> {
357
+ return this.addEdges([edge]);
358
+ }
359
+
360
+ /**
361
+ * Add multiple edges
362
+ */
363
+ async addEdges(edges: InputEdge[], skipValidation = false): Promise<void> {
364
+ if (!this.client) throw new Error('Not connected');
365
+ if (!edges.length) return;
366
+
367
+ // Track edge types
368
+ for (const e of edges) {
369
+ const edgeType = e.edgeType || e.edge_type || e.etype || e.type;
370
+ if (typeof edgeType === 'string') this.edgeTypes.add(edgeType);
371
+ }
372
+
373
+ const useV3 = this.protocolVersion >= 3;
374
+ const wireEdges: WireEdge[] = edges.map(e => {
375
+ const { src, dst, type, edgeType, edge_type, etype, metadata, ...rest } = e;
376
+
377
+ // Flatten metadata: spread both edge-level properties and nested metadata
378
+ const flatMetadata = useV3
379
+ ? { ...rest, ...(typeof metadata === 'object' && metadata !== null ? metadata : {}) }
380
+ : { _origSrc: String(src), _origDst: String(dst), ...rest, ...(typeof metadata === 'object' && metadata !== null ? metadata : {}) };
381
+
382
+ return {
383
+ src: String(src),
384
+ dst: String(dst),
385
+ edgeType: (edgeType || edge_type || etype || type || 'UNKNOWN') as EdgeType,
386
+ metadata: JSON.stringify(flatMetadata),
387
+ };
388
+ });
389
+
390
+ await this.client.addEdges(wireEdges, skipValidation);
391
+ }
392
+
393
+ /**
394
+ * Get a node by ID
395
+ */
396
+ async getNode(id: string): Promise<BaseNodeRecord | null> {
397
+ if (!this.client) throw new Error('Not connected');
398
+ const node = await this.client.getNode(String(id));
399
+ if (!node) return null;
400
+
401
+ return this._parseNode(node);
402
+ }
403
+
404
+ /**
405
+ * Check if node exists
406
+ */
407
+ async nodeExists(id: string): Promise<boolean> {
408
+ if (!this.client) throw new Error('Not connected');
409
+ return this.client.nodeExists(id);
410
+ }
411
+
412
+ /**
413
+ * Delete a node
414
+ */
415
+ async deleteNode(id: string): Promise<void> {
416
+ if (!this.client) throw new Error('Not connected');
417
+ await this.client.deleteNode(id);
418
+ }
419
+
420
+ /**
421
+ * Find nodes by attributes
422
+ */
423
+ async findByAttr(query: AttrQuery): Promise<string[]> {
424
+ if (!this.client) throw new Error('Not connected');
425
+ return this.client.findByAttr(query);
426
+ }
427
+
428
+ /**
429
+ * Parse a node from wire format to JS format
430
+ */
431
+ private _parseNode(wireNode: WireNode): AnyBrandedNode {
432
+ const metadata: Record<string, unknown> = wireNode.metadata ? JSON.parse(wireNode.metadata) : {};
433
+
434
+ // Parse nested JSON strings
435
+ for (const [key, value] of Object.entries(metadata)) {
436
+ if (typeof value === 'string' && (value.startsWith('[') || value.startsWith('{'))) {
437
+ try {
438
+ metadata[key] = JSON.parse(value);
439
+ } catch {
440
+ // Not JSON, keep as string
441
+ }
442
+ }
443
+ }
444
+
445
+ // Prefer metadata.semanticId (original v1 format preserved by RFDB server),
446
+ // then v3 semanticId, then v2 originalId metadata hack, then raw id
447
+ const humanId = (metadata.semanticId as string) || wireNode.semanticId || (metadata.originalId as string) || wireNode.id;
448
+
449
+ // Exclude standard fields from metadata to prevent overwriting wireNode values
450
+ // REG-325: Metadata spread was overwriting name with LITERAL node data
451
+ const {
452
+ id: _id,
453
+ type: _type,
454
+ name: _name,
455
+ file: _file,
456
+ exported: _exported,
457
+ nodeType: _nodeType,
458
+ originalId: _originalId, // Already extracted above
459
+ semanticId: _semanticId, // Exclude from safeMetadata (used for humanId above)
460
+ ...safeMetadata
461
+ } = metadata;
462
+
463
+ const parsed = {
464
+ id: humanId,
465
+ type: wireNode.nodeType,
466
+ name: wireNode.name,
467
+ file: wireNode.file,
468
+ exported: wireNode.exported,
469
+ ...safeMetadata,
470
+ };
471
+
472
+ // Re-brand nodes coming from database
473
+ return brandNodeInternal(parsed);
474
+ }
475
+
476
+ /**
477
+ * Parse an edge from wire format to EdgeRecord
478
+ */
479
+ private _parseEdge(wireEdge: WireEdge): EdgeRecord {
480
+ const meta: Record<string, unknown> = wireEdge.metadata ? JSON.parse(wireEdge.metadata) : {};
481
+ // v3: server resolves src/dst to semantic IDs, use directly
482
+ // v2: fall back to _origSrc/_origDst metadata hack
483
+ const { _origSrc, _origDst, ...rest } = meta;
484
+ const src = this.protocolVersion >= 3
485
+ ? wireEdge.src
486
+ : (_origSrc as string) || wireEdge.src;
487
+ const dst = this.protocolVersion >= 3
488
+ ? wireEdge.dst
489
+ : (_origDst as string) || wireEdge.dst;
490
+ return {
491
+ src,
492
+ dst,
493
+ type: wireEdge.edgeType,
494
+ metadata: Object.keys(rest).length > 0 ? rest : undefined,
495
+ };
496
+ }
497
+
498
+ /**
499
+ * Async generator for querying nodes
500
+ */
501
+ async *queryNodes(query: NodeQuery): AsyncGenerator<BaseNodeRecord, void, unknown> {
502
+ if (!this.client) throw new Error('Not connected');
503
+
504
+ // Build query for server
505
+ const serverQuery: NodeQuery = {};
506
+ if (query.nodeType) serverQuery.nodeType = query.nodeType;
507
+ if (query.type) serverQuery.nodeType = query.type;
508
+ if (query.name) serverQuery.name = query.name;
509
+ if (query.file) serverQuery.file = query.file;
510
+ if (query.substringMatch) serverQuery.substringMatch = query.substringMatch;
511
+
512
+ // Use findByType if only nodeType specified
513
+ if (serverQuery.nodeType && Object.keys(serverQuery).length === 1) {
514
+ const ids = await this.client.findByType(serverQuery.nodeType);
515
+ for (const id of ids) {
516
+ const node = await this.getNode(id);
517
+ if (node) yield node;
518
+ }
519
+ return;
520
+ }
521
+
522
+ // Otherwise use client's queryNodes
523
+ for await (const wireNode of this.client.queryNodes(serverQuery as unknown as RFDBAttrQuery)) {
524
+ yield this._parseNode(wireNode);
525
+ }
526
+ }
527
+
528
+ /**
529
+ * Get ALL nodes matching query (collects from queryNodes into array)
530
+ */
531
+ async getAllNodes(query: NodeQuery = {}): Promise<BaseNodeRecord[]> {
532
+ const nodes: BaseNodeRecord[] = [];
533
+ for await (const node of this.queryNodes(query)) {
534
+ nodes.push(node);
535
+ }
536
+ return nodes;
537
+ }
538
+
539
+ // ===========================================================================
540
+ // Edge Operations
541
+ // ===========================================================================
542
+
543
+ /**
544
+ * Delete an edge
545
+ */
546
+ async deleteEdge(src: string, dst: string, type: string): Promise<void> {
547
+ if (!this.client) throw new Error('Not connected');
548
+ await this.client.deleteEdge(src, dst, type as EdgeType);
549
+ }
550
+
551
+ /**
552
+ * Get all edges
553
+ */
554
+ async getAllEdges(): Promise<EdgeRecord[]> {
555
+ return this.getAllEdgesAsync();
556
+ }
557
+
558
+ /**
559
+ * Get all edges (async version)
560
+ */
561
+ async getAllEdgesAsync(): Promise<EdgeRecord[]> {
562
+ if (!this.client) throw new Error('Not connected');
563
+ const edges = await this.client.getAllEdges();
564
+ return edges.map(e => this._parseEdge(e));
565
+ }
566
+
567
+ /**
568
+ * Get outgoing edges from a node
569
+ */
570
+ async getOutgoingEdges(nodeId: string, edgeTypes: EdgeType[] | null = null): Promise<EdgeRecord[]> {
571
+ if (!this.client) throw new Error('Not connected');
572
+ const edges = await this.client.getOutgoingEdges(nodeId, edgeTypes || undefined);
573
+ return edges.map(e => this._parseEdge(e));
574
+ }
575
+
576
+ /**
577
+ * Get incoming edges to a node
578
+ */
579
+ async getIncomingEdges(nodeId: string, edgeTypes: EdgeType[] | null = null): Promise<EdgeRecord[]> {
580
+ if (!this.client) throw new Error('Not connected');
581
+ const edges = await this.client.getIncomingEdges(nodeId, edgeTypes || undefined);
582
+ return edges.map(e => this._parseEdge(e));
583
+ }
584
+
585
+ // ===========================================================================
586
+ // Graph Traversal
587
+ // ===========================================================================
588
+
589
+ /**
590
+ * BFS traversal
591
+ */
592
+ async bfs(startIds: string[], maxDepth: number, edgeTypes: EdgeType[]): Promise<string[]> {
593
+ if (!this.client) throw new Error('Not connected');
594
+ return this.client.bfs(startIds, maxDepth, edgeTypes);
595
+ }
596
+
597
+ /**
598
+ * DFS traversal
599
+ */
600
+ async dfs(startIds: string[], maxDepth: number, edgeTypes: EdgeType[] = []): Promise<string[]> {
601
+ if (!this.client) throw new Error('Not connected');
602
+ return this.client.dfs(startIds, maxDepth, edgeTypes);
603
+ }
604
+
605
+ /**
606
+ * Reachability query - find all nodes reachable from start nodes
607
+ */
608
+ async reachability(
609
+ startIds: string[],
610
+ maxDepth: number,
611
+ edgeTypes: EdgeType[] = [],
612
+ backward: boolean = false
613
+ ): Promise<string[]> {
614
+ if (!this.client) throw new Error('Not connected');
615
+ return this.client.reachability(startIds, maxDepth, edgeTypes, backward);
616
+ }
617
+
618
+ // ===========================================================================
619
+ // Statistics
620
+ // ===========================================================================
621
+
622
+ /**
623
+ * Get node count
624
+ */
625
+ async nodeCount(): Promise<number> {
626
+ if (!this.client) throw new Error('Not connected');
627
+ return this.client.nodeCount();
628
+ }
629
+
630
+ /**
631
+ * Get edge count
632
+ */
633
+ async edgeCount(): Promise<number> {
634
+ if (!this.client) throw new Error('Not connected');
635
+ return this.client.edgeCount();
636
+ }
637
+
638
+ /**
639
+ * Get statistics
640
+ */
641
+ async getStats(): Promise<BackendStats> {
642
+ if (!this.client) throw new Error('Not connected');
643
+ const nodeCount = await this.client.nodeCount();
644
+ const edgeCount = await this.client.edgeCount();
645
+ const nodeCounts = await this.client.countNodesByType();
646
+ const edgeCounts = await this.client.countEdgesByType();
647
+
648
+ return {
649
+ nodeCount,
650
+ edgeCount,
651
+ nodesByType: nodeCounts,
652
+ edgesByType: edgeCounts,
653
+ };
654
+ }
655
+
656
+ /**
657
+ * Get full server stats including shard diagnostics.
658
+ * Uses the GetStats wire command which returns all metrics in one call.
659
+ */
660
+ async getServerStats(): Promise<ServerStats> {
661
+ if (!this.client) throw new Error('Not connected');
662
+ return this.client.getStats();
663
+ }
664
+
665
+ /**
666
+ * Count nodes by type (sync, returns cached value)
667
+ */
668
+ async countNodesByType(_types: string[] | null = null): Promise<Record<string, number>> {
669
+ if (!this.client) throw new Error('Not connected');
670
+ return this.client.countNodesByType();
671
+ }
672
+
673
+ /**
674
+ * Count edges by type
675
+ */
676
+ async countEdgesByType(_edgeTypes: string[] | null = null): Promise<Record<string, number>> {
677
+ if (!this.client) throw new Error('Not connected');
678
+ return this.client.countEdgesByType();
679
+ }
680
+
681
+ /**
682
+ * Refresh cached counts (call after analysis)
683
+ */
684
+ async refreshCounts(): Promise<void> {
685
+ if (!this.client) throw new Error('Not connected');
686
+ this._cachedNodeCounts = await this.client.countNodesByType();
687
+ this._cachedEdgeCounts = await this.client.countEdgesByType();
688
+ }
689
+
690
+ // ===========================================================================
691
+ // Datalog Queries
692
+ // ===========================================================================
693
+
694
+ /**
695
+ * Check a guarantee (Datalog rule) and return violations.
696
+ * @param explain Pass literal `true` to get explain data.
697
+ */
698
+ async checkGuarantee(ruleSource: string): Promise<Array<{ bindings: Array<{ name: string; value: string }> }>>;
699
+ async checkGuarantee(ruleSource: string, explain: true): Promise<DatalogExplainResult>;
700
+ async checkGuarantee(ruleSource: string, explain?: boolean): Promise<Array<{ bindings: Array<{ name: string; value: string }> }> | DatalogExplainResult> {
701
+ if (!this.client) throw new Error('Not connected');
702
+ if (explain) {
703
+ return await this.client.checkGuarantee(ruleSource, true);
704
+ }
705
+ const violations = await this.client.checkGuarantee(ruleSource);
706
+ // Convert bindings from {X: "value"} to [{name: "X", value: "value"}]
707
+ return violations.map(v => ({
708
+ bindings: Object.entries(v.bindings).map(([name, value]) => ({ name, value }))
709
+ }));
710
+ }
711
+
712
+ /**
713
+ * Load Datalog rules
714
+ */
715
+ async datalogLoadRules(source: string): Promise<number> {
716
+ if (!this.client) throw new Error('Not connected');
717
+ return await this.client.datalogLoadRules(source);
718
+ }
719
+
720
+ /**
721
+ * Clear Datalog rules
722
+ */
723
+ async datalogClearRules(): Promise<void> {
724
+ if (!this.client) throw new Error('Not connected');
725
+ await this.client.datalogClearRules();
726
+ }
727
+
728
+ /**
729
+ * Run a Datalog query.
730
+ * @param explain Pass literal `true` to get explain data.
731
+ */
732
+ async datalogQuery(query: string): Promise<Array<{ bindings: Array<{ name: string; value: string }> }>>;
733
+ async datalogQuery(query: string, explain: true): Promise<DatalogExplainResult>;
734
+ async datalogQuery(query: string, explain?: boolean): Promise<Array<{ bindings: Array<{ name: string; value: string }> }> | DatalogExplainResult> {
735
+ if (!this.client) throw new Error('Not connected');
736
+ if (explain) {
737
+ return await this.client.datalogQuery(query, true);
738
+ }
739
+ const results = await this.client.datalogQuery(query);
740
+ // Convert bindings from {X: "value"} to [{name: "X", value: "value"}]
741
+ return results.map(r => ({
742
+ bindings: Object.entries(r.bindings).map(([name, value]) => ({ name, value }))
743
+ }));
744
+ }
745
+
746
+ /**
747
+ * Execute unified Datalog query or program.
748
+ * Auto-detects whether input is rules or direct query.
749
+ * @param explain Pass literal `true` to get explain data.
750
+ */
751
+ async executeDatalog(source: string): Promise<Array<{ bindings: Array<{ name: string; value: string }> }>>;
752
+ async executeDatalog(source: string, explain: true): Promise<DatalogExplainResult>;
753
+ async executeDatalog(source: string, explain?: boolean): Promise<Array<{ bindings: Array<{ name: string; value: string }> }> | DatalogExplainResult> {
754
+ if (!this.client) throw new Error('Not connected');
755
+ if (explain) {
756
+ return await this.client.executeDatalog(source, true);
757
+ }
758
+ const results = await this.client.executeDatalog(source);
759
+ return results.map(r => ({
760
+ bindings: Object.entries(r.bindings).map(([name, value]) => ({ name, value }))
761
+ }));
762
+ }
763
+
764
+ /**
765
+ * Run a Cypher query.
766
+ */
767
+ async cypherQuery(query: string): Promise<CypherResult> {
768
+ if (!this.client) throw new Error('Not connected');
769
+ return await this.client.cypherQuery(query);
770
+ }
771
+
772
+ // ===========================================================================
773
+ // Batch Operations (RFD-16: CommitBatch protocol)
774
+ // ===========================================================================
775
+
776
+ /**
777
+ * Begin a batch operation. While batching, addNodes/addEdges buffer locally.
778
+ * Call commitBatch() to send all buffered data atomically.
779
+ */
780
+ beginBatch(): void {
781
+ if (!this.client) throw new Error('Not connected to RFDB server');
782
+ this.client.beginBatch();
783
+ }
784
+
785
+ /**
786
+ * Commit the current batch to the server atomically.
787
+ * Returns a CommitDelta describing what changed.
788
+ *
789
+ * @param tags - Optional tags for the commit
790
+ * @param deferIndex - When true, server writes data but skips index rebuild.
791
+ */
792
+ async commitBatch(tags?: string[], deferIndex?: boolean, protectedTypes?: string[], changedFiles?: string[]): Promise<CommitDelta> {
793
+ if (!this.client) throw new Error('Not connected to RFDB server');
794
+ return this.client.commitBatch(tags, deferIndex, protectedTypes, changedFiles);
795
+ }
796
+
797
+ /**
798
+ * Synchronously batch a node. Must be inside beginBatch/commitBatch.
799
+ * Bypasses async wrapper for direct batch insertion.
800
+ */
801
+ batchNode(node: InputNode): void {
802
+ if (!this.client) throw new Error('Not connected');
803
+ const { id, type, nodeType, node_type, name, file, exported, ...rest } = node;
804
+ const useV3 = this.protocolVersion >= 3;
805
+ const wire: Record<string, unknown> = {
806
+ id: String(id),
807
+ nodeType: (nodeType || node_type || type || 'UNKNOWN'),
808
+ name: name || '',
809
+ file: file || '',
810
+ exported: exported || false,
811
+ metadata: useV3 ? JSON.stringify(rest) : JSON.stringify({ originalId: String(id), ...rest }),
812
+ };
813
+ if (useV3) {
814
+ wire.semanticId = String(id);
815
+ }
816
+ this.client.batchNode(wire as Parameters<typeof this.client.batchNode>[0]);
817
+ }
818
+
819
+ /**
820
+ * Synchronously batch an edge. Must be inside beginBatch/commitBatch.
821
+ */
822
+ batchEdge(edge: InputEdge): void {
823
+ if (!this.client) throw new Error('Not connected');
824
+ const { src, dst, type, edgeType, edge_type, etype, metadata, ...rest } = edge;
825
+ const edgeTypeStr = edgeType || edge_type || (etype as string) || type;
826
+ if (typeof edgeTypeStr === 'string') this.edgeTypes.add(edgeTypeStr);
827
+ const flatMetadata = { ...rest, ...(typeof metadata === 'object' && metadata !== null ? metadata as Record<string, unknown> : {}) };
828
+ this.client.batchEdge({
829
+ src: String(src),
830
+ dst: String(dst),
831
+ edgeType: (edgeTypeStr || 'UNKNOWN'),
832
+ metadata: JSON.stringify(flatMetadata),
833
+ } as Record<string, unknown>);
834
+ }
835
+
836
+ /**
837
+ * Abort the current batch, discarding all buffered data.
838
+ */
839
+ abortBatch(): void {
840
+ if (!this.client) throw new Error('Not connected to RFDB server');
841
+ this.client.abortBatch();
842
+ }
843
+
844
+ /**
845
+ * Rebuild all secondary indexes after deferred-index commits (REG-487).
846
+ * Call this once after a series of commitBatch(tags, true) calls.
847
+ */
848
+ async rebuildIndexes(): Promise<void> {
849
+ if (!this.client) throw new Error('Not connected to RFDB server');
850
+ await this.client.rebuildIndexes();
851
+ }
852
+
853
+ /**
854
+ * Create an isolated batch handle for concurrent-safe batching (REG-487).
855
+ * Each handle has its own buffers — safe for parallel workers.
856
+ */
857
+ createBatch(): BatchHandle {
858
+ if (!this.client) throw new Error('Not connected to RFDB server');
859
+ return this.client.createBatch();
860
+ }
861
+
862
+ // ===========================================================================
863
+ // Export/Import
864
+ // ===========================================================================
865
+
866
+ /**
867
+ * Export graph (for tests)
868
+ */
869
+ async export(): Promise<GraphExport> {
870
+ const nodes = await this.getAllNodes();
871
+ const edges = await this.getAllEdgesAsync();
872
+ return {
873
+ nodes: nodes as unknown as GraphExport['nodes'],
874
+ edges: edges as unknown as GraphExport['edges'],
875
+ };
876
+ }
877
+
878
+ /**
879
+ * Find nodes by predicate (for compatibility)
880
+ */
881
+ async findNodes(predicate: (node: BaseNodeRecord) => boolean): Promise<BaseNodeRecord[]> {
882
+ const allNodes = await this.getAllNodes();
883
+ return allNodes.filter(predicate);
884
+ }
885
+
886
+ // ===========================================================================
887
+ // Graph property (for compatibility)
888
+ // ===========================================================================
889
+
890
+ get graph(): this {
891
+ return this;
892
+ }
893
+ }
894
+
895
+ export default RFDBServerBackend;