@nicia-ai/typegraph 0.35.0 → 0.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. package/dist/backend/postgres/index.cjs +24 -24
  2. package/dist/backend/postgres/index.d.cts +6 -6
  3. package/dist/backend/postgres/index.d.ts +6 -6
  4. package/dist/backend/postgres/index.js +11 -11
  5. package/dist/backend/postgres/pglite.cjs +16 -16
  6. package/dist/backend/postgres/pglite.d.cts +3 -3
  7. package/dist/backend/postgres/pglite.d.ts +3 -3
  8. package/dist/backend/postgres/pglite.js +11 -11
  9. package/dist/backend/sqlite/index.cjs +22 -21
  10. package/dist/backend/sqlite/index.d.cts +11 -9
  11. package/dist/backend/sqlite/index.d.ts +11 -9
  12. package/dist/backend/sqlite/index.js +10 -9
  13. package/dist/backend/sqlite/libsql.cjs +15 -14
  14. package/dist/backend/sqlite/libsql.cjs.map +1 -1
  15. package/dist/backend/sqlite/libsql.d.cts +3 -3
  16. package/dist/backend/sqlite/libsql.d.ts +3 -3
  17. package/dist/backend/sqlite/libsql.js +11 -10
  18. package/dist/backend/sqlite/libsql.js.map +1 -1
  19. package/dist/backend/sqlite/local.cjs +30 -23
  20. package/dist/backend/sqlite/local.cjs.map +1 -1
  21. package/dist/backend/sqlite/local.d.cts +3 -3
  22. package/dist/backend/sqlite/local.d.ts +3 -3
  23. package/dist/backend/sqlite/local.js +18 -11
  24. package/dist/backend/sqlite/local.js.map +1 -1
  25. package/dist/{chunk-TCOCYN2P.cjs → chunk-27SSDCEP.cjs} +14 -14
  26. package/dist/{chunk-TCOCYN2P.cjs.map → chunk-27SSDCEP.cjs.map} +1 -1
  27. package/dist/{chunk-5AQIKK56.cjs → chunk-2N6EOBTH.cjs} +16 -16
  28. package/dist/{chunk-5AQIKK56.cjs.map → chunk-2N6EOBTH.cjs.map} +1 -1
  29. package/dist/{chunk-FN35DLCN.cjs → chunk-3C2RYI6L.cjs} +13 -107
  30. package/dist/chunk-3C2RYI6L.cjs.map +1 -0
  31. package/dist/{chunk-4JEZZJ6S.cjs → chunk-3DS7Z4T4.cjs} +39 -39
  32. package/dist/{chunk-4JEZZJ6S.cjs.map → chunk-3DS7Z4T4.cjs.map} +1 -1
  33. package/dist/{chunk-BLZDPRXT.cjs → chunk-3GH67G4Z.cjs} +5 -5
  34. package/dist/{chunk-BLZDPRXT.cjs.map → chunk-3GH67G4Z.cjs.map} +1 -1
  35. package/dist/{chunk-K3PPLHY7.js → chunk-3RWZPJKV.js} +24 -9
  36. package/dist/chunk-3RWZPJKV.js.map +1 -0
  37. package/dist/{chunk-QWEUYILR.js → chunk-3SLPDKHQ.js} +4 -4
  38. package/dist/chunk-3SLPDKHQ.js.map +1 -0
  39. package/dist/{chunk-5A6FNS6N.cjs → chunk-3XXGYU62.cjs} +11 -11
  40. package/dist/{chunk-5A6FNS6N.cjs.map → chunk-3XXGYU62.cjs.map} +1 -1
  41. package/dist/{chunk-JBTOYDMN.js → chunk-44TA22QT.js} +58 -11
  42. package/dist/chunk-44TA22QT.js.map +1 -0
  43. package/dist/{chunk-UES55P7G.js → chunk-552KNYQJ.js} +2652 -409
  44. package/dist/chunk-552KNYQJ.js.map +1 -0
  45. package/dist/{chunk-VPYHY3LJ.cjs → chunk-5KEQ74YU.cjs} +14 -14
  46. package/dist/chunk-5KEQ74YU.cjs.map +1 -0
  47. package/dist/{chunk-Q5TEXCTB.js → chunk-5KPNWT6X.js} +3 -3
  48. package/dist/{chunk-Q5TEXCTB.js.map → chunk-5KPNWT6X.js.map} +1 -1
  49. package/dist/{chunk-MP2JQKSG.js → chunk-5PNMDJ7J.js} +108 -5
  50. package/dist/chunk-5PNMDJ7J.js.map +1 -0
  51. package/dist/{chunk-AW4A3P5A.cjs → chunk-5VQM6VWP.cjs} +2 -2
  52. package/dist/{chunk-AW4A3P5A.cjs.map → chunk-5VQM6VWP.cjs.map} +1 -1
  53. package/dist/{chunk-A3W7QG7K.js → chunk-5WWVZMVX.js} +2 -2
  54. package/dist/{chunk-A3W7QG7K.js.map → chunk-5WWVZMVX.js.map} +1 -1
  55. package/dist/{chunk-FACJJWGF.cjs → chunk-6EEO3OVU.cjs} +7 -7
  56. package/dist/{chunk-FACJJWGF.cjs.map → chunk-6EEO3OVU.cjs.map} +1 -1
  57. package/dist/{chunk-LGEALRC6.cjs → chunk-BVRDGYXQ.cjs} +113 -4
  58. package/dist/chunk-BVRDGYXQ.cjs.map +1 -0
  59. package/dist/{chunk-GSQ6NPH6.cjs → chunk-DEFQM7JU.cjs} +7 -7
  60. package/dist/{chunk-GSQ6NPH6.cjs.map → chunk-DEFQM7JU.cjs.map} +1 -1
  61. package/dist/{chunk-EM3CWPND.cjs → chunk-DIQ3AV5W.cjs} +574 -268
  62. package/dist/chunk-DIQ3AV5W.cjs.map +1 -0
  63. package/dist/{chunk-FPIZFYHE.js → chunk-EXU3RASM.js} +74 -15
  64. package/dist/chunk-EXU3RASM.js.map +1 -0
  65. package/dist/{chunk-H7YNSMA7.cjs → chunk-HAHNRASW.cjs} +20 -2
  66. package/dist/chunk-HAHNRASW.cjs.map +1 -0
  67. package/dist/{chunk-FL472AMI.cjs → chunk-IP3CGGPA.cjs} +8 -8
  68. package/dist/{chunk-FL472AMI.cjs.map → chunk-IP3CGGPA.cjs.map} +1 -1
  69. package/dist/{chunk-4TOD4LBZ.cjs → chunk-IPSVVTWT.cjs} +94 -35
  70. package/dist/chunk-IPSVVTWT.cjs.map +1 -0
  71. package/dist/{chunk-RGP33ZJ5.js → chunk-IXGYRP2P.js} +184 -92
  72. package/dist/chunk-IXGYRP2P.js.map +1 -0
  73. package/dist/{chunk-3TWF3SH4.js → chunk-J4IUSYFF.js} +251 -121
  74. package/dist/chunk-J4IUSYFF.js.map +1 -0
  75. package/dist/{chunk-O7UN7OXA.cjs → chunk-JBNOGRKW.cjs} +318 -188
  76. package/dist/chunk-JBNOGRKW.cjs.map +1 -0
  77. package/dist/{chunk-WDBCBWC4.js → chunk-K3Q3LJY3.js} +520 -221
  78. package/dist/chunk-K3Q3LJY3.js.map +1 -0
  79. package/dist/{chunk-OR5ZA5WP.js → chunk-K5UBVGJN.js} +4 -4
  80. package/dist/{chunk-OR5ZA5WP.js.map → chunk-K5UBVGJN.js.map} +1 -1
  81. package/dist/{chunk-UZV3AZKF.js → chunk-KQJ54OG5.js} +3 -3
  82. package/dist/{chunk-UZV3AZKF.js.map → chunk-KQJ54OG5.js.map} +1 -1
  83. package/dist/{chunk-QOTI55H7.js → chunk-L7HQ2OSI.js} +4 -4
  84. package/dist/{chunk-QOTI55H7.js.map → chunk-L7HQ2OSI.js.map} +1 -1
  85. package/dist/{chunk-55MZDDZV.js → chunk-M6VES7V6.js} +4 -4
  86. package/dist/{chunk-55MZDDZV.js.map → chunk-M6VES7V6.js.map} +1 -1
  87. package/dist/{chunk-J7WFGBJR.cjs → chunk-NBP2WTJW.cjs} +3249 -1006
  88. package/dist/chunk-NBP2WTJW.cjs.map +1 -0
  89. package/dist/{chunk-CK5BVQOW.cjs → chunk-NUIG3BCV.cjs} +100 -85
  90. package/dist/chunk-NUIG3BCV.cjs.map +1 -0
  91. package/dist/{chunk-EMF62TZG.cjs → chunk-NXQFATMF.cjs} +269 -177
  92. package/dist/chunk-NXQFATMF.cjs.map +1 -0
  93. package/dist/{chunk-KBGQIPRQ.js → chunk-PC2UBAUB.js} +20 -3
  94. package/dist/chunk-PC2UBAUB.js.map +1 -0
  95. package/dist/chunk-QARBLBXT.cjs +219 -0
  96. package/dist/chunk-QARBLBXT.cjs.map +1 -0
  97. package/dist/{chunk-6FR2MMS6.js → chunk-QERAIDRJ.js} +45 -4
  98. package/dist/chunk-QERAIDRJ.js.map +1 -0
  99. package/dist/chunk-QSXTWU4U.cjs +12 -0
  100. package/dist/{chunk-GUR2FNB5.cjs.map → chunk-QSXTWU4U.cjs.map} +1 -1
  101. package/dist/{chunk-QNKSQTKQ.js → chunk-RCVLFTCT.js} +49 -19
  102. package/dist/chunk-RCVLFTCT.js.map +1 -0
  103. package/dist/{chunk-VSPCZAXF.js → chunk-RRSNR3TP.js} +3 -3
  104. package/dist/{chunk-VSPCZAXF.js.map → chunk-RRSNR3TP.js.map} +1 -1
  105. package/dist/{chunk-WJT7OU7E.cjs → chunk-TGOMW4MB.cjs} +129 -98
  106. package/dist/chunk-TGOMW4MB.cjs.map +1 -0
  107. package/dist/{chunk-KMI7LYAF.js → chunk-TKT2XHED.js} +8 -8
  108. package/dist/{chunk-KMI7LYAF.js.map → chunk-TKT2XHED.js.map} +1 -1
  109. package/dist/{chunk-RGPG7EOJ.js → chunk-TPF25IG6.js} +3 -3
  110. package/dist/{chunk-RGPG7EOJ.js.map → chunk-TPF25IG6.js.map} +1 -1
  111. package/dist/{chunk-5SEGFTZX.js → chunk-UGZUAYAR.js} +3 -3
  112. package/dist/{chunk-5SEGFTZX.js.map → chunk-UGZUAYAR.js.map} +1 -1
  113. package/dist/{chunk-VHMPG6UL.js → chunk-UXLSNRYL.js} +11 -105
  114. package/dist/chunk-UXLSNRYL.js.map +1 -0
  115. package/dist/{chunk-FRV5Q5IO.js → chunk-UYQMBBWJ.js} +3 -3
  116. package/dist/{chunk-FRV5Q5IO.js.map → chunk-UYQMBBWJ.js.map} +1 -1
  117. package/dist/{chunk-UUT7UN2N.cjs → chunk-VGDDG3XJ.cjs} +7 -7
  118. package/dist/{chunk-UUT7UN2N.cjs.map → chunk-VGDDG3XJ.cjs.map} +1 -1
  119. package/dist/{chunk-CZQ3FO2Q.js → chunk-VKGKDOKR.js} +56 -21
  120. package/dist/chunk-VKGKDOKR.js.map +1 -0
  121. package/dist/{chunk-CTTSDJLY.cjs → chunk-VL2V6PEN.cjs} +11 -11
  122. package/dist/{chunk-CTTSDJLY.cjs.map → chunk-VL2V6PEN.cjs.map} +1 -1
  123. package/dist/{chunk-XOX22WHL.cjs → chunk-WOJMKB3N.cjs} +125 -90
  124. package/dist/chunk-WOJMKB3N.cjs.map +1 -0
  125. package/dist/{chunk-UMDVATGY.cjs → chunk-WWFHO375.cjs} +6 -6
  126. package/dist/{chunk-UMDVATGY.cjs.map → chunk-WWFHO375.cjs.map} +1 -1
  127. package/dist/{chunk-I7ZUZKFD.js → chunk-XGPUQ56G.js} +3 -3
  128. package/dist/{chunk-I7ZUZKFD.js.map → chunk-XGPUQ56G.js.map} +1 -1
  129. package/dist/{chunk-D7ZEOS56.cjs → chunk-XGU7DZDA.cjs} +74 -14
  130. package/dist/chunk-XGU7DZDA.cjs.map +1 -0
  131. package/dist/{chunk-S55OV6PW.cjs → chunk-XPCZADMT.cjs} +54 -11
  132. package/dist/chunk-XPCZADMT.cjs.map +1 -0
  133. package/dist/{chunk-T2WUSPDA.js → chunk-XWV4JT34.js} +3 -3
  134. package/dist/{chunk-T2WUSPDA.js.map → chunk-XWV4JT34.js.map} +1 -1
  135. package/dist/chunk-YTSN73ND.js +212 -0
  136. package/dist/chunk-YTSN73ND.js.map +1 -0
  137. package/dist/{ddl-CZz7a0E8.d.cts → ddl-BD-ZfKeX.d.cts} +3 -3
  138. package/dist/{ddl-C2rZ-Aen.d.ts → ddl-CS0KUVwB.d.ts} +3 -3
  139. package/dist/{extension-types-ixRpJVUp.d.cts → extension-types-BM2ACfYO.d.cts} +6 -1
  140. package/dist/{extension-types-ixRpJVUp.d.ts → extension-types-BM2ACfYO.d.ts} +6 -1
  141. package/dist/{fulltext-strategy-CDkSSYhq.d.cts → fulltext-strategy-C27vXaeB.d.cts} +127 -8
  142. package/dist/{fulltext-strategy-B57yvC5Q.d.ts → fulltext-strategy-DebsMrqg.d.ts} +127 -8
  143. package/dist/graph-extension/index.cjs +19 -19
  144. package/dist/graph-extension/index.d.cts +4 -4
  145. package/dist/graph-extension/index.d.ts +4 -4
  146. package/dist/graph-extension/index.js +4 -4
  147. package/dist/graph-merge/index.cjs +88 -88
  148. package/dist/graph-merge/index.cjs.map +1 -1
  149. package/dist/graph-merge/index.d.cts +6 -6
  150. package/dist/graph-merge/index.d.ts +6 -6
  151. package/dist/graph-merge/index.js +19 -19
  152. package/dist/graph-merge/index.js.map +1 -1
  153. package/dist/{index-Bks_3HHK.d.cts → index-BPM59vsg.d.cts} +119 -7
  154. package/dist/{index-6CSUAq-x.d.ts → index-BeacbPoe.d.ts} +119 -7
  155. package/dist/index.cjs +230 -185
  156. package/dist/index.cjs.map +1 -1
  157. package/dist/index.d.cts +11 -11
  158. package/dist/index.d.ts +11 -11
  159. package/dist/index.js +53 -40
  160. package/dist/index.js.map +1 -1
  161. package/dist/indexes/index.cjs +30 -21
  162. package/dist/indexes/index.d.cts +5 -5
  163. package/dist/indexes/index.d.ts +5 -5
  164. package/dist/indexes/index.js +8 -7
  165. package/dist/interchange/index.cjs +208 -30
  166. package/dist/interchange/index.cjs.map +1 -1
  167. package/dist/interchange/index.d.cts +25 -8
  168. package/dist/interchange/index.d.ts +25 -8
  169. package/dist/interchange/index.js +191 -11
  170. package/dist/interchange/index.js.map +1 -1
  171. package/dist/{manager-DwQi8eV0.d.cts → manager-D00_NXvT.d.ts} +11 -2
  172. package/dist/{manager-ejUCB52T.d.ts → manager-nU1vTJLy.d.cts} +11 -2
  173. package/dist/{materialize-indexes-B4e9360H.d.cts → materialize-indexes-CuR3gAZb.d.cts} +8 -2
  174. package/dist/{materialize-indexes-te9TPMMJ.d.ts → materialize-indexes-zK2J_flP.d.ts} +8 -2
  175. package/dist/{postgres-DQ5R6rzV.d.ts → postgres-IaBA4RgE.d.ts} +1 -1
  176. package/dist/{postgres-CfEqsRMp.d.cts → postgres-YrYmPlaU.d.cts} +1 -1
  177. package/dist/profiler/index.cjs +3 -3
  178. package/dist/profiler/index.d.cts +7 -7
  179. package/dist/profiler/index.d.ts +7 -7
  180. package/dist/profiler/index.js +2 -2
  181. package/dist/provenance/index.cjs +32 -33
  182. package/dist/provenance/index.cjs.map +1 -1
  183. package/dist/provenance/index.d.cts +5 -5
  184. package/dist/provenance/index.d.ts +5 -5
  185. package/dist/provenance/index.js +5 -6
  186. package/dist/provenance/index.js.map +1 -1
  187. package/dist/schema/index.cjs +35 -35
  188. package/dist/schema/index.d.cts +7 -7
  189. package/dist/schema/index.d.ts +7 -7
  190. package/dist/schema/index.js +11 -11
  191. package/dist/{sqlite-DvQndLky.d.ts → sqlite-BUOHPx7p.d.ts} +1 -1
  192. package/dist/{sqlite-D7zBinNR.d.cts → sqlite-Pp8O19WE.d.cts} +1 -1
  193. package/dist/{store-view-CKbHFRnn.d.ts → store-view-ANL6mTph.d.ts} +457 -57
  194. package/dist/{store-view-BSpqvAxK.d.cts → store-view-X96Rd00T.d.cts} +457 -57
  195. package/dist/{types-Bv9DcS_E.d.cts → types-r-Zyq2Hz.d.cts} +2 -2
  196. package/dist/{types-CoxuV7EJ.d.ts → types-wsvuBgPK.d.ts} +2 -2
  197. package/dist/where-CJOpJj3a.d.ts +36 -0
  198. package/dist/where-K3uBP33l.d.cts +36 -0
  199. package/package.json +1 -1
  200. package/dist/chunk-3TWF3SH4.js.map +0 -1
  201. package/dist/chunk-4TOD4LBZ.cjs.map +0 -1
  202. package/dist/chunk-6FR2MMS6.js.map +0 -1
  203. package/dist/chunk-CK5BVQOW.cjs.map +0 -1
  204. package/dist/chunk-CZQ3FO2Q.js.map +0 -1
  205. package/dist/chunk-D7ZEOS56.cjs.map +0 -1
  206. package/dist/chunk-EM3CWPND.cjs.map +0 -1
  207. package/dist/chunk-EMF62TZG.cjs.map +0 -1
  208. package/dist/chunk-EOLERH7Y.cjs +0 -40
  209. package/dist/chunk-EOLERH7Y.cjs.map +0 -1
  210. package/dist/chunk-FN35DLCN.cjs.map +0 -1
  211. package/dist/chunk-FPIZFYHE.js.map +0 -1
  212. package/dist/chunk-GUR2FNB5.cjs +0 -12
  213. package/dist/chunk-H7YNSMA7.cjs.map +0 -1
  214. package/dist/chunk-J7WFGBJR.cjs.map +0 -1
  215. package/dist/chunk-JBTOYDMN.js.map +0 -1
  216. package/dist/chunk-K3PPLHY7.js.map +0 -1
  217. package/dist/chunk-KBGQIPRQ.js.map +0 -1
  218. package/dist/chunk-LGEALRC6.cjs.map +0 -1
  219. package/dist/chunk-MP2JQKSG.js.map +0 -1
  220. package/dist/chunk-O7UN7OXA.cjs.map +0 -1
  221. package/dist/chunk-QNKSQTKQ.js.map +0 -1
  222. package/dist/chunk-QWEUYILR.js.map +0 -1
  223. package/dist/chunk-RGP33ZJ5.js.map +0 -1
  224. package/dist/chunk-S55OV6PW.cjs.map +0 -1
  225. package/dist/chunk-UES55P7G.js.map +0 -1
  226. package/dist/chunk-UT3S4SEN.js +0 -31
  227. package/dist/chunk-UT3S4SEN.js.map +0 -1
  228. package/dist/chunk-VHMPG6UL.js.map +0 -1
  229. package/dist/chunk-VPYHY3LJ.cjs.map +0 -1
  230. package/dist/chunk-WDBCBWC4.js.map +0 -1
  231. package/dist/chunk-WJT7OU7E.cjs.map +0 -1
  232. package/dist/chunk-XOX22WHL.cjs.map +0 -1
  233. package/dist/where-7RKFOsXt.d.ts +0 -11
  234. package/dist/where-DdMIXOtH.d.cts +0 -11
@@ -1,23 +1,23 @@
1
- import { createEdgeRowMapper, createNodeRowMapper, POSTGRES_ROW_MAPPER_CONFIG, SQLITE_ROW_MAPPER_CONFIG, quoteIdentifier, EMPTY_REQUIRED_COLUMNS, addRequiredColumn, mapSelectiveSystemFieldToColumn, markFieldRefAsRequired, findSelectivePropsFieldForFieldRef, markSelectiveFieldAsRequired, NODE_COLUMNS, shouldProjectColumn, isIdFieldRef, isAggregateExpr, EDGE_COLUMNS } from './chunk-55MZDDZV.js';
2
- import { rowToNode, executeNodeBulkFindByIndex, executeNodeBulkFindByConstraint, executeNodeFindByConstraint, executeNodeBulkGetOrCreateByConstraint, executeNodeGetOrCreateByConstraint, executeNodeHardDelete, executeNodeDelete, executeNodeUpsertUpdate, executeNodeUpdate, executeNodeCreateNoReturnBatch, executeNodeCreateBatch, executeNodeCreate, rowToEdge, runInWriteTransaction, checkCardinalityConstraint, runHookedWriteOperation, rowToNodeMeta, rowToEdgeMeta } from './chunk-QNKSQTKQ.js';
3
- import { loadAndMergeGraphExtensionDocument, ensureSchema, loadAndVerifyGraph, buildKindRegistry, mergeGraphExtension, commitNewSchemaVersion, applyDeprecatedKinds, loadActiveSchemaWithBootstrap, parseSerializedSchema, extensionKindNames, serializeSchemaProperties, buildGraphExtensionOntologyKeySet, compileTimeOntologyKey, getTypeName, serializeIndexDeclaration } from './chunk-K3PPLHY7.js';
4
- import { sortedReplacer, canonicalEqual } from './chunk-UR7W5YQK.js';
5
- import { isKnownKind, validateEdgeProps } from './chunk-QWEUYILR.js';
6
- import { generateIndexDDL } from './chunk-UZV3AZKF.js';
7
- import { NODE_META_KEYS, EDGE_META_KEYS } from './chunk-OR5ZA5WP.js';
1
+ import { createEdgeRowMapper, createNodeRowMapper, POSTGRES_ROW_MAPPER_CONFIG, SQLITE_ROW_MAPPER_CONFIG, quoteIdentifier, EMPTY_REQUIRED_COLUMNS, addRequiredColumn, mapSelectiveSystemFieldToColumn, markFieldRefAsRequired, findSelectivePropsFieldForFieldRef, markSelectiveFieldAsRequired, NODE_COLUMNS, shouldProjectColumn, isIdFieldRef, isAggregateExpr, EDGE_COLUMNS } from './chunk-M6VES7V6.js';
2
+ import { rowToNode, executeNodeBulkFindByIndex, executeNodeBulkFindByConstraint, executeNodeFindByConstraint, executeNodeBulkGetOrCreateByConstraint, executeNodeGetOrCreateByConstraint, executeNodeHardDelete, executeNodeDelete, nodeUpsertDirtyCheck, executeNodeUpsertUpdate, executeNodeUpdate, executeNodeCreateNoReturnBatch, executeNodeCreateBatch, executeNodeCreate, rowToEdge, runInWriteTransaction, checkCardinalityConstraint, runHookedWriteOperation, rowToNodeMeta, rowToEdgeMeta } from './chunk-RCVLFTCT.js';
3
+ import { loadAndMergeGraphExtensionDocument, ensureSchema, loadAndVerifyGraph, buildKindRegistry, mergeGraphExtension, commitNewSchemaVersion, applyDeprecatedKinds, loadActiveSchemaWithBootstrap, parseSerializedSchema, extensionKindNames, serializeSchemaProperties, buildGraphExtensionOntologyKeySet, compileTimeOntologyKey, getTypeName, serializeIndexDeclaration } from './chunk-3RWZPJKV.js';
4
+ import { isKnownKind, validateEdgeProps } from './chunk-3SLPDKHQ.js';
5
+ import { canonicalEqual, sortedReplacer } from './chunk-UR7W5YQK.js';
6
+ import { generateIndexDDL } from './chunk-KQJ54OG5.js';
7
+ import { SYSTEM_INDEX_DECLARATIONS, resolveSystemIndexTableName, systemIndexName, resolveSystemIndexNames, generateSystemIndexDDL } from './chunk-YTSN73ND.js';
8
+ import { NODE_META_KEYS, EDGE_META_KEYS } from './chunk-K5UBVGJN.js';
8
9
  import { sha256Hex, fnv1aBase36 } from './chunk-W63OVYY4.js';
