@nicia-ai/typegraph 0.45.0 → 0.46.1

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 (384) hide show
  1. package/dist/backend/drizzle/indexes.cjs +11 -11
  2. package/dist/backend/drizzle/indexes.d.cts +1 -1
  3. package/dist/backend/drizzle/indexes.d.ts +1 -1
  4. package/dist/backend/drizzle/indexes.js +7 -7
  5. package/dist/backend/index.cjs +53 -54
  6. package/dist/backend/index.d.cts +3 -3
  7. package/dist/backend/index.d.ts +3 -3
  8. package/dist/backend/index.js +13 -14
  9. package/dist/backend/postgres/index.cjs +29 -30
  10. package/dist/backend/postgres/index.d.cts +7 -7
  11. package/dist/backend/postgres/index.d.ts +7 -7
  12. package/dist/backend/postgres/index.js +16 -17
  13. package/dist/backend/postgres/pglite-store.cjs +48 -42
  14. package/dist/backend/postgres/pglite-store.cjs.map +1 -1
  15. package/dist/backend/postgres/pglite-store.d.cts +15 -9
  16. package/dist/backend/postgres/pglite-store.d.ts +15 -9
  17. package/dist/backend/postgres/pglite-store.js +44 -38
  18. package/dist/backend/postgres/pglite-store.js.map +1 -1
  19. package/dist/backend/postgres/pglite.cjs +18 -19
  20. package/dist/backend/postgres/pglite.d.cts +4 -4
  21. package/dist/backend/postgres/pglite.d.ts +4 -4
  22. package/dist/backend/postgres/pglite.js +17 -18
  23. package/dist/backend/sqlite/index.cjs +26 -27
  24. package/dist/backend/sqlite/index.d.cts +6 -6
  25. package/dist/backend/sqlite/index.d.ts +6 -6
  26. package/dist/backend/sqlite/index.js +14 -15
  27. package/dist/backend/sqlite/libsql.cjs +21 -22
  28. package/dist/backend/sqlite/libsql.cjs.map +1 -1
  29. package/dist/backend/sqlite/libsql.d.cts +15 -8
  30. package/dist/backend/sqlite/libsql.d.ts +15 -8
  31. package/dist/backend/sqlite/libsql.js +17 -18
  32. package/dist/backend/sqlite/libsql.js.map +1 -1
  33. package/dist/backend/sqlite/local-store.cjs +49 -43
  34. package/dist/backend/sqlite/local-store.cjs.map +1 -1
  35. package/dist/backend/sqlite/local-store.d.cts +15 -10
  36. package/dist/backend/sqlite/local-store.d.ts +15 -10
  37. package/dist/backend/sqlite/local-store.js +45 -39
  38. package/dist/backend/sqlite/local-store.js.map +1 -1
  39. package/dist/backend/sqlite/local.cjs +19 -20
  40. package/dist/backend/sqlite/local.d.cts +3 -3
  41. package/dist/backend/sqlite/local.d.ts +3 -3
  42. package/dist/backend/sqlite/local.js +17 -18
  43. package/dist/{chunk-S3ZRKJHY.js → chunk-2LG2XB2M.js} +3 -3
  44. package/dist/{chunk-S3ZRKJHY.js.map → chunk-2LG2XB2M.js.map} +1 -1
  45. package/dist/{chunk-7PF2L3H6.cjs → chunk-2NA5DYLO.cjs} +4 -4
  46. package/dist/{chunk-7PF2L3H6.cjs.map → chunk-2NA5DYLO.cjs.map} +1 -1
  47. package/dist/chunk-2Z2Z3ZD6.js +58 -0
  48. package/dist/chunk-2Z2Z3ZD6.js.map +1 -0
  49. package/dist/{chunk-VVQM4CZD.cjs → chunk-3DHZCOT7.cjs} +20 -20
  50. package/dist/{chunk-VVQM4CZD.cjs.map → chunk-3DHZCOT7.cjs.map} +1 -1
  51. package/dist/{chunk-JXUTCSXX.cjs → chunk-4UGOKMI5.cjs} +250 -158
  52. package/dist/chunk-4UGOKMI5.cjs.map +1 -0
  53. package/dist/{chunk-RFRM3Q5K.js → chunk-5262ZMTR.js} +14 -5
  54. package/dist/chunk-5262ZMTR.js.map +1 -0
  55. package/dist/{chunk-QRNYDV5Z.js → chunk-5763DULU.js} +214 -69
  56. package/dist/chunk-5763DULU.js.map +1 -0
  57. package/dist/{chunk-BGYJW37L.js → chunk-5UVRGXD3.js} +277 -7
  58. package/dist/chunk-5UVRGXD3.js.map +1 -0
  59. package/dist/{chunk-QR24DJDF.cjs → chunk-62ZP23AW.cjs} +15 -15
  60. package/dist/{chunk-QR24DJDF.cjs.map → chunk-62ZP23AW.cjs.map} +1 -1
  61. package/dist/{chunk-ZI6VQSZA.cjs → chunk-65E3XWGF.cjs} +724 -577
  62. package/dist/chunk-65E3XWGF.cjs.map +1 -0
  63. package/dist/{chunk-S4KQ5LV4.js → chunk-775A4ZMN.js} +40 -6
  64. package/dist/chunk-775A4ZMN.js.map +1 -0
  65. package/dist/{chunk-J2ICGNHO.cjs → chunk-7BQXPCGN.cjs} +830 -169
  66. package/dist/chunk-7BQXPCGN.cjs.map +1 -0
  67. package/dist/{chunk-FJMPR3OH.cjs → chunk-7EO3TCQB.cjs} +33 -7
  68. package/dist/chunk-7EO3TCQB.cjs.map +1 -0
  69. package/dist/{chunk-DXEEHSGN.js → chunk-7VQXLT5O.js} +436 -43
  70. package/dist/chunk-7VQXLT5O.js.map +1 -0
  71. package/dist/chunk-A4OC4DVN.cjs +12 -0
  72. package/dist/{chunk-4SZ6XPTG.cjs.map → chunk-A4OC4DVN.cjs.map} +1 -1
  73. package/dist/{chunk-TDXIBCGY.cjs → chunk-A7J2YDDI.cjs} +5 -3
  74. package/dist/chunk-A7J2YDDI.cjs.map +1 -0
  75. package/dist/{chunk-55ITBQJI.js → chunk-AKLS3BYZ.js} +56 -5
  76. package/dist/chunk-AKLS3BYZ.js.map +1 -0
  77. package/dist/{chunk-D7PG5D5K.js → chunk-AMKPPFUO.js} +32 -6
  78. package/dist/chunk-AMKPPFUO.js.map +1 -0
  79. package/dist/{chunk-WUFASQUO.js → chunk-ASTI57H3.js} +3 -3
  80. package/dist/{chunk-WUFASQUO.js.map → chunk-ASTI57H3.js.map} +1 -1
  81. package/dist/{chunk-4QIKLIGG.js → chunk-BKCUOV7A.js} +15 -5
  82. package/dist/chunk-BKCUOV7A.js.map +1 -0
  83. package/dist/{chunk-JQXSZHF2.cjs → chunk-BSFJMMV3.cjs} +134 -7
  84. package/dist/chunk-BSFJMMV3.cjs.map +1 -0
  85. package/dist/{chunk-4GUTQPVO.js → chunk-C23WSN5E.js} +185 -24
  86. package/dist/chunk-C23WSN5E.js.map +1 -0
  87. package/dist/{chunk-QLUXRWKL.js → chunk-CBTIUB76.js} +3 -3
  88. package/dist/{chunk-QLUXRWKL.js.map → chunk-CBTIUB76.js.map} +1 -1
  89. package/dist/chunk-CMWHLBVN.cjs +2082 -0
  90. package/dist/chunk-CMWHLBVN.cjs.map +1 -0
  91. package/dist/{chunk-QB6VMOJ2.cjs → chunk-CXBHOOIQ.cjs} +45 -10
  92. package/dist/chunk-CXBHOOIQ.cjs.map +1 -0
  93. package/dist/{chunk-FL5UQRES.js → chunk-DCQVHAYQ.js} +5 -5
  94. package/dist/{chunk-FL5UQRES.js.map → chunk-DCQVHAYQ.js.map} +1 -1
  95. package/dist/chunk-DTVWX77L.cjs +3432 -0
  96. package/dist/chunk-DTVWX77L.cjs.map +1 -0
  97. package/dist/chunk-DYATYRY4.js +47 -0
  98. package/dist/chunk-DYATYRY4.js.map +1 -0
  99. package/dist/chunk-E4WNQBEF.cjs +217 -0
  100. package/dist/chunk-E4WNQBEF.cjs.map +1 -0
  101. package/dist/{chunk-G6Q5UM7U.js → chunk-FWJKWH6U.js} +5 -3
  102. package/dist/chunk-FWJKWH6U.js.map +1 -0
  103. package/dist/chunk-GF3KXUTW.js +3393 -0
  104. package/dist/chunk-GF3KXUTW.js.map +1 -0
  105. package/dist/{chunk-VTN3C3CH.js → chunk-HFHTMECK.js} +7 -7
  106. package/dist/{chunk-VTN3C3CH.js.map → chunk-HFHTMECK.js.map} +1 -1
  107. package/dist/{chunk-FLEQ2HBK.js → chunk-I5MOXA4H.js} +20 -14
  108. package/dist/chunk-I5MOXA4H.js.map +1 -0
  109. package/dist/{chunk-4MB3AQAQ.js → chunk-J3WZ3FA2.js} +77 -3
  110. package/dist/chunk-J3WZ3FA2.js.map +1 -0
  111. package/dist/{chunk-UR7W5YQK.js → chunk-JBQQ7THH.js} +5 -3
  112. package/dist/chunk-JBQQ7THH.js.map +1 -0
  113. package/dist/{chunk-TKP5T34P.js → chunk-JXA2ZP2Q.js} +6 -16
  114. package/dist/chunk-JXA2ZP2Q.js.map +1 -0
  115. package/dist/{chunk-GYA4JRP5.js → chunk-JXXDZTMN.js} +2 -2
  116. package/dist/chunk-JXXDZTMN.js.map +1 -0
  117. package/dist/chunk-JZUR7ZWE.js +254 -0
  118. package/dist/chunk-JZUR7ZWE.js.map +1 -0
  119. package/dist/{chunk-42PYUGLS.js → chunk-K5ELX2GT.js} +108 -8
  120. package/dist/chunk-K5ELX2GT.js.map +1 -0
  121. package/dist/{chunk-Q6EDCUXV.js → chunk-L65OU2U3.js} +556 -204
  122. package/dist/chunk-L65OU2U3.js.map +1 -0
  123. package/dist/{chunk-D54WH24V.cjs → chunk-LBJKZXDE.cjs} +115 -13
  124. package/dist/chunk-LBJKZXDE.cjs.map +1 -0
  125. package/dist/{chunk-FQ7ZPOTZ.js → chunk-LGHCOKTT.js} +637 -493
  126. package/dist/chunk-LGHCOKTT.js.map +1 -0
  127. package/dist/chunk-M3WP7RQZ.js +736 -0
  128. package/dist/chunk-M3WP7RQZ.js.map +1 -0
  129. package/dist/{chunk-4MEWSUVM.cjs → chunk-M4BDU5RX.cjs} +12 -12
  130. package/dist/chunk-M4BDU5RX.cjs.map +1 -0
  131. package/dist/chunk-MCLYU6BN.cjs +332 -0
  132. package/dist/chunk-MCLYU6BN.cjs.map +1 -0
  133. package/dist/{chunk-MIJKEVV7.js → chunk-MSY7FSX4.js} +37 -4
  134. package/dist/chunk-MSY7FSX4.js.map +1 -0
  135. package/dist/{chunk-H7VE3FL4.cjs → chunk-MUK4BU7K.cjs} +519 -125
  136. package/dist/chunk-MUK4BU7K.cjs.map +1 -0
  137. package/dist/{chunk-D56XG4EI.cjs → chunk-MXJDTH5B.cjs} +56 -45
  138. package/dist/chunk-MXJDTH5B.cjs.map +1 -0
  139. package/dist/{chunk-5EKPX6QU.js → chunk-MZLPX2R7.js} +8 -9
  140. package/dist/chunk-MZLPX2R7.js.map +1 -0
  141. package/dist/chunk-N66HCGCY.cjs +57 -0
  142. package/dist/chunk-N66HCGCY.cjs.map +1 -0
  143. package/dist/{chunk-CPEL3YIR.cjs → chunk-NFCVHQ5P.cjs} +55 -22
  144. package/dist/chunk-NFCVHQ5P.cjs.map +1 -0
  145. package/dist/{chunk-C4F7VJ5D.cjs → chunk-NI72YOJB.cjs} +84 -2
  146. package/dist/chunk-NI72YOJB.cjs.map +1 -0
  147. package/dist/{chunk-BNS75YNF.cjs → chunk-NK5Q7UNE.cjs} +10 -20
  148. package/dist/chunk-NK5Q7UNE.cjs.map +1 -0
  149. package/dist/{chunk-TKORQCG4.js → chunk-NLI2AVEF.js} +386 -862
  150. package/dist/chunk-NLI2AVEF.js.map +1 -0
  151. package/dist/{chunk-YMTXMPV6.js → chunk-NMWDXFPH.js} +4 -4
  152. package/dist/{chunk-YMTXMPV6.js.map → chunk-NMWDXFPH.js.map} +1 -1
  153. package/dist/{chunk-J7LXTUTP.cjs → chunk-PF6F5NLJ.cjs} +604 -1094
  154. package/dist/chunk-PF6F5NLJ.cjs.map +1 -0
  155. package/dist/{chunk-NAAMUYMS.cjs → chunk-PNN7RHVR.cjs} +58 -48
  156. package/dist/chunk-PNN7RHVR.cjs.map +1 -0
  157. package/dist/{chunk-YOALGKRE.cjs → chunk-PNTCATDE.cjs} +3528 -1763
  158. package/dist/chunk-PNTCATDE.cjs.map +1 -0
  159. package/dist/{chunk-6DCB664D.js → chunk-PQBQG7IM.js} +3 -3
  160. package/dist/{chunk-6DCB664D.js.map → chunk-PQBQG7IM.js.map} +1 -1
  161. package/dist/{chunk-UCQ3BFSY.cjs → chunk-PT7RIRIZ.cjs} +684 -332
  162. package/dist/chunk-PT7RIRIZ.cjs.map +1 -0
  163. package/dist/{chunk-ACSVZPK7.cjs → chunk-PWLJ77UV.cjs} +5 -3
  164. package/dist/chunk-PWLJ77UV.cjs.map +1 -0
  165. package/dist/chunk-Q3G6A4I3.cjs +755 -0
  166. package/dist/chunk-Q3G6A4I3.cjs.map +1 -0
  167. package/dist/{chunk-Z3WO6MND.cjs → chunk-RMQLSPA2.cjs} +297 -27
  168. package/dist/chunk-RMQLSPA2.cjs.map +1 -0
  169. package/dist/{chunk-SJDMVITW.cjs → chunk-RT5ZY4XA.cjs} +6 -5
  170. package/dist/chunk-RT5ZY4XA.cjs.map +1 -0
  171. package/dist/{chunk-ID4WXTKL.js → chunk-S6PW7VV5.js} +2382 -617
  172. package/dist/chunk-S6PW7VV5.js.map +1 -0
  173. package/dist/{chunk-4GUBTVPV.js → chunk-T4XNKYXB.js} +780 -126
  174. package/dist/chunk-T4XNKYXB.js.map +1 -0
  175. package/dist/{chunk-YDWLUT5V.cjs → chunk-TQR4KBY2.cjs} +323 -178
  176. package/dist/chunk-TQR4KBY2.cjs.map +1 -0
  177. package/dist/{chunk-OOA273IJ.cjs → chunk-U5SON7R2.cjs} +27 -27
  178. package/dist/{chunk-OOA273IJ.cjs.map → chunk-U5SON7R2.cjs.map} +1 -1
  179. package/dist/{chunk-IX64T5OL.cjs → chunk-U63GDNRC.cjs} +32 -26
  180. package/dist/chunk-U63GDNRC.cjs.map +1 -0
  181. package/dist/{chunk-WDBXJOU5.cjs → chunk-UDISNXDL.cjs} +56 -47
  182. package/dist/chunk-UDISNXDL.cjs.map +1 -0
  183. package/dist/{chunk-EFEOOCJ3.js → chunk-UV3SN74I.js} +5 -5
  184. package/dist/{chunk-EFEOOCJ3.js.map → chunk-UV3SN74I.js.map} +1 -1
  185. package/dist/{chunk-U5W4LKZV.cjs → chunk-VCRRKBLN.cjs} +9 -9
  186. package/dist/{chunk-U5W4LKZV.cjs.map → chunk-VCRRKBLN.cjs.map} +1 -1
  187. package/dist/{chunk-ISLPNELU.js → chunk-VGTJPTIC.js} +16 -5
  188. package/dist/chunk-VGTJPTIC.js.map +1 -0
  189. package/dist/chunk-VLT6PEWR.cjs +60 -0
  190. package/dist/chunk-VLT6PEWR.cjs.map +1 -0
  191. package/dist/chunk-VQH4VAWD.cjs +12 -0
  192. package/dist/{chunk-B3F2D3SP.cjs.map → chunk-VQH4VAWD.cjs.map} +1 -1
  193. package/dist/{chunk-SNXVDZAJ.js → chunk-W6OG3HDG.js} +5 -4
  194. package/dist/chunk-W6OG3HDG.js.map +1 -0
  195. package/dist/chunk-WDGA7IYB.js +207 -0
  196. package/dist/chunk-WDGA7IYB.js.map +1 -0
  197. package/dist/chunk-WQZLUGVR.js +2057 -0
  198. package/dist/chunk-WQZLUGVR.js.map +1 -0
  199. package/dist/{chunk-EKFUG4G7.cjs → chunk-XBYVV3MP.cjs} +60 -5
  200. package/dist/chunk-XBYVV3MP.cjs.map +1 -0
  201. package/dist/{chunk-5GAONX5G.cjs → chunk-XCAGZAKP.cjs} +7 -7
  202. package/dist/{chunk-5GAONX5G.cjs.map → chunk-XCAGZAKP.cjs.map} +1 -1
  203. package/dist/chunk-XL7SLCXS.js +309 -0
  204. package/dist/chunk-XL7SLCXS.js.map +1 -0
  205. package/dist/{chunk-SVJ5GEXW.cjs → chunk-XUO3KQFO.cjs} +2 -2
  206. package/dist/chunk-XUO3KQFO.cjs.map +1 -0
  207. package/dist/{chunk-F4NHQHEZ.cjs → chunk-YAAM3M2S.cjs} +10 -10
  208. package/dist/{chunk-F4NHQHEZ.cjs.map → chunk-YAAM3M2S.cjs.map} +1 -1
  209. package/dist/{chunk-RUQS6M7G.js → chunk-YBFHZUXX.js} +6 -6
  210. package/dist/chunk-YBFHZUXX.js.map +1 -0
  211. package/dist/{chunk-KMXWZLQX.cjs → chunk-YBQOHKHN.cjs} +99 -100
  212. package/dist/chunk-YBQOHKHN.cjs.map +1 -0
  213. package/dist/{chunk-PVUI6KGR.js → chunk-YDFDBE6D.js} +93 -6
  214. package/dist/chunk-YDFDBE6D.js.map +1 -0
  215. package/dist/{chunk-P7GTYOLB.cjs → chunk-YVWGEQJE.cjs} +297 -135
  216. package/dist/chunk-YVWGEQJE.cjs.map +1 -0
  217. package/dist/{chunk-QT6V3YRH.js → chunk-YWGVVALJ.js} +3 -3
  218. package/dist/{chunk-QT6V3YRH.js.map → chunk-YWGVVALJ.js.map} +1 -1
  219. package/dist/core/index.cjs +40 -40
  220. package/dist/core/index.d.cts +4 -5
  221. package/dist/core/index.d.ts +4 -5
  222. package/dist/core/index.js +8 -8
  223. package/dist/{ddl-BG4J805L.d.cts → ddl-BJcOp4WZ.d.cts} +3 -3
  224. package/dist/{ddl-CgiS9QLj.d.ts → ddl-BXfGHbPN.d.ts} +3 -3
  225. package/dist/graph-extension/index.cjs +26 -22
  226. package/dist/graph-extension/index.d.cts +16 -6
  227. package/dist/graph-extension/index.d.ts +16 -6
  228. package/dist/graph-extension/index.js +7 -7
  229. package/dist/graph-merge/index.cjs +2459 -654
  230. package/dist/graph-merge/index.cjs.map +1 -1
  231. package/dist/graph-merge/index.d.cts +170 -33
  232. package/dist/graph-merge/index.d.ts +170 -33
  233. package/dist/graph-merge/index.js +2269 -467
  234. package/dist/graph-merge/index.js.map +1 -1
  235. package/dist/{index-C3y_98iN.d.cts → index-BSHvKfok.d.ts} +221 -10
  236. package/dist/{index-BBxZCUdu.d.ts → index-DxaYCv8Y.d.cts} +221 -10
  237. package/dist/index.cjs +330 -268
  238. package/dist/index.cjs.map +1 -1
  239. package/dist/index.d.cts +590 -576
  240. package/dist/index.d.ts +590 -576
  241. package/dist/index.js +73 -49
  242. package/dist/index.js.map +1 -1
  243. package/dist/indexes/index.cjs +17 -17
  244. package/dist/indexes/index.d.cts +4 -4
  245. package/dist/indexes/index.d.ts +4 -4
  246. package/dist/indexes/index.js +10 -10
  247. package/dist/interchange/index.cjs +165 -51
  248. package/dist/interchange/index.cjs.map +1 -1
  249. package/dist/interchange/index.d.cts +261 -16
  250. package/dist/interchange/index.d.ts +261 -16
  251. package/dist/interchange/index.js +128 -25
  252. package/dist/interchange/index.js.map +1 -1
  253. package/dist/{manager-YoLEfcc1.d.ts → manager-CfZAimti.d.ts} +36 -5
  254. package/dist/{manager-KS7CGQQT.d.cts → manager-HGk6E_HW.d.cts} +36 -5
  255. package/dist/{materialize-indexes-Bhhyb1B3.d.ts → materialize-indexes-6A7dWGtb.d.cts} +10 -2
  256. package/dist/{materialize-indexes-OHRWypW6.d.cts → materialize-indexes-BMJP6Tms.d.ts} +10 -2
  257. package/dist/{postgres-DpkW3R5Y.d.cts → postgres-DFYWENdR.d.cts} +1961 -439
  258. package/dist/{postgres-DwqyIniy.d.ts → postgres-e7a9TdvR.d.ts} +1961 -439
  259. package/dist/profiler/index.cjs +15 -15
  260. package/dist/profiler/index.d.cts +9 -10
  261. package/dist/profiler/index.d.ts +9 -10
  262. package/dist/profiler/index.js +4 -4
  263. package/dist/provenance/index.cjs +46 -48
  264. package/dist/provenance/index.cjs.map +1 -1
  265. package/dist/provenance/index.d.cts +7 -7
  266. package/dist/provenance/index.d.ts +7 -7
  267. package/dist/provenance/index.js +12 -14
  268. package/dist/provenance/index.js.map +1 -1
  269. package/dist/schema/index.cjs +50 -48
  270. package/dist/schema/index.d.cts +9 -16
  271. package/dist/schema/index.d.ts +9 -16
  272. package/dist/schema/index.js +23 -21
  273. package/dist/{searchable-DLWEfC4a.d.ts → searchable-9OQoPuRs.d.ts} +1 -2
  274. package/dist/{searchable-CIZL8Hyw.d.cts → searchable-CIje5XYD.d.cts} +1 -2
  275. package/dist/{sqlite-DY0WODUK.d.cts → sqlite-DerU5tc7.d.ts} +2029 -347
  276. package/dist/{sqlite-L-VIRzqA.d.ts → sqlite-DitnISa9.d.cts} +2029 -347
  277. package/dist/{store-view-A85FFRnk.d.cts → store-B0h6x7wo.d.cts} +1289 -767
  278. package/dist/{store-view-66p92IPi.d.ts → store-ClQ-MZQS.d.ts} +1289 -767
  279. package/dist/{temporal-BNbjXqer.d.cts → temporal-Bn-Fi8FJ.d.cts} +1 -1
  280. package/dist/{temporal-Bob6Nfbp.d.ts → temporal-BtM4UALv.d.ts} +1 -1
  281. package/dist/{types-DeORZhxn.d.cts → types-CoIx8HX5.d.cts} +1 -1
  282. package/dist/{types-Ch9a0Dx6.d.ts → types-HkveCCx0.d.cts} +726 -5
  283. package/dist/{types-Ch9a0Dx6.d.cts → types-HkveCCx0.d.ts} +726 -5
  284. package/dist/{types-ClCzT-dT.d.ts → types-KFfk-8Ah.d.ts} +1 -1
  285. package/dist/{where-DPw52ksb.d.cts → where--fk7_0kv.d.cts} +1 -1
  286. package/dist/{where-kWUZPvEA.d.ts → where-ws5hf-Ov.d.ts} +1 -1
  287. package/package.json +3 -3
  288. package/dist/chunk-2L54C2C2.js +0 -144
  289. package/dist/chunk-2L54C2C2.js.map +0 -1
  290. package/dist/chunk-35S6P5ZU.js +0 -27
  291. package/dist/chunk-35S6P5ZU.js.map +0 -1
  292. package/dist/chunk-42PYUGLS.js.map +0 -1
  293. package/dist/chunk-4GUBTVPV.js.map +0 -1
  294. package/dist/chunk-4GUTQPVO.js.map +0 -1
  295. package/dist/chunk-4MB3AQAQ.js.map +0 -1
  296. package/dist/chunk-4MEWSUVM.cjs.map +0 -1
  297. package/dist/chunk-4QIKLIGG.js.map +0 -1
  298. package/dist/chunk-4SZ6XPTG.cjs +0 -12
  299. package/dist/chunk-55ITBQJI.js.map +0 -1
  300. package/dist/chunk-5EKPX6QU.js.map +0 -1
  301. package/dist/chunk-6RYPD3NJ.cjs +0 -1282
  302. package/dist/chunk-6RYPD3NJ.cjs.map +0 -1
  303. package/dist/chunk-764JHTLT.cjs +0 -15
  304. package/dist/chunk-764JHTLT.cjs.map +0 -1
  305. package/dist/chunk-ACSVZPK7.cjs.map +0 -1
  306. package/dist/chunk-B3F2D3SP.cjs +0 -12
  307. package/dist/chunk-BGYJW37L.js.map +0 -1
  308. package/dist/chunk-BNS75YNF.cjs.map +0 -1
  309. package/dist/chunk-C4F7VJ5D.cjs.map +0 -1
  310. package/dist/chunk-CBKG6HEP.js +0 -1263
  311. package/dist/chunk-CBKG6HEP.js.map +0 -1
  312. package/dist/chunk-CPEL3YIR.cjs.map +0 -1
  313. package/dist/chunk-D54WH24V.cjs.map +0 -1
  314. package/dist/chunk-D56XG4EI.cjs.map +0 -1
  315. package/dist/chunk-D7PG5D5K.js.map +0 -1
  316. package/dist/chunk-DI74SMXS.cjs +0 -29
  317. package/dist/chunk-DI74SMXS.cjs.map +0 -1
  318. package/dist/chunk-DK2S42XB.js +0 -77
  319. package/dist/chunk-DK2S42XB.js.map +0 -1
  320. package/dist/chunk-DRACN6XG.js +0 -27
  321. package/dist/chunk-DRACN6XG.js.map +0 -1
  322. package/dist/chunk-DXEEHSGN.js.map +0 -1
  323. package/dist/chunk-EKFUG4G7.cjs.map +0 -1
  324. package/dist/chunk-EQ7JKLZO.cjs +0 -157
  325. package/dist/chunk-EQ7JKLZO.cjs.map +0 -1
  326. package/dist/chunk-FJMPR3OH.cjs.map +0 -1
  327. package/dist/chunk-FLEQ2HBK.js.map +0 -1
  328. package/dist/chunk-FQ7ZPOTZ.js.map +0 -1
  329. package/dist/chunk-G6Q5UM7U.js.map +0 -1
  330. package/dist/chunk-GYA4JRP5.js.map +0 -1
  331. package/dist/chunk-H7VE3FL4.cjs.map +0 -1
  332. package/dist/chunk-ID4WXTKL.js.map +0 -1
  333. package/dist/chunk-ISLPNELU.js.map +0 -1
  334. package/dist/chunk-ITE4IXAK.js +0 -25
  335. package/dist/chunk-ITE4IXAK.js.map +0 -1
  336. package/dist/chunk-IX64T5OL.cjs.map +0 -1
  337. package/dist/chunk-J2ICGNHO.cjs.map +0 -1
  338. package/dist/chunk-J7LXTUTP.cjs.map +0 -1
  339. package/dist/chunk-JQXSZHF2.cjs.map +0 -1
  340. package/dist/chunk-JXUTCSXX.cjs.map +0 -1
  341. package/dist/chunk-KCO5EFJU.js +0 -79
  342. package/dist/chunk-KCO5EFJU.js.map +0 -1
  343. package/dist/chunk-KKYELPZ2.cjs +0 -83
  344. package/dist/chunk-KKYELPZ2.cjs.map +0 -1
  345. package/dist/chunk-KMXWZLQX.cjs.map +0 -1
  346. package/dist/chunk-MIJKEVV7.js.map +0 -1
  347. package/dist/chunk-NAAMUYMS.cjs.map +0 -1
  348. package/dist/chunk-P7GTYOLB.cjs.map +0 -1
  349. package/dist/chunk-PVUI6KGR.js.map +0 -1
  350. package/dist/chunk-Q6EDCUXV.js.map +0 -1
  351. package/dist/chunk-QB6VMOJ2.cjs.map +0 -1
  352. package/dist/chunk-QC4MCOFV.cjs +0 -83
  353. package/dist/chunk-QC4MCOFV.cjs.map +0 -1
  354. package/dist/chunk-QMQLUA66.js +0 -12
  355. package/dist/chunk-QMQLUA66.js.map +0 -1
  356. package/dist/chunk-QRNYDV5Z.js.map +0 -1
  357. package/dist/chunk-RFRM3Q5K.js.map +0 -1
  358. package/dist/chunk-RUQS6M7G.js.map +0 -1
  359. package/dist/chunk-S4KQ5LV4.js.map +0 -1
  360. package/dist/chunk-SJDMVITW.cjs.map +0 -1
  361. package/dist/chunk-SNXVDZAJ.js.map +0 -1
  362. package/dist/chunk-SVJ5GEXW.cjs.map +0 -1
  363. package/dist/chunk-TDXIBCGY.cjs.map +0 -1
  364. package/dist/chunk-TKORQCG4.js.map +0 -1
  365. package/dist/chunk-TKP5T34P.js.map +0 -1
  366. package/dist/chunk-TRF4EBJT.cjs +0 -30
  367. package/dist/chunk-TRF4EBJT.cjs.map +0 -1
  368. package/dist/chunk-UCQ3BFSY.cjs.map +0 -1
  369. package/dist/chunk-UR7W5YQK.js.map +0 -1
  370. package/dist/chunk-V34A6R5V.js +0 -134
  371. package/dist/chunk-V34A6R5V.js.map +0 -1
  372. package/dist/chunk-WDAVPX46.js +0 -81
  373. package/dist/chunk-WDAVPX46.js.map +0 -1
  374. package/dist/chunk-WDBXJOU5.cjs.map +0 -1
  375. package/dist/chunk-YDWLUT5V.cjs.map +0 -1
  376. package/dist/chunk-YOALGKRE.cjs.map +0 -1
  377. package/dist/chunk-Z3WO6MND.cjs.map +0 -1
  378. package/dist/chunk-ZDH3DX3C.cjs +0 -30
  379. package/dist/chunk-ZDH3DX3C.cjs.map +0 -1
  380. package/dist/chunk-ZI6VQSZA.cjs.map +0 -1
  381. package/dist/chunk-ZTAJL4DT.cjs +0 -85
  382. package/dist/chunk-ZTAJL4DT.cjs.map +0 -1
  383. package/dist/define-graph-BGrJahvp.d.ts +0 -173
  384. package/dist/define-graph-alREY2wj.d.cts +0 -173
