grafeo-ogm 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (333) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1121 -0
  3. package/dist/cjs/compilers/fulltext.compiler.d.ts +67 -0
  4. package/dist/cjs/compilers/fulltext.compiler.d.ts.map +1 -0
  5. package/dist/cjs/compilers/fulltext.compiler.js +254 -0
  6. package/dist/cjs/compilers/fulltext.compiler.js.map +1 -0
  7. package/dist/cjs/compilers/mutation.compiler.d.ts +126 -0
  8. package/dist/cjs/compilers/mutation.compiler.d.ts.map +1 -0
  9. package/dist/cjs/compilers/mutation.compiler.js +1239 -0
  10. package/dist/cjs/compilers/mutation.compiler.js.map +1 -0
  11. package/dist/cjs/compilers/select-normalizer.d.ts +50 -0
  12. package/dist/cjs/compilers/select-normalizer.d.ts.map +1 -0
  13. package/dist/cjs/compilers/select-normalizer.js +225 -0
  14. package/dist/cjs/compilers/select-normalizer.js.map +1 -0
  15. package/dist/cjs/compilers/selection.compiler.d.ts +113 -0
  16. package/dist/cjs/compilers/selection.compiler.d.ts.map +1 -0
  17. package/dist/cjs/compilers/selection.compiler.js +410 -0
  18. package/dist/cjs/compilers/selection.compiler.js.map +1 -0
  19. package/dist/cjs/compilers/where.compiler.d.ts +39 -0
  20. package/dist/cjs/compilers/where.compiler.d.ts.map +1 -0
  21. package/dist/cjs/compilers/where.compiler.js +456 -0
  22. package/dist/cjs/compilers/where.compiler.js.map +1 -0
  23. package/dist/cjs/errors.d.ts +28 -0
  24. package/dist/cjs/errors.d.ts.map +1 -0
  25. package/dist/cjs/errors.js +42 -0
  26. package/dist/cjs/errors.js.map +1 -0
  27. package/dist/cjs/execution/executor.d.ts +38 -0
  28. package/dist/cjs/execution/executor.d.ts.map +1 -0
  29. package/dist/cjs/execution/executor.js +50 -0
  30. package/dist/cjs/execution/executor.js.map +1 -0
  31. package/dist/cjs/execution/result-mapper.d.ts +26 -0
  32. package/dist/cjs/execution/result-mapper.d.ts.map +1 -0
  33. package/dist/cjs/execution/result-mapper.js +92 -0
  34. package/dist/cjs/execution/result-mapper.js.map +1 -0
  35. package/dist/cjs/generator/generate-types.d.ts +55 -0
  36. package/dist/cjs/generator/generate-types.d.ts.map +1 -0
  37. package/dist/cjs/generator/generate-types.js +181 -0
  38. package/dist/cjs/generator/generate-types.js.map +1 -0
  39. package/dist/cjs/generator/index.d.ts +3 -0
  40. package/dist/cjs/generator/index.d.ts.map +1 -0
  41. package/dist/cjs/generator/index.js +6 -0
  42. package/dist/cjs/generator/index.js.map +1 -0
  43. package/dist/cjs/generator/type-emitters/aggregation-emitter.d.ts +7 -0
  44. package/dist/cjs/generator/type-emitters/aggregation-emitter.d.ts.map +1 -0
  45. package/dist/cjs/generator/type-emitters/aggregation-emitter.js +196 -0
  46. package/dist/cjs/generator/type-emitters/aggregation-emitter.js.map +1 -0
  47. package/dist/cjs/generator/type-emitters/connection-emitter.d.ts +22 -0
  48. package/dist/cjs/generator/type-emitters/connection-emitter.d.ts.map +1 -0
  49. package/dist/cjs/generator/type-emitters/connection-emitter.js +270 -0
  50. package/dist/cjs/generator/type-emitters/connection-emitter.js.map +1 -0
  51. package/dist/cjs/generator/type-emitters/enum-emitter.d.ts +7 -0
  52. package/dist/cjs/generator/type-emitters/enum-emitter.d.ts.map +1 -0
  53. package/dist/cjs/generator/type-emitters/enum-emitter.js +45 -0
  54. package/dist/cjs/generator/type-emitters/enum-emitter.js.map +1 -0
  55. package/dist/cjs/generator/type-emitters/fulltext-emitter.d.ts +15 -0
  56. package/dist/cjs/generator/type-emitters/fulltext-emitter.d.ts.map +1 -0
  57. package/dist/cjs/generator/type-emitters/fulltext-emitter.js +80 -0
  58. package/dist/cjs/generator/type-emitters/fulltext-emitter.js.map +1 -0
  59. package/dist/cjs/generator/type-emitters/helpers.d.ts +32 -0
  60. package/dist/cjs/generator/type-emitters/helpers.d.ts.map +1 -0
  61. package/dist/cjs/generator/type-emitters/helpers.js +69 -0
  62. package/dist/cjs/generator/type-emitters/helpers.js.map +1 -0
  63. package/dist/cjs/generator/type-emitters/input-emitter.d.ts +6 -0
  64. package/dist/cjs/generator/type-emitters/input-emitter.d.ts.map +1 -0
  65. package/dist/cjs/generator/type-emitters/input-emitter.js +560 -0
  66. package/dist/cjs/generator/type-emitters/input-emitter.js.map +1 -0
  67. package/dist/cjs/generator/type-emitters/interface-emitter.d.ts +10 -0
  68. package/dist/cjs/generator/type-emitters/interface-emitter.d.ts.map +1 -0
  69. package/dist/cjs/generator/type-emitters/interface-emitter.js +88 -0
  70. package/dist/cjs/generator/type-emitters/interface-emitter.js.map +1 -0
  71. package/dist/cjs/generator/type-emitters/model-map-emitter.d.ts +32 -0
  72. package/dist/cjs/generator/type-emitters/model-map-emitter.d.ts.map +1 -0
  73. package/dist/cjs/generator/type-emitters/model-map-emitter.js +154 -0
  74. package/dist/cjs/generator/type-emitters/model-map-emitter.js.map +1 -0
  75. package/dist/cjs/generator/type-emitters/mutation-response-emitter.d.ts +9 -0
  76. package/dist/cjs/generator/type-emitters/mutation-response-emitter.d.ts.map +1 -0
  77. package/dist/cjs/generator/type-emitters/mutation-response-emitter.js +69 -0
  78. package/dist/cjs/generator/type-emitters/mutation-response-emitter.js.map +1 -0
  79. package/dist/cjs/generator/type-emitters/node-type-emitter.d.ts +13 -0
  80. package/dist/cjs/generator/type-emitters/node-type-emitter.d.ts.map +1 -0
  81. package/dist/cjs/generator/type-emitters/node-type-emitter.js +123 -0
  82. package/dist/cjs/generator/type-emitters/node-type-emitter.js.map +1 -0
  83. package/dist/cjs/generator/type-emitters/select-fields-emitter.d.ts +8 -0
  84. package/dist/cjs/generator/type-emitters/select-fields-emitter.d.ts.map +1 -0
  85. package/dist/cjs/generator/type-emitters/select-fields-emitter.js +169 -0
  86. package/dist/cjs/generator/type-emitters/select-fields-emitter.js.map +1 -0
  87. package/dist/cjs/generator/type-emitters/select-result-emitter.d.ts +9 -0
  88. package/dist/cjs/generator/type-emitters/select-result-emitter.d.ts.map +1 -0
  89. package/dist/cjs/generator/type-emitters/select-result-emitter.js +61 -0
  90. package/dist/cjs/generator/type-emitters/select-result-emitter.js.map +1 -0
  91. package/dist/cjs/generator/type-emitters/sort-options-emitter.d.ts +8 -0
  92. package/dist/cjs/generator/type-emitters/sort-options-emitter.d.ts.map +1 -0
  93. package/dist/cjs/generator/type-emitters/sort-options-emitter.js +47 -0
  94. package/dist/cjs/generator/type-emitters/sort-options-emitter.js.map +1 -0
  95. package/dist/cjs/generator/type-emitters/utility-types.d.ts +10 -0
  96. package/dist/cjs/generator/type-emitters/utility-types.d.ts.map +1 -0
  97. package/dist/cjs/generator/type-emitters/utility-types.js +93 -0
  98. package/dist/cjs/generator/type-emitters/utility-types.js.map +1 -0
  99. package/dist/cjs/generator/type-emitters/where-emitter.d.ts +15 -0
  100. package/dist/cjs/generator/type-emitters/where-emitter.d.ts.map +1 -0
  101. package/dist/cjs/generator/type-emitters/where-emitter.js +189 -0
  102. package/dist/cjs/generator/type-emitters/where-emitter.js.map +1 -0
  103. package/dist/cjs/index.d.ts +30 -0
  104. package/dist/cjs/index.d.ts.map +1 -0
  105. package/dist/cjs/index.js +59 -0
  106. package/dist/cjs/index.js.map +1 -0
  107. package/dist/cjs/interface-model.d.ts +158 -0
  108. package/dist/cjs/interface-model.d.ts.map +1 -0
  109. package/dist/cjs/interface-model.js +214 -0
  110. package/dist/cjs/interface-model.js.map +1 -0
  111. package/dist/cjs/model.d.ts +354 -0
  112. package/dist/cjs/model.d.ts.map +1 -0
  113. package/dist/cjs/model.js +513 -0
  114. package/dist/cjs/model.js.map +1 -0
  115. package/dist/cjs/ogm.d.ts +97 -0
  116. package/dist/cjs/ogm.d.ts.map +1 -0
  117. package/dist/cjs/ogm.js +224 -0
  118. package/dist/cjs/ogm.js.map +1 -0
  119. package/dist/cjs/package.json +1 -0
  120. package/dist/cjs/schema/parser.d.ts +12 -0
  121. package/dist/cjs/schema/parser.d.ts.map +1 -0
  122. package/dist/cjs/schema/parser.js +331 -0
  123. package/dist/cjs/schema/parser.js.map +1 -0
  124. package/dist/cjs/schema/types.d.ts +66 -0
  125. package/dist/cjs/schema/types.d.ts.map +1 -0
  126. package/dist/cjs/schema/types.js +3 -0
  127. package/dist/cjs/schema/types.js.map +1 -0
  128. package/dist/cjs/schema/utils.d.ts +23 -0
  129. package/dist/cjs/schema/utils.d.ts.map +1 -0
  130. package/dist/cjs/schema/utils.js +92 -0
  131. package/dist/cjs/schema/utils.js.map +1 -0
  132. package/dist/cjs/subgraph/index.d.ts +3 -0
  133. package/dist/cjs/subgraph/index.d.ts.map +1 -0
  134. package/dist/cjs/subgraph/index.js +7 -0
  135. package/dist/cjs/subgraph/index.js.map +1 -0
  136. package/dist/cjs/subgraph/subgraph-operations.d.ts +34 -0
  137. package/dist/cjs/subgraph/subgraph-operations.d.ts.map +1 -0
  138. package/dist/cjs/subgraph/subgraph-operations.js +225 -0
  139. package/dist/cjs/subgraph/subgraph-operations.js.map +1 -0
  140. package/dist/cjs/subgraph/types.d.ts +23 -0
  141. package/dist/cjs/subgraph/types.d.ts.map +1 -0
  142. package/dist/cjs/subgraph/types.js +3 -0
  143. package/dist/cjs/subgraph/types.js.map +1 -0
  144. package/dist/cjs/testing/cypher-assert.d.ts +35 -0
  145. package/dist/cjs/testing/cypher-assert.d.ts.map +1 -0
  146. package/dist/cjs/testing/cypher-assert.js +72 -0
  147. package/dist/cjs/testing/cypher-assert.js.map +1 -0
  148. package/dist/cjs/testing/index.d.ts +4 -0
  149. package/dist/cjs/testing/index.d.ts.map +1 -0
  150. package/dist/cjs/testing/index.js +10 -0
  151. package/dist/cjs/testing/index.js.map +1 -0
  152. package/dist/cjs/testing/neo4j-record-factory.d.ts +25 -0
  153. package/dist/cjs/testing/neo4j-record-factory.d.ts.map +1 -0
  154. package/dist/cjs/testing/neo4j-record-factory.js +43 -0
  155. package/dist/cjs/testing/neo4j-record-factory.js.map +1 -0
  156. package/dist/cjs/testing/selection-set-factory.d.ts +24 -0
  157. package/dist/cjs/testing/selection-set-factory.d.ts.map +1 -0
  158. package/dist/cjs/testing/selection-set-factory.js +44 -0
  159. package/dist/cjs/testing/selection-set-factory.js.map +1 -0
  160. package/dist/cjs/utils/lucene.d.ts +9 -0
  161. package/dist/cjs/utils/lucene.d.ts.map +1 -0
  162. package/dist/cjs/utils/lucene.js +25 -0
  163. package/dist/cjs/utils/lucene.js.map +1 -0
  164. package/dist/cjs/utils/validation.d.ts +25 -0
  165. package/dist/cjs/utils/validation.d.ts.map +1 -0
  166. package/dist/cjs/utils/validation.js +53 -0
  167. package/dist/cjs/utils/validation.js.map +1 -0
  168. package/dist/esm/compilers/fulltext.compiler.d.ts +67 -0
  169. package/dist/esm/compilers/fulltext.compiler.d.ts.map +1 -0
  170. package/dist/esm/compilers/fulltext.compiler.js +250 -0
  171. package/dist/esm/compilers/fulltext.compiler.js.map +1 -0
  172. package/dist/esm/compilers/mutation.compiler.d.ts +126 -0
  173. package/dist/esm/compilers/mutation.compiler.d.ts.map +1 -0
  174. package/dist/esm/compilers/mutation.compiler.js +1235 -0
  175. package/dist/esm/compilers/mutation.compiler.js.map +1 -0
  176. package/dist/esm/compilers/select-normalizer.d.ts +50 -0
  177. package/dist/esm/compilers/select-normalizer.d.ts.map +1 -0
  178. package/dist/esm/compilers/select-normalizer.js +221 -0
  179. package/dist/esm/compilers/select-normalizer.js.map +1 -0
  180. package/dist/esm/compilers/selection.compiler.d.ts +113 -0
  181. package/dist/esm/compilers/selection.compiler.d.ts.map +1 -0
  182. package/dist/esm/compilers/selection.compiler.js +406 -0
  183. package/dist/esm/compilers/selection.compiler.js.map +1 -0
  184. package/dist/esm/compilers/where.compiler.d.ts +39 -0
  185. package/dist/esm/compilers/where.compiler.d.ts.map +1 -0
  186. package/dist/esm/compilers/where.compiler.js +452 -0
  187. package/dist/esm/compilers/where.compiler.js.map +1 -0
  188. package/dist/esm/errors.d.ts +28 -0
  189. package/dist/esm/errors.d.ts.map +1 -0
  190. package/dist/esm/errors.js +36 -0
  191. package/dist/esm/errors.js.map +1 -0
  192. package/dist/esm/execution/executor.d.ts +38 -0
  193. package/dist/esm/execution/executor.d.ts.map +1 -0
  194. package/dist/esm/execution/executor.js +46 -0
  195. package/dist/esm/execution/executor.js.map +1 -0
  196. package/dist/esm/execution/result-mapper.d.ts +26 -0
  197. package/dist/esm/execution/result-mapper.d.ts.map +1 -0
  198. package/dist/esm/execution/result-mapper.js +85 -0
  199. package/dist/esm/execution/result-mapper.js.map +1 -0
  200. package/dist/esm/generator/generate-types.d.ts +55 -0
  201. package/dist/esm/generator/generate-types.d.ts.map +1 -0
  202. package/dist/esm/generator/generate-types.js +141 -0
  203. package/dist/esm/generator/generate-types.js.map +1 -0
  204. package/dist/esm/generator/index.d.ts +3 -0
  205. package/dist/esm/generator/index.d.ts.map +1 -0
  206. package/dist/esm/generator/index.js +2 -0
  207. package/dist/esm/generator/index.js.map +1 -0
  208. package/dist/esm/generator/type-emitters/aggregation-emitter.d.ts +7 -0
  209. package/dist/esm/generator/type-emitters/aggregation-emitter.d.ts.map +1 -0
  210. package/dist/esm/generator/type-emitters/aggregation-emitter.js +193 -0
  211. package/dist/esm/generator/type-emitters/aggregation-emitter.js.map +1 -0
  212. package/dist/esm/generator/type-emitters/connection-emitter.d.ts +22 -0
  213. package/dist/esm/generator/type-emitters/connection-emitter.d.ts.map +1 -0
  214. package/dist/esm/generator/type-emitters/connection-emitter.js +266 -0
  215. package/dist/esm/generator/type-emitters/connection-emitter.js.map +1 -0
  216. package/dist/esm/generator/type-emitters/enum-emitter.d.ts +7 -0
  217. package/dist/esm/generator/type-emitters/enum-emitter.d.ts.map +1 -0
  218. package/dist/esm/generator/type-emitters/enum-emitter.js +42 -0
  219. package/dist/esm/generator/type-emitters/enum-emitter.js.map +1 -0
  220. package/dist/esm/generator/type-emitters/fulltext-emitter.d.ts +15 -0
  221. package/dist/esm/generator/type-emitters/fulltext-emitter.d.ts.map +1 -0
  222. package/dist/esm/generator/type-emitters/fulltext-emitter.js +77 -0
  223. package/dist/esm/generator/type-emitters/fulltext-emitter.js.map +1 -0
  224. package/dist/esm/generator/type-emitters/helpers.d.ts +32 -0
  225. package/dist/esm/generator/type-emitters/helpers.d.ts.map +1 -0
  226. package/dist/esm/generator/type-emitters/helpers.js +63 -0
  227. package/dist/esm/generator/type-emitters/helpers.js.map +1 -0
  228. package/dist/esm/generator/type-emitters/input-emitter.d.ts +6 -0
  229. package/dist/esm/generator/type-emitters/input-emitter.d.ts.map +1 -0
  230. package/dist/esm/generator/type-emitters/input-emitter.js +557 -0
  231. package/dist/esm/generator/type-emitters/input-emitter.js.map +1 -0
  232. package/dist/esm/generator/type-emitters/interface-emitter.d.ts +10 -0
  233. package/dist/esm/generator/type-emitters/interface-emitter.d.ts.map +1 -0
  234. package/dist/esm/generator/type-emitters/interface-emitter.js +85 -0
  235. package/dist/esm/generator/type-emitters/interface-emitter.js.map +1 -0
  236. package/dist/esm/generator/type-emitters/model-map-emitter.d.ts +32 -0
  237. package/dist/esm/generator/type-emitters/model-map-emitter.d.ts.map +1 -0
  238. package/dist/esm/generator/type-emitters/model-map-emitter.js +149 -0
  239. package/dist/esm/generator/type-emitters/model-map-emitter.js.map +1 -0
  240. package/dist/esm/generator/type-emitters/mutation-response-emitter.d.ts +9 -0
  241. package/dist/esm/generator/type-emitters/mutation-response-emitter.d.ts.map +1 -0
  242. package/dist/esm/generator/type-emitters/mutation-response-emitter.js +66 -0
  243. package/dist/esm/generator/type-emitters/mutation-response-emitter.js.map +1 -0
  244. package/dist/esm/generator/type-emitters/node-type-emitter.d.ts +13 -0
  245. package/dist/esm/generator/type-emitters/node-type-emitter.d.ts.map +1 -0
  246. package/dist/esm/generator/type-emitters/node-type-emitter.js +120 -0
  247. package/dist/esm/generator/type-emitters/node-type-emitter.js.map +1 -0
  248. package/dist/esm/generator/type-emitters/select-fields-emitter.d.ts +8 -0
  249. package/dist/esm/generator/type-emitters/select-fields-emitter.d.ts.map +1 -0
  250. package/dist/esm/generator/type-emitters/select-fields-emitter.js +166 -0
  251. package/dist/esm/generator/type-emitters/select-fields-emitter.js.map +1 -0
  252. package/dist/esm/generator/type-emitters/select-result-emitter.d.ts +9 -0
  253. package/dist/esm/generator/type-emitters/select-result-emitter.d.ts.map +1 -0
  254. package/dist/esm/generator/type-emitters/select-result-emitter.js +58 -0
  255. package/dist/esm/generator/type-emitters/select-result-emitter.js.map +1 -0
  256. package/dist/esm/generator/type-emitters/sort-options-emitter.d.ts +8 -0
  257. package/dist/esm/generator/type-emitters/sort-options-emitter.d.ts.map +1 -0
  258. package/dist/esm/generator/type-emitters/sort-options-emitter.js +44 -0
  259. package/dist/esm/generator/type-emitters/sort-options-emitter.js.map +1 -0
  260. package/dist/esm/generator/type-emitters/utility-types.d.ts +10 -0
  261. package/dist/esm/generator/type-emitters/utility-types.d.ts.map +1 -0
  262. package/dist/esm/generator/type-emitters/utility-types.js +88 -0
  263. package/dist/esm/generator/type-emitters/utility-types.js.map +1 -0
  264. package/dist/esm/generator/type-emitters/where-emitter.d.ts +15 -0
  265. package/dist/esm/generator/type-emitters/where-emitter.d.ts.map +1 -0
  266. package/dist/esm/generator/type-emitters/where-emitter.js +186 -0
  267. package/dist/esm/generator/type-emitters/where-emitter.js.map +1 -0
  268. package/dist/esm/index.d.ts +30 -0
  269. package/dist/esm/index.d.ts.map +1 -0
  270. package/dist/esm/index.js +29 -0
  271. package/dist/esm/index.js.map +1 -0
  272. package/dist/esm/interface-model.d.ts +158 -0
  273. package/dist/esm/interface-model.d.ts.map +1 -0
  274. package/dist/esm/interface-model.js +210 -0
  275. package/dist/esm/interface-model.js.map +1 -0
  276. package/dist/esm/model.d.ts +354 -0
  277. package/dist/esm/model.d.ts.map +1 -0
  278. package/dist/esm/model.js +507 -0
  279. package/dist/esm/model.js.map +1 -0
  280. package/dist/esm/ogm.d.ts +97 -0
  281. package/dist/esm/ogm.d.ts.map +1 -0
  282. package/dist/esm/ogm.js +220 -0
  283. package/dist/esm/ogm.js.map +1 -0
  284. package/dist/esm/package.json +1 -0
  285. package/dist/esm/schema/parser.d.ts +12 -0
  286. package/dist/esm/schema/parser.d.ts.map +1 -0
  287. package/dist/esm/schema/parser.js +324 -0
  288. package/dist/esm/schema/parser.js.map +1 -0
  289. package/dist/esm/schema/types.d.ts +66 -0
  290. package/dist/esm/schema/types.d.ts.map +1 -0
  291. package/dist/esm/schema/types.js +2 -0
  292. package/dist/esm/schema/types.js.map +1 -0
  293. package/dist/esm/schema/utils.d.ts +23 -0
  294. package/dist/esm/schema/utils.d.ts.map +1 -0
  295. package/dist/esm/schema/utils.js +87 -0
  296. package/dist/esm/schema/utils.js.map +1 -0
  297. package/dist/esm/subgraph/index.d.ts +3 -0
  298. package/dist/esm/subgraph/index.d.ts.map +1 -0
  299. package/dist/esm/subgraph/index.js +2 -0
  300. package/dist/esm/subgraph/index.js.map +1 -0
  301. package/dist/esm/subgraph/subgraph-operations.d.ts +34 -0
  302. package/dist/esm/subgraph/subgraph-operations.d.ts.map +1 -0
  303. package/dist/esm/subgraph/subgraph-operations.js +221 -0
  304. package/dist/esm/subgraph/subgraph-operations.js.map +1 -0
  305. package/dist/esm/subgraph/types.d.ts +23 -0
  306. package/dist/esm/subgraph/types.d.ts.map +1 -0
  307. package/dist/esm/subgraph/types.js +2 -0
  308. package/dist/esm/subgraph/types.js.map +1 -0
  309. package/dist/esm/testing/cypher-assert.d.ts +35 -0
  310. package/dist/esm/testing/cypher-assert.d.ts.map +1 -0
  311. package/dist/esm/testing/cypher-assert.js +68 -0
  312. package/dist/esm/testing/cypher-assert.js.map +1 -0
  313. package/dist/esm/testing/index.d.ts +4 -0
  314. package/dist/esm/testing/index.d.ts.map +1 -0
  315. package/dist/esm/testing/index.js +4 -0
  316. package/dist/esm/testing/index.js.map +1 -0
  317. package/dist/esm/testing/neo4j-record-factory.d.ts +25 -0
  318. package/dist/esm/testing/neo4j-record-factory.d.ts.map +1 -0
  319. package/dist/esm/testing/neo4j-record-factory.js +36 -0
  320. package/dist/esm/testing/neo4j-record-factory.js.map +1 -0
  321. package/dist/esm/testing/selection-set-factory.d.ts +24 -0
  322. package/dist/esm/testing/selection-set-factory.d.ts.map +1 -0
  323. package/dist/esm/testing/selection-set-factory.js +40 -0
  324. package/dist/esm/testing/selection-set-factory.js.map +1 -0
  325. package/dist/esm/utils/lucene.d.ts +9 -0
  326. package/dist/esm/utils/lucene.d.ts.map +1 -0
  327. package/dist/esm/utils/lucene.js +22 -0
  328. package/dist/esm/utils/lucene.js.map +1 -0
  329. package/dist/esm/utils/validation.d.ts +25 -0
  330. package/dist/esm/utils/validation.d.ts.map +1 -0
  331. package/dist/esm/utils/validation.js +46 -0
  332. package/dist/esm/utils/validation.js.map +1 -0
  333. package/package.json +88 -0