9
- import { getDialect, assertVectorMinScore, vectorScoreExpression, vectorMinScoreCondition } from './chunk-FPIZFYHE.js';
10
- import { createSchemaIntrospector } from './chunk-5SEGFTZX.js';
11
- import { IncompatibleChangeError, RemoveCompileTimeKindError, KindHasReferentsError } from './chunk-QOTI55H7.js';
12
- import { validateProjectionField } from './chunk-RGPG7EOJ.js';
10
+ import { getDialect, assertVectorMinScore, vectorScoreExpression, vectorMinScoreCondition } from './chunk-EXU3RASM.js';
11
+ import { createSchemaIntrospector } from './chunk-UGZUAYAR.js';
12
+ import { IncompatibleChangeError, RemoveCompileTimeKindError, KindHasReferentsError } from './chunk-L7HQ2OSI.js';
13
+ import { validateProjectionField } from './chunk-TPF25IG6.js';
13
14
  import { compareStrings, compareCodePoints } from './chunk-BYPOGQWK.js';
14
- import { resolveReadCoordinate, withRecordedCoordinate, describeCoordinate, coordinateContext, DEFAULT_SQL_SCHEMA, requireSqlSchema, recordedReadSchemaFor, assertRevisionTrackableBackend, createSqlSchema, requireExternalRecordedReadSource, createRecordedReadBinding, createRecordedBackend, readRecordedClock, asRecordedInstant, ensureRevisionOrigin, withRecordedFlushObserver, recordedCaptureRequiresCallbackTransactionError, assertRecordedCaptureTransactionIsolation, createRecordedTransactionScope, lockRecordedGraphWrite, createHistoryUnsafeSqlRef, createRevisionTrackingUnsafeSqlRef, generateId, vectorSlotKey, computeFulltextContent, advanceRevisionClock, recordedReadSqlSchema, requireRecordedReadBinding, runInsertBatch, edgeInsertDispatch, runInsertBatchReturning, getEmbeddingFields, getNodeRowsByIds, getEdgeRowsByIds, assertValidRecordedInstant, validateEdgeEndpoints, recordedBindParamBudget, closeRecordedHardDeletedKind, ALIAS_CTE_PREFIX, EMBEDDINGS_CTE_ALIAS, FULLTEXT_CTE_ALIAS, HYBRID_CANDIDATES_CTE_ALIAS } from './chunk-CZQ3FO2Q.js';
15
- import { markAnnIndexScan, asCompiledSelectSql, isRawExecutable, annIndexScanTypes, asCompiledRowsSql, markForceCustomPlan, asCompiledStatementSql } from './chunk-UT3S4SEN.js';
16
- import { isParameterRef, fieldRef, buildFieldBuilderForTypeInfo, stringField, createFulltextAccessor, CURRENT_READ_INSTANT_PLACEHOLDER, withPinnedReadInstant, runOptionallyInTransaction, baseField, objectField, arrayField, dateField, numberField, rowPropsToObject, createBackendOverlay, compileKindFilter, currentReadInstant, compileTemporalFilter, sqlValueList, chunk, buildPredicateIndex, compileFieldValue, extractVectorSimilarityPredicates, extractFulltextMatchPredicates, currentReadInstantFor, extractTemporalOptions, compilePredicateExpression, getPredicatesForAlias, getNodeKindsForAlias, getHybridTargetAlias, compilePredicateClauses } from './chunk-JBTOYDMN.js';
17
- import { withRecordedRelationsPrecondition, nowIso, validateOptionalCanonicalIsoDate, isMissingTableError } from './chunk-I7ZUZKFD.js';
15
+ import { resolveReadCoordinate, withRecordedCoordinate, describeCoordinate, coordinateContext, DEFAULT_SQL_SCHEMA, requireSqlSchema, recordedReadSchemaFor, assertRevisionTrackableBackend, createSqlSchema, requireExternalRecordedReadSource, createRecordedReadBinding, createRecordedBackend, readRecordedClock, asRecordedInstant, ensureRevisionOrigin, withRecordedFlushObserver, recordedCaptureRequiresCallbackTransactionError, assertRecordedCaptureTransactionIsolation, createRecordedTransactionScope, lockRecordedGraphWrite, createHistoryUnsafeSqlRef, createRevisionTrackingUnsafeSqlRef, generateId, vectorSlotKey, computeFulltextContent, advanceRevisionClock, recordedReadSqlSchema, requireRecordedReadBinding, runInsertBatch, edgeInsertDispatch, runInsertBatchReturning, getEmbeddingFields, getNodeRowsByIds, getEdgeRowsByIds, validateEdgeEndpoints, recordedBindParamBudget, assertValidRecordedInstant, closeRecordedHardDeletedKind, ALIAS_CTE_PREFIX, EMBEDDINGS_CTE_ALIAS, FULLTEXT_CTE_ALIAS, HYBRID_CANDIDATES_CTE_ALIAS } from './chunk-VKGKDOKR.js';
16
+ import { isParameterRef, fieldRef, buildFieldBuilderForTypeInfo, stringField, createFulltextAccessor, CURRENT_READ_INSTANT_PLACEHOLDER, withPinnedReadInstant, markAnnIndexScan, asCompiledSelectSql, isRawExecutable, runOptionallyInTransaction, baseField, objectField, arrayField, dateField, numberField, annIndexScanTypes, rowPropsToObject, asCompiledRowsSql, sqlValueList, createBackendOverlay, compileKindFilter, currentReadInstant, compileTemporalFilter, markForceCustomPlan, chunk, INTERNAL_TEMPORARY_WRITES, buildPredicateIndex, compileFieldValue, asCompiledTemporaryStatementSql, extractVectorSimilarityPredicates, extractFulltextMatchPredicates, currentReadInstantFor, extractTemporalOptions, compilePredicateExpression, getPredicatesForAlias, getNodeKindsForAlias, getHybridTargetAlias, asCompiledStatementSql, compilePredicateClauses, isPresent } from './chunk-44TA22QT.js';
17
+ import { withRecordedRelationsPrecondition, nowIso, validateOptionalCanonicalIsoDate, isMissingTableError } from './chunk-XGPUQ56G.js';
18
18
  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-OHMP6N2J.js';
19
- import { getSearchableFields, resolveEmbeddingFields, resolveDeclaredFulltextLanguage } from './chunk-KBGQIPRQ.js';
20
- import { ConfigurationError, UnsupportedPredicateError, ValidationError, KindNotFoundError, EndpointError, EagerMaterializationError, CompilerInvariantError, CardinalityError, EdgeNotFoundError, DatabaseOperationError, EndpointNotFoundError } from './chunk-MP2JQKSG.js';
19
+ import { getSearchableFields, resolveEmbeddingFields, resolveGraphVectorSlots, resolveDeclaredFulltextLanguage } from './chunk-PC2UBAUB.js';
20
+ import { ConfigurationError, UnsupportedPredicateError, ValidationError, KindNotFoundError, EndpointError, EagerMaterializationError, CompilerInvariantError, CardinalityError, EdgeNotFoundError, DatabaseOperationError, UnsupportedBackendCapabilityError, GraphAlgorithmConvergenceError, EndpointNotFoundError, InvalidEdgeWeightError } from './chunk-5PNMDJ7J.js';
21
21
  import { jsonPointer, parseJsonPointer, resolveJsonPointer } from './chunk-G6Q5UM7U.js';
22
22
  import { Placeholder, sql } from 'drizzle-orm';
23
23
 