@@ -1,32 +1,32 @@
1
- import { createEdgeRowMapper, createNodeRowMapper, SQLITE_ROW_MAPPER_CONFIG, POSTGRES_ROW_MAPPER_CONFIG } from './chunk-YMTXMPV6.js';
2
- import { rowToNode, executeNodeBulkFindByIndex, executeNodeBulkFindByConstraint, executeNodeFindByConstraint, executeNodeBulkGetOrCreateByConstraint, executeNodeGetOrCreateByConstraint, executeNodeHardDelete, executeNodeDelete, nodeUpsertDirtyCheck, executeNodeUpsertUpdate, executeNodeUpdateWhere, executeNodeUpdate, executeNodeCreateNoReturnBatch, executeNodeCreateBatch, executeNodeCreate, rowToEdge, lockSchemaVersionForStoreWrite, runInWriteTransaction, checkCardinalityConstraint, runHookedWriteOperation, rowToNodeMeta, rowToEdgeMeta } from './chunk-Q6EDCUXV.js';
3
- import { isParameterRef, resolveParameterValueType, fieldRef, buildFieldBuilderForTypeInfo, stringField, createFulltextAccessor, storeRuntime, CURRENT_READ_INSTANT_PLACEHOLDER, withPinnedReadInstant, STORE_RUNTIME, assertRevisionTrackableBackend, createRecordedBackend, readRecordedClock, ensureRevisionOrigin, TRANSACTION_RUNTIME, withRecordedFlushObserver, recordedCaptureRequiresCallbackTransactionError, assertRecordedCaptureTransactionIsolation, createRecordedTransactionScope, lockRecordedGraphWrite, createGraphBackendProjection, generateId, assertCurrentRecordedSchema, baseField, objectField, arrayField, dateField, numberField, computeFulltextContent, AUTO_REFRESH_STATISTICS_ROW_THRESHOLD, advanceRevisionClock, runInsertBatch, edgeInsertDispatch, runInsertBatchReturning, projectBackendMembers, sqlValueList, getEmbeddingFields, getNodeRowsByIds, getEdgeRowsByIds, compileKindFilter, currentReadInstant, compileTemporalFilter, validateEdgeEndpoints, closeRecordedHardDeletedKind, buildPredicateIndex, compileFieldValue, recordedBindParamBudget, extractVectorSimilarityPredicates, extractFulltextMatchPredicates, currentReadInstantFor, extractTemporalOptions, compilePredicateExpression, getPredicatesForAlias, getFieldPointer, getNodeKindsForAlias, getHybridTargetAlias, throwHistoryUnsafeSqlAccess, throwRevisionTrackingUnsafeSqlAccess, compilePredicateClauses } from './chunk-TKORQCG4.js';
4
- import { chunk, groupBy } from './chunk-35S6P5ZU.js';
5
- import { DEFAULT_SQL_SCHEMA, requireSqlSchema, recordedReadSchemaFor, createSqlSchema, requireExternalRecordedReadSource, createRecordedReadBinding, vectorSlotKey, recordedReadSqlSchema, requireRecordedReadBinding, ALIAS_CTE_PREFIX, EMBEDDINGS_CTE_ALIAS, FULLTEXT_CTE_ALIAS, HYBRID_CANDIDATES_CTE_ALIAS } from './chunk-MIJKEVV7.js';
6
- import { runOptionallyInTransaction, createTransactionReadBackend, rowPropsToObject, createBackendOverlay, INTERNAL_TEMPORARY_WRITES } from './chunk-55ITBQJI.js';
7
- import { resolveReadCoordinate, withRecordedCoordinate, describeCoordinate, coordinateContext, recordedInstantWallTime, asRecordedInstant, assertValidRecordedInstant } from './chunk-2L54C2C2.js';
8
- import { loadAndMergeGraphExtensionDocument, ensureSchema, loadAndVerifyGraph, buildKindRegistry, getSchemaChanges, requiresMigration, mergeGraphExtension, commitNewSchemaVersion, planRemovals, stripGraphExtension, applyDeprecatedKinds, loadActiveSchemaWithBootstrap, parseSerializedSchema, extensionKindNames, serializeSchemaProperties, buildGraphExtensionOntologyKeySet, compileTimeOntologyKey, commitNewSchemaVersionIfKindsEmpty, serializeIndexDeclaration } from './chunk-FQ7ZPOTZ.js';
9
- import { compareStrings, compareCodePoints } from './chunk-DRACN6XG.js';
10
- import { getTypeName } from './chunk-TKP5T34P.js';
11
- import { validateEdgeProps } from './chunk-DK2S42XB.js';
12
- import { canonicalEqual, sortedReplacer } from './chunk-UR7W5YQK.js';
13
- import { withRecordedRelationsPrecondition, isMissingTableError } from './chunk-V34A6R5V.js';
14
- import { isKnownKind } from './chunk-S4KQ5LV4.js';
15
- import { nowIso, validateOptionalCanonicalIsoDate } from './chunk-KCO5EFJU.js';
16
- import { generateIndexDDL } from './chunk-FL5UQRES.js';
17
- import { SYSTEM_INDEX_DECLARATIONS, resolveSystemIndexTableName, systemIndexName, resolveSystemIndexNames, generateSystemIndexDDL } from './chunk-QLUXRWKL.js';
18
- import { NODE_META_KEYS, EDGE_META_KEYS } from './chunk-FLEQ2HBK.js';
1
+ import { createEdgeRowMapper, createNodeRowMapper, SQLITE_ROW_MAPPER_CONFIG, POSTGRES_ROW_MAPPER_CONFIG } from './chunk-NMWDXFPH.js';
2
+ import { rowToNode, executeNodeBulkFindByIndex, executeNodeBulkFindByConstraint, executeNodeFindByConstraint, executeNodeBulkGetOrCreateByConstraint, executeNodeGetOrCreateByConstraint, executeNodeHardDelete, executeNodeDeleteBatch, executeNodeDelete, nodeUpsertDirtyCheck, executeNodeUpsertUpdate, executeNodeUpdateWhere, executeNodeUpdate, executeNodeCreateNoReturnBatch, executeNodeCreateBatch, executeNodeCreate, rowToEdge, checkCardinalityConstraint, edgeWriteNeedsConstraintFence, withAlreadyExistsTranslation, rowToNodeMeta, rowToEdgeMeta } from './chunk-L65OU2U3.js';
3
+ import { storeRuntime, STORE_RUNTIME, TRANSACTION_RUNTIME, AUTO_REFRESH_STATISTICS_ROW_THRESHOLD, getEmbeddingFields, computeFulltextContent } from './chunk-M3WP7RQZ.js';
4
+ import { loadAndMergeGraphExtensionDocument, serializeSchema, buildKindRegistry, ensureIdentitySchemaStorage, loadAndVerifyGraph, identitySchemaCommitPreflight, getSchemaChanges, requiresMigration, mergeGraphExtension, commitNewSchemaVersion, commitNewSchemaVersionWithPreflight, planRemovals, stripGraphExtension, applyDeprecatedKinds, identityKindCascadeNeeded, ensureSchema, loadActiveSchemaWithBootstrap, parseSerializedSchema, extensionKindNames, serializeSchemaProperties, buildGraphExtensionOntologyKeySet, compileTimeOntologyKey, commitNewSchemaVersionIfKindsEmpty, serializeIndexDeclaration } from './chunk-LGHCOKTT.js';
5
+ import { getTypeName } from './chunk-JXA2ZP2Q.js';
6
+ import { rebuildIdentityClosureForContext, loadCurrentStructuralClasses, loadAssertionsByIds, toTransferAssertion, liveNodeKindsSharingIds, createIdentityFacade, createIdentityReadFacade, runInWriteTransaction, validateIdentityForContext, removeIdentityKindsForContext, readIdentityAssertionsForInterchange, readIdentityAssertionPageAtTarget, lockIdentityGraph, foldIdentityForCreatedNodes, importIdentityAssertionsIntoTarget, assertAffectedIdentityClassesConsistent, applyIdentityChangesForContext, withWriteTransactionSession, lockSchemaVersionForStoreWrite, refKey, detachIdentityForNode, validateEdgeProps, runHookedWriteOperation, encodeTupleKey } from './chunk-GF3KXUTW.js';
7
+ import { isParameterRef, resolveParameterValueType, fieldRef, compareStrings, buildFieldBuilderForTypeInfo, stringField, createFulltextAccessor, CURRENT_READ_INSTANT_PLACEHOLDER, withPinnedReadInstant, compareCodePoints, assertRevisionTrackableBackend, createRecordedBackend, createRecordedTransactionScope, readRecordedClock, ensureRevisionOrigin, withRecordedFlushObserver, recordedCaptureRequiresCallbackTransactionError, assertRecordedCaptureTransactionIsolation, lockRecordedGraphWrite, createGraphBackendProjection, generateId, assertCurrentRecordedSchema, baseField, objectField, arrayField, dateField, numberField, advanceRevisionClock, runInsertBatch, edgeInsertDispatch, runInsertBatchReturning, projectBackendMembers, sqlValueList, getNodeRowsByIds, getEdgeRowsByIds, compileKindFilter, runInsertNoReturn, currentReadInstant, compileTemporalFilter, closeRecordedHardDeletedKind, buildPredicateIndex, historicalIdentityPeerClassQuery, IDENTITY_PEER_CLASS_COLUMNS, compileFieldValue, recordedBindParamBudget, extractVectorSimilarityPredicates, extractFulltextMatchPredicates, currentReadInstantFor, extractTemporalOptions, compilePredicateExpression, getPredicatesForAlias, getFieldPointer, getNodeKindsForAlias, getHybridTargetAlias, throwHistoryUnsafeSqlAccess, throwRevisionTrackingUnsafeSqlAccess, compilePredicateClauses } from './chunk-NLI2AVEF.js';
8
+ import { canonicalEqual, sortedReplacer } from './chunk-JBQQ7THH.js';
9
+ import { withRecordedRelationsPrecondition, chunk, postgresReadWriteRefusedSqlState, isMissingTableError, groupBy, postgresTemporaryTableUnavailableSqlState } from './chunk-JZUR7ZWE.js';
10
+ import { createSqlSchema, requireSqlSchema, DEFAULT_SQL_SCHEMA, recordedReadSchemaFor, requireExternalRecordedReadSource, createRecordedReadBinding, vectorSlotKey, recordedReadSqlSchema, requireRecordedReadBinding, ALIAS_CTE_PREFIX, EMBEDDINGS_CTE_ALIAS, FULLTEXT_CTE_ALIAS, HYBRID_CANDIDATES_CTE_ALIAS } from './chunk-MSY7FSX4.js';
11
+ import { runOptionallyInTransaction, createTransactionReadBackend, createBackendOverlay, rowPropsToObject, INTERNAL_TEMPORARY_WRITES } from './chunk-AKLS3BYZ.js';
12
+ import { isKnownKind } from './chunk-775A4ZMN.js';
13
+ import { validateEdgeEndpoints } from './chunk-WDGA7IYB.js';
14
+ import { resolveReadCoordinate, withRecordedCoordinate, describeCoordinate, coordinateContext, nowIso, recordedInstantWallTime, asRecordedInstant, validateOptionalCanonicalIsoDate, assertOrderedValidityWindow, assertWritableValidityWindow, assertValidRecordedInstant, optionalRecordedInstantParts, preservesImmutableLowerBound, isCanonicalIsoDate, statedBoundMatchesStored } from './chunk-XL7SLCXS.js';
15
+ import { generateIndexDDL } from './chunk-DCQVHAYQ.js';
16
+ import { SYSTEM_INDEX_DECLARATIONS, resolveSystemIndexTableName, systemIndexName, resolveSystemIndexNames, generateSystemIndexDDL } from './chunk-CBTIUB76.js';
17
+ import { NODE_META_KEYS, EDGE_META_KEYS } from './chunk-I5MOXA4H.js';
19
18
  import { sha256Hex, fnv1aBase36 } from './chunk-K34OPSJK.js';
20
19
  import { DEFAULT_PAGINATION_LIMIT, DEFAULT_STREAM_BATCH_SIZE, SQLITE_PATH_DELIMITER, PG_ARRAY_START, PG_ARRAY_END, PG_PATH_ELEMENT_SEPARATOR, MAX_PG_IDENTIFIER_LENGTH } from './chunk-2EBMPJBO.js';
21
- import { getDialect, assertVectorMinScore, vectorScoreExpression, vectorMinScoreCondition } from './chunk-PVUI6KGR.js';
22
- import { isSqlPlaceholder, bindSqlValue, markAnnIndexScan, asCompiledSelectSql, isRawExecutable, renderSql, annIndexScanTypes, sql, asCompiledRowsSql, markForceCustomPlan, asCompiledTemporaryStatementSql, asCompiledStatementSql } from './chunk-6DCB664D.js';
23
- import { createSchemaIntrospector } from './chunk-D7PG5D5K.js';
24
- import { requireDefined, isPresent } from './chunk-QMQLUA66.js';
25
- import { getSearchableFields, resolveEmbeddingFields, resolveGraphVectorSlots, resolveDeclaredFulltextLanguage } from './chunk-PC2UBAUB.js';
26
- import { IncompatibleChangeError } from './chunk-4GUBTVPV.js';
27
- import { validateProjectionField } from './chunk-42PYUGLS.js';
28
- import { ConfigurationError, UnsupportedPredicateError, ValidationError, KindNotFoundError, EndpointError, EagerMaterializationError, CompilerInvariantError, CardinalityError, EdgeNotFoundError, DatabaseOperationError, UnsupportedBackendCapabilityError, GraphAlgorithmConvergenceError, EndpointNotFoundError, InvalidEdgeWeightError } from './chunk-4MB3AQAQ.js';
29
- import { jsonPointer, parseJsonPointer, resolveJsonPointer } from './chunk-G6Q5UM7U.js';
20
+ import { getDialect, assertVectorMinScore, assertApproximateMetricSupported, vectorScoreExpression, vectorMinScoreCondition } from './chunk-YDFDBE6D.js';
21
+ import { isSqlPlaceholder, bindSqlValue, markAnnIndexScan, asCompiledSelectSql, isRawExecutable, renderSql, annIndexScanTypes, sql, asCompiledRowsSql, markForceCustomPlan, asCompiledStatementSql, asCompiledTemporaryStatementSql } from './chunk-PQBQG7IM.js';
22
+ import { createSchemaIntrospector } from './chunk-AMKPPFUO.js';
23
+ import { resolveEmbeddingFields, resolveGraphVectorSlots, resolveDeclaredFulltextLanguage, getSearchableFields } from './chunk-PC2UBAUB.js';
24
+ import { IncompatibleChangeError } from './chunk-T4XNKYXB.js';
25
+ import { META_EDGE_DISJOINT_WITH, META_EDGE_EQUIVALENT_TO, META_EDGE_SAME_AS, META_EDGE_SUB_CLASS_OF } from './chunk-JXXDZTMN.js';
26
+ import { validateProjectionField } from './chunk-K5ELX2GT.js';
27
+ import { ConfigurationError, UnsupportedPredicateError, ValidationError, KindNotFoundError, EndpointError, EagerMaterializationError, ContributionRebuildUnsupportedError, CompilerInvariantError, CardinalityError, DatabaseOperationError, EdgeNotFoundError, MigrationError, EDGE_IDENTITY_MISMATCH_CODE, UnsupportedBackendCapabilityError, EndpointNotFoundError, GraphAlgorithmConvergenceError, InvalidEdgeWeightError } from './chunk-J3WZ3FA2.js';
28
+ import { jsonPointer, parseJsonPointer, resolveJsonPointer } from './chunk-FWJKWH6U.js';
29
+ import { readOwnProperty, requireDefined, createDataKeyedBag, isInteropProbeKey, hasOwnKey, isPresent } from './chunk-DYATYRY4.js';
30
30
 
31
31
  // src/store/collection-surface.ts
32
32
  var CURRENT_ONLY_READ_NAMES = [
@@ -411,6 +411,10 @@ var CoordinatePinnedView = class {
411
411
  constructor(store, coordinate) {
412
412
  this.store = store;
413
413
  this.coordinate = coordinate;
414
+ Object.defineProperty(this, "identity", {
415
+ enumerable: false,
416
+ get: () => storeRuntime(this.store).identityAtCoordinate(this.coordinate)
417
+ });
414
418
  }
415
419
  /** The temporal mode this view reads in. */
416
420
  get mode() {
@@ -532,7 +536,7 @@ var CoordinatePinnedView = class {
532
536
  });
533
537
  }
534
538
  };