@@ -0,0 +1,1235 @@
1
+ import { OGMError } from '../errors';
2
+ import { getTargetLabelString, resolveTargetDef } from '../schema/utils';
3
+ import { assertSafeIdentifier, assertSafeLabel, escapeIdentifier, } from '../utils/validation';
4
+ /**
5
+ * Relationship filter suffixes that require EXISTS subquery patterns.
6
+ * These match related nodes via relationship traversal rather than scalar property comparison.
7
+ */
8
+ const RELATIONSHIP_SUFFIXES = ['_SOME', '_NONE', '_ALL', '_SINGLE'];
9
+ /**
10
+ * Compiles create, update, delete, and label mutations into Cypher + params.
11
+ */
12
+ export class MutationCompiler {
13
+ constructor(schema) {
14
+ this.schema = schema;
15
+ /**
16
+ * Cache of computed label strings per type name.
17
+ * Bounded by the number of node types in the schema (~50-100 entries).
18
+ */
19
+ this.labelCache = new Map();
20
+ /** Counter for unique relationship filter variables within a single WHERE clause. */
21
+ this.relFilterCounter = 0;
22
+ }
23
+ /** Clear internal caches. Useful in tests to prevent cross-test pollution. */
24
+ clearCaches() {
25
+ this.labelCache.clear();
26
+ }
27
+ getCachedLabelString(nodeDef) {
28
+ const cached = this.labelCache.get(nodeDef.typeName);
29
+ if (cached)
30
+ return cached;
31
+ const labelStr = getTargetLabelString(nodeDef);
32
+ this.labelCache.set(nodeDef.typeName, labelStr);
33
+ return labelStr;
34
+ }
35
+ /**
36
+ * Generate CREATE Cypher for one or more nodes.
37
+ * Handles scalar properties, nested relationship creates, and connects.
38
+ */
39
+ compileCreate(inputs, nodeDef, labels) {
40
+ this.relFilterCounter = 0;
41
+ const lines = [];
42
+ const params = {};
43
+ const createdVars = [];
44
+ for (let i = 0; i < inputs.length; i++) {
45
+ const input = inputs[i];
46
+ const prefix = `create${i}`;
47
+ const nodeVar = i === 0 ? 'n' : `n_${i}`;
48
+ const { propString, propParams } = this.buildCreateProperties(input, nodeDef, prefix);
49
+ const labelStr = this.getCachedLabelString(nodeDef);
50
+ lines.push(`CREATE (${nodeVar}:${labelStr} { ${propString} })`);
51
+ Object.assign(params, propParams);
52
+ createdVars.push(nodeVar);
53
+ // Process relationship operations (nested create / connect)
54
+ // Pass all previously created node vars as ancestors so WITH clauses
55
+ // don't drop them from scope (needed for RETURN n at the end)
56
+ const relLines = this.buildCreateRelationships(input, nodeDef, nodeVar, prefix, params, createdVars.slice(0, -1));
57
+ lines.push(...relLines);
58
+ }
59
+ // Add extra labels to all created nodes
60
+ if (labels && labels.length > 0) {
61
+ const validatedLabels = labels.map((l) => assertSafeLabel(l)).join(':');
62
+ for (const nodeVar of createdVars)
63
+ lines.push(`SET ${nodeVar}:${validatedLabels}`);
64
+ }
65
+ lines.push('RETURN n');
66
+ return { cypher: lines.join('\n'), params };
67
+ }
68
+ /**
69
+ * Generate UPDATE Cypher (SET properties + connect/disconnect).
70
+ */
71
+ compileUpdate(_where, update, connect, disconnect, nodeDef, whereResult, labels, returnMode = 'node') {
72
+ this.relFilterCounter = 0;
73
+ const labelStr = this.getCachedLabelString(nodeDef);
74
+ const lines = [];
75
+ const params = { ...whereResult.params };
76
+ // Apply runtime labels to MATCH pattern (same as find())
77
+ if (labels && labels.length > 0) {
78
+ const extraLabels = labels.map((l) => assertSafeLabel(l)).join(':');
79
+ lines.push(`MATCH (n:${labelStr}:${extraLabels})`);
80
+ }
81
+ else
82
+ lines.push(`MATCH (n:${labelStr})`);
83
+ if (whereResult.cypher)
84
+ lines.push(`WHERE ${whereResult.cypher}`);
85
+ // SET properties
86
+ if (update && Object.keys(update).length > 0) {
87
+ const setClauses = [];
88
+ for (const [key, value] of Object.entries(update)) {
89
+ // Skip relationship fields — handled separately below
90
+ if (nodeDef.relationships.has(key))
91
+ continue;
92
+ if (value === undefined)
93
+ continue;
94
+ assertSafeIdentifier(key, 'update property');
95
+ const paramName = `update_${key}`;
96
+ setClauses.push(`n.${escapeIdentifier(key)} = $${paramName}`);
97
+ params[paramName] = value;
98
+ }
99
+ if (setClauses.length > 0)
100
+ lines.push(`SET ${setClauses.join(', ')}`);
101
+ // Process nested relationship operations within update body
102
+ const relLines = this.buildUpdateRelationships(update, nodeDef, 'n', 'update', params);
103
+ lines.push(...relLines);
104
+ }
105
+ // Disconnect relationships (top-level) — must run before connects
106
+ // so that blanket disconnects don't remove newly-connected relationships.
107
+ if (disconnect) {
108
+ const disconnectLines = this.buildDisconnects(disconnect, nodeDef, params);
109
+ lines.push(...disconnectLines);
110
+ }
111
+ // Connect relationships (top-level)
112
+ if (connect) {
113
+ const connectLines = this.buildConnects(connect, nodeDef, params);
114
+ lines.push(...connectLines);
115
+ }
116
+ lines.push(returnMode === 'count' ? 'RETURN count(n) AS count' : 'RETURN n');
117
+ return { cypher: lines.join('\n'), params };
118
+ }
119
+ /**
120
+ * Generate DELETE Cypher with optional cascade.
121
+ */
122
+ compileDelete(nodeDef, whereResult, deleteInput) {
123
+ const labelStr = this.getCachedLabelString(nodeDef);
124
+ const lines = [];
125
+ const params = { ...whereResult.params };
126
+ lines.push(`MATCH (n:${labelStr})`);
127
+ if (whereResult.cypher)
128
+ lines.push(`WHERE ${whereResult.cypher}`);
129
+ if (deleteInput && Object.keys(deleteInput).length > 0) {
130
+ const deleteVars = [];
131
+ let varCounter = 0;
132
+ for (const [fieldName] of Object.entries(deleteInput)) {
133
+ const relDef = nodeDef.relationships.get(fieldName);
134
+ if (!relDef)
135
+ continue;
136
+ const cascadeVar = this.getCascadeVarName(varCounter);
137
+ varCounter++;
138
+ const pattern = this.buildRelPattern('n', relDef, cascadeVar, undefined, relDef.target);
139
+ lines.push(`OPTIONAL MATCH ${pattern}`);
140
+ deleteVars.push(cascadeVar);
141
+ }
142
+ lines.push(`DETACH DELETE ${[...deleteVars, 'n'].join(', ')}`);
143
+ }
144
+ else
145
+ lines.push('DETACH DELETE n');
146
+ return { cypher: lines.join('\n'), params };
147
+ }
148
+ /**
149
+ * Generate MERGE (upsert) Cypher with ON CREATE SET / ON MATCH SET.
150
+ * Scalar properties only — nested relationship ops are not supported.
151
+ */
152
+ compileMerge(where, create, update, nodeDef, labels) {
153
+ const labelStr = this.getCachedLabelString(nodeDef);
154
+ const lines = [];
155
+ const params = {};
156
+ // Build MERGE key properties from where clause (scalar only)
157
+ const mergeProps = [];
158
+ for (const [key, value] of Object.entries(where)) {
159
+ if (value === undefined || value === null)
160
+ continue;
161
+ // Skip relationship suffixes and operators
162
+ if (key === 'AND' || key === 'OR' || key === 'NOT')
163
+ continue;
164
+ if (key.includes('_') && !nodeDef.properties.has(key))
165
+ continue;
166
+ assertSafeIdentifier(key, 'merge key');
167
+ const paramName = `merge_${key}`;
168
+ mergeProps.push(`${escapeIdentifier(key)}: $${paramName}`);
169
+ params[paramName] = value;
170
+ }
171
+ if (mergeProps.length === 0)
172
+ throw new OGMError('upsert requires at least one scalar property in "where" for MERGE key');
173
+ lines.push(`MERGE (n:${labelStr} { ${mergeProps.join(', ')} })`);
174
+ // ON CREATE SET — all properties from create input (scalar only)
175
+ const createSets = [];
176
+ for (const [key, value] of Object.entries(create)) {
177
+ if (value === undefined)
178
+ continue;
179
+ if (nodeDef.relationships.has(key))
180
+ continue;
181
+ assertSafeIdentifier(key, 'create property');
182
+ const paramName = `onCreate_${key}`;
183
+ createSets.push(`n.${escapeIdentifier(key)} = $${paramName}`);
184
+ params[paramName] = value;
185
+ }
186
+ if (createSets.length > 0)
187
+ lines.push(`ON CREATE SET ${createSets.join(', ')}`);
188
+ // ON MATCH SET — all properties from update input (scalar only)
189
+ const updateSets = [];
190
+ for (const [key, value] of Object.entries(update)) {
191
+ if (value === undefined)
192
+ continue;
193
+ if (nodeDef.relationships.has(key))
194
+ continue;
195
+ assertSafeIdentifier(key, 'update property');
196
+ const paramName = `onMatch_${key}`;
197
+ updateSets.push(`n.${escapeIdentifier(key)} = $${paramName}`);
198
+ params[paramName] = value;
199
+ }
200
+ if (updateSets.length > 0)
201
+ lines.push(`ON MATCH SET ${updateSets.join(', ')}`);
202
+ // Extra labels
203
+ if (labels && labels.length > 0) {
204
+ const validatedLabels = labels.map((l) => assertSafeLabel(l)).join(':');
205
+ lines.push(`SET n:${validatedLabels}`);
206
+ }
207
+ lines.push('RETURN n');
208
+ return { cypher: lines.join('\n'), params };
209
+ }
210
+ /**
211
+ * Generate SET/REMOVE labels Cypher.
212
+ */
213
+ compileSetLabels(nodeDef, whereResult, addLabels, removeLabels) {
214
+ const labelStr = this.getCachedLabelString(nodeDef);
215
+ const lines = [];
216
+ const params = { ...whereResult.params };
217
+ lines.push(`MATCH (n:${labelStr})`);
218
+ if (whereResult.cypher)
219
+ lines.push(`WHERE ${whereResult.cypher}`);
220
+ if (addLabels && addLabels.length > 0)
221
+ lines.push(`SET n:${addLabels.map((l) => assertSafeLabel(l)).join(':')}`);
222
+ if (removeLabels && removeLabels.length > 0)
223
+ for (const label of removeLabels)
224
+ lines.push(`REMOVE n:${assertSafeLabel(label)}`);
225
+ return { cypher: lines.join('\n'), params };
226
+ }
227
+ /**
228
+ * Generate batch CREATE (or MERGE for skipDuplicates) Cypher via UNWIND.
229
+ * Scalar properties only — no nested relationship operations.
230
+ * Returns count of created nodes.
231
+ */
232
+ compileCreateMany(data, nodeDef, skipDuplicates, labels) {
233
+ if (data.length === 0)
234
+ return { cypher: 'RETURN 0 AS count', params: {} };
235
+ const labelStr = this.getCachedLabelString(nodeDef);
236
+ const lines = [];
237
+ const params = {};
238
+ // Validate and sanitize: only scalar properties allowed
239
+ const scalarKeys = [];
240
+ for (const key of Object.keys(data[0])) {
241
+ if (nodeDef.relationships.has(key))
242
+ throw new OGMError(`createMany does not support relationship fields. Found: "${key}". Use create() for nested operations.`);
243
+ assertSafeIdentifier(key, 'createMany property');
244
+ scalarKeys.push(key);
245
+ }
246
+ // Identify @id (generated) fields that are NOT provided in data
247
+ const generatedFields = [];
248
+ for (const [, propDef] of nodeDef.properties)
249
+ if (propDef.isGenerated && !scalarKeys.includes(propDef.name))
250
+ generatedFields.push(propDef.name);
251
+ // Build sanitized items (strip undefined values)
252
+ const sanitizedItems = data.map((item) => {
253
+ const sanitized = {};
254
+ for (const key of scalarKeys)
255
+ if (item[key] !== undefined)
256
+ sanitized[key] = item[key];
257
+ return sanitized;
258
+ });
259
+ params.items = sanitizedItems;
260
+ lines.push('UNWIND $items AS item');
261
+ if (skipDuplicates) {
262
+ // Find unique/id fields present in data for MERGE key
263
+ const mergeKeys = [];
264
+ for (const key of scalarKeys) {
265
+ const propDef = nodeDef.properties.get(key);
266
+ if (propDef && (propDef.isUnique || propDef.isGenerated))
267
+ mergeKeys.push(key);
268
+ }
269
+ if (mergeKeys.length === 0)
270
+ throw new OGMError('createMany with skipDuplicates requires at least one @id or @unique field in data');
271
+ // MERGE on unique key(s)
272
+ const mergeProps = mergeKeys
273
+ .map((k) => `${escapeIdentifier(k)}: item.${escapeIdentifier(k)}`)
274
+ .join(', ');
275
+ lines.push(`MERGE (n:${labelStr} { ${mergeProps} })`);
276
+ // ON CREATE SET — remaining fields + generated IDs
277
+ const onCreateSets = [];
278
+ for (const key of scalarKeys)
279
+ if (!mergeKeys.includes(key))
280
+ onCreateSets.push(`n.${escapeIdentifier(key)} = item.${escapeIdentifier(key)}`);
281
+ for (const field of generatedFields)
282
+ onCreateSets.push(`n.${escapeIdentifier(field)} = randomUUID()`);
283
+ if (onCreateSets.length > 0)
284
+ lines.push(`ON CREATE SET ${onCreateSets.join(', ')}`);
285
+ }
286
+ else {
287
+ // CREATE with all scalar properties + generated IDs
288
+ const propParts = [];
289
+ for (const field of generatedFields)
290
+ propParts.push(`${escapeIdentifier(field)}: randomUUID()`);
291
+ for (const key of scalarKeys)
292
+ propParts.push(`${escapeIdentifier(key)}: item.${escapeIdentifier(key)}`);
293
+ lines.push(`CREATE (n:${labelStr} { ${propParts.join(', ')} })`);
294
+ }
295
+ // Extra labels
296
+ if (labels && labels.length > 0) {
297
+ const validatedLabels = labels.map((l) => assertSafeLabel(l)).join(':');
298
+ lines.push(`SET n:${validatedLabels}`);
299
+ }
300
+ lines.push('RETURN count(n) AS count');
301
+ return { cypher: lines.join('\n'), params };
302
+ }
303
+ // ─── Private helpers ──────────────────────────────────────────────
304
+ buildCreateProperties(input, nodeDef, prefix) {
305
+ const parts = [];
306
+ const propParams = {};
307
+ // Auto-generate ID if property has isGenerated
308
+ for (const [, propDef] of nodeDef.properties)
309
+ if (propDef.isGenerated)
310
+ parts.push(`${escapeIdentifier(propDef.name)}: randomUUID()`);
311
+ for (const [key, value] of Object.entries(input)) {
312
+ // Skip relationship fields and undefined values
313
+ if (nodeDef.relationships.has(key))
314
+ continue;
315
+ if (value === undefined)
316
+ continue;
317
+ assertSafeIdentifier(key, 'create property');
318
+ const paramName = `${prefix}_${key}`;
319
+ parts.push(`${escapeIdentifier(key)}: $${paramName}`);
320
+ propParams[paramName] = value;
321
+ }
322
+ return { propString: parts.join(', '), propParams };
323
+ }
324
+ buildCreateRelationships(input, nodeDef, nodeVar, prefix, params, ancestorVars = []) {
325
+ const lines = [];
326
+ let nestedCounter = 0;
327
+ // WITH clause must carry all ancestors + the node being created on
328
+ const allVars = [...new Set([...ancestorVars, nodeVar])];
329
+ const withClause = `WITH ${allVars.join(', ')}`;
330
+ for (const [key, value] of Object.entries(input)) {
331
+ const relDef = nodeDef.relationships.get(key);
332
+ if (!relDef)
333
+ continue;
334
+ const relInput = value;
335
+ // Check if target is a union type — if so, relInput uses per-member keys
336
+ // e.g., items: { Ebook: { create: [...] } }
337
+ const isUnionTarget = !this.schema.nodes.has(relDef.target) &&
338
+ this.schema.unions?.has(relDef.target);
339
+ if (isUnionTarget) {
340
+ const unionMembers = this.schema.unions.get(relDef.target);
341
+ for (const [memberKey, memberValue] of Object.entries(relInput)) {
342
+ if (!unionMembers.includes(memberKey))
343
+ continue;
344
+ const memberNodeDef = this.schema.nodes.get(memberKey);
345
+ if (!memberNodeDef)
346
+ continue;
347
+ const memberInput = memberValue;
348
+ // Process creates/connects for this union member
349
+ const memberLines = this.buildCreateRelationshipsForTarget(memberInput, memberNodeDef, relDef, nodeVar, `${prefix}_${key}_${memberKey}`, params, allVars, withClause, nestedCounter);
350
+ nestedCounter += memberLines.counterUsed;
351
+ lines.push(...memberLines.lines);
352
+ }
353
+ continue;
354
+ }
355
+ const targetNodeDef = this.schema.nodes.get(relDef.target);
356
+ if (!targetNodeDef)
357
+ continue;
358
+ const resultLines = this.buildCreateRelationshipsForTarget(relInput, targetNodeDef, relDef, nodeVar, `${prefix}_${key}`, params, allVars, withClause, nestedCounter);
359
+ nestedCounter += resultLines.counterUsed;
360
+ lines.push(...resultLines.lines);
361
+ }
362
+ return lines;
363
+ }
364
+ /**
365
+ * Process create/connect operations for a specific target node type.
366
+ * Used by buildCreateRelationships for both union-member and non-union targets.
367
+ */
368
+ buildCreateRelationshipsForTarget(relInput, targetNodeDef, relDef, nodeVar, prefix, params, allVars, withClause, startCounter) {
369
+ const lines = [];
370
+ let counter = 0;
371
+ const targetLabelStr = this.getCachedLabelString(targetNodeDef);
372
+ // --- create ---
373
+ if (relInput.create) {
374
+ const createItems = Array.isArray(relInput.create)
375
+ ? relInput.create
376
+ : [relInput.create];
377
+ for (let ci = 0; ci < createItems.length; ci++) {
378
+ const createSpec = createItems[ci];
379
+ const nodeSpec = (createSpec.node ?? createSpec);
380
+ const createVar = `${nodeVar}_c${startCounter + counter}`;
381
+ counter++;
382
+ const { propString, propParams } = this.buildCreateProperties(nodeSpec, targetNodeDef, `${prefix}_create${ci}`);
383
+ // Handle nested relationship creates within the created node
384
+ const nestedRelLines = this.buildCreateRelationships(nodeSpec, targetNodeDef, createVar, `${prefix}_create${ci}`, params, allVars);
385
+ const propsClause = propString.length > 0
386
+ ? ` { ${propString} }`
387
+ : ` { ${this.buildGeneratedIdClause(targetNodeDef)} }`;
388
+ const relPattern = this.buildRelPattern(nodeVar, relDef, createVar);
389
+ // Handle edge properties if present in createSpec
390
+ const edgeInput = createSpec.edge;
391
+ // Wrap in CALL subquery if there are nested ops so failed MATCHes don't kill pipeline
392
+ const hasNestedOps = nestedRelLines.length > 0;
393
+ if (hasNestedOps) {
394
+ lines.push(withClause);
395
+ lines.push(`CALL {`);
396
+ lines.push(withClause);
397
+ }
398
+ else
399
+ lines.push(withClause);
400
+ lines.push(`CREATE (${createVar}:${targetLabelStr}${propsClause})`);
401
+ Object.assign(params, propParams);
402
+ lines.push(`CREATE ${relPattern}`);
403
+ // Set edge properties if present
404
+ if (edgeInput && Object.keys(edgeInput).length > 0) {
405
+ const relVar = `r_edge_${startCounter + counter - 1}`;
406
+ // Re-match the relationship to set edge props
407
+ const relArrow = this.buildRelPattern(nodeVar, relDef, createVar, relVar, 'auto');
408
+ lines.push(`WITH ${[...allVars, createVar].join(', ')}`);
409
+ lines.push(`MATCH ${relArrow}`);
410
+ const setItems = [];
411
+ for (const [prop, val] of Object.entries(edgeInput)) {
412
+ if (val === undefined)
413
+ continue;
414
+ assertSafeIdentifier(prop, 'edge property');
415
+ const paramName = `${prefix}_create${ci}_edge_${prop}`;
416
+ setItems.push(`${relVar}.${escapeIdentifier(prop)} = $${paramName}`);
417
+ params[paramName] = val;
418
+ }
419
+ if (setItems.length > 0)
420
+ lines.push(`SET ${setItems.join(', ')}`);
421
+ }
422
+ if (hasNestedOps) {
423
+ lines.push(...nestedRelLines);
424
+ lines.push(`RETURN count(*) AS _cc_${prefix}_${ci}`);
425
+ lines.push(`}`);
426
+ }
427
+ }
428
+ }
429
+ // --- connect ---
430
+ if (relInput.connect) {
431
+ const connectItems = Array.isArray(relInput.connect)
432
+ ? relInput.connect
433
+ : [relInput.connect];
434
+ for (let ci = 0; ci < connectItems.length; ci++) {
435
+ const connectSpec = connectItems[ci];
436
+ const whereSpec = connectSpec.where;
437
+ const edgeInput = connectSpec.edge;
438
+ // Wrap in CALL subquery so a failed MATCH doesn't kill the outer pipeline
439
+ lines.push(withClause);
440
+ lines.push(`CALL {`);
441
+ lines.push(withClause);
442
+ const connectVar = `${nodeVar}_cn${startCounter + counter}`;
443
+ counter++;
444
+ lines.push(`MATCH (${connectVar}:${targetLabelStr})`);
445
+ const conditions = this.buildConnectionWhereConditions(whereSpec, connectVar, `${prefix}_conn${ci}`, params, targetNodeDef);
446
+ if (conditions.length > 0)
447
+ lines.push(`WHERE ${conditions.join(' AND ')}`);
448
+ if (edgeInput && Object.keys(edgeInput).length > 0) {
449
+ // Use bare target var (no label) since connectVar is already bound by MATCH.
450
+ // Including labels on a bound variable in MERGE causes Neo4j to reject it.
451
+ const relVar = `r_conn_${startCounter + counter - 1}`;
452
+ assertSafeIdentifier(relDef.type, 'relationship type');
453
+ const escapedRelType = escapeIdentifier(relDef.type);
454
+ const mergePattern = relDef.direction === 'OUT'
455
+ ? `(${nodeVar})-[${relVar}:${escapedRelType}]->(${connectVar})`
456
+ : `(${nodeVar})<-[${relVar}:${escapedRelType}]-(${connectVar})`;
457
+ lines.push(`MERGE ${mergePattern}`);
458
+ const setItems = [];
459
+ for (const [prop, val] of Object.entries(edgeInput)) {
460
+ if (val === undefined)
461
+ continue;
462
+ assertSafeIdentifier(prop, 'edge property');
463
+ const paramName = `${prefix}_conn${ci}_edge_${prop}`;
464
+ setItems.push(`r_conn_${startCounter + counter - 1}.${escapeIdentifier(prop)} = $${paramName}`);
465
+ params[paramName] = val;
466
+ }
467
+ if (setItems.length > 0)
468
+ lines.push(`SET ${setItems.join(', ')}`);
469
+ }
470
+ else {
471
+ const relPattern = this.buildRelPattern(nodeVar, relDef, connectVar);
472
+ lines.push(`MERGE ${relPattern}`);
473
+ }
474
+ lines.push(`RETURN count(*) AS _ccn_${prefix}_${ci}`);
475
+ lines.push(`}`);
476
+ }
477
+ }
478
+ return { lines, counterUsed: counter };
479
+ }
480
+ buildConnects(connect, nodeDef, params) {
481
+ const lines = [];
482
+ for (const [fieldName, spec] of Object.entries(connect)) {
483
+ const relDef = nodeDef.relationships.get(fieldName);
484
+ if (!relDef)
485
+ continue;
486
+ const targetNodeDef = resolveTargetDef(relDef.target, this.schema);
487
+ if (!targetNodeDef)
488
+ continue;
489
+ const targetLabelStr = this.getCachedLabelString(targetNodeDef);
490
+ // Array connect
491
+ if (Array.isArray(spec)) {
492
+ if (spec.length === 0)
493
+ continue; // Empty array — nothing to connect
494
+ // Check if WHERE contains relationship filters (nested objects referencing
495
+ // relationships on the target node). UNWIND can't handle EXISTS subqueries
496
+ // with dynamic params, so we fall back to individual CALL subqueries.
497
+ const hasRelFilters = this.arrayConnectHasRelationshipFilters(spec, targetNodeDef);
498
+ if (hasRelFilters)
499
+ // Fall back to individual CALL subqueries per item — these use
500
+ // buildConnectionWhereConditions which handles relationship filters.
501
+ for (let ci = 0; ci < spec.length; ci++) {
502
+ const connectSpec = spec[ci];
503
+ const whereSpec = connectSpec.where;
504
+ const edgeInput = connectSpec.edge;
505
+ lines.push('WITH n');
506
+ lines.push(`CALL {`);
507
+ lines.push('WITH n');
508
+ const connectVar = `target_${fieldName}_${ci}`;
509
+ lines.push(`MATCH (${connectVar}:${targetLabelStr})`);
510
+ const conditions = this.buildConnectionWhereConditions(whereSpec, connectVar, `connect_${fieldName}_${ci}`, params, targetNodeDef);
511
+ if (conditions.length > 0)
512
+ lines.push(`WHERE ${conditions.join(' AND ')}`);
513
+ if (edgeInput && Object.keys(edgeInput).length > 0) {
514
+ assertSafeIdentifier(relDef.type, 'relationship type');
515
+ const escapedRelType = escapeIdentifier(relDef.type);
516
+ const relVar = `r_conn_${fieldName}_${ci}`;
517
+ const mergePattern = relDef.direction === 'OUT'
518
+ ? `(n)-[${relVar}:${escapedRelType}]->(${connectVar})`
519
+ : `(n)<-[${relVar}:${escapedRelType}]-(${connectVar})`;
520
+ lines.push(`MERGE ${mergePattern}`);
521
+ const setItems = [];
522
+ for (const [prop, val] of Object.entries(edgeInput)) {
523
+ if (val === undefined)
524
+ continue;
525
+ assertSafeIdentifier(prop, 'edge property');
526
+ const paramName = `connect_${fieldName}_${ci}_edge_${prop}`;
527
+ setItems.push(`${relVar}.${escapeIdentifier(prop)} = $${paramName}`);
528
+ params[paramName] = val;
529
+ }
530
+ if (setItems.length > 0)
531
+ lines.push(`SET ${setItems.join(', ')}`);
532
+ }
533
+ else {
534
+ const mergePattern = this.buildRelPattern('n', relDef, connectVar);
535
+ lines.push(`MERGE ${mergePattern}`);
536
+ }
537
+ lines.push(`RETURN count(*) AS _ccn_${fieldName}_${ci}`);
538
+ lines.push(`}`);
539
+ }
540
+ else {
541
+ // Scalar-only WHERE — use efficient UNWIND approach
542
+ const paramName = `connect_${fieldName}`;
543
+ // Strip undefined values from edge objects — Neo4j driver drops undefined,
544
+ // causing param mismatches when the Cypher references nested edge properties.
545
+ params[paramName] = spec.map((item) => {
546
+ const edge = item.edge;
547
+ if (!edge)
548
+ return item;
549
+ const cleaned = {};
550
+ for (const [k, v] of Object.entries(edge))
551
+ if (v !== undefined)
552
+ cleaned[k] = v;
553
+ return { ...item, edge: cleaned };
554
+ });
555
+ lines.push('WITH n');
556
+ lines.push(`UNWIND $${paramName} AS connItem`);
557
+ lines.push(`MATCH (target:${targetLabelStr})`);
558
+ // Build WHERE from the first item's structure to determine the path
559
+ const firstItem = spec[0];
560
+ const whereConditions = this.extractConnectWhereConditions(firstItem, 'target', 'connItem');
561
+ if (whereConditions.length > 0)
562
+ lines.push(`WHERE ${whereConditions.join(' AND ')}`);
563
+ const mergePattern = this.buildRelPattern('n', relDef, 'target');
564
+ lines.push(`MERGE ${mergePattern}`);
565
+ // Handle edge properties from first item structure
566
+ const edgeProps = this.extractEdgeProperties(firstItem);
567
+ if (edgeProps.length > 0) {
568
+ // Use bare target var (no label) since target is already bound by MATCH
569
+ assertSafeIdentifier(relDef.type, 'relationship type');
570
+ const escapedRelType = escapeIdentifier(relDef.type);
571
+ const mergeWithVar = relDef.direction === 'OUT'
572
+ ? `(n)-[r:${escapedRelType}]->(target)`
573
+ : `(n)<-[r:${escapedRelType}]-(target)`;
574
+ // Replace MERGE line with one that has a variable
575
+ lines[lines.length - 1] = `MERGE ${mergeWithVar}`;
576
+ // r.prop is relationship property (escape), connItem.edge.prop is parameter map (no escape)
577
+ const setItems = edgeProps.map((p) => `r.${escapeIdentifier(p)} = connItem.edge.${p}`);
578
+ lines.push(`SET ${setItems.join(', ')}`);
579
+ }
580
+ }
581
+ }
582
+ else {
583
+ // Single connect
584
+ const connectSpec = spec;
585
+ const whereSpec = connectSpec.where;
586
+ const edgeInput = connectSpec.edge;
587
+ lines.push('WITH n');
588
+ lines.push(`MATCH (target:${targetLabelStr})`);
589
+ const matchConditions = this.buildConnectionWhereConditions(whereSpec, 'target', `connect_${fieldName}`, params, targetNodeDef);
590
+ if (matchConditions.length > 0)
591
+ lines.push(`WHERE ${matchConditions.join(' AND ')}`);
592
+ if (edgeInput && Object.keys(edgeInput).length > 0) {
593
+ // Use bare target var (no label) since target is already bound by MATCH
594
+ assertSafeIdentifier(relDef.type, 'relationship type');
595
+ const escapedRelType = escapeIdentifier(relDef.type);
596
+ const mergeWithVar = relDef.direction === 'OUT'
597
+ ? `(n)-[r:${escapedRelType}]->(target)`
598
+ : `(n)<-[r:${escapedRelType}]-(target)`;
599
+ lines.push(`MERGE ${mergeWithVar}`);
600
+ const setItems = [];
601
+ for (const [prop, val] of Object.entries(edgeInput)) {
602
+ if (val === undefined)
603
+ continue;
604
+ const paramName = `connect_${fieldName}_edge_${prop}`;
605
+ setItems.push(`r.${escapeIdentifier(prop)} = $${paramName}`);
606
+ params[paramName] = val;
607
+ }
608
+ if (setItems.length > 0)
609
+ lines.push(`SET ${setItems.join(', ')}`);
610
+ }
611
+ else {
612
+ const mergePattern = this.buildRelPattern('n', relDef, 'target');
613
+ lines.push(`MERGE ${mergePattern}`);
614
+ }
615
+ }
616
+ }
617
+ return lines;
618
+ }
619
+ buildDisconnects(disconnect, nodeDef, params) {
620
+ const lines = [];
621
+ for (const [fieldName, spec] of Object.entries(disconnect)) {
622
+ const relDef = nodeDef.relationships.get(fieldName);
623
+ if (!relDef)
624
+ continue;
625
+ const targetNodeDef = resolveTargetDef(relDef.target, this.schema) ?? undefined;
626
+ // Normalize spec to an array of disconnect items
627
+ const specItems = Array.isArray(spec) ? spec : [spec];
628
+ for (let si = 0; si < specItems.length; si++) {
629
+ const item = specItems[si];
630
+ const relVar = `r_${fieldName}_${si}`;
631
+ lines.push('WITH n');
632
+ if (item == null ||
633
+ (typeof item === 'object' && Object.keys(item).length === 0)) {
634
+ // Blanket disconnect — remove all relationships of this type
635
+ const pattern = this.buildRelPattern('n', relDef, '', relVar);
636
+ lines.push(`OPTIONAL MATCH ${pattern}`);
637
+ lines.push(`DELETE ${relVar}`);
638
+ }
639
+ else {
640
+ // Specific disconnect with conditions
641
+ const disconnectSpec = item;
642
+ const whereSpec = disconnectSpec.where;
643
+ const targetVar = `target_${fieldName}_${si}`;
644
+ const pattern = this.buildRelPattern('n', relDef, targetVar, relVar, 'auto');
645
+ lines.push(`OPTIONAL MATCH ${pattern}`);
646
+ const conditions = this.buildConnectionWhereConditions(whereSpec, targetVar, `disconnect_${fieldName}_${si}`, params, targetNodeDef);
647
+ if (conditions.length > 0)
648
+ lines.push(`WHERE ${conditions.join(' AND ')}`);
649
+ lines.push(`DELETE ${relVar}`);
650
+ }
651
+ }
652
+ }
653
+ return lines;
654
+ }
655
+ /**
656
+ * Process nested relationship operations within an update input.
657
+ * Handles: create, connect, disconnect, update, and arrays of these.
658
+ */
659
+ buildUpdateRelationships(update, nodeDef, sourceVar, prefix, params, depth = 0, ancestorVars = []) {
660
+ if (depth > 5)
661
+ throw new OGMError(`[neo4j-ogm] buildUpdateRelationships: max nesting depth (5) exceeded for prefix "${prefix}". ` +
662
+ 'Nested relationship mutations beyond depth 5 are not supported.');
663
+ const lines = [];
664
+ let varCounter = 0;
665
+ // All WITH clauses must carry forward ancestor vars + current source
666
+ const allVars = [...new Set([...ancestorVars, sourceVar])];
667
+ const withClause = `WITH ${allVars.join(', ')}`;
668
+ for (const [key, value] of Object.entries(update)) {
669
+ const relDef = nodeDef.relationships.get(key);
670
+ if (!relDef || value == null)
671
+ continue;
672
+ // Check if target is a union type — if so, value uses per-member keys
673
+ // e.g., doseTypes: { InfusionStandard: [{ update: { node: { minValue: 1 } } }] }
674
+ const isUnionTarget = !this.schema.nodes.has(relDef.target) &&
675
+ this.schema.unions?.has(relDef.target);
676
+ if (isUnionTarget) {
677
+ const unionMembers = this.schema.unions.get(relDef.target);
678
+ const memberEntries = value;
679
+ for (const [memberKey, memberValue] of Object.entries(memberEntries)) {
680
+ if (!unionMembers.includes(memberKey))
681
+ continue;
682
+ const memberNodeDef = this.schema.nodes.get(memberKey);
683
+ if (!memberNodeDef || memberValue == null)
684
+ continue;
685
+ // Recurse: treat as if the relationship points directly to the member type
686
+ const memberRelDef = {
687
+ ...relDef,
688
+ target: memberKey,
689
+ };
690
+ // Build a pseudo-update object with just this relationship
691
+ // so we can reuse buildUpdateRelationships logic
692
+ const memberItems = Array.isArray(memberValue)
693
+ ? memberValue
694
+ : [memberValue];
695
+ const memberUpdate = { [key]: memberItems };
696
+ const memberNodeDef2 = {
697
+ ...nodeDef,
698
+ relationships: new Map([[key, memberRelDef]]),
699
+ };
700
+ const memberLines = this.buildUpdateRelationships(memberUpdate, memberNodeDef2, sourceVar, `${prefix}_${key}_${memberKey}`, params, depth + 1, ancestorVars);
701
+ lines.push(...memberLines);
702
+ }
703
+ continue;
704
+ }
705
+ const targetNodeDef = this.schema.nodes.get(relDef.target);
706
+ if (!targetNodeDef)
707
+ continue;
708
+ const targetLabelStr = this.getCachedLabelString(targetNodeDef);
709
+ const relPrefix = `${prefix}_${key}`;
710
+ // Value can be an array (e.g., chapters: [{ create: ... }, { update: ... }])
711
+ // or an object (e.g., author: { connect: ... })
712
+ const items = Array.isArray(value)
713
+ ? value
714
+ : [value];
715
+ for (let i = 0; i < items.length; i++) {
716
+ const item = items[i];
717
+ const itemPrefix = `${relPrefix}_${i}`;
718
+ // --- delete (detach delete target nodes) ---
719
+ // Wrapped in CALL subquery to prevent OPTIONAL MATCH from multiplying
720
+ // pipeline rows (N existing nodes → N rows → N subsequent CREATEs).
721
+ if (item.delete) {
722
+ const deleteItems = Array.isArray(item.delete)
723
+ ? item.delete
724
+ : [item.delete];
725
+ for (let di = 0; di < deleteItems.length; di++) {
726
+ const deleteSpec = deleteItems[di];
727
+ const whereSpec = deleteSpec.where;
728
+ const delTarget = `${sourceVar}_del${varCounter}`;
729
+ const delRel = `r_del_${key}_${i}_${di}`;
730
+ varCounter++;
731
+ lines.push(withClause);
732
+ lines.push(`CALL {`);
733
+ lines.push(withClause);
734
+ const pattern = this.buildRelPattern(sourceVar, relDef, delTarget, delRel, 'auto');
735
+ lines.push(`OPTIONAL MATCH ${pattern}`);
736
+ if (whereSpec && Object.keys(whereSpec).length > 0) {
737
+ const nodeWhere = (whereSpec.node ?? whereSpec);
738
+ const conditions = [];
739
+ for (const [prop, val] of Object.entries(nodeWhere)) {
740
+ assertSafeIdentifier(prop, 'delete where property');
741
+ const paramName = `${itemPrefix}_del${di}_${prop}`;
742
+ conditions.push(`${delTarget}.${escapeIdentifier(prop)} = $${paramName}`);
743
+ params[paramName] = val;
744
+ }
745
+ if (conditions.length > 0)
746
+ lines.push(`WHERE ${conditions.join(' AND ')}`);
747
+ }
748
+ lines.push(`DETACH DELETE ${delTarget}`);
749
+ lines.push(`RETURN count(*) AS _del_${key}_${i}_${di}`);
750
+ lines.push(`}`);
751
+ }
752
+ }
753
+ // --- create ---
754
+ if (item.create) {
755
+ const createItems = Array.isArray(item.create)
756
+ ? item.create
757
+ : [item.create];
758
+ for (let ci = 0; ci < createItems.length; ci++) {
759
+ const createSpec = createItems[ci];
760
+ const nodeSpec = (createSpec.node ?? createSpec);
761
+ const createVar = `${sourceVar}_cr${varCounter}`;
762
+ varCounter++;
763
+ const { propString, propParams } = this.buildCreateProperties(nodeSpec, targetNodeDef, `${itemPrefix}_create${ci}`);
764
+ // Handle nested relationship creates within the created node
765
+ const nestedRelLines = this.buildCreateRelationships(nodeSpec, targetNodeDef, createVar, `${itemPrefix}_create${ci}`, params, allVars);
766
+ const propsClause = propString.length > 0
767
+ ? ` { ${propString} }`
768
+ : ` { ${this.buildGeneratedIdClause(targetNodeDef)} }`;
769
+ const relPattern = this.buildRelPattern(sourceVar, relDef, createVar);
770
+ // Wrap in CALL subquery so failed connect MATCHes don't kill the outer pipeline
771
+ const hasNestedOps = nestedRelLines.length > 0;
772
+ if (hasNestedOps) {
773
+ lines.push(withClause);
774
+ lines.push(`CALL {`);
775
+ lines.push(withClause);
776
+ }
777
+ else
778
+ lines.push(withClause);
779
+ lines.push(`CREATE (${createVar}:${targetLabelStr}${propsClause})`);
780
+ Object.assign(params, propParams);
781
+ lines.push(`CREATE ${relPattern}`);
782
+ if (hasNestedOps) {
783
+ lines.push(...nestedRelLines);
784
+ lines.push(`RETURN count(*) AS _cc_${key}_${ci}`);
785
+ lines.push(`}`);
786
+ }
787
+ }
788
+ }
789
+ // --- disconnect (runs before connect so "disconnect all + connect new" works) ---
790
+ if (item.disconnect) {
791
+ const disconnectItems = Array.isArray(item.disconnect)
792
+ ? item.disconnect
793
+ : [item.disconnect];
794
+ for (let di = 0; di < disconnectItems.length; di++) {
795
+ const disconnectSpec = disconnectItems[di];
796
+ const whereSpec = disconnectSpec.where;
797
+ const relVar = `r_disc_${key}_${i}_${di}`;
798
+ lines.push(withClause);
799
+ if (!whereSpec || Object.keys(whereSpec).length === 0) {
800
+ const pattern = this.buildRelPattern(sourceVar, relDef, '', relVar);
801
+ lines.push(`OPTIONAL MATCH ${pattern}`);
802
+ lines.push(`DELETE ${relVar}`);
803
+ }
804
+ else {
805
+ const nodeWhere = (whereSpec.node ?? whereSpec);
806
+ const discTarget = `${sourceVar}_disc${varCounter}`;
807
+ varCounter++;
808
+ const pattern = this.buildRelPattern(sourceVar, relDef, discTarget, relVar, 'auto');
809
+ lines.push(`OPTIONAL MATCH ${pattern}`);
810
+ const conditions = this.buildNodeWhereConditions(nodeWhere, discTarget, `${itemPrefix}_disc_${di}`, params, targetNodeDef);
811
+ if (conditions.length > 0)
812
+ lines.push(`WHERE ${conditions.join(' AND ')}`);
813
+ lines.push(`DELETE ${relVar}`);
814
+ }
815
+ }
816
+ }
817
+ // --- connect ---
818
+ if (item.connect) {
819
+ const connectItems = Array.isArray(item.connect)
820
+ ? item.connect
821
+ : [item.connect];
822
+ for (let ci = 0; ci < connectItems.length; ci++) {
823
+ const connectSpec = connectItems[ci];
824
+ const whereSpec = connectSpec.where;
825
+ const nodeWhere = (whereSpec?.node ?? whereSpec ?? {});
826
+ const edgeInput = connectSpec.edge;
827
+ // Wrap in CALL subquery so a failed MATCH doesn't kill the outer pipeline
828
+ lines.push(withClause);
829
+ lines.push(`CALL {`);
830
+ lines.push(withClause);
831
+ const connectVar = `${sourceVar}_conn${varCounter}`;
832
+ varCounter++;
833
+ lines.push(`MATCH (${connectVar}:${targetLabelStr})`);
834
+ const conditions = this.buildNodeWhereConditions(nodeWhere, connectVar, `${itemPrefix}_conn${ci}`, params, targetNodeDef);
835
+ if (conditions.length > 0)
836
+ lines.push(`WHERE ${conditions.join(' AND ')}`);
837
+ if (edgeInput && Object.keys(edgeInput).length > 0) {
838
+ // Use bare target var (no label) since connectVar is already bound by MATCH
839
+ const relVar = `r_conn_${key}_${ci}`;
840
+ assertSafeIdentifier(relDef.type, 'relationship type');
841
+ const escapedRelType = escapeIdentifier(relDef.type);
842
+ const mergePattern = relDef.direction === 'OUT'
843
+ ? `(${sourceVar})-[${relVar}:${escapedRelType}]->(${connectVar})`
844
+ : `(${sourceVar})<-[${relVar}:${escapedRelType}]-(${connectVar})`;
845
+ lines.push(`MERGE ${mergePattern}`);
846
+ const setItems = [];
847
+ for (const [prop, val] of Object.entries(edgeInput)) {
848
+ if (val === undefined)
849
+ continue;
850
+ assertSafeIdentifier(prop, 'edge property');
851
+ const paramName = `${itemPrefix}_conn${ci}_edge_${prop}`;
852
+ setItems.push(`r_conn_${key}_${ci}.${escapeIdentifier(prop)} = $${paramName}`);
853
+ params[paramName] = val;
854
+ }
855
+ if (setItems.length > 0)
856
+ lines.push(`SET ${setItems.join(', ')}`);
857
+ }
858
+ else {
859
+ const relPattern = this.buildRelPattern(sourceVar, relDef, connectVar);
860
+ lines.push(`MERGE ${relPattern}`);
861
+ }
862
+ lines.push(`RETURN count(*) AS _cn_${key}_${ci}`);
863
+ lines.push(`}`);
864
+ }
865
+ }
866
+ // --- update (nested) ---
867
+ if (item.update) {
868
+ const updateSpec = item.update;
869
+ const edgeUpdate = updateSpec.edge;
870
+ const nodeUpdate = (updateSpec.node ??
871
+ (edgeUpdate ? {} : updateSpec));
872
+ const updateWhere = item.where;
873
+ const updateVar = `${sourceVar}_u${varCounter}`;
874
+ const relVar = `r_${key}_${i}`;
875
+ varCounter++;
876
+ // Pre-compute SET clauses for node properties
877
+ const setClauses = [];
878
+ const setParams = {};
879
+ for (const [prop, val] of Object.entries(nodeUpdate)) {
880
+ if (targetNodeDef.relationships.has(prop))
881
+ continue;
882
+ if (val === undefined)
883
+ continue;
884
+ assertSafeIdentifier(prop, 'nested update property');
885
+ const paramName = `${itemPrefix}_set_${prop}`;
886
+ setClauses.push(`${updateVar}.${escapeIdentifier(prop)} = $${paramName}`);
887
+ setParams[paramName] = val;
888
+ }
889
+ // Pre-compute SET clauses for edge (relationship) properties
890
+ const edgeClauses = [];
891
+ const edgeParams = {};
892
+ if (edgeUpdate)
893
+ for (const [prop, val] of Object.entries(edgeUpdate)) {
894
+ if (val === undefined)
895
+ continue;
896
+ assertSafeIdentifier(prop, 'edge update property');
897
+ const paramName = `${itemPrefix}_edge_${prop}`;
898
+ edgeClauses.push(`${relVar}.${escapeIdentifier(prop)} = $${paramName}`);
899
+ edgeParams[paramName] = val;
900
+ }
901
+ // Pre-compute nested relationship operations
902
+ const nestedParams = {};
903
+ const nestedRelLines = this.buildUpdateRelationships(nodeUpdate, targetNodeDef, updateVar, itemPrefix, nestedParams, depth + 1, allVars);
904
+ // Only emit the MATCH block if there's actual work to do
905
+ const hasWork = setClauses.length > 0 ||
906
+ edgeClauses.length > 0 ||
907
+ nestedRelLines.length > 0;
908
+ if (hasWork) {
909
+ // Wrap in CALL subquery so a failed MATCH doesn't kill the outer pipeline.
910
+ // RETURN count(*) ensures exactly 1 row is returned even if MATCH finds nothing.
911
+ // WITH is required before CALL (Neo4j syntax rule after CREATE/DELETE/SET).
912
+ lines.push(withClause);
913
+ lines.push(`CALL {`);
914
+ lines.push(withClause);
915
+ const relArrow = this.buildRelPattern(sourceVar, relDef, updateVar, relVar, 'auto');
916
+ lines.push(`MATCH ${relArrow}`);
917
+ // WHERE on the target
918
+ if (updateWhere) {
919
+ const nodeWhere = (updateWhere.node ?? updateWhere);
920
+ const conditions = [];
921
+ for (const [prop, val] of Object.entries(nodeWhere)) {
922
+ assertSafeIdentifier(prop, 'update where property');
923
+ const paramName = `${itemPrefix}_where_${prop}`;
924
+ conditions.push(`${updateVar}.${escapeIdentifier(prop)} = $${paramName}`);
925
+ params[paramName] = val;
926
+ }
927
+ if (conditions.length > 0)
928
+ lines.push(`WHERE ${conditions.join(' AND ')}`);
929
+ }
930
+ if (setClauses.length > 0)
931
+ lines.push(`SET ${setClauses.join(', ')}`);
932
+ Object.assign(params, setParams);
933
+ if (edgeClauses.length > 0)
934
+ lines.push(`SET ${edgeClauses.join(', ')}`);
935
+ Object.assign(params, edgeParams);
936
+ Object.assign(params, nestedParams);
937
+ lines.push(...nestedRelLines);
938
+ lines.push(`RETURN count(*) AS _uc_${key}_${i}`);
939
+ lines.push(`}`);
940
+ }
941
+ }
942
+ }
943
+ }
944
+ return lines;
945
+ }
946
+ /**
947
+ * Parse a property key that may contain an operator suffix (e.g. `id_IN`, `name_CONTAINS`).
948
+ * Returns the base property name and the Cypher expression template.
949
+ */
950
+ parseOperatorSuffix(key) {
951
+ for (const [suffix, template] of MutationCompiler.OPERATOR_SUFFIXES)
952
+ if (key.endsWith(suffix))
953
+ return { baseProp: key.slice(0, -suffix.length), template };
954
+ // No suffix — simple equality
955
+ return { baseProp: key, template: '%v = $%p' };
956
+ }
957
+ /**
958
+ * Build WHERE conditions for connect/disconnect operations.
959
+ * Handles operator suffixes (_IN, _NOT, _NOT_IN, _CONTAINS, etc.),
960
+ * NOT blocks, and relationship filters (_SOME, _NONE, _ALL, _SINGLE).
961
+ *
962
+ * @param targetNodeDef - Optional node definition for the target node.
963
+ * Required to resolve relationship filters like `configurations_SOME`.
964
+ * When omitted, relationship filters are silently skipped.
965
+ */
966
+ buildNodeWhereConditions(nodeWhere, targetVar, prefix, params, targetNodeDef) {
967
+ const conditions = [];
968
+ for (const [prop, val] of Object.entries(nodeWhere))
969
+ if (prop === 'NOT') {
970
+ const notSpec = val;
971
+ for (const [notProp, notVal] of Object.entries(notSpec)) {
972
+ const { baseProp, template } = this.parseOperatorSuffix(notProp);
973
+ assertSafeIdentifier(baseProp, 'where property');
974
+ const paramName = `${prefix}_NOT_${notProp}`;
975
+ const escapedProp = escapeIdentifier(baseProp);
976
+ let expr;
977
+ if (template === '%v = $%p')
978
+ // Simple equality → negate to <>
979
+ expr = `${targetVar}.${escapedProp} <> $${paramName}`;
980
+ else {
981
+ // Operator suffix — wrap with NOT if not already negated
982
+ const resolved = template
983
+ .replace('%v', `${targetVar}.${escapedProp}`)
984
+ .replace('$%p', `$${paramName}`);
985
+ expr = resolved.startsWith('NOT ') ? resolved : `NOT ${resolved}`;
986
+ }
987
+ conditions.push(expr);
988
+ params[paramName] = notVal;
989
+ }
990
+ }
991
+ else {
992
+ // Check for relationship filter suffixes (_SOME, _NONE, _ALL, _SINGLE)
993
+ const relFilter = this.tryBuildRelationshipFilter(prop, val, targetVar, prefix, params, targetNodeDef);
994
+ if (relFilter) {
995
+ conditions.push(relFilter);
996
+ continue;
997
+ }
998
+ const { baseProp, template } = this.parseOperatorSuffix(prop);
999
+ assertSafeIdentifier(baseProp, 'where property');
1000
+ const paramName = `${prefix}_${prop}`;
1001
+ conditions.push(template
1002
+ .replace('%v', `${targetVar}.${escapeIdentifier(baseProp)}`)
1003
+ .replace('$%p', `$${paramName}`));
1004
+ params[paramName] = val;
1005
+ }
1006
+ return conditions;
1007
+ }
1008
+ /**
1009
+ * Build WHERE conditions from a connection-level WHERE spec.
1010
+ * Handles `node`, `NOT`, `AND`, `OR` at the connection level.
1011
+ *
1012
+ * Connection WHERE format: `{ node: {...}, NOT: { node: {...} }, AND: [...], OR: [...] }`
1013
+ */
1014
+ buildConnectionWhereConditions(whereSpec, targetVar, prefix, params, targetNodeDef) {
1015
+ if (!whereSpec || Object.keys(whereSpec).length === 0)
1016
+ return [];
1017
+ const conditions = [];
1018
+ // Direct node conditions
1019
+ if (whereSpec.node) {
1020
+ const nodeConditions = this.buildNodeWhereConditions(whereSpec.node, targetVar, prefix, params, targetNodeDef);
1021
+ conditions.push(...nodeConditions);
1022
+ }
1023
+ // NOT wrapper — negate the inner connection conditions
1024
+ if (whereSpec.NOT) {
1025
+ const notSpec = whereSpec.NOT;
1026
+ const innerConditions = this.buildConnectionWhereConditions(notSpec, targetVar, `${prefix}_NOT`, params, targetNodeDef);
1027
+ if (innerConditions.length > 0)
1028
+ conditions.push(`NOT (${innerConditions.join(' AND ')})`);
1029
+ }
1030
+ // AND — all sub-conditions must hold
1031
+ if (Array.isArray(whereSpec.AND))
1032
+ for (let i = 0; i < whereSpec.AND.length; i++) {
1033
+ const sub = this.buildConnectionWhereConditions(whereSpec.AND[i], targetVar, `${prefix}_AND${i}`, params, targetNodeDef);
1034
+ conditions.push(...sub);
1035
+ }
1036
+ // OR — any sub-condition must hold
1037
+ if (Array.isArray(whereSpec.OR)) {
1038
+ const orClauses = [];
1039
+ for (let i = 0; i < whereSpec.OR.length; i++) {
1040
+ const sub = this.buildConnectionWhereConditions(whereSpec.OR[i], targetVar, `${prefix}_OR${i}`, params, targetNodeDef);
1041
+ if (sub.length > 0)
1042
+ orClauses.push(`(${sub.join(' AND ')})`);
1043
+ }
1044
+ if (orClauses.length > 0)
1045
+ conditions.push(`(${orClauses.join(' OR ')})`);
1046
+ }
1047
+ // Fallback: if no recognized keys, treat the whole spec as node conditions
1048
+ // (backward compatibility for simple `{ id: '...' }` style)
1049
+ if (!whereSpec.node && !whereSpec.NOT && !whereSpec.AND && !whereSpec.OR) {
1050
+ // Reject unsupported connection WHERE keys (e.g., "edge")
1051
+ if (whereSpec.edge !== undefined)
1052
+ throw new OGMError(`Connection WHERE with "edge" filters is not supported in mutations. ` +
1053
+ `Only "node", "NOT", "AND", "OR" keys or direct property conditions are allowed.`);
1054
+ const fallbackConditions = this.buildNodeWhereConditions(whereSpec, targetVar, prefix, params, targetNodeDef);
1055
+ conditions.push(...fallbackConditions);
1056
+ }
1057
+ return conditions;
1058
+ }
1059
+ /**
1060
+ * Try to compile a relationship filter condition (_SOME, _NONE, _ALL, _SINGLE).
1061
+ * Returns the Cypher expression string or null if the key is not a relationship filter.
1062
+ */
1063
+ tryBuildRelationshipFilter(key, value, nodeVar, prefix, params, targetNodeDef) {
1064
+ if (!targetNodeDef || typeof value !== 'object' || value === null)
1065
+ return null;
1066
+ // Detect suffix and extract field name
1067
+ let suffix = null;
1068
+ let fieldName = '';
1069
+ for (const s of RELATIONSHIP_SUFFIXES)
1070
+ if (key.endsWith(s)) {
1071
+ suffix = s;
1072
+ fieldName = key.slice(0, -s.length);
1073
+ break;
1074
+ }
1075
+ // If no suffix, check if bare key is a relationship (defaults to _SOME)
1076
+ if (!suffix) {
1077
+ const relDef = targetNodeDef.relationships.get(key);
1078
+ if (!relDef)
1079
+ return null;
1080
+ suffix = '_SOME';
1081
+ fieldName = key;
1082
+ }
1083
+ const relDef = targetNodeDef.relationships.get(fieldName);
1084
+ if (!relDef)
1085
+ return null;
1086
+ const relTargetDef = resolveTargetDef(relDef.target, this.schema);
1087
+ if (!relTargetDef)
1088
+ return null;
1089
+ const relVar = `rf${this.relFilterCounter++}`;
1090
+ const innerWhere = value;
1091
+ // Build the MATCH pattern
1092
+ const edgePart = `[:${escapeIdentifier(relDef.type)}]`;
1093
+ const escapedTarget = escapeIdentifier(relDef.target);
1094
+ const pattern = relDef.direction === 'IN'
1095
+ ? `(${nodeVar})<-${edgePart}-(${relVar}:${escapedTarget})`
1096
+ : `(${nodeVar})-${edgePart}->(${relVar}:${escapedTarget})`;
1097
+ // Recursively compile inner conditions
1098
+ const innerConditions = this.buildNodeWhereConditions(innerWhere, relVar, `${prefix}_${fieldName}`, params, relTargetDef);
1099
+ const innerClause = innerConditions.length > 0
1100
+ ? ` WHERE ${innerConditions.join(' AND ')}`
1101
+ : '';
1102
+ switch (suffix) {
1103
+ case '_SOME':
1104
+ return `EXISTS { MATCH ${pattern}${innerClause} }`;
1105
+ case '_NONE':
1106
+ return `NOT EXISTS { MATCH ${pattern}${innerClause} }`;
1107
+ case '_ALL':
1108
+ // Double negation: all match ≡ none fail to match
1109
+ if (!innerClause)
1110
+ return '';
1111
+ return `NOT EXISTS { MATCH ${pattern} WHERE NOT (${innerConditions.join(' AND ')}) }`;
1112
+ case '_SINGLE':
1113
+ return `size([${relVar} IN [(${pattern}${innerClause} | ${relVar})] | ${relVar}]) = 1`;
1114
+ default:
1115
+ return null;
1116
+ }
1117
+ }
1118
+ /**
1119
+ * Build a Cypher relationship pattern string.
1120
+ *
1121
+ * @param sourceVar - Cypher variable for the source node
1122
+ * @param relDef - Relationship definition
1123
+ * @param targetVar - Cypher variable for the target node (empty string for anonymous)
1124
+ * @param relVar - Optional relationship variable (omit for anonymous `[:TYPE]`)
1125
+ * @param targetLabel - Target label override. Pass explicit label, `'auto'` to use
1126
+ * `relDef.target`, or omit for no label on the target node.
1127
+ */
1128
+ buildRelPattern(sourceVar, relDef, targetVar, relVar, targetLabel) {
1129
+ assertSafeIdentifier(relDef.type, 'relationship type');
1130
+ const escapedType = escapeIdentifier(relDef.type);
1131
+ const relPart = relVar ? `[${relVar}:${escapedType}]` : `[:${escapedType}]`;
1132
+ let targetPart;
1133
+ if (targetLabel) {
1134
+ const label = targetLabel === 'auto' ? relDef.target : targetLabel;
1135
+ const escapedLabel = assertSafeLabel(label);
1136
+ targetPart = `(${targetVar}:${escapedLabel})`;
1137
+ }
1138
+ else if (targetVar)
1139
+ targetPart = `(${targetVar})`;
1140
+ else
1141
+ targetPart = `()`;
1142
+ if (relDef.direction === 'OUT')
1143
+ return `(${sourceVar})-${relPart}->${targetPart}`;
1144
+ return `(${sourceVar})<-${relPart}-${targetPart}`;
1145
+ }
1146
+ buildGeneratedIdClause(nodeDef) {
1147
+ const parts = [];
1148
+ for (const [, propDef] of nodeDef.properties)
1149
+ if (propDef.isGenerated)
1150
+ parts.push(`${escapeIdentifier(propDef.name)}: randomUUID()`);
1151
+ return parts.join(', ');
1152
+ }
1153
+ getCascadeVarName(index) {
1154
+ return `cascade_${index}`;
1155
+ }
1156
+ /**
1157
+ * Check if any item in an array connect spec has relationship filters in its WHERE clause.
1158
+ * Relationship filters require EXISTS subqueries which can't use UNWIND param references.
1159
+ */
1160
+ arrayConnectHasRelationshipFilters(items, targetNodeDef) {
1161
+ for (const item of items) {
1162
+ const whereSpec = item.where;
1163
+ if (!whereSpec)
1164
+ continue;
1165
+ const nodeWhere = (whereSpec.node ?? whereSpec);
1166
+ for (const key of Object.keys(nodeWhere)) {
1167
+ // Strip operator suffixes to get the base field name
1168
+ let fieldName = key;
1169
+ for (const s of RELATIONSHIP_SUFFIXES)
1170
+ if (key.endsWith(s)) {
1171
+ fieldName = key.slice(0, -s.length);
1172
+ break;
1173
+ }
1174
+ // If the base field name is a relationship on the target, it's a rel filter
1175
+ if (targetNodeDef.relationships.has(fieldName))
1176
+ return true;
1177
+ // Also check if the value is an object (nested filter on a relationship)
1178
+ const val = nodeWhere[key];
1179
+ if (typeof val === 'object' &&
1180
+ val !== null &&
1181
+ !Array.isArray(val) &&
1182
+ targetNodeDef.relationships.has(key))
1183
+ return true;
1184
+ }
1185
+ }
1186
+ return false;
1187
+ }
1188
+ extractConnectWhereConditions(item, targetVar, itemVar) {
1189
+ const conditions = [];
1190
+ const whereSpec = item.where;
1191
+ if (!whereSpec)
1192
+ return conditions;
1193
+ const nodeWhere = (whereSpec.node ?? whereSpec);
1194
+ for (const key of Object.keys(nodeWhere)) {
1195
+ const { baseProp, template } = this.parseOperatorSuffix(key);
1196
+ assertSafeIdentifier(baseProp, 'connect where property');
1197
+ // Replace template placeholders with dynamic UNWIND references
1198
+ // Note: itemVar map access is NOT escaped (parameter map keys, not Cypher identifiers)
1199
+ const valueRef = `${itemVar}.where.node.${key}`;
1200
+ conditions.push(template
1201
+ .replace('%v', `${targetVar}.${escapeIdentifier(baseProp)}`)
1202
+ .replace('$%p', valueRef));
1203
+ }
1204
+ return conditions;
1205
+ }
1206
+ extractEdgeProperties(item) {
1207
+ const edgeSpec = item.edge;
1208
+ if (!edgeSpec)
1209
+ return [];
1210
+ const keys = Object.keys(edgeSpec).filter((k) => edgeSpec[k] !== undefined);
1211
+ for (const key of keys)
1212
+ assertSafeIdentifier(key, 'edge property');
1213
+ return keys;
1214
+ }
1215
+ }
1216
+ /**
1217
+ * Operator suffixes supported in connect/disconnect WHERE conditions.
1218
+ * Maps suffix → Cypher operator template (use %v for variable, %p for param).
1219
+ */
1220
+ MutationCompiler.OPERATOR_SUFFIXES = [
1221
+ ['_NOT_IN', 'NOT %v IN $%p'],
1222
+ ['_IN', '%v IN $%p'],
1223
+ ['_NOT_CONTAINS', 'NOT %v CONTAINS $%p'],
1224
+ ['_CONTAINS', '%v CONTAINS $%p'],
1225
+ ['_NOT_STARTS_WITH', 'NOT %v STARTS WITH $%p'],
1226
+ ['_STARTS_WITH', '%v STARTS WITH $%p'],
1227
+ ['_NOT_ENDS_WITH', 'NOT %v ENDS WITH $%p'],
1228
+ ['_ENDS_WITH', '%v ENDS WITH $%p'],
1229
+ ['_LTE', '%v <= $%p'],
1230
+ ['_LT', '%v < $%p'],
1231
+ ['_GTE', '%v >= $%p'],
1232
+ ['_GT', '%v > $%p'],
1233
+ ['_NOT', '%v <> $%p'],
1234
+ ];
1235
+ //# sourceMappingURL=mutation.compiler.js.map