@@ -394,7 +394,8 @@ function recordedSearch(coordinate) {
394
394
  var CoordinatePinnedView = class {
395
395
  store;
396
396
  coordinate;
397
- #algorithms;
397
+ #algorithmFacade;
398
+ #internalAlgorithms;
398
399
  constructor(store, coordinate) {
399
400
  this.store = store;
400
401
  this.coordinate = coordinate;
@@ -415,9 +416,24 @@ var CoordinatePinnedView = class {
415
416
  query() {
416
417
  return this.store.sealedQuery(this.coordinate);
417
418
  }
418
- algorithms() {
419
- this.#algorithms ??= this.store.algorithmsAtCoordinate(this.coordinate);
420
- return this.#algorithms;
419
+ internalAlgorithms() {
420
+ this.#internalAlgorithms ??= this.store.algorithmsAtCoordinate(
421
+ this.coordinate
422
+ );
423
+ return this.#internalAlgorithms;
424
+ }
425
+ /** Graph algorithms pinned to this view's immutable temporal coordinate. */
426
+ get algorithms() {
427
+ this.#algorithmFacade ??= Object.freeze({
428
+ shortestPath: (from, to, options) => this.shortestPath(from, to, options),
429
+ weightedShortestPath: (from, to, options) => this.weightedShortestPath(from, to, options),
430
+ reachable: (from, options) => this.reachable(from, options),
431
+ canReach: (from, to, options) => this.canReach(from, to, options),
432
+ neighbors: (node, options) => this.neighbors(node, options),
433
+ degree: (node, options) => this.degree(node, options),
434
+ weaklyConnectedComponents: (options) => this.weaklyConnectedComponents(options)
435
+ });
436
+ return this.#algorithmFacade;
421
437
  }
422
438
  /** Extracts a subgraph at this view's pinned coordinate. */
423
439
  subgraph(rootId, options) {
@@ -429,35 +445,49 @@ var CoordinatePinnedView = class {
429
445
  }
430
446
  /** Shortest path between two nodes at this view's pinned coordinate. */
431
447
  shortestPath(from, to, options) {
432
- return this.algorithms().shortestPath(from, to, {
448
+ return this.internalAlgorithms().shortestPath(from, to, {
449
+ ...options,
450
+ ...withCoordinate(this.coordinate)
451
+ });
452
+ }
453
+ /** Minimum-total-weight path at this view's pinned coordinate. */
454
+ weightedShortestPath(from, to, options) {
455
+ return this.internalAlgorithms().weightedShortestPath(from, to, {
433
456
  ...options,
434
457
  ...withCoordinate(this.coordinate)
435
458
  });
436
459
  }
437
460
  /** Nodes reachable from `from` at this view's pinned coordinate. */
438
461
  reachable(from, options) {
439
- return this.algorithms().reachable(from, {
462
+ return this.internalAlgorithms().reachable(from, {
440
463
  ...options,
441
464
  ...withCoordinate(this.coordinate)
442
465
  });
443
466
  }
444
467
  /** Whether `to` is reachable from `from` at this view's pinned coordinate. */
445
468
  canReach(from, to, options) {
446
- return this.algorithms().canReach(from, to, {
469
+ return this.internalAlgorithms().canReach(from, to, {
447
470
  ...options,
448
471
  ...withCoordinate(this.coordinate)
449
472
  });
450
473
  }
451
474
  /** The k-hop neighborhood of `node` at this view's pinned coordinate. */
452
475
  neighbors(node, options) {
453
- return this.algorithms().neighbors(node, {
476
+ return this.internalAlgorithms().neighbors(node, {
454
477
  ...options,
455
478
  ...withCoordinate(this.coordinate)
456
479
  });
457
480
  }
458
481
  /** Counts active edges incident to `node` at this view's pinned coordinate. */
459
482
  degree(node, options) {
460
- return this.algorithms().degree(node, {
483
+ return this.internalAlgorithms().degree(node, {
484
+ ...options,
485
+ ...withCoordinate(this.coordinate)
486
+ });
487
+ }
488
+ /** Exact WCC memberships at this view's pinned coordinate. */
489
+ weaklyConnectedComponents(options) {
490
+ return this.internalAlgorithms().weaklyConnectedComponents({
461
491
  ...options,
462
492
  ...withCoordinate(this.coordinate)
463
493
  });
@@ -1457,6 +1487,97 @@ function buildStandardOrderBy(input) {
1457
1487
  }
1458
1488
  return sql`ORDER BY ${sql.join(parts, sql`, `)}`;
1459
1489
  }
1490
+ var LATE_MAT_TOPK_CTE_ALIAS = "cte_lm_topk";
1491
+ var LATE_MAT_SORT_KEY_PREFIX = "__lm_sk";
1492
+ var LATE_MAT_PHYSICAL_ALIAS_PREFIX = "lm_";
1493
+ function lateMaterializedPhysicalAlias(alias) {
1494
+ return `${LATE_MAT_PHYSICAL_ALIAS_PREFIX}${alias}`;
1495
+ }
1496
+ function lateMaterializedNodeAliases(ast) {
1497
+ return [
1498
+ ast.start.alias,
1499
+ ...ast.traversals.map((traversal) => traversal.nodeAlias)
1500
+ ];
1501
+ }
1502
+ function lateMaterializedProjectedNodeAliases(ast) {
1503
+ const referenced = /* @__PURE__ */ new Set();
1504
+ for (const field2 of ast.selectiveFields ?? []) {
1505
+ referenced.add(field2.alias);
1506
+ }
1507
+ return lateMaterializedNodeAliases(ast).filter(
1508
+ (alias) => referenced.has(alias)
1509
+ );
1510
+ }
1511
+ function buildLateMaterializedTopKCte(input) {
1512
+ const { ast, collapsedTraversalCteAlias, dialect, fromClause } = input;
1513
+ const aliasToCte = buildAliasToCteMap(ast);
1514
+ const cteAliasFor = (alias) => collapsedTraversalCteAlias ?? aliasToCte.get(alias) ?? `${ALIAS_CTE_PREFIX}${alias}`;
1515
+ const columns = [];
1516
+ for (const alias of lateMaterializedNodeAliases(ast)) {
1517
+ const cteAlias = cteAliasFor(alias);
1518
+ columns.push(
1519
+ sql`${qualifyColumn(cteAlias, `${alias}_id`)} AS ${sql.raw(`${alias}_id`)}`,
1520
+ sql`${qualifyColumn(cteAlias, `${alias}_kind`)} AS ${sql.raw(`${alias}_kind`)}`
1521
+ );
1522
+ }
1523
+ for (const [index, orderSpec] of (ast.orderBy ?? []).entries()) {
1524
+ const value = compileOrderFieldValue(
1525
+ ast,
1526
+ orderSpec.field,
1527
+ dialect,
1528
+ cteAliasFor(orderSpec.field.alias)
1529
+ );
1530
+ columns.push(
1531
+ sql`${value} AS ${sql.raw(`${LATE_MAT_SORT_KEY_PREFIX}${index}`)}`
1532
+ );
1533
+ }
1534
+ const innerOrderBy = buildStandardOrderBy({
1535
+ ast,
1536
+ dialect,
1537
+ ...collapsedTraversalCteAlias === void 0 ? {} : { collapsedTraversalCteAlias }
1538
+ });
1539
+ const limitOffset = buildLimitOffsetClause({
1540
+ limit: input.limit,
1541
+ offset: input.offset
1542
+ });
1543
+ const parts = [sql`SELECT ${sql.join(columns, sql`, `)}`, fromClause];
1544
+ if (innerOrderBy !== void 0) parts.push(innerOrderBy);
1545
+ if (limitOffset !== void 0) parts.push(limitOffset);
1546
+ return sql`${sql.raw(LATE_MAT_TOPK_CTE_ALIAS)} AS (${sql.join(parts, sql` `)})`;
1547
+ }
1548
+ function buildLateMaterializedOuterProjection(ast, dialect) {
1549
+ const columns = (ast.selectiveFields ?? []).map((field2) => {
1550
+ const physicalAlias = lateMaterializedPhysicalAlias(field2.alias);
1551
+ if (field2.isSystemField) {
1552
+ const dbColumn = mapSelectiveSystemFieldToColumn(field2.field);
1553
+ return sql`${compileColumnReference(physicalAlias, dbColumn)} AS ${quoteIdentifier(field2.outputName)}`;
1554
+ }
1555
+ const extracted = compileSelectivePropsExtraction(
1556
+ field2,
1557
+ compileColumnReference(physicalAlias, "props"),
1558
+ dialect
1559
+ );
1560
+ return sql`${extracted} AS ${quoteIdentifier(field2.outputName)}`;
1561
+ });
1562
+ return sql.join(columns, sql`, `);
1563
+ }
1564
+ function buildLateMaterializedOuterOrderBy(ast) {
1565
+ const orderBy = ast.orderBy ?? [];
1566
+ if (orderBy.length === 0) return void 0;
1567
+ const topk = sql.raw(LATE_MAT_TOPK_CTE_ALIAS);
1568
+ const parts = [];
1569
+ for (const [index, orderSpec] of orderBy.entries()) {
1570
+ const column = sql`${topk}.${sql.raw(`${LATE_MAT_SORT_KEY_PREFIX}${index}`)}`;
1571
+ const direction = sql.raw(orderSpec.direction.toUpperCase());
1572
+ const nulls = orderSpec.nulls ?? (orderSpec.direction === "asc" ? "last" : "first");
1573
+ const nullsDirection = sql.raw(nulls === "first" ? "DESC" : "ASC");
1574
+ parts.push(
1575
+ sql`(${column} IS NULL) ${nullsDirection}`,
1576
+ sql`${column} ${direction}`
1577
+ );
1578
+ }
1579
+ return sql`ORDER BY ${sql.join(parts, sql`, `)}`;
1580
+ }
1460
1581
  function fieldRefKey(field2) {
1461
1582
  const pointer = field2.jsonPointer ?? "";
1462
1583
  return `${field2.alias}:${field2.path.join(".")}:${pointer}`;
@@ -3611,31 +3732,34 @@ function markPredicateFieldsAsRequired(requiredColumnsByAlias, expression) {
3611
3732
  }
3612
3733
  }
3613
3734
  }
3614
- function collectRequiredColumnsByAlias2(ast) {
3735
+ function collectRequiredColumnsByAlias2(ast, options) {
3736
+ const includeProjection = options?.includeProjection ?? true;
3615
3737
  const requiredColumnsByAlias = /* @__PURE__ */ new Map();
3616
3738
  addRequiredColumn(requiredColumnsByAlias, ast.start.alias, "id");
3617
3739
  for (const traversal of ast.traversals) {
3618
3740
  addRequiredColumn(requiredColumnsByAlias, traversal.nodeAlias, "id");
3619
3741
  }
3620
3742
  const hasSelectiveFields = (ast.selectiveFields?.length ?? 0) > 0;
3621
- if (hasSelectiveFields) {
3622
- for (const field2 of ast.selectiveFields ?? []) {
3623
- if (field2.isSystemField) {
3624
- addRequiredColumn(
3625
- requiredColumnsByAlias,
3626
- field2.alias,
3627
- mapSelectiveSystemFieldToColumn(field2.field)
3628
- );
3743
+ if (includeProjection) {
3744
+ if (hasSelectiveFields) {
3745
+ for (const field2 of ast.selectiveFields ?? []) {
3746
+ if (field2.isSystemField) {
3747
+ addRequiredColumn(
3748
+ requiredColumnsByAlias,
3749
+ field2.alias,
3750
+ mapSelectiveSystemFieldToColumn(field2.field)
3751
+ );
3752
+ }
3629
3753
  }
3630
- }
3631
- } else {
3632
- for (const projectedField of ast.projection.fields) {
3633
- const source = projectedField.source;
3634
- if (source.__type === "field_ref") {
3635
- markFieldRefAsRequired(requiredColumnsByAlias, source);
3636
- } else {
3637
- addRequiredColumn(requiredColumnsByAlias, source.field.alias, "id");
3638
- markFieldRefAsRequired(requiredColumnsByAlias, source.field);
3754
+ } else {
3755
+ for (const projectedField of ast.projection.fields) {
3756
+ const source = projectedField.source;
3757
+ if (source.__type === "field_ref") {
3758
+ markFieldRefAsRequired(requiredColumnsByAlias, source);
3759
+ } else {
3760
+ addRequiredColumn(requiredColumnsByAlias, source.field.alias, "id");
3761
+ markFieldRefAsRequired(requiredColumnsByAlias, source.field);
3762
+ }
3639
3763
  }
3640
3764
  }
3641
3765
  }
@@ -4318,6 +4442,169 @@ function stripLimitOffsetFromPlanNode(node) {
4318
4442
  }
4319
4443
  return node;
4320
4444
  }
4445
+ function buildStandardStartAndTraversalCtes(input) {
4446
+ const {
4447
+ ast,
4448
+ carryForwardPreviousColumns,
4449
+ ctx,
4450
+ graphId,
4451
+ predicateIndex,
4452
+ requiredColumnsByAlias,
4453
+ temporalFilterPass,
4454
+ traversalLimit
4455
+ } = input;
4456
+ const ctes = [
4457
+ buildStandardStartCte({
4458
+ ast,
4459
+ ctx,
4460
+ graphId,
4461
+ predicateIndex,
4462
+ requiredColumnsByAlias,
4463
+ temporalFilterPass
4464
+ })
4465
+ ];
4466
+ for (let index = 0; index < ast.traversals.length; index++) {
4467
+ ctes.push(
4468
+ buildStandardTraversalCte({
4469
+ ast,
4470
+ carryForwardPreviousColumns,
4471
+ ctx,
4472
+ graphId,
4473
+ materializeCte: shouldMaterializeTraversalCte(
4474
+ ctx.dialect,
4475
+ ast.traversals.length,
4476
+ index
4477
+ ),
4478
+ predicateIndex,
4479
+ requiredColumnsByAlias,
4480
+ temporalFilterPass,
4481
+ traversalIndex: index,
4482
+ traversalLimit
4483
+ })
4484
+ );
4485
+ }
4486
+ return ctes;
4487
+ }
4488
+ function selectiveFieldIsIdentity(field2) {
4489
+ if (!field2.isSystemField) {
4490
+ return false;
4491
+ }
4492
+ const column = mapSelectiveSystemFieldToColumn(field2.field);
4493
+ return column === "id" || column === "kind";
4494
+ }
4495
+ function resolveLateMaterializationPlan(ast) {
4496
+ if (ast.recordedAsOf !== void 0) {
4497
+ return void 0;
4498
+ }
4499
+ const selectiveFields = ast.selectiveFields;
4500
+ if (selectiveFields === void 0 || selectiveFields.length === 0) {
4501
+ return void 0;
4502
+ }
4503
+ const orderBy = ast.orderBy;
4504
+ if (orderBy === void 0 || orderBy.length === 0) {
4505
+ return void 0;
4506
+ }
4507
+ if (ast.limit === void 0 || ast.limit <= 0) {
4508
+ return void 0;
4509
+ }
4510
+ if (ast.groupBy !== void 0 || ast.having !== void 0) {
4511
+ return void 0;
4512
+ }
4513
+ if (ast.aggregateOrderBy !== void 0 && ast.aggregateOrderBy.length > 0) {
4514
+ return void 0;
4515
+ }
4516
+ if (ast.traversals.some((traversal) => traversal.optional)) {
4517
+ return void 0;
4518
+ }
4519
+ const nodeAliases = /* @__PURE__ */ new Set([
4520
+ ast.start.alias,
4521
+ ...ast.traversals.map((traversal) => traversal.nodeAlias)
4522
+ ]);
4523
+ for (const field2 of selectiveFields) {
4524
+ if (!nodeAliases.has(field2.alias)) {
4525
+ return void 0;
4526
+ }
4527
+ }
4528
+ for (const orderSpec of orderBy) {
4529
+ if (!nodeAliases.has(orderSpec.field.alias)) {
4530
+ return void 0;
4531
+ }
4532
+ }
4533
+ const orderReferenced = /* @__PURE__ */ new Set();
4534
+ for (const orderSpec of orderBy) {
4535
+ const matched = findSelectivePropsFieldForFieldRef(
4536
+ selectiveFields,
4537
+ orderSpec.field
4538
+ );
4539
+ if (matched !== void 0) {
4540
+ orderReferenced.add(matched);
4541
+ }
4542
+ }
4543
+ const hasDeferredColumn = selectiveFields.some(
4544
+ (field2) => !selectiveFieldIsIdentity(field2) && !orderReferenced.has(field2)
4545
+ );
4546
+ if (!hasDeferredColumn) {
4547
+ return void 0;
4548
+ }
4549
+ const prunedSelectiveFields = selectiveFields.filter(
4550
+ (field2) => field2.isSystemField || orderReferenced.has(field2)
4551
+ );
4552
+ return {
4553
+ leanAst: { ...ast, selectiveFields: prunedSelectiveFields },
4554
+ limit: ast.limit,
4555
+ offset: ast.offset
4556
+ };
4557
+ }
4558
+ function buildLateMaterializedOuterFromClause(ast, graphId, ctx) {
4559
+ const topk = sql.raw(LATE_MAT_TOPK_CTE_ALIAS);
4560
+ const joins = lateMaterializedProjectedNodeAliases(ast).map((alias) => {
4561
+ const physical = sql.raw(lateMaterializedPhysicalAlias(alias));
4562
+ return sql`JOIN ${ctx.schema.nodesTable} ${physical} ON ${physical}.graph_id = ${graphId} AND ${physical}.kind = ${topk}.${sql.raw(`${alias}_kind`)} AND ${physical}.id = ${topk}.${sql.raw(`${alias}_id`)}`;
4563
+ });
4564
+ return joins.length === 0 ? sql`FROM ${topk}` : sql`FROM ${topk} ${sql.join(joins, sql` `)}`;
4565
+ }
4566
+ function compileLateMaterializedQuery(ast, graphId, ctx, logicalPlan, predicateIndex, temporalFilterPass, collapsedTraversalCteAlias, shouldCollapseSelectiveTraversalRowset) {
4567
+ const plan = resolveLateMaterializationPlan(ast);
4568
+ if (plan === void 0) {
4569
+ return void 0;
4570
+ }
4571
+ const { leanAst, limit, offset } = plan;
4572
+ const { dialect } = ctx;
4573
+ const leanRequiredColumns = collectRequiredColumnsByAlias2(leanAst, {
4574
+ includeProjection: false
4575
+ });
4576
+ const ctes = buildStandardStartAndTraversalCtes({
4577
+ ast: leanAst,
4578
+ carryForwardPreviousColumns: shouldCollapseSelectiveTraversalRowset,
4579
+ ctx,
4580
+ graphId,
4581
+ predicateIndex,
4582
+ requiredColumnsByAlias: leanRequiredColumns,
4583
+ temporalFilterPass,
4584
+ traversalLimit: void 0
4585
+ });
4586
+ ctes.push(
4587
+ buildLateMaterializedTopKCte({
4588
+ ast: leanAst,
4589
+ dialect,
4590
+ fromClause: buildStandardFromClause({
4591
+ ast: leanAst,
4592
+ ...collapsedTraversalCteAlias === void 0 ? {} : { collapsedTraversalCteAlias }
4593
+ }),
4594
+ limit,
4595
+ offset,
4596
+ ...collapsedTraversalCteAlias === void 0 ? {} : { collapsedTraversalCteAlias }
4597
+ })
4598
+ );
4599
+ const orderBy = buildLateMaterializedOuterOrderBy(ast);
4600
+ return emitStandardQuerySql({
4601
+ ctes,
4602
+ fromClause: buildLateMaterializedOuterFromClause(ast, graphId, ctx),
4603
+ ...orderBy === void 0 ? {} : { orderBy },
4604
+ logicalPlan: stripLimitOffsetFromPlan(logicalPlan),
4605
+ projection: buildLateMaterializedOuterProjection(ast, dialect)
4606
+ });
4607
+ }
4321
4608
  var STANDARD_QUERY_STRATEGY_HANDLERS = {
4322
4609
  cte_project: compileStandardQueryWithCteStrategy
4323
4610
  };
@@ -4417,38 +4704,30 @@ function compileStandardQueryWithCteStrategy(ast, graphId, ctx) {
4417
4704
  if (fastPathSql) {
4418
4705
  return fastPathSql;
4419
4706
  }
4420
- }
4421
- const ctes = [
4422
- buildStandardStartCte({
4707
+ const lateMaterializedSql = compileLateMaterializedQuery(
4423
4708
  ast,
4424
- ctx,
4425
4709
  graphId,
4710
+ ctx,
4711
+ logicalPlan,
4426
4712
  predicateIndex,
4427
- requiredColumnsByAlias,
4428
- temporalFilterPass
4429
- })
4430
- ];
4431
- for (let index = 0; index < ast.traversals.length; index++) {
4432
- const materializeTraversalCte = shouldMaterializeTraversalCte(
4433
- dialect,
4434
- ast.traversals.length,
4435
- index
4436
- );
4437
- ctes.push(
4438
- buildStandardTraversalCte({
4439
- ast,
4440
- carryForwardPreviousColumns: shouldCollapseSelectiveTraversalRowset,
4441
- ctx,
4442
- graphId,
4443
- materializeCte: materializeTraversalCte,
4444
- predicateIndex,
4445
- requiredColumnsByAlias,
4446
- temporalFilterPass,
4447
- traversalIndex: index,
4448
- traversalLimit: traversalCteLimit
4449
- })
4713
+ temporalFilterPass,
4714
+ collapsedTraversalCteAlias,
4715
+ shouldCollapseSelectiveTraversalRowset
4450
4716
  );
4717
+ if (lateMaterializedSql) {
4718
+ return lateMaterializedSql;
4719
+ }
4451
4720
  }
4721
+ const ctes = buildStandardStartAndTraversalCtes({
4722
+ ast,
4723
+ carryForwardPreviousColumns: shouldCollapseSelectiveTraversalRowset,
4724
+ ctx,
4725
+ graphId,
4726
+ predicateIndex,
4727
+ requiredColumnsByAlias,
4728
+ temporalFilterPass,
4729
+ traversalLimit: traversalCteLimit
4730
+ });
4452
4731
  if (vectorPredicate) {
4453
4732
  const nodeKinds = getNodeKindsForAlias(ast, vectorPredicate.field.alias);
4454
4733
  ctes.push(
@@ -10512,6 +10791,16 @@ function resolveMaxHops(rawMaxHops, fallback, optionName) {
10512
10791
  }
10513
10792
  return value;
10514
10793
  }
10794
+ function resolveMaxIterations(value, fallback, algorithm) {
10795
+ const maxIterations = value ?? fallback;
10796
+ if (!Number.isSafeInteger(maxIterations) || maxIterations < 1) {
10797
+ throw new ConfigurationError(
10798
+ `${algorithm} maxIterations must be a positive safe integer, got ${String(maxIterations)}.`,
10799
+ { maxIterations }
10800
+ );
10801
+ }
10802
+ return maxIterations;
10803
+ }
10515
10804
  function assertEdgeKinds(edges) {
10516
10805
  if (edges.length === 0) {
10517
10806
  throw new ConfigurationError(
@@ -10561,65 +10850,982 @@ function compileDirectionFilter(ctx, direction, nodeId, nodesTable) {
10561
10850
  }
10562
10851
  }
10563
10852
  }
10564
- async function executeReachable(ctx, sourceId, options) {
10565
- assertEdgeKinds(options.edges);
10566
- const maxHops = resolveMaxHops(
10567
- options.maxHops,
10568
- DEFAULT_ALGORITHM_MAX_HOPS,
10569
- "maxHops"
10570
- );
10571
- const cte = buildReachableCte({
10572
- graphId: ctx.graphId,
10573
- sourceId,
10574
- edgeKinds: options.edges,
10575
- maxHops,
10576
- direction: options.direction ?? "out",
10577
- cyclePolicy: options.cyclePolicy ?? "prevent",
10578
- includePath: false,
10579
- ...resolveTemporalOptions(ctx, options),
10580
- dialect: ctx.dialect,
10581
- schema: ctx.schema,
10582
- ...ctx.recordedReadBinding === void 0 ? {} : { recordedReadBinding: ctx.recordedReadBinding }
10583
- });
10584
- const sourceFilter = options.excludeSource === true ? sql` WHERE id != ${sourceId}` : sql``;
10585
- const query = sql`${cte} SELECT id, kind, MIN(depth) AS depth FROM reachable${sourceFilter} GROUP BY id, kind ORDER BY depth ASC, id ASC`;
10586
- const rows = await ctx.backend.execute(
10587
- asCompiledRowsSql(query)
10588
- );
10589
- return rows.map((row) => ({
10590
- id: row.id,
10591
- kind: row.kind,
10592
- depth: Number(row.depth)
10593
- }));
10853
+ var DEFAULT_MAX_BIND_PARAMETERS = 999;
10854
+ var RESERVED_TEMPORAL_BIND_PARAMETERS_PER_BRANCH = 12;
10855
+ var RESERVED_WEIGHT_BIND_PARAMETERS_PER_BRANCH = 4;
10856
+ var WORKING_TABLE_ANALYZE_MINIMUM_ROWS = 16;
10857
+ var WORKING_TABLE_ANALYZE_GROWTH_FACTOR = 4;
10858
+ var ITERATIVE_WORKING_MEMORY_PATTERN = /^(\d+)(kB|MB|GB)$/;
10859
+ var WORKING_MEMORY_KILOBYTES_PER_UNIT = {
10860
+ kB: 1,
10861
+ MB: 1024,
10862
+ GB: 1024 * 1024
10863
+ };
10864
+ var MIN_WORKING_MEMORY_KILOBYTES = 64;
10865
+ var MAX_WORKING_MEMORY_KILOBYTES = 2147483647;
10866
+ function resolveWorkingMemory(workingMemory) {
10867
+ if (workingMemory === void 0) return void 0;
10868
+ const match = ITERATIVE_WORKING_MEMORY_PATTERN.exec(workingMemory);
10869
+ if (match === null) {
10870
+ throw new ConfigurationError(
10871
+ `Iterative graph operation workingMemory must be digits followed by kB, MB, or GB (for example "64MB"), got ${JSON.stringify(workingMemory)}.`,
10872
+ { workingMemory }
10873
+ );
10874
+ }
10875
+ const kilobytes = Number(match[1]) * (WORKING_MEMORY_KILOBYTES_PER_UNIT[match[2] ?? ""] ?? 0);
10876
+ if (kilobytes < MIN_WORKING_MEMORY_KILOBYTES || kilobytes > MAX_WORKING_MEMORY_KILOBYTES) {
10877
+ throw new ConfigurationError(
10878
+ `Iterative graph operation workingMemory must be between ${MIN_WORKING_MEMORY_KILOBYTES}kB and ${MAX_WORKING_MEMORY_KILOBYTES}kB, got ${JSON.stringify(workingMemory)}.`,
10879
+ { workingMemory, kilobytes }
10880
+ );
10881
+ }
10882
+ return workingMemory;
10594
10883
  }
10595
- async function executeCanReach(ctx, sourceId, targetId, options) {
10596
- assertEdgeKinds(options.edges);
10597
- const maxHops = resolveMaxHops(
10598
- options.maxHops,
10599
- DEFAULT_ALGORITHM_MAX_HOPS,
10600
- "maxHops"
10601
- );
10602
- const cte = buildReachableCte({
10603
- graphId: ctx.graphId,
10604
- sourceId,
10605
- edgeKinds: options.edges,
10606
- maxHops,
10607
- direction: options.direction ?? "out",
10608
- cyclePolicy: options.cyclePolicy ?? "prevent",
10609
- includePath: false,
10610
- ...resolveTemporalOptions(ctx, options),
10611
- dialect: ctx.dialect,
10612
- schema: ctx.schema,
10613
- ...ctx.recordedReadBinding === void 0 ? {} : { recordedReadBinding: ctx.recordedReadBinding }
10614
- });
10615
- const query = sql`${cte} SELECT 1 AS hit FROM reachable WHERE id = ${targetId} LIMIT 1`;
10616
- const rows = await ctx.backend.execute(
10617
- asCompiledRowsSql(query)
10618
- );
10619
- return rows.length > 0;
10884
+ async function withInlineIterativeGraphOperation(ctx, options, run) {
10885
+ if (ctx.backend.capabilities.transactions) {
10886
+ return ctx.backend.transaction(
10887
+ async (backend) => run(createOperation(ctx, options, backend)),
10888
+ {
10889
+ isolationLevel: "repeatable_read",
10890
+ accessMode: "read_only"
10891
+ }
10892
+ );
10893
+ }
10894
+ return run(createOperation(ctx, options, ctx.backend));
10620
10895
  }
10621
- async function executeNeighbors(ctx, sourceId, options) {
10622
- const depth = resolveMaxHops(options.depth, DEFAULT_NEIGHBOR_DEPTH, "depth");
10896
+ async function runIterativeGraphOperation(ctx, options, plan) {
10897
+ if (!ctx.backend.capabilities.transactions) {
10898
+ throw new ConfigurationError(
10899
+ "Temporary-table graph iteration requires a transactional backend.",
10900
+ { dialect: ctx.backend.dialect }
10901
+ );
10902
+ }
10903
+ return ctx.backend.transaction(
10904
+ async (backend) => {
10905
+ const temporaryBackend = requireTemporaryStatements(backend);
10906
+ const runId = generateId();
10907
+ const workingTable = sql.identifier(
10908
+ `typegraph_iterative_${runId.replaceAll("-", "_")}`
10909
+ );
10910
+ const context = {
10911
+ operation: createOperation(ctx, options, temporaryBackend),
10912
+ backend: temporaryBackend,
10913
+ workingTable,
10914
+ graphId: ctx.graphId,
10915
+ runId,
10916
+ executeTemporary: async (query) => {
10917
+ await temporaryBackend.executeTemporaryStatement(
10918
+ asCompiledTemporaryStatementSql(query)
10919
+ );
10920
+ }
10921
+ };
10922
+ if (context.operation.workingMemory !== void 0) {
10923
+ const workingMemoryStatement = ctx.dialect.setTransactionWorkingMemory(
10924
+ context.operation.workingMemory
10925
+ );
10926
+ if (workingMemoryStatement !== void 0) {
10927
+ await context.executeTemporary(workingMemoryStatement);
10928
+ }
10929
+ }
10930
+ await context.executeTemporary(plan.createWorkingTable(context));
10931
+ let operationError;
10932
+ try {
10933
+ let state = await plan.initialize(context);
10934
+ let analyzedRowCount = await refreshWorkingTableStatistics(
10935
+ context,
10936
+ state.workingTableSize
10937
+ );
10938
+ for (let iteration = 1; iteration <= plan.maxIterations && !plan.hasConverged(state); iteration++) {
10939
+ state = await plan.runRound(context, state, iteration);
10940
+ if (iteration < plan.maxIterations && !plan.hasConverged(state)) {
10941
+ analyzedRowCount = await refreshWorkingTableStatistics(
10942
+ context,
10943
+ state.workingTableSize,
10944
+ analyzedRowCount
10945
+ );
10946
+ }
10947
+ }
10948
+ if (!plan.hasConverged(state)) {
10949
+ throw new GraphAlgorithmConvergenceError(
10950
+ plan.algorithm,
10951
+ plan.maxIterations
10952
+ );
10953
+ }
10954
+ return await plan.extractResult(context, state);
10955
+ } catch (error) {
10956
+ operationError = error;
10957
+ throw error;
10958
+ } finally {
10959
+ await dropWorkingTable(context, operationError);
10960
+ }
10961
+ },
10962
+ {
10963
+ isolationLevel: "repeatable_read",
10964
+ accessMode: "read_only",
10965
+ temporaryWrites: INTERNAL_TEMPORARY_WRITES
10966
+ }
10967
+ );
10968
+ }
10969
+ function shouldRefreshWorkingTableStatistics(workingTableSize, analyzedRowCount) {
10970
+ if (workingTableSize < WORKING_TABLE_ANALYZE_MINIMUM_ROWS) return false;
10971
+ if (analyzedRowCount === void 0) return true;
10972
+ return workingTableSize >= analyzedRowCount * WORKING_TABLE_ANALYZE_GROWTH_FACTOR;
10973
+ }
10974
+ async function refreshWorkingTableStatistics(context, workingTableSize, analyzedRowCount) {
10975
+ if (!shouldRefreshWorkingTableStatistics(workingTableSize, analyzedRowCount)) {
10976
+ return analyzedRowCount;
10977
+ }
10978
+ const statement = context.operation.ctx.dialect.analyzeTemporaryTable(
10979
+ context.workingTable
10980
+ );
10981
+ if (statement === void 0) return analyzedRowCount;
10982
+ await context.executeTemporary(statement);
10983
+ return workingTableSize;
10984
+ }
10985
+ async function dropWorkingTable(context, operationError) {
10986
+ try {
10987
+ await context.executeTemporary(
10988
+ sql`DROP TABLE IF EXISTS ${context.workingTable}`
10989
+ );
10990
+ } catch (cleanupError) {
10991
+ if (operationError === void 0) throw cleanupError;
10992
+ }
10993
+ }
10994
+ async function fetchVisibleWorkingNodes(operation, nodeIds) {
10995
+ const nodes = /* @__PURE__ */ new Map();
10996
+ for (const chunk2 of chunkValues(nodeIds, operation.maxWorkingSetSize)) {
10997
+ const query = sql`SELECT n.id, n.kind FROM ${operation.schema.nodesTable} n WHERE n.graph_id = ${operation.ctx.graphId} AND n.id IN (${sqlValueList(chunk2)}) AND ${operation.nodeTemporalFilter}`;
10998
+ const rows = await operation.backend.execute(
10999
+ asCompiledRowsSql(query)
11000
+ );
11001
+ for (const row of rows) nodes.set(nodeIdentityKey(row), row);
11002
+ }
11003
+ return [...nodes.values()].toSorted(
11004
+ (left, right) => compareNodeIdentity(left, right)
11005
+ );
11006
+ }
11007
+ async function reduceExpandedWorkingSet(operation, workingSet, direction, reduce) {
11008
+ const reduced = /* @__PURE__ */ new Map();
11009
+ for (const edgeKinds of operation.edgeKindChunks) {
11010
+ for (const chunk2 of chunkValues(workingSet, operation.maxWorkingSetSize)) {
11011
+ const rows = await operation.backend.execute(
11012
+ asCompiledRowsSql(
11013
+ compileExpansionQuery(operation, chunk2, direction, edgeKinds)
11014
+ )
11015
+ );
11016
+ for (const row of rows) {
11017
+ if (operation.weightExpression !== void 0 && !isPresent(row.weight)) {
11018
+ throw new CompilerInvariantError(
11019
+ "Weighted graph expansion produced a NULL weight. Either the plan skipped its weight audit, or edge data changed concurrently during a run on a backend without snapshot isolation.",
11020
+ { source: row.source_id, target: row.target_id }
11021
+ );
11022
+ }
11023
+ const expansion = {
11024
+ source: { id: row.source_id, kind: row.source_kind },
11025
+ target: { id: row.target_id, kind: row.target_kind },
11026
+ ...isPresent(row.weight) ? { weight: Number(row.weight) } : {}
11027
+ };
11028
+ const targetKey = nodeIdentityKey(expansion.target);
11029
+ const selected = reduce(reduced.get(targetKey), expansion);
11030
+ if (selected !== void 0) reduced.set(targetKey, selected);
11031
+ }
11032
+ }
11033
+ }
11034
+ return reduced;
11035
+ }
11036
+ function nodeIdentityKey(node) {
11037
+ return `${node.kind}\0${node.id}`;
11038
+ }
11039
+ function nodeIdentityKeyFromRow(id, kind) {
11040
+ if (typeof id !== "string" || typeof kind !== "string") return void 0;
11041
+ return nodeIdentityKey({ id, kind });
11042
+ }
11043
+ function compareNodeIdentity(left, right) {
11044
+ return compareStrings(left.id, right.id) || compareStrings(left.kind, right.kind);
11045
+ }
11046
+ function compileExpansionQuery(operation, workingSet, direction, edgeKinds) {
11047
+ const workingValues = sql.join(
11048
+ workingSet.map((node) => sql`(${node.id}, ${node.kind})`),
11049
+ sql`, `
11050
+ );
11051
+ const workingCte = sql`WITH working_set(node_id, node_kind) AS (VALUES ${workingValues})`;
11052
+ switch (direction) {
11053
+ case "out": {
11054
+ return sql`${workingCte} ${compileDirectionalExpansion(operation, sql`working_set`, sql`TRUE`, edgeKinds, "from_id", "from_kind", "to_id", "to_kind")}`;
11055
+ }
11056
+ case "in": {
11057
+ return sql`${workingCte} ${compileDirectionalExpansion(operation, sql`working_set`, sql`TRUE`, edgeKinds, "to_id", "to_kind", "from_id", "from_kind")}`;
11058
+ }
11059
+ case "both": {
11060
+ const outgoing = compileDirectionalExpansion(
11061
+ operation,
11062
+ sql`working_set`,
11063
+ sql`TRUE`,
11064
+ edgeKinds,
11065
+ "from_id",
11066
+ "from_kind",
11067
+ "to_id",
11068
+ "to_kind"
11069
+ );
11070
+ const incoming = compileDirectionalExpansion(
11071
+ operation,
11072
+ sql`working_set`,
11073
+ sql`TRUE`,
11074
+ edgeKinds,
11075
+ "to_id",
11076
+ "to_kind",
11077
+ "from_id",
11078
+ "from_kind"
11079
+ );
11080
+ return sql`${workingCte} ${outgoing} UNION ALL ${incoming}`;
11081
+ }
11082
+ }
11083
+ }
11084
+ function compileWorkingTableExpansion(operation, workingTable, sourceFilter, direction, edgeKinds) {
11085
+ switch (direction) {
11086
+ case "out": {
11087
+ return compileDirectionalExpansion(
11088
+ operation,
11089
+ workingTable,
11090
+ sourceFilter,
11091
+ edgeKinds,
11092
+ "from_id",
11093
+ "from_kind",
11094
+ "to_id",
11095
+ "to_kind"
11096
+ );
11097
+ }
11098
+ case "in": {
11099
+ return compileDirectionalExpansion(
11100
+ operation,
11101
+ workingTable,
11102
+ sourceFilter,
11103
+ edgeKinds,
11104
+ "to_id",
11105
+ "to_kind",
11106
+ "from_id",
11107
+ "from_kind"
11108
+ );
11109
+ }
11110
+ case "both": {
11111
+ const outgoing = compileDirectionalExpansion(
11112
+ operation,
11113
+ workingTable,
11114
+ sourceFilter,
11115
+ edgeKinds,
11116
+ "from_id",
11117
+ "from_kind",
11118
+ "to_id",
11119
+ "to_kind"
11120
+ );
11121
+ const incoming = compileDirectionalExpansion(
11122
+ operation,
11123
+ workingTable,
11124
+ sourceFilter,
11125
+ edgeKinds,
11126
+ "to_id",
11127
+ "to_kind",
11128
+ "from_id",
11129
+ "from_kind"
11130
+ );
11131
+ return sql`${outgoing} UNION ALL ${incoming}`;
11132
+ }
11133
+ }
11134
+ }
11135
+ function compileWorkingTableEdgeExpansion(operation, workingTable, sourceFilter, direction, edgeKinds) {
11136
+ switch (direction) {
11137
+ case "out": {
11138
+ return compileDirectionalEdgeExpansion(
11139
+ operation,
11140
+ workingTable,
11141
+ sourceFilter,
11142
+ edgeKinds,
11143
+ "from_id",
11144
+ "from_kind",
11145
+ "to_id",
11146
+ "to_kind"
11147
+ );
11148
+ }
11149
+ case "in": {
11150
+ return compileDirectionalEdgeExpansion(
11151
+ operation,
11152
+ workingTable,
11153
+ sourceFilter,
11154
+ edgeKinds,
11155
+ "to_id",
11156
+ "to_kind",
11157
+ "from_id",
11158
+ "from_kind"
11159
+ );
11160
+ }
11161
+ case "both": {
11162
+ const outgoing = compileDirectionalEdgeExpansion(
11163
+ operation,
11164
+ workingTable,
11165
+ sourceFilter,
11166
+ edgeKinds,
11167
+ "from_id",
11168
+ "from_kind",
11169
+ "to_id",
11170
+ "to_kind"
11171
+ );
11172
+ const incoming = compileDirectionalEdgeExpansion(
11173
+ operation,
11174
+ workingTable,
11175
+ sourceFilter,
11176
+ edgeKinds,
11177
+ "to_id",
11178
+ "to_kind",
11179
+ "from_id",
11180
+ "from_kind"
11181
+ );
11182
+ return sql`${outgoing} UNION ALL ${incoming}`;
11183
+ }
11184
+ }
11185
+ }
11186
+ function compileDirectionalExpansion(operation, workingRelation, sourceFilter, edgeKinds, joinField, joinKindField, targetField, targetKindField) {
11187
+ const edgeExpansion = compileDirectionalEdgeExpansion(
11188
+ operation,
11189
+ workingRelation,
11190
+ sourceFilter,
11191
+ edgeKinds,
11192
+ joinField,
11193
+ joinKindField,
11194
+ targetField,
11195
+ targetKindField
11196
+ );
11197
+ const weightColumn = operation.weightExpression === void 0 ? sql`` : sql`, expanded.weight`;
11198
+ return sql`SELECT expanded.source_id, expanded.source_kind, n.id AS target_id, n.kind AS target_kind${weightColumn} FROM (${edgeExpansion}) expanded JOIN ${operation.schema.nodesTable} n ON n.graph_id = ${operation.ctx.graphId} AND n.id = expanded.target_id AND n.kind = expanded.target_kind WHERE ${operation.nodeTemporalFilter}`;
11199
+ }
11200
+ function compileDirectionalEdgeExpansion(operation, workingRelation, sourceFilter, edgeKinds, joinField, joinKindField, targetField, targetKindField) {
11201
+ const edgeKindFilter = compileKindFilter(sql.raw("e.kind"), edgeKinds);
11202
+ const weightColumn = operation.weightExpression === void 0 ? sql`` : sql`, ${operation.weightExpression} AS weight`;
11203
+ return sql`SELECT w.node_id AS source_id, w.node_kind AS source_kind, e.${sql.raw(targetField)} AS target_id, e.${sql.raw(targetKindField)} AS target_kind${weightColumn} FROM ${workingRelation} w JOIN ${operation.schema.edgesTable} e ON e.${sql.raw(joinField)} = w.node_id AND e.${sql.raw(joinKindField)} = w.node_kind AND e.graph_id = ${operation.ctx.graphId} WHERE ${sourceFilter} AND ${edgeKindFilter} AND ${operation.edgeTemporalFilter}`;
11204
+ }
11205
+ function chunkValues(values, chunkSize) {
11206
+ const chunks = [];
11207
+ for (let offset = 0; offset < values.length; offset += chunkSize) {
11208
+ chunks.push(values.slice(offset, offset + chunkSize));
11209
+ }
11210
+ return chunks;
11211
+ }
11212
+ function createOperation(ctx, options, backend) {
11213
+ const temporal = resolveTemporalOptions(ctx, options);
11214
+ const currentTimestamp = currentReadInstant();
11215
+ const nodeTemporalFilter = compileTemporalFilter({
11216
+ mode: temporal.temporalMode,
11217
+ ...temporal.asOf === void 0 ? {} : { asOf: temporal.asOf },
11218
+ ...temporal.recordedAsOf === void 0 ? {} : { recordedAsOf: temporal.recordedAsOf },
11219
+ tableAlias: "n",
11220
+ currentTimestamp
11221
+ });
11222
+ const edgeTemporalFilter = compileTemporalFilter({
11223
+ mode: temporal.temporalMode,
11224
+ ...temporal.asOf === void 0 ? {} : { asOf: temporal.asOf },
11225
+ ...temporal.recordedAsOf === void 0 ? {} : { recordedAsOf: temporal.recordedAsOf },
11226
+ tableAlias: "e",
11227
+ currentTimestamp
11228
+ });
11229
+ const weightExpression = options.weightProperty === void 0 ? void 0 : compileWeightExpression(
11230
+ ctx.dialect,
11231
+ options.weightProperty,
11232
+ options.defaultWeight
11233
+ );
11234
+ const direction = options.direction ?? "out";
11235
+ const branchCount = direction === "both" ? 2 : 1;
11236
+ const parameterLimit = backend.capabilities.maxBindParameters ?? DEFAULT_MAX_BIND_PARAMETERS;
11237
+ const reservedPerBranch = RESERVED_TEMPORAL_BIND_PARAMETERS_PER_BRANCH + (weightExpression === void 0 ? 0 : RESERVED_WEIGHT_BIND_PARAMETERS_PER_BRANCH);
11238
+ const fixedParameters = branchCount * reservedPerBranch;
11239
+ const sharedBudget = parameterLimit - fixedParameters;
11240
+ const maxEdgeKindsPerQuery = Math.floor(sharedBudget / (branchCount + 2));
11241
+ if (maxEdgeKindsPerQuery < 1) {
11242
+ throw new ConfigurationError(
11243
+ "An iterative graph operation cannot fit its fixed filters within the backend bind-parameter limit.",
11244
+ { parameterLimit, direction }
11245
+ );
11246
+ }
11247
+ const edgeKindChunks = chunkValues(
11248
+ [...new Set(options.edges)].toSorted(
11249
+ (left, right) => compareStrings(left, right)
11250
+ ),
11251
+ maxEdgeKindsPerQuery
11252
+ );
11253
+ const largestEdgeKindChunk = Math.max(
11254
+ ...edgeKindChunks.map((chunk2) => chunk2.length)
11255
+ );
11256
+ const maxWorkingSetSize = Math.floor(
11257
+ (parameterLimit - branchCount * (largestEdgeKindChunk + reservedPerBranch)) / 2
11258
+ );
11259
+ if (maxWorkingSetSize < 1) {
11260
+ throw new ConfigurationError(
11261
+ "An iterative graph operation cannot fit one working-set node within the backend bind-parameter limit.",
11262
+ { parameterLimit, direction }
11263
+ );
11264
+ }
11265
+ return {
11266
+ backend,
11267
+ ctx,
11268
+ edgeKindChunks,
11269
+ direction,
11270
+ maxWorkingSetSize,
11271
+ nodeTemporalFilter,
11272
+ edgeTemporalFilter,
11273
+ schema: resolveReadSchema(ctx, options),
11274
+ workingMemory: resolveWorkingMemory(options.workingMemory),
11275
+ weightExpression
11276
+ };
11277
+ }
11278
+ function compileWeightExpression(dialect, weightProperty, defaultWeight) {
11279
+ const extracted = dialect.jsonExtractDouble(
11280
+ sql.raw("e.props"),
11281
+ jsonPointer([weightProperty])
11282
+ );
11283
+ return defaultWeight === void 0 ? extracted : sql`COALESCE(${extracted}, ${defaultWeight})`;
11284
+ }
11285
+ function supportsTemporaryIteration(ctx) {
11286
+ return ctx.backend.capabilities.transactions && ctx.backend.capabilities.returning !== false && ctx.backend.executeTemporaryStatement !== void 0;
11287
+ }
11288
+ function requireTemporaryStatements(backend) {
11289
+ if (backend.executeTemporaryStatement === void 0) {
11290
+ throw new ConfigurationError(
11291
+ "Iterative graph operations require temporary-statement support on the transaction backend.",
11292
+ { dialect: backend.dialect },
11293
+ {
11294
+ suggestion: "Use a built-in SQLite/PostgreSQL backend or implement executeTemporaryStatement on the custom backend."
11295
+ }
11296
+ );
11297
+ }
11298
+ return backend;
11299
+ }
11300
+
11301
+ // src/store/algorithms/breadth-first.ts
11302
+ async function findReachableNodes(ctx, sourceId, maxHops, options) {
11303
+ if (supportsTemporaryIteration(ctx)) {
11304
+ return findReachableNodesInWorkingTable(ctx, sourceId, maxHops, options);
11305
+ }
11306
+ return findReachableNodesInline(ctx, sourceId, maxHops, options);
11307
+ }
11308
+ async function findShortestPath(ctx, sourceId, targetId, maxHops, options) {
11309
+ if (supportsTemporaryIteration(ctx)) {
11310
+ return findShortestPathInWorkingTable(
11311
+ ctx,
11312
+ sourceId,
11313
+ targetId,
11314
+ maxHops,
11315
+ options
11316
+ );
11317
+ }
11318
+ return findShortestPathInline(ctx, sourceId, targetId, maxHops, options);
11319
+ }
11320
+ async function findReachableNodesInWorkingTable(ctx, sourceId, maxHops, options) {
11321
+ return runIterativeGraphOperation(ctx, options, {
11322
+ algorithm: "reachable",
11323
+ maxIterations: maxHops,
11324
+ createWorkingTable,
11325
+ async initialize(context) {
11326
+ const seeded = await seedWorkingSide(context, sourceId, "forward");
11327
+ return {
11328
+ depth: 0,
11329
+ frontierCount: seeded.length,
11330
+ workingTableSize: seeded.length
11331
+ };
11332
+ },
11333
+ async runRound(context, state) {
11334
+ const nextDepth = state.depth + 1;
11335
+ const frontierCount = await expandReachableWorkingTableRound(
11336
+ context,
11337
+ "forward",
11338
+ state.depth,
11339
+ nextDepth,
11340
+ context.operation.direction
11341
+ );
11342
+ return {
11343
+ depth: nextDepth,
11344
+ frontierCount,
11345
+ workingTableSize: state.workingTableSize + frontierCount
11346
+ };
11347
+ },
11348
+ hasConverged(state) {
11349
+ return state.frontierCount === 0 || state.depth >= maxHops;
11350
+ },
11351
+ async extractResult(context) {
11352
+ const rows = await readWorkingRows(context, "forward");
11353
+ return rows.map((row) => ({
11354
+ id: row.node_id,
11355
+ kind: row.node_kind,
11356
+ depth: Number(row.depth)
11357
+ })).toSorted(
11358
+ (left, right) => left.depth - right.depth || compareNodeIdentity(left, right)
11359
+ );
11360
+ }
11361
+ });
11362
+ }
11363
+ async function findShortestPathInWorkingTable(ctx, sourceId, targetId, maxHops, options) {
11364
+ return runIterativeGraphOperation(ctx, options, {
11365
+ algorithm: "shortestPath",
11366
+ maxIterations: maxHops,
11367
+ createWorkingTable,
11368
+ async initialize(context) {
11369
+ const forwardSeeds = await seedWorkingSide(context, sourceId, "forward");
11370
+ const reverseSeeds = await seedWorkingSide(context, targetId, "reverse");
11371
+ return {
11372
+ forwardDepth: 0,
11373
+ reverseDepth: 0,
11374
+ forwardFrontierCount: forwardSeeds.length,
11375
+ reverseFrontierCount: reverseSeeds.length,
11376
+ meeting: findSeedMeeting(forwardSeeds, reverseSeeds),
11377
+ workingTableSize: forwardSeeds.length + reverseSeeds.length
11378
+ };
11379
+ },
11380
+ async runRound(context, state) {
11381
+ const expandForward = state.forwardFrontierCount <= state.reverseFrontierCount;
11382
+ const side = expandForward ? "forward" : "reverse";
11383
+ const currentDepth = expandForward ? state.forwardDepth : state.reverseDepth;
11384
+ const nextDepth = currentDepth + 1;
11385
+ const direction = expandForward ? context.operation.direction : reverseDirection(context.operation.direction);
11386
+ const round = await expandWorkingTableRound(
11387
+ context,
11388
+ side,
11389
+ currentDepth,
11390
+ nextDepth,
11391
+ direction,
11392
+ maxHops
11393
+ );
11394
+ return {
11395
+ forwardDepth: expandForward ? nextDepth : state.forwardDepth,
11396
+ reverseDepth: expandForward ? state.reverseDepth : nextDepth,
11397
+ forwardFrontierCount: expandForward ? round.insertedCount : state.forwardFrontierCount,
11398
+ reverseFrontierCount: expandForward ? state.reverseFrontierCount : round.insertedCount,
11399
+ meeting: round.meeting,
11400
+ workingTableSize: state.workingTableSize + round.insertedCount
11401
+ };
11402
+ },
11403
+ hasConverged(state) {
11404
+ return state.meeting !== void 0 || state.forwardFrontierCount === 0 || state.reverseFrontierCount === 0 || state.forwardDepth + state.reverseDepth >= maxHops;
11405
+ },
11406
+ async extractResult(context, state) {
11407
+ if (state.meeting === void 0) return;
11408
+ const rows = await readWorkingRows(context);
11409
+ return buildShortestPath(
11410
+ nodeIdentityKey(state.meeting),
11411
+ createVisitedMapFromRows(rows, "forward"),
11412
+ createVisitedMapFromRows(rows, "reverse")
11413
+ );
11414
+ }
11415
+ });
11416
+ }
11417
+ function createWorkingTable(context) {
11418
+ return sql`
11419
+ CREATE TEMP TABLE ${context.workingTable} (
11420
+ graph_id TEXT NOT NULL,
11421
+ run_id TEXT NOT NULL,
11422
+ side TEXT NOT NULL,
11423
+ node_id TEXT NOT NULL,
11424
+ node_kind TEXT NOT NULL,
11425
+ depth INTEGER NOT NULL,
11426
+ predecessor_id TEXT,
11427
+ predecessor_kind TEXT,
11428
+ meeting_depth INTEGER,
11429
+ PRIMARY KEY (graph_id, run_id, side, node_kind, node_id)
11430
+ )
11431
+ `;
11432
+ }
11433
+ async function seedWorkingSide(context, nodeId, side) {
11434
+ const { operation, workingTable, graphId, runId } = context;
11435
+ return context.backend.execute(
11436
+ asCompiledRowsSql(sql`
11437
+ INSERT INTO ${workingTable}
11438
+ (graph_id, run_id, side, node_id, node_kind, depth,
11439
+ predecessor_id, predecessor_kind)
11440
+ SELECT ${graphId}, ${runId}, ${side}, n.id, n.kind, 0, NULL, NULL
11441
+ FROM ${operation.schema.nodesTable} n
11442
+ WHERE n.graph_id = ${graphId}
11443
+ AND n.id = ${nodeId}
11444
+ AND ${operation.nodeTemporalFilter}
11445
+ ON CONFLICT (graph_id, run_id, side, node_kind, node_id) DO NOTHING
11446
+ RETURNING node_id, node_kind
11447
+ `)
11448
+ );
11449
+ }
11450
+ function findSeedMeeting(forwardSeeds, reverseSeeds) {
11451
+ const reverseKeys = new Set(
11452
+ reverseSeeds.map(
11453
+ (row) => nodeIdentityKey({ id: row.node_id, kind: row.node_kind })
11454
+ )
11455
+ );
11456
+ let meeting;
11457
+ for (const row of forwardSeeds) {
11458
+ const candidate = { id: row.node_id, kind: row.node_kind, depth: 0 };
11459
+ if (!reverseKeys.has(nodeIdentityKey(candidate))) continue;
11460
+ if (meeting === void 0 || compareNodeIdentity(candidate, meeting) < 0) {
11461
+ meeting = candidate;
11462
+ }
11463
+ }
11464
+ return meeting;
11465
+ }
11466
+ async function expandWorkingTableRound(context, side, currentDepth, nextDepth, direction, maxHops) {
11467
+ const oppositeSide = side === "forward" ? "reverse" : "forward";
11468
+ let insertedCount = 0;
11469
+ let meeting;
11470
+ for (const edgeKinds of context.operation.edgeKindChunks) {
11471
+ const sourceFilter = sql`
11472
+ w.graph_id = ${context.graphId}
11473
+ AND w.run_id = ${context.runId}
11474
+ AND w.side = ${side}
11475
+ AND w.depth = ${currentDepth}
11476
+ `;
11477
+ const expansion = compileWorkingTableExpansion(
11478
+ context.operation,
11479
+ context.workingTable,
11480
+ sourceFilter,
11481
+ direction,
11482
+ edgeKinds
11483
+ );
11484
+ const rows = await context.backend.execute(
11485
+ asCompiledRowsSql(sql`
11486
+ INSERT INTO ${context.workingTable}
11487
+ (graph_id, run_id, side, node_id, node_kind, depth,
11488
+ predecessor_id, predecessor_kind, meeting_depth)
11489
+ SELECT
11490
+ ${context.graphId}, ${context.runId}, ${side},
11491
+ ranked.target_id, ranked.target_kind, ${nextDepth},
11492
+ ranked.source_id, ranked.source_kind,
11493
+ (
11494
+ SELECT opposite.depth
11495
+ FROM ${context.workingTable} opposite
11496
+ WHERE opposite.graph_id = ${context.graphId}
11497
+ AND opposite.run_id = ${context.runId}
11498
+ AND opposite.side = ${oppositeSide}
11499
+ AND opposite.node_id = ranked.target_id
11500
+ AND opposite.node_kind = ranked.target_kind
11501
+ )
11502
+ FROM (
11503
+ SELECT expanded.*,
11504
+ ROW_NUMBER() OVER (
11505
+ PARTITION BY expanded.target_kind, expanded.target_id
11506
+ ORDER BY expanded.source_id, expanded.source_kind
11507
+ ) AS candidate_rank
11508
+ FROM (${expansion}) expanded
11509
+ ) ranked
11510
+ WHERE ranked.candidate_rank = 1
11511
+ AND NOT EXISTS (
11512
+ SELECT 1
11513
+ FROM ${context.workingTable} visited
11514
+ WHERE visited.graph_id = ${context.graphId}
11515
+ AND visited.run_id = ${context.runId}
11516
+ AND visited.side = ${side}
11517
+ AND visited.node_id = ranked.target_id
11518
+ AND visited.node_kind = ranked.target_kind
11519
+ )
11520
+ ON CONFLICT (graph_id, run_id, side, node_kind, node_id) DO NOTHING
11521
+ RETURNING node_id, node_kind, meeting_depth
11522
+ `)
11523
+ );
11524
+ insertedCount += rows.length;
11525
+ meeting = selectRoundMeeting(meeting, rows, nextDepth, maxHops);
11526
+ }
11527
+ return { insertedCount, meeting };
11528
+ }
11529
+ function selectRoundMeeting(currentMeeting, rows, nextDepth, maxHops) {
11530
+ let meeting = currentMeeting;
11531
+ for (const row of rows) {
11532
+ if (row.meeting_depth === null || row.meeting_depth === void 0) {
11533
+ continue;
11534
+ }
11535
+ const totalDepth = nextDepth + Number(row.meeting_depth);
11536
+ if (totalDepth > maxHops) continue;
11537
+ const candidate = {
11538
+ id: row.node_id,
11539
+ kind: row.node_kind,
11540
+ depth: totalDepth
11541
+ };
11542
+ if (meeting === void 0 || candidate.depth < meeting.depth || candidate.depth === meeting.depth && compareNodeIdentity(candidate, meeting) < 0) {
11543
+ meeting = candidate;
11544
+ }
11545
+ }
11546
+ return meeting;
11547
+ }
11548
+ async function expandReachableWorkingTableRound(context, side, currentDepth, nextDepth, direction) {
11549
+ let insertedCount = 0;
11550
+ for (const edgeKinds of context.operation.edgeKindChunks) {
11551
+ const sourceFilter = sql`
11552
+ w.graph_id = ${context.graphId}
11553
+ AND w.run_id = ${context.runId}
11554
+ AND w.side = ${side}
11555
+ AND w.depth = ${currentDepth}
11556
+ `;
11557
+ const edgeExpansion = compileWorkingTableEdgeExpansion(
11558
+ context.operation,
11559
+ context.workingTable,
11560
+ sourceFilter,
11561
+ direction,
11562
+ edgeKinds
11563
+ );
11564
+ const rows = await context.backend.execute(
11565
+ asCompiledRowsSql(sql`
11566
+ INSERT INTO ${context.workingTable}
11567
+ (graph_id, run_id, side, node_id, node_kind, depth,
11568
+ predecessor_id, predecessor_kind)
11569
+ SELECT
11570
+ ${context.graphId}, ${context.runId}, ${side},
11571
+ candidates.target_id, candidates.target_kind, ${nextDepth},
11572
+ NULL, NULL
11573
+ FROM (
11574
+ SELECT DISTINCT expanded.target_id, expanded.target_kind
11575
+ FROM (${edgeExpansion}) expanded
11576
+ ) candidates
11577
+ JOIN ${context.operation.schema.nodesTable} n
11578
+ ON n.graph_id = ${context.graphId}
11579
+ AND n.id = candidates.target_id
11580
+ AND n.kind = candidates.target_kind
11581
+ WHERE ${context.operation.nodeTemporalFilter}
11582
+ AND NOT EXISTS (
11583
+ SELECT 1
11584
+ FROM ${context.workingTable} visited
11585
+ WHERE visited.graph_id = ${context.graphId}
11586
+ AND visited.run_id = ${context.runId}
11587
+ AND visited.side = ${side}
11588
+ AND visited.node_id = candidates.target_id
11589
+ AND visited.node_kind = candidates.target_kind
11590
+ )
11591
+ ON CONFLICT (graph_id, run_id, side, node_kind, node_id) DO NOTHING
11592
+ RETURNING node_id
11593
+ `)
11594
+ );
11595
+ insertedCount += rows.length;
11596
+ }
11597
+ return insertedCount;
11598
+ }
11599
+ async function readWorkingRows(context, side) {
11600
+ const sideFilter = side === void 0 ? sql`TRUE` : sql`side = ${side}`;
11601
+ return context.backend.execute(
11602
+ asCompiledRowsSql(sql`
11603
+ SELECT side, node_id, node_kind, depth,
11604
+ predecessor_id, predecessor_kind
11605
+ FROM ${context.workingTable}
11606
+ WHERE graph_id = ${context.graphId}
11607
+ AND run_id = ${context.runId}
11608
+ AND ${sideFilter}
11609
+ ORDER BY side, depth, node_id, node_kind
11610
+ `)
11611
+ );
11612
+ }
11613
+ async function findReachableNodesInline(ctx, sourceId, maxHops, options) {
11614
+ return withInlineIterativeGraphOperation(ctx, options, async (operation) => {
11615
+ const sources = await fetchVisibleWorkingNodes(operation, [sourceId]);
11616
+ if (sources.length === 0) return [];
11617
+ const reached = new Map(
11618
+ sources.map((source) => [
11619
+ nodeIdentityKey(source),
11620
+ { id: source.id, kind: source.kind, depth: 0 }
11621
+ ])
11622
+ );
11623
+ let workingSet = sources;
11624
+ for (let depth = 1; depth <= maxHops && workingSet.length > 0; depth++) {
11625
+ const candidates = await expandInlineWorkingSet(
11626
+ operation,
11627
+ workingSet,
11628
+ operation.direction,
11629
+ reached
11630
+ );
11631
+ const nextWorkingSet = sortCandidates(candidates);
11632
+ for (const candidate of nextWorkingSet) {
11633
+ reached.set(nodeIdentityKey(candidate), {
11634
+ id: candidate.id,
11635
+ kind: candidate.kind,
11636
+ depth
11637
+ });
11638
+ }
11639
+ workingSet = nextWorkingSet;
11640
+ }
11641
+ return [...reached.values()].toSorted(
11642
+ (left, right) => left.depth - right.depth || compareNodeIdentity(left, right)
11643
+ );
11644
+ });
11645
+ }
11646
+ async function findShortestPathInline(ctx, sourceId, targetId, maxHops, options) {
11647
+ return withInlineIterativeGraphOperation(ctx, options, async (operation) => {
11648
+ const visibleNodes = await fetchVisibleWorkingNodes(operation, [
11649
+ sourceId,
11650
+ targetId
11651
+ ]);
11652
+ const sources = visibleNodes.filter((node) => node.id === sourceId);
11653
+ const targets = visibleNodes.filter((node) => node.id === targetId);
11654
+ if (sources.length === 0 || targets.length === 0) return;
11655
+ if (sourceId === targetId) return { nodes: [sources[0]], depth: 0 };
11656
+ const forwardVisited = createVisitedMap(sources);
11657
+ const reverseVisited = createVisitedMap(targets);
11658
+ let forwardWorkingSet = sources;
11659
+ let reverseWorkingSet = targets;
11660
+ let forwardDepth = 0;
11661
+ let reverseDepth = 0;
11662
+ while (forwardWorkingSet.length > 0 && reverseWorkingSet.length > 0 && forwardDepth + reverseDepth < maxHops) {
11663
+ const expandForward = forwardWorkingSet.length <= reverseWorkingSet.length;
11664
+ const activeVisited = expandForward ? forwardVisited : reverseVisited;
11665
+ const oppositeVisited = expandForward ? reverseVisited : forwardVisited;
11666
+ const currentWorkingSet = expandForward ? forwardWorkingSet : reverseWorkingSet;
11667
+ const direction = expandForward ? operation.direction : reverseDirection(operation.direction);
11668
+ const candidates = await expandInlineWorkingSet(
11669
+ operation,
11670
+ currentWorkingSet,
11671
+ direction,
11672
+ activeVisited
11673
+ );
11674
+ const nextDepth = (expandForward ? forwardDepth : reverseDepth) + 1;
11675
+ const nextWorkingSet = sortCandidates(candidates);
11676
+ for (const candidate of nextWorkingSet) {
11677
+ activeVisited.set(nodeIdentityKey(candidate), {
11678
+ id: candidate.id,
11679
+ kind: candidate.kind,
11680
+ depth: nextDepth,
11681
+ parentKey: nodeIdentityKey({
11682
+ id: candidate.parentId,
11683
+ kind: candidate.parentKind
11684
+ })
11685
+ });
11686
+ }
11687
+ const meetingKey = findMeetingNodeKey(
11688
+ nextWorkingSet,
11689
+ activeVisited,
11690
+ oppositeVisited
11691
+ );
11692
+ if (meetingKey !== void 0) {
11693
+ return buildShortestPath(meetingKey, forwardVisited, reverseVisited);
11694
+ }
11695
+ if (expandForward) {
11696
+ forwardWorkingSet = nextWorkingSet;
11697
+ forwardDepth = nextDepth;
11698
+ } else {
11699
+ reverseWorkingSet = nextWorkingSet;
11700
+ reverseDepth = nextDepth;
11701
+ }
11702
+ }
11703
+ return;
11704
+ });
11705
+ }
11706
+ async function expandInlineWorkingSet(operation, workingSet, direction, visited) {
11707
+ return reduceExpandedWorkingSet(
11708
+ operation,
11709
+ workingSet,
11710
+ direction,
11711
+ (existing, expansion) => selectPredecessor(existing, expansion, visited)
11712
+ );
11713
+ }
11714
+ function selectPredecessor(existing, expansion, visited) {
11715
+ if (visited.has(nodeIdentityKey(expansion.target))) return existing;
11716
+ if (existing !== void 0 && compareNodeIdentity(expansion.source, {
11717
+ id: existing.parentId,
11718
+ kind: existing.parentKind
11719
+ }) >= 0) {
11720
+ return existing;
11721
+ }
11722
+ return {
11723
+ id: expansion.target.id,
11724
+ kind: expansion.target.kind,
11725
+ parentId: expansion.source.id,
11726
+ parentKind: expansion.source.kind
11727
+ };
11728
+ }
11729
+ function sortCandidates(candidates) {
11730
+ return [...candidates.values()].toSorted(
11731
+ (left, right) => compareNodeIdentity(left, right)
11732
+ );
11733
+ }
11734
+ function findMeetingNodeKey(candidates, activeVisited, oppositeVisited) {
11735
+ let best;
11736
+ for (const candidate of candidates) {
11737
+ const key = nodeIdentityKey(candidate);
11738
+ const active = activeVisited.get(key);
11739
+ const opposite = oppositeVisited.get(key);
11740
+ if (active === void 0 || opposite === void 0) continue;
11741
+ const meeting = { key, depth: active.depth + opposite.depth };
11742
+ if (best === void 0 || meeting.depth < best.depth || meeting.depth === best.depth && compareStrings(meeting.key, best.key) < 0) {
11743
+ best = meeting;
11744
+ }
11745
+ }
11746
+ return best?.key;
11747
+ }
11748
+ function buildShortestPath(meetingKey, forwardVisited, reverseVisited) {
11749
+ const forwardKeys = [];
11750
+ let cursor = meetingKey;
11751
+ while (cursor !== void 0) {
11752
+ forwardKeys.push(cursor);
11753
+ cursor = forwardVisited.get(cursor)?.parentKey;
11754
+ }
11755
+ forwardKeys.reverse();
11756
+ const pathKeys = [...forwardKeys];
11757
+ cursor = reverseVisited.get(meetingKey)?.parentKey;
11758
+ while (cursor !== void 0) {
11759
+ pathKeys.push(cursor);
11760
+ cursor = reverseVisited.get(cursor)?.parentKey;
11761
+ }
11762
+ const nodes = pathKeys.map((key) => {
11763
+ const node = forwardVisited.get(key) ?? reverseVisited.get(key);
11764
+ return { id: node?.id ?? "", kind: node?.kind ?? "" };
11765
+ });
11766
+ return { nodes, depth: nodes.length - 1 };
11767
+ }
11768
+ function createVisitedMap(nodes) {
11769
+ return new Map(
11770
+ nodes.map((node) => [
11771
+ nodeIdentityKey(node),
11772
+ { id: node.id, kind: node.kind, depth: 0, parentKey: void 0 }
11773
+ ])
11774
+ );
11775
+ }
11776
+ function createVisitedMapFromRows(rows, side) {
11777
+ return new Map(
11778
+ rows.filter((row) => row.side === side).map((row) => {
11779
+ const parentKey = nodeIdentityKeyFromRow(
11780
+ row.predecessor_id,
11781
+ row.predecessor_kind
11782
+ );
11783
+ const node = {
11784
+ id: row.node_id,
11785
+ kind: row.node_kind,
11786
+ depth: Number(row.depth),
11787
+ parentKey
11788
+ };
11789
+ return [nodeIdentityKey(node), node];
11790
+ })
11791
+ );
11792
+ }
11793
+ function reverseDirection(direction) {
11794
+ switch (direction) {
11795
+ case "out": {
11796
+ return "in";
11797
+ }
11798
+ case "in": {
11799
+ return "out";
11800
+ }
11801
+ case "both": {
11802
+ return "both";
11803
+ }
11804
+ }
11805
+ }
11806
+
11807
+ // src/store/algorithms/reachable.ts
11808
+ async function executeReachable(ctx, sourceId, options) {
11809
+ assertEdgeKinds(options.edges);
11810
+ const maxHops = resolveMaxHops(
11811
+ options.maxHops,
11812
+ DEFAULT_ALGORITHM_MAX_HOPS,
11813
+ "maxHops"
11814
+ );
11815
+ const reached = await findReachableNodes(ctx, sourceId, maxHops, options);
11816
+ return options.excludeSource === true ? reached.filter((node) => node.id !== sourceId) : reached;
11817
+ }
11818
+ async function executeCanReach(ctx, sourceId, targetId, options) {
11819
+ assertEdgeKinds(options.edges);
11820
+ const maxHops = resolveMaxHops(
11821
+ options.maxHops,
11822
+ DEFAULT_ALGORITHM_MAX_HOPS,
11823
+ "maxHops"
11824
+ );
11825
+ return await findShortestPath(ctx, sourceId, targetId, maxHops, options) !== void 0;
11826
+ }
11827
+ async function executeNeighbors(ctx, sourceId, options) {
11828
+ const depth = resolveMaxHops(options.depth, DEFAULT_NEIGHBOR_DEPTH, "depth");
10623
11829
  return executeReachable(ctx, sourceId, {
10624
11830
  edges: options.edges,
10625
11831
  maxHops: depth,
@@ -10632,9 +11838,14 @@ async function executeNeighbors(ctx, sourceId, options) {
10632
11838
  ...options.recordedAsOf !== void 0 && {
10633
11839
  recordedAsOf: options.recordedAsOf
10634
11840
  },
11841
+ ...options.workingMemory !== void 0 && {
11842
+ workingMemory: options.workingMemory
11843
+ },
10635
11844
  excludeSource: true
10636
11845
  });
10637
11846
  }
11847
+
11848
+ // src/store/algorithms/shortest-path.ts
10638
11849
  async function executeShortestPath(ctx, sourceId, targetId, options) {
10639
11850
  assertEdgeKinds(options.edges);
10640
11851
  const maxHops = resolveMaxHops(
@@ -10642,56 +11853,696 @@ async function executeShortestPath(ctx, sourceId, targetId, options) {
10642
11853
  DEFAULT_ALGORITHM_MAX_HOPS,
10643
11854
  "maxHops"
10644
11855
  );
11856
+ return findShortestPath(ctx, sourceId, targetId, maxHops, options);
11857
+ }
11858
+ var DEFAULT_WCC_MAX_ITERATIONS = 1e3;
11859
+ async function executeWeaklyConnectedComponents(ctx, options) {
11860
+ assertEdgeKinds(options.edges);
11861
+ assertWeaklyConnectedComponentsSupported(ctx);
11862
+ const maxIterations = resolveMaxIterations(
11863
+ options.maxIterations,
11864
+ DEFAULT_WCC_MAX_ITERATIONS,
11865
+ "weaklyConnectedComponents"
11866
+ );
11867
+ const nodeKinds = options.nodeKinds === void 0 ? void 0 : [...new Set(options.nodeKinds)].toSorted(
11868
+ (left, right) => compareCodePoints(left, right)
11869
+ );
11870
+ const traversalOptions = {
11871
+ edges: options.edges,
11872
+ direction: "both",
11873
+ ...options.temporalMode === void 0 ? {} : { temporalMode: options.temporalMode },
11874
+ ...options.asOf === void 0 ? {} : { asOf: options.asOf },
11875
+ ...options.recordedAsOf === void 0 ? {} : { recordedAsOf: options.recordedAsOf },
11876
+ ...options.workingMemory === void 0 ? {} : { workingMemory: options.workingMemory }
11877
+ };
11878
+ return runIterativeGraphOperation(ctx, traversalOptions, {
11879
+ algorithm: "weaklyConnectedComponents",
11880
+ maxIterations,
11881
+ createWorkingTable: createWorkingTable2,
11882
+ initialize: (context) => initializeWorkingTable(context, nodeKinds),
11883
+ runRound: runLabelPropagationRound,
11884
+ hasConverged(state) {
11885
+ return state.changedCount === 0;
11886
+ },
11887
+ extractResult: extractMemberships
11888
+ });
11889
+ }
11890
+ function assertWeaklyConnectedComponentsSupported(ctx) {
11891
+ if (ctx.backend.capabilities.graphAnalytics?.supported === true && ctx.backend.capabilities.windowFunctions) {
11892
+ return;
11893
+ }
11894
+ throw new UnsupportedBackendCapabilityError(
11895
+ "weaklyConnectedComponents",
11896
+ "graphAnalytics",
11897
+ {
11898
+ dialect: ctx.backend.dialect,
11899
+ supported: ctx.backend.capabilities.graphAnalytics?.supported === true,
11900
+ windowFunctions: ctx.backend.capabilities.windowFunctions
11901
+ },
11902
+ "Use a built-in transactional SQLite/PostgreSQL backend, or declare graphAnalytics support on a compatible custom backend."
11903
+ );
11904
+ }
11905
+ function createWorkingTable2(context) {
11906
+ return sql`
11907
+ CREATE TEMP TABLE ${context.workingTable} (
11908
+ graph_id TEXT NOT NULL,
11909
+ run_id TEXT NOT NULL,
11910
+ node_id TEXT NOT NULL,
11911
+ node_kind TEXT NOT NULL,
11912
+ label_id TEXT NOT NULL,
11913
+ label_kind TEXT NOT NULL,
11914
+ next_label_id TEXT NOT NULL,
11915
+ next_label_kind TEXT NOT NULL,
11916
+ PRIMARY KEY (graph_id, run_id, node_kind, node_id)
11917
+ )
11918
+ `;
11919
+ }
11920
+ async function initializeWorkingTable(context, nodeKinds) {
11921
+ const { operation, workingTable, graphId, runId } = context;
11922
+ const nodeKindFilter = nodeKinds === void 0 ? sql`TRUE` : compileKindFilter(sql.raw("n.kind"), nodeKinds);
11923
+ await context.executeTemporary(sql`
11924
+ INSERT INTO ${workingTable}
11925
+ (graph_id, run_id, node_id, node_kind, label_id, label_kind,
11926
+ next_label_id, next_label_kind)
11927
+ SELECT
11928
+ ${graphId}, ${runId}, n.id, n.kind, n.id, n.kind, n.id, n.kind
11929
+ FROM ${operation.schema.nodesTable} n
11930
+ WHERE n.graph_id = ${graphId}
11931
+ AND ${nodeKindFilter}
11932
+ AND ${operation.nodeTemporalFilter}
11933
+ `);
11934
+ const rows = await context.backend.execute(
11935
+ asCompiledRowsSql(sql`
11936
+ SELECT COUNT(*) AS count
11937
+ FROM ${workingTable}
11938
+ WHERE graph_id = ${graphId} AND run_id = ${runId}
11939
+ `)
11940
+ );
11941
+ const workingTableSize = Number(rows[0]?.count ?? 0);
11942
+ return { changedCount: workingTableSize, workingTableSize };
11943
+ }
11944
+ async function runLabelPropagationRound(context, state) {
11945
+ await resetNextLabels(context);
11946
+ for (const edgeKinds of context.operation.edgeKindChunks) {
11947
+ await propagateChunkLabels(context, edgeKinds);
11948
+ }
11949
+ const changedRows = await applyNextLabels(context);
11950
+ return {
11951
+ changedCount: changedRows.length,
11952
+ workingTableSize: state.workingTableSize
11953
+ };
11954
+ }
11955
+ async function resetNextLabels(context) {
11956
+ await context.executeTemporary(sql`
11957
+ UPDATE ${context.workingTable}
11958
+ SET next_label_id = label_id, next_label_kind = label_kind
11959
+ WHERE graph_id = ${context.graphId} AND run_id = ${context.runId}
11960
+ `);
11961
+ }
11962
+ async function propagateChunkLabels(context, edgeKinds) {
11963
+ const { operation, workingTable, graphId, runId } = context;
11964
+ const expansion = compileWorkingTableEdgeExpansion(
11965
+ operation,
11966
+ workingTable,
11967
+ sql`w.graph_id = ${graphId} AND w.run_id = ${runId}`,
11968
+ "both",
11969
+ edgeKinds
11970
+ );
11971
+ const candidateId = operation.ctx.dialect.binaryText(sql`ranked.label_id`);
11972
+ const candidateKind = operation.ctx.dialect.binaryText(
11973
+ sql`ranked.label_kind`
11974
+ );
11975
+ const currentId = operation.ctx.dialect.binaryText(sql`target.next_label_id`);
11976
+ const currentKind = operation.ctx.dialect.binaryText(
11977
+ sql`target.next_label_kind`
11978
+ );
11979
+ const sourceId = operation.ctx.dialect.binaryText(sql`source.label_id`);
11980
+ const sourceKind = operation.ctx.dialect.binaryText(sql`source.label_kind`);
11981
+ const targetId = operation.ctx.dialect.binaryText(sql`source.target_id`);
11982
+ const targetKind = operation.ctx.dialect.binaryText(sql`source.target_kind`);
11983
+ await context.executeTemporary(sql`
11984
+ WITH candidates AS (
11985
+ SELECT
11986
+ expanded.target_id,
11987
+ expanded.target_kind,
11988
+ source.label_id,
11989
+ source.label_kind
11990
+ FROM (${expansion}) expanded
11991
+ JOIN ${workingTable} source
11992
+ ON source.graph_id = ${graphId}
11993
+ AND source.run_id = ${runId}
11994
+ AND source.node_id = expanded.source_id
11995
+ AND source.node_kind = expanded.source_kind
11996
+ JOIN ${workingTable} scoped_target
11997
+ ON scoped_target.graph_id = ${graphId}
11998
+ AND scoped_target.run_id = ${runId}
11999
+ AND scoped_target.node_id = expanded.target_id
12000
+ AND scoped_target.node_kind = expanded.target_kind
12001
+ ), ranked AS (
12002
+ SELECT
12003
+ target_id,
12004
+ target_kind,
12005
+ label_id,
12006
+ label_kind,
12007
+ ROW_NUMBER() OVER (
12008
+ PARTITION BY ${targetKind}, ${targetId}
12009
+ ORDER BY ${sourceId}, ${sourceKind}
12010
+ ) AS candidate_rank
12011
+ FROM candidates source
12012
+ )
12013
+ UPDATE ${workingTable} AS target
12014
+ SET next_label_id = ranked.label_id,
12015
+ next_label_kind = ranked.label_kind
12016
+ FROM ranked
12017
+ WHERE target.graph_id = ${graphId}
12018
+ AND target.run_id = ${runId}
12019
+ AND target.node_id = ranked.target_id
12020
+ AND target.node_kind = ranked.target_kind
12021
+ AND ranked.candidate_rank = 1
12022
+ AND (
12023
+ ${candidateId} < ${currentId}
12024
+ OR (
12025
+ ${candidateId} = ${currentId}
12026
+ AND ${candidateKind} < ${currentKind}
12027
+ )
12028
+ )
12029
+ `);
12030
+ }
12031
+ function applyNextLabels(context) {
12032
+ return context.backend.execute(
12033
+ asCompiledRowsSql(sql`
12034
+ UPDATE ${context.workingTable}
12035
+ SET label_id = next_label_id, label_kind = next_label_kind
12036
+ WHERE graph_id = ${context.graphId}
12037
+ AND run_id = ${context.runId}
12038
+ AND (
12039
+ label_id <> next_label_id OR label_kind <> next_label_kind
12040
+ )
12041
+ RETURNING node_id
12042
+ `)
12043
+ );
12044
+ }
12045
+ async function extractMemberships(context) {
12046
+ const { operation } = context;
12047
+ const componentId = operation.ctx.dialect.binaryText(sql`label_id`);
12048
+ const componentKind = operation.ctx.dialect.binaryText(sql`label_kind`);
12049
+ const nodeId = operation.ctx.dialect.binaryText(sql`node_id`);
12050
+ const nodeKind = operation.ctx.dialect.binaryText(sql`node_kind`);
12051
+ const rows = await context.backend.execute(
12052
+ asCompiledRowsSql(sql`
12053
+ SELECT
12054
+ node_id,
12055
+ node_kind,
12056
+ label_id AS component_id,
12057
+ label_kind AS component_kind,
12058
+ COUNT(*) OVER (
12059
+ PARTITION BY ${componentKind}, ${componentId}
12060
+ ) AS component_size
12061
+ FROM ${context.workingTable}
12062
+ WHERE graph_id = ${context.graphId} AND run_id = ${context.runId}
12063
+ ORDER BY ${componentId}, ${componentKind}, ${nodeId}, ${nodeKind}
12064
+ `)
12065
+ );
12066
+ return rows.map((row) => ({
12067
+ id: row.node_id,
12068
+ kind: row.node_kind,
12069
+ componentId: row.component_id,
12070
+ componentKind: row.component_kind,
12071
+ size: Number(row.component_size)
12072
+ })).toSorted(compareMemberships);
12073
+ }
12074
+ function compareMemberships(left, right) {
12075
+ return compareCodePoints(left.componentId, right.componentId) || compareCodePoints(left.componentKind, right.componentKind) || compareCodePoints(left.id, right.id) || compareCodePoints(left.kind, right.kind);
12076
+ }
12077
+ var DEFAULT_WEIGHTED_SHORTEST_PATH_MAX_ITERATIONS = 1e3;
12078
+ var WEIGHTED_ALGORITHM_NAME = "weightedShortestPath";
12079
+ var MAX_EDGE_WEIGHT = Number.MAX_VALUE / 2 ** 64;
12080
+ var MIN_EDGE_WEIGHT = Number.MIN_VALUE;
12081
+ async function executeWeightedShortestPath(ctx, sourceId, targetId, options) {
12082
+ assertEdgeKinds(options.edges);
12083
+ assertWeightOptions(options.weightProperty, options.defaultWeight);
12084
+ const maxIterations = resolveMaxIterations(
12085
+ options.maxIterations,
12086
+ DEFAULT_WEIGHTED_SHORTEST_PATH_MAX_ITERATIONS,
12087
+ WEIGHTED_ALGORITHM_NAME
12088
+ );
12089
+ const traversalOptions = options;
10645
12090
  if (sourceId === targetId) {
10646
- const source = await fetchNodeKind(ctx, sourceId, options);
10647
- if (source === void 0) return void 0;
10648
- return { nodes: [source], depth: 0 };
12091
+ return findWeightedSelfPath(ctx, sourceId, traversalOptions);
10649
12092
  }
10650
- const cte = buildReachableCte({
10651
- graphId: ctx.graphId,
12093
+ if (supportsTemporaryIteration(ctx)) {
12094
+ return findWeightedShortestPathInWorkingTable(
12095
+ ctx,
12096
+ sourceId,
12097
+ targetId,
12098
+ maxIterations,
12099
+ traversalOptions
12100
+ );
12101
+ }
12102
+ return findWeightedShortestPathInline(
12103
+ ctx,
10652
12104
  sourceId,
10653
- edgeKinds: options.edges,
10654
- maxHops,
10655
- direction: options.direction ?? "out",
10656
- cyclePolicy: options.cyclePolicy ?? "prevent",
10657
- includePath: true,
10658
- ...resolveTemporalOptions(ctx, options),
10659
- dialect: ctx.dialect,
10660
- schema: ctx.schema,
10661
- ...ctx.recordedReadBinding === void 0 ? {} : { recordedReadBinding: ctx.recordedReadBinding }
12105
+ targetId,
12106
+ maxIterations,
12107
+ traversalOptions
12108
+ );
12109
+ }
12110
+ function assertWeightOptions(weightProperty, defaultWeight) {
12111
+ if (typeof weightProperty !== "string" || weightProperty.length === 0) {
12112
+ throw new ConfigurationError(
12113
+ `${WEIGHTED_ALGORITHM_NAME} weightProperty must be a non-empty string.`,
12114
+ { weightProperty }
12115
+ );
12116
+ }
12117
+ if (defaultWeight !== void 0 && (!Number.isFinite(defaultWeight) || defaultWeight < 0 || defaultWeight > MAX_EDGE_WEIGHT)) {
12118
+ throw new ConfigurationError(
12119
+ `${WEIGHTED_ALGORITHM_NAME} defaultWeight must be a non-negative number no greater than ${MAX_EDGE_WEIGHT}, got ${String(defaultWeight)}.`,
12120
+ { defaultWeight, maximum: MAX_EDGE_WEIGHT }
12121
+ );
12122
+ }
12123
+ }
12124
+ async function assertValidEdgeWeights(operation, weightProperty, defaultWeight) {
12125
+ const { dialect } = operation.ctx;
12126
+ const pointer = jsonPointer([weightProperty]);
12127
+ const propsColumn = sql.raw("e.props");
12128
+ const isNumber = dialect.jsonPathIsNumber(propsColumn, pointer);
12129
+ const isMissing = dialect.jsonPathIsNull(propsColumn, pointer);
12130
+ const weightText = dialect.jsonExtractText(propsColumn, pointer);
12131
+ const weightNumber = sql`CASE WHEN ${isNumber} THEN ${dialect.jsonExtractNumber(propsColumn, pointer)} END`;
12132
+ const missingViolation = defaultWeight === void 0 ? sql`audited.is_missing = 1` : dialect.booleanLiteral(false);
12133
+ const negativeViolation = sql`audited.is_number = 1 AND audited.weight_number < 0`;
12134
+ const outOfRangeViolation = sql`audited.is_number = 1 AND (ABS(audited.weight_number) > CAST(${MAX_EDGE_WEIGHT} AS NUMERIC) OR (audited.weight_number <> 0 AND ABS(audited.weight_number) < CAST(${MIN_EDGE_WEIGHT} AS NUMERIC)))`;
12135
+ for (const edgeKinds of operation.edgeKindChunks) {
12136
+ const edgeKindFilter = compileKindFilter(sql.raw("e.kind"), edgeKinds);
12137
+ const rows = await operation.backend.execute(
12138
+ asCompiledRowsSql(sql`
12139
+ SELECT audited.edge_id, audited.edge_kind, audited.weight_text,
12140
+ audited.is_number, audited.is_missing
12141
+ FROM (
12142
+ SELECT e.id AS edge_id, e.kind AS edge_kind,
12143
+ ${weightText} AS weight_text,
12144
+ ${weightNumber} AS weight_number,
12145
+ CASE WHEN ${isNumber} THEN 1 ELSE 0 END AS is_number,
12146
+ CASE WHEN ${isMissing} THEN 1 ELSE 0 END AS is_missing
12147
+ FROM ${operation.schema.edgesTable} e
12148
+ WHERE e.graph_id = ${operation.ctx.graphId}
12149
+ AND ${edgeKindFilter}
12150
+ AND ${operation.edgeTemporalFilter}
12151
+ ) audited
12152
+ WHERE (${missingViolation})
12153
+ OR (audited.is_missing = 0 AND audited.is_number = 0)
12154
+ OR (${negativeViolation})
12155
+ OR (${outOfRangeViolation})
12156
+ ORDER BY ${dialect.binaryText(sql`audited.edge_id`)}
12157
+ LIMIT 1
12158
+ `)
12159
+ );
12160
+ const row = rows[0];
12161
+ if (row !== void 0) {
12162
+ throw createInvalidEdgeWeightError(row, weightProperty);
12163
+ }
12164
+ }
12165
+ }
12166
+ function createInvalidEdgeWeightError(row, weightProperty) {
12167
+ const reason = resolveInvalidWeightReason(row);
12168
+ const value = reason === "missing" || row.weight_text === null ? void 0 : formatWeightValue(row.weight_text);
12169
+ return new InvalidEdgeWeightError({
12170
+ edgeId: row.edge_id,
12171
+ edgeKind: row.edge_kind,
12172
+ property: weightProperty,
12173
+ reason,
12174
+ ...value === void 0 ? {} : { value }
10662
12175
  });
10663
- const pathContainsCheck = sql`NOT (${ctx.dialect.cycleCheck(sql.raw("r.id"), sql.raw("h.path"))})`;
10664
- const query = sql`${cte}, hit AS (SELECT id, kind, depth, path FROM reachable WHERE id = ${targetId} ORDER BY depth ASC LIMIT 1) SELECT h.id AS hit_id, h.kind AS hit_kind, h.depth AS hit_depth, h.path AS hit_path, r.id AS node_id, r.kind AS node_kind FROM hit h LEFT JOIN reachable r ON ${pathContainsCheck}`;
10665
- const rows = await ctx.backend.execute(
10666
- asCompiledRowsSql(query)
12176
+ }
12177
+ function resolveInvalidWeightReason(row) {
12178
+ if (flagIsSet(row.is_missing)) return "missing";
12179
+ if (!flagIsSet(row.is_number)) return "non_numeric";
12180
+ return Number(row.weight_text) < 0 ? "negative" : "out_of_range";
12181
+ }
12182
+ function flagIsSet(value) {
12183
+ return Number(value) !== 0;
12184
+ }
12185
+ function formatWeightValue(value) {
12186
+ if (typeof value === "string") return value;
12187
+ if (typeof value === "number" || typeof value === "bigint" || typeof value === "boolean") {
12188
+ return String(value);
12189
+ }
12190
+ return JSON.stringify(value);
12191
+ }
12192
+ function compareNodeIdentityCodePoints(left, right) {
12193
+ return compareCodePoints(left.id, right.id) || compareCodePoints(left.kind, right.kind);
12194
+ }
12195
+ async function findWeightedShortestPathInWorkingTable(ctx, sourceId, targetId, maxIterations, options) {
12196
+ return runIterativeGraphOperation(ctx, options, {
12197
+ algorithm: WEIGHTED_ALGORITHM_NAME,
12198
+ maxIterations,
12199
+ createWorkingTable: createWorkingTable3,
12200
+ async initialize(context) {
12201
+ await assertValidEdgeWeights(
12202
+ context.operation,
12203
+ options.weightProperty,
12204
+ options.defaultWeight
12205
+ );
12206
+ if (!await hasVisibleNode(context.operation, targetId)) {
12207
+ return {
12208
+ frontierCount: 0,
12209
+ workingTableSize: 0,
12210
+ bestTargetDistance: void 0
12211
+ };
12212
+ }
12213
+ await context.executeTemporary(sql`
12214
+ CREATE INDEX ${frontierIndexIdentifier(context)}
12215
+ ON ${context.workingTable} (graph_id, run_id, improved_round)
12216
+ `);
12217
+ const seeded = await seedDistances(context, sourceId);
12218
+ return {
12219
+ frontierCount: seeded.length,
12220
+ workingTableSize: seeded.length,
12221
+ // Seeds carry the source id, never the target's — the self-path
12222
+ // case is short-circuited before this plan runs.
12223
+ bestTargetDistance: void 0
12224
+ };
12225
+ },
12226
+ async runRound(context, state, iteration) {
12227
+ const round = await relaxFrontierRound(
12228
+ context,
12229
+ targetId,
12230
+ iteration,
12231
+ state.bestTargetDistance
12232
+ );
12233
+ return {
12234
+ frontierCount: round.frontierCount,
12235
+ // Overcounts by re-improved existing rows (RETURNING includes
12236
+ // updates); acceptable because the size only paces the substrate's
12237
+ // ANALYZE refresh, where too-early merely refreshes stats sooner.
12238
+ workingTableSize: state.workingTableSize + round.frontierCount,
12239
+ bestTargetDistance: round.bestTargetDistance
12240
+ };
12241
+ },
12242
+ hasConverged(state) {
12243
+ return state.frontierCount === 0;
12244
+ },
12245
+ async extractResult(context) {
12246
+ return extractPathFromWorkingTable(context, targetId, maxIterations);
12247
+ }
12248
+ });
12249
+ }
12250
+ function frontierIndexIdentifier(context) {
12251
+ return sql.identifier(
12252
+ `typegraph_iterative_${context.runId.replaceAll("-", "_")}_frontier`
10667
12253
  );
10668
- const first = rows[0];
10669
- if (first === void 0) return void 0;
10670
- const pathIds = normalizePath(first.hit_path);
10671
- const kindById = buildKindIndex(rows);
10672
- const pathNodes = pathIds.map((id) => ({
10673
- id,
10674
- kind: kindById.get(id) ?? ""
10675
- }));
10676
- return { nodes: pathNodes, depth: Number(first.hit_depth) };
10677
12254
  }
10678
- function buildKindIndex(rows) {
10679
- const map = /* @__PURE__ */ new Map();
10680
- for (const row of rows) {
10681
- if (row.node_id !== void 0 && row.node_kind !== void 0) {
10682
- map.set(row.node_id, row.node_kind);
12255
+ async function hasVisibleNode(operation, nodeId) {
12256
+ const rows = await operation.backend.execute(
12257
+ asCompiledRowsSql(sql`
12258
+ SELECT n.id FROM ${operation.schema.nodesTable} n
12259
+ WHERE n.graph_id = ${operation.ctx.graphId}
12260
+ AND n.id = ${nodeId}
12261
+ AND ${operation.nodeTemporalFilter}
12262
+ LIMIT 1
12263
+ `)
12264
+ );
12265
+ return rows.length > 0;
12266
+ }
12267
+ function createWorkingTable3(context) {
12268
+ return sql`
12269
+ CREATE TEMP TABLE ${context.workingTable} (
12270
+ graph_id TEXT NOT NULL,
12271
+ run_id TEXT NOT NULL,
12272
+ node_id TEXT NOT NULL,
12273
+ node_kind TEXT NOT NULL,
12274
+ distance DOUBLE PRECISION NOT NULL,
12275
+ hops INTEGER NOT NULL,
12276
+ predecessor_id TEXT,
12277
+ predecessor_kind TEXT,
12278
+ improved_round INTEGER NOT NULL,
12279
+ PRIMARY KEY (graph_id, run_id, node_kind, node_id)
12280
+ )
12281
+ `;
12282
+ }
12283
+ async function seedDistances(context, sourceId) {
12284
+ const { operation, workingTable, graphId, runId } = context;
12285
+ return context.backend.execute(
12286
+ asCompiledRowsSql(sql`
12287
+ INSERT INTO ${workingTable}
12288
+ (graph_id, run_id, node_id, node_kind, distance, hops,
12289
+ predecessor_id, predecessor_kind, improved_round)
12290
+ SELECT ${graphId}, ${runId}, n.id, n.kind, 0, 0, NULL, NULL, 0
12291
+ FROM ${operation.schema.nodesTable} n
12292
+ WHERE n.graph_id = ${graphId}
12293
+ AND n.id = ${sourceId}
12294
+ AND ${operation.nodeTemporalFilter}
12295
+ ON CONFLICT (graph_id, run_id, node_kind, node_id) DO NOTHING
12296
+ RETURNING node_id
12297
+ `)
12298
+ );
12299
+ }
12300
+ async function relaxFrontierRound(context, targetId, iteration, roundBound) {
12301
+ const { operation, workingTable, graphId, runId } = context;
12302
+ const { dialect } = operation.ctx;
12303
+ let frontierCount = 0;
12304
+ let bestTargetDistance = roundBound;
12305
+ for (const edgeKinds of operation.edgeKindChunks) {
12306
+ const sourceFilter = sql`
12307
+ w.graph_id = ${graphId}
12308
+ AND w.run_id = ${runId}
12309
+ AND w.improved_round = ${iteration - 1}
12310
+ `;
12311
+ const expansion = compileWorkingTableExpansion(
12312
+ operation,
12313
+ workingTable,
12314
+ sourceFilter,
12315
+ operation.direction,
12316
+ edgeKinds
12317
+ );
12318
+ const rows = await context.backend.execute(
12319
+ asCompiledRowsSql(sql`
12320
+ INSERT INTO ${workingTable}
12321
+ (graph_id, run_id, node_id, node_kind, distance, hops,
12322
+ predecessor_id, predecessor_kind, improved_round)
12323
+ SELECT
12324
+ ${graphId}, ${runId}, ranked.target_id, ranked.target_kind,
12325
+ ranked.candidate_distance, ranked.candidate_hops,
12326
+ ranked.source_id, ranked.source_kind, ${iteration}
12327
+ FROM (
12328
+ SELECT candidates.*,
12329
+ ROW_NUMBER() OVER (
12330
+ PARTITION BY ${dialect.binaryText(sql`candidates.target_kind`)},
12331
+ ${dialect.binaryText(sql`candidates.target_id`)}
12332
+ ORDER BY candidates.candidate_distance,
12333
+ ${dialect.binaryText(sql`candidates.source_id`)},
12334
+ ${dialect.binaryText(sql`candidates.source_kind`)}
12335
+ ) AS candidate_rank
12336
+ FROM (
12337
+ SELECT expanded.source_id, expanded.source_kind,
12338
+ expanded.target_id, expanded.target_kind,
12339
+ frontier.distance + expanded.weight AS candidate_distance,
12340
+ frontier.hops + 1 AS candidate_hops
12341
+ FROM (${expansion}) expanded
12342
+ JOIN ${workingTable} frontier
12343
+ ON frontier.graph_id = ${graphId}
12344
+ AND frontier.run_id = ${runId}
12345
+ AND frontier.node_id = expanded.source_id
12346
+ AND frontier.node_kind = expanded.source_kind
12347
+ ) candidates
12348
+ WHERE ${roundBound === void 0 ? sql`TRUE` : sql`candidates.candidate_distance <= ${roundBound}`}
12349
+ ) ranked
12350
+ WHERE ranked.candidate_rank = 1
12351
+ ON CONFLICT (graph_id, run_id, node_kind, node_id) DO UPDATE SET
12352
+ distance = excluded.distance,
12353
+ hops = excluded.hops,
12354
+ predecessor_id = excluded.predecessor_id,
12355
+ predecessor_kind = excluded.predecessor_kind,
12356
+ improved_round = excluded.improved_round
12357
+ WHERE excluded.distance < ${workingTable}.distance
12358
+ RETURNING node_id, distance
12359
+ `)
12360
+ );
12361
+ frontierCount += rows.length;
12362
+ for (const row of rows) {
12363
+ if (row.node_id !== targetId) continue;
12364
+ const distance = Number(row.distance);
12365
+ if (bestTargetDistance === void 0 || distance < bestTargetDistance) {
12366
+ bestTargetDistance = distance;
12367
+ }
10683
12368
  }
10684
12369
  }
10685
- return map;
12370
+ return { frontierCount, bestTargetDistance };
10686
12371
  }
10687
- async function fetchNodeKind(ctx, nodeId, options) {
10688
- const temporalFilter = resolveTemporalFilter(ctx, options);
10689
- const schema = resolveReadSchema(ctx, options);
10690
- const query = sql`SELECT id, kind FROM ${schema.nodesTable} WHERE graph_id = ${ctx.graphId} AND id = ${nodeId} AND ${temporalFilter} LIMIT 1`;
10691
- const rows = await ctx.backend.execute(asCompiledRowsSql(query));
10692
- const row = rows[0];
10693
- if (row === void 0) return void 0;
10694
- return { id: row.id, kind: row.kind };
12372
+ async function extractPathFromWorkingTable(context, targetId, maxIterations) {
12373
+ const { operation, workingTable, graphId, runId } = context;
12374
+ const { dialect } = operation.ctx;
12375
+ const rows = await context.backend.execute(
12376
+ asCompiledRowsSql(sql`
12377
+ WITH RECURSIVE chain AS (
12378
+ SELECT best.node_id, best.node_kind, best.distance, best.hops,
12379
+ best.predecessor_id, best.predecessor_kind, 0 AS position
12380
+ FROM (
12381
+ SELECT node_id, node_kind, distance, hops,
12382
+ predecessor_id, predecessor_kind
12383
+ FROM ${workingTable}
12384
+ WHERE graph_id = ${graphId}
12385
+ AND run_id = ${runId}
12386
+ AND node_id = ${targetId}
12387
+ ORDER BY distance ASC, ${dialect.binaryText(sql`node_kind`)}
12388
+ LIMIT 1
12389
+ ) best
12390
+ UNION ALL
12391
+ SELECT w.node_id, w.node_kind, w.distance, w.hops,
12392
+ w.predecessor_id, w.predecessor_kind, c.position + 1
12393
+ FROM chain c
12394
+ JOIN ${workingTable} w
12395
+ ON w.graph_id = ${graphId}
12396
+ AND w.run_id = ${runId}
12397
+ AND w.node_id = c.predecessor_id
12398
+ AND w.node_kind = c.predecessor_kind
12399
+ WHERE c.position <= ${maxIterations}
12400
+ )
12401
+ SELECT node_id, node_kind, distance, hops
12402
+ FROM chain
12403
+ ORDER BY position DESC
12404
+ `)
12405
+ );
12406
+ const target = rows.at(-1);
12407
+ if (target === void 0) return void 0;
12408
+ return {
12409
+ nodes: rows.map((row) => ({ id: row.node_id, kind: row.node_kind })),
12410
+ depth: Number(target.hops),
12411
+ totalWeight: Number(target.distance)
12412
+ };
12413
+ }
12414
+ async function findWeightedSelfPath(ctx, nodeId, options) {
12415
+ return withInlineIterativeGraphOperation(ctx, options, async (operation) => {
12416
+ await assertValidEdgeWeights(
12417
+ operation,
12418
+ options.weightProperty,
12419
+ options.defaultWeight
12420
+ );
12421
+ const nodes = await fetchVisibleWorkingNodes(operation, [nodeId]);
12422
+ const node = nodes.toSorted(
12423
+ (left, right) => compareNodeIdentityCodePoints(left, right)
12424
+ )[0];
12425
+ if (node === void 0) return;
12426
+ return { nodes: [node], depth: 0, totalWeight: 0 };
12427
+ });
12428
+ }
12429
+ async function findWeightedShortestPathInline(ctx, sourceId, targetId, maxIterations, options) {
12430
+ return withInlineIterativeGraphOperation(ctx, options, async (operation) => {
12431
+ await assertValidEdgeWeights(
12432
+ operation,
12433
+ options.weightProperty,
12434
+ options.defaultWeight
12435
+ );
12436
+ const endpoints = await fetchVisibleWorkingNodes(operation, [
12437
+ sourceId,
12438
+ targetId
12439
+ ]);
12440
+ const sources = endpoints.filter((node) => node.id === sourceId);
12441
+ if (sources.length === 0 || !endpoints.some((node) => node.id === targetId)) {
12442
+ return;
12443
+ }
12444
+ const settled = new Map(
12445
+ sources.map((source) => [
12446
+ nodeIdentityKey(source),
12447
+ {
12448
+ id: source.id,
12449
+ kind: source.kind,
12450
+ distance: 0,
12451
+ hops: 0,
12452
+ parentKey: void 0
12453
+ }
12454
+ ])
12455
+ );
12456
+ let bestTargetDistance;
12457
+ let frontier = sources;
12458
+ let rounds = 0;
12459
+ while (frontier.length > 0) {
12460
+ if (rounds >= maxIterations) {
12461
+ throw new GraphAlgorithmConvergenceError(
12462
+ WEIGHTED_ALGORITHM_NAME,
12463
+ maxIterations
12464
+ );
12465
+ }
12466
+ rounds++;
12467
+ const candidates = await reduceExpandedWorkingSet(
12468
+ operation,
12469
+ frontier,
12470
+ operation.direction,
12471
+ (existing, expansion) => reduceWeightedCandidate(existing, expansion, settled)
12472
+ );
12473
+ const roundBound = bestTargetDistance;
12474
+ const improved = [];
12475
+ for (const [candidateKey, candidate] of candidates) {
12476
+ const existing = settled.get(candidateKey);
12477
+ if (existing !== void 0 && existing.distance <= candidate.distance) {
12478
+ continue;
12479
+ }
12480
+ if (roundBound !== void 0 && candidate.distance > roundBound) {
12481
+ continue;
12482
+ }
12483
+ settled.set(candidateKey, {
12484
+ id: candidate.id,
12485
+ kind: candidate.kind,
12486
+ distance: candidate.distance,
12487
+ hops: candidate.hops,
12488
+ parentKey: nodeIdentityKey({
12489
+ id: candidate.parentId,
12490
+ kind: candidate.parentKind
12491
+ })
12492
+ });
12493
+ improved.push({ id: candidate.id, kind: candidate.kind });
12494
+ if (candidate.id === targetId && (bestTargetDistance === void 0 || candidate.distance < bestTargetDistance)) {
12495
+ bestTargetDistance = candidate.distance;
12496
+ }
12497
+ }
12498
+ frontier = improved.toSorted(
12499
+ (left, right) => compareNodeIdentityCodePoints(left, right)
12500
+ );
12501
+ }
12502
+ return buildWeightedPathResult(settled, targetId);
12503
+ });
12504
+ }
12505
+ function reduceWeightedCandidate(existing, expansion, settled) {
12506
+ const weight = expansion.weight;
12507
+ if (weight === void 0) return existing;
12508
+ const sourceEntry = settled.get(nodeIdentityKey(expansion.source));
12509
+ if (sourceEntry === void 0) return existing;
12510
+ const candidateDistance = sourceEntry.distance + weight;
12511
+ if (existing !== void 0 && (existing.distance < candidateDistance || existing.distance === candidateDistance && compareNodeIdentityCodePoints(
12512
+ { id: existing.parentId, kind: existing.parentKind },
12513
+ expansion.source
12514
+ ) <= 0)) {
12515
+ return existing;
12516
+ }
12517
+ return {
12518
+ id: expansion.target.id,
12519
+ kind: expansion.target.kind,
12520
+ distance: candidateDistance,
12521
+ hops: sourceEntry.hops + 1,
12522
+ parentId: expansion.source.id,
12523
+ parentKind: expansion.source.kind
12524
+ };
12525
+ }
12526
+ function buildWeightedPathResult(visited, targetId) {
12527
+ let target;
12528
+ for (const node of visited.values()) {
12529
+ if (node.id !== targetId) continue;
12530
+ if (target === void 0 || node.distance < target.distance || node.distance === target.distance && compareNodeIdentityCodePoints(node, target) < 0) {
12531
+ target = node;
12532
+ }
12533
+ }
12534
+ if (target === void 0) return void 0;
12535
+ const reversedNodes = [];
12536
+ let cursor = target;
12537
+ while (cursor !== void 0) {
12538
+ reversedNodes.push({ id: cursor.id, kind: cursor.kind });
12539
+ cursor = cursor.parentKey === void 0 ? void 0 : visited.get(cursor.parentKey);
12540
+ }
12541
+ return {
12542
+ nodes: reversedNodes.toReversed(),
12543
+ depth: target.hops,
12544
+ totalWeight: target.distance
12545
+ };
10695
12546
  }
10696
12547
 
10697
12548
  // src/store/algorithms/index.ts
@@ -10704,7 +12555,7 @@ function assertRecordedAsOfInternalOnly(options, method, allowRecordedAsOf) {
10704
12555
  code: "ALGORITHM_RECORDED_ASOF_INTERNAL_ONLY",
10705
12556
  message: `recordedAsOf is only available through store.asOfRecorded(...).${method}(...).`,
10706
12557
  context: { method },
10707
- suggestion: "Use store.asOfRecorded(recordedAt).reachable/shortestPath/canReach/neighbors/degree(...) instead of passing recordedAsOf directly."
12558
+ suggestion: "Use store.asOfRecorded(recordedAt) or a recorded StoreView instead of passing recordedAsOf directly."
10708
12559
  });
10709
12560
  }
10710
12561
  function createGraphAlgorithms(params) {
@@ -10733,6 +12584,19 @@ function createGraphAlgorithms(params) {
10733
12584
  options
10734
12585
  );
10735
12586
  },
12587
+ weightedShortestPath(from, to, options) {
12588
+ assertRecordedAsOfInternalOnly(
12589
+ options,
12590
+ "weightedShortestPath",
12591
+ allowRecordedAsOf
12592
+ );
12593
+ return executeWeightedShortestPath(
12594
+ ctx,
12595
+ resolveNodeId(from),
12596
+ resolveNodeId(to),
12597
+ options
12598
+ );
12599
+ },
10736
12600
  reachable(from, options) {
10737
12601
  assertRecordedAsOfInternalOnly(options, "reachable", allowRecordedAsOf);
10738
12602
  return executeReachable(ctx, resolveNodeId(from), options);
@@ -10753,6 +12617,14 @@ function createGraphAlgorithms(params) {
10753
12617
  degree(node, options) {
10754
12618
  assertRecordedAsOfInternalOnly(options, "degree", allowRecordedAsOf);
10755
12619
  return executeDegree(ctx, resolveNodeId(node), options ?? {});
12620
+ },
12621
+ weaklyConnectedComponents(options) {
12622
+ assertRecordedAsOfInternalOnly(
12623
+ options,
12624
+ "weaklyConnectedComponents",
12625
+ allowRecordedAsOf
12626
+ );
12627
+ return executeWeaklyConnectedComponents(ctx, options);
10756
12628
  }
10757
12629
  };
10758
12630
  }
@@ -10955,32 +12827,70 @@ function createEdgeCollection(config) {
10955
12827
  const upsertAll = async (target) => {
10956
12828
  const ids = items.map((item) => item.id);
10957
12829
  const existingMap = await getEdgeRowsByIds(target, graphId, ids);
12830
+ const results2 = Array.from({ length: items.length });
10958
12831
  const toCreate = [];
10959
12832
  const toUpdate = [];
12833
+ const pending = /* @__PURE__ */ new Map();
12834
+ const deferred = [];
10960
12835
  let itemIndex = 0;
10961
12836
  for (const item of items) {
10962
- const existing = existingMap.get(item.id);
10963
- if (existing) {
10964
- toUpdate.push({
12837
+ const pendingEntry = pending.get(item.id);
12838
+ const original = existingMap.get(item.id);
12839
+ const inputProps = item.props ?? {};
12840
+ if (pendingEntry === void 0 && original === void 0) {
12841
+ toCreate.push({
10965
12842
  index: itemIndex,
10966
- input: buildUpdateEdgeInput(item.id, item.props ?? {}, item),
10967
- clearDeleted: existing.deleted_at !== void 0
12843
+ input: buildCreateEdgeInput(
12844
+ kind,
12845
+ item.from,
12846
+ item.to,
12847
+ inputProps,
12848
+ item
12849
+ )
10968
12850
  });
12851
+ itemIndex++;
12852
+ continue;
12853
+ }
12854
+ const deletedAt = pendingEntry === void 0 ? original.deleted_at : void 0;
12855
+ let dirty;
12856
+ if (config.upsertDirtyCheck !== void 0) {
12857
+ const currentProps = pendingEntry?.props ?? rowPropsToObject(original.props);
12858
+ try {
12859
+ dirty = config.upsertDirtyCheck(
12860
+ original?.kind ?? kind,
12861
+ item.id,
12862
+ currentProps,
12863
+ inputProps
12864
+ );
12865
+ } catch {
12866
+ dirty = void 0;
12867
+ }
12868
+ }
12869
+ const coalesce = dirty?.unchanged === true && deletedAt === void 0 && item.validFrom === void 0 && item.validTo === void 0;
12870
+ if (coalesce) {
12871
+ if (pendingEntry === void 0) {
12872
+ results2[itemIndex] = narrowEdge(rowToEdge2(original));
12873
+ } else {
12874
+ deferred.push({
12875
+ index: itemIndex,
12876
+ sourceIndex: pendingEntry.sourceIndex
12877
+ });
12878
+ }
10969
12879
  } else {
10970
- toCreate.push({
12880
+ toUpdate.push({
10971
12881
  index: itemIndex,
10972
- input: buildCreateEdgeInput(
10973
- kind,
10974
- item.from,
10975
- item.to,
10976
- item.props ?? {},
10977
- item
10978
- )
12882
+ input: buildUpdateEdgeInput(item.id, inputProps, item),
12883
+ clearDeleted: deletedAt !== void 0
10979
12884
  });
12885
+ if (dirty !== void 0) {
12886
+ pending.set(item.id, {
12887
+ props: dirty.validatedProps,
12888
+ sourceIndex: itemIndex
12889
+ });
12890
+ }
10980
12891
  }
10981
12892
  itemIndex++;
10982
12893
  }
10983
- const results2 = Array.from({ length: items.length });
10984
12894
  if (toCreate.length > 0) {
10985
12895
  const createInputs = toCreate.map((entry2) => entry2.input);
10986
12896
  const created = await executeEdgeCreateBatch2(createInputs, target);
@@ -10994,10 +12904,13 @@ function createEdgeCollection(config) {
10994
12904
  });
10995
12905
  results2[entry2.index] = narrowEdge(result);
10996
12906
  }
10997
- return results2;
12907
+ for (const { index, sourceIndex } of deferred) {
12908
+ results2[index] = results2[sourceIndex];
12909
+ }
12910
+ return { results: results2, mutations: toCreate.length + toUpdate.length };
10998
12911
  };
10999
- const results = backend.capabilities.transactions && "transaction" in backend ? await backend.transaction(async (txBackend) => upsertAll(txBackend)) : await upsertAll(backend);
11000
- await config.maybeRefreshStatisticsAfterBulk?.(results.length);
12912
+ const { results, mutations } = backend.capabilities.transactions && "transaction" in backend ? await backend.transaction(async (txBackend) => upsertAll(txBackend)) : await upsertAll(backend);
12913
+ await config.maybeRefreshStatisticsAfterBulk?.(mutations);
11001
12914
  return results;
11002
12915
  },
11003
12916
  async bulkInsert(items) {
@@ -11091,6 +13004,18 @@ function createEdgeCollection(config) {
11091
13004
  };
11092
13005
  }
11093
13006
 
13007
+ // src/store/collections/coalesce.ts
13008
+ function shouldCoalesceUpsert(existing, options, runDirtyCheck) {
13009
+ if (runDirtyCheck === void 0 || existing.deleted_at !== void 0 || options?.validFrom !== void 0 || options?.validTo !== void 0) {
13010
+ return false;
13011
+ }
13012
+ try {
13013
+ return runDirtyCheck().unchanged;
13014
+ } catch {
13015
+ return false;
13016
+ }
13017
+ }
13018
+
11094
13019
  // src/store/collections/node-collection.ts
11095
13020
  function narrowNode(node) {
11096
13021
  return node;
@@ -11218,11 +13143,19 @@ function createNodeCollection(config) {
11218
13143
  async upsertByIdFromRecord(id, data, options) {
11219
13144
  const existing = await backend.getNode(graphId, kind, id);
11220
13145
  if (existing) {
11221
- const clearDeleted = existing.deleted_at !== void 0;
13146
+ const runDirtyCheck = config.upsertDirtyCheck && (() => config.upsertDirtyCheck(
13147
+ kind,
13148
+ id,
13149
+ rowPropsToObject(existing.props),
13150
+ data
13151
+ ));
13152
+ if (shouldCoalesceUpsert(existing, options, runDirtyCheck)) {
13153
+ return narrowNode(rowToNode2(existing));
13154
+ }
11222
13155
  const result2 = await executeNodeUpdate2(
11223
13156
  buildUpdateInput(kind, id, data, options),
11224
13157
  backend,
11225
- { clearDeleted }
13158
+ { clearDeleted: existing.deleted_at !== void 0 }
11226
13159
  );
11227
13160
  return narrowNode(result2);
11228
13161
  }
@@ -11263,26 +13196,63 @@ function createNodeCollection(config) {
11263
13196
  existingMap.set(row.id, row);
11264
13197
  }
11265
13198
  }
13199
+ const results2 = Array.from({ length: items.length });
11266
13200
  const toCreate = [];
11267
13201
  const toUpdate = [];
13202
+ const pending = /* @__PURE__ */ new Map();
13203
+ const deferred = [];
11268
13204
  let itemIndex = 0;
11269
13205
  for (const item of items) {
11270
- const existing = existingMap.get(item.id);
11271
- if (existing) {
11272
- toUpdate.push({
13206
+ const pendingEntry = pending.get(item.id);
13207
+ const original = existingMap.get(item.id);
13208
+ if (pendingEntry === void 0 && original === void 0) {
13209
+ toCreate.push({
11273
13210
  index: itemIndex,
11274
- input: buildUpdateInput(kind, item.id, item.props, item),
11275
- clearDeleted: existing.deleted_at !== void 0
13211
+ input: buildCreateInput(kind, item.props, item)
11276
13212
  });
13213
+ itemIndex++;
13214
+ continue;
13215
+ }
13216
+ const deletedAt = pendingEntry === void 0 ? original.deleted_at : void 0;
13217
+ let dirty;
13218
+ if (config.upsertDirtyCheck !== void 0) {
13219
+ const currentProps = pendingEntry?.props ?? rowPropsToObject(original.props);
13220
+ try {
13221
+ dirty = config.upsertDirtyCheck(
13222
+ kind,
13223
+ item.id,
13224
+ currentProps,
13225
+ item.props
13226
+ );
13227
+ } catch {
13228
+ dirty = void 0;
13229
+ }
13230
+ }
13231
+ const coalesce = dirty?.unchanged === true && deletedAt === void 0 && item.validFrom === void 0 && item.validTo === void 0;
13232
+ if (coalesce) {
13233
+ if (pendingEntry === void 0) {
13234
+ results2[itemIndex] = narrowNode(rowToNode2(original));
13235
+ } else {
13236
+ deferred.push({
13237
+ index: itemIndex,
13238
+ sourceIndex: pendingEntry.sourceIndex
13239
+ });
13240
+ }
11277
13241
  } else {
11278
- toCreate.push({
13242
+ toUpdate.push({
11279
13243
  index: itemIndex,
11280
- input: buildCreateInput(kind, item.props, item)
13244
+ input: buildUpdateInput(kind, item.id, item.props, item),
13245
+ clearDeleted: deletedAt !== void 0
11281
13246
  });
13247
+ if (dirty !== void 0) {
13248
+ pending.set(item.id, {
13249
+ props: dirty.validatedProps,
13250
+ sourceIndex: itemIndex
13251
+ });
13252
+ }
11282
13253
  }
11283
13254
  itemIndex++;
11284
13255
  }
11285
- const results2 = Array.from({ length: items.length });
11286
13256
  if (toCreate.length > 0) {
11287
13257
  const createInputs = toCreate.map((entry2) => entry2.input);
11288
13258
  const created = await executeNodeCreateBatch2(createInputs, target);
@@ -11296,10 +13266,13 @@ function createNodeCollection(config) {
11296
13266
  });
11297
13267
  results2[entry2.index] = narrowNode(result);
11298
13268
  }
11299
- return results2;
13269
+ for (const { index, sourceIndex } of deferred) {
13270
+ results2[index] = results2[sourceIndex];
13271
+ }
13272
+ return { results: results2, mutations: toCreate.length + toUpdate.length };
11300
13273
  };
11301
- const results = backend.capabilities.transactions && "transaction" in backend ? await backend.transaction(async (txBackend) => upsertAll(txBackend)) : await upsertAll(backend);
11302
- await config.maybeRefreshStatisticsAfterBulk?.(results.length);
13274
+ const { results, mutations } = backend.capabilities.transactions && "transaction" in backend ? await backend.transaction(async (txBackend) => upsertAll(txBackend)) : await upsertAll(backend);
13275
+ await config.maybeRefreshStatisticsAfterBulk?.(mutations);
11303
13276
  return results;
11304
13277
  },
11305
13278
  async bulkInsert(items) {
@@ -11513,6 +13486,33 @@ async function ensureFocusedStatusTable(backend, ensureFocused) {
11513
13486
  }
11514
13487
  await backend.bootstrapTables?.();
11515
13488
  }
13489
+ async function runBucketedMaterialization(items, options, bucketKey, runOne) {
13490
+ if (options.stopOnError === true) {
13491
+ const results2 = [];
13492
+ for (const item of items) {
13493
+ const entry2 = await runOne(item);
13494
+ results2.push(entry2);
13495
+ if (entry2.status === "failed") break;
13496
+ }
13497
+ return results2;
13498
+ }
13499
+ const buckets = /* @__PURE__ */ new Map();
13500
+ for (const [index, item] of items.entries()) {
13501
+ const key = bucketKey(item);
13502
+ const bucket = buckets.get(key);
13503
+ if (bucket === void 0) buckets.set(key, [[index, item]]);
13504
+ else bucket.push([index, item]);
13505
+ }
13506
+ const results = Array.from({ length: items.length });
13507
+ await Promise.all(
13508
+ [...buckets.values()].map(async (group) => {
13509
+ for (const [index, item] of group) {
13510
+ results[index] = await runOne(item);
13511
+ }
13512
+ })
13513
+ );
13514
+ return results;
13515
+ }
11516
13516
  async function runMaterialization(items, options, runOne) {
11517
13517
  if (options.stopOnError === true) {
11518
13518
  const results = [];
@@ -11540,18 +13540,10 @@ function delay(ms) {
11540
13540
  }
11541
13541
  async function materializeIndexes(context, options = {}) {
11542
13542
  const { graph, graphId, backend, schemaVersion } = context;
11543
- if (backend.executeDdl === void 0) {
11544
- throw new ConfigurationError(
11545
- "store.materializeIndexes() requires a backend with `executeDdl` support. The bundled SQLite and Postgres backends provide it; a custom backend without `executeDdl` cannot run index DDL.",
11546
- { code: "MATERIALIZE_BACKEND_UNSUPPORTED" }
11547
- );
11548
- }
11549
- if (backend.getIndexMaterialization === void 0 || backend.recordIndexMaterialization === void 0) {
11550
- throw new ConfigurationError(
11551
- "store.materializeIndexes() requires a backend with index materialization status primitives. The bundled SQLite and Postgres backends provide them; a custom backend must implement `getIndexMaterialization` and `recordIndexMaterialization`.",
11552
- { code: "MATERIALIZE_BACKEND_UNSUPPORTED" }
11553
- );
11554
- }
13543
+ assertBackendSupportsIndexMaterialization(
13544
+ backend,
13545
+ "store.materializeIndexes()"
13546
+ );
11555
13547
  const declared = graph.indexes ?? [];
11556
13548
  const kindFilter = options.kinds === void 0 ? void 0 : new Set(options.kinds);
11557
13549
  if (kindFilter !== void 0) {
@@ -11598,65 +13590,42 @@ async function materializeIndexes(context, options = {}) {
11598
13590
  backend,
11599
13591
  relationalPhysicalNames
11600
13592
  );
11601
- const materializeEntry = (declaration) => declaration.entity === "vector" ? materializeVectorIndex(
11602
- declaration,
11603
- backend,
11604
- graphId,
11605
- schemaVersion,
11606
- existingByStatusKey,
11607
- invalidLeftovers
11608
- ) : materializeRelationalIndex(
11609
- declaration,
11610
- backend,
11611
- dialect,
11612
- ddlOptions,
11613
- graphId,
11614
- schemaVersion,
11615
- existingByStatusKey,
11616
- invalidLeftovers
11617
- );
11618
- if (options.stopOnError === true) {
11619
- const results2 = [];
11620
- for (const declaration of candidates) {
11621
- const entry2 = await materializeEntry(declaration);
11622
- results2.push(entry2);
11623
- if (entry2.status === "failed") break;
13593
+ const reservedSystemNames = resolveSystemIndexNames(backend.tableNames);
13594
+ const materializeEntry = (declaration) => {
13595
+ if (declaration.entity !== "vector" && reservedSystemNames.has(declaration.name)) {
13596
+ return Promise.resolve(
13597
+ entry(
13598
+ declaration,
13599
+ "failed",
13600
+ new Error(
13601
+ `Index name "${declaration.name}" collides with a TypeGraph system index. Rename the declaration \u2014 a same-named CREATE INDEX IF NOT EXISTS would silently no-op against the system index instead of creating the declared one.`
13602
+ )
13603
+ )
13604
+ );
11624
13605
  }
11625
- await refreshStatisticsAfterCreation(
13606
+ return declaration.entity === "vector" ? materializeVectorIndex(
13607
+ declaration,
11626
13608
  backend,
11627
- results2,
11628
- options,
11629
- ddlOptions.concurrent
13609
+ graphId,
13610
+ schemaVersion,
13611
+ existingByStatusKey,
13612
+ invalidLeftovers
13613
+ ) : materializeRelationalIndex(
13614
+ declaration,
13615
+ backend,
13616
+ dialect,
13617
+ ddlOptions,
13618
+ graphId,
13619
+ schemaVersion,
13620
+ existingByStatusKey,
13621
+ invalidLeftovers
11630
13622
  );
11631
- return { results: results2 };
11632
- }
11633
- const buckets = /* @__PURE__ */ new Map();
11634
- for (const declaration of candidates) {
11635
- const key = parallelBucketKey(declaration);
11636
- const list = buckets.get(key);
11637
- if (list === void 0) buckets.set(key, [declaration]);
11638
- else list.push(declaration);
11639
- }
11640
- const bucketResults = await Promise.all(
11641
- [...buckets.values()].map(async (group) => {
11642
- const out = [];
11643
- for (const declaration of group) {
11644
- out.push(await materializeEntry(declaration));
11645
- }
11646
- return out;
11647
- })
11648
- );
11649
- const byDeclaration = /* @__PURE__ */ new Map();
11650
- let bucketIndex = 0;
11651
- for (const group of buckets.values()) {
11652
- const entries = bucketResults[bucketIndex];
11653
- for (const [declarationIndex, declaration] of group.entries()) {
11654
- byDeclaration.set(declaration, entries[declarationIndex]);
11655
- }
11656
- bucketIndex += 1;
11657
- }
11658
- const results = candidates.map(
11659
- (declaration) => byDeclaration.get(declaration)
13623
+ };
13624
+ const results = await runBucketedMaterialization(
13625
+ candidates,
13626
+ options,
13627
+ (declaration) => parallelBucketKey(declaration),
13628
+ (declaration) => materializeEntry(declaration)
11660
13629
  );
11661
13630
  await refreshStatisticsAfterCreation(
11662
13631
  backend,
@@ -11713,7 +13682,7 @@ async function materializeRelationalIndex(declaration, backend, dialect, ddlOpti
11713
13682
  await backend.executeDdl(ddl);
11714
13683
  },
11715
13684
  existingByStatusKey,
11716
- invalidLeftovers
13685
+ physicalRebuildPreload: invalidLeftovers
11717
13686
  });
11718
13687
  }
11719
13688
  async function materializeVectorIndex(declaration, backend, graphId, schemaVersion, existingByStatusKey, invalidLeftovers) {
@@ -11771,7 +13740,7 @@ async function materializeVectorIndex(declaration, backend, graphId, schemaVersi
11771
13740
  driftLabel: "Vector index",
11772
13741
  run: () => backend.createVectorIndex(params),
11773
13742
  existingByStatusKey,
11774
- invalidLeftovers
13743
+ physicalRebuildPreload: invalidLeftovers
11775
13744
  });
11776
13745
  }
11777
13746
  async function materializeOne(declaration, backend, graphId, schemaVersion, action) {
@@ -11788,7 +13757,8 @@ async function materializeOne(declaration, backend, graphId, schemaVersion, acti
11788
13757
  statusKey,
11789
13758
  signature,
11790
13759
  driftLabel,
11791
- isInvalidLeftover: (physicalIndexName) => action.invalidLeftovers.has(physicalIndexName)
13760
+ needsPhysicalRebuild: (physicalIndexName) => action.physicalRebuildPreload.has(physicalIndexName),
13761
+ ...action.rebuildWithoutClaim === void 0 ? {} : { rebuildWithoutClaim: action.rebuildWithoutClaim }
11792
13762
  }
11793
13763
  );
11794
13764
  if (settled !== void 0) return settled;
@@ -11797,7 +13767,9 @@ async function materializeOne(declaration, backend, graphId, schemaVersion, acti
11797
13767
  statusKey,
11798
13768
  signature,
11799
13769
  driftLabel,
11800
- run
13770
+ run,
13771
+ ...action.freshNeedsPhysicalRebuild === void 0 ? {} : { freshNeedsPhysicalRebuild: action.freshNeedsPhysicalRebuild },
13772
+ ...action.rebuildWithoutClaim === void 0 ? {} : { rebuildWithoutClaim: action.rebuildWithoutClaim }
11801
13773
  });
11802
13774
  }
11803
13775
  try {
@@ -11834,9 +13806,9 @@ async function materializeOne(declaration, backend, graphId, schemaVersion, acti
11834
13806
  }
11835
13807
  async function settleAgainstExisting(existing, declaration, backend, graphId, schemaVersion, action) {
11836
13808
  if (existing?.materializedAt === void 0) return void 0;
11837
- const { statusKey, signature, driftLabel, isInvalidLeftover } = action;
13809
+ const { statusKey, signature, driftLabel, needsPhysicalRebuild } = action;
11838
13810
  if (existing.signature === signature) {
11839
- if (declaration.entity !== "vector" && hasIndexBuildClaimProtocol(backend) && await isInvalidLeftover(declaration.name)) {
13811
+ if (declaration.entity !== "vector" && (hasIndexBuildClaimProtocol(backend) || action.rebuildWithoutClaim === true) && await needsPhysicalRebuild(declaration.name)) {
11840
13812
  return void 0;
11841
13813
  }
11842
13814
  return entry(declaration, "alreadyMaterialized");
@@ -11901,7 +13873,8 @@ async function materializeWithClaim(declaration, backend, graphId, schemaVersion
11901
13873
  driftLabel,
11902
13874
  // Post-claim the bulk preload is stale (we may have waited on
11903
13875
  // another builder's claim), so re-check this index fresh.
11904
- isInvalidLeftover: (physicalIndexName) => hasInvalidIndexLeftover(backend, physicalIndexName)
13876
+ needsPhysicalRebuild: action.freshNeedsPhysicalRebuild ?? ((physicalIndexName) => hasInvalidIndexLeftover(backend, physicalIndexName)),
13877
+ ...action.rebuildWithoutClaim === void 0 ? {} : { rebuildWithoutClaim: action.rebuildWithoutClaim }
11905
13878
  }
11906
13879
  );
11907
13880
  if (settled !== void 0) return settled;
@@ -11962,6 +13935,7 @@ async function hasInvalidIndexLeftover(backend, physicalIndexName) {
11962
13935
  FROM pg_class c
11963
13936
  JOIN pg_index i ON i.indexrelid = c.oid
11964
13937
  WHERE c.relname = ${physicalIndexName}
13938
+ AND pg_catalog.pg_table_is_visible(c.oid)
11965
13939
  `)
11966
13940
  );
11967
13941
  return rows[0]?.invalid === true;
@@ -11970,17 +13944,64 @@ async function preloadInvalidIndexLeftovers(backend, physicalIndexNames) {
11970
13944
  if (backend.dialect !== "postgres" || physicalIndexNames.length === 0) {
11971
13945
  return /* @__PURE__ */ new Set();
11972
13946
  }
13947
+ const { invalid } = await preloadPhysicalIndexStates(
13948
+ backend,
13949
+ physicalIndexNames
13950
+ );
13951
+ return invalid;
13952
+ }
13953
+ async function preloadExistingTables(backend, tableNames) {
13954
+ if (tableNames.length === 0) return /* @__PURE__ */ new Set();
13955
+ if (backend.dialect === "postgres") {
13956
+ const rows2 = await backend.execute(
13957
+ asCompiledRowsSql(sql`
13958
+ SELECT c.relname AS name
13959
+ FROM pg_class c
13960
+ WHERE c.relname IN (${sqlValueList(tableNames)})
13961
+ AND c.relkind IN ('r', 'p')
13962
+ AND pg_catalog.pg_table_is_visible(c.oid)
13963
+ `)
13964
+ );
13965
+ return new Set(rows2.map((row) => row.name));
13966
+ }
11973
13967
  const rows = await backend.execute(
11974
13968
  asCompiledRowsSql(sql`
11975
- SELECT c.relname AS name
11976
- FROM pg_class c
11977
- JOIN pg_index i ON i.indexrelid = c.oid
11978
- WHERE c.relname IN (${sqlValueList(physicalIndexNames)})
11979
- AND NOT i.indisvalid
13969
+ SELECT name
13970
+ FROM sqlite_master
13971
+ WHERE type = 'table'
13972
+ AND name IN (${sqlValueList(tableNames)})
11980
13973
  `)
11981
13974
  );
11982
13975
  return new Set(rows.map((row) => row.name));
11983
13976
  }
13977
+ async function preloadPhysicalIndexStates(backend, physicalIndexNames) {
13978
+ const valid = /* @__PURE__ */ new Set();
13979
+ const invalid = /* @__PURE__ */ new Set();
13980
+ if (physicalIndexNames.length === 0) return { valid, invalid };
13981
+ if (backend.dialect === "postgres") {
13982
+ const rows2 = await backend.execute(
13983
+ asCompiledRowsSql(sql`
13984
+ SELECT c.relname AS name, i.indisvalid AS valid
13985
+ FROM pg_class c
13986
+ JOIN pg_index i ON i.indexrelid = c.oid
13987
+ WHERE c.relname IN (${sqlValueList(physicalIndexNames)})
13988
+ AND pg_catalog.pg_table_is_visible(c.oid)
13989
+ `)
13990
+ );
13991
+ for (const row of rows2) (row.valid ? valid : invalid).add(row.name);
13992
+ return { valid, invalid };
13993
+ }
13994
+ const rows = await backend.execute(
13995
+ asCompiledRowsSql(sql`
13996
+ SELECT name
13997
+ FROM sqlite_master
13998
+ WHERE type = 'index'
13999
+ AND name IN (${sqlValueList(physicalIndexNames)})
14000
+ `)
14001
+ );
14002
+ for (const row of rows) valid.add(row.name);
14003
+ return { valid, invalid };
14004
+ }
11984
14005
  async function dropInvalidIndexLeftover(backend, physicalIndexName) {
11985
14006
  if (backend.executeDdl === void 0) return;
11986
14007
  if (!await hasInvalidIndexLeftover(backend, physicalIndexName)) return;
@@ -12039,6 +14060,121 @@ function buildAttempt(args) {
12039
14060
  error: args.error?.message
12040
14061
  };
12041
14062
  }
14063
+ function backendSupportsIndexMaterialization(backend) {
14064
+ return backend.executeDdl !== void 0 && backend.getIndexMaterialization !== void 0 && backend.recordIndexMaterialization !== void 0;
14065
+ }
14066
+ function assertBackendSupportsIndexMaterialization(backend, surface) {
14067
+ if (backendSupportsIndexMaterialization(backend)) return;
14068
+ throw new ConfigurationError(
14069
+ `${surface} requires a backend with \`executeDdl\` and the index materialization status primitives (\`getIndexMaterialization\`, \`recordIndexMaterialization\`). The bundled SQLite and Postgres backends provide them; a custom backend must implement all three.`,
14070
+ { code: "MATERIALIZE_BACKEND_UNSUPPORTED" }
14071
+ );
14072
+ }
14073
+ async function materializeSystemIndexes(context, options = {}) {
14074
+ const { backend, graphId, schemaVersion } = context;
14075
+ assertBackendSupportsIndexMaterialization(
14076
+ backend,
14077
+ "store.materializeSystemIndexes()"
14078
+ );
14079
+ await ensureFocusedStatusTable(
14080
+ backend,
14081
+ backend.ensureIndexMaterializationsTable
14082
+ );
14083
+ const concurrent = backend.dialect === "postgres";
14084
+ const candidates = SYSTEM_INDEX_DECLARATIONS.map((declaration) => {
14085
+ const physicalTable = resolveSystemIndexTableName(
14086
+ declaration.table,
14087
+ backend.tableNames
14088
+ );
14089
+ return {
14090
+ declaration,
14091
+ physicalTable,
14092
+ name: systemIndexName(physicalTable, declaration.suffix)
14093
+ };
14094
+ });
14095
+ const statusKeys = candidates.map((candidate) => candidate.name);
14096
+ const targetTables = [
14097
+ ...new Set(candidates.map((candidate) => candidate.physicalTable))
14098
+ ];
14099
+ const [existingByStatusKey, physicalStates, existingTables] = await Promise.all([
14100
+ preloadMaterializations(backend, statusKeys),
14101
+ preloadPhysicalIndexStates(backend, statusKeys),
14102
+ preloadExistingTables(backend, targetTables)
14103
+ ]);
14104
+ const { valid: physicallyPresent } = physicalStates;
14105
+ const physicalRebuildPreload = new Set(
14106
+ statusKeys.filter((name) => !physicallyPresent.has(name))
14107
+ );
14108
+ const materializeEntry = async (candidate) => {
14109
+ const identity = {
14110
+ name: candidate.name,
14111
+ entity: "system",
14112
+ kind: candidate.declaration.table
14113
+ };
14114
+ if (!existingTables.has(candidate.physicalTable)) {
14115
+ return {
14116
+ indexName: candidate.name,
14117
+ entity: "system",
14118
+ kind: candidate.declaration.table,
14119
+ status: "skipped",
14120
+ reason: `Relation "${candidate.physicalTable}" does not exist in this database`
14121
+ };
14122
+ }
14123
+ const existing = existingByStatusKey.get(candidate.name);
14124
+ if (existing !== void 0 && existing.entity !== "system") {
14125
+ return entry(
14126
+ identity,
14127
+ "failed",
14128
+ new Error(
14129
+ `System index name "${candidate.name}" is already recorded by a ${existing.entity} index declaration (graph "${existing.graphId}"). Rename the graph-declared index \u2014 "typegraph_"-prefixed names belong to TypeGraph's system indexes.`
14130
+ )
14131
+ );
14132
+ }
14133
+ if (existing === void 0 && physicallyPresent.has(candidate.name)) {
14134
+ return entry(identity, "alreadyMaterialized");
14135
+ }
14136
+ const signature = await computeSystemIndexSignature(
14137
+ backend.dialect,
14138
+ candidate.physicalTable,
14139
+ candidate.declaration
14140
+ );
14141
+ const ddl = generateSystemIndexDDL(
14142
+ candidate.declaration,
14143
+ candidate.physicalTable,
14144
+ { concurrent }
14145
+ );
14146
+ return materializeOne(identity, backend, graphId, schemaVersion, {
14147
+ statusKey: candidate.name,
14148
+ signature,
14149
+ driftLabel: "System index",
14150
+ run: () => backend.executeDdl(ddl),
14151
+ existingByStatusKey,
14152
+ physicalRebuildPreload,
14153
+ freshNeedsPhysicalRebuild: async (physicalIndexName) => {
14154
+ const fresh = await preloadPhysicalIndexStates(backend, [
14155
+ physicalIndexName
14156
+ ]);
14157
+ return !fresh.valid.has(physicalIndexName);
14158
+ },
14159
+ rebuildWithoutClaim: true
14160
+ });
14161
+ };
14162
+ const results = await runBucketedMaterialization(
14163
+ candidates,
14164
+ options,
14165
+ (candidate) => candidate.physicalTable,
14166
+ (candidate) => materializeEntry(candidate)
14167
+ );
14168
+ await refreshStatisticsAfterCreation(backend, results, options, concurrent);
14169
+ return { results };
14170
+ }
14171
+ async function computeSystemIndexSignature(dialect, targetTableName, declaration) {
14172
+ const json = JSON.stringify(
14173
+ { dialect, targetTableName, declaration },
14174
+ sortedReplacer
14175
+ );
14176
+ return sha256Hex(json, 16);
14177
+ }
12042
14178
  async function computeIndexSignature(dialect, targetTableName, declaration) {
12043
14179
  const hashable = {
12044
14180
  dialect,
@@ -12267,13 +14403,13 @@ function buildEmbeddingTableCleanup(ctx, row) {
12267
14403
  const slots = await resolveRemovedKindEmbeddingSlots(ctx.backend, row);
12268
14404
  await Promise.all(
12269
14405
  slots.map(
12270
- (slot) => dropVectorSlotStorage(vectorStrategy, executeDdl, slot)
14406
+ (slot) => dropVectorSlotStorage(ctx.backend, vectorStrategy, executeDdl, slot)
12271
14407
  )
12272
14408
  );
12273
14409
  })();
12274
14410
  return [cleanup];
12275
14411
  }
12276
- async function dropVectorSlotStorage(strategy, executeDdl, slot) {
14412
+ async function dropVectorSlotStorage(backend, strategy, executeDdl, slot) {
12277
14413
  try {
12278
14414
  for (const ddl of strategy.buildDropStorage(slot)) {
12279
14415
  await executeDdl(ddl);
@@ -12281,6 +14417,7 @@ async function dropVectorSlotStorage(strategy, executeDdl, slot) {
12281
14417
  } catch (error) {
12282
14418
  if (!isMissingTableError(error)) throw error;
12283
14419
  }
14420
+ await backend.deleteVectorSlotContribution?.(slot);
12284
14421
  }
12285
14422
  async function resolveRemovedKindEmbeddingSlots(backend, row) {
12286
14423
  const priorVersion = row.schemaVersion - 1;
@@ -12356,7 +14493,12 @@ async function reclaimRemovedVectorFieldTables(context) {
12356
14493
  const results = [];
12357
14494
  for (const field2 of orphans) {
12358
14495
  try {
12359
- await dropVectorSlotStorage(vectorStrategy, executeDdl, field2.slot);
14496
+ await dropVectorSlotStorage(
14497
+ backend,
14498
+ vectorStrategy,
14499
+ executeDdl,
14500
+ field2.slot
14501
+ );
12360
14502
  results.push({
12361
14503
  kind: field2.kind,
12362
14504
  fieldPath: field2.fieldPath,
@@ -12697,21 +14839,45 @@ async function executeEdgeCreateBatch(ctx, inputs, backend) {
12697
14839
  return rows.map((row) => rowToEdge(row));
12698
14840
  });
12699
14841
  }
14842
+ function computeEdgeUpdate(ctx, kind, id, existingProps, inputProps) {
14843
+ const registration = getEdgeRegistration(ctx.graph, kind);
14844
+ return validateEdgeProps(
14845
+ registration.type.schema,
14846
+ { ...existingProps, ...inputProps },
14847
+ { kind, operation: "update", id }
14848
+ );
14849
+ }
14850
+ function resolveEdgeUpdateProps(ctx, existing, inputProps) {
14851
+ const existingProps = rowPropsToObject(existing.props);
14852
+ const validatedProps = computeEdgeUpdate(
14853
+ ctx,
14854
+ existing.kind,
14855
+ existing.id,
14856
+ existingProps,
14857
+ inputProps
14858
+ );
14859
+ return { existingProps, validatedProps };
14860
+ }
14861
+ function edgeUpsertDirtyCheck(ctx, kind, id, existingProps, inputProps) {
14862
+ const validatedProps = computeEdgeUpdate(
14863
+ ctx,
14864
+ kind,
14865
+ id,
14866
+ existingProps,
14867
+ inputProps
14868
+ );
14869
+ return {
14870
+ validatedProps,
14871
+ unchanged: canonicalEqual(validatedProps, existingProps)
14872
+ };
14873
+ }
12700
14874
  async function performEdgeUpdate(ctx, input, target, options) {
12701
14875
  const id = input.id;
12702
14876
  const existing = await target.getEdge(ctx.graphId, id);
12703
14877
  if (!existing || !options?.clearDeleted && existing.deleted_at) {
12704
14878
  throw new EdgeNotFoundError("unknown", id);
12705
14879
  }
12706
- const registration = getEdgeRegistration(ctx.graph, existing.kind);
12707
- const edgeKind = registration.type;
12708
- const existingProps = rowPropsToObject(existing.props);
12709
- const mergedProps = { ...existingProps, ...input.props };
12710
- const validatedProps = validateEdgeProps(edgeKind.schema, mergedProps, {
12711
- kind: existing.kind,
12712
- operation: "update",
12713
- id
12714
- });
14880
+ const { validatedProps } = resolveEdgeUpdateProps(ctx, existing, input.props);
12715
14881
  const validTo = validateOptionalCanonicalIsoDate(input.validTo, "validTo");
12716
14882
  const updateParams = {
12717
14883
  graphId: ctx.graphId,
@@ -13369,27 +15535,48 @@ function increment(counts, kind, count2) {
13369
15535
  counts[kind] = (counts[kind] ?? 0) + count2;
13370
15536
  }
13371
15537
  function createTransactionReceiptRecorder() {
13372
- const nodes = createCountBucket();
13373
- const edges = createCountBucket();
13374
- let total = 0;
15538
+ const counters = {
15539
+ nodes: createCountBucket(),
15540
+ edges: createCountBucket(),
15541
+ total: 0
15542
+ };
15543
+ let sealed = false;
13375
15544
  return {
13376
15545
  recordNode(kind, count2) {
13377
- increment(nodes, kind, count2);
13378
- total += count2;
15546
+ increment(counters.nodes, kind, count2);
15547
+ counters.total += count2;
13379
15548
  },
13380
15549
  recordEdge(kind, count2) {
13381
- increment(edges, kind, count2);
13382
- total += count2;
15550
+ increment(counters.edges, kind, count2);
15551
+ counters.total += count2;
13383
15552
  },
13384
15553
  snapshot(recorded) {
13385
15554
  return Object.freeze({
13386
15555
  writes: Object.freeze({
13387
- nodes: Object.freeze(Object.assign(createCountBucket(), nodes)),
13388
- edges: Object.freeze(Object.assign(createCountBucket(), edges)),
13389
- total
15556
+ nodes: Object.freeze(
15557
+ Object.assign(createCountBucket(), counters.nodes)
15558
+ ),
15559
+ edges: Object.freeze(
15560
+ Object.assign(createCountBucket(), counters.edges)
15561
+ ),
15562
+ total: counters.total
13390
15563
  }),
13391
15564
  ...recorded === void 0 ? {} : { recorded }
13392
15565
  });
15566
+ },
15567
+ seal() {
15568
+ sealed = true;
15569
+ },
15570
+ assertWritable() {
15571
+ if (sealed) {
15572
+ throw new ConfigurationError(
15573
+ "Transaction context is sealed: a write happened through the receipt-tracked collections after the transaction callback returned.",
15574
+ {},
15575
+ {
15576
+ suggestion: "Perform all writes inside the transactionWithReceipt / withRecordedTransaction callback; do not reuse the transaction context after it returns."
15577
+ }
15578
+ );
15579
+ }
13393
15580
  }
13394
15581
  };
13395
15582
  }
@@ -13423,7 +15610,7 @@ function memoizedProxyGet(target, shouldWrap, wrap) {
13423
15610
  }
13424
15611
  });
13425
15612
  }
13426
- function wrapWriteCollection(collection, kind, surface, record) {
15613
+ function wrapWriteCollection(collection, kind, surface, record, assertWritable) {
13427
15614
  return memoizedProxyGet(
13428
15615
  collection,
13429
15616
  (property) => surface.methodNames.has(property),
@@ -13431,6 +15618,7 @@ function wrapWriteCollection(collection, kind, surface, record) {
13431
15618
  if (!isWrappedMethod(value)) return value;
13432
15619
  const method = property;
13433
15620
  const wrapped = async (...args) => {
15621
+ assertWritable();
13434
15622
  const count2 = surface.counters[method](args);
13435
15623
  const result = await Reflect.apply(value, collection, args);
13436
15624
  record(kind, count2);
@@ -13455,7 +15643,8 @@ function wrapTransactionCollections(nodes, edges, recorder) {
13455
15643
  collection,
13456
15644
  kind,
13457
15645
  NODE_WRITE_SURFACE,
13458
- recorder.recordNode
15646
+ recorder.recordNode,
15647
+ recorder.assertWritable
13459
15648
  )
13460
15649
  ),
13461
15650
  edges: wrapCollections(
@@ -13464,7 +15653,8 @@ function wrapTransactionCollections(nodes, edges, recorder) {
13464
15653
  collection,
13465
15654
  kind,
13466
15655
  EDGE_WRITE_SURFACE,
13467
- recorder.recordEdge
15656
+ recorder.recordEdge,
15657
+ recorder.assertWritable
13468
15658
  )
13469
15659
  )
13470
15660
  };
@@ -13505,6 +15695,7 @@ function rowMapperConfigFor(backend) {
13505
15695
  function asError(error) {
13506
15696
  return error instanceof Error ? error : new Error(String(error));
13507
15697
  }
15698
+ var EMPTY_RECORDED_FLUSH_INSTANTS = /* @__PURE__ */ new Map();
13508
15699
  function transactionOutcome(result, recorder, recordedByGraph, graphId) {
13509
15700
  const recorded = recordedByGraph?.get(graphId);
13510
15701
  return {
@@ -13691,9 +15882,11 @@ var Store = class _Store {
13691
15882
  * Tier 1 graph algorithms: shortest path, reachability, neighborhoods, and
13692
15883
  * degree centrality.
13693
15884
  *
13694
- * Each call compiles to a single recursive-CTE query against the backend.
13695
- * The facade is built lazily on first access and cached for the lifetime
13696
- * of the store.
15885
+ * Traversal calls use the iterative graph-operation substrate: a set-based
15886
+ * breadth-first frontier backed by a temporary working table, or a chunked
15887
+ * inline relation when the backend cannot pin a transactional connection.
15888
+ * `degree` uses a single count query. The facade is built lazily on first
15889
+ * access and cached for the lifetime of the store.
13697
15890
  *
13698
15891
  * @example
13699
15892
  * ```typescript
@@ -13899,6 +16092,10 @@ var Store = class _Store {
13899
16092
  backend,
13900
16093
  options
13901
16094
  ),
16095
+ // Present only when opted in; its absence is the coalesce off switch.
16096
+ ...this.#options?.coalesceUnchangedUpserts === true && {
16097
+ upsertDirtyCheck: (kind, id, existingProps, inputProps) => nodeUpsertDirtyCheck(ctx, kind, id, existingProps, inputProps)
16098
+ },
13902
16099
  executeDelete: (kind, id, backend) => executeNodeDelete(ctx, kind, id, backend),
13903
16100
  executeHardDelete: (kind, id, backend) => executeNodeHardDelete(ctx, kind, id, backend),
13904
16101
  temporalRowMatcher: (options) => this.#temporalRowMatcher(options),
@@ -13953,6 +16150,10 @@ var Store = class _Store {
13953
16150
  executeCreateNoReturnBatch: (inputs, backend) => executeEdgeCreateNoReturnBatch(ctx, inputs, backend),
13954
16151
  executeUpdate: (input, backend) => executeEdgeUpdate(ctx, input, backend),
13955
16152
  executeUpsertUpdate: (input, backend, options) => executeEdgeUpsertUpdate(ctx, input, backend, options),
16153
+ // Present only when opted in; its absence is the coalesce off switch.
16154
+ ...this.#options?.coalesceUnchangedUpserts === true && {
16155
+ upsertDirtyCheck: (kind, id, existingProps, inputProps) => edgeUpsertDirtyCheck(ctx, kind, id, existingProps, inputProps)
16156
+ },
13956
16157
  executeDelete: (id, backend) => executeEdgeDelete(ctx, id, backend),
13957
16158
  executeHardDelete: (id, backend) => executeEdgeHardDelete(ctx, id, backend),
13958
16159
  temporalRowMatcher: (options) => this.#temporalRowMatcher(options),
@@ -14422,9 +16623,11 @@ var Store = class _Store {
14422
16623
  receiptRecorder
14423
16624
  ));
14424
16625
  }
14425
- const result = await invoke({
16626
+ const fallbackContext = {
14426
16627
  nodes,
14427
16628
  edges,
16629
+ // No real transaction: `tx.sql` is absent and there is no atomicity.
16630
+ sqlAvailability: "unavailable",
14428
16631
  backend: this.#backend,
14429
16632
  getNodeCollection: (kind) => this.#resolveDynamicNodeCollection(nodes, kind),
14430
16633
  // No transaction, no deferral: operations apply as they happen, so
@@ -14433,7 +16636,10 @@ var Store = class _Store {
14433
16636
  this.#createOperationContext(operation, "node", kind, id),
14434
16637
  hookedFunction
14435
16638
  )
14436
- });
16639
+ };
16640
+ const result = await invoke(
16641
+ receiptRecorder === void 0 ? fallbackContext : this.#attachMeasure(fallbackContext)
16642
+ );
14437
16643
  return { result, recordedByGraph: void 0 };
14438
16644
  }
14439
16645
  const pending = [];
@@ -14444,10 +16650,10 @@ var Store = class _Store {
14444
16650
  }) : backendOptions;
14445
16651
  try {
14446
16652
  const result = await this.#backend.transaction(
14447
- async (txBackend, sql17) => invoke(
16653
+ async (txBackend, sql19) => invoke(
14448
16654
  this.#buildTransactionContext(
14449
16655
  txBackend,
14450
- sql17,
16656
+ sql19,
14451
16657
  runHooks,
14452
16658
  receiptRecorder
14453
16659
  )
@@ -14468,83 +16674,9 @@ var Store = class _Store {
14468
16674
  throw error;
14469
16675
  }
14470
16676
  }
14471
- /**
14472
- * Adopts a caller-owned, already-open Drizzle transaction so the graph
14473
- * store and the caller's relational writes commit or roll back as one
14474
- * Postgres/SQLite transaction (#134).
14475
- *
14476
- * Use this when the **relational layer owns the transaction**: the
14477
- * caller has opened a transaction and needs TypeGraph writes enlisted
14478
- * on the *same* connection. Unlike {@link transaction}, this opens no
14479
- * transaction — the caller's transaction is the single commit/rollback
14480
- * boundary.
14481
- *
14482
- * `withTransaction` is driver-agnostic; how the caller opens the
14483
- * transaction is not. **Async drivers** (node-postgres,
14484
- * `neon-serverless` Pool, libsql) use `db.transaction(async …)`.
14485
- * **Synchronous `better-sqlite3`** cannot — its driver rejects an
14486
- * `async` transaction callback (`Transaction function cannot return a
14487
- * promise`) and the async continuation would run outside the
14488
- * rolled-back transaction — so the caller opens the transaction with
14489
- * explicit `BEGIN`/`COMMIT`/`ROLLBACK` on the single connection
14490
- * instead. See the "Cross-Store Transactions" recipe for both shapes.
14491
- *
14492
- * The returned context reuses this store's already-resolved
14493
- * schema/registry: it runs **no** schema bootstrap, `evolve`, or
14494
- * migration, and emits **no DDL** inside the caller's transaction.
14495
- * Fulltext operations assert the durable materialization marker (a
14496
- * cached SELECT) and throw {@link StoreNotInitializedError} on a
14497
- * missing/stale/failed marker rather than migrating mid-transaction —
14498
- * so boot the parent store via `createStoreWithSchema` once at
14499
- * startup.
14500
- *
14501
- * @example
14502
- * ```typescript
14503
- * // Async driver (Postgres / libsql):
14504
- * await db.transaction(async (sqlTx) => {
14505
- * const connector = await createConnectorRow(sqlTx, input); // Drizzle
14506
- * const txStore = store.withTransaction(sqlTx);
14507
- * await txStore.nodes.ArtifactSource.create({ // TypeGraph
14508
- * connectorId: connector.id,
14509
- * });
14510
- * }); // one COMMIT / ROLLBACK across both layers
14511
- * ```
14512
- *
14513
- * **The caller owns the connection — don't overlap writes on it.** The graph
14514
- * store and your Drizzle writes share the one connection the caller's
14515
- * transaction pinned. TypeGraph serializes the statements *its* collections
14516
- * issue, but your raw Drizzle statements (and any graph write run alongside
14517
- * them) are yours to sequence: a `Promise.all` mixing the two races two
14518
- * queries on that connection — the overlap Postgres removes in `pg@9`. Await
14519
- * each write before starting the next.
14520
- *
14521
- * Not available when the store was created with `{ history: true }`: a
14522
- * caller-owned transaction context would let writes happen after capture has
14523
- * lost its flush point, so this throws `ConfigurationError`
14524
- * (`RECORDED_CAPTURE_REQUIRES_CALLBACK_TRANSACTION`). Use
14525
- * {@link Store.withRecordedTransaction} on a history-enabled store, which
14526
- * adopts the same external transaction but flushes recorded-time capture
14527
- * before the caller commits.
14528
- *
14529
- * @throws {ConfigurationError} when the store has history capture enabled
14530
- * (use {@link Store.withRecordedTransaction} instead), or when the backend
14531
- * cannot adopt an external transaction — either it is not a Drizzle
14532
- * Postgres/SQLite backend, or `backend.capabilities.transactions` is `false`
14533
- * (`drizzle-orm/neon-http`, Cloudflare D1, SQLite
14534
- * `transactionMode: "none"`). A non-atomic fallback is deliberately
14535
- * not offered here: the caller's relational write *would* still
14536
- * commit, defeating the purpose of cross-store atomicity.
14537
- * @throws {StoreNotInitializedError} at point of use, when a fulltext
14538
- * operation on the returned context observes a missing/stale/failed
14539
- * durable materialization marker (parent store not booted via
14540
- * `createStoreWithSchema`). Rolls back the caller's transaction
14541
- * without emitting any DDL.
14542
- */
14543
16677
  withTransaction(externalTx) {
14544
16678
  if (this.#captureEnabled) {
14545
- throw recordedCaptureRequiresCallbackTransactionError({
14546
- code: "RECORDED_CAPTURE_REQUIRES_CALLBACK_TRANSACTION"
14547
- });
16679
+ throw recordedCaptureRequiresCallbackTransactionError();
14548
16680
  }
14549
16681
  const adopt = this.#backend.adoptTransaction;
14550
16682
  if (!adopt) {
@@ -14572,17 +16704,62 @@ var Store = class _Store {
14572
16704
  }
14573
16705
  const scope = this.#captureEnabled ? createRecordedTransactionScope(txBackend, this.#sqlSchema()) : {
14574
16706
  backend: txBackend,
14575
- flush: () => Promise.resolve()
16707
+ flush: () => Promise.resolve(EMPTY_RECORDED_FLUSH_INSTANTS)
14576
16708
  };
14577
16709
  if (this.#captureEnabled) {
14578
16710
  await lockRecordedGraphWrite(scope.backend, this.graphId);
14579
16711
  }
16712
+ const receiptRecorder = createTransactionReceiptRecorder();
14580
16713
  const invoke = fn;
14581
16714
  const result = await invoke(
14582
- this.#buildTransactionContext(scope.backend, externalTx)
16715
+ this.#buildTransactionContext(
16716
+ scope.backend,
16717
+ externalTx,
16718
+ void 0,
16719
+ receiptRecorder
16720
+ )
14583
16721
  );
14584
- await scope.flush();
14585
- return result;
16722
+ const recordedByGraph = await scope.flush();
16723
+ if (!this.#captureEnabled) receiptRecorder.seal();
16724
+ return transactionOutcome(
16725
+ result,
16726
+ receiptRecorder,
16727
+ recordedByGraph,
16728
+ this.graphId
16729
+ );
16730
+ }
16731
+ /**
16732
+ * Decorates a receipt-enabled transaction `context` with `measure`.
16733
+ * Attribution is structural: `measure` wraps the context's *own* (already
16734
+ * outer-recording) collections a second time with a fresh scope recorder, so a
16735
+ * write through the scoped context counts in the scope and — via the inner
16736
+ * wrapper it delegates to — the outer receipt, while a write through the outer
16737
+ * `context` never reaches the scope recorder. This makes overlapping/concurrent
16738
+ * measures safe by construction (each holds its own scope recorder) and lets
16739
+ * scopes nest: the scoped context is itself decorated, so `scoped.measure(...)`
16740
+ * chains one more wrapper. The scoped context's `getNodeCollection` resolves
16741
+ * against the scope-wrapped map too, so dynamic-kind writes are attributed like
16742
+ * `scoped.nodes.<Kind>`. The scope receipt's `recorded` is always undefined —
16743
+ * the recorded instant is a whole-transaction flush concern.
16744
+ */
16745
+ #attachMeasure(context) {
16746
+ const measure = async (fn) => {
16747
+ const scopeRecorder = createTransactionReceiptRecorder();
16748
+ const { nodes, edges } = wrapTransactionCollections(
16749
+ context.nodes,
16750
+ context.edges,
16751
+ scopeRecorder
16752
+ );
16753
+ const scoped = this.#attachMeasure({
16754
+ ...context,
16755
+ nodes,
16756
+ edges,
16757
+ getNodeCollection: (kind) => this.#resolveDynamicNodeCollection(nodes, kind)
16758
+ });
16759
+ const result = await fn(scoped);
16760
+ return { result, receipt: scopeRecorder.snapshot() };
16761
+ };
16762
+ return { ...context, measure };
14586
16763
  }
14587
16764
  /**
14588
16765
  * Builds the `{ nodes, edges, sql }` projection bound to a
@@ -14594,7 +16771,7 @@ var Store = class _Store {
14594
16771
  * (#140); when history capture is enabled it is replaced with a fail-loud
14595
16772
  * guard because raw graph writes would bypass recorded-time capture.
14596
16773
  */
14597
- #buildTransactionContext(txBackend, sql17, runHooks = this.#immediateHookRunner(), receiptRecorder) {
16774
+ #buildTransactionContext(txBackend, sql19, runHooks = this.#immediateHookRunner(), receiptRecorder) {
14598
16775
  const txNodeOperations = {
14599
16776
  ...this.#buildNodeOperations(this.#createNodeOperationContext(runHooks)),
14600
16777
  createQuery: () => this.#createQueryForBackend(txBackend),
@@ -14628,24 +16805,20 @@ var Store = class _Store {
14628
16805
  ));
14629
16806
  }
14630
16807
  const getNodeCollection = (kind) => this.#resolveDynamicNodeCollection(nodes, kind);
14631
- if (sql17 === void 0) {
14632
- return {
14633
- nodes,
14634
- edges,
14635
- backend: txBackend,
14636
- getNodeCollection,
14637
- runNodeOperationHooks
14638
- };
14639
- }
14640
- const exposedSql = this.#captureEnabled ? createHistoryUnsafeSqlRef() : this.#revisionTrackingEnabled ? createRevisionTrackingUnsafeSqlRef() : sql17;
14641
- return {
16808
+ const sqlAvailability = this.#captureEnabled ? "history" : this.#revisionTrackingEnabled ? "revisionTracking" : sql19 === void 0 ? "unavailable" : "available";
16809
+ const base = {
14642
16810
  nodes,
14643
16811
  edges,
14644
- sql: exposedSql,
16812
+ sqlAvailability,
14645
16813
  backend: txBackend,
14646
16814
  getNodeCollection,
14647
16815
  runNodeOperationHooks
14648
16816
  };
16817
+ const withSql = sql19 === void 0 ? base : {
16818
+ ...base,
16819
+ sql: this.#captureEnabled ? createHistoryUnsafeSqlRef() : this.#revisionTrackingEnabled ? createRevisionTrackingUnsafeSqlRef() : sql19
16820
+ };
16821
+ return receiptRecorder === void 0 ? withSql : this.#attachMeasure(withSql);
14649
16822
  }
14650
16823
  // === Graph Lifecycle ===
14651
16824
  /**
@@ -14834,6 +17007,7 @@ var Store = class _Store {
14834
17007
  merged,
14835
17008
  activeRow.version
14836
17009
  );
17010
+ await materializeVectorContributions(this.#backend, merged);
14837
17011
  const evolved = this.#cloneWithGraph(
14838
17012
  merged,
14839
17013
  options?.ref,
@@ -14936,6 +17110,29 @@ var Store = class _Store {
14936
17110
  options ?? {}
14937
17111
  );
14938
17112
  }
17113
+ /**
17114
+ * Materializes TypeGraph's own base-relation indexes
17115
+ * (`SYSTEM_INDEX_DECLARATIONS`) against the live database.
17116
+ *
17117
+ * Bootstrap DDL runs only on first boot, so a system index shipped in a
17118
+ * newer library version never reaches an already-initialized database on
17119
+ * its own. `createStoreWithSchema` runs this automatically; deployments
17120
+ * that boot with plain `createStore` / `createVerifiedStore` (zero-DDL by
17121
+ * contract) adopt new system indexes by calling this once under a role
17122
+ * that may run DDL. Same status-table, drift-signature, and Postgres
17123
+ * `CREATE INDEX CONCURRENTLY` claim semantics as `materializeIndexes`.
17124
+ */
17125
+ async materializeSystemIndexes(options) {
17126
+ const { activeRow } = await this.#loadCaughtUp("materialize");
17127
+ return materializeSystemIndexes(
17128
+ {
17129
+ backend: this.#baseBackend,
17130
+ graphId: this.graphId,
17131
+ schemaVersion: activeRow.version
17132
+ },
17133
+ options ?? {}
17134
+ );
17135
+ }
14939
17136
  /**
14940
17137
  * Recreate a vector field's per-field storage at its current declared
14941
17138
  * dimension, then optionally re-embed existing rows.
@@ -14992,10 +17189,14 @@ var Store = class _Store {
14992
17189
  for (const ddl of strategy.buildDropStorage(slot)) {
14993
17190
  await backend.executeDdl(ddl);
14994
17191
  }
14995
- for (const contribution of strategy.ownedTables(slot)) {
14996
- for (const ddl of contribution.createDdl) {
14997
- await backend.executeDdl(ddl);
17192
+ if (backend.ensureVectorSlotContribution === void 0) {
17193
+ for (const contribution of strategy.ownedTables(slot)) {
17194
+ for (const ddl of contribution.createDdl) {
17195
+ await backend.executeDdl(ddl);
17196
+ }
14998
17197
  }
17198
+ } else {
17199
+ await backend.ensureVectorSlotContribution(slot, { force: true });
14999
17200
  }
15000
17201
  const declaredIndexMaterializes = declaration.indexType !== "none" && backend.capabilities.vector?.indexTypes.includes(
15001
17202
  declaration.indexType
@@ -15477,6 +17678,20 @@ async function materializeRuntimeContributions(backend, graphId) {
15477
17678
  }
15478
17679
  await backend.ensureFulltextTable?.(graphId);
15479
17680
  }
17681
+ async function materializeVectorContributions(backend, graph) {
17682
+ if (backend.capabilities.vector?.supported !== true) return;
17683
+ const slots = resolveGraphVectorSlots(graph);
17684
+ const ensureVectorSlotContributions = backend.ensureVectorSlotContributions;
17685
+ if (ensureVectorSlotContributions !== void 0) {
17686
+ await ensureVectorSlotContributions(slots, { onDrift: "skip" });
17687
+ return;
17688
+ }
17689
+ const ensureVectorSlotContribution = backend.ensureVectorSlotContribution;
17690
+ if (ensureVectorSlotContribution === void 0) return;
17691
+ for (const slot of slots) {
17692
+ await ensureVectorSlotContribution(slot, { onDrift: "skip" });
17693
+ }
17694
+ }
15480
17695
  async function createStoreWithSchema(graph, backend, options) {
15481
17696
  const {
15482
17697
  graph: merged,
@@ -15488,6 +17703,10 @@ async function createStoreWithSchema(graph, backend, options) {
15488
17703
  preloaded: { activeRow, storedSchema }
15489
17704
  });
15490
17705
  await materializeRuntimeContributions(backend, merged.id);
17706
+ await materializeVectorContributions(backend, merged);
17707
+ if (options?.systemIndexes !== "skip") {
17708
+ await materializeSystemIndexesOnBoot(backend, merged.id, result);
17709
+ }
15491
17710
  const store = new Store(
15492
17711
  merged,
15493
17712
  backend,
@@ -15496,6 +17715,30 @@ async function createStoreWithSchema(graph, backend, options) {
15496
17715
  );
15497
17716
  return [store, result];
15498
17717
  }
17718
+ async function materializeSystemIndexesOnBoot(backend, graphId, result) {
17719
+ if (result.status === "breaking") return;
17720
+ if (!backendSupportsIndexMaterialization(backend)) return;
17721
+ const schemaVersion = result.status === "migrated" ? result.toVersion : result.version;
17722
+ try {
17723
+ const { results } = await materializeSystemIndexes(
17724
+ { backend: asRawBackend(backend), graphId, schemaVersion },
17725
+ {}
17726
+ );
17727
+ const failed = results.filter(
17728
+ (entryResult) => entryResult.status === "failed"
17729
+ );
17730
+ if (failed.length === 0) return;
17731
+ console.warn(
17732
+ `[typegraph] ${String(failed.length)} system index(es) failed to materialize at boot (${failed.map((entryResult) => entryResult.indexName).join(", ")}); the store is usable but the affected access paths fall back to scans. Retry via store.materializeSystemIndexes().`,
17733
+ failed[0]?.error
17734
+ );
17735
+ } catch (error) {
17736
+ console.warn(
17737
+ "[typegraph] system-index materialization failed at boot; the store is usable but new system indexes were not adopted. Retry via store.materializeSystemIndexes().",
17738
+ error
17739
+ );
17740
+ }
17741
+ }
15499
17742
  async function createVerifiedStore(graph, backend, options) {
15500
17743
  const {
15501
17744
  graph: merged,
@@ -15512,5 +17755,5 @@ async function createVerifiedStore(graph, backend, options) {
15512
17755
  }
15513
17756
 
15514
17757
  export { ExecutableAggregateQuery, ExecutableQuery, MAX_EXPLICIT_RECURSIVE_DEPTH, MAX_RECURSIVE_DEPTH, PreparedQuery, QueryBuilder, RecordedStoreView, StoreSearch, StoreView, UnionableQuery, avg, composeFragments, count, countDistinct, countDistinctEdges, countEdges, createFragment, createQueryBuilder, createStore, createStoreWithSchema, createVerifiedStore, defineSubgraphProject, field, having, havingEq, havingGt, havingGte, havingLt, havingLte, limitFragment, max, min, offsetFragment, orderByFragment, sum };
15515
- //# sourceMappingURL=chunk-UES55P7G.js.map
15516
- //# sourceMappingURL=chunk-UES55P7G.js.map
17758
+ //# sourceMappingURL=chunk-552KNYQJ.js.map
17759
+ //# sourceMappingURL=chunk-552KNYQJ.js.map