535
- var StoreView = class extends CoordinatePinnedView {
539
+ var StoreViewImplementation = class extends CoordinatePinnedView {
536
540
  #nodes;
537
541
  #edges;
538
542
  #search;
@@ -591,7 +595,8 @@ var StoreView = class extends CoordinatePinnedView {
591
595
  return this.#search;
592
596
  }
593
597
  };
594
- var RecordedStoreView = class extends CoordinatePinnedView {
598
+ var StoreView = StoreViewImplementation;
599
+ var RecordedStoreViewImplementation = class extends CoordinatePinnedView {
595
600
  #nodes;
596
601
  #edges;
597
602
  constructor(store, coordinate) {
@@ -650,6 +655,48 @@ var RecordedStoreView = class extends CoordinatePinnedView {
650
655
  return this.#edges;
651
656
  }
652
657
  };
658
+ var RecordedStoreView = RecordedStoreViewImplementation;
659
+
660
+ // src/utils/path.ts
661
+ function parseSqlitePath(path) {
662
+ const emptyPath = `${SQLITE_PATH_DELIMITER}${SQLITE_PATH_DELIMITER}`;
663
+ if (!path || path === emptyPath) return [];
664
+ const trimmed = path.slice(1, -1);
665
+ if (trimmed === "") return [];
666
+ return trimmed.split(SQLITE_PATH_DELIMITER);
667
+ }
668
+ function isSqlitePath(value) {
669
+ return typeof value === "string" && value.startsWith(SQLITE_PATH_DELIMITER) && value.endsWith(SQLITE_PATH_DELIMITER);
670
+ }
671
+ function normalizePath(value) {
672
+ if (Array.isArray(value)) {
673
+ return value;
674
+ }
675
+ if (isSqlitePath(value)) {
676
+ return parseSqlitePath(value);
677
+ }
678
+ if (isPostgresTextArray(value)) {
679
+ return parsePostgresTextArray(value);
680
+ }
681
+ return [];
682
+ }
683
+ var IDENTITY_PATH_TOKEN_SEPARATOR = "";
684
+ function stripIdentityPathTokens(path) {
685
+ return path.map((token) => stripIdentityPathToken(token));
686
+ }
687
+ function stripIdentityPathToken(token) {
688
+ const separatorIndex = token.indexOf(IDENTITY_PATH_TOKEN_SEPARATOR);
689
+ if (separatorIndex === -1) return token;
690
+ return token.slice(separatorIndex + IDENTITY_PATH_TOKEN_SEPARATOR.length);
691
+ }
692
+ function isPostgresTextArray(value) {
693
+ return typeof value === "string" && value.startsWith(PG_ARRAY_START) && value.endsWith(PG_ARRAY_END);
694
+ }
695
+ function parsePostgresTextArray(value) {
696
+ const inner = value.slice(1, -1);
697
+ if (inner === "") return [];
698
+ return inner.split(PG_PATH_ELEMENT_SEPARATOR);
699
+ }
653
700
 
654
701
  // src/query/compiler/emitter/plan-inspector.ts
655
702
  function collectPlanOperations(node, ops) {
@@ -865,9 +912,13 @@ function assertRecursiveEmitterClauseAlignment(logicalPlan, input) {
865
912
  }
866
913
  function emitRecursiveQuerySql(input) {
867
914
  assertRecursiveEmitterClauseAlignment(input.logicalPlan, input);
915
+ const cteList = sql.join(
916
+ [...input.precedingCtes ?? [], input.recursiveCte],
917
+ sql`, `
918
+ );
868
919
  const parts = [
869
920
  sql`WITH RECURSIVE`,
870
- input.recursiveCte,
921
+ cteList,
871
922
  sql`SELECT ${input.projection}`,
872
923
  sql`FROM recursive_cte`,
873
924
  input.depthFilter
@@ -1023,6 +1074,118 @@ function mergeEdgeKinds(traversal) {
1023
1074
  ];
1024
1075
  }
1025
1076
 
1077
+ // src/query/compiler/identity-traversal.ts
1078
+ var IDENTITY_CLASS_CTE_ALIAS = "identity_peer_class";
1079
+ var PEER_CLASS_ALIAS = "identity_peer";
1080
+ var CURRENT_SEED_CLASS_ALIAS = "identity_seed_class";
1081
+ var CURRENT_MEMBER_NODE_ALIAS = "identity_peer_node";
1082
+ function historicalCoordinate(input) {
1083
+ const { ast, temporalFilterPass } = input;
1084
+ const recorded = optionalRecordedInstantParts(
1085
+ ast.recordedAsOf,
1086
+ "recordedAsOf"
1087
+ );
1088
+ if (recorded === void 0 && ast.temporalMode.mode === "current") {
1089
+ return void 0;
1090
+ }
1091
+ return {
1092
+ validMode: ast.temporalMode.mode,
1093
+ validAsOf: ast.temporalMode.asOf,
1094
+ recorded,
1095
+ currentInstant: temporalFilterPass.currentInstant
1096
+ };
1097
+ }
1098
+ function compileIdentityClassCte(input) {
1099
+ const { ast, ctx, graphId, temporalFilterPass } = input;
1100
+ const expandsIdentity = ast.traversals.some(
1101
+ (traversal) => traversal.includeIdentityMembers === true
1102
+ );
1103
+ if (!expandsIdentity) return void 0;
1104
+ const coordinate = historicalCoordinate({ ast, temporalFilterPass });
1105
+ if (coordinate === void 0) return void 0;
1106
+ const peerClasses = historicalIdentityPeerClassQuery({
1107
+ schema: ctx.schema,
1108
+ graphId,
1109
+ coordinate,
1110
+ sameIdAcrossKinds: ctx.identitySameIdAcrossKinds ?? "fold"
1111
+ });
1112
+ return sql`
1113
+ ${sql.identifier(IDENTITY_CLASS_CTE_ALIAS)}(${IDENTITY_PEER_CLASS_COLUMNS}) AS MATERIALIZED (
1114
+ ${peerClasses}
1115
+ )
1116
+ `;
1117
+ }
1118
+ function planCurrentIdentityFrontierExpansion(input) {
1119
+ const { ctx, graphId, previousId, previousKind, temporalFilterPass } = input;
1120
+ const seedClass = sql.raw(CURRENT_SEED_CLASS_ALIAS);
1121
+ const peer = sql.raw(PEER_CLASS_ALIAS);
1122
+ const memberNode = sql.raw(CURRENT_MEMBER_NODE_ALIAS);
1123
+ return {
1124
+ frontierJoin: sql`
1125
+ LEFT JOIN ${ctx.schema.identityClosureTable} ${seedClass}
1126
+ ON ${seedClass}.graph_id = ${graphId}
1127
+ AND ${seedClass}.member_kind = ${previousKind}
1128
+ AND ${seedClass}.member_id = ${previousId}
1129
+ LEFT JOIN ${ctx.schema.identityClosureTable} ${peer}
1130
+ ON ${peer}.graph_id = ${seedClass}.graph_id
1131
+ AND ${peer}.class_kind = ${seedClass}.class_kind
1132
+ AND ${peer}.class_id = ${seedClass}.class_id
1133
+ LEFT JOIN ${ctx.schema.nodesTable} ${memberNode}
1134
+ ON ${memberNode}.graph_id = ${peer}.graph_id
1135
+ AND ${memberNode}.kind = ${peer}.member_kind
1136
+ AND ${memberNode}.id = ${peer}.member_id
1137
+ AND ${temporalFilterPass.forAlias(CURRENT_MEMBER_NODE_ALIAS)}
1138
+ `,
1139
+ memberId: sql`COALESCE(${peer}.member_id, ${previousId})`,
1140
+ memberKind: sql`COALESCE(${peer}.member_kind, ${previousKind})`,
1141
+ whereClauses: [
1142
+ sql`(${peer}.member_id IS NULL OR ${memberNode}.id IS NOT NULL)`
1143
+ ]
1144
+ };
1145
+ }
1146
+ function planHistoricalIdentityFrontierExpansion(input) {
1147
+ const { previousId, previousKind } = input;
1148
+ const peer = sql.identifier(PEER_CLASS_ALIAS);
1149
+ return {
1150
+ frontierJoin: sql`
1151
+ LEFT JOIN ${sql.identifier(IDENTITY_CLASS_CTE_ALIAS)} ${peer}
1152
+ ON ${peer}.seed_kind = ${previousKind}
1153
+ AND ${peer}.seed_id = ${previousId}
1154
+ `,
1155
+ memberId: sql`COALESCE(${peer}.id, ${previousId})`,
1156
+ memberKind: sql`COALESCE(${peer}.kind, ${previousKind})`,
1157
+ whereClauses: []
1158
+ };
1159
+ }
1160
+ function planIdentityFrontierExpansion(input) {
1161
+ const { ast, ctx, graphId, previousId, previousKind, temporalFilterPass } = input;
1162
+ const coordinate = historicalCoordinate({ ast, temporalFilterPass });
1163
+ if (coordinate !== void 0) {
1164
+ return planHistoricalIdentityFrontierExpansion({
1165
+ previousId,
1166
+ previousKind
1167
+ });
1168
+ }
1169
+ return planCurrentIdentityFrontierExpansion({
1170
+ ctx,
1171
+ graphId,
1172
+ previousId,
1173
+ previousKind,
1174
+ temporalFilterPass
1175
+ });
1176
+ }
1177
+
1178
+ // src/query/compiler/inverse-traversal-guard.ts
1179
+ function compileInverseTraversalDuplicateGuard(directEdgeKinds, inverseEdgeKinds, compileKindFilter3) {
1180
+ const overlappingKinds = inverseEdgeKinds.filter(
1181
+ (kind) => directEdgeKinds.includes(kind)
1182
+ );
1183
+ if (overlappingKinds.length === 0) {
1184
+ return void 0;
1185
+ }
1186
+ return sql`NOT (e.from_id = e.to_id AND e.from_kind = e.to_kind AND ${compileKindFilter3(overlappingKinds)})`;
1187
+ }
1188
+
1026
1189
  // src/query/compiler/typed-json-extract.ts
1027
1190
  function compileTypedJsonExtract(input) {
1028
1191
  const { column, dialect, pointer, valueType } = input;
@@ -1337,24 +1500,71 @@ function buildStandardTraversalCte(input) {
1337
1500
  })
1338
1501
  ];
1339
1502
  const cteMaterialization = materializeCte ? sql`MATERIALIZED ` : ctx.dialect.capabilities.emitNotMaterializedHint ? sql`NOT MATERIALIZED ` : sql``;
1503
+ const previousIdColumn = sql`cte_${sql.raw(previousAlias)}.${sql.raw(`${previousAlias}_id`)}`;
1504
+ const previousKindColumn = sql`cte_${sql.raw(previousAlias)}.${sql.raw(`${previousAlias}_kind`)}`;
1505
+ const identityFrontierExpansion = traversal.includeIdentityMembers === true ? planIdentityFrontierExpansion({
1506
+ ast,
1507
+ ctx,
1508
+ graphId,
1509
+ previousId: previousIdColumn,
1510
+ previousKind: previousKindColumn,
1511
+ temporalFilterPass
1512
+ }) : void 0;
1513
+ const pinFrontierAheadOfEdges = identityFrontierExpansion !== void 0 && ctx.dialect.capabilities.forceRecursiveWorktableOuterJoinOrder;
1514
+ function compileSourceJoin(branch) {
1515
+ const edgeId = sql`e.${sql.raw(branch.joinField)}`;
1516
+ const edgeKind = sql`e.${sql.raw(branch.joinKindField)}`;
1517
+ if (identityFrontierExpansion !== void 0) {
1518
+ return sql`
1519
+ ${identityFrontierExpansion.memberId} = ${edgeId}
1520
+ AND ${identityFrontierExpansion.memberKind} = ${edgeKind}
1521
+ `;
1522
+ }
1523
+ return sql`
1524
+ ${previousIdColumn} = ${edgeId}
1525
+ AND ${previousKindColumn} = ${edgeKind}
1526
+ `;
1527
+ }
1340
1528
  function compileTraversalBranch(branch) {
1341
1529
  const whereClauses = [
1342
1530
  ...baseWhereClauses,
1531
+ // Conditions the frontier widening cannot state in a join condition —
1532
+ // currently the member-visibility guard. Every branch of the step carries
1533
+ // them, because every branch reads the widened frontier.
1534
+ ...identityFrontierExpansion?.whereClauses ?? [],
1343
1535
  compileKindFilter(sql.raw("e.kind"), branch.edgeKinds),
1344
- compileKindFilter(
1345
- sql.raw(`e.${branch.joinKindField}`),
1346
- previousNodeKinds
1347
- ),
1348
1536
  compileKindFilter(sql.raw(`e.${branch.targetKindField}`), nodeKinds)
1349
1537
  ];
1538
+ if (traversal.includeIdentityMembers !== true) {
1539
+ whereClauses.push(
1540
+ compileKindFilter(
1541
+ sql.raw(`e.${branch.joinKindField}`),
1542
+ previousNodeKinds
1543
+ )
1544
+ );
1545
+ }
1350
1546
  if (branch.duplicateGuard !== void 0) {
1351
1547
  whereClauses.push(branch.duplicateGuard);
1352
1548
  }
1549
+ const sourceJoin = compileSourceJoin(branch);
1550
+ const frontierJoin = identityFrontierExpansion?.frontierJoin ?? sql``;
1551
+ if (pinFrontierAheadOfEdges) {
1552
+ return sql`
1553
+ SELECT ${sql.join(selectColumns, sql`, `)}
1554
+ FROM cte_${sql.raw(previousAlias)}
1555
+ ${frontierJoin}
1556
+ CROSS JOIN ${ctx.schema.edgesTable} e
1557
+ JOIN ${ctx.schema.nodesTable} n ON n.graph_id = e.graph_id
1558
+ AND n.id = e.${sql.raw(branch.targetField)}
1559
+ AND n.kind = e.${sql.raw(branch.targetKindField)}
1560
+ WHERE ${sql.join([sourceJoin, ...whereClauses], sql` AND `)}
1561
+ `;
1562
+ }
1353
1563
  return sql`
1354
1564
  SELECT ${sql.join(selectColumns, sql`, `)}
1355
1565
  FROM cte_${sql.raw(previousAlias)}
1356
- JOIN ${ctx.schema.edgesTable} e ON cte_${sql.raw(previousAlias)}.${sql.raw(previousAlias)}_id = e.${sql.raw(branch.joinField)}
1357
- AND cte_${sql.raw(previousAlias)}.${sql.raw(previousAlias)}_kind = e.${sql.raw(branch.joinKindField)}
1566
+ ${frontierJoin}
1567
+ JOIN ${ctx.schema.edgesTable} e ON ${sourceJoin}
1358
1568
  JOIN ${ctx.schema.nodesTable} n ON n.graph_id = e.graph_id
1359
1569
  AND n.id = e.${sql.raw(branch.targetField)}
1360
1570
  AND n.kind = e.${sql.raw(branch.targetKindField)}
@@ -1393,13 +1603,11 @@ function buildStandardTraversalCte(input) {
1393
1603
  const inverseTargetField = traversal.direction === "out" ? "from_id" : "to_id";
1394
1604
  const inverseJoinKindField = traversal.direction === "out" ? "to_kind" : "from_kind";
1395
1605
  const inverseTargetKindField = traversal.direction === "out" ? "from_kind" : "to_kind";
1396
- const overlappingKinds = inverseEdgeKinds.filter(
1397
- (kind) => directEdgeKinds.includes(kind)
1606
+ const duplicateGuard = compileInverseTraversalDuplicateGuard(
1607
+ directEdgeKinds,
1608
+ inverseEdgeKinds,
1609
+ (overlappingKinds) => compileKindFilter(sql.raw("e.kind"), overlappingKinds)
1398
1610
  );
1399
- const duplicateGuard = overlappingKinds.length > 0 ? sql`NOT (e.from_id = e.to_id AND ${compileKindFilter(
1400
- sql.raw("e.kind"),
1401
- overlappingKinds
1402
- )})` : void 0;
1403
1611
  const inverseBranch = compileTraversalBranch({
1404
1612
  duplicateGuard,
1405
1613
  edgeKinds: inverseEdgeKinds,
@@ -1803,6 +2011,16 @@ function buildStandardEmbeddingsCte(input) {
1803
2011
  const tableName = vectorStrategy.tableName(graphId, kind, fieldPath);
1804
2012
  const slotDescriptor = vectorSlots?.get(vectorSlotKey(kind, fieldPath));
1805
2013
  const branchMetric = metric ?? slotDescriptor?.metric ?? "cosine";
2014
+ if (slotDescriptor !== void 0) {
2015
+ assertApproximateMetricSupported({
2016
+ approximate: vectorPredicate.approximate,
2017
+ requestedMetric: metric,
2018
+ declaredMetric: slotDescriptor.metric,
2019
+ indexType: slotDescriptor.indexType,
2020
+ nodeKind: kind,
2021
+ fieldPath
2022
+ });
2023
+ }
1806
2024
  const engineFormEligible = vectorPredicate.approximate === true || vectorStrategy.searchIsExact === true;
1807
2025
  const annSlot = engineFormEligible && branchMetric === slotDescriptor?.metric ? slotDescriptor : void 0;
1808
2026
  if (annSlot !== void 0) {
@@ -2152,7 +2370,8 @@ function createTemporalFilterPass(ast, readInstant = "literal") {
2152
2370
  ...extractTemporalOptions(ast, tableAlias),
2153
2371
  currentTimestamp
2154
2372
  });
2155
- }
2373
+ },
2374
+ currentInstant: currentTimestamp
2156
2375
  };
2157
2376
  }
2158
2377
 
@@ -2600,6 +2819,12 @@ function throwRecordedCurrentIndexError(recordedAsOf, usage) {
2600
2819
  var MAX_RECURSIVE_DEPTH = 10;
2601
2820
  var MAX_EXPLICIT_RECURSIVE_DEPTH = 1e3;
2602
2821
  var NO_ALWAYS_REQUIRED_COLUMNS = /* @__PURE__ */ new Set();
2822
+ function compilePathToken(tableAlias, composite) {
2823
+ if (!composite) {
2824
+ return sql.raw(`${tableAlias}.id`);
2825
+ }
2826
+ return sql`(${sql.raw(`${tableAlias}.kind`)} || ${IDENTITY_PATH_TOKEN_SEPARATOR} || ${sql.raw(`${tableAlias}.id`)})`;
2827
+ }
2603
2828
  function runRecursiveQueryPassPipeline(ast, graphId, ctx) {
2604
2829
  let state = {
2605
2830
  ast,
@@ -2720,6 +2945,12 @@ function compileVariableLengthQueryWithRecursiveCteStrategy(ast, graphId, ctx) {
2720
2945
  requiredColumnsByAlias,
2721
2946
  temporalFilterPass
2722
2947
  );
2948
+ const identityClassCte = compileIdentityClassCte({
2949
+ ast,
2950
+ ctx,
2951
+ graphId,
2952
+ temporalFilterPass
2953
+ });
2723
2954
  const projection = compileRecursiveProjection(ast, vlTraversal, dialect);
2724
2955
  const minDepth = vlTraversal.variableLength.minDepth;
2725
2956
  const depthFilter = minDepth > 0 ? sql`WHERE depth >= ${minDepth}` : sql.raw("");
@@ -2730,6 +2961,7 @@ function compileVariableLengthQueryWithRecursiveCteStrategy(ast, graphId, ctx) {
2730
2961
  ...limitOffset === void 0 ? {} : { limitOffset },
2731
2962
  logicalPlan,
2732
2963
  ...orderBy === void 0 ? {} : { orderBy },
2964
+ ...identityClassCte === void 0 ? {} : { precedingCtes: [identityClassCte] },
2733
2965
  projection,
2734
2966
  recursiveCte
2735
2967
  });
@@ -2752,7 +2984,7 @@ function compileRecursiveCte(ast, traversal, graphId, ctx, requiredColumnsByAlia
2752
2984
  const shouldEnforceCycleCheck = vl.cyclePolicy !== "allow";
2753
2985
  const shouldTrackPath = shouldEnforceCycleCheck || vl.pathAlias !== void 0;
2754
2986
  const recursiveJoinRequiredColumns = /* @__PURE__ */ new Set(["id"]);
2755
- if (previousNodeKinds.length > 1) {
2987
+ if (previousNodeKinds.length > 1 || traversal.includeIdentityMembers === true) {
2756
2988
  recursiveJoinRequiredColumns.add("kind");
2757
2989
  }
2758
2990
  const requiredStartColumns = requiredColumnsByAlias ? requiredColumnsByAlias.get(startAlias) ?? EMPTY_REQUIRED_COLUMNS : void 0;
@@ -2806,33 +3038,74 @@ function compileRecursiveCte(ast, traversal, graphId, ctx, requiredColumnsByAlia
2806
3038
  }
2807
3039
  const effectiveMaxDepth = vl.maxDepth > 0 ? vl.maxDepth : MAX_RECURSIVE_DEPTH;
2808
3040
  const maxDepthCondition = sql`r.depth < ${effectiveMaxDepth}`;
2809
- const cycleCheck = shouldEnforceCycleCheck ? dialect.cycleCheck(sql.raw("n.id"), sql.raw("r.path")) : void 0;
2810
- const initialPath = shouldTrackPath ? dialect.initializePath(sql.raw("n0.id")) : void 0;
2811
- const pathExtension = shouldTrackPath ? dialect.extendPath(sql.raw("r.path"), sql.raw("n.id")) : void 0;
3041
+ const baseToken = compilePathToken(
3042
+ "n0",
3043
+ traversal.includeIdentityMembers === true
3044
+ );
3045
+ const stepToken = compilePathToken(
3046
+ "n",
3047
+ traversal.includeIdentityMembers === true
3048
+ );
3049
+ const cycleCheck = shouldEnforceCycleCheck ? dialect.cycleCheck(stepToken, sql.raw("r.path")) : void 0;
3050
+ const initialPath = shouldTrackPath ? dialect.initializePath(baseToken) : void 0;
3051
+ const pathExtension = shouldTrackPath ? dialect.extendPath(sql.raw("r.path"), stepToken) : void 0;
2812
3052
  const baseWhereClauses = [
2813
3053
  sql`n0.graph_id = ${graphId}`,
2814
3054
  startKindFilter,
2815
3055
  startTemporalFilter,
2816
3056
  ...startPredicates
2817
3057
  ];
3058
+ const previousIdColumn = sql`r.${sql.raw(nodeAlias)}_id`;
3059
+ const previousKindColumn = sql`r.${sql.raw(nodeAlias)}_kind`;
3060
+ const identityFrontierExpansion = traversal.includeIdentityMembers === true ? planIdentityFrontierExpansion({
3061
+ ast,
3062
+ ctx,
3063
+ graphId,
3064
+ previousId: previousIdColumn,
3065
+ previousKind: previousKindColumn,
3066
+ temporalFilterPass
3067
+ }) : void 0;
2818
3068
  const recursiveBaseWhereClauses = [
2819
3069
  sql`e.graph_id = ${graphId}`,
2820
3070
  nodeKindFilter,
2821
3071
  edgeTemporalFilter,
2822
3072
  nodeTemporalFilter,
2823
- maxDepthCondition
3073
+ maxDepthCondition,
3074
+ // Conditions the frontier widening cannot state in a join condition —
3075
+ // currently the member-visibility guard. Every branch of the recursive term
3076
+ // carries them, because every branch reads the widened frontier.
3077
+ ...identityFrontierExpansion?.whereClauses ?? []
2824
3078
  ];
2825
3079
  if (cycleCheck !== void 0) {
2826
3080
  recursiveBaseWhereClauses.push(cycleCheck);
2827
3081
  }
2828
3082
  recursiveBaseWhereClauses.push(...edgePredicates, ...targetNodePredicates);
3083
+ function compileWorktableJoinClauses(branch) {
3084
+ const edgeId = sql`e.${sql.raw(branch.joinField)}`;
3085
+ const edgeKind = sql`e.${sql.raw(branch.joinKindField)}`;
3086
+ if (identityFrontierExpansion !== void 0) {
3087
+ return [
3088
+ sql`${edgeId} = ${identityFrontierExpansion.memberId}`,
3089
+ sql`${edgeKind} = ${identityFrontierExpansion.memberKind}`
3090
+ ];
3091
+ }
3092
+ const clauses = [sql`${edgeId} = ${previousIdColumn}`];
3093
+ if (previousNodeKinds.length > 1) {
3094
+ clauses.push(sql`${edgeKind} = ${previousKindColumn}`);
3095
+ }
3096
+ return clauses;
3097
+ }
2829
3098
  function compileRecursiveBranch2(branch) {
2830
3099
  const recursiveFilterClauses = [
2831
3100
  ...recursiveBaseWhereClauses,
2832
3101
  compileKindFilter2(branch.edgeKinds, "e.kind"),
2833
- compileKindFilter2(previousNodeKinds, `e.${branch.joinKindField}`),
2834
3102
  compileKindFilter2(nodeKinds, `e.${branch.targetKindField}`)
2835
3103
  ];
3104
+ if (traversal.includeIdentityMembers !== true) {
3105
+ recursiveFilterClauses.push(
3106
+ compileKindFilter2(previousNodeKinds, `e.${branch.joinKindField}`)
3107
+ );
3108
+ }
2836
3109
  if (branch.duplicateGuard !== void 0) {
2837
3110
  recursiveFilterClauses.push(branch.duplicateGuard);
2838
3111
  }
@@ -2844,14 +3117,8 @@ function compileRecursiveCte(ast, traversal, graphId, ctx, requiredColumnsByAlia
2844
3117
  if (pathExtension !== void 0) {
2845
3118
  recursiveSelectColumns.push(sql`${pathExtension} AS path`);
2846
3119
  }
2847
- const recursiveJoinClauses = [
2848
- sql`e.${sql.raw(branch.joinField)} = r.${sql.raw(nodeAlias)}_id`
2849
- ];
2850
- if (previousNodeKinds.length > 1) {
2851
- recursiveJoinClauses.push(
2852
- sql`e.${sql.raw(branch.joinKindField)} = r.${sql.raw(nodeAlias)}_kind`
2853
- );
2854
- }
3120
+ const recursiveJoinClauses = compileWorktableJoinClauses(branch);
3121
+ const frontierJoin = identityFrontierExpansion?.frontierJoin ?? sql``;
2855
3122
  if (forceWorktableOuterJoinOrder) {
2856
3123
  const recursiveWhereClauses = [
2857
3124
  ...recursiveJoinClauses,
@@ -2860,6 +3127,7 @@ function compileRecursiveCte(ast, traversal, graphId, ctx, requiredColumnsByAlia
2860
3127
  return sql`
2861
3128
  SELECT ${sql.join(recursiveSelectColumns, sql`, `)}
2862
3129
  FROM recursive_cte r
3130
+ ${frontierJoin}
2863
3131
  CROSS JOIN ${ctx.schema.edgesTable} e
2864
3132
  JOIN ${ctx.schema.nodesTable} n ON n.graph_id = e.graph_id
2865
3133
  AND n.id = e.${sql.raw(branch.targetField)}
@@ -2870,6 +3138,7 @@ function compileRecursiveCte(ast, traversal, graphId, ctx, requiredColumnsByAlia
2870
3138
  return sql`
2871
3139
  SELECT ${sql.join(recursiveSelectColumns, sql`, `)}
2872
3140
  FROM recursive_cte r
3141
+ ${frontierJoin}
2873
3142
  JOIN ${ctx.schema.edgesTable} e ON ${sql.join(recursiveJoinClauses, sql` AND `)}
2874
3143
  JOIN ${ctx.schema.nodesTable} n ON n.graph_id = e.graph_id
2875
3144
  AND n.id = e.${sql.raw(branch.targetField)}
@@ -2893,10 +3162,11 @@ function compileRecursiveCte(ast, traversal, graphId, ctx, requiredColumnsByAlia
2893
3162
  const inverseTargetField = direction === "out" ? "from_id" : "to_id";
2894
3163
  const inverseJoinKindField = direction === "out" ? "to_kind" : "from_kind";
2895
3164
  const inverseTargetKindField = direction === "out" ? "from_kind" : "to_kind";
2896
- const overlappingKinds = inverseEdgeKinds.filter(
2897
- (kind) => directEdgeKinds.includes(kind)
3165
+ const duplicateGuard = compileInverseTraversalDuplicateGuard(
3166
+ directEdgeKinds,
3167
+ inverseEdgeKinds,
3168
+ (overlappingKinds) => compileKindFilter2(overlappingKinds, "e.kind")
2898
3169
  );
2899
- const duplicateGuard = overlappingKinds.length > 0 ? sql`NOT (e.from_id = e.to_id AND ${compileKindFilter2(overlappingKinds, "e.kind")})` : void 0;
2900
3170
  const inverseBranch = compileRecursiveBranch2({
2901
3171
  joinField: inverseJoinField,
2902
3172
  targetField: inverseTargetField,
@@ -3419,7 +3689,7 @@ function buildProjectionPlan(kinds, projectionMap, resolveFieldType, entityPrefi
3419
3689
  const projectedKinds = /* @__PURE__ */ new Map();
3420
3690
  const fullKinds = [];
3421
3691
  for (const kind of kinds) {
3422
- const selection = projectionMap?.[kind];
3692
+ const selection = projectionMap !== void 0 && hasOwnKey(projectionMap, kind) ? projectionMap[kind] : void 0;
3423
3693
  if (selection === void 0) {
3424
3694
  fullKinds.push(kind);
3425
3695
  continue;
@@ -4241,6 +4511,7 @@ function compileQuery(ast, graphId, options = "sqlite") {
4241
4511
  schema,
4242
4512
  annIndexTypes,
4243
4513
  readInstant: options_.readInstant ?? "literal",
4514
+ identitySameIdAcrossKinds: options_.identitySameIdAcrossKinds ?? "fold",
4244
4515
  compileQuery: (subAst, subGraphId) => compileQuery(
4245
4516
  inheritRecordedAsOf(subAst, ast.recordedAsOf),
4246
4517
  subGraphId,
@@ -4350,7 +4621,10 @@ function propagateOptions(options_) {
4350
4621
  ...options_.vectorSlots === void 0 ? {} : { vectorSlots: options_.vectorSlots },
4351
4622
  ...options_.fulltextLanguages === void 0 ? {} : { fulltextLanguages: options_.fulltextLanguages },
4352
4623
  ...options_.recordedReadBinding === void 0 ? {} : { recordedReadBinding: options_.recordedReadBinding },
4353
- ...options_.readInstant === void 0 ? {} : { readInstant: options_.readInstant }
4624
+ ...options_.readInstant === void 0 ? {} : { readInstant: options_.readInstant },
4625
+ ...options_.identitySameIdAcrossKinds === void 0 ? {} : {
4626
+ identitySameIdAcrossKinds: options_.identitySameIdAcrossKinds
4627
+ }
4354
4628
  };
4355
4629
  }
4356
4630
  function resolveCountAggregateFastPath(ast) {
@@ -4418,6 +4692,11 @@ function resolveCountAggregateFastPath(ast) {
4418
4692
  };
4419
4693
  }
4420
4694
  function compileCountAggregateFastPath(ast, graphId, ctx, logicalPlan, requiredColumnsByAlias, predicateIndex, temporalFilterPass) {
4695
+ if (ast.traversals.some(
4696
+ (traversal2) => traversal2.includeIdentityMembers === true
4697
+ )) {
4698
+ return void 0;
4699
+ }
4421
4700
  const plan = resolveCountAggregateFastPath(ast);
4422
4701
  if (!plan) {
4423
4702
  return void 0;
@@ -4609,7 +4888,14 @@ function buildStandardStartAndTraversalCtes(input) {
4609
4888
  temporalFilterPass,
4610
4889
  traversalLimit
4611
4890
  } = input;
4891
+ const identityClassCte = compileIdentityClassCte({
4892
+ ast,
4893
+ ctx,
4894
+ graphId,
4895
+ temporalFilterPass
4896
+ });
4612
4897
  const ctes = [
4898
+ ...identityClassCte === void 0 ? [] : [identityClassCte],
4613
4899
  buildStandardStartCte({
4614
4900
  ast,
4615
4901
  ctx,
@@ -4946,57 +5232,35 @@ function compileStandardQueryWithCteStrategy(ast, graphId, ctx) {
4946
5232
  });
4947
5233
  }
4948
5234
 
4949
- // src/utils/path.ts
4950
- function parseSqlitePath(path) {
4951
- const emptyPath = `${SQLITE_PATH_DELIMITER}${SQLITE_PATH_DELIMITER}`;
4952
- if (!path || path === emptyPath) return [];
4953
- const trimmed = path.slice(1, -1);
4954
- if (trimmed === "") return [];
4955
- return trimmed.split(SQLITE_PATH_DELIMITER);
4956
- }
4957
- function isSqlitePath(value) {
4958
- return typeof value === "string" && value.startsWith(SQLITE_PATH_DELIMITER) && value.endsWith(SQLITE_PATH_DELIMITER);
4959
- }
4960
- function normalizePath(value) {
4961
- if (Array.isArray(value)) {
4962
- return value;
4963
- }
4964
- if (isSqlitePath(value)) {
4965
- return parseSqlitePath(value);
4966
- }
4967
- if (isPostgresTextArray(value)) {
4968
- return parsePostgresTextArray(value);
4969
- }
4970
- return [];
4971
- }
4972
- function isPostgresTextArray(value) {
4973
- return typeof value === "string" && value.startsWith(PG_ARRAY_START) && value.endsWith(PG_ARRAY_END);
4974
- }
4975
- function parsePostgresTextArray(value) {
4976
- const inner = value.slice(1, -1);
4977
- if (inner === "") return [];
4978
- return inner.split(PG_PATH_ELEMENT_SEPARATOR);
4979
- }
4980
-
4981
5235
  // src/query/execution/result-mapper.ts
4982
- function transformPathColumns(rows, state, _dialect) {
4983
- const pathAliases = [];
5236
+ function collectPathColumns(state) {
5237
+ const columns = [];
4984
5238
  for (const traversal of state.traversals) {
4985
- if (traversal.variableLength?.pathAlias !== void 0) {
4986
- pathAliases.push(traversal.variableLength.pathAlias);
5239
+ const pathAlias = traversal.variableLength?.pathAlias;
5240
+ if (pathAlias !== void 0) {
5241
+ columns.push({
5242
+ alias: pathAlias,
5243
+ identityExpanded: traversal.includeIdentityMembers === true
5244
+ });
4987
5245
  }
4988
5246
  }
4989
- if (pathAliases.length === 0) return rows;
5247
+ return columns;
5248
+ }
5249
+ function transformPathColumns(rows, state, _dialect) {
5250
+ const pathColumns = collectPathColumns(state);
5251
+ if (pathColumns.length === 0) return rows;
4990
5252
  const result = [];
4991
5253
  let changed = false;
4992
5254
  for (const row of rows) {
4993
5255
  let transformed;
4994
- for (const alias of pathAliases) {
5256
+ for (const { alias, identityExpanded } of pathColumns) {
4995
5257
  const value = row[alias];
4996
- if (value !== void 0 && !Array.isArray(value)) {
4997
- transformed ??= { ...row };
4998
- transformed[alias] = normalizePath(value);
4999
- }
5258
+ if (value === void 0) continue;
5259
+ const normalized = normalizePath(value);
5260
+ const path = identityExpanded ? stripIdentityPathTokens(normalized) : normalized;
5261
+ if (path === value) continue;
5262
+ transformed ??= { ...row };
5263
+ transformed[alias] = path;
5000
5264
  }
5001
5265
  if (transformed === void 0) {
5002
5266
  result.push(row);
@@ -5008,16 +5272,24 @@ function transformPathColumns(rows, state, _dialect) {
5008
5272
  return changed ? result : rows;
5009
5273
  }
5010
5274
  var RESERVED_NODE_KEYS = /* @__PURE__ */ new Set(["id", "kind", "meta"]);
5011
- var RESERVED_EDGE_KEYS = /* @__PURE__ */ new Set(["id", "kind", "fromId", "toId", "meta"]);
5275
+ var RESERVED_EDGE_KEYS = /* @__PURE__ */ new Set([
5276
+ "id",
5277
+ "kind",
5278
+ "fromId",
5279
+ "toId",
5280
+ "meta"
5281
+ ]);
5012
5282
  function nullToUndefined2(value) {
5013
5283
  return value === null ? void 0 : value;
5014
5284
  }
5015
- function assignPropsExcludingReserved(target, props, reservedKeys) {
5285
+ function propsExcludingReserved(props, reservedKeys) {
5286
+ const kept = createDataKeyedBag();
5016
5287
  for (const [key, value] of Object.entries(props)) {
5017
5288
  if (!reservedKeys.has(key)) {
5018
- target[key] = value;
5289
+ kept[key] = value;
5019
5290
  }
5020
5291
  }
5292
+ return kept;
5021
5293
  }
5022
5294
  function buildSelectableNode(row, alias) {
5023
5295
  const id = row[`${alias}_id`];
@@ -5044,9 +5316,9 @@ function buildSelectableNode(row, alias) {
5044
5316
  createdAt,
5045
5317
  updatedAt,
5046
5318
  deletedAt
5047
- }
5319
+ },
5320
+ ...propsExcludingReserved(rawProps, RESERVED_NODE_KEYS)
5048
5321
  };
5049
- assignPropsExcludingReserved(result, rawProps, RESERVED_NODE_KEYS);
5050
5322
  return result;
5051
5323
  }
5052
5324
  function buildSelectableNodeOrUndefined(row, alias) {
@@ -5086,15 +5358,14 @@ function buildSelectableEdge(row, alias) {
5086
5358
  createdAt,
5087
5359
  updatedAt,
5088
5360
  deletedAt
5089
- }
5361
+ },
5362
+ ...propsExcludingReserved(rawProps, RESERVED_EDGE_KEYS)
5090
5363
  };
5091
- assignPropsExcludingReserved(result, rawProps, RESERVED_EDGE_KEYS);
5092
5364
  return result;
5093
5365
  }
5094
5366
  function buildSelectContext(row, startAlias, traversals) {
5095
- const context = {
5096
- [startAlias]: buildSelectableNode(row, startAlias)
5097
- };
5367
+ const context = createDataKeyedBag();
5368
+ context[startAlias] = buildSelectableNode(row, startAlias);
5098
5369
  for (const traversal of traversals) {
5099
5370
  const nodeAlias = traversal.nodeAlias;
5100
5371
  const edgeAlias = traversal.edgeAlias;
@@ -5110,7 +5381,7 @@ function buildSelectContext(row, startAlias, traversals) {
5110
5381
  }
5111
5382
  }
5112
5383
  }
5113
- return context;
5384
+ return { ...context };
5114
5385
  }
5115
5386
  function mapResults(rows, startAlias, traversals, selectFunction) {
5116
5387
  return rows.map((row) => {
@@ -5408,14 +5679,13 @@ var FieldAccessTracker = class {
5408
5679
  }
5409
5680
  };
5410
5681
  function createTrackingContext(state, tracker, options) {
5411
- const context = {
5412
- [state.startAlias]: createNodeTrackingProxy(
5413
- state.startAlias,
5414
- state.startKinds,
5415
- tracker,
5416
- options
5417
- )
5418
- };
5682
+ const context = createDataKeyedBag();
5683
+ context[state.startAlias] = createNodeTrackingProxy(
5684
+ state.startAlias,
5685
+ state.startKinds,
5686
+ tracker,
5687
+ options
5688
+ );
5419
5689
  for (const traversal of state.traversals) {
5420
5690
  const edgeKindNames = mergeEdgeKinds(traversal);
5421
5691
  const optionalAbsent = options.optionalTraversalAliases === "absent" && traversal.optional;
@@ -5441,7 +5711,7 @@ function createTrackingContext(state, tracker, options) {
5441
5711
  }
5442
5712
  }
5443
5713
  }
5444
- return context;
5714
+ return { ...context };
5445
5715
  }
5446
5716
  function createNodeTrackingProxy(alias, kindNames, tracker, options) {
5447
5717
  return new Proxy(
@@ -5449,9 +5719,10 @@ function createNodeTrackingProxy(alias, kindNames, tracker, options) {
5449
5719
  {
5450
5720
  get: (_, property) => {
5451
5721
  if (typeof property === "symbol") return;
5452
- if (property === "then") return;
5453
- if (property === "toJSON") return;
5454
- if (OBJECT_PROTOTYPE_PROPERTIES.has(property)) {
5722
+ if (isInteropProbeKey(property) && !options.schemaIntrospector.hasDeclaredField(kindNames, property)) {
5723
+ return;
5724
+ }
5725
+ if (OBJECT_PROTOTYPE_PROPERTIES.has(property) && !options.schemaIntrospector.hasDeclaredField(kindNames, property)) {
5455
5726
  if (property === "constructor") return Object;
5456
5727
  if (property === "__proto__") return Object.prototype;
5457
5728
  return Reflect.get(Object.prototype, property);
@@ -5482,9 +5753,16 @@ function createEdgeTrackingProxy(alias, edgeKindNames, tracker, options) {
5482
5753
  {
5483
5754
  get: (_, property) => {
5484
5755
  if (typeof property === "symbol") return;
5485
- if (property === "then") return;
5486
- if (property === "toJSON") return;
5487
- if (OBJECT_PROTOTYPE_PROPERTIES.has(property)) {
5756
+ if (isInteropProbeKey(property) && !options.schemaIntrospector.hasDeclaredEdgeField(
5757
+ edgeKindNames,
5758
+ property
5759
+ )) {
5760
+ return;
5761
+ }
5762
+ if (OBJECT_PROTOTYPE_PROPERTIES.has(property) && !options.schemaIntrospector.hasDeclaredEdgeField(
5763
+ edgeKindNames,
5764
+ property
5765
+ )) {
5488
5766
  if (property === "constructor") return Object;
5489
5767
  if (property === "__proto__") return Object.prototype;
5490
5768
  return Reflect.get(Object.prototype, property);
@@ -5762,7 +6040,7 @@ function buildAliasPlans(state, selectiveFields, schemaIntrospector) {
5762
6040
  return plans.toSorted((a, b) => compareStrings(a.alias, b.alias));
5763
6041
  }
5764
6042
  function buildSelectiveContext(row, plans, traversals) {
5765
- const context = {};
6043
+ const context = createDataKeyedBag();
5766
6044
  for (const plan of plans) {
5767
6045
  const value = plan.optional && plan.idOutputName !== void 0 ? buildOptionalAliasValue(row, plan) : buildRequiredAliasValue(row, plan);
5768
6046
  context[plan.alias] = value;
@@ -5778,7 +6056,7 @@ function buildSelectiveContext(row, plans, traversals) {
5778
6056
  }
5779
6057
  }
5780
6058
  }
5781
- return context;
6059
+ return { ...context };
5782
6060
  }
5783
6061
  function buildOptionalAliasValue(row, plan) {
5784
6062
  if (plan.idOutputName === void 0) {
@@ -5791,17 +6069,16 @@ function buildOptionalAliasValue(row, plan) {
5791
6069
  return buildRequiredAliasValue(row, plan);
5792
6070
  }
5793
6071
  function buildRequiredAliasValue(row, plan) {
5794
- const base = {
5795
- [SELECTABLE_ALIAS_MARKER]: {
5796
- alias: plan.alias,
5797
- kind: plan.kind
5798
- }
6072
+ const base = createDataKeyedBag();
6073
+ base[SELECTABLE_ALIAS_MARKER] = {
6074
+ alias: plan.alias,
6075
+ kind: plan.kind
5799
6076
  };
5800
6077
  for (const field2 of plan.systemFields) {
5801
6078
  base[field2.field] = nullToUndefined(row[field2.outputName]);
5802
6079
  }
5803
6080
  if (plan.metaFields.length > 0) {
5804
- const meta = {};
6081
+ const meta = createDataKeyedBag();
5805
6082
  for (const field2 of plan.metaFields) {
5806
6083
  meta[field2.metaKey] = nullToUndefined(row[field2.outputName]);
5807
6084
  }
@@ -5814,23 +6091,26 @@ function buildRequiredAliasValue(row, plan) {
5814
6091
  return createGuardedProxy(base, plan.alias);
5815
6092
  }
5816
6093
  function createGuardedProxy(target, debugPath) {
5817
- return new Proxy(target, {
5818
- get: (object, property, receiver) => {
5819
- if (typeof property === "symbol") {
5820
- return Reflect.get(object, property, receiver);
5821
- }
5822
- if (property === "then" || property === "toJSON") {
5823
- return;
5824
- }
5825
- if (property in object) {
5826
- return Reflect.get(object, property, receiver);
5827
- }
5828
- if (property in Object.prototype) {
5829
- return Reflect.get(Object.prototype, property, receiver);
6094
+ return new Proxy(
6095
+ { ...target },
6096
+ {
6097
+ get: (object, property, receiver) => {
6098
+ if (typeof property === "symbol") {
6099
+ return Reflect.get(object, property, receiver);
6100
+ }
6101
+ if (hasOwnKey(object, property)) {
6102
+ return Reflect.get(object, property, receiver);
6103
+ }
6104
+ if (isInteropProbeKey(property)) {
6105
+ return;
6106
+ }
6107
+ if (property in Object.prototype) {
6108
+ return Reflect.get(Object.prototype, property, receiver);
6109
+ }
6110
+ throw new MissingSelectiveFieldError(debugPath, property);
5830
6111
  }
5831
- throw new MissingSelectiveFieldError(debugPath, property);
5832
6112
  }
5833
- });
6113
+ );
5834
6114
  }
5835
6115
 
5836
6116
  // src/query/builder/read-instant-template.ts
@@ -5879,7 +6159,7 @@ function fillTemplateParams(params, bindings, dialect, listParameters = EMPTY_LI
5879
6159
  readInstant ??= nowIso();
5880
6160
  return readInstant;
5881
6161
  }
5882
- const value = bindings[name];
6162
+ const value = readOwnProperty(bindings, name);
5883
6163
  if (value === void 0) {
5884
6164
  throw new ConfigurationError(`Missing binding for parameter "${name}"`, {
5885
6165
  parameterName: name
@@ -5946,7 +6226,7 @@ function substitutePredicateExpression(expr, bindings) {
5946
6226
  }
5947
6227
  case "string_op": {
5948
6228
  if (isParameterRef(expr.pattern)) {
5949
- const value = bindings[expr.pattern.name];
6229
+ const value = readOwnProperty(bindings, expr.pattern.name);
5950
6230
  if (value === void 0) {
5951
6231
  throw new ConfigurationError(
5952
6232
  `Missing binding for parameter "${expr.pattern.name}"`,
@@ -6020,7 +6300,7 @@ function substitutePredicateExpression(expr, bindings) {
6020
6300
  }
6021
6301
  }
6022
6302
  function resolveBinding(bindings, name) {
6023
- const value = bindings[name];
6303
+ const value = readOwnProperty(bindings, name);
6024
6304
  if (value === void 0) {
6025
6305
  throw new ConfigurationError(`Missing binding for parameter "${name}"`, {
6026
6306
  parameterName: name
@@ -6300,7 +6580,7 @@ function validateBindings(bindings, metadata) {
6300
6580
  const { names: expectedNames, stringOpParameters, listParameters } = metadata;
6301
6581
  const missing = [];
6302
6582
  for (const name of expectedNames) {
6303
- if (bindings[name] === void 0) {
6583
+ if (readOwnProperty(bindings, name) === void 0) {
6304
6584
  missing.push(name);
6305
6585
  }
6306
6586
  }
@@ -6320,7 +6600,7 @@ function validateBindings(bindings, metadata) {
6320
6600
  );
6321
6601
  }
6322
6602
  for (const name of expectedNames) {
6323
- const value = bindings[name];
6603
+ const value = readOwnProperty(bindings, name);
6324
6604
  if (listParameters.has(name)) {
6325
6605
  validateListBinding(name, value, listParameters.get(name));
6326
6606
  continue;
@@ -6711,6 +6991,7 @@ function buildCompileOptions(config) {
6711
6991
  dialect: config.dialect ?? "sqlite",
6712
6992
  schema: config.schema,
6713
6993
  windowFunctions: config.backend?.capabilities.windowFunctions ?? true,
6994
+ identitySameIdAcrossKinds: config.identitySameIdAcrossKinds,
6714
6995
  ...fulltextStrategy === void 0 ? {} : {
6715
6996
  fulltextStrategy,
6716
6997
  fulltextLanguages: buildFulltextLanguages(config.registry)
@@ -7460,7 +7741,9 @@ var ExecutableQuery = class _ExecutableQuery {
7460
7741
  optionalNodeAliases.add(traversal.nodeAlias);
7461
7742
  }
7462
7743
  }
7463
- const cursorContext = {};
7744
+ const cursorContext = /* @__PURE__ */ Object.create(
7745
+ null
7746
+ );
7464
7747
  for (const spec of this.#paginationOrderBy()) {
7465
7748
  const alias = spec.field.alias;
7466
7749
  const jsonPointer2 = spec.field.jsonPointer;
@@ -7518,7 +7801,9 @@ var ExecutableQuery = class _ExecutableQuery {
7518
7801
  if (typeof existing_ === "object" && existing_ !== null) {
7519
7802
  current = existing_;
7520
7803
  } else {
7521
- const created = {};
7804
+ const created = /* @__PURE__ */ Object.create(
7805
+ null
7806
+ );
7522
7807
  current[segment] = created;
7523
7808
  current = created;
7524
7809
  }
@@ -7532,7 +7817,7 @@ var ExecutableQuery = class _ExecutableQuery {
7532
7817
  if (typeof existing === "object" && existing !== null) {
7533
7818
  return existing;
7534
7819
  }
7535
- const created = {};
7820
+ const created = /* @__PURE__ */ Object.create(null);
7536
7821
  cursorContext[alias] = created;
7537
7822
  return created;
7538
7823
  }
@@ -7880,7 +8165,7 @@ var ExecutableAggregateQuery = class _ExecutableAggregateQuery {
7880
8165
  */
7881
8166
  #mapResults(rows) {
7882
8167
  return rows.map((row) => {
7883
- const result = {};
8168
+ const result = createDataKeyedBag();
7884
8169
  for (const key of Object.keys(this.#fields)) {
7885
8170
  const field2 = this.#fields[key];
7886
8171
  if (!field2) continue;
@@ -7891,7 +8176,7 @@ var ExecutableAggregateQuery = class _ExecutableAggregateQuery {
7891
8176
  }
7892
8177
  result[key] = normalizeFieldValue(field2, value);
7893
8178
  }
7894
- return result;
8179
+ return { ...result };
7895
8180
  });
7896
8181
  }
7897
8182
  };
@@ -8004,9 +8289,10 @@ var TraversalBuilder = class _TraversalBuilder {
8004
8289
  #direction;
8005
8290
  #fromAlias;
8006
8291
  #optional;
8292
+ #includeIdentityMembers;
8007
8293
  #variableLength;
8008
8294
  #pendingEdgePredicates;
8009
- constructor(config, state, edgeKinds, edgeAlias, direction, fromAlias, inverseEdgeKinds = [], optional = false, variableLength = DEFAULT_VARIABLE_LENGTH_STATE, pendingEdgePredicates = []) {
8295
+ constructor(config, state, edgeKinds, edgeAlias, direction, fromAlias, inverseEdgeKinds = [], optional = false, variableLength = DEFAULT_VARIABLE_LENGTH_STATE, pendingEdgePredicates = [], includeIdentityMembers = false) {
8010
8296
  this.#config = config;
8011
8297
  this.#state = state;
8012
8298
  this.#edgeKinds = edgeKinds;
@@ -8015,6 +8301,7 @@ var TraversalBuilder = class _TraversalBuilder {
8015
8301
  this.#direction = direction;
8016
8302
  this.#fromAlias = fromAlias;
8017
8303
  this.#optional = optional;
8304
+ this.#includeIdentityMembers = includeIdentityMembers;
8018
8305
  this.#variableLength = variableLength;
8019
8306
  this.#pendingEdgePredicates = pendingEdgePredicates;
8020
8307
  }
@@ -8062,7 +8349,8 @@ var TraversalBuilder = class _TraversalBuilder {
8062
8349
  ...depthAlias !== void 0 && { depthAlias }
8063
8350
  }
8064
8351
  },
8065
- this.#pendingEdgePredicates
8352
+ this.#pendingEdgePredicates,
8353
+ this.#includeIdentityMembers
8066
8354
  );
8067
8355
  }
8068
8356
  /**
@@ -8091,7 +8379,8 @@ var TraversalBuilder = class _TraversalBuilder {
8091
8379
  this.#inverseEdgeKinds,
8092
8380
  this.#optional,
8093
8381
  this.#variableLength,
8094
- [...this.#pendingEdgePredicates, newPredicate]
8382
+ [...this.#pendingEdgePredicates, newPredicate],
8383
+ this.#includeIdentityMembers
8095
8384
  );
8096
8385
  }
8097
8386
  /**
@@ -8172,12 +8461,16 @@ var TraversalBuilder = class _TraversalBuilder {
8172
8461
  return new Proxy({}, {
8173
8462
  get: (_, property) => {
8174
8463
  if (typeof property === "symbol") return;
8175
- if (property === "then") return;
8176
- if (property === "toJSON") return;
8177
8464
  if (property === "id") return idAccessor;
8178
8465
  if (property === "kind") return kindAccessor;
8179
8466
  if (property === "fromId") return fromIdAccessor;
8180
8467
  if (property === "toId") return toIdAccessor;
8468
+ if (isInteropProbeKey(property) && !this.#config.schemaIntrospector.hasDeclaredEdgeField(
8469
+ allEdgeKinds,
8470
+ property
8471
+ )) {
8472
+ return;
8473
+ }
8181
8474
  return buildFieldAccessor(property);
8182
8475
  }
8183
8476
  });
@@ -8223,7 +8516,8 @@ var TraversalBuilder = class _TraversalBuilder {
8223
8516
  nodeKinds: kinds,
8224
8517
  joinFromAlias: this.#fromAlias,
8225
8518
  joinEdgeField: this.#direction === "out" ? "from_id" : "to_id",
8226
- optional: this.#optional
8519
+ optional: this.#optional,
8520
+ ...this.#includeIdentityMembers ? { includeIdentityMembers: true } : {}
8227
8521
  };
8228
8522
  const baseTraversal = this.#inverseEdgeKinds.length > 0 ? { ...traversalBase, inverseEdgeKinds: this.#inverseEdgeKinds } : traversalBase;
8229
8523
  const traversal = this.#variableLength.enabled ? {
@@ -8514,6 +8808,7 @@ var QueryBuilder = class _QueryBuilder {
8514
8808
  traverse(edgeKind, edgeAlias, options) {
8515
8809
  validateSqlIdentifier(edgeAlias);
8516
8810
  const direction = options?.direction ?? "out";
8811
+ this.#assertIdentityTraversalAllowed(options);
8517
8812
  const expansion = options?.expand ?? this.#config.defaultTraversalExpansion;
8518
8813
  const includeImplyingEdges = expansion === "implying" || expansion === "all";
8519
8814
  const includeInverseEdges = expansion === "inverse" || expansion === "all";
@@ -8531,7 +8826,12 @@ var QueryBuilder = class _QueryBuilder {
8531
8826
  direction,
8532
8827
  fromAlias,
8533
8828
  inverseEdgeKinds,
8534
- false
8829
+ false,
8830
+ void 0,
8831
+ // variableLength — default
8832
+ void 0,
8833
+ // pendingEdgePredicates — default
8834
+ options?.includeIdentityMembers ?? false
8535
8835
  );
8536
8836
  }
8537
8837
  /**
@@ -8544,6 +8844,7 @@ var QueryBuilder = class _QueryBuilder {
8544
8844
  optionalTraverse(edgeKind, edgeAlias, options) {
8545
8845
  validateSqlIdentifier(edgeAlias);
8546
8846
  const direction = options?.direction ?? "out";
8847
+ this.#assertIdentityTraversalAllowed(options);
8547
8848
  const expansion = options?.expand ?? this.#config.defaultTraversalExpansion;
8548
8849
  const includeImplyingEdges = expansion === "implying" || expansion === "all";
8549
8850
  const includeInverseEdges = expansion === "inverse" || expansion === "all";
@@ -8561,7 +8862,12 @@ var QueryBuilder = class _QueryBuilder {
8561
8862
  direction,
8562
8863
  fromAlias,
8563
8864
  inverseEdgeKinds,
8564
- true
8865
+ true,
8866
+ void 0,
8867
+ // variableLength — default
8868
+ void 0,
8869
+ // pendingEdgePredicates — default
8870
+ options?.includeIdentityMembers ?? false
8565
8871
  );
8566
8872
  }
8567
8873
  /**
@@ -8586,6 +8892,7 @@ var QueryBuilder = class _QueryBuilder {
8586
8892
  });
8587
8893
  }
8588
8894
  const direction = options?.direction ?? "out";
8895
+ this.#assertIdentityTraversalAllowed(options);
8589
8896
  const expansion = options?.expand ?? this.#config.defaultTraversalExpansion;
8590
8897
  const includeImplyingEdges = expansion === "implying" || expansion === "all";
8591
8898
  const includeInverseEdges = expansion === "inverse" || expansion === "all";
@@ -8610,7 +8917,12 @@ var QueryBuilder = class _QueryBuilder {
8610
8917
  direction,
8611
8918
  fromAlias,
8612
8919
  inverseEdgeKinds,
8613
- optional
8920
+ optional,
8921
+ void 0,
8922
+ // variableLength — default
8923
+ void 0,
8924
+ // pendingEdgePredicates — default
8925
+ options?.includeIdentityMembers ?? false
8614
8926
  );
8615
8927
  }
8616
8928
  /**
@@ -8912,11 +9224,15 @@ var QueryBuilder = class _QueryBuilder {
8912
9224
  return new Proxy({}, {
8913
9225
  get: (_, property) => {
8914
9226
  if (typeof property === "symbol") return;
8915
- if (property === "then") return;
8916
- if (property === "toJSON") return;
8917
9227
  if (property === "id") return idAccessor;
8918
9228
  if (property === "kind") return kindAccessor;
8919
9229
  if (property === "$fulltext") return fulltextAccessor;
9230
+ if (isInteropProbeKey(property) && !(kindNames !== void 0 && this.#config.schemaIntrospector.hasDeclaredField(
9231
+ kindNames,
9232
+ property
9233
+ ))) {
9234
+ return;
9235
+ }
8920
9236
  return this.#getFieldBuilderForProperty(kindNames, property, alias);
8921
9237
  }
8922
9238
  });
@@ -8925,6 +9241,21 @@ var QueryBuilder = class _QueryBuilder {
8925
9241
  if (!kindNames) return false;
8926
9242
  return this.#config.schemaIntrospector.hasSearchableField(kindNames);
8927
9243
  }
9244
+ /**
9245
+ * Guards `includeIdentityMembers` against a non-identity-enabled builder.
9246
+ */
9247
+ #assertIdentityTraversalAllowed(options) {
9248
+ if (!options?.includeIdentityMembers || this.#config.identityEnabled) {
9249
+ return;
9250
+ }
9251
+ throw new ConfigurationError(
9252
+ "includeIdentityMembers requires an identity-enabled graph registry.",
9253
+ { code: "IDENTITY_NOT_ENABLED", graphId: this.#config.graphId },
9254
+ {
9255
+ suggestion: "Enable defineGraph(...).identity and build the registry from that graph."
9256
+ }
9257
+ );
9258
+ }
8928
9259
  #expandTraversalEdgeKinds(edgeKind, includeImplyingEdges) {
8929
9260
  return includeImplyingEdges ? this.#config.registry.expandImplyingEdges(edgeKind) : [edgeKind];
8930
9261
  }
@@ -9005,12 +9336,16 @@ var QueryBuilder = class _QueryBuilder {
9005
9336
  return new Proxy({}, {
9006
9337
  get: (_, property) => {
9007
9338
  if (typeof property === "symbol") return;
9008
- if (property === "then") return;
9009
- if (property === "toJSON") return;
9010
9339
  if (property === "id") return idAccessor;
9011
9340
  if (property === "kind") return kindAccessor;
9012
9341
  if (property === "fromId") return fromIdAccessor;
9013
9342
  if (property === "toId") return toIdAccessor;
9343
+ if (isInteropProbeKey(property) && !(edgeKindNames !== void 0 && this.#config.schemaIntrospector.hasDeclaredEdgeField(
9344
+ edgeKindNames,
9345
+ property
9346
+ ))) {
9347
+ return;
9348
+ }
9014
9349
  return this.#getFieldBuilderForEdgeProperty(
9015
9350
  edgeKindNames,
9016
9351
  property,
@@ -9464,11 +9799,31 @@ function createQueryBuilderWithContext(graphId, registry, options) {
9464
9799
  registry.nodeKinds,
9465
9800
  registry.edgeKinds
9466
9801
  );
9802
+ if (options?.identityEnabled === true && registry.identity === void 0) {
9803
+ throw new ConfigurationError(
9804
+ "Identity-aware query compilation requires an identity-enabled graph registry.",
9805
+ { code: "IDENTITY_NOT_ENABLED", graphId },
9806
+ {
9807
+ suggestion: "Build the registry from a graph whose defineGraph(...) config includes identity."
9808
+ }
9809
+ );
9810
+ }
9811
+ if (options?.identitySameIdAcrossKinds !== void 0 && registry.identity === void 0) {
9812
+ throw new ConfigurationError(
9813
+ "identitySameIdAcrossKinds requires an identity-enabled graph registry.",
9814
+ { code: "IDENTITY_NOT_ENABLED", graphId },
9815
+ {
9816
+ suggestion: "Configure sameIdAcrossKinds on defineGraph(...).identity instead."
9817
+ }
9818
+ );
9819
+ }
9467
9820
  const config = {
9468
9821
  graphId,
9469
9822
  registry,
9470
9823
  schemaIntrospector,
9471
9824
  defaultTraversalExpansion: options?.defaultTraversalExpansion ?? "inverse",
9825
+ identityEnabled: options?.identityEnabled ?? registry.identity !== void 0,
9826
+ identitySameIdAcrossKinds: options?.identitySameIdAcrossKinds ?? registry.identity?.sameIdAcrossKinds ?? "fold",
9472
9827
  ...options?.backend !== void 0 && { backend: options.backend },
9473
9828
  ...options?.dialect !== void 0 && { dialect: options.dialect },
9474
9829
  ...options?.schema !== void 0 && { schema: options.schema }
@@ -9543,11 +9898,32 @@ function isPropsObject(value) {
9543
9898
  return typeof value === "object" && value !== null && !Array.isArray(value);
9544
9899
  }
9545
9900
  async function rebuildFulltextIndex(ctx, nodeKind, options) {
9546
- const { backend, registry } = ctx;
9547
- if (!backend.upsertFulltext || !backend.deleteFulltext) {
9548
- throw new ConfigurationError(
9549
- "Backend does not support fulltext; cannot rebuild index",
9550
- { backend: backend.dialect, capability: "fulltext" }
9901
+ const { backend } = ctx;
9902
+ return runFulltextRebuild(
9903
+ {
9904
+ read: backend,
9905
+ runPage: (write) => runOptionallyInTransaction(backend, write)
9906
+ },
9907
+ ctx,
9908
+ nodeKind,
9909
+ options
9910
+ );
9911
+ }
9912
+ async function repopulateFulltextInTransaction(ctx, target, options = {}) {
9913
+ return runFulltextRebuild(
9914
+ { read: target, runPage: (write) => write(target) },
9915
+ ctx,
9916
+ void 0,
9917
+ options
9918
+ );
9919
+ }
9920
+ async function runFulltextRebuild(driver, ctx, nodeKind, options) {
9921
+ const { registry } = ctx;
9922
+ const backend = driver.read;
9923
+ if (!backend.upsertFulltext || !backend.deleteFulltext) {
9924
+ throw new ConfigurationError(
9925
+ "Backend does not support fulltext; cannot rebuild index",
9926
+ { backend: backend.dialect, capability: "fulltext" }
9551
9927
  );
9552
9928
  }
9553
9929
  const pageSize = validatePageSize(options.pageSize);
@@ -9623,7 +9999,7 @@ async function rebuildFulltextIndex(ctx, nodeKind, options) {
9623
9999
  }
9624
10000
  }
9625
10001
  };
9626
- await runOptionallyInTransaction(backend, writePage);
10002
+ await driver.runPage(writePage);
9627
10003
  processed += rows.length;
9628
10004
  upserted += pageResult.toUpsert.length;
9629
10005
  cleared += pageResult.toDelete.length;
@@ -10465,7 +10841,8 @@ function classifyModifications(existing, next) {
10465
10841
  }
10466
10842
  };
10467
10843
  for (const [name, nextNode] of Object.entries(next.nodes ?? {})) {
10468
- const existingNode = existing.nodes?.[name];
10844
+ const existingNodes = existing.nodes;
10845
+ const existingNode = existingNodes !== void 0 && hasOwnKey(existingNodes, name) ? existingNodes[name] : void 0;
10469
10846
  if (existingNode === void 0) continue;
10470
10847
  classifyNode(name, existingNode, nextNode, {
10471
10848
  recordIncompatible,
@@ -10475,7 +10852,8 @@ function classifyModifications(existing, next) {
10475
10852
  });
10476
10853
  }
10477
10854
  for (const [name, nextEdge] of Object.entries(next.edges ?? {})) {
10478
- const existingEdge = existing.edges?.[name];
10855
+ const existingEdges = existing.edges;
10856
+ const existingEdge = existingEdges !== void 0 && hasOwnKey(existingEdges, name) ? existingEdges[name] : void 0;
10479
10857
  if (existingEdge === void 0) continue;
10480
10858
  classifyEdge(name, existingEdge, nextEdge, {
10481
10859
  recordIncompatible,
@@ -10869,13 +11247,17 @@ function resolveMaxHops(rawMaxHops, fallback, optionName) {
10869
11247
  }
10870
11248
  function resolveMaxIterations(value, fallback, algorithm) {
10871
11249
  const maxIterations = value ?? fallback;
10872
- if (!Number.isSafeInteger(maxIterations) || maxIterations < 1) {
11250
+ assertPositiveSafeIntegerOption(maxIterations, algorithm, "maxIterations");
11251
+ return maxIterations;
11252
+ }
11253
+ function assertPositiveSafeIntegerOption(value, algorithm, optionName) {
11254
+ if (value === void 0) return;
11255
+ if (!Number.isSafeInteger(value) || value < 1) {
10873
11256
  throw new ConfigurationError(
10874
- `${algorithm} maxIterations must be a positive safe integer, got ${String(maxIterations)}.`,
10875
- { maxIterations }
11257
+ `${algorithm} ${optionName} must be a positive safe integer, got ${String(value)}.`,
11258
+ { [optionName]: value }
10876
11259
  );
10877
11260
  }
10878
- return maxIterations;
10879
11261
  }
10880
11262
  function assertEdgeKinds(edges) {
10881
11263
  if (edges.length === 0) {
@@ -10885,16 +11267,6 @@ function assertEdgeKinds(edges) {
10885
11267
  );
10886
11268
  }
10887
11269
  }
10888
- function compileNodeKindSeedFilter(nodeKinds) {
10889
- if (nodeKinds === void 0) return sql`TRUE`;
10890
- return compileKindFilter(sql.raw("n.kind"), nodeKinds);
10891
- }
10892
- function normalizeNodeKinds(nodeKinds) {
10893
- if (nodeKinds === void 0) return void 0;
10894
- return [...new Set(nodeKinds)].toSorted(
10895
- (left, right) => compareCodePoints(left, right)
10896
- );
10897
- }
10898
11270
  function assertGraphAnalyticsSupported(ctx, algorithm, options = {}) {
10899
11271
  const graphAnalytics = ctx.backend.capabilities.graphAnalytics?.supported === true;
10900
11272
  const windowFunctions = ctx.backend.capabilities.windowFunctions;
@@ -10970,6 +11342,56 @@ async function countWorkingTableRows(context) {
10970
11342
  );
10971
11343
  return Number(rows[0]?.count ?? 0);
10972
11344
  }
11345
+ async function seedWorkingTableFromNodes(context, nodeKinds, seedColumns) {
11346
+ if (nodeKinds?.length === 0) return;
11347
+ const additionalColumns = seedColumns.length === 0 ? sql.empty() : sql`, ${sql.join(
11348
+ seedColumns.map((column) => sql.identifier(column.name)),
11349
+ sql`, `
11350
+ )}`;
11351
+ const additionalValues = seedColumns.length === 0 ? sql.empty() : sql`, ${sql.join(
11352
+ seedColumns.map((column) => column.value),
11353
+ sql`, `
11354
+ )}`;
11355
+ const compileSeedStatement = (nodeKindFilter) => {
11356
+ return sql`
11357
+ INSERT INTO ${context.workingTable}
11358
+ (graph_id, run_id, node_id, node_kind${additionalColumns})
11359
+ SELECT ${context.graphId}, ${context.runId}, n.id, n.kind${additionalValues}
11360
+ FROM ${context.operation.schema.nodesTable} n
11361
+ WHERE n.graph_id = ${context.graphId}
11362
+ AND ${nodeKindFilter}
11363
+ AND ${context.operation.nodeTemporalFilter}
11364
+ `;
11365
+ };
11366
+ const fixedStatement = compileSeedStatement(sql`TRUE`);
11367
+ const parameterLimit = context.operation.backend.capabilities.maxBindParameters ?? DEFAULT_MAX_BIND_PARAMETERS;
11368
+ const fixedParameterCount = countBindParameters(fixedStatement);
11369
+ const kindBindBudget = parameterLimit - fixedParameterCount;
11370
+ if (kindBindBudget < (nodeKinds === void 0 ? 0 : 1)) {
11371
+ throw new ConfigurationError(
11372
+ "An iterative graph operation cannot fit its node-kind initialization within the backend bind-parameter limit.",
11373
+ { fixedParameterCount, parameterLimit }
11374
+ );
11375
+ }
11376
+ if (nodeKinds === void 0) {
11377
+ await context.executeTemporary(fixedStatement);
11378
+ return;
11379
+ }
11380
+ const normalizedKinds = [...new Set(nodeKinds)].toSorted(
11381
+ (left, right) => compareCodePoints(left, right)
11382
+ );
11383
+ for (const kindChunk of chunkValues(normalizedKinds, kindBindBudget)) {
11384
+ await context.executeTemporary(
11385
+ compileSeedStatement(compileKindFilter(sql.raw("n.kind"), kindChunk))
11386
+ );
11387
+ }
11388
+ }
11389
+ function countBindParameters(fragment) {
11390
+ return fragment.chunks.reduce(
11391
+ (count2, chunk2) => count2 + (chunk2.kind === "parameter" || chunk2.kind === "placeholder" ? 1 : 0),
11392
+ 0
11393
+ );
11394
+ }
10973
11395
  var DEFAULT_MAX_BIND_PARAMETERS = 999;
10974
11396
  var RESERVED_TEMPORAL_BIND_PARAMETERS_PER_BRANCH = 12;
10975
11397
  var RESERVED_WEIGHT_BIND_PARAMETERS_PER_BRANCH = 4;
@@ -11025,64 +11447,101 @@ async function runIterativeGraphOperation(ctx, options, plan) {
11025
11447
  accessMode: "read_only",
11026
11448
  temporaryWrites: INTERNAL_TEMPORARY_WRITES
11027
11449
  };
11028
- return ctx.backend.transaction(async (backend) => {
11029
- const temporaryBackend = requireTemporaryStatements(backend);
11030
- const runId = generateId();
11031
- const workingTable = sql.identifier(
11032
- `typegraph_iterative_${runId.replaceAll("-", "_")}`
11033
- );
11034
- const context = {
11035
- operation: createOperation(ctx, options, temporaryBackend),
11036
- backend: temporaryBackend,
11037
- workingTable,
11038
- graphId: ctx.graphId,
11039
- runId,
11040
- executeTemporary: async (query) => {
11041
- await temporaryBackend.executeTemporaryStatement(
11042
- asCompiledTemporaryStatementSql(query)
11043
- );
11044
- }
11045
- };
11046
- if (context.operation.workingMemory !== void 0) {
11047
- const workingMemoryStatement = ctx.dialect.setTransactionWorkingMemory(
11048
- context.operation.workingMemory
11450
+ const rounds = { started: false };
11451
+ try {
11452
+ return await ctx.backend.transaction(async (backend) => {
11453
+ rounds.started = true;
11454
+ return runWorkingTableRounds(ctx, options, plan, backend);
11455
+ }, transactionOptions);
11456
+ } catch (error) {
11457
+ if (rounds.started || ctx.backend.dialect !== "postgres") throw error;
11458
+ const sqlState = postgresReadWriteRefusedSqlState(error);
11459
+ if (sqlState === void 0) throw error;
11460
+ throw temporaryTableCapabilityError(ctx, plan.algorithm, sqlState, error);
11461
+ }
11462
+ }
11463
+ function temporaryTableCapabilityError(ctx, algorithm, sqlState, cause) {
11464
+ const error = new UnsupportedBackendCapabilityError(
11465
+ algorithm,
11466
+ "graphAnalytics",
11467
+ {
11468
+ dialect: ctx.backend.dialect,
11469
+ supported: false,
11470
+ requirement: "transaction-local temporary tables",
11471
+ sqlState
11472
+ },
11473
+ "Run graph analytics on a writable PostgreSQL primary with a role that has the TEMP privilege."
11474
+ );
11475
+ Object.defineProperty(error, "cause", {
11476
+ configurable: true,
11477
+ value: cause,
11478
+ writable: true
11479
+ });
11480
+ return error;
11481
+ }
11482
+ async function runWorkingTableRounds(ctx, options, plan, backend) {
11483
+ const temporaryBackend = requireTemporaryStatements(backend);
11484
+ const runId = generateId();
11485
+ const workingTable = sql.identifier(
11486
+ `typegraph_iterative_${runId.replaceAll("-", "_")}`
11487
+ );
11488
+ const context = {
11489
+ operation: createOperation(ctx, options, temporaryBackend),
11490
+ backend: temporaryBackend,
11491
+ workingTable,
11492
+ graphId: ctx.graphId,
11493
+ runId,
11494
+ executeTemporary: async (query) => {
11495
+ await temporaryBackend.executeTemporaryStatement(
11496
+ asCompiledTemporaryStatementSql(query)
11049
11497
  );
11050
- if (workingMemoryStatement !== void 0) {
11051
- await context.executeTemporary(workingMemoryStatement);
11052
- }
11053
11498
  }
11499
+ };
11500
+ if (context.operation.workingMemory !== void 0) {
11501
+ const workingMemoryStatement = ctx.dialect.setTransactionWorkingMemory(
11502
+ context.operation.workingMemory
11503
+ );
11504
+ if (workingMemoryStatement !== void 0) {
11505
+ await context.executeTemporary(workingMemoryStatement);
11506
+ }
11507
+ }
11508
+ try {
11054
11509
  await context.executeTemporary(plan.createWorkingTable(context));
11055
- let operationFailure;
11056
- try {
11057
- let state = await plan.initialize(context);
11058
- let analyzedRowCount = await refreshWorkingTableStatistics(
11059
- context,
11060
- state.workingTableSize
11061
- );
11062
- for (let iteration = 1; iteration <= plan.maxIterations && !plan.hasConverged(state); iteration++) {
11063
- state = await plan.runRound(context, state, iteration);
11064
- if (iteration < plan.maxIterations && !plan.hasConverged(state)) {
11065
- analyzedRowCount = await refreshWorkingTableStatistics(
11066
- context,
11067
- state.workingTableSize,
11068
- analyzedRowCount
11069
- );
11070
- }
11071
- }
11072
- if (!plan.hasConverged(state)) {
11073
- throw new GraphAlgorithmConvergenceError(
11074
- plan.algorithm,
11075
- plan.maxIterations
11510
+ } catch (error) {
11511
+ const sqlState = ctx.backend.dialect === "postgres" ? postgresTemporaryTableUnavailableSqlState(error) : void 0;
11512
+ if (sqlState === void 0) throw error;
11513
+ throw temporaryTableCapabilityError(ctx, plan.algorithm, sqlState, error);
11514
+ }
11515
+ let operationFailure;
11516
+ try {
11517
+ let state = await plan.initialize(context);
11518
+ let analyzedRowCount = await refreshWorkingTableStatistics(
11519
+ context,
11520
+ state.workingTableSize
11521
+ );
11522
+ for (let iteration = 1; iteration <= plan.maxIterations && !plan.hasConverged(state); iteration++) {
11523
+ state = await plan.runRound(context, state, iteration);
11524
+ if (iteration < plan.maxIterations && !plan.hasConverged(state)) {
11525
+ analyzedRowCount = await refreshWorkingTableStatistics(
11526
+ context,
11527
+ state.workingTableSize,
11528
+ analyzedRowCount
11076
11529
  );
11077
11530
  }
11078
- return await plan.extractResult(context, state);
11079
- } catch (error) {
11080
- operationFailure = { error };
11081
- throw error;
11082
- } finally {
11083
- await cleanupWorkingTables(context, plan.cleanup, operationFailure);
11084
11531
  }
11085
- }, transactionOptions);
11532
+ if (!plan.hasConverged(state)) {
11533
+ throw new GraphAlgorithmConvergenceError(
11534
+ plan.algorithm,
11535
+ plan.maxIterations
11536
+ );
11537
+ }
11538
+ return await plan.extractResult(context, state);
11539
+ } catch (error) {
11540
+ operationFailure = { error };
11541
+ throw error;
11542
+ } finally {
11543
+ await cleanupWorkingTables(context, plan.cleanup, operationFailure);
11544
+ }
11086
11545
  }
11087
11546
  function shouldRefreshWorkingTableStatistics(workingTableSize, analyzedRowCount) {
11088
11547
  if (workingTableSize < WORKING_TABLE_ANALYZE_MINIMUM_ROWS) return false;
@@ -11385,7 +11844,6 @@ async function executeLabelPropagation(ctx, options) {
11385
11844
  DEFAULT_LABEL_PROPAGATION_MAX_ITERATIONS,
11386
11845
  "labelPropagation"
11387
11846
  );
11388
- const nodeKinds = normalizeNodeKinds(options.nodeKinds);
11389
11847
  const policy = {
11390
11848
  detector: createOscillationDetector(),
11391
11849
  maxIterations,
@@ -11401,7 +11859,7 @@ async function executeLabelPropagation(ctx, options) {
11401
11859
  algorithm: "labelPropagation",
11402
11860
  maxIterations,
11403
11861
  createWorkingTable,
11404
- initialize: (context) => initializeWorkingTables(context, nodeKinds),
11862
+ initialize: (context) => initializeWorkingTables(context, options.nodeKinds),
11405
11863
  runRound: (context, state, iteration) => runLabelPropagationRound(context, state, iteration, policy),
11406
11864
  hasConverged(state) {
11407
11865
  return state.changedCount === 0 || state.completed;
@@ -11466,26 +11924,21 @@ function activeIndexIdentifier(context) {
11466
11924
  );
11467
11925
  }
11468
11926
  async function initializeWorkingTables(context, nodeKinds) {
11469
- const { operation, workingTable, graphId, runId } = context;
11470
- const nodeKindFilter = compileNodeKindSeedFilter(nodeKinds);
11471
- await context.executeTemporary(sql`
11472
- INSERT INTO ${workingTable}
11473
- (graph_id, run_id, node_id, node_kind, label_id, label_kind,
11474
- next_label_id, next_label_kind, changed_round, active_round)
11475
- SELECT
11476
- ${graphId}, ${runId}, n.id, n.kind, n.id, n.kind, n.id, n.kind, 0, 1
11477
- FROM ${operation.schema.nodesTable} n
11478
- WHERE n.graph_id = ${graphId}
11479
- AND ${nodeKindFilter}
11480
- AND ${operation.nodeTemporalFilter}
11481
- `);
11927
+ await seedWorkingTableFromNodes(context, nodeKinds, [
11928
+ { name: "label_id", value: sql.raw("n.id") },
11929
+ { name: "label_kind", value: sql.raw("n.kind") },
11930
+ { name: "next_label_id", value: sql.raw("n.id") },
11931
+ { name: "next_label_kind", value: sql.raw("n.kind") },
11932
+ { name: "changed_round", value: sql.raw("0") },
11933
+ { name: "active_round", value: sql.raw("1") }
11934
+ ]);
11482
11935
  await context.executeTemporary(sql`
11483
11936
  CREATE INDEX ${frontierIndexIdentifier(context)}
11484
- ON ${workingTable} (graph_id, run_id, changed_round)
11937
+ ON ${context.workingTable} (graph_id, run_id, changed_round)
11485
11938
  `);
11486
11939
  await context.executeTemporary(sql`
11487
11940
  CREATE INDEX ${activeIndexIdentifier(context)}
11488
- ON ${workingTable} (graph_id, run_id, active_round)
11941
+ ON ${context.workingTable} (graph_id, run_id, active_round)
11489
11942
  `);
11490
11943
  const neighborTable = neighborTableIdentifier(context);
11491
11944
  await context.executeTemporary(sql`
@@ -11497,10 +11950,10 @@ async function initializeWorkingTables(context, nodeKinds) {
11497
11950
  PRIMARY KEY (target_kind, target_id, neighbor_kind, neighbor_id)
11498
11951
  )
11499
11952
  `);
11500
- for (const edgeKinds of operation.edgeKindChunks) {
11953
+ for (const edgeKinds of context.operation.edgeKindChunks) {
11501
11954
  await materializeChunkNeighbors(context, edgeKinds);
11502
11955
  }
11503
- const analyzeNeighbors = operation.ctx.dialect.analyzeTemporaryTable(neighborTable);
11956
+ const analyzeNeighbors = context.operation.ctx.dialect.analyzeTemporaryTable(neighborTable);
11504
11957
  if (analyzeNeighbors !== void 0) {
11505
11958
  await context.executeTemporary(analyzeNeighbors);
11506
11959
  }
@@ -11742,7 +12195,7 @@ function executePageRankOperation(ctx, options, seeds) {
11742
12195
  hasConverged(state) {
11743
12196
  return state.maximumChange <= resolved.tolerance;
11744
12197
  },
11745
- extractResult: extractScores
12198
+ extractResult: (context, state) => extractScores(context, state, resolved.topK)
11746
12199
  });
11747
12200
  }
11748
12201
  function resolvePageRankOptions(options, algorithm) {
@@ -11760,6 +12213,7 @@ function resolvePageRankOptions(options, algorithm) {
11760
12213
  { tolerance }
11761
12214
  );
11762
12215
  }
12216
+ assertPositiveSafeIntegerOption(options.topK, algorithm, "topK");
11763
12217
  return {
11764
12218
  dampingFactor,
11765
12219
  tolerance,
@@ -11768,7 +12222,8 @@ function resolvePageRankOptions(options, algorithm) {
11768
12222
  DEFAULT_MAX_ITERATIONS,
11769
12223
  algorithm
11770
12224
  ),
11771
- nodeKinds: normalizeNodeKinds(options.nodeKinds)
12225
+ nodeKinds: options.nodeKinds,
12226
+ topK: options.topK
11772
12227
  };
11773
12228
  }
11774
12229
  function normalizeSeeds(seeds) {
@@ -11841,17 +12296,12 @@ function createWorkingTable2(context) {
11841
12296
  }
11842
12297
  async function initializeWorkingTable(context, nodeKinds, seeds) {
11843
12298
  const { operation, workingTable, graphId, runId } = context;
11844
- const nodeKindFilter = compileNodeKindSeedFilter(nodeKinds);
11845
- await context.executeTemporary(sql`
11846
- INSERT INTO ${workingTable}
11847
- (graph_id, run_id, node_id, node_kind, score, next_score,
11848
- personalization, out_weight)
11849
- SELECT ${graphId}, ${runId}, n.id, n.kind, 0.0, 0.0, 0.0, 0.0
11850
- FROM ${operation.schema.nodesTable} n
11851
- WHERE n.graph_id = ${graphId}
11852
- AND ${nodeKindFilter}
11853
- AND ${operation.nodeTemporalFilter}
11854
- `);
12299
+ await seedWorkingTableFromNodes(context, nodeKinds, [
12300
+ { name: "score", value: sql.raw("0.0") },
12301
+ { name: "next_score", value: sql.raw("0.0") },
12302
+ { name: "personalization", value: sql.raw("0.0") },
12303
+ { name: "out_weight", value: sql.raw("0.0") }
12304
+ ]);
11855
12305
  const workingTableSize = await countWorkingTableRows(context);
11856
12306
  if (workingTableSize === 0) {
11857
12307
  if (seeds !== void 0) {
@@ -12087,16 +12537,18 @@ async function readMaximumChange(context, sourceScoreColumn, targetScoreColumn)
12087
12537
  );
12088
12538
  return Number(rows[0]?.value ?? 0);
12089
12539
  }
12090
- async function extractScores(context, state) {
12540
+ async function extractScores(context, state, topK) {
12091
12541
  const score = sql.identifier(state.activeScoreColumn);
12092
12542
  const nodeId = context.operation.ctx.dialect.binaryText(sql`node_id`);
12093
12543
  const nodeKind = context.operation.ctx.dialect.binaryText(sql`node_kind`);
12544
+ const limitClause = topK === void 0 ? sql.empty() : sql`LIMIT ${topK}`;
12094
12545
  const rows = await context.backend.execute(
12095
12546
  asCompiledRowsSql(sql`
12096
12547
  SELECT node_id, node_kind, ${score} AS score
12097
12548
  FROM ${context.workingTable}
12098
12549
  WHERE graph_id = ${context.graphId} AND run_id = ${context.runId}
12099
12550
  ORDER BY ${score} DESC, ${nodeId}, ${nodeKind}
12551
+ ${limitClause}
12100
12552
  `)
12101
12553
  );
12102
12554
  return rows.map((row) => ({
@@ -12679,7 +13131,11 @@ async function executeWeaklyConnectedComponents(ctx, options) {
12679
13131
  DEFAULT_WCC_MAX_ITERATIONS,
12680
13132
  "weaklyConnectedComponents"
12681
13133
  );
12682
- const nodeKinds = normalizeNodeKinds(options.nodeKinds);
13134
+ assertPositiveSafeIntegerOption(
13135
+ options.minComponentSize,
13136
+ "weaklyConnectedComponents",
13137
+ "minComponentSize"
13138
+ );
12683
13139
  const traversalOptions = {
12684
13140
  edges: options.edges,
12685
13141
  direction: "both",
@@ -12690,12 +13146,12 @@ async function executeWeaklyConnectedComponents(ctx, options) {
12690
13146
  algorithm: "weaklyConnectedComponents",
12691
13147
  maxIterations,
12692
13148
  createWorkingTable: createWorkingTable4,
12693
- initialize: (context) => initializeWorkingTable2(context, nodeKinds),
13149
+ initialize: (context) => initializeWorkingTable2(context, options.nodeKinds),
12694
13150
  runRound: runLabelPropagationRound2,
12695
13151
  hasConverged(state) {
12696
13152
  return state.changedCount === 0;
12697
13153
  },
12698
- extractResult: extractMemberships2
13154
+ extractResult: (context) => extractMemberships2(context, options.minComponentSize)
12699
13155
  });
12700
13156
  }
12701
13157
  function createWorkingTable4(context) {
@@ -12715,22 +13171,16 @@ function createWorkingTable4(context) {
12715
13171
  `;
12716
13172
  }
12717
13173
  async function initializeWorkingTable2(context, nodeKinds) {
12718
- const { operation, workingTable, graphId, runId } = context;
12719
- const nodeKindFilter = compileNodeKindSeedFilter(nodeKinds);
12720
- await context.executeTemporary(sql`
12721
- INSERT INTO ${workingTable}
12722
- (graph_id, run_id, node_id, node_kind, label_id, label_kind,
12723
- next_label_id, next_label_kind, improved_round)
12724
- SELECT
12725
- ${graphId}, ${runId}, n.id, n.kind, n.id, n.kind, n.id, n.kind, 0
12726
- FROM ${operation.schema.nodesTable} n
12727
- WHERE n.graph_id = ${graphId}
12728
- AND ${nodeKindFilter}
12729
- AND ${operation.nodeTemporalFilter}
12730
- `);
13174
+ await seedWorkingTableFromNodes(context, nodeKinds, [
13175
+ { name: "label_id", value: sql.raw("n.id") },
13176
+ { name: "label_kind", value: sql.raw("n.kind") },
13177
+ { name: "next_label_id", value: sql.raw("n.id") },
13178
+ { name: "next_label_kind", value: sql.raw("n.kind") },
13179
+ { name: "improved_round", value: sql.raw("0") }
13180
+ ]);
12731
13181
  await context.executeTemporary(sql`
12732
13182
  CREATE INDEX ${frontierIndexIdentifier(context)}
12733
- ON ${workingTable} (graph_id, run_id, improved_round)
13183
+ ON ${context.workingTable} (graph_id, run_id, improved_round)
12734
13184
  `);
12735
13185
  const workingTableSize = await countWorkingTableRows(context);
12736
13186
  return { changedCount: workingTableSize, workingTableSize };
@@ -12830,24 +13280,32 @@ function applyNextLabels2(context, iteration) {
12830
13280
  `)
12831
13281
  );
12832
13282
  }
12833
- async function extractMemberships2(context) {
13283
+ async function extractMemberships2(context, minComponentSize) {
12834
13284
  const { operation } = context;
12835
- const componentId = operation.ctx.dialect.binaryText(sql`label_id`);
12836
- const componentKind = operation.ctx.dialect.binaryText(sql`label_kind`);
13285
+ const labelId = operation.ctx.dialect.binaryText(sql`label_id`);
13286
+ const labelKind = operation.ctx.dialect.binaryText(sql`label_kind`);
13287
+ const componentId = operation.ctx.dialect.binaryText(sql`component_id`);
13288
+ const componentKind = operation.ctx.dialect.binaryText(sql`component_kind`);
12837
13289
  const nodeId = operation.ctx.dialect.binaryText(sql`node_id`);
12838
13290
  const nodeKind = operation.ctx.dialect.binaryText(sql`node_kind`);
13291
+ const componentSizeFilter = minComponentSize === void 0 ? sql.empty() : sql`WHERE component_size >= ${minComponentSize}`;
12839
13292
  const rows = await context.backend.execute(
12840
13293
  asCompiledRowsSql(sql`
12841
- SELECT
12842
- node_id,
12843
- node_kind,
12844
- label_id AS component_id,
12845
- label_kind AS component_kind,
12846
- COUNT(*) OVER (
12847
- PARTITION BY ${componentKind}, ${componentId}
12848
- ) AS component_size
12849
- FROM ${context.workingTable}
12850
- WHERE graph_id = ${context.graphId} AND run_id = ${context.runId}
13294
+ WITH memberships AS (
13295
+ SELECT
13296
+ node_id,
13297
+ node_kind,
13298
+ label_id AS component_id,
13299
+ label_kind AS component_kind,
13300
+ COUNT(*) OVER (
13301
+ PARTITION BY ${labelKind}, ${labelId}
13302
+ ) AS component_size
13303
+ FROM ${context.workingTable}
13304
+ WHERE graph_id = ${context.graphId} AND run_id = ${context.runId}
13305
+ )
13306
+ SELECT node_id, node_kind, component_id, component_kind, component_size
13307
+ FROM memberships
13308
+ ${componentSizeFilter}
12851
13309
  ORDER BY ${componentId}, ${componentKind}, ${nodeId}, ${nodeKind}
12852
13310
  `)
12853
13311
  );
@@ -13428,6 +13886,94 @@ function createGraphAlgorithms(params) {
13428
13886
  };
13429
13887
  }
13430
13888
 
13889
+ // src/store/operations/edge-identity.ts
13890
+ function edgeIdentityFromRow(row) {
13891
+ return {
13892
+ kind: row.kind,
13893
+ fromKind: row.from_kind,
13894
+ fromId: row.from_id,
13895
+ toKind: row.to_kind,
13896
+ toId: row.to_id
13897
+ };
13898
+ }
13899
+ function assertEdgeIdentityMatches(id, expected, actual, operation) {
13900
+ const mismatches = [
13901
+ expected.kind === actual.kind ? void 0 : "kind",
13902
+ expected.fromKind === void 0 || expected.fromKind === actual.fromKind ? void 0 : "from.kind",
13903
+ expected.fromId === void 0 || expected.fromId === actual.fromId ? void 0 : "from.id",
13904
+ expected.toKind === void 0 || expected.toKind === actual.toKind ? void 0 : "to.kind",
13905
+ expected.toId === void 0 || expected.toId === actual.toId ? void 0 : "to.id"
13906
+ ].filter((path) => path !== void 0);
13907
+ if (mismatches.length === 0) return;
13908
+ throw new ValidationError(
13909
+ `Edge "${id}" belongs to ${actual.kind} (${actual.fromKind}/${actual.fromId} -> ${actual.toKind}/${actual.toId}), not ${expected.kind}` + (expected.fromKind === void 0 ? "." : ` (${expected.fromKind}/${expected.fromId} -> ${expected.toKind}/${expected.toId}).`),
13910
+ {
13911
+ entityType: "edge",
13912
+ kind: expected.kind,
13913
+ operation,
13914
+ id,
13915
+ issues: mismatches.map((path) => ({
13916
+ path,
13917
+ message: "The edge id resolves to a different immutable identity",
13918
+ code: EDGE_IDENTITY_MISMATCH_CODE
13919
+ }))
13920
+ },
13921
+ {
13922
+ suggestion: "Use the edge collection and endpoints that created this id, or choose a new id."
13923
+ }
13924
+ );
13925
+ }
13926
+
13927
+ // src/store/collections/coalesce.ts
13928
+ function findRepeatedUpsertIds(items) {
13929
+ const seen = /* @__PURE__ */ new Set();
13930
+ const repeated = /* @__PURE__ */ new Set();
13931
+ for (const item of items) {
13932
+ if (seen.has(item.id)) repeated.add(item.id);
13933
+ seen.add(item.id);
13934
+ }
13935
+ return repeated;
13936
+ }
13937
+ function windowFieldChanges(requested, stored) {
13938
+ if (requested === void 0) {
13939
+ return false;
13940
+ }
13941
+ if (!isCanonicalIsoDate(requested)) {
13942
+ return true;
13943
+ }
13944
+ return !statedBoundMatchesStored(requested, stored);
13945
+ }
13946
+ function lowerBoundRequiresWrite(requested, stored) {
13947
+ const requestedValidFrom = requested?.validFrom;
13948
+ if (!preservesImmutableLowerBound(requested?.onImmutableLowerBound)) {
13949
+ return windowFieldChanges(requestedValidFrom, stored);
13950
+ }
13951
+ return requestedValidFrom !== void 0 && !isCanonicalIsoDate(requestedValidFrom);
13952
+ }
13953
+ function upsertWindowChanges(requested, current) {
13954
+ return lowerBoundRequiresWrite(requested, current.valid_from) || windowFieldChanges(requested?.validTo, current.valid_to);
13955
+ }
13956
+ function windowAfterUpsertCreate(requested) {
13957
+ return { valid_from: requested.validFrom, valid_to: requested.validTo };
13958
+ }
13959
+ function windowAfterUpsertUpdate(current, requested) {
13960
+ return {
13961
+ valid_from: current.valid_from,
13962
+ valid_to: requested.validTo ?? current.valid_to
13963
+ };
13964
+ }
13965
+ function shouldCoalesceUpsert(existing, options, runDirtyCheck) {
13966
+ const windowChanges = upsertWindowChanges(options, existing);
13967
+ if (runDirtyCheck === void 0 || existing.deleted_at !== void 0 || windowChanges) {
13968
+ return false;
13969
+ }
13970
+ try {
13971
+ return runDirtyCheck().unchanged;
13972
+ } catch {
13973
+ return false;
13974
+ }
13975
+ }
13976
+
13431
13977
  // src/store/collections/edge-collection.ts
13432
13978
  function narrowEdge(edge) {
13433
13979
  return edge;
@@ -13449,13 +13995,29 @@ function buildCreateEdgeInput(kind, from, to, props, options) {
13449
13995
  if (options?.validTo !== void 0) input.validTo = options.validTo;
13450
13996
  return input;
13451
13997
  }
13452
- function buildUpdateEdgeInput(id, props, options) {
13453
- const input = { id, props };
13998
+ function buildUpdateEdgeInput(kind, id, props, options) {
13999
+ const input = { id, identity: { kind }, props };
14000
+ if (options?.validTo !== void 0) input.validTo = options.validTo;
14001
+ return input;
14002
+ }
14003
+ function buildUpsertUpdateEdgeInput(kind, id, from, to, props, options) {
14004
+ const input = {
14005
+ id,
14006
+ identity: {
14007
+ kind,
14008
+ fromKind: from.kind,
14009
+ fromId: from.id,
14010
+ toKind: to.kind,
14011
+ toId: to.id
14012
+ },
14013
+ props
14014
+ };
14015
+ if (options?.validFrom !== void 0) input.validFrom = options.validFrom;
13454
14016
  if (options?.validTo !== void 0) input.validTo = options.validTo;
13455
14017
  return input;
13456
14018
  }
13457
14019
  function endpointKey(endpointKind, id) {
13458
- return `${endpointKind}\0${id}`;
14020
+ return encodeTupleKey([endpointKind, id]);
13459
14021
  }
13460
14022
  function groupEndpointIdsByKind(references) {
13461
14023
  const referencesByKind = groupBy(references, (ref) => ref.kind);
@@ -13502,6 +14064,7 @@ function createEdgeCollection(config) {
13502
14064
  executeUpdate: executeEdgeUpdate2,
13503
14065
  executeUpsertUpdate: executeEdgeUpsertUpdate2,
13504
14066
  executeDelete: executeEdgeDelete2,
14067
+ executeDeleteBatch: executeEdgeDeleteBatch2,
13505
14068
  executeHardDelete: executeEdgeHardDelete2,
13506
14069
  temporalRowMatcher
13507
14070
  } = config;
@@ -13610,7 +14173,7 @@ function createEdgeCollection(config) {
13610
14173
  },
13611
14174
  async update(id, props, options) {
13612
14175
  const result = await executeEdgeUpdate2(
13613
- buildUpdateEdgeInput(id, props, options),
14176
+ buildUpdateEdgeInput(kind, id, props, options),
13614
14177
  backend
13615
14178
  );
13616
14179
  return narrowEdge(result);
@@ -13650,10 +14213,10 @@ function createEdgeCollection(config) {
13650
14213
  };
13651
14214
  },
13652
14215
  async delete(id) {
13653
- await executeEdgeDelete2(id, backend);
14216
+ await executeEdgeDelete2(kind, id, backend);
13654
14217
  },
13655
14218
  async hardDelete(id) {
13656
- await executeEdgeHardDelete2(id, backend);
14219
+ await executeEdgeHardDelete2(kind, id, backend);
13657
14220
  },
13658
14221
  async find(filter, temporal) {
13659
14222
  const untypedFilter = filter;
@@ -13712,6 +14275,20 @@ function createEdgeCollection(config) {
13712
14275
  const toUpdate = [];
13713
14276
  const pending = /* @__PURE__ */ new Map();
13714
14277
  const deferred = [];
14278
+ const repeatedIds = config.upsertDirtyCheck === void 0 ? /* @__PURE__ */ new Set() : findRepeatedUpsertIds(items);
14279
+ function runDirtyCheck(edgeKind, id, currentProps, inputProps) {
14280
+ if (config.upsertDirtyCheck === void 0) return void 0;
14281
+ try {
14282
+ return config.upsertDirtyCheck(
14283
+ edgeKind,
14284
+ id,
14285
+ currentProps,
14286
+ inputProps
14287
+ );
14288
+ } catch {
14289
+ return void 0;
14290
+ }
14291
+ }
13715
14292
  let itemIndex = 0;
13716
14293
  for (const item of items) {
13717
14294
  const pendingEntry = pending.get(item.id);
@@ -13728,25 +14305,48 @@ function createEdgeCollection(config) {
13728
14305
  item
13729
14306
  )
13730
14307
  });
14308
+ pending.set(item.id, {
14309
+ identity: {
14310
+ kind,
14311
+ fromKind: item.from.kind,
14312
+ fromId: item.from.id,
14313
+ toKind: item.to.kind,
14314
+ toId: item.to.id
14315
+ },
14316
+ props: repeatedIds.has(item.id) ? runDirtyCheck(kind, item.id, {}, inputProps)?.validatedProps : void 0,
14317
+ window: windowAfterUpsertCreate(item),
14318
+ sourceIndex: itemIndex
14319
+ });
13731
14320
  itemIndex++;
13732
14321
  continue;
13733
14322
  }
13734
14323
  const deletedAt = pendingEntry === void 0 ? requireDefined(original).deleted_at : void 0;
13735
- let dirty;
13736
- if (config.upsertDirtyCheck !== void 0) {
13737
- const currentProps = pendingEntry?.props ?? rowPropsToObject(requireDefined(original).props);
13738
- try {
13739
- dirty = config.upsertDirtyCheck(
13740
- original?.kind ?? kind,
13741
- item.id,
13742
- currentProps,
13743
- inputProps
13744
- );
13745
- } catch {
13746
- dirty = void 0;
13747
- }
13748
- }
13749
- const coalesce = dirty?.unchanged === true && deletedAt === void 0 && item.validFrom === void 0 && item.validTo === void 0;
14324
+ const expectedIdentity = {
14325
+ kind,
14326
+ fromKind: item.from.kind,
14327
+ fromId: item.from.id,
14328
+ toKind: item.to.kind,
14329
+ toId: item.to.id
14330
+ };
14331
+ const actualIdentity = pendingEntry === void 0 ? edgeIdentityFromRow(requireDefined(original)) : pendingEntry.identity;
14332
+ assertEdgeIdentityMatches(
14333
+ item.id,
14334
+ expectedIdentity,
14335
+ actualIdentity,
14336
+ "update"
14337
+ );
14338
+ const currentProps = pendingEntry === void 0 ? rowPropsToObject(requireDefined(original).props) : pendingEntry.props;
14339
+ const dirty = currentProps === void 0 ? void 0 : runDirtyCheck(
14340
+ original?.kind ?? kind,
14341
+ item.id,
14342
+ currentProps,
14343
+ inputProps
14344
+ );
14345
+ const currentWindow = pendingEntry === void 0 ? {
14346
+ valid_from: requireDefined(original).valid_from,
14347
+ valid_to: requireDefined(original).valid_to
14348
+ } : pendingEntry.window;
14349
+ const coalesce = dirty?.unchanged === true && deletedAt === void 0 && !upsertWindowChanges(item, currentWindow);
13750
14350
  if (coalesce) {
13751
14351
  if (pendingEntry === void 0) {
13752
14352
  results2[itemIndex] = narrowEdge(
@@ -13761,15 +14361,25 @@ function createEdgeCollection(config) {
13761
14361
  } else {
13762
14362
  toUpdate.push({
13763
14363
  index: itemIndex,
13764
- input: buildUpdateEdgeInput(item.id, inputProps, item),
14364
+ input: buildUpsertUpdateEdgeInput(
14365
+ kind,
14366
+ item.id,
14367
+ item.from,
14368
+ item.to,
14369
+ inputProps,
14370
+ item
14371
+ ),
13765
14372
  clearDeleted: deletedAt !== void 0
13766
14373
  });
13767
- if (dirty !== void 0) {
13768
- pending.set(item.id, {
13769
- props: dirty.validatedProps,
13770
- sourceIndex: itemIndex
13771
- });
13772
- }
14374
+ pending.set(item.id, {
14375
+ identity: actualIdentity,
14376
+ props: dirty?.validatedProps,
14377
+ // A resurrection rewrites both bounds only when it NAMES the lower
14378
+ // one (buildUpdateEdge's clearDeleted leg); omitting `validFrom`
14379
+ // keeps the stored window, exactly like a live-row update.
14380
+ window: deletedAt !== void 0 && item.validFrom !== void 0 ? windowAfterUpsertCreate(item) : windowAfterUpsertUpdate(currentWindow, item),
14381
+ sourceIndex: itemIndex
14382
+ });
13773
14383
  }
13774
14384
  itemIndex++;
13775
14385
  }
@@ -13793,7 +14403,10 @@ function createEdgeCollection(config) {
13793
14403
  }
13794
14404
  return { results: results2, mutations: toCreate.length + toUpdate.length };
13795
14405
  };
13796
- const { results, mutations } = backend.capabilities.transactions && "transaction" in backend ? await backend.transaction(async (txBackend) => upsertAll(txBackend)) : await upsertAll(backend);
14406
+ const { results, mutations } = await runOptionallyInTransaction(
14407
+ backend,
14408
+ (target) => upsertAll(target)
14409
+ );
13797
14410
  await config.maybeRefreshStatisticsAfterBulk?.(mutations);
13798
14411
  return results;
13799
14412
  },
@@ -13811,16 +14424,7 @@ function createEdgeCollection(config) {
13811
14424
  },
13812
14425
  async bulkDelete(ids) {
13813
14426
  if (ids.length === 0) return;
13814
- const deleteAll = async (target) => {
13815
- for (const id of ids) {
13816
- await executeEdgeDelete2(id, target);
13817
- }
13818
- };
13819
- if (backend.capabilities.transactions && "transaction" in backend) {
13820
- await backend.transaction(async (txBackend) => deleteAll(txBackend));
13821
- return;
13822
- }
13823
- await deleteAll(backend);
14427
+ await executeEdgeDeleteBatch2(kind, ids, backend);
13824
14428
  },
13825
14429
  async findByEndpoints(from, to, options, temporal) {
13826
14430
  const result = await config.executeFindByEndpoints(
@@ -13900,18 +14504,6 @@ function createEdgeCollection(config) {
13900
14504
  };
13901
14505
  }
13902
14506
 
13903
- // src/store/collections/coalesce.ts
13904
- function shouldCoalesceUpsert(existing, options, runDirtyCheck) {
13905
- if (runDirtyCheck === void 0 || existing.deleted_at !== void 0 || options?.validFrom !== void 0 || options?.validTo !== void 0) {
13906
- return false;
13907
- }
13908
- try {
13909
- return runDirtyCheck().unchanged;
13910
- } catch {
13911
- return false;
13912
- }
13913
- }
13914
-
13915
14507
  // src/store/collections/node-collection.ts
13916
14508
  function narrowNode(node) {
13917
14509
  return node;
@@ -13937,7 +14529,6 @@ function evaluateNodePredicate(accessor, predicate) {
13937
14529
  {
13938
14530
  get(_target, property) {
13939
14531
  if (typeof property === "symbol") return;
13940
- if (property === "then" || property === "toJSON") return;
13941
14532
  if (property === "id" || property === "kind" || property === "$fulltext") {
13942
14533
  return accessor[property];
13943
14534
  }
@@ -13960,6 +14551,15 @@ function buildUpdateInput(kind, id, props, options) {
13960
14551
  if (options?.validTo !== void 0) input.validTo = options.validTo;
13961
14552
  return input;
13962
14553
  }
14554
+ function buildUpsertUpdateInput(kind, id, props, options) {
14555
+ const input = { kind, id, props };
14556
+ if (options?.validFrom !== void 0) input.validFrom = options.validFrom;
14557
+ if (options?.validTo !== void 0) input.validTo = options.validTo;
14558
+ if (options?.onImmutableLowerBound !== void 0) {
14559
+ input.onImmutableLowerBound = options.onImmutableLowerBound;
14560
+ }
14561
+ return input;
14562
+ }
13963
14563
  function mapBulkNodeInputs(kind, items) {
13964
14564
  return items.map((item) => buildCreateInput(kind, item.props, item));
13965
14565
  }
@@ -13977,6 +14577,7 @@ function createNodeCollection(config) {
13977
14577
  executeUpdateWhere: executeNodeUpdateWhere2,
13978
14578
  executeUpsertUpdate: executeNodeUpsertUpdate2,
13979
14579
  executeDelete: executeNodeDelete2,
14580
+ executeDeleteBatch: executeNodeDeleteBatch2,
13980
14581
  executeHardDelete: executeNodeHardDelete2,
13981
14582
  temporalRowMatcher,
13982
14583
  createQuery,
@@ -14147,20 +14748,30 @@ function createNodeCollection(config) {
14147
14748
  },
14148
14749
  async upsertByIdFromRecord(id, data, options) {
14149
14750
  const existing = await backend.getNode(graphId, kind, id);
14150
- if (existing) {
14751
+ const coalesces = (row) => {
14151
14752
  const runDirtyCheck = config.upsertDirtyCheck && (() => requireDefined(config.upsertDirtyCheck)(
14152
14753
  kind,
14153
14754
  id,
14154
- rowPropsToObject(existing.props),
14755
+ rowPropsToObject(row.props),
14155
14756
  data
14156
14757
  ));
14157
- if (shouldCoalesceUpsert(existing, options, runDirtyCheck)) {
14158
- return narrowNode(rowToNode2(existing));
14758
+ return shouldCoalesceUpsert(row, options, runDirtyCheck);
14759
+ };
14760
+ const decision = existing !== void 0 && coalesces(existing) ? await runOptionallyInTransaction(
14761
+ backend,
14762
+ async (target) => {
14763
+ const confirmed = await target.getNode(graphId, kind, id);
14764
+ return confirmed !== void 0 && coalesces(confirmed) ? { verdict: "skip", row: confirmed } : { verdict: "write", row: confirmed };
14159
14765
  }
14766
+ ) : { verdict: "write", row: existing };
14767
+ if (decision.verdict === "skip") {
14768
+ return narrowNode(rowToNode2(decision.row));
14769
+ }
14770
+ if (decision.row !== void 0) {
14160
14771
  const result2 = await executeNodeUpdate2(
14161
- buildUpdateInput(kind, id, data, options),
14772
+ buildUpsertUpdateInput(kind, id, data, options),
14162
14773
  backend,
14163
- { clearDeleted: existing.deleted_at !== void 0 }
14774
+ { clearDeleted: decision.row.deleted_at !== void 0 }
14164
14775
  );
14165
14776
  return narrowNode(result2);
14166
14777
  }
@@ -14206,6 +14817,15 @@ function createNodeCollection(config) {
14206
14817
  const toUpdate = [];
14207
14818
  const pending = /* @__PURE__ */ new Map();
14208
14819
  const deferred = [];
14820
+ const repeatedIds = config.upsertDirtyCheck === void 0 ? /* @__PURE__ */ new Set() : findRepeatedUpsertIds(items);
14821
+ function runDirtyCheck(id, currentProps, inputProps) {
14822
+ if (config.upsertDirtyCheck === void 0) return void 0;
14823
+ try {
14824
+ return config.upsertDirtyCheck(kind, id, currentProps, inputProps);
14825
+ } catch {
14826
+ return void 0;
14827
+ }
14828
+ }
14209
14829
  let itemIndex = 0;
14210
14830
  for (const item of items) {
14211
14831
  const pendingEntry = pending.get(item.id);
@@ -14215,25 +14835,22 @@ function createNodeCollection(config) {
14215
14835
  index: itemIndex,
14216
14836
  input: buildCreateInput(kind, item.props, item)
14217
14837
  });
14838
+ pending.set(item.id, {
14839
+ props: repeatedIds.has(item.id) ? runDirtyCheck(item.id, {}, item.props)?.validatedProps : void 0,
14840
+ window: windowAfterUpsertCreate(item),
14841
+ sourceIndex: itemIndex
14842
+ });
14218
14843
  itemIndex++;
14219
14844
  continue;
14220
14845
  }
14221
14846
  const deletedAt = pendingEntry === void 0 ? requireDefined(original).deleted_at : void 0;
14222
- let dirty;
14223
- if (config.upsertDirtyCheck !== void 0) {
14224
- const currentProps = pendingEntry?.props ?? rowPropsToObject(requireDefined(original).props);
14225
- try {
14226
- dirty = config.upsertDirtyCheck(
14227
- kind,
14228
- item.id,
14229
- currentProps,
14230
- item.props
14231
- );
14232
- } catch {
14233
- dirty = void 0;
14234
- }
14235
- }
14236
- const coalesce = dirty?.unchanged === true && deletedAt === void 0 && item.validFrom === void 0 && item.validTo === void 0;
14847
+ const currentProps = pendingEntry === void 0 ? rowPropsToObject(requireDefined(original).props) : pendingEntry.props;
14848
+ const dirty = currentProps === void 0 ? void 0 : runDirtyCheck(item.id, currentProps, item.props);
14849
+ const currentWindow = pendingEntry === void 0 ? {
14850
+ valid_from: requireDefined(original).valid_from,
14851
+ valid_to: requireDefined(original).valid_to
14852
+ } : pendingEntry.window;
14853
+ const coalesce = dirty?.unchanged === true && deletedAt === void 0 && !upsertWindowChanges(item, currentWindow);
14237
14854
  if (coalesce) {
14238
14855
  if (pendingEntry === void 0) {
14239
14856
  results2[itemIndex] = narrowNode(
@@ -14248,15 +14865,17 @@ function createNodeCollection(config) {
14248
14865
  } else {
14249
14866
  toUpdate.push({
14250
14867
  index: itemIndex,
14251
- input: buildUpdateInput(kind, item.id, item.props, item),
14868
+ input: buildUpsertUpdateInput(kind, item.id, item.props, item),
14252
14869
  clearDeleted: deletedAt !== void 0
14253
14870
  });
14254
- if (dirty !== void 0) {
14255
- pending.set(item.id, {
14256
- props: dirty.validatedProps,
14257
- sourceIndex: itemIndex
14258
- });
14259
- }
14871
+ pending.set(item.id, {
14872
+ props: dirty?.validatedProps,
14873
+ // A resurrecting update rewrites BOTH bounds (buildUpdateNode's
14874
+ // clearDeleted leg stamps `valid_from` and reopens `valid_to`);
14875
+ // a live-row update moves only `valid_to`.
14876
+ window: deletedAt === void 0 ? windowAfterUpsertUpdate(currentWindow, item) : windowAfterUpsertCreate(item),
14877
+ sourceIndex: itemIndex
14878
+ });
14260
14879
  }
14261
14880
  itemIndex++;
14262
14881
  }
@@ -14280,7 +14899,10 @@ function createNodeCollection(config) {
14280
14899
  }
14281
14900
  return { results: results2, mutations: toCreate.length + toUpdate.length };
14282
14901
  };
14283
- const { results, mutations } = backend.capabilities.transactions && "transaction" in backend ? await backend.transaction(async (txBackend) => upsertAll(txBackend)) : await upsertAll(backend);
14902
+ const { results, mutations } = await runOptionallyInTransaction(
14903
+ backend,
14904
+ (target) => upsertAll(target)
14905
+ );
14284
14906
  await config.maybeRefreshStatisticsAfterBulk?.(mutations);
14285
14907
  return results;
14286
14908
  },
@@ -14298,16 +14920,7 @@ function createNodeCollection(config) {
14298
14920
  },
14299
14921
  async bulkDelete(ids) {
14300
14922
  if (ids.length === 0) return;
14301
- const deleteAll = async (target) => {
14302
- for (const id of ids) {
14303
- await executeNodeDelete2(kind, id, target);
14304
- }
14305
- };
14306
- if (backend.capabilities.transactions && "transaction" in backend) {
14307
- await backend.transaction(async (txBackend) => deleteAll(txBackend));
14308
- return;
14309
- }
14310
- await deleteAll(backend);
14923
+ await executeNodeDeleteBatch2(kind, ids, backend);
14311
14924
  },
14312
14925
  async findByConstraint(constraintName, props) {
14313
14926
  const result = await executeFindByConstraint(
@@ -14505,6 +15118,7 @@ var HISTORY_STORE_BACKEND_KEYS = [
14505
15118
  "insertNodesBatchReturning",
14506
15119
  "insertUnique",
14507
15120
  "insertUniqueBatch",
15121
+ "probeContributions",
14508
15122
  "recordContributionMaterialization",
14509
15123
  "recordIndexMaterialization",
14510
15124
  "recordKindRemoval",
@@ -15415,7 +16029,7 @@ async function reconcilePendingRemovals(context) {
15415
16029
  const priorEntries = priorSchema[entity === "node" ? "nodes" : "edges"];
15416
16030
  const currentEntries = currentSchema[entity === "node" ? "nodes" : "edges"];
15417
16031
  for (const kindName of Object.keys(priorEntries)) {
15418
- if (kindName in currentEntries) continue;
16032
+ if (hasOwnKey(currentEntries, kindName)) continue;
15419
16033
  if (recorded.has(kindRemovalKey(entity, kindName, currentRow.version))) {
15420
16034
  continue;
15421
16035
  }
@@ -15736,22 +16350,29 @@ function literal(value) {
15736
16350
 
15737
16351
  // src/store/operations/edge-operations.ts
15738
16352
  function getEdgeRegistration(graph, kind) {
16353
+ if (!hasOwnKey(graph.edges, kind)) throw new KindNotFoundError(kind, "edge");
15739
16354
  const registration = graph.edges[kind];
15740
16355
  if (registration === void 0) throw new KindNotFoundError(kind, "edge");
15741
16356
  return registration;
15742
16357
  }
15743
16358
  function buildEdgeEndpointCacheKey(graphId, kind, id) {
15744
- return `${graphId}\0${kind}\0${id}`;
16359
+ return encodeTupleKey([graphId, kind, id]);
15745
16360
  }
15746
16361
  function buildEdgeFromCacheKey(graphId, edgeKind, fromKind, fromId) {
15747
- return `${graphId}\0${edgeKind}\0${fromKind}\0${fromId}`;
16362
+ return encodeTupleKey([graphId, edgeKind, fromKind, fromId]);
15748
16363
  }
15749
16364
  function buildEdgeBetweenCacheKey(graphId, edgeKind, fromKind, fromId, toKind, toId) {
15750
- return `${graphId}\0${edgeKind}\0${fromKind}\0${fromId}\0${toKind}\0${toId}`;
16365
+ return encodeTupleKey([graphId, edgeKind, fromKind, fromId, toKind, toId]);
15751
16366
  }
15752
16367
  function buildCountEdgesFromCacheKey(params) {
15753
16368
  const activeOnly = params.activeOnly === true ? "1" : "0";
15754
- return `${params.graphId}\0${params.edgeKind}\0${params.fromKind}\0${params.fromId}\0${activeOnly}`;
16369
+ return encodeTupleKey([
16370
+ params.graphId,
16371
+ params.edgeKind,
16372
+ params.fromKind,
16373
+ params.fromId,
16374
+ activeOnly
16375
+ ]);
15755
16376
  }
15756
16377
  function buildInsertEdgeParams(graphId, id, kind, fromKind, fromId, toKind, toId, props, validFrom, validTo) {
15757
16378
  const insertParams = {
@@ -15909,6 +16530,7 @@ async function validateAndPrepareEdgeCreate(ctx, input, id, backend) {
15909
16530
  "validFrom"
15910
16531
  );
15911
16532
  const validTo = validateOptionalCanonicalIsoDate(input.validTo, "validTo");
16533
+ assertOrderedValidityWindow(`edge "${id}"`, validFrom, validTo);
15912
16534
  const cardinality = registration.cardinality ?? "many";
15913
16535
  const constraintContext = {
15914
16536
  graphId: ctx.graphId,
@@ -15941,32 +16563,87 @@ async function validateAndPrepareEdgeCreate(ctx, input, id, backend) {
15941
16563
  )
15942
16564
  };
15943
16565
  }
16566
+ function edgeCardinality(ctx, kind) {
16567
+ if (!hasOwnKey(ctx.graph.edges, kind)) return "many";
16568
+ return getEdgeRegistration(ctx.graph, kind).cardinality ?? "many";
16569
+ }
16570
+ var EdgeConvergenceRaced = class extends Error {
16571
+ constructor(kind) {
16572
+ super(
16573
+ `A competing writer claimed the ${kind} match key; re-resolving it. This is internal to getOrCreateByEndpoints and is never returned to a caller.`
16574
+ );
16575
+ this.name = "EdgeConvergenceRaced";
16576
+ }
16577
+ };
15944
16578
  async function executeEdgeCreateInternal(ctx, input, backend, options) {
15945
16579
  const kind = input.kind;
15946
16580
  const id = input.id ?? generateId();
15947
16581
  const opContext = ctx.createOperationContext("create", "edge", kind, id);
15948
- return runHookedWriteOperation(ctx, opContext, backend, async (target) => {
15949
- const prepared = await validateAndPrepareEdgeCreate(ctx, input, id, target);
15950
- let row;
15951
- {
15952
- row = await target.insertEdge(prepared.insertParams);
15953
- }
15954
- if (row === void 0) return;
15955
- return rowToEdge(row);
15956
- });
15957
- }
15958
- async function executeEdgeCreate(ctx, input, backend) {
15959
- const result = await executeEdgeCreateInternal(ctx, input, backend);
15960
- if (!result) {
15961
- throw new DatabaseOperationError(
15962
- "Edge create failed: expected created edge row",
15963
- { operation: "insert", entity: "edge" }
15964
- );
15965
- }
15966
- return result;
15967
- }
15968
- async function primeEdgeBatchValidationCache(ctx, inputs, backend, seedEndpointRow) {
15969
- if (backend.getNodes === void 0) return;
16582
+ const shouldReturnRow = options?.returnRow ?? true;
16583
+ const convergeOn = options?.convergeOn;
16584
+ return runHookedWriteOperation(
16585
+ ctx,
16586
+ opContext,
16587
+ backend,
16588
+ async (target) => {
16589
+ if (convergeOn !== void 0) {
16590
+ const candidateRows = await target.findEdgesByKind({
16591
+ graphId: ctx.graphId,
16592
+ kind,
16593
+ fromKind: input.fromKind,
16594
+ fromId: input.fromId,
16595
+ toKind: input.toKind,
16596
+ toId: input.toId,
16597
+ excludeDeleted: false,
16598
+ temporalMode: "includeTombstones"
16599
+ });
16600
+ const { liveRow, deletedRow } = findMatchingEdge(
16601
+ candidateRows,
16602
+ convergeOn.matchOn,
16603
+ convergeOn.props
16604
+ );
16605
+ if (liveRow !== void 0 || deletedRow !== void 0) {
16606
+ throw new EdgeConvergenceRaced(kind);
16607
+ }
16608
+ }
16609
+ const prepared = await validateAndPrepareEdgeCreate(
16610
+ ctx,
16611
+ input,
16612
+ id,
16613
+ target
16614
+ );
16615
+ const row = await withAlreadyExistsTranslation("edge", async () => {
16616
+ if (shouldReturnRow) return target.insertEdge(prepared.insertParams);
16617
+ await runInsertNoReturn(
16618
+ edgeInsertDispatch(target),
16619
+ prepared.insertParams
16620
+ );
16621
+ return;
16622
+ });
16623
+ return row === void 0 ? void 0 : rowToEdge(row);
16624
+ },
16625
+ {
16626
+ // A create that runs a cardinality probe, or one converging on a match
16627
+ // key, decides something the write must not have invalidated. A plain
16628
+ // `many` create decides nothing and takes no lock.
16629
+ fencesConstraintProbe: convergeOn === void 0 ? edgeWriteNeedsConstraintFence(edgeCardinality(ctx, kind)) : "edgeMatchKeyConvergence"
16630
+ }
16631
+ );
16632
+ }
16633
+ async function executeEdgeCreate(ctx, input, backend) {
16634
+ const edge = await executeEdgeCreateInternal(ctx, input, backend, {
16635
+ returnRow: true
16636
+ });
16637
+ if (!edge) {
16638
+ throw new DatabaseOperationError(
16639
+ "Edge create failed: expected created edge row",
16640
+ { operation: "insert", entity: "edge" }
16641
+ );
16642
+ }
16643
+ return edge;
16644
+ }
16645
+ async function primeEdgeBatchValidationCache(ctx, inputs, backend, seedEndpointRow) {
16646
+ if (backend.getNodes === void 0) return;
15970
16647
  const idsByKind = /* @__PURE__ */ new Map();
15971
16648
  const addEndpoint = (kind, id) => {
15972
16649
  const ids = idsByKind.get(kind) ?? /* @__PURE__ */ new Set();
@@ -16017,31 +16694,53 @@ async function executeEdgeCreateNoReturnBatch(ctx, inputs, backend) {
16017
16694
  if (inputs.length === 0) {
16018
16695
  return;
16019
16696
  }
16020
- await runInWriteTransaction(ctx, backend, async (target) => {
16021
- const { batchInsertParams } = await prepareEdgeBatchCreates(
16022
- ctx,
16023
- inputs,
16024
- target
16025
- );
16026
- await runInsertBatch(edgeInsertDispatch(target), batchInsertParams);
16027
- });
16697
+ await runInWriteTransaction(
16698
+ ctx,
16699
+ backend,
16700
+ async (target) => {
16701
+ const { batchInsertParams } = await prepareEdgeBatchCreates(
16702
+ ctx,
16703
+ inputs,
16704
+ target
16705
+ );
16706
+ await withAlreadyExistsTranslation(
16707
+ "edge",
16708
+ () => runInsertBatch(edgeInsertDispatch(target), batchInsertParams)
16709
+ );
16710
+ },
16711
+ { fencesConstraintProbe: batchFencesConstraintProbe(ctx, inputs) }
16712
+ );
16028
16713
  }
16029
16714
  async function executeEdgeCreateBatch(ctx, inputs, backend) {
16030
16715
  if (inputs.length === 0) {
16031
16716
  return [];
16032
16717
  }
16033
- return runInWriteTransaction(ctx, backend, async (target) => {
16034
- const { batchInsertParams } = await prepareEdgeBatchCreates(
16035
- ctx,
16036
- inputs,
16037
- target
16038
- );
16039
- const rows = await runInsertBatchReturning(
16040
- edgeInsertDispatch(target),
16041
- batchInsertParams
16718
+ return runInWriteTransaction(
16719
+ ctx,
16720
+ backend,
16721
+ async (target) => {
16722
+ const { batchInsertParams } = await prepareEdgeBatchCreates(
16723
+ ctx,
16724
+ inputs,
16725
+ target
16726
+ );
16727
+ const rows = await withAlreadyExistsTranslation(
16728
+ "edge",
16729
+ () => runInsertBatchReturning(edgeInsertDispatch(target), batchInsertParams)
16730
+ );
16731
+ return rows.map((row) => rowToEdge(row));
16732
+ },
16733
+ { fencesConstraintProbe: batchFencesConstraintProbe(ctx, inputs) }
16734
+ );
16735
+ }
16736
+ function batchFencesConstraintProbe(ctx, inputs) {
16737
+ for (const input of inputs) {
16738
+ const reason = edgeWriteNeedsConstraintFence(
16739
+ edgeCardinality(ctx, input.kind)
16042
16740
  );
16043
- return rows.map((row) => rowToEdge(row));
16044
- });
16741
+ if (reason !== void 0) return reason;
16742
+ }
16743
+ return void 0;
16045
16744
  }
16046
16745
  function computeEdgeUpdate(ctx, kind, id, existingProps, inputProps) {
16047
16746
  const registration = getEdgeRegistration(ctx.graph, kind);
@@ -16079,65 +16778,234 @@ async function performEdgeUpdate(ctx, input, target, options) {
16079
16778
  const id = input.id;
16080
16779
  const existing = await target.getEdge(ctx.graphId, id);
16081
16780
  if (!existing || !options?.clearDeleted && existing.deleted_at) {
16082
- throw new EdgeNotFoundError("unknown", id);
16781
+ throw new EdgeNotFoundError(input.identity.kind, id);
16083
16782
  }
16783
+ assertEdgeIdentityMatches(
16784
+ id,
16785
+ input.identity,
16786
+ edgeIdentityFromRow(existing),
16787
+ "update"
16788
+ );
16084
16789
  const { validatedProps } = resolveEdgeUpdateProps(ctx, existing, input.props);
16790
+ const validFrom = validateOptionalCanonicalIsoDate(
16791
+ input.validFrom,
16792
+ "validFrom"
16793
+ );
16085
16794
  const validTo = validateOptionalCanonicalIsoDate(input.validTo, "validTo");
16795
+ const windowVerdict = assertWritableValidityWindow(
16796
+ `edge "${id}"`,
16797
+ validFrom,
16798
+ {
16799
+ effectiveValidFrom: existing.valid_from,
16800
+ appliesStatedValidFrom: options?.clearDeleted === true,
16801
+ // Unlike the node side, an edge RETAINS `valid_from` through a
16802
+ // resurrection that does not name a new one, so the effective bound is the
16803
+ // row's stored one on BOTH legs and there is no carve-out to make.
16804
+ effectiveBoundIsStored: true
16805
+ },
16806
+ validTo
16807
+ );
16086
16808
  const updateParams = {
16087
16809
  graphId: ctx.graphId,
16088
16810
  id,
16089
- props: validatedProps
16811
+ kind: input.identity.kind,
16812
+ props: validatedProps,
16813
+ // The bound the window verdict above READ, carried into the same `WHERE` as
16814
+ // the identity components and on exactly the same terms: present only when
16815
+ // the verdict consulted it, because a component the caller made no claim
16816
+ // about must not become a predicate that refuses legitimate writes.
16817
+ ...windowVerdict.storedLowerBoundFence
16090
16818
  };
16819
+ if (input.identity.fromKind !== void 0) {
16820
+ updateParams.fromKind = input.identity.fromKind;
16821
+ }
16822
+ if (input.identity.fromId !== void 0) {
16823
+ updateParams.fromId = input.identity.fromId;
16824
+ }
16825
+ if (input.identity.toKind !== void 0) {
16826
+ updateParams.toKind = input.identity.toKind;
16827
+ }
16828
+ if (input.identity.toId !== void 0) {
16829
+ updateParams.toId = input.identity.toId;
16830
+ }
16831
+ if (validFrom !== void 0) updateParams.validFrom = validFrom;
16091
16832
  if (validTo !== void 0) updateParams.validTo = validTo;
16092
16833
  if (options?.clearDeleted) updateParams.clearDeleted = true;
16093
- const row = await target.updateEdge(updateParams);
16834
+ const row = await withUnmatchedEdgeUpdateRefusal(
16835
+ ctx,
16836
+ target,
16837
+ id,
16838
+ input.identity,
16839
+ updateParams.expectedValidFrom !== void 0,
16840
+ () => target.updateEdge(updateParams)
16841
+ );
16094
16842
  return rowToEdge(row);
16095
16843
  }
16844
+ function isEdgeUpdateNoRowError(error) {
16845
+ return error instanceof DatabaseOperationError && error.details.operation === "update" && error.details.entity === "edge" && error.details.reason === "no_row_returned";
16846
+ }
16847
+ async function withUnmatchedEdgeUpdateRefusal(ctx, target, id, expected, assertedValidFrom, write) {
16848
+ try {
16849
+ return await write();
16850
+ } catch (error) {
16851
+ if (!isEdgeUpdateNoRowError(error)) throw error;
16852
+ const current = await target.getEdge(ctx.graphId, id);
16853
+ if (current !== void 0) {
16854
+ assertEdgeIdentityMatches(
16855
+ id,
16856
+ expected,
16857
+ edgeIdentityFromRow(current),
16858
+ "update"
16859
+ );
16860
+ if (assertedValidFrom && current.deleted_at === void 0) {
16861
+ throw new EdgeUpdateTargetMoved(expected.kind, id);
16862
+ }
16863
+ }
16864
+ throw new EdgeNotFoundError(expected.kind, id);
16865
+ }
16866
+ }
16867
+ var EdgeUpdateTargetMoved = class extends Error {
16868
+ constructor(kind, id) {
16869
+ super(
16870
+ `The ${kind} edge "${id}" was replaced between this update's read and its write; re-resolving it. This is internal and is never returned to a caller.`
16871
+ );
16872
+ this.name = "EdgeUpdateTargetMoved";
16873
+ }
16874
+ };
16875
+ var EDGE_UPDATE_ATTEMPTS = 2;
16876
+ async function performEdgeUpdateConverging(ctx, input, target, options) {
16877
+ for (let attempt = 1; attempt <= EDGE_UPDATE_ATTEMPTS; attempt += 1) {
16878
+ try {
16879
+ return await performEdgeUpdate(ctx, input, target, options);
16880
+ } catch (error) {
16881
+ if (!(error instanceof EdgeUpdateTargetMoved)) throw error;
16882
+ if (attempt === EDGE_UPDATE_ATTEMPTS) {
16883
+ throw new DatabaseOperationError(
16884
+ `Edge update for "${input.id}" could not be applied to a stable row after ${EDGE_UPDATE_ATTEMPTS} attempts: the row was replaced between each read and its write. A concurrent writer is replacing this edge faster than it can be read; serialize the writers, or retry.`,
16885
+ { operation: "update", entity: "edge" },
16886
+ { cause: error }
16887
+ );
16888
+ }
16889
+ }
16890
+ }
16891
+ throw new EdgeNotFoundError(input.identity.kind, input.id);
16892
+ }
16096
16893
  async function executeEdgeUpdate(ctx, input, backend) {
16097
16894
  const id = input.id;
16098
16895
  const gate = await backend.getEdge(ctx.graphId, id);
16099
16896
  if (!gate || gate.deleted_at) {
16100
- throw new EdgeNotFoundError("unknown", id);
16897
+ throw new EdgeNotFoundError(input.identity.kind, id);
16101
16898
  }
16899
+ assertEdgeIdentityMatches(
16900
+ id,
16901
+ input.identity,
16902
+ edgeIdentityFromRow(gate),
16903
+ "update"
16904
+ );
16102
16905
  const opContext = ctx.createOperationContext("update", "edge", gate.kind, id);
16103
16906
  return runHookedWriteOperation(
16104
16907
  ctx,
16105
16908
  opContext,
16106
16909
  backend,
16107
- (target) => performEdgeUpdate(ctx, input, target)
16910
+ (target) => performEdgeUpdateConverging(ctx, input, target)
16108
16911
  );
16109
16912
  }
16110
16913
  async function executeEdgeUpsertUpdate(ctx, input, backend, options) {
16914
+ const resurrectCardinality = options?.resurrectCardinality;
16111
16915
  return runInWriteTransaction(
16112
16916
  ctx,
16113
16917
  backend,
16114
- (target) => performEdgeUpdate(ctx, input, target, options)
16918
+ async (target) => {
16919
+ if (resurrectCardinality !== void 0) {
16920
+ await checkCardinalityConstraint(
16921
+ {
16922
+ graphId: ctx.graphId,
16923
+ registry: ctx.registry,
16924
+ backend: target
16925
+ },
16926
+ input.identity.kind,
16927
+ resurrectCardinality.cardinality,
16928
+ resurrectCardinality.fromKind,
16929
+ resurrectCardinality.fromId,
16930
+ resurrectCardinality.toKind,
16931
+ resurrectCardinality.toId,
16932
+ resurrectCardinality.effectiveValidTo
16933
+ );
16934
+ }
16935
+ return performEdgeUpdateConverging(ctx, input, target, options);
16936
+ },
16937
+ {
16938
+ // An in-place props update re-derives no constraint verdict: endpoints
16939
+ // are immutable, so cardinality cannot change under it.
16940
+ fencesConstraintProbe: resurrectCardinality === void 0 ? void 0 : edgeWriteNeedsConstraintFence(resurrectCardinality.cardinality)
16941
+ }
16115
16942
  );
16116
16943
  }
16117
- async function executeEdgeDelete(ctx, id, backend) {
16944
+ async function executeEdgeDelete(ctx, expectedKind, id, backend) {
16118
16945
  const gate = await backend.getEdge(ctx.graphId, id);
16119
- if (!gate || gate.deleted_at) return;
16946
+ if (!gate) return;
16947
+ assertEdgeIdentityMatches(
16948
+ id,
16949
+ { kind: expectedKind },
16950
+ edgeIdentityFromRow(gate),
16951
+ "delete"
16952
+ );
16953
+ if (gate.deleted_at) return;
16120
16954
  const opContext = ctx.createOperationContext("delete", "edge", gate.kind, id);
16121
16955
  return runHookedWriteOperation(ctx, opContext, backend, async (target) => {
16122
16956
  await target.deleteEdge({
16123
16957
  graphId: ctx.graphId,
16124
- id
16958
+ id,
16959
+ kind: expectedKind
16125
16960
  });
16126
16961
  });
16127
16962
  }
16128
- async function executeEdgeHardDelete(ctx, id, backend) {
16963
+ async function executeEdgeDeleteBatch(ctx, expectedKind, ids, backend) {
16964
+ await runInWriteTransaction(
16965
+ ctx,
16966
+ backend,
16967
+ async (target) => {
16968
+ let affectedCount = 0;
16969
+ for (const id of ids) {
16970
+ const current = await target.getEdge(ctx.graphId, id);
16971
+ if (!current) continue;
16972
+ assertEdgeIdentityMatches(
16973
+ id,
16974
+ { kind: expectedKind },
16975
+ edgeIdentityFromRow(current),
16976
+ "delete"
16977
+ );
16978
+ if (current.deleted_at) continue;
16979
+ await target.deleteEdge({
16980
+ graphId: ctx.graphId,
16981
+ id,
16982
+ kind: expectedKind
16983
+ });
16984
+ affectedCount += 1;
16985
+ }
16986
+ return affectedCount;
16987
+ },
16988
+ { didWrite: (affectedCount) => affectedCount > 0 }
16989
+ );
16990
+ }
16991
+ async function executeEdgeHardDelete(ctx, expectedKind, id, backend) {
16129
16992
  const gate = await backend.getEdge(ctx.graphId, id);
16130
16993
  if (!gate) return;
16994
+ assertEdgeIdentityMatches(
16995
+ id,
16996
+ { kind: expectedKind },
16997
+ edgeIdentityFromRow(gate),
16998
+ "hardDelete"
16999
+ );
16131
17000
  const opContext = ctx.createOperationContext("delete", "edge", gate.kind, id);
16132
17001
  return runHookedWriteOperation(ctx, opContext, backend, async (target) => {
16133
17002
  await target.hardDeleteEdge({
16134
17003
  graphId: ctx.graphId,
16135
- id
17004
+ id,
17005
+ kind: expectedKind
16136
17006
  });
16137
17007
  });
16138
17008
  }
16139
- var RECORD_SEP = "";
16140
- var UNIT_SEP = "";
16141
17009
  var UNDEFINED_SENTINEL = "";
16142
17010
  function validateMatchOnFields(schema, matchOn, edgeKind) {
16143
17011
  if (matchOn.length === 0) return;
@@ -16155,7 +17023,7 @@ function validateMatchOnFields(schema, matchOn, edgeKind) {
16155
17023
  }
16156
17024
  );
16157
17025
  }
16158
- const invalidFields = matchOn.filter((field2) => !(field2 in shape));
17026
+ const invalidFields = matchOn.filter((field2) => !hasOwnKey(shape, field2));
16159
17027
  if (invalidFields.length > 0) {
16160
17028
  throw new ValidationError(
16161
17029
  `Invalid matchOn fields for edge kind "${edgeKind}": ${invalidFields.join(", ")}`,
@@ -16183,16 +17051,20 @@ function stableStringify(value) {
16183
17051
  return `{${entries.join(",")}}`;
16184
17052
  }
16185
17053
  function buildEdgeCompositeKey(fromKind, fromId, toKind, toId, props, matchOn) {
16186
- const endpointPart = `${fromKind}${RECORD_SEP}${fromId}${RECORD_SEP}${toKind}${RECORD_SEP}${toId}`;
16187
- if (matchOn.length === 0) return endpointPart;
16188
17054
  const sortedFields = [...matchOn].toSorted();
16189
- const propertyParts = sortedFields.map(
16190
- (field2) => `${RECORD_SEP}${field2}${UNIT_SEP}${stableStringify(props[field2])}`
16191
- );
16192
- return endpointPart + propertyParts.join("");
17055
+ return encodeTupleKey([
17056
+ fromKind,
17057
+ fromId,
17058
+ toKind,
17059
+ toId,
17060
+ ...sortedFields.flatMap((field2) => [
17061
+ field2,
17062
+ stableStringify(readOwnProperty(props, field2))
17063
+ ])
17064
+ ]);
16193
17065
  }
16194
17066
  function buildEndpointPairKey(fromKind, fromId, toKind, toId) {
16195
- return `${fromKind}${RECORD_SEP}${fromId}${RECORD_SEP}${toKind}${RECORD_SEP}${toId}`;
17067
+ return encodeTupleKey([fromKind, fromId, toKind, toId]);
16196
17068
  }
16197
17069
  function findMatchingEdge(rows, matchOn, inputProps) {
16198
17070
  let liveRow;
@@ -16201,7 +17073,7 @@ function findMatchingEdge(rows, matchOn, inputProps) {
16201
17073
  if (matchOn.length > 0) {
16202
17074
  const rowProps = rowPropsToObject(row.props);
16203
17075
  const matches = matchOn.every(
16204
- (field2) => stableStringify(rowProps[field2]) === stableStringify(inputProps[field2])
17076
+ (field2) => stableStringify(readOwnProperty(rowProps, field2)) === stableStringify(readOwnProperty(inputProps, field2))
16205
17077
  );
16206
17078
  if (!matches) continue;
16207
17079
  }
@@ -16255,6 +17127,11 @@ async function executeEdgeGetOrCreateByEndpoints(ctx, kind, fromKind, fromId, to
16255
17127
  "validFrom"
16256
17128
  );
16257
17129
  const validTo = validateOptionalCanonicalIsoDate(options?.validTo, "validTo");
17130
+ assertOrderedValidityWindow(
17131
+ `${kind} edge between ${fromKind} "${fromId}" and ${toKind} "${toId}"`,
17132
+ validFrom,
17133
+ validTo
17134
+ );
16258
17135
  if (ifExists === "return") {
16259
17136
  const probeRows = await backend.findEdgesByKind({
16260
17137
  graphId: ctx.graphId,
@@ -16302,8 +17179,17 @@ async function executeEdgeGetOrCreateByEndpoints(ctx, kind, fromKind, fromId, to
16302
17179
  ...validFrom !== void 0 && { validFrom },
16303
17180
  ...validTo !== void 0 && { validTo }
16304
17181
  };
16305
- const edge2 = await executeEdgeCreate(ctx, input, backend);
16306
- return { edge: edge2, action: "created" };
17182
+ const created = await executeEdgeCreateInternal(ctx, input, backend, {
17183
+ returnRow: true,
17184
+ convergeOn: { matchOn, props: validatedProps }
17185
+ });
17186
+ if (created === void 0) {
17187
+ throw new DatabaseOperationError(
17188
+ "Edge create failed: expected created edge row",
17189
+ { operation: "insert", entity: "edge" }
17190
+ );
17191
+ }
17192
+ return { edge: created, action: "created" };
16307
17193
  }
16308
17194
  if (liveRow !== void 0) {
16309
17195
  if (ifExists === "return") {
@@ -16313,7 +17199,9 @@ async function executeEdgeGetOrCreateByEndpoints(ctx, kind, fromKind, fromId, to
16313
17199
  ctx,
16314
17200
  {
16315
17201
  id: liveRow.id,
17202
+ identity: { kind, fromKind, fromId, toKind, toId },
16316
17203
  props: validatedProps,
17204
+ ...validFrom !== void 0 && { validFrom },
16317
17205
  ...validTo !== void 0 && { validTo }
16318
17206
  },
16319
17207
  backend
@@ -16326,39 +17214,50 @@ async function executeEdgeGetOrCreateByEndpoints(ctx, kind, fromKind, fromId, to
16326
17214
  }
16327
17215
  const matchedDeletedRow = deletedRow;
16328
17216
  const effectiveValidTo = validTo ?? matchedDeletedRow.valid_to;
16329
- const constraintContext = {
16330
- graphId: ctx.graphId,
16331
- registry: ctx.registry,
16332
- backend
16333
- };
16334
- await checkCardinalityConstraint(
16335
- constraintContext,
16336
- kind,
16337
- cardinality,
16338
- fromKind,
16339
- fromId,
16340
- toKind,
16341
- toId,
16342
- effectiveValidTo
16343
- );
16344
17217
  const edge = await executeEdgeUpsertUpdate(
16345
17218
  ctx,
16346
17219
  {
16347
17220
  id: matchedDeletedRow.id,
17221
+ identity: { kind, fromKind, fromId, toKind, toId },
16348
17222
  props: validatedProps,
17223
+ ...validFrom !== void 0 && { validFrom },
16349
17224
  ...validTo !== void 0 && { validTo }
16350
17225
  },
16351
17226
  backend,
16352
- { clearDeleted: true }
17227
+ {
17228
+ clearDeleted: true,
17229
+ resurrectCardinality: {
17230
+ cardinality,
17231
+ fromKind,
17232
+ fromId,
17233
+ toKind,
17234
+ toId,
17235
+ effectiveValidTo
17236
+ }
17237
+ }
16353
17238
  );
16354
17239
  return { edge, action: "resurrected" };
16355
17240
  }
16356
- try {
16357
- return await attempt();
16358
- } catch (error) {
16359
- if (!(error instanceof CardinalityError)) throw error;
16360
- return attempt();
17241
+ const ATTEMPT_LIMIT = 3;
17242
+ for (let remaining = ATTEMPT_LIMIT; remaining > 0; remaining -= 1) {
17243
+ try {
17244
+ return await attempt();
17245
+ } catch (error) {
17246
+ const retryable = error instanceof CardinalityError || error instanceof EdgeConvergenceRaced;
17247
+ if (!retryable || remaining === 1) {
17248
+ if (!(error instanceof EdgeConvergenceRaced)) throw error;
17249
+ throw new DatabaseOperationError(
17250
+ `getOrCreateByEndpoints for ${kind} between ${fromKind} "${fromId}" and ${toKind} "${toId}" lost its match key to a competing writer ${String(ATTEMPT_LIMIT)} times without converging. A concurrent writer is repeatedly creating and removing this edge; serialize those callers or retry the operation.`,
17251
+ { operation: "insert", entity: "edge" },
17252
+ { cause: error }
17253
+ );
17254
+ }
17255
+ }
16361
17256
  }
17257
+ throw new DatabaseOperationError(
17258
+ "getOrCreateByEndpoints convergence loop exited without a verdict",
17259
+ { operation: "insert", entity: "edge" }
17260
+ );
16362
17261
  }
16363
17262
  async function executeEdgeBulkGetOrCreateByEndpoints(ctx, kind, items, backend, options) {
16364
17263
  if (items.length === 0) return [];
@@ -16379,6 +17278,11 @@ async function executeEdgeBulkGetOrCreateByEndpoints(ctx, kind, items, backend,
16379
17278
  "validFrom"
16380
17279
  );
16381
17280
  const validTo = validateOptionalCanonicalIsoDate(item.validTo, "validTo");
17281
+ assertOrderedValidityWindow(
17282
+ `${kind} edge between ${item.fromKind} "${item.fromId}" and ${item.toKind} "${item.toId}"`,
17283
+ validFrom,
17284
+ validTo
17285
+ );
16382
17286
  const compositeKey = buildEdgeCompositeKey(
16383
17287
  item.fromKind,
16384
17288
  item.fromId,
@@ -16481,6 +17385,9 @@ async function executeEdgeBulkGetOrCreateByEndpoints(ctx, kind, items, backend,
16481
17385
  fromId: entry2.fromId,
16482
17386
  toKind: entry2.toKind,
16483
17387
  toId: entry2.toId,
17388
+ ...entry2.validFrom !== void 0 && {
17389
+ validFrom: entry2.validFrom
17390
+ },
16484
17391
  ...entry2.validTo !== void 0 && { validTo: entry2.validTo }
16485
17392
  });
16486
17393
  }
@@ -16504,75 +17411,108 @@ async function executeEdgeBulkGetOrCreateByEndpoints(ctx, kind, items, backend,
16504
17411
  return found;
16505
17412
  }
16506
17413
  }
16507
- return runInWriteTransaction(ctx, backend, async (target) => {
16508
- const { toCreate, toFetch, duplicateOf } = partitionEntries(
16509
- await fetchRowsByEndpoint(target)
16510
- );
16511
- const results = Array.from({ length: items.length });
16512
- if (toCreate.length > 0) {
16513
- const createInputs = toCreate.map((entry2) => entry2.input);
16514
- const createdEdges = await executeEdgeCreateBatch(
16515
- ctx,
16516
- createInputs,
16517
- target
16518
- );
16519
- for (const [batchIndex, entry2] of toCreate.entries()) {
16520
- results[entry2.index] = {
16521
- edge: requireDefined(createdEdges[batchIndex]),
16522
- action: "created"
16523
- };
16524
- }
16525
- }
16526
- for (const entry2 of toFetch) {
16527
- if (entry2.isDeleted) {
16528
- const effectiveValidTo = entry2.validTo ?? entry2.row.valid_to;
16529
- const constraintContext = {
16530
- graphId: ctx.graphId,
16531
- registry: ctx.registry,
16532
- backend: target
16533
- };
16534
- await checkCardinalityConstraint(
16535
- constraintContext,
16536
- kind,
16537
- cardinality,
16538
- entry2.fromKind,
16539
- entry2.fromId,
16540
- entry2.toKind,
16541
- entry2.toId,
16542
- effectiveValidTo
16543
- );
16544
- const edge = await executeEdgeUpsertUpdate(
16545
- ctx,
16546
- {
16547
- id: entry2.row.id,
16548
- props: entry2.validatedProps,
16549
- ...entry2.validTo !== void 0 && { validTo: entry2.validTo }
16550
- },
16551
- target,
16552
- { clearDeleted: true }
16553
- );
16554
- results[entry2.index] = { edge, action: "resurrected" };
16555
- } else if (ifExists === "update") {
16556
- const edge = await executeEdgeUpsertUpdate(
16557
- ctx,
16558
- {
16559
- id: entry2.row.id,
16560
- props: entry2.validatedProps,
16561
- ...entry2.validTo !== void 0 && { validTo: entry2.validTo }
16562
- },
16563
- target
17414
+ function runBatch() {
17415
+ return runInWriteTransaction(
17416
+ ctx,
17417
+ backend,
17418
+ async (target) => {
17419
+ const { toCreate, toFetch, duplicateOf } = partitionEntries(
17420
+ await fetchRowsByEndpoint(target)
16564
17421
  );
16565
- results[entry2.index] = { edge, action: "updated" };
16566
- } else {
16567
- results[entry2.index] = { edge: rowToEdge(entry2.row), action: "found" };
16568
- }
16569
- }
16570
- for (const { index, sourceIndex } of duplicateOf) {
16571
- const sourceResult = requireDefined(results[sourceIndex]);
16572
- results[index] = { edge: sourceResult.edge, action: "found" };
16573
- }
16574
- return results;
16575
- });
17422
+ const results = Array.from({ length: items.length });
17423
+ if (toCreate.length > 0) {
17424
+ const createInputs = toCreate.map((entry2) => entry2.input);
17425
+ const createdEdges = await executeEdgeCreateBatch(
17426
+ ctx,
17427
+ createInputs,
17428
+ target
17429
+ );
17430
+ for (const [batchIndex, entry2] of toCreate.entries()) {
17431
+ results[entry2.index] = {
17432
+ edge: requireDefined(createdEdges[batchIndex]),
17433
+ action: "created"
17434
+ };
17435
+ }
17436
+ }
17437
+ for (const entry2 of toFetch) {
17438
+ if (entry2.isDeleted) {
17439
+ const edge = await executeEdgeUpsertUpdate(
17440
+ ctx,
17441
+ {
17442
+ id: entry2.row.id,
17443
+ identity: {
17444
+ kind,
17445
+ fromKind: entry2.fromKind,
17446
+ fromId: entry2.fromId,
17447
+ toKind: entry2.toKind,
17448
+ toId: entry2.toId
17449
+ },
17450
+ props: entry2.validatedProps,
17451
+ ...entry2.validFrom !== void 0 && {
17452
+ validFrom: entry2.validFrom
17453
+ },
17454
+ ...entry2.validTo !== void 0 && { validTo: entry2.validTo }
17455
+ },
17456
+ target,
17457
+ {
17458
+ clearDeleted: true,
17459
+ resurrectCardinality: {
17460
+ cardinality,
17461
+ fromKind: entry2.fromKind,
17462
+ fromId: entry2.fromId,
17463
+ toKind: entry2.toKind,
17464
+ toId: entry2.toId,
17465
+ effectiveValidTo: entry2.validTo ?? entry2.row.valid_to
17466
+ }
17467
+ }
17468
+ );
17469
+ results[entry2.index] = { edge, action: "resurrected" };
17470
+ } else if (ifExists === "update") {
17471
+ const edge = await executeEdgeUpsertUpdate(
17472
+ ctx,
17473
+ {
17474
+ id: entry2.row.id,
17475
+ identity: {
17476
+ kind,
17477
+ fromKind: entry2.fromKind,
17478
+ fromId: entry2.fromId,
17479
+ toKind: entry2.toKind,
17480
+ toId: entry2.toId
17481
+ },
17482
+ props: entry2.validatedProps,
17483
+ ...entry2.validFrom !== void 0 && {
17484
+ validFrom: entry2.validFrom
17485
+ },
17486
+ ...entry2.validTo !== void 0 && { validTo: entry2.validTo }
17487
+ },
17488
+ target
17489
+ );
17490
+ results[entry2.index] = { edge, action: "updated" };
17491
+ } else {
17492
+ results[entry2.index] = {
17493
+ edge: rowToEdge(entry2.row),
17494
+ action: "found"
17495
+ };
17496
+ }
17497
+ }
17498
+ for (const { index, sourceIndex } of duplicateOf) {
17499
+ const sourceResult = requireDefined(results[sourceIndex]);
17500
+ results[index] = { edge: sourceResult.edge, action: "found" };
17501
+ }
17502
+ return results;
17503
+ },
17504
+ // A bulk getOrCreate always converges on a match key no database key
17505
+ // backs, whatever its cardinality — the same reason the single-item path
17506
+ // fences unconditionally.
17507
+ { fencesConstraintProbe: "edgeMatchKeyConvergence" }
17508
+ );
17509
+ }
17510
+ try {
17511
+ return await runBatch();
17512
+ } catch (error) {
17513
+ if (!(error instanceof CardinalityError)) throw error;
17514
+ return runBatch();
17515
+ }
16576
17516
  }
16577
17517
 
16578
17518
  // src/store/recorded-read-service.ts
@@ -16906,6 +17846,12 @@ function createTransactionReceiptRecorder() {
16906
17846
  const counters = {
16907
17847
  nodes: createCountBucket(),
16908
17848
  edges: createCountBucket(),
17849
+ identity: {
17850
+ sameAssertions: 0,
17851
+ differentAssertions: 0,
17852
+ retractions: 0,
17853
+ total: 0
17854
+ },
16909
17855
  total: 0
16910
17856
  };
16911
17857
  let sealed = false;
@@ -16918,15 +17864,17 @@ function createTransactionReceiptRecorder() {
16918
17864
  increment(counters.edges, kind, count2);
16919
17865
  counters.total += count2;
16920
17866
  },
17867
+ recordIdentity(kind, count2) {
17868
+ counters.identity[kind] += count2;
17869
+ counters.identity.total += count2;
17870
+ counters.total += count2;
17871
+ },
16921
17872
  snapshot(recorded) {
16922
17873
  return Object.freeze({
16923
17874
  writes: Object.freeze({
16924
- nodes: Object.freeze(
16925
- Object.assign(createCountBucket(), counters.nodes)
16926
- ),
16927
- edges: Object.freeze(
16928
- Object.assign(createCountBucket(), counters.edges)
16929
- ),
17875
+ nodes: Object.freeze({ ...counters.nodes }),
17876
+ edges: Object.freeze({ ...counters.edges }),
17877
+ identity: Object.freeze({ ...counters.identity }),
16930
17878
  total: counters.total
16931
17879
  }),
16932
17880
  ...recorded === void 0 ? {} : { recorded }
@@ -16948,6 +17896,67 @@ function createTransactionReceiptRecorder() {
16948
17896
  }
16949
17897
  };
16950
17898
  }
17899
+ function wrapTransactionIdentity(identity, recorder) {
17900
+ return {
17901
+ representativeOf: (ref) => identity.representativeOf(ref),
17902
+ membersOf: (ref) => identity.membersOf(ref),
17903
+ nodesOf: (ref) => identity.nodesOf(ref),
17904
+ areSame: (a, b) => identity.areSame(a, b),
17905
+ areDifferent: (a, b) => identity.areDifferent(a, b),
17906
+ assertionsOf: (ref) => identity.assertionsOf(ref),
17907
+ async assertSame(a, b) {
17908
+ recorder.assertWritable();
17909
+ const result = await identity.assertSame(a, b);
17910
+ recorder.recordIdentity("sameAssertions", 1);
17911
+ return result;
17912
+ },
17913
+ async assertDifferent(a, b) {
17914
+ recorder.assertWritable();
17915
+ const result = await identity.assertDifferent(a, b);
17916
+ recorder.recordIdentity("differentAssertions", 1);
17917
+ return result;
17918
+ },
17919
+ async bulkAssertSame(pairs) {
17920
+ recorder.assertWritable();
17921
+ const count2 = pairs.length;
17922
+ const result = await identity.bulkAssertSame(pairs);
17923
+ recorder.recordIdentity("sameAssertions", count2);
17924
+ return result;
17925
+ },
17926
+ async bulkAssertDifferent(pairs) {
17927
+ recorder.assertWritable();
17928
+ const count2 = pairs.length;
17929
+ const result = await identity.bulkAssertDifferent(pairs);
17930
+ recorder.recordIdentity("differentAssertions", count2);
17931
+ return result;
17932
+ },
17933
+ async retractAssertion(id) {
17934
+ recorder.assertWritable();
17935
+ const result = await identity.retractAssertion(id);
17936
+ recorder.recordIdentity("retractions", 1);
17937
+ return result;
17938
+ },
17939
+ async retractSameAssertion(a, b) {
17940
+ recorder.assertWritable();
17941
+ const result = await identity.retractSameAssertion(a, b);
17942
+ recorder.recordIdentity("retractions", 1);
17943
+ return result;
17944
+ },
17945
+ async retractDifferentAssertion(a, b) {
17946
+ recorder.assertWritable();
17947
+ const result = await identity.retractDifferentAssertion(a, b);
17948
+ recorder.recordIdentity("retractions", 1);
17949
+ return result;
17950
+ },
17951
+ async bulkRetractAssertions(ids) {
17952
+ recorder.assertWritable();
17953
+ const count2 = ids.length;
17954
+ const result = await identity.bulkRetractAssertions(ids);
17955
+ recorder.recordIdentity("retractions", count2);
17956
+ return result;
17957
+ }
17958
+ };
17959
+ }
16951
17960
  function isWrappedMethod(value) {
16952
17961
  return typeof value === "function";
16953
17962
  }
@@ -17056,6 +18065,10 @@ var CAUGHT_UP_VERB_DETAILS = {
17056
18065
  repair: {
17057
18066
  phrase: "repair contributions for",
17058
18067
  code: "REPAIR_CONTRIBUTIONS_BEFORE_INITIALIZE"
18068
+ },
18069
+ rebuild: {
18070
+ phrase: "rebuild a contribution for",
18071
+ code: "REBUILD_CONTRIBUTION_BEFORE_INITIALIZE"
17059
18072
  }
17060
18073
  };
17061
18074
  var ROW_MAPPER_CONFIGS = {
@@ -17128,6 +18141,20 @@ async function commitEvolvedSchemaWhenRequiredKindsAreEmpty(backend, graph, curr
17128
18141
  }
17129
18142
  );
17130
18143
  }
18144
+ async function assertEvolvedSchemaRequiredKindsEmpty(backend, graphId, classification) {
18145
+ const counts = await Promise.all(
18146
+ classification.requireEmpty.map(async (entry2) => ({
18147
+ entry: entry2,
18148
+ count: entry2.entity === "node" ? await backend.countNodesByKind({ graphId, kind: entry2.kindName }) : await backend.countEdgesByKind({ graphId, kind: entry2.kindName })
18149
+ }))
18150
+ );
18151
+ const nonEmpty = new Set(
18152
+ counts.filter(({ count: count2 }) => count2 > 0).map(({ entry: entry2 }) => entry2)
18153
+ );
18154
+ const error = buildIncompatibleChangeError(classification, nonEmpty, graphId);
18155
+ if (error !== void 0) throw error;
18156
+ }
18157
+ var IDENTITY_FACADES = /* @__PURE__ */ new WeakMap();
17131
18158
  var StoreImplementation = class _StoreImplementation {
17132
18159
  [STORE_RUNTIME];
17133
18160
  #graph;
@@ -17159,6 +18186,19 @@ var StoreImplementation = class _StoreImplementation {
17159
18186
  #search;
17160
18187
  constructor(graph, backend, options, schemaMetadata, adapterBackend) {
17161
18188
  this.#graph = graph;
18189
+ if (graph.identity !== void 0 && (!backend.capabilities.transactions || backend.executeStatement === void 0)) {
18190
+ throw new ConfigurationError(
18191
+ "Operational Identity requires an atomic transactional backend with statement execution support.",
18192
+ {
18193
+ code: "IDENTITY_REQUIRES_ATOMIC_BACKEND",
18194
+ transactions: backend.capabilities.transactions,
18195
+ statementExecution: backend.executeStatement !== void 0
18196
+ },
18197
+ {
18198
+ suggestion: "Use a transactional SQLite or PostgreSQL driver; Cloudflare D1 and neon-http cannot host identity closure maintenance."
18199
+ }
18200
+ );
18201
+ }
17162
18202
  this.#baseBackend = asRawBackend(backend);
17163
18203
  this.#adapterBackend = adapterBackend;
17164
18204
  this.#captureEnabled = options?.history === true;
@@ -17207,7 +18247,56 @@ var StoreImplementation = class _StoreImplementation {
17207
18247
  recordedEdgeGetByIds: (kind, ids, coordinate) => this.recordedEdgeGetByIds(kind, ids, coordinate),
17208
18248
  recordedEdgeScan: (kind, coordinate, options2) => this.recordedEdgeScan(kind, coordinate, options2),
17209
18249
  subgraphAtCoordinate: (rootId, subgraphOptions) => this.subgraphAtCoordinate(rootId, subgraphOptions),
17210
- algorithmsAtCoordinate: (coordinate) => this.algorithmsAtCoordinate(coordinate)
18250
+ algorithmsAtCoordinate: (coordinate) => this.algorithmsAtCoordinate(coordinate),
18251
+ identityAtCoordinate: (coordinate) => this.identityAtCoordinate(coordinate),
18252
+ rebuildIdentityClosure: () => this.rebuildIdentityClosure(),
18253
+ validateIdentity: () => this.validateIdentity(),
18254
+ liveNodesSharingIds: async (ids, target) => {
18255
+ const backend2 = target ?? this.#baseBackend;
18256
+ const liveKindsById = await liveNodeKindsSharingIds(
18257
+ this.#identityContext(backend2),
18258
+ backend2,
18259
+ ids
18260
+ );
18261
+ const peers = [];
18262
+ for (const [id, kinds] of liveKindsById) {
18263
+ for (const kind of kinds) peers.push({ kind, id });
18264
+ }
18265
+ return peers;
18266
+ },
18267
+ identityAssertionRowsByIds: async (ids, target) => {
18268
+ if (this.#graph.identity === void 0) return /* @__PURE__ */ new Map();
18269
+ const backend2 = target ?? this.#baseBackend;
18270
+ const ctx = this.#identityContext(backend2);
18271
+ const rows = await loadAssertionsByIds(
18272
+ backend2,
18273
+ ctx.schema,
18274
+ this.graphId,
18275
+ ids
18276
+ );
18277
+ return new Map(
18278
+ [...rows].map(([id, row]) => [id, toTransferAssertion(row)])
18279
+ );
18280
+ },
18281
+ structuralIdentityClasses: async (references, target) => {
18282
+ if (this.#graph.identity === void 0) return /* @__PURE__ */ new Map();
18283
+ const backend2 = target ?? this.#baseBackend;
18284
+ const ctx = this.#identityContext(backend2);
18285
+ return loadCurrentStructuralClasses(
18286
+ backend2,
18287
+ ctx.schema,
18288
+ this.graphId,
18289
+ references
18290
+ );
18291
+ },
18292
+ readCurrentIdentityAssertions: (mode, options2) => this.readCurrentIdentityAssertions(mode, options2),
18293
+ identityAssertionsAtTarget: (target, mode) => this.identityAssertionsAtTarget(target, mode),
18294
+ readIdentityAssertionPageAtTarget: (target, mode, options2) => this.readIdentityAssertionPageAtTarget(target, mode, options2),
18295
+ lockIdentityImportTarget: (target) => this.lockIdentityImportTarget(target),
18296
+ foldImportedIdentityNodes: (target, references) => this.foldImportedIdentityNodes(target, references),
18297
+ importIdentityAssertionsAtTarget: (target, assertions, mode) => this.importIdentityAssertionsAtTarget(target, assertions, mode),
18298
+ applyIdentityMergeAtTarget: (target, retractionIds, assertions) => this.applyIdentityMergeAtTarget(target, retractionIds, assertions),
18299
+ assertIdentityClassesConsistentAtTarget: (target, seeds) => this.assertIdentityClassesConsistentAtTarget(target, seeds)
17211
18300
  };
17212
18301
  Object.defineProperty(this, STORE_RUNTIME, {
17213
18302
  configurable: false,
@@ -17232,6 +18321,215 @@ var StoreImplementation = class _StoreImplementation {
17232
18321
  get registry() {
17233
18322
  return this.#registry;
17234
18323
  }
18324
+ /**
18325
+ * The TypeGraph Identity Profile facade. Reachable through the public
18326
+ * {@link Store} surface only on graphs that declared `identity: { ... }`;
18327
+ * the runtime guard below catches widened or JavaScript callers.
18328
+ *
18329
+ * The facade is memoized per store, so it must not close over any state a
18330
+ * lifecycle operation can move — see `#identityContext`.
18331
+ */
18332
+ get identity() {
18333
+ this.#requireIdentityEnabled(
18334
+ 'Add identity: { sameIdAcrossKinds: "fold" } to defineGraph(...).'
18335
+ );
18336
+ const existing = IDENTITY_FACADES.get(this);
18337
+ if (existing !== void 0) return existing;
18338
+ const facade = createIdentityFacade(this.#identityContext(this.#backend));
18339
+ IDENTITY_FACADES.set(this, facade);
18340
+ return facade;
18341
+ }
18342
+ /**
18343
+ * Refuses an identity operation on a graph that never declared
18344
+ * `identity: { ... }`, where none of the identity tables exist.
18345
+ */
18346
+ #requireIdentityEnabled(suggestion) {
18347
+ if (this.#graph.identity !== void 0) return;
18348
+ throw new ConfigurationError(
18349
+ "Identity is not enabled for this graph.",
18350
+ { code: "IDENTITY_NOT_ENABLED", graphId: this.graphId },
18351
+ suggestion === void 0 ? void 0 : { suggestion }
18352
+ );
18353
+ }
18354
+ /** @internal Builds the identity read facade for a pinned StoreView. */
18355
+ identityAtCoordinate(coordinate) {
18356
+ this.#requireIdentityEnabled();
18357
+ const recordedAsOf = coordinate.recorded?.asOf;
18358
+ const backend = recordedAsOf === void 0 ? this.#backend : this.#recordedReads.backendForCoordinate(
18359
+ coordinate,
18360
+ "recorded-identity"
18361
+ );
18362
+ const schema = recordedReadSchemaFor(
18363
+ this.#sqlSchema(),
18364
+ recordedAsOf,
18365
+ this.#recordedReadBinding,
18366
+ "recorded-identity"
18367
+ );
18368
+ return createIdentityReadFacade({
18369
+ ...this.#identityContext(backend),
18370
+ schema,
18371
+ coordinate
18372
+ });
18373
+ }
18374
+ /** Rebuilds derived current identity closure without advancing revision. */
18375
+ async rebuildIdentityClosure() {
18376
+ this.#requireIdentityEnabled();
18377
+ await runInWriteTransaction(
18378
+ {
18379
+ graphId: this.graphId,
18380
+ schemaVersion: this.#schemaMetadata.schemaVersion,
18381
+ historyEnabled: this.#captureEnabled,
18382
+ revisionTrackingEnabled: this.#revisionTrackingEnabled,
18383
+ revisionSchema: this.#sqlSchema()
18384
+ },
18385
+ this.#baseBackend,
18386
+ async (target) => rebuildIdentityClosureForContext(this.#identityContext(target)),
18387
+ { didWrite: () => false }
18388
+ );
18389
+ }
18390
+ /**
18391
+ * @internal Validates/rebuilds identity under schema-transition locks.
18392
+ *
18393
+ * `provisionDerivedRelations` carries the DDL `ensureIdentitySchemaStorage`
18394
+ * deliberately did not run outside this transaction, so an evolution that
18395
+ * fails leaves no half-published derived relation behind.
18396
+ */
18397
+ async identitySchemaPreflight(target, provisionDerivedRelations) {
18398
+ await identitySchemaCommitPreflight(this.#identityContext(this.#backend), {
18399
+ enablement: false,
18400
+ provisionDerivedRelations
18401
+ })(target);
18402
+ }
18403
+ /** @internal Read-only startup integrity verification. */
18404
+ async validateIdentity() {
18405
+ if (this.#graph.identity === void 0) return;
18406
+ await validateIdentityForContext(this.#identityContext(this.#baseBackend));
18407
+ }
18408
+ /** @internal Cascades removed kinds in the schema-commit transaction. */
18409
+ async removeIdentityKindsInSchemaPreflight(target, kinds, options) {
18410
+ if (!this.#captureEnabled) {
18411
+ await removeIdentityKindsForContext(
18412
+ this.#identityContext(target),
18413
+ kinds,
18414
+ options
18415
+ );
18416
+ return;
18417
+ }
18418
+ const scope = createRecordedTransactionScope(
18419
+ target,
18420
+ this.#sqlSchema(),
18421
+ target.dialect === "sqlite"
18422
+ );
18423
+ await removeIdentityKindsForContext(
18424
+ this.#identityContext(scope.backend),
18425
+ kinds,
18426
+ options
18427
+ );
18428
+ await scope.flush();
18429
+ }
18430
+ /**
18431
+ * @internal Reads the graph's identity assertions in transfer shape, honoring
18432
+ * this store's SQL binding. Used by interchange export, base-version
18433
+ * fingerprinting, and merge staging/diff.
18434
+ */
18435
+ readCurrentIdentityAssertions(mode, options) {
18436
+ if (this.#graph.identity === void 0) return Promise.resolve([]);
18437
+ return readIdentityAssertionsForInterchange(
18438
+ this.#identityContext(this.#baseBackend),
18439
+ mode,
18440
+ options
18441
+ );
18442
+ }
18443
+ /** @internal Reads identity truth through an already-bound transaction. */
18444
+ identityAssertionsAtTarget(target, mode = "state") {
18445
+ if (this.#graph.identity === void 0) return Promise.resolve([]);
18446
+ return readIdentityAssertionsForInterchange(
18447
+ this.#identityContext(target),
18448
+ mode
18449
+ );
18450
+ }
18451
+ /** @internal Reads one bounded identity-assertion page at a bound target. */
18452
+ readIdentityAssertionPageAtTarget(target, mode, options) {
18453
+ if (this.#graph.identity === void 0) {
18454
+ return Promise.resolve({ assertions: [], done: true });
18455
+ }
18456
+ return readIdentityAssertionPageAtTarget(
18457
+ this.#identityContext(target),
18458
+ target,
18459
+ mode,
18460
+ options
18461
+ );
18462
+ }
18463
+ /** @internal Acquires the enabled graph's identity lock for an import. */
18464
+ lockIdentityImportTarget(target) {
18465
+ return this.#graph.identity === void 0 ? Promise.resolve() : lockIdentityGraph(target, this.graphId);
18466
+ }
18467
+ /** @internal Restores same-id folding after the ops-layer import bypass. */
18468
+ foldImportedIdentityNodes(target, references) {
18469
+ if (this.#graph.identity === void 0 || references.length === 0) {
18470
+ return Promise.resolve();
18471
+ }
18472
+ return foldIdentityForCreatedNodes(
18473
+ {
18474
+ graphId: this.graphId,
18475
+ registry: this.#registry,
18476
+ sameIdAcrossKinds: this.#graph.identity.sameIdAcrossKinds,
18477
+ schema: this.#sqlSchema()
18478
+ },
18479
+ target,
18480
+ references
18481
+ );
18482
+ }
18483
+ /** @internal Applies identity interchange rows inside an import transaction. */
18484
+ importIdentityAssertionsAtTarget(target, assertions, mode) {
18485
+ if (assertions.length === 0) {
18486
+ return Promise.resolve({ created: 0, skipped: 0 });
18487
+ }
18488
+ if (this.#graph.identity === void 0) {
18489
+ throw new ConfigurationError(
18490
+ "Cannot import identity assertions into an identity-disabled graph.",
18491
+ { code: "IDENTITY_IMPORT_REQUIRES_PROFILE", graphId: this.graphId }
18492
+ );
18493
+ }
18494
+ return importIdentityAssertionsIntoTarget(
18495
+ this.#identityContext(target),
18496
+ target,
18497
+ assertions,
18498
+ mode
18499
+ );
18500
+ }
18501
+ /**
18502
+ * @internal Post-write identity validation for a graph-merge commit: proves
18503
+ * the identity classes the merge touched carry no contradiction in the state
18504
+ * the merge just wrote, inside the merge's own transaction.
18505
+ */
18506
+ assertIdentityClassesConsistentAtTarget(target, seeds) {
18507
+ if (this.#graph.identity === void 0 || seeds.length === 0) {
18508
+ return Promise.resolve();
18509
+ }
18510
+ return assertAffectedIdentityClassesConsistent(
18511
+ this.#identityContext(target),
18512
+ target,
18513
+ seeds
18514
+ );
18515
+ }
18516
+ /** @internal Mechanical graph-merge apply through the mutation coordinator. */
18517
+ applyIdentityMergeAtTarget(target, retractionIds, assertions) {
18518
+ if (retractionIds.length === 0 && assertions.length === 0) {
18519
+ return Promise.resolve({ created: 0, retracted: 0 });
18520
+ }
18521
+ if (this.#graph.identity === void 0) {
18522
+ throw new ConfigurationError(
18523
+ "Cannot apply identity merge changes to an identity-disabled graph.",
18524
+ { code: "IDENTITY_MERGE_REQUIRES_PROFILE", graphId: this.graphId }
18525
+ );
18526
+ }
18527
+ return applyIdentityChangesForContext(
18528
+ this.#identityContext(target),
18529
+ retractionIds,
18530
+ assertions
18531
+ );
18532
+ }
17235
18533
  /**
17236
18534
  * An opaque, in-memory snapshot of this store's reconciled schema — the
17237
18535
  * merged compile-time + runtime-committed graph plus the committed version
@@ -17604,6 +18902,7 @@ var StoreImplementation = class _StoreImplementation {
17604
18902
  upsertDirtyCheck: (kind, id, existingProps, inputProps) => nodeUpsertDirtyCheck(ctx, kind, id, existingProps, inputProps)
17605
18903
  },
17606
18904
  executeDelete: (kind, id, backend) => executeNodeDelete(ctx, kind, id, backend),
18905
+ executeDeleteBatch: (kind, ids, backend) => executeNodeDeleteBatch(ctx, kind, ids, backend),
17607
18906
  executeHardDelete: (kind, id, backend) => executeNodeHardDelete(ctx, kind, id, backend),
17608
18907
  temporalRowMatcher: (options) => this.#temporalRowMatcher(options),
17609
18908
  createQuery: () => this.query(),
@@ -17661,8 +18960,9 @@ var StoreImplementation = class _StoreImplementation {
17661
18960
  ...this.#options?.coalesceUnchangedUpserts === true && {
17662
18961
  upsertDirtyCheck: (kind, id, existingProps, inputProps) => edgeUpsertDirtyCheck(ctx, kind, id, existingProps, inputProps)
17663
18962
  },
17664
- executeDelete: (id, backend) => executeEdgeDelete(ctx, id, backend),
17665
- executeHardDelete: (id, backend) => executeEdgeHardDelete(ctx, id, backend),
18963
+ executeDelete: (kind, id, backend) => executeEdgeDelete(ctx, kind, id, backend),
18964
+ executeDeleteBatch: (kind, ids, backend) => executeEdgeDeleteBatch(ctx, kind, ids, backend),
18965
+ executeHardDelete: (kind, id, backend) => executeEdgeHardDelete(ctx, kind, id, backend),
17666
18966
  temporalRowMatcher: (options) => this.#temporalRowMatcher(options),
17667
18967
  createQuery: () => this.query(),
17668
18968
  executeGetOrCreateByEndpoints: (kind, fromKind, fromId, toKind, toId, props, backend, options) => executeEdgeGetOrCreateByEndpoints(
@@ -18267,9 +19567,12 @@ var StoreImplementation = class _StoreImplementation {
18267
19567
  receiptRecorder
18268
19568
  ));
18269
19569
  }
19570
+ const identity = this.#graph.identity === void 0 ? void 0 : createIdentityFacade(this.#identityContext(this.#backend));
19571
+ const receiptIdentity = identity === void 0 || receiptRecorder === void 0 ? identity : wrapTransactionIdentity(identity, receiptRecorder);
18270
19572
  const fallbackContext = {
18271
19573
  nodes,
18272
19574
  edges,
19575
+ ...receiptIdentity === void 0 ? {} : { identity: receiptIdentity },
18273
19576
  // No real transaction: `tx.sql` is absent and there is no atomicity.
18274
19577
  sqlAvailability: "unavailable",
18275
19578
  backend: createTransactionReadBackend(this.#backend),
@@ -18300,15 +19603,31 @@ var StoreImplementation = class _StoreImplementation {
18300
19603
  recordedByGraph = instants;
18301
19604
  }) : backendOptions;
18302
19605
  try {
18303
- const run = async (txBackend, nativeTransaction) => invoke(
18304
- this.#buildTransactionContext(
19606
+ const run = async (txBackend, nativeTransaction) => {
19607
+ const invokeTransaction = () => invoke(
19608
+ this.#buildTransactionContext(
19609
+ txBackend,
19610
+ nativeTransaction,
19611
+ runHooks,
19612
+ receiptRecorder,
19613
+ runBulkHooks
19614
+ )
19615
+ );
19616
+ if (!this.#captureEnabled && !this.#revisionTrackingEnabled) {
19617
+ return invokeTransaction();
19618
+ }
19619
+ return withWriteTransactionSession(
18305
19620
  txBackend,
18306
- nativeTransaction,
18307
- runHooks,
18308
- receiptRecorder,
18309
- runBulkHooks
18310
- )
18311
- );
19621
+ {
19622
+ graphId: this.graphId,
19623
+ schemaVersion: this.#schemaMetadata.schemaVersion,
19624
+ historyEnabled: this.#captureEnabled,
19625
+ revisionTrackingEnabled: this.#revisionTrackingEnabled,
19626
+ revisionSchema: this.#sqlSchema()
19627
+ },
19628
+ invokeTransaction
19629
+ );
19630
+ };
18312
19631
  const result = this.#captureEnabled || this.#adapterBackend === void 0 ? await this.#backend.transaction(
18313
19632
  (txBackend) => run(txBackend, void 0),
18314
19633
  transactionOptions
@@ -18494,10 +19813,15 @@ var StoreImplementation = class _StoreImplementation {
18494
19813
  context.edges,
18495
19814
  scopeRecorder
18496
19815
  );
19816
+ const identity = this.#graph.identity === void 0 ? void 0 : wrapTransactionIdentity(
19817
+ context.identity,
19818
+ scopeRecorder
19819
+ );
18497
19820
  const scoped = this.#attachMeasure(
18498
19821
  overlayPropertyDescriptors(context, {
18499
19822
  nodes,
18500
19823
  edges,
19824
+ ...identity === void 0 ? {} : { identity },
18501
19825
  getNodeCollection: (kind) => this.#resolveDynamicNodeCollection(nodes, kind)
18502
19826
  })
18503
19827
  );
@@ -18551,10 +19875,13 @@ var StoreImplementation = class _StoreImplementation {
18551
19875
  receiptRecorder
18552
19876
  ));
18553
19877
  }
19878
+ const identity = this.#graph.identity === void 0 ? void 0 : createIdentityFacade(this.#identityContext(txBackend));
19879
+ const receiptIdentity = identity === void 0 || receiptRecorder === void 0 ? identity : wrapTransactionIdentity(identity, receiptRecorder);
18554
19880
  const getNodeCollection = (kind) => this.#resolveDynamicNodeCollection(nodes, kind);
18555
19881
  const base = {
18556
19882
  nodes,
18557
19883
  edges,
19884
+ ...receiptIdentity === void 0 ? {} : { identity: receiptIdentity },
18558
19885
  backend: createTransactionReadBackend(txBackend),
18559
19886
  [TRANSACTION_RUNTIME]: { backend: txBackend, runNodeOperationHooks },
18560
19887
  getNodeCollection
@@ -18587,6 +19914,67 @@ var StoreImplementation = class _StoreImplementation {
18587
19914
  });
18588
19915
  return receiptRecorder === void 0 ? withSql : this.#attachMeasure(withSql);
18589
19916
  }
19917
+ #identityContext(backend) {
19918
+ const currentSchemaVersion = () => this.#schemaMetadata.schemaVersion;
19919
+ return {
19920
+ graph: this.#graph,
19921
+ graphId: this.graphId,
19922
+ get schemaVersion() {
19923
+ return currentSchemaVersion();
19924
+ },
19925
+ registry: this.#registry,
19926
+ backend,
19927
+ schema: this.#sqlSchema(),
19928
+ historyEnabled: this.#captureEnabled,
19929
+ revisionTrackingEnabled: this.#revisionTrackingEnabled,
19930
+ sameIdAcrossKinds: this.#graph.identity?.sameIdAcrossKinds ?? "ignore",
19931
+ loadNodes: async (references, coordinate) => {
19932
+ const idsByKind = /* @__PURE__ */ new Map();
19933
+ for (const reference of references) {
19934
+ const ids = idsByKind.get(reference.kind) ?? /* @__PURE__ */ new Set();
19935
+ ids.add(reference.id);
19936
+ idsByKind.set(reference.kind, ids);
19937
+ }
19938
+ const loadedByReference = /* @__PURE__ */ new Map();
19939
+ await Promise.all(
19940
+ [...idsByKind].map(async ([kind, idSet]) => {
19941
+ const ids = [...idSet];
19942
+ if (coordinate?.recorded !== void 0) {
19943
+ const nodes = await this.recordedNodeGetByIds(
19944
+ kind,
19945
+ ids.map((id) => id),
19946
+ coordinate
19947
+ );
19948
+ for (const [index, node] of nodes.entries()) {
19949
+ if (node !== void 0) {
19950
+ loadedByReference.set(
19951
+ refKey({ kind, id: requireDefined(ids[index]) }),
19952
+ node
19953
+ );
19954
+ }
19955
+ }
19956
+ return;
19957
+ }
19958
+ const rows = backend.getNodes === void 0 ? await Promise.all(
19959
+ ids.map((id) => backend.getNode(this.graphId, kind, id))
19960
+ ) : await backend.getNodes(this.graphId, kind, ids);
19961
+ const includeTombstoned = coordinate?.valid.mode === "includeTombstones";
19962
+ for (const row of rows) {
19963
+ if (row === void 0) continue;
19964
+ if (!includeTombstoned && row.deleted_at !== void 0) continue;
19965
+ loadedByReference.set(
19966
+ refKey({ kind, id: row.id }),
19967
+ rowToNode(row)
19968
+ );
19969
+ }
19970
+ })
19971
+ );
19972
+ return references.map(
19973
+ (reference) => loadedByReference.get(refKey(reference))
19974
+ );
19975
+ }
19976
+ };
19977
+ }
18590
19978
  // === Graph Lifecycle ===
18591
19979
  /**
18592
19980
  * Hard-deletes all data for this graph from the database.
@@ -18602,6 +19990,9 @@ var StoreImplementation = class _StoreImplementation {
18602
19990
  if (this.#revisionTrackingEnabled) {
18603
19991
  await lockRecordedGraphWrite(target, this.graphId);
18604
19992
  }
19993
+ if (this.#graph.identity !== void 0) {
19994
+ await lockIdentityGraph(target, this.graphId);
19995
+ }
18605
19996
  const previousRevision = this.#revisionTrackingEnabled && !this.#captureEnabled ? await readRecordedClock(target, this.#sqlSchema(), this.graphId) : void 0;
18606
19997
  await target.clearGraph(this.graphId);
18607
19998
  if (this.#revisionTrackingEnabled && !this.#captureEnabled) {
@@ -18764,12 +20155,40 @@ var StoreImplementation = class _StoreImplementation {
18764
20155
  this.graphId
18765
20156
  );
18766
20157
  }
18767
- const committed = classification.requireEmpty.length > 0 ? await commitEvolvedSchemaWhenRequiredKindsAreEmpty(
20158
+ const identityCandidate = merged.identity === void 0 ? void 0 : this.#cloneWithGraph(merged, void 0);
20159
+ const identityProvisioning = identityCandidate === void 0 ? void 0 : await ensureIdentitySchemaStorage(
20160
+ this.#baseBackend,
20161
+ this.#sqlSchema(),
20162
+ {
20163
+ graphId: this.graphId,
20164
+ enablement: false,
20165
+ // The evolved graph's registry — the one the candidate's own
20166
+ // preflight rebuilds through, so the predicate and the fill agree
20167
+ // on which assertions count.
20168
+ registry: buildKindRegistry(merged)
20169
+ }
20170
+ );
20171
+ const committed = identityCandidate === void 0 ? classification.requireEmpty.length > 0 ? await commitEvolvedSchemaWhenRequiredKindsAreEmpty(
18768
20172
  this.#backend,
18769
20173
  merged,
18770
20174
  activeRow.version,
18771
20175
  classification
18772
- ) : await commitNewSchemaVersion(this.#backend, merged, activeRow.version);
20176
+ ) : await commitNewSchemaVersion(this.#backend, merged, activeRow.version) : await commitNewSchemaVersionWithPreflight(
20177
+ this.#backend,
20178
+ merged,
20179
+ activeRow.version,
20180
+ async (target) => {
20181
+ await assertEvolvedSchemaRequiredKindsEmpty(
20182
+ target,
20183
+ this.graphId,
20184
+ classification
20185
+ );
20186
+ await identityCandidate.identitySchemaPreflight(
20187
+ target,
20188
+ identityProvisioning?.provisionInCommit ?? []
20189
+ );
20190
+ }
20191
+ );
18773
20192
  await materializeVectorContributions(this.#backend, merged);
18774
20193
  const evolved = this.#cloneWithGraph(
18775
20194
  merged,
@@ -19136,6 +20555,169 @@ var StoreImplementation = class _StoreImplementation {
19136
20555
  const vectorSlots = backend.capabilities.vector?.supported === true ? resolveGraphVectorSlots(baseline) : [];
19137
20556
  return repair(this.graphId, vectorSlots);
19138
20557
  }
20558
+ /**
20559
+ * Read-only readiness check for the search projections: is search
20560
+ * coherent with the graph right now, or is a query about to hit a
20561
+ * degraded index?
20562
+ *
20563
+ * The read-only half of {@link Store.repairContributions}, and the
20564
+ * bottom rung of the contribution health ladder — probe (read) →
20565
+ * `repairContributions()` (incremental, non-destructive) →
20566
+ * {@link Store.rebuildContribution} (destructive full reset). It shares
20567
+ * the detection logic of the other two exactly: a second implementation
20568
+ * would be free to disagree with the one the hot-path gate consults,
20569
+ * and a health check that disagrees with the gate is worse than none.
20570
+ *
20571
+ * Writes nothing — no DDL, no marker writes, no effect on the
20572
+ * per-instance caches the hot path relies on — so it is safe on a read
20573
+ * path, on a replica, and under a least-privilege role. That is the
20574
+ * whole reason it exists: the alternative for a caller who wants to
20575
+ * know whether search is ready was to run `repairContributions()`, a
20576
+ * write with repair side effects, and hope.
20577
+ *
20578
+ * One entry per search projection that this graph actually declares
20579
+ * contributions for. A projection with none is omitted rather than
20580
+ * reported `ready`, so an empty `entries` array means "nothing to
20581
+ * assess" — never "assessed and healthy". Use
20582
+ * {@link Store.verifyContributions} for the structured per-table
20583
+ * diagnostics behind a `degraded` entry.
20584
+ *
20585
+ * @throws {ConfigurationError} when the backend provisions
20586
+ * contributions but cannot probe its own catalog. Answering `ready`
20587
+ * without having looked is the one answer this method must never
20588
+ * give. A backend with no contribution machinery at all has nothing
20589
+ * to assess and resolves to empty entries instead.
20590
+ */
20591
+ async probeContributions() {
20592
+ const backend = this.#baseBackend;
20593
+ const probe = backend.probeContributions;
20594
+ if (probe === void 0) {
20595
+ if (backend.capabilities.contributions?.supported !== true) {
20596
+ return { entries: [] };
20597
+ }
20598
+ throw new ConfigurationError(
20599
+ "probeContributions requires a backend that can probe its catalog for contribution tables.",
20600
+ {
20601
+ backend: backend.dialect,
20602
+ capability: "contributions",
20603
+ operation: "probe"
20604
+ }
20605
+ );
20606
+ }
20607
+ const vectorSlots = backend.capabilities.vector?.supported === true ? resolveGraphVectorSlots(this.#graph) : [];
20608
+ const entries = await probe(this.graphId, vectorSlots);
20609
+ const revision = await this.revisionNow();
20610
+ return {
20611
+ ...revision === void 0 ? {} : { graphRevision: revision },
20612
+ entries
20613
+ };
20614
+ }
20615
+ /**
20616
+ * Destructively rebuilds one search projection for THIS graph: clears the
20617
+ * graph's rows from the projection's storage, recreates that storage from
20618
+ * the current DDL when recreating it is safe, reconstructs the content
20619
+ * from the node rows, and stamps the durable marker at the current
20620
+ * signature.
20621
+ *
20622
+ * **Scoped to this graph.** The fulltext table is one physical table
20623
+ * holding every graph's rows, keyed by `graph_id`, and this method is
20624
+ * fenced per graph — so it removes only this graph's rows
20625
+ * (`DELETE ... WHERE graph_id`, the same statement `clear()` uses) and
20626
+ * escalates to dropping and recreating the table only when no other graph
20627
+ * has rows in it. That drop is the one repair for storage provisioned at a
20628
+ * shape the current DDL no longer produces; when another graph's content
20629
+ * is in the way it is refused
20630
+ * ({@link ContributionRebuildUnsupportedError}, reason
20631
+ * `shared-storage-in-use`) rather than taken, because fulltext content is
20632
+ * reconstructed from a graph's own nodes through its own schema and no
20633
+ * other process can put back what the drop would destroy.
20634
+ *
20635
+ * The top rung of the health ladder, and the repair
20636
+ * {@link Store.repairContributions} deliberately refuses to perform.
20637
+ * Never reachable from that method — a `stale` contribution's table
20638
+ * exists at the *old* shape, so the ordinary ensure path's idempotent
20639
+ * `CREATE ... IF NOT EXISTS` no-ops and re-stamping the marker there
20640
+ * would leave it blessing a table whose shape is wrong, which is
20641
+ * exactly what the drift guard exists to prevent. Only a recreate makes
20642
+ * the ensure meaningful, and destroying content is a decision stated at
20643
+ * the call site rather than reached through a flag named `force`.
20644
+ *
20645
+ * **This destroys this graph's index content.** For `"fulltext"` nothing
20646
+ * is permanently lost: the searchable content is derived from node
20647
+ * properties TypeGraph already stores, so the refill reconstructs it in
20648
+ * the same transaction. Nodes whose stored `props` cannot be read as an
20649
+ * object are counted in `skipped` and are absent from the rebuilt index —
20650
+ * `store.search.rebuildFulltext()` reports their ids individually.
20651
+ *
20652
+ * Atomic: the clear (or drop), recreate, refill, and stamp all run inside
20653
+ * one transaction under the same per-graph fence as a schema commit, plus
20654
+ * a database-scoped advisory lock serializing this contribution's DDL
20655
+ * across graphs, so an interrupted rebuild leaves the contribution exactly
20656
+ * as it was rather than attested-but-empty.
20657
+ *
20658
+ * The cost differs by path, and only one of them is a maintenance-window
20659
+ * operation. A rebuild that drops and recreates the shared table takes
20660
+ * `ACCESS EXCLUSIVE` on it before deciding to — the verdict "no other
20661
+ * graph has rows here" is only sound under the exclusion the drop needs —
20662
+ * so on PostgreSQL every graph's fulltext writers wait for the whole
20663
+ * refill. That was already true of the drop itself, which holds the same
20664
+ * lock to commit; the exclusion now merely starts a few statements
20665
+ * earlier. The graph-scoped path takes no table lock at all: its
20666
+ * `DELETE ... WHERE graph_id` is transactional and touches only this
20667
+ * graph's rows, so it never makes another graph's writers wait.
20668
+ * `store.search.rebuildFulltext()` remains the incremental, resumable way
20669
+ * to refresh content when the storage shape is fine.
20670
+ *
20671
+ * @throws {ContributionRebuildUnsupportedError} for `"vector"`, always:
20672
+ * TypeGraph stores the vectors callers supply and never the inputs
20673
+ * that produced them, so embeddings exist only in the storage a
20674
+ * rebuild would drop and there is nothing to reconstruct them from.
20675
+ * `store.reembedVectorField(kind, fieldPath, { embed })` is the
20676
+ * sanctioned destructive path — it takes the callback that can
20677
+ * regenerate what the drop destroys. Also thrown when the active
20678
+ * strategy declares no teardown DDL, when the backend has no
20679
+ * transactional schema fence to make the sequence atomic, and when the
20680
+ * recorded shape is stale but the storage that would have to be
20681
+ * recreated holds another graph's rows (`shared-storage-in-use`). Every
20682
+ * one of these refuses before anything is dropped or deleted.
20683
+ * @throws {ConfigurationError} when the backend cannot rebuild
20684
+ * strategy-owned contributions at all.
20685
+ */
20686
+ async rebuildContribution(scope, options) {
20687
+ const backend = this.#baseBackend;
20688
+ const rebuild = backend.rebuildContribution;
20689
+ if (rebuild === void 0) {
20690
+ throw new ConfigurationError(
20691
+ "rebuildContribution requires a backend that can drop, recreate, and re-stamp strategy-owned contribution tables.",
20692
+ {
20693
+ backend: backend.dialect,
20694
+ capability: "contributions",
20695
+ operation: "rebuild"
20696
+ }
20697
+ );
20698
+ }
20699
+ await this.#loadCaughtUp("rebuild");
20700
+ const registry = this.#registry;
20701
+ const graphId = this.graphId;
20702
+ return rebuild(graphId, scope, async (target) => {
20703
+ if (scope !== "fulltext") {
20704
+ throw new ContributionRebuildUnsupportedError(
20705
+ "vector-source-unavailable",
20706
+ { graphId, contribution: scope, backend: backend.dialect }
20707
+ );
20708
+ }
20709
+ const result = await repopulateFulltextInTransaction(
20710
+ { graphId, registry },
20711
+ target,
20712
+ options?.pageSize === void 0 ? {} : { pageSize: options.pageSize }
20713
+ );
20714
+ return {
20715
+ processed: result.processed,
20716
+ repopulated: result.upserted,
20717
+ skipped: result.skipped
20718
+ };
20719
+ });
20720
+ }
19139
20721
  /**
19140
20722
  * Removes extension kinds from the schema with cascading edge and
19141
20723
  * ontology cleanup. Two-phase by design:
@@ -19183,10 +20765,19 @@ var StoreImplementation = class _StoreImplementation {
19183
20765
  const compileTimeGraph = stripGraphExtension(this.#graph);
19184
20766
  const merged = plan.document === void 0 ? compileTimeGraph : mergeGraphExtension(compileTimeGraph, plan.document);
19185
20767
  const finalGraph = applyDeprecatedKinds(merged, baseline.deprecatedKinds);
19186
- const committedRow = await commitNewSchemaVersion(
20768
+ const identityCascade = await this.#identityRemovalPreflight(
20769
+ finalGraph,
20770
+ plan.removedNodeKinds
20771
+ );
20772
+ const committedRow = identityCascade === void 0 ? await commitNewSchemaVersion(
19187
20773
  this.#backend,
19188
20774
  finalGraph,
19189
20775
  activeRow.version
20776
+ ) : await commitNewSchemaVersionWithPreflight(
20777
+ this.#backend,
20778
+ finalGraph,
20779
+ activeRow.version,
20780
+ identityCascade
19190
20781
  );
19191
20782
  const recordKindRemoval = this.#backend.recordKindRemoval;
19192
20783
  if (recordKindRemoval !== void 0) {
@@ -19224,6 +20815,35 @@ var StoreImplementation = class _StoreImplementation {
19224
20815
  }
19225
20816
  return evolved;
19226
20817
  }
20818
+ /**
20819
+ * The identity cascade `removeKinds()` commits with, or `undefined` when the
20820
+ * removal cannot touch the assertion ledger.
20821
+ *
20822
+ * Identity being switched OFF is not that case: disabling identity retains
20823
+ * the assertion rows, so a graph with no profile can still hold assertions
20824
+ * naming the kinds being removed. Those rows are cascaded too — otherwise
20825
+ * they survive as current orphans that a later re-enablement filters out of
20826
+ * the closure while raw ledger reads and merge staging still see them. What
20827
+ * the profile's absence does remove is the closure contract, so the repair
20828
+ * pass is skipped and the ledger cleanup runs alone — and a graph that has no
20829
+ * such rows keeps committing through the plain schema-commit primitive.
20830
+ */
20831
+ async #identityRemovalPreflight(finalGraph, removedNodeKinds) {
20832
+ if (removedNodeKinds.length === 0) return void 0;
20833
+ const repairClosure = finalGraph.identity !== void 0;
20834
+ if (!repairClosure && !await identityKindCascadeNeeded(
20835
+ this.#backend,
20836
+ this.#sqlSchema(),
20837
+ this.graphId,
20838
+ removedNodeKinds
20839
+ )) {
20840
+ return void 0;
20841
+ }
20842
+ const candidate = this.#cloneWithGraph(finalGraph, void 0);
20843
+ return async (target) => candidate.removeIdentityKindsInSchemaPreflight(target, removedNodeKinds, {
20844
+ repairClosure
20845
+ });
20846
+ }
19227
20847
  /**
19228
20848
  * Runs the data-cleanup phase for any kinds removed via
19229
20849
  * `removeKinds()` whose data has not yet been deleted on this
@@ -19326,10 +20946,10 @@ var StoreImplementation = class _StoreImplementation {
19326
20946
  const getPendingKindRemovals = this.#backend.getPendingKindRemovals;
19327
20947
  if (getPendingKindRemovals === void 0) return;
19328
20948
  const addedNodes = Object.keys(merged.nodes).filter(
19329
- (kind) => !(kind in baseline.nodes)
20949
+ (kind) => !hasOwnKey(baseline.nodes, kind)
19330
20950
  );
19331
20951
  const addedEdges = Object.keys(merged.edges).filter(
19332
- (kind) => !(kind in baseline.edges)
20952
+ (kind) => !hasOwnKey(baseline.edges, kind)
19333
20953
  );
19334
20954
  if (addedNodes.length === 0 && addedEdges.length === 0) return;
19335
20955
  await ensureFocusedStatusTable(
@@ -19386,6 +21006,7 @@ var StoreImplementation = class _StoreImplementation {
19386
21006
  return (ctx, fn) => this.#withBulkOperationHooks(ctx, fn);
19387
21007
  }
19388
21008
  #createNodeOperationContext(runHooks = this.#immediateHookRunner(), runBulkHooks = this.#immediateBulkHookRunner()) {
21009
+ const identityConfig = this.#graph.identity;
19389
21010
  return {
19390
21011
  graph: this.#graph,
19391
21012
  graphId: this.graphId,
@@ -19394,6 +21015,31 @@ var StoreImplementation = class _StoreImplementation {
19394
21015
  revisionTrackingEnabled: this.#revisionTrackingEnabled,
19395
21016
  revisionSchema: this.#sqlSchema(),
19396
21017
  registry: this.#registry,
21018
+ ...identityConfig === void 0 ? {} : {
21019
+ identity: {
21020
+ lock: (target) => lockIdentityGraph(target, this.graphId),
21021
+ foldCreated: (target, references) => foldIdentityForCreatedNodes(
21022
+ {
21023
+ graphId: this.graphId,
21024
+ registry: this.#registry,
21025
+ sameIdAcrossKinds: identityConfig.sameIdAcrossKinds,
21026
+ schema: this.#sqlSchema()
21027
+ },
21028
+ target,
21029
+ references
21030
+ ),
21031
+ detachDeleted: (target, ref, mode) => detachIdentityForNode(
21032
+ {
21033
+ graphId: this.graphId,
21034
+ sameIdAcrossKinds: identityConfig.sameIdAcrossKinds,
21035
+ schema: this.#sqlSchema()
21036
+ },
21037
+ target,
21038
+ ref,
21039
+ mode
21040
+ )
21041
+ }
21042
+ },
19397
21043
  createOperationContext: (operation, entity, kind, id) => this.#createOperationContext(operation, entity, kind, id),
19398
21044
  withOperationHooks: runHooks,
19399
21045
  createBulkOperationContext: (operation, kind) => ({
@@ -19423,6 +21069,11 @@ var StoreImplementation = class _StoreImplementation {
19423
21069
  * Decorates the query execution port so hooks observe each statement the
19424
21070
  * query builder submits to the backend. A selective-projection retry
19425
21071
  * therefore emits two independent start/end pairs with their actual SQL.
21072
+ *
21073
+ * Decoration goes through {@link createBackendOverlay}, which carries the
21074
+ * projection's serialized-resource ownership: the hooked backend still
21075
+ * executes on the source's connection, so it must answer the same "shares one
21076
+ * serialized connection" question as its source.
19426
21077
  */
19427
21078
  #createHookedQueryBackend(backend) {
19428
21079
  if (this.#hooks.onQueryStart === void 0 && this.#hooks.onQueryEnd === void 0 && this.#hooks.onError === void 0) {
@@ -19431,8 +21082,7 @@ var StoreImplementation = class _StoreImplementation {
19431
21082
  const executeRaw = backend.executeRaw;
19432
21083
  const compileSql = backend.compileSql;
19433
21084
  const projected = createGraphBackendProjection(backend);
19434
- return {
19435
- ...projected,
21085
+ return createBackendOverlay(projected, {
19436
21086
  execute: (query) => {
19437
21087
  const compiled = compileSql === void 0 ? renderSql(query, backend.dialect) : compileSql(query);
19438
21088
  return this.#withQueryHooks(
@@ -19448,7 +21098,7 @@ var StoreImplementation = class _StoreImplementation {
19448
21098
  () => executeRaw(sqlText, params)
19449
21099
  )
19450
21100
  }
19451
- };
21101
+ });
19452
21102
  }
19453
21103
  async #withQueryHooks(sql2, params, execute) {
19454
21104
  const ctx = {
@@ -19804,10 +21454,70 @@ async function createStoreWithSchema(graph, backend, options) {
19804
21454
  prepared.result
19805
21455
  ];
19806
21456
  }
19807
- async function assertHistorySchemaOnOpen(backend, options) {
21457
+ async function assertHistorySchemaOnOpen(backend, graph, options) {
19808
21458
  if (options?.history !== true) return;
19809
21459
  const schema = options.schema === void 0 ? createSqlSchema(backend.tableNames) : requireSqlSchema(options.schema, "store schema");
19810
- await assertCurrentRecordedSchema(backend, schema);
21460
+ await assertCurrentRecordedSchema(
21461
+ backend,
21462
+ schema,
21463
+ graph.identity !== void 0
21464
+ );
21465
+ }
21466
+ var IDENTITY_ONTOLOGY_META_EDGES = /* @__PURE__ */ new Set([
21467
+ META_EDGE_DISJOINT_WITH,
21468
+ META_EDGE_EQUIVALENT_TO,
21469
+ META_EDGE_SAME_AS,
21470
+ META_EDGE_SUB_CLASS_OF
21471
+ ]);
21472
+ function identityOntologyChanged(before, after) {
21473
+ function relevantRelations(schema) {
21474
+ return schema.ontology.relations.filter((relation) => IDENTITY_ONTOLOGY_META_EDGES.has(relation.metaEdge)).map(
21475
+ (relation) => JSON.stringify([relation.metaEdge, relation.from, relation.to])
21476
+ ).toSorted();
21477
+ }
21478
+ return JSON.stringify(relevantRelations(before)) !== JSON.stringify(relevantRelations(after));
21479
+ }
21480
+ var IDENTITY_GATE_CODES = {
21481
+ enablement: "IDENTITY_ENABLEMENT_PENDING",
21482
+ profile: "IDENTITY_PROFILE_MIGRATION_PENDING",
21483
+ ontology: "IDENTITY_SCHEMA_MIGRATION_PENDING"
21484
+ };
21485
+ async function ensureSchemaWithIdentityPrecedence(backend, graph, options, gate) {
21486
+ try {
21487
+ return await ensureSchema(backend, graph, options);
21488
+ } catch (error) {
21489
+ if (gate === void 0 || !isIdentityOnlyBreakingChange(error)) throw error;
21490
+ throw identityMigrationRequiredError(graph.id, gate, {
21491
+ blockedBy: "breaking-change",
21492
+ cause: error
21493
+ });
21494
+ }
21495
+ }
21496
+ function isIdentityOnlyBreakingChange(error) {
21497
+ if (!(error instanceof MigrationError)) return false;
21498
+ const diff = "diff" in error.details ? error.details.diff : void 0;
21499
+ if (diff?.identity?.severity !== "breaking") return false;
21500
+ return ![
21501
+ ...diff.nodes,
21502
+ ...diff.edges,
21503
+ ...diff.ontology,
21504
+ ...diff.indexes
21505
+ ].some((change) => change.severity === "breaking");
21506
+ }
21507
+ function identityMigrationRequiredError(graphId, gate, context) {
21508
+ const blocker = context.blockedBy === "breaking-change" ? "but the change is breaking and has not been applied." : "but autoMigrate is disabled and the change is pending.";
21509
+ return new ConfigurationError(
21510
+ `Changing Operational Identity semantics requires the schema migration to commit, ${blocker}`,
21511
+ {
21512
+ code: IDENTITY_GATE_CODES[gate],
21513
+ graphId,
21514
+ ...context.pendingVersion === void 0 ? {} : { pendingVersion: context.pendingVersion }
21515
+ },
21516
+ {
21517
+ cause: context.cause,
21518
+ suggestion: "Apply the migration explicitly with migrateSchema(backend, graph, activeVersion) \u2014 it commits the new version and rebuilds the identity closure in the same transaction \u2014 or, for a change that is not breaking, enable autoMigrate."
21519
+ }
21520
+ );
19811
21521
  }
19812
21522
  async function prepareStoreWithSchema(graph, backend, options) {
19813
21523
  const {
@@ -19815,11 +21525,70 @@ async function prepareStoreWithSchema(graph, backend, options) {
19815
21525
  activeRow,
19816
21526
  storedSchema
19817
21527
  } = await loadAndMergeGraphExtensionDocument(backend, graph);
19818
- const result = await ensureSchema(backend, merged, {
19819
- ...options,
21528
+ const identityProfileChanged = activeRow !== void 0 && storedSchema?.identity?.sameIdAcrossKinds !== merged.identity?.sameIdAcrossKinds;
21529
+ const identityInitialization = activeRow === void 0 && merged.identity !== void 0;
21530
+ const identityEnablement = identityInitialization || identityProfileChanged && storedSchema?.identity === void 0 && merged.identity !== void 0;
21531
+ const identitySemanticsChanged = identityProfileChanged || activeRow !== void 0 && storedSchema !== void 0 && merged.identity !== void 0 && identityOntologyChanged(
21532
+ storedSchema,
21533
+ serializeSchema(merged, activeRow.version + 1)
21534
+ );
21535
+ const identityGate = identitySemanticsChanged ? identityEnablement ? "enablement" : identityProfileChanged ? "profile" : "ontology" : void 0;
21536
+ const identityProfile = merged.identity;
21537
+ if (identityProfile !== void 0) {
21538
+ const resolvedIdentitySchema = options?.schema === void 0 ? createSqlSchema(backend.tableNames) : requireSqlSchema(options.schema, "store schema");
21539
+ const identityRegistry = buildKindRegistry(merged);
21540
+ await ensureIdentitySchemaStorage(backend, resolvedIdentitySchema, {
21541
+ graphId: merged.id,
21542
+ enablement: identityEnablement,
21543
+ registry: identityRegistry,
21544
+ // A derived relation this library version added, absent from (or left
21545
+ // empty in) a database that predates it, is created and FILLED as one
21546
+ // unit here — never created now and filled at the end of boot. Between
21547
+ // those two points sit the schema commit, the history assertion, three
21548
+ // materialization steps and an index build, and for that whole window an
21549
+ // empty separation relation would answer "not separated" to every
21550
+ // concurrent `assertSame`. See `ensureIdentitySchemaStorage`.
21551
+ //
21552
+ // Withheld when a schema commit is gated on an identity SEMANTICS
21553
+ // change: that commit's own preflight creates AND rebuilds inside the
21554
+ // commit transaction, under the semantics being committed. Filling here
21555
+ // would instead derive classes from semantics this database has not
21556
+ // accepted yet — and would keep them if the commit were then refused.
21557
+ // Nothing is provisioned on that path either, so a refused commit leaves
21558
+ // the relation absent rather than readable-empty, and the next open of a
21559
+ // graph whose semantics ARE committed heals it here.
21560
+ ...identityGate === void 0 ? {
21561
+ recomputeDerivedRelations: (target) => rebuildIdentityClosureForContext({
21562
+ backend: target,
21563
+ graphId: merged.id,
21564
+ registry: identityRegistry,
21565
+ schema: resolvedIdentitySchema,
21566
+ sameIdAcrossKinds: identityProfile.sameIdAcrossKinds
21567
+ })
21568
+ } : {}
21569
+ });
21570
+ }
21571
+ const ensureOptions = {
21572
+ // The fallback is not useless: spreading `options` while it is possibly
21573
+ // undefined makes TypeScript distribute the StoreOptions union through
21574
+ // the literal and reject the call below.
21575
+ // eslint-disable-next-line unicorn/no-useless-fallback-in-spread
21576
+ ...options ?? {},
19820
21577
  preloaded: { activeRow, storedSchema }
19821
- });
19822
- await assertHistorySchemaOnOpen(backend, options);
21578
+ };
21579
+ const result = await ensureSchemaWithIdentityPrecedence(
21580
+ backend,
21581
+ merged,
21582
+ ensureOptions,
21583
+ identityGate
21584
+ );
21585
+ if (identityGate !== void 0 && result.status === "pending") {
21586
+ throw identityMigrationRequiredError(merged.id, identityGate, {
21587
+ blockedBy: "auto-migrate-disabled",
21588
+ pendingVersion: result.version
21589
+ });
21590
+ }
21591
+ await assertHistorySchemaOnOpen(backend, merged, options);
19823
21592
  await materializeRuntimeContributions(backend, merged.id);
19824
21593
  await materializeVectorContributions(backend, merged);
19825
21594
  if (options?.systemIndexes !== "skip") {
@@ -19871,15 +21640,14 @@ async function materializeSystemIndexesOnBoot(backend, graphId, result) {
19871
21640
  }
19872
21641
  async function createVerifiedStore(graph, backend, options) {
19873
21642
  const prepared = await prepareVerifiedStore(graph, backend, options);
19874
- return [
19875
- new StoreImplementation(
19876
- prepared.graph,
19877
- backend,
19878
- options,
19879
- prepared.schemaMetadata
19880
- ),
19881
- prepared.result
19882
- ];
21643
+ const store = new StoreImplementation(
21644
+ prepared.graph,
21645
+ backend,
21646
+ options,
21647
+ prepared.schemaMetadata
21648
+ );
21649
+ await store.validateIdentity();
21650
+ return [store, prepared.result];
19883
21651
  }
19884
21652
  async function prepareVerifiedStore(graph, backend, options) {
19885
21653
  const {
@@ -19887,7 +21655,7 @@ async function prepareVerifiedStore(graph, backend, options) {
19887
21655
  activeRow,
19888
21656
  result
19889
21657
  } = await loadAndVerifyGraph(backend, graph);
19890
- await assertHistorySchemaOnOpen(backend, options);
21658
+ await assertHistorySchemaOnOpen(backend, merged, options);
19891
21659
  return {
19892
21660
  graph: merged,
19893
21661
  result,
@@ -19896,19 +21664,16 @@ async function prepareVerifiedStore(graph, backend, options) {
19896
21664
  }
19897
21665
  async function createVerifiedAdapterStore(graph, backend, options) {
19898
21666
  const prepared = await prepareVerifiedStore(graph, backend, options);
19899
- return [
19900
- asAdapterStoreSurface(
19901
- new AdapterStoreImplementation(
19902
- prepared.graph,
19903
- backend,
19904
- options,
19905
- prepared.schemaMetadata
19906
- )
19907
- ),
19908
- prepared.result
19909
- ];
21667
+ const store = new AdapterStoreImplementation(
21668
+ prepared.graph,
21669
+ backend,
21670
+ options,
21671
+ prepared.schemaMetadata
21672
+ );
21673
+ await store.validateIdentity();
21674
+ return [asAdapterStoreSurface(store), prepared.result];
19910
21675
  }
19911
21676
 
19912
21677
  export { ExecutableAggregateQuery, ExecutableQuery, MAX_EXPLICIT_RECURSIVE_DEPTH, MAX_RECURSIVE_DEPTH, PreparedQuery, QueryBuilder, RecordedStoreView, StoreSearch, StoreView, UnionableQuery, avg, composeFragments, count, countDistinct, countDistinctEdges, countEdges, createAdapterStore, createAdapterStoreWithSchema, createFragment, createQueryBuilder, createStore, createStoreWithSchema, createVerifiedAdapterStore, createVerifiedStore, defineSubgraphProject, field, having, havingEq, havingGt, havingGte, havingLt, havingLte, limitFragment, max, min, offsetFragment, orderByFragment, sum };
19913
- //# sourceMappingURL=chunk-ID4WXTKL.js.map
19914
- //# sourceMappingURL=chunk-ID4WXTKL.js.map
21678
+ //# sourceMappingURL=chunk-S6PW7VV5.js.map
21679
+ //# sourceMappingURL=chunk-S6PW7VV5.js.map