@nicia-ai/typegraph 0.47.0 → 0.49.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 (211) hide show
  1. package/dist/backend/drizzle/indexes.d.cts +1 -1
  2. package/dist/backend/drizzle/indexes.d.ts +1 -1
  3. package/dist/backend/index.cjs +27 -19
  4. package/dist/backend/index.d.cts +37 -55
  5. package/dist/backend/index.d.ts +37 -55
  6. package/dist/backend/index.js +4 -4
  7. package/dist/backend/postgres/index.cjs +7 -7
  8. package/dist/backend/postgres/index.d.cts +21 -7
  9. package/dist/backend/postgres/index.d.ts +21 -7
  10. package/dist/backend/postgres/index.js +6 -6
  11. package/dist/backend/postgres/pglite-store.cjs +17 -17
  12. package/dist/backend/postgres/pglite-store.d.cts +8 -8
  13. package/dist/backend/postgres/pglite-store.d.ts +8 -8
  14. package/dist/backend/postgres/pglite-store.js +14 -14
  15. package/dist/backend/postgres/pglite.cjs +8 -8
  16. package/dist/backend/postgres/pglite.d.cts +3 -3
  17. package/dist/backend/postgres/pglite.d.ts +3 -3
  18. package/dist/backend/postgres/pglite.js +7 -7
  19. package/dist/backend/sqlite/index.cjs +7 -7
  20. package/dist/backend/sqlite/index.d.cts +20 -7
  21. package/dist/backend/sqlite/index.d.ts +20 -7
  22. package/dist/backend/sqlite/index.js +6 -6
  23. package/dist/backend/sqlite/libsql.cjs +8 -8
  24. package/dist/backend/sqlite/libsql.d.cts +3 -3
  25. package/dist/backend/sqlite/libsql.d.ts +3 -3
  26. package/dist/backend/sqlite/libsql.js +6 -6
  27. package/dist/backend/sqlite/local-store.cjs +18 -18
  28. package/dist/backend/sqlite/local-store.d.cts +8 -8
  29. package/dist/backend/sqlite/local-store.d.ts +8 -8
  30. package/dist/backend/sqlite/local-store.js +15 -15
  31. package/dist/backend/sqlite/local.cjs +9 -9
  32. package/dist/backend/sqlite/local.d.cts +3 -3
  33. package/dist/backend/sqlite/local.d.ts +3 -3
  34. package/dist/backend/sqlite/local.js +7 -7
  35. package/dist/{chunk-5FBRVORR.cjs → chunk-2O4PCIZ6.cjs} +8 -7
  36. package/dist/chunk-2O4PCIZ6.cjs.map +1 -0
  37. package/dist/{chunk-6KFLYYOC.cjs → chunk-2QGYQBS2.cjs} +5 -5
  38. package/dist/{chunk-6KFLYYOC.cjs.map → chunk-2QGYQBS2.cjs.map} +1 -1
  39. package/dist/{chunk-XYBQI6LE.js → chunk-2TYSSBU5.js} +6 -6
  40. package/dist/{chunk-XYBQI6LE.js.map → chunk-2TYSSBU5.js.map} +1 -1
  41. package/dist/{chunk-6JVZSOHQ.cjs → chunk-3FPR224C.cjs} +26 -26
  42. package/dist/{chunk-6JVZSOHQ.cjs.map → chunk-3FPR224C.cjs.map} +1 -1
  43. package/dist/{chunk-U5EDAVOJ.cjs → chunk-3G5W2U2L.cjs} +82 -51
  44. package/dist/chunk-3G5W2U2L.cjs.map +1 -0
  45. package/dist/{chunk-MFZR4QKM.cjs → chunk-3KOY4FH3.cjs} +181 -4
  46. package/dist/chunk-3KOY4FH3.cjs.map +1 -0
  47. package/dist/{chunk-UPAJHMW3.cjs → chunk-3QNZ5SAJ.cjs} +55 -58
  48. package/dist/chunk-3QNZ5SAJ.cjs.map +1 -0
  49. package/dist/{chunk-NG2RB5WL.js → chunk-5K2BKEBD.js} +18 -3
  50. package/dist/chunk-5K2BKEBD.js.map +1 -0
  51. package/dist/chunk-5PRKMB24.cjs +124 -0
  52. package/dist/chunk-5PRKMB24.cjs.map +1 -0
  53. package/dist/chunk-6LN7FIVU.js +107 -0
  54. package/dist/chunk-6LN7FIVU.js.map +1 -0
  55. package/dist/{chunk-M35DF33T.js → chunk-7LZJNLA5.js} +29 -16
  56. package/dist/chunk-7LZJNLA5.js.map +1 -0
  57. package/dist/{chunk-I4G3SQPF.cjs → chunk-7UOLHNWC.cjs} +7 -7
  58. package/dist/chunk-7UOLHNWC.cjs.map +1 -0
  59. package/dist/{chunk-DJZTXBWY.js → chunk-BRRP76WO.js} +71 -30
  60. package/dist/chunk-BRRP76WO.js.map +1 -0
  61. package/dist/{chunk-5FT3TLPK.js → chunk-D4QLTJSM.js} +16 -13
  62. package/dist/chunk-D4QLTJSM.js.map +1 -0
  63. package/dist/{chunk-XY5OMOJD.js → chunk-DZAICA5J.js} +3 -3
  64. package/dist/{chunk-XY5OMOJD.js.map → chunk-DZAICA5J.js.map} +1 -1
  65. package/dist/{chunk-OIFSOPSR.cjs → chunk-EZEEWCFJ.cjs} +38 -156
  66. package/dist/chunk-EZEEWCFJ.cjs.map +1 -0
  67. package/dist/{chunk-AMMWA76F.cjs → chunk-FA2AFWLP.cjs} +6 -6
  68. package/dist/{chunk-AMMWA76F.cjs.map → chunk-FA2AFWLP.cjs.map} +1 -1
  69. package/dist/{chunk-FNOMT74T.js → chunk-I4GAZH5Z.js} +180 -5
  70. package/dist/chunk-I4GAZH5Z.js.map +1 -0
  71. package/dist/{chunk-WGX7JH7E.cjs → chunk-JB6T4GLL.cjs} +106 -134
  72. package/dist/chunk-JB6T4GLL.cjs.map +1 -0
  73. package/dist/{chunk-MX4DHSQP.cjs → chunk-JFRT7S3B.cjs} +92 -98
  74. package/dist/chunk-JFRT7S3B.cjs.map +1 -0
  75. package/dist/{chunk-5UMTUOM5.js → chunk-JPD2CTC3.js} +5 -4
  76. package/dist/chunk-JPD2CTC3.js.map +1 -0
  77. package/dist/{chunk-SE6EGQSD.js → chunk-KF6K7TGH.js} +38 -66
  78. package/dist/chunk-KF6K7TGH.js.map +1 -0
  79. package/dist/{chunk-QS3NQZVC.js → chunk-LTXQOEIC.js} +44 -14
  80. package/dist/chunk-LTXQOEIC.js.map +1 -0
  81. package/dist/{chunk-ZHNXLS3C.js → chunk-NACVVDWB.js} +208 -8
  82. package/dist/chunk-NACVVDWB.js.map +1 -0
  83. package/dist/{chunk-AVGUJWVX.js → chunk-S5TERQVW.js} +15 -132
  84. package/dist/chunk-S5TERQVW.js.map +1 -0
  85. package/dist/chunk-SIT26LAZ.cjs +614 -0
  86. package/dist/chunk-SIT26LAZ.cjs.map +1 -0
  87. package/dist/{chunk-BR7SHKGM.cjs → chunk-SR725PWF.cjs} +548 -507
  88. package/dist/chunk-SR725PWF.cjs.map +1 -0
  89. package/dist/{chunk-425IZOQ5.cjs → chunk-TQHGE7UC.cjs} +20 -2
  90. package/dist/chunk-TQHGE7UC.cjs.map +1 -0
  91. package/dist/{chunk-GYUJZARH.cjs → chunk-TUNPBZY3.cjs} +4 -4
  92. package/dist/{chunk-GYUJZARH.cjs.map → chunk-TUNPBZY3.cjs.map} +1 -1
  93. package/dist/chunk-UGHG7PGX.js +592 -0
  94. package/dist/chunk-UGHG7PGX.js.map +1 -0
  95. package/dist/{chunk-BCQ76AXA.cjs → chunk-UGHQV7MP.cjs} +107 -94
  96. package/dist/chunk-UGHQV7MP.cjs.map +1 -0
  97. package/dist/{chunk-KABFHAL7.cjs → chunk-UMGZDVOA.cjs} +213 -12
  98. package/dist/chunk-UMGZDVOA.cjs.map +1 -0
  99. package/dist/{chunk-6FWSNSRE.js → chunk-WIC7DRXW.js} +4 -4
  100. package/dist/chunk-WIC7DRXW.js.map +1 -0
  101. package/dist/{chunk-LEWHMOUY.js → chunk-YIURNGXI.js} +4 -4
  102. package/dist/{chunk-LEWHMOUY.js.map → chunk-YIURNGXI.js.map} +1 -1
  103. package/dist/{chunk-EP4RUYD6.js → chunk-YVF2EM7B.js} +3 -3
  104. package/dist/{chunk-EP4RUYD6.js.map → chunk-YVF2EM7B.js.map} +1 -1
  105. package/dist/{chunk-EMIB2RXD.cjs → chunk-YYUHLYM2.cjs} +99 -96
  106. package/dist/chunk-YYUHLYM2.cjs.map +1 -0
  107. package/dist/{chunk-KH7XW7VV.js → chunk-Z2JCLZFJ.js} +25 -28
  108. package/dist/chunk-Z2JCLZFJ.js.map +1 -0
  109. package/dist/{chunk-NNQ6A3RM.js → chunk-ZFMCDJGB.js} +7 -12
  110. package/dist/chunk-ZFMCDJGB.js.map +1 -0
  111. package/dist/core/index.cjs +6 -6
  112. package/dist/core/index.d.cts +4 -4
  113. package/dist/core/index.d.ts +4 -4
  114. package/dist/core/index.js +2 -2
  115. package/dist/ddl-BL1NZLMY.d.cts +78 -0
  116. package/dist/ddl-D_oW-EVO.d.ts +78 -0
  117. package/dist/graph-extension/index.d.cts +4 -4
  118. package/dist/graph-extension/index.d.ts +4 -4
  119. package/dist/graph-merge/index.cjs +357 -191
  120. package/dist/graph-merge/index.cjs.map +1 -1
  121. package/dist/graph-merge/index.d.cts +68 -15
  122. package/dist/graph-merge/index.d.ts +68 -15
  123. package/dist/graph-merge/index.js +197 -32
  124. package/dist/graph-merge/index.js.map +1 -1
  125. package/dist/{index-CHTv4XMZ.d.cts → index-COs_z6nV.d.cts} +2 -2
  126. package/dist/{index-CzoJ3MUw.d.ts → index-D7d1u2gr.d.ts} +2 -2
  127. package/dist/index.cjs +90 -86
  128. package/dist/index.cjs.map +1 -1
  129. package/dist/index.d.cts +12 -12
  130. package/dist/index.d.ts +12 -12
  131. package/dist/index.js +13 -13
  132. package/dist/index.js.map +1 -1
  133. package/dist/indexes/index.d.cts +4 -4
  134. package/dist/indexes/index.d.ts +4 -4
  135. package/dist/ingestion-import-target-XPxZYw89.d.cts +17 -0
  136. package/dist/ingestion-import-target-dNeL7iAv.d.ts +17 -0
  137. package/dist/interchange/index.cjs +38 -38
  138. package/dist/interchange/index.d.cts +15 -12
  139. package/dist/interchange/index.d.ts +15 -12
  140. package/dist/interchange/index.js +10 -10
  141. package/dist/{manager-DlY1vlla.d.cts → manager-CYaff3n3.d.cts} +2 -2
  142. package/dist/{manager-B6sOQzcQ.d.ts → manager-D9eRT_ns.d.ts} +2 -2
  143. package/dist/{materialize-indexes-DQL4xcpf.d.ts → materialize-indexes-DJ3ykH59.d.ts} +1 -1
  144. package/dist/{materialize-indexes-sskhp-c_.d.cts → materialize-indexes-DTtAnvfY.d.cts} +1 -1
  145. package/dist/{postgres-BXy_nyMs.d.cts → postgres-CcL4bAHS.d.ts} +1 -1
  146. package/dist/{postgres-B6YOUcaK.d.ts → postgres-DMj39MEu.d.cts} +1 -1
  147. package/dist/profiler/index.d.cts +9 -9
  148. package/dist/profiler/index.d.ts +9 -9
  149. package/dist/provenance/index.cjs +25 -26
  150. package/dist/provenance/index.cjs.map +1 -1
  151. package/dist/provenance/index.d.cts +7 -7
  152. package/dist/provenance/index.d.ts +7 -7
  153. package/dist/provenance/index.js +9 -10
  154. package/dist/provenance/index.js.map +1 -1
  155. package/dist/repair-validity-windows-BFNZvd_H.d.cts +166 -0
  156. package/dist/repair-validity-windows-DlUSqwCg.d.ts +166 -0
  157. package/dist/schema/index.cjs +35 -35
  158. package/dist/schema/index.d.cts +6 -6
  159. package/dist/schema/index.d.ts +6 -6
  160. package/dist/schema/index.js +8 -8
  161. package/dist/{searchable-D9gMiwcS.d.ts → searchable-DHJMh_nj.d.cts} +1 -1
  162. package/dist/{searchable-0Cn_lY8U.d.cts → searchable-bpE1bBfc.d.ts} +1 -1
  163. package/dist/{sqlite-DioSxaPe.d.cts → sqlite-D7Oz0SQh.d.ts} +1 -1
  164. package/dist/{sqlite-ClXuBYAj.d.ts → sqlite-YppHrNFb.d.cts} +1 -1
  165. package/dist/{store-5j-w5onJ.d.ts → store-DRrFCx5x.d.ts} +122 -27
  166. package/dist/{store-CFedKGU5.d.cts → store-bphCSo7z.d.cts} +122 -27
  167. package/dist/{temporal-CjO9FH1l.d.ts → temporal-5GqpYCk2.d.cts} +1 -1
  168. package/dist/{temporal-Ce1pG0S1.d.cts → temporal-CbnLzTmM.d.ts} +1 -1
  169. package/dist/{types-DKkMJZjR.d.cts → types-BqFZTQmV.d.cts} +29 -4
  170. package/dist/{types-DKkMJZjR.d.ts → types-BqFZTQmV.d.ts} +29 -4
  171. package/dist/{types-BrdOFu48.d.ts → types-Bx74kaTt.d.ts} +1 -1
  172. package/dist/{types-oj_OGAaZ.d.cts → types-CXAhpW-c.d.cts} +1 -1
  173. package/dist/{where-D1wKCJ4Q.d.cts → where-DI_Fqz-X.d.cts} +1 -1
  174. package/dist/{where-C8w2627G.d.ts → where-O-k2wgYm.d.ts} +1 -1
  175. package/package.json +2 -3
  176. package/dist/chunk-3HGRTCDU.cjs +0 -269
  177. package/dist/chunk-3HGRTCDU.cjs.map +0 -1
  178. package/dist/chunk-425IZOQ5.cjs.map +0 -1
  179. package/dist/chunk-5FBRVORR.cjs.map +0 -1
  180. package/dist/chunk-5FT3TLPK.js.map +0 -1
  181. package/dist/chunk-5UMTUOM5.js.map +0 -1
  182. package/dist/chunk-6FWSNSRE.js.map +0 -1
  183. package/dist/chunk-AVGUJWVX.js.map +0 -1
  184. package/dist/chunk-BCQ76AXA.cjs.map +0 -1
  185. package/dist/chunk-BR7SHKGM.cjs.map +0 -1
  186. package/dist/chunk-DJZTXBWY.js.map +0 -1
  187. package/dist/chunk-EMIB2RXD.cjs.map +0 -1
  188. package/dist/chunk-FNOMT74T.js.map +0 -1
  189. package/dist/chunk-I4G3SQPF.cjs.map +0 -1
  190. package/dist/chunk-JR6SX5RH.cjs +0 -268
  191. package/dist/chunk-JR6SX5RH.cjs.map +0 -1
  192. package/dist/chunk-KABFHAL7.cjs.map +0 -1
  193. package/dist/chunk-KH7XW7VV.js.map +0 -1
  194. package/dist/chunk-M35DF33T.js.map +0 -1
  195. package/dist/chunk-MFZR4QKM.cjs.map +0 -1
  196. package/dist/chunk-MX4DHSQP.cjs.map +0 -1
  197. package/dist/chunk-NAZ2RA4L.js +0 -254
  198. package/dist/chunk-NAZ2RA4L.js.map +0 -1
  199. package/dist/chunk-NG2RB5WL.js.map +0 -1
  200. package/dist/chunk-NNQ6A3RM.js.map +0 -1
  201. package/dist/chunk-OIFSOPSR.cjs.map +0 -1
  202. package/dist/chunk-QS3NQZVC.js.map +0 -1
  203. package/dist/chunk-SE6EGQSD.js.map +0 -1
  204. package/dist/chunk-SHSSRCFR.js +0 -246
  205. package/dist/chunk-SHSSRCFR.js.map +0 -1
  206. package/dist/chunk-U5EDAVOJ.cjs.map +0 -1
  207. package/dist/chunk-UPAJHMW3.cjs.map +0 -1
  208. package/dist/chunk-WGX7JH7E.cjs.map +0 -1
  209. package/dist/chunk-ZHNXLS3C.js.map +0 -1
  210. package/dist/ddl-CprnMXJ-.d.ts +0 -38
  211. package/dist/ddl-DJA7Lci5.d.cts +0 -38
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/backend/transaction-resource.ts","../src/backend/types.ts"],"names":["typeGraphGlobalSymbol"],"mappings":"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","file":"chunk-3HGRTCDU.cjs","sourcesContent":["/**\n * Ownership tracking for backends that serialize every statement onto ONE\n * database connection.\n *\n * Two facts are recorded here, and only these two:\n *\n * 1. **Which serialized resource a backend wrapper belongs to.** Distinct\n * `GraphBackend` objects (a second `createPostgresBackend(...)` over the same\n * client, a projection, an overlay, a managed-close wrapper) are not `===`,\n * yet their statements land on the same connection. Marking a backend with\n * the underlying client object — and inheriting that mark through every\n * decorator — makes \"these two wrappers cannot run a read transaction and a\n * write transaction at the same time\" answerable.\n *\n * Only backends whose driver is known to be single-connection are marked;\n * everything else is deliberately left unmarked, because a pooled connection\n * hands out an independent connection per checkout and refusing concurrent\n * work there would refuse legitimate work. The complete classification —\n * marked, deliberately unmarked, and known gaps — is the inventory at the\n * bottom of this doc.\n *\n * 2. **Which long-lived interchange stream currently holds that resource.** A\n * streaming export holds a read-only transaction on its connection for the\n * whole stream; a streaming import opens a write transaction per chunk on\n * that same connection for the whole stream. Any second long-lived stream on\n * the resource therefore either nests a transaction inside the first one\n * (`cannot start a transaction within a transaction`) or waits for a\n * connection that is never released — and that is true of ALL FOUR pairings,\n * not only the two cross-kind ones:\n *\n * | holder | second stream | outcome without the lease |\n * | --------------- | ---------------- | ---------------------------------- |\n * | export snapshot | import stream | import's chunk waits on the reader |\n * | import stream | export snapshot | export's read strands the import |\n * | import stream | import stream | nested BEGIN |\n * | export snapshot | export snapshot | nested BEGIN |\n *\n * So the lease is EXCLUSIVE, not refcounted: at most one stream of any kind\n * holds a given serialized resource, and the second one is refused with the\n * holder's kind named (see {@link acquireSerializedStreamLease}). Refcounting\n * admitted the two same-kind rows above, which then failed on the driver\n * after chunks had already committed.\n *\n * `\"import-stream\"` is the kind of EVERY long-lived import, not only the\n * chunk-streaming one: an in-memory `importGraph` and a `trustedImport`\n * session write through the same one connection over the same kind of window,\n * so they claim the same lease and are refused by the same holders.\n *\n * The check and the registration happen in ONE synchronous section inside\n * {@link acquireSerializedStreamLease} — there is no way for a caller to check\n * without claiming, so there is no window to get wrong. On a single-threaded\n * event loop a check with no `await` before its registration is atomic: two\n * long-lived streams cannot both observe a free resource, so no interleaving is\n * left where each waits for the other.\n *\n * Neither fact is a general concurrency model:\n *\n * - No attempt is made to detect shared connections we cannot see (two `pg`\n * Clients dialed at the same server, two better-sqlite3 handles on one file).\n * Those are genuinely independent connections and are correctly not refused.\n * - RESIDUAL GAP: the lease is keyed by serialized resource, so an UNMARKED\n * backend registers nothing and can hold nothing. A driver we cannot\n * positively identify as single-connection therefore keeps exactly today's\n * protection — the identity-based pre-flight in `importGraphStream`, which\n * only sees a stream that still names its own source backend — and an\n * export/import pair interleaved on such a connection can still wedge. Closing\n * that requires recognizing the driver, not more bookkeeping here.\n * - RESIDUAL GAP: a `transactions: false` export abstains entirely — it opens no\n * snapshot transaction, so `exportGraphStream` neither claims the lease nor\n * consults it: such an export is never refused and never refuses anyone.\n * A streaming import claims the lease whatever its backend reports,\n * because its writes still have to land somewhere an open snapshot is not.\n * The visible cost of that asymmetry is one conservative refusal: two\n * streaming imports through a MARKED but deliberately `transactionMode:\n * \"none\"` connection frame nothing and would in fact interleave harmlessly,\n * yet the second is refused. That is the deliberate trade — the alternative\n * (gating the import's claim on `capabilities.transactions` too) would stop a\n * real snapshot export from being refused mid-import on a mixed-profile\n * connection, which is the far likelier pairing.\n * - RESIDUAL GAP: the lease's population is INTERCHANGE STREAMS. Ordinary\n * long-lived frames on the same serialized connection — a `store.transaction`\n * held across awaits by application code, `schemaWriteTransaction` (the\n * provenance claim, the identity DDL fence), `store.evolve()` — neither hold\n * nor consult it, so one of those opening while an export snapshot is live\n * on the same connection blocks or wedges without a typed error, exactly as\n * any two of them always have. Representing every long-lived frame here is a\n * different, larger contract (a connection-wide frame lease); the streams\n * are covered because they are the pairing users actually compose.\n *\n * ## Inventory of serialized resources\n *\n * Every `GraphBackend` in this package is built by `createSqliteBackend` or\n * `createPostgresBackend` (the batteries-included factories — local\n * better-sqlite3, libSQL, PGlite — and every decorator go through one of them),\n * so those two factories are the only places a mark is applied, and this list is\n * the whole population. Predicates are named rather than line-cited so the\n * inventory stays greppable as the files move.\n *\n * MARKED — the factory resolves the resource before the backend object exists,\n * so no wrapper can observe it unmarked:\n *\n * - better-sqlite3 `Database` (`prepare` + `pragma`) — `createSqliteBackend` via\n * `getSerializedSqliteConnection`, which asks one named predicate per driver.\n * - bun:sqlite `Database` (`prepare` + `query` + `run`/`exec` + `serialize` +\n * a `filename` string) — same site, via `isBunSqliteClient`. One synchronous\n * connection, like better-sqlite3; `query` and `filename` are what separate\n * it structurally from better-sqlite3 (`pragma`, `name`), so the mark never\n * rests on a session's constructor name, which bundlers rename.\n * - sql.js `Database` (`prepare` + `exec` + `run` + `export` +\n * `getRowsModified`) — same site, via `isSqlJsClient`. One in-WASM handle\n * executed in-process; `export` and `getRowsModified` are sql.js's own API\n * and exist on no other SQLite client.\n * - Local `@libsql/client` (`protocol === \"file\"`: `file:` paths, `:memory:`,\n * an embedded replica's local file) — same site, via `isLocalLibsqlClient`.\n * - PGlite — `createPostgresBackend` via `getPgliteClient` (`query` +\n * `dumpDataDir`).\n * - Bare `pg` / `@neondatabase/serverless` `Client`, including a checked-out\n * `PoolClient` — same site, via `isBarePgClient`.\n * - `pg` / neon-serverless `Pool` whose resolved `options.max === 1` — same\n * site, via `isSingleConnectionPgPool`. pg-pool normalizes `max` (and the\n * legacy `poolSize`) into `options`, so every cap that pool honors is visible\n * there.\n * - postgres-js built with `{ max: 1 }` — same site, via\n * `isSingleConnectionCallablePgClient`. The client is CALLABLE, so it needed a\n * resolver arm of its own; `begin` reserves the sole connection, making an\n * export snapshot hold the connection every other wrapper writes through.\n * Requires postgres-js identity (`isPostgresJsClient`) as well as the cap: a\n * cap on a callable we cannot attribute to a known driver is not evidence.\n * - Cloudflare Durable Object storage (`drizzle(ctx.storage)`) —\n * `createSqliteBackend` via `getDurableObjectStorageClient`, the same\n * full-shape evidence `transactionMode: \"do-sqlite\"` requires to run a\n * transaction. The storage transaction frame is AMBIENT on the storage object\n * (no tx handle exists on DO), so a second wrapper's writes land inside the\n * first wrapper's export snapshot; nothing else abstains, since the DO backend\n * reports `capabilities.transactions: true`.\n *\n * DELIBERATELY UNMARKED, by class — marking these would refuse work that\n * succeeds:\n *\n * - Pooled connections: a default-size `pg` / neon-serverless / `@vercel/postgres`\n * pool, postgres-js or Bun `SQL` at default size. Each checkout is an\n * independent connection.\n * - Session-less HTTP drivers: `neon-http`, Cloudflare D1, RDS Data API. Nothing\n * is held between statements, and the two that report\n * `capabilities.transactions: false` are additionally short-circuited by\n * {@link snapshotExportContention} before marking is consulted.\n * - Remote `@libsql/client` (`http` / `ws`): an independent stream per\n * transaction.\n * - Separate handles on one database (two `pg` Clients at one server, two\n * better-sqlite3 handles on one file): genuinely concurrent connections.\n * - Transaction-scoped backends (`transaction()`, `adoptTransaction`): the\n * caller's transaction already owns the connection and opens no second frame,\n * and the guards run on the root backend a Store holds. Decorators over a root\n * backend DO carry the mark, via\n * {@link inheritSerializedTransactionResource}.\n *\n * KNOWN GAPS — serialized in fact, unmarked, so they keep only the\n * identity-based pre-flight in `importGraphStream` (which a `history: true`\n * store's overlay already defeats). Each is a coverage extension needing\n * positive evidence of the client shape, never a silent no-op:\n *\n * - React Native / Expo SQLite drivers — `expo-sqlite` and `op-sqlite`. One\n * connection each, and both are genuinely serialized, but neither exposes a\n * plain `prepare` (`prepareAsync` / `prepareSync`, `prepareStatement`), so\n * none of the predicates above can see them and neither driver is installable\n * here to derive the shape from rather than guess it. Remaining #434 scope:\n * each needs its own positive shape, taken from the driver's own typings.\n * - `node:sqlite` `DatabaseSync`: UNREACHABLE through Drizzle today —\n * drizzle-orm 0.45.2 ships `bun-sqlite`, `sql-js`, `durable-sqlite`,\n * `expo-sqlite`, `op-sqlite`, `sqlite-proxy` and `better-sqlite3`, and no\n * `node-sqlite` entrypoint, so no `createSqliteBackend` call can be handed\n * one. Re-check when the Drizzle floor moves; nothing to mark until then.\n * - Bun `SQL` (Postgres) built with `{ max: 1 }`: genuinely serialized, but\n * nothing in this package positively identifies that driver — there is no\n * Postgres equivalent of the SQLite driver shapes above — so `options.max`\n * there cannot be attributed to a driver whose dispatch we know. Marking it\n * needs a Bun-`SQL` discriminator first. Remaining #434 scope.\n * - Drivers we cannot identify at all (`sqlite-proxy`, `pg-proxy`, a bespoke\n * adapter): whether the far side serializes is unknowable from here, so they\n * fall under the residual gap above.\n */\nimport { type GraphBackend } from \"./types\";\n\nconst SERIALIZED_TRANSACTION_RESOURCES = new WeakMap<object, object>();\n\n/**\n * The kinds of long-lived interchange stream that hold a serialized connection\n * across many statements — the population the lease is exclusive over.\n */\nexport type SerializedStreamKind = \"export-snapshot\" | \"import-stream\";\n\n/**\n * The outcome of claiming a serialized resource for a long-lived stream: the\n * (idempotent) release when the claim succeeded, or the kind of stream already\n * holding it when it did not.\n */\nexport type SerializedStreamLease =\n | Readonly<{ acquired: true; release: () => void }>\n | Readonly<{ acquired: false; heldBy: SerializedStreamKind }>;\n\n/**\n * The one long-lived stream holding each serialized resource. An entry exists\n * only while that stream is in flight; its release deletes the key, so a\n * finished stream retains nothing and refuses nobody.\n */\nconst ACTIVE_SERIALIZED_STREAMS = new Map<object, SerializedStreamKind>();\n\n/**\n * Marks backends whose distinct wrappers still serialize on one connection.\n *\n * INVARIANT: a backend factory must call this before anything can wrap the\n * backend it built. Decorators (createBackendOverlay, wrapWithManagedClose,\n * projections) copy the mark at construction time, so a wrapper built before\n * the mark lands is silently unowned and evades the import/clone guards.\n */\nexport function markSerializedTransactionResource(\n backend: GraphBackend,\n resource: object,\n): void {\n SERIALIZED_TRANSACTION_RESOURCES.set(backend, resource);\n}\n\n/** Preserves serialized-resource ownership when decorating a backend. */\nexport function inheritSerializedTransactionResource(\n target: object,\n source: object,\n): void {\n const resource = SERIALIZED_TRANSACTION_RESOURCES.get(source);\n if (resource !== undefined) {\n SERIALIZED_TRANSACTION_RESOURCES.set(target, resource);\n }\n}\n\n/** Whether two backend wrappers cannot make snapshot reads and writes concurrently. */\nexport function sharesSerializedTransactionResource(\n left: GraphBackend,\n right: GraphBackend,\n): boolean {\n const leftResource = SERIALIZED_TRANSACTION_RESOURCES.get(left);\n return (\n leftResource !== undefined &&\n leftResource === SERIALIZED_TRANSACTION_RESOURCES.get(right)\n );\n}\n\n/** How a snapshot export on one backend contends with a write on another. */\nexport type SnapshotExportContention =\n \"same-sqlite-backend\" | \"shared-resource\";\n\n/**\n * Whether a snapshot export on `source` would hold the one connection `target`\n * writes through, and via which detector. `undefined` means no contention.\n *\n * The single owner of \"a snapshot export blocks this write\": the streaming\n * import guard, and the working-copy cloner that exists to pre-empt it, both\n * ask here rather than re-deriving the arms.\n *\n * - A `transactions: false` source (SQLite `transactionMode: \"none\"`, HTTP-only\n * Postgres drivers) exports statement by statement with nothing held open, so\n * sharing its connection is merely interleaving and is never contention.\n * - Object identity on a SQLite backend is checked FIRST, so a marked\n * better-sqlite3 backend exporting into itself reports the more specific\n * detector even though the shared-resource arm would also match.\n */\nexport function snapshotExportContention(\n source: GraphBackend,\n target: GraphBackend,\n): SnapshotExportContention | undefined {\n if (!source.capabilities.transactions) return undefined;\n if (source === target && source.dialect === \"sqlite\") {\n return \"same-sqlite-backend\";\n }\n if (sharesSerializedTransactionResource(source, target)) {\n return \"shared-resource\";\n }\n return undefined;\n}\n\n/**\n * Claims the serialized resource `backend` runs on for a long-lived stream of\n * `kind`, or reports which kind of stream already holds it.\n *\n * EXCLUSIVE: one stream per serialized resource, whatever its kind. Two exports\n * nest their snapshot transactions on the one connection exactly as an export\n * and an import do, so \"is a stream already running here\" — not \"is a stream of\n * the OTHER kind already running here\" — is the question the holder registry\n * answers.\n *\n * ONE SYNCHRONOUS SECTION, by construction: the lookup and the registration are\n * in this function's body with no `await` between them, and there is no separate\n * \"is it free?\" query a caller could read and then act on later. On a\n * single-threaded event loop that makes \"no other stream held this connection\n * when this one claimed it\" true for the whole stream, in whichever order two\n * streams start — the second one always finds the first's registration.\n *\n * Marked backends only: without a known serialized resource there is no shared\n * connection for anyone to be refused against, so an unmarked backend always\n * acquires and registers nothing (see the residual gap in the module doc).\n *\n * The returned release is idempotent and deletes only its own registration,\n * so an already-released stream can never evict the next stream's lease.\n */\nexport function acquireSerializedStreamLease(\n backend: GraphBackend,\n kind: SerializedStreamKind,\n): SerializedStreamLease {\n const resource = SERIALIZED_TRANSACTION_RESOURCES.get(backend);\n if (resource === undefined) {\n return {\n acquired: true,\n release: () => {\n // Nothing was registered: an unmarked backend has no known serialized\n // resource, so there is nothing to give back.\n },\n };\n }\n const holder = ACTIVE_SERIALIZED_STREAMS.get(resource);\n if (holder !== undefined) return { acquired: false, heldBy: holder };\n ACTIVE_SERIALIZED_STREAMS.set(resource, kind);\n let released = false;\n return {\n acquired: true,\n release: () => {\n if (released) return;\n released = true;\n ACTIVE_SERIALIZED_STREAMS.delete(resource);\n },\n };\n}\n","/**\n * Backend interface types for TypeGraph storage.\n *\n * The backend abstracts database operations, allowing different\n * SQL implementations (SQLite, PostgreSQL) behind a common interface.\n */\nimport {\n type IndexEntity,\n type JsonValue,\n type KindEntity,\n type TemporalMode,\n} from \"../core/types\";\nimport { type SqlTableNames } from \"../query/compiler/schema\";\nimport { type FulltextStrategy } from \"../query/dialect/fulltext-strategy\";\nimport {\n type VectorSlot,\n type VectorStrategy,\n} from \"../query/dialect/vector-strategy\";\nimport { type SqlFragment } from \"../query/sql-fragment\";\nimport {\n type CompiledRowsSql,\n type CompiledSelectSql,\n type CompiledStatementSql,\n type CompiledTemporaryStatementSql,\n} from \"../query/sql-intent\";\nimport { type SerializedSchema } from \"../schema/types\";\nimport { typeGraphGlobalSymbol } from \"../utils/global-symbol\";\nimport { inheritSerializedTransactionResource } from \"./transaction-resource\";\n\n// ============================================================\n// Vector Search Types\n// ============================================================\n\n/**\n * Supported vector similarity metrics.\n */\nexport type VectorMetric = \"cosine\" | \"l2\" | \"inner_product\";\n\n/**\n * Supported vector index types.\n */\nexport type VectorIndexType = \"hnsw\" | \"ivfflat\" | \"none\";\n\n/**\n * The mechanism a strategy uses to combine a row filter with an *approximate*\n * (ANN) vector search.\n *\n * Every approximate search TypeGraph issues carries at least one filter: the\n * liveness predicate that excludes soft-deleted and out-of-validity rows. Add\n * a `.where(...)` predicate and it narrows further. Engines differ in where\n * they apply it relative to the index traversal:\n *\n * - `\"filter-pushdown\"` — the filter constrains the ANN candidate set itself.\n * sqlite-vec's `vec0` KNN accepts primary-key `IN (SELECT …)` pushdown.\n * - `\"iterative-scan\"` — the engine re-enters the index, gathering more\n * candidates until `LIMIT` rows survive the filter *or* it hits its own scan\n * bound. pgvector >= 0.8 (`hnsw.iterative_scan` / `ivfflat.iterative_scan`,\n * applied automatically by the backend).\n * - `\"post-filter\"` — the engine retrieves a fixed multiple of the page from\n * the ANN index and applies the filter afterwards. libSQL's DiskANN\n * `vector_top_k` is a table function with no filter pushdown, so this is the\n * only shape available to it.\n *\n * This names what the strategy *asks the engine to do*. Whether the engine can\n * honor it — and whether honoring it is enough to fill a page — is\n * {@link FilteredApproximateSearch.guaranteesFullPage}.\n */\nexport type FilteredApproximateSearchMode =\n \"filter-pushdown\" | \"iterative-scan\" | \"post-filter\";\n\n/**\n * How a filtered approximate (ANN) vector search behaves, and whether it can\n * silently return fewer rows than `limit` while more matches exist.\n *\n * Read `guaranteesFullPage` — not `mode` — to decide whether a short page is\n * possible. Only `\"filter-pushdown\"` guarantees a full page:\n *\n * - **sqlite-vec** pushes the filter into the KNN. Exact.\n * - **pgvector** re-enters the index, but the iterative scan stops at\n * `hnsw.max_scan_tuples` / `ivfflat.max_probes`, and on **pgvector < 0.8**\n * there is no iterative scan at all — the backend detects that at runtime,\n * warns once, and the search stays `ef_search`-bounded, behaving like\n * `\"post-filter\"`. Neither case is visible in a statically-declared `mode`,\n * which is exactly why the guarantee is a separate field.\n * - **libSQL** over-fetches a fixed multiple of the page and filters\n * afterwards, so it under-fills once more than that headroom is filtered out.\n *\n * A store with heavy tombstone drift — routine in a temporal store — is what\n * turns a short page from a theoretical caveat into an observed one. Exact\n * (`approximate: false`) searches are unaffected on every engine: they scan, so\n * the filter reaches every row.\n */\nexport type FilteredApproximateSearch = Readonly<{\n mode: FilteredApproximateSearchMode;\n /**\n * `true` only when a filtered approximate search is guaranteed to return\n * `limit` rows whenever `limit` matching rows exist — no engine-side scan\n * bound, no version dependence, no over-fetch headroom to exhaust.\n */\n guaranteesFullPage: boolean;\n}>;\n\n/**\n * Whether an engine exposes a PER-SEARCH knob for the ANN candidate frontier —\n * the parameter {@link VectorSearchParams.efSearch} maps onto.\n *\n * Declared, never inferred: `efSearch` is an accepted option, so a backend that\n * cannot apply it must refuse it rather than ignore it (AGENTS.md contract\n * discipline). This capability is what the shared refusal predicate reads, and\n * what a caller reads to know whether passing `efSearch` will be honored.\n *\n * - **pgvector**: `hnsw.ef_search`, applied per search with `SET LOCAL` inside\n * the search's own transaction — hence `requiresTransactionScope`, since a\n * session-wide `SET` would leak the override into concurrent searches.\n * - **sqlite-vec**: `tunable: false`. A vec0 KNN takes `k` and nothing else;\n * there is no frontier to widen.\n * - **libSQL DiskANN**: `tunable: false`. `vector_top_k(idx, q, k)` is a table\n * function with no per-query parameters; DiskANN's `search_l` is fixed at\n * index-creation time.\n */\nexport type VectorSearchFrontierTuning =\n | Readonly<{\n tunable: true;\n /** Engine-native parameter `efSearch` maps to (`\"hnsw.ef_search\"`). */\n parameter: string;\n /**\n * The one index type whose search honors the parameter. `efSearch` on a\n * slot of any other index type is refused, not ignored: an IVFFlat or\n * brute-force slot would silently discard it.\n */\n indexType: VectorIndexType;\n /**\n * `true` when applying the parameter needs a transaction to scope it to\n * the one search. A backend that reports `transactions: false` refuses\n * `efSearch` rather than leaking a session-wide setting.\n */\n requiresTransactionScope: boolean;\n }>\n | Readonly<{\n tunable: false;\n /**\n * Why this engine has no per-search frontier knob — surfaced in the\n * refusal's details so the state is named rather than implied.\n */\n reason: string;\n }>;\n\n/**\n * Vector search capabilities.\n */\nexport type VectorCapabilities = Readonly<{\n /** Whether the backend supports vector operations */\n supported: boolean;\n /** Supported similarity metrics */\n metrics: readonly VectorMetric[];\n /** Supported index types */\n indexTypes: readonly VectorIndexType[];\n /** Maximum dimensions supported */\n maxDimensions: number;\n /**\n * How a filtered approximate search bounds recall — and whether it can\n * silently return a short page. See {@link FilteredApproximateSearch}.\n *\n * Required, so a new vector strategy cannot omit the declaration and inherit\n * an engine promise it does not keep.\n */\n filteredApproximateSearch: FilteredApproximateSearch;\n /**\n * Whether a per-search ANN frontier override (`efSearch`) can be applied.\n * See {@link VectorSearchFrontierTuning}.\n *\n * Required, so a new vector strategy must state whether it honors the\n * option instead of inheriting silence — the exact defect this closes.\n */\n searchFrontierTuning: VectorSearchFrontierTuning;\n}>;\n\n// ============================================================\n// Fulltext Search Types\n// ============================================================\n\n/**\n * Query modes for fulltext search.\n *\n * - \"websearch\": Google-style syntax (quoted phrases, +required, -excluded).\n * Postgres: `websearch_to_tsquery`. SQLite: translated to FTS5 MATCH.\n * - \"phrase\": treats the whole query as a phrase.\n * Postgres: `phraseto_tsquery`. SQLite: FTS5 `\"...\"` phrase.\n * - \"plain\": splits on whitespace and ANDs terms.\n * Postgres: `plainto_tsquery`. SQLite: default FTS5 AND.\n * - \"raw\": dialect-native syntax passed through unchanged.\n */\nexport type FulltextQueryMode = \"websearch\" | \"phrase\" | \"plain\" | \"raw\";\n\n/**\n * Fulltext search capabilities declared by a backend.\n */\nexport type FulltextCapabilities = Readonly<{\n /** Whether the backend supports fulltext operations */\n supported: boolean;\n /**\n * Language / tokenizer names understood by this backend.\n * Postgres: installed regconfigs (english, simple, ...).\n * SQLite FTS5: tokenizer names (unicode61, porter, trigram).\n */\n languages: readonly string[];\n /** Whether phrase queries are supported. */\n phraseQueries: boolean;\n /** Whether prefix (`foo*`) queries are supported. */\n prefixQueries: boolean;\n /** Whether highlighting / snippets are supported. */\n highlighting: boolean;\n}>;\n\n/**\n * Whole-graph analytics capabilities declared by a backend.\n *\n * `supported` means the backend can pin one transactional connection while\n * an algorithm creates, updates, and drops session-local temporary state.\n * `mathFunctions` records whether transcendental SQL functions needed by\n * deferred algorithms such as MCL/spectral are available; exact WCC does not\n * require them.\n */\nexport type GraphAnalyticsCapabilities = Readonly<{\n supported: boolean;\n mathFunctions: boolean;\n}>;\n\n/**\n * How much of the contribution health lifecycle a backend can serve.\n *\n * The three rungs escalate — probe (read-only) → repair\n * (non-destructive) → rebuild (destructive) — and each is declared\n * separately because a backend can genuinely stop at any of them. A\n * backend that provisions contributions but cannot probe its own catalog\n * is a real gap, and the honest answer to `store.probeContributions()`\n * there is a refusal, not `ready`: \"assessed and healthy\" and \"never\n * looked\" must never share a return value.\n */\nexport type ContributionCapabilities = Readonly<{\n /**\n * Whether strategy-owned contribution tables are provisioned and\n * attested by durable markers on this backend at all. `false` means\n * there is nothing to assess, and the probe reports no entries rather\n * than refusing.\n */\n supported: boolean;\n /**\n * Whether the backend can cross its durable markers against the live\n * catalog — `store.probeContributions()` and\n * `store.verifyContributions()`. False with `supported: true` is the\n * declared gap: both refuse with `ConfigurationError`.\n */\n probe: boolean;\n /**\n * Whether the backend can run the destructive drop → recreate →\n * repopulate → stamp rebuild (`store.rebuildContribution()`). Requires\n * both a strategy that declares `dropDdl` and a transactional schema\n * fence to run it under; false means `rebuildContribution` refuses.\n *\n * Describes the fulltext projection only. `rebuildContribution(\"vector\")`\n * refuses on every backend regardless of this flag — embeddings exist\n * only in the storage a rebuild would drop, so there is nothing to\n * reconstruct them from — and `reembedVectorField` is the sanctioned\n * destructive path there.\n */\n rebuild: boolean;\n}>;\n\n// ============================================================\n// SQL Dialect & Capabilities\n// ============================================================\n\nimport { type SqlDialect } from \"../query/dialect/types\";\n\nexport type { SqlDialect } from \"../query/dialect/types\";\n\n/**\n * Backend capabilities that vary by dialect.\n */\nexport type BackendCapabilities = Readonly<{\n /** Whether the backend supports atomic transactions (D1 does not) */\n transactions: boolean;\n /** Whether the backend supports SQL window functions such as ROW_NUMBER() */\n windowFunctions: boolean;\n /**\n * Whether `updateNode` / `updateEdge` honor `clearValidTo: true` by storing\n * SQL NULL in `valid_to`. Absent is `false`: custom backends must opt in so\n * the store refuses every explicit clear-bearing call before lookup,\n * coalescing, or write instead of making support depend on row state.\n */\n clearValidTo?: boolean;\n /**\n * Whether the backend's `execute()` supports `UPDATE … RETURNING`. Absent or\n * `true` means supported (every engine TypeGraph ships — SQLite ≥ 3.35,\n * PostgreSQL ≥ 8.2 — supports it). A custom backend whose engine cannot run\n * `RETURNING` must set this to `false` so recorded-time history capture\n * (`history: true`), which relies on `UPDATE … RETURNING` on its hot path,\n * is refused up front instead of failing mid-flush.\n */\n returning?: boolean;\n /**\n * Maximum number of bound parameters the engine accepts in one statement.\n * SQLite defaults to 999 (raisable at compile time via\n * `SQLITE_MAX_VARIABLE_NUMBER`), while hosted SQLite runtimes may impose a\n * lower platform ceiling. PostgreSQL's wire protocol encodes a 65535-count,\n * but postgres.js accepts at most 65533 bound values, so the bundled\n * PostgreSQL capability advertises that lower shared-driver ceiling.\n * Recorded-time capture and recorded point reads size their multi-row\n * statements to this ceiling — the same budget the backend's own batched\n * inserts use — instead of a conservative dialect-blind constant. Custom\n * runtimes can override a detected or probed limit here, but hosted platform\n * hard ceilings may only be lowered. Absent means the recorded-time fallback\n * budget applies.\n */\n maxBindParameters?: number;\n /** Vector search capabilities (undefined if not configured) */\n vector?: VectorCapabilities | undefined;\n /** Fulltext search capabilities (undefined if not configured) */\n fulltext?: FulltextCapabilities | undefined;\n /** Whole-graph analytics capabilities (undefined when unavailable). */\n graphAnalytics?: GraphAnalyticsCapabilities | undefined;\n /**\n * How far up the contribution health ladder this backend goes\n * (undefined on a backend with no contribution machinery at all,\n * equivalent to every member `false`).\n */\n contributions?: ContributionCapabilities | undefined;\n}>;\n\n/** Keeps session-scoped analytics honest when a required SQL feature is absent. */\nexport function normalizeGraphAnalyticsCapabilities(\n capabilities: BackendCapabilities,\n): BackendCapabilities {\n if (\n capabilities.graphAnalytics?.supported !== true ||\n (capabilities.transactions &&\n capabilities.windowFunctions &&\n capabilities.returning !== false)\n ) {\n return capabilities;\n }\n return {\n ...capabilities,\n graphAnalytics: { ...capabilities.graphAnalytics, supported: false },\n };\n}\n\n// ============================================================\n// Row Types (Database Records)\n// ============================================================\n\n/**\n * A node/edge row's `props` column as the driver returned it: SQLite always\n * yields the JSON text; PostgreSQL drivers yield the jsonb value already\n * parsed. Keeping the driver's shape avoids a per-row\n * parse→stringify→re-parse round trip on the PostgreSQL read path —\n * consumers normalize through {@link rowPropsToObject} /\n * {@link rowPropsToJsonText} at the point of use.\n */\nexport type RowProps = string | Readonly<Record<string, unknown>>;\n\n/** The row's props as an object, parsing only when the driver gave text. */\nexport function rowPropsToObject(props: RowProps): Record<string, unknown> {\n return typeof props === \"string\" ?\n (JSON.parse(props) as Record<string, unknown>)\n : props;\n}\n\n/** The row's props as JSON text, serializing only when the driver gave an object. */\nexport function rowPropsToJsonText(props: RowProps): string {\n return typeof props === \"string\" ? props : JSON.stringify(props);\n}\n\n/**\n * A row from the typegraph_nodes table.\n */\nexport type NodeRow = Readonly<{\n graph_id: string;\n kind: string;\n id: string;\n props: RowProps;\n version: number;\n valid_from: string | undefined;\n valid_to: string | undefined;\n created_at: string;\n updated_at: string;\n deleted_at: string | undefined;\n}>;\n\n/**\n * A {@link NodeRow} proven live (not soft-deleted). Functions that must only\n * ever touch live rows — the live-row update pipeline, soft delete — take\n * this type so a caller holding a possibly-tombstoned row is forced through\n * {@link isLiveNodeRow} first, instead of silently running live-row side\n * effects (uniqueness reservations, embedding/fulltext sync) for a row that\n * stays invisible.\n */\nexport type LiveNodeRow = NodeRow & Readonly<{ deleted_at: undefined }>;\n\n/**\n * A {@link NodeRow} proven soft-deleted. A resurrect targets exactly this\n * shape; see {@link isTombstonedNodeRow}.\n */\nexport type TombstonedNodeRow = NodeRow & Readonly<{ deleted_at: string }>;\n\nexport function isLiveNodeRow(row: NodeRow): row is LiveNodeRow {\n return row.deleted_at === undefined;\n}\n\nexport function isTombstonedNodeRow(row: NodeRow): row is TombstonedNodeRow {\n return row.deleted_at !== undefined;\n}\n\n/**\n * The read-only slice of a backend. Give this type to code that is\n * semantically a read — probes, gates, support-graph loads — so a read path\n * structurally CANNOT open a write transaction, mutate rows, or execute raw\n * SQL: those members do not exist on the type. Extend the pick as read paths\n * need more of the read surface.\n */\nexport type GraphReadBackend = Pick<\n GraphBackend,\n | \"dialect\"\n | \"getNode\"\n | \"getEdge\"\n | \"findNodesByKind\"\n | \"findEdgesByKind\"\n | \"findEdgesConnectedTo\"\n>;\n\n/**\n * A row from the typegraph_edges table.\n */\nexport type EdgeRow = Readonly<{\n graph_id: string;\n id: string;\n kind: string;\n from_kind: string;\n from_id: string;\n to_kind: string;\n to_id: string;\n props: RowProps;\n valid_from: string | undefined;\n valid_to: string | undefined;\n created_at: string;\n updated_at: string;\n deleted_at: string | undefined;\n}>;\n\n/**\n * A row from the typegraph_node_uniques table.\n */\nexport type UniqueRow = Readonly<{\n graph_id: string;\n node_kind: string;\n constraint_name: string;\n key: string;\n node_id: string;\n concrete_kind: string;\n deleted_at: string | undefined;\n}>;\n\n/**\n * A row from the typegraph_schema_versions table.\n */\nexport type SchemaVersionRow = Readonly<{\n graph_id: string;\n version: number;\n schema_hash: string;\n schema_doc: string; // JSON string\n created_at: string;\n is_active: boolean;\n}>;\n\n// ============================================================\n// Insert Parameters\n// ============================================================\n\n/**\n * Parameters for inserting a node.\n */\nexport type InsertNodeParams = Readonly<{\n graphId: string;\n kind: string;\n id: string;\n props: Readonly<Record<string, unknown>>;\n /**\n * Omitted (`undefined`): defaults to the insert's creation timestamp.\n * `null`: preserves an explicit open-left validity window (no lower\n * bound) — used by interchange import to round-trip a row that was\n * already NULL, instead of re-stamping it to the import's own timestamp.\n */\n validFrom?: string | null;\n validTo?: string;\n}>;\n\n/**\n * A backend validity-end mutation. Omission preserves the stored end,\n * `validTo` sets it, and `clearValidTo` reopens the window. The union keeps the\n * two write actions mutually exclusive without exposing SQL `NULL`.\n */\nexport type BackendValidityEndMutation =\n | Readonly<{ validTo?: string; clearValidTo?: never }>\n | Readonly<{ validTo?: never; clearValidTo: true }>;\n\n/** Parameters for updating a node. */\nexport type UpdateNodeParams = Readonly<{\n graphId: string;\n kind: string;\n id: string;\n props: Readonly<Record<string, unknown>>;\n /** Applied when resurrecting a tombstone; omitted means the resurrection instant. */\n validFrom?: string | null;\n /**\n * The effective `valid_from` this write ASSERTS the target row already\n * carries, stated only by a caller whose decision DEPENDED on it.\n *\n * `(graph_id, kind, id)` does not pin a row across time. A caller that\n * validated the document's window against the bound it probed — interchange\n * import's `onConflict: \"update\"` is the case — decided what to write from a\n * value a concurrent `hardDelete` + recreate can replace between the probe\n * and the write, so a predicate on identity alone lets that decision land on\n * a row it was never computed for: the document's stated `validFrom` is\n * ignored, or a `valid_to` is persisted below the new row's `valid_from`.\n * Stating the bound here puts it in the UPDATE's own `WHERE`, which is the\n * only placement the race cannot slip past.\n *\n * NULL-SAFE: `null` asserts the row has NO lower bound (`IS NULL`), a string\n * asserts equality, and omitting it asserts nothing. `null` and `undefined`\n * are therefore NOT interchangeable here — see `expectedValidFromPredicate`.\n *\n * A backend MUST apply the predicate when it is present, on the same terms as\n * {@link UpdateEdgeParams.kind}: silently ignoring it re-opens the window the\n * caller stated it to close. A write whose stated bound does not match affects\n * zero rows and surfaces as a `no_row_returned` `DatabaseOperationError`.\n */\n expectedValidFrom?: string | null;\n incrementVersion?: boolean;\n /** If true, clears deleted_at (un-deletes the node). Used by upsert. */\n clearDeleted?: boolean;\n}> &\n BackendValidityEndMutation;\n\n/**\n * Parameters for updating a set of live nodes selected by a compiled\n * TypeGraph query.\n *\n * The candidate query must expose the named node-id column from the same graph\n * and kind. The backend independently fences the rows it writes to\n * `graphId` and `kind`; constructing the matching candidate query remains the\n * caller's responsibility.\n * Property values replace top-level keys, including preserving an explicit\n * JSON `null` value.\n *\n * This is a storage primitive: callers that expose it as a graph mutation are\n * responsible for schema validation and for synchronizing uniqueness,\n * fulltext, and vector sidecars. The result contains after-images only; callers\n * that need previous property values must freeze the candidate set and retain\n * its before-images in the same transaction before invoking this method.\n */\nexport type UpdateNodeSetParams = Readonly<{\n graphId: string;\n kind: string;\n patch: Readonly<Record<string, JsonValue>>;\n /** Top-level properties to remove (the storage form of an `undefined` patch value). */\n unsetProperties?: readonly string[];\n candidateIds: CompiledSelectSql;\n /** Projected SQL column holding the candidate node id (for example `n_id`). */\n candidateIdColumn: string;\n}>;\n\n/** The after-images changed by {@link GraphBackend.updateNodeSet}. */\nexport type UpdateNodeSetResult = Readonly<{\n affectedCount: number;\n rows: readonly NodeRow[];\n}>;\n\n/**\n * Parameters for deleting a node (soft delete).\n */\nexport type DeleteNodeParams = Readonly<{\n graphId: string;\n kind: string;\n id: string;\n}>;\n\n/**\n * Parameters for inserting an edge.\n */\nexport type InsertEdgeParams = Readonly<{\n graphId: string;\n id: string;\n kind: string;\n fromKind: string;\n fromId: string;\n toKind: string;\n toId: string;\n props: Readonly<Record<string, unknown>>;\n /**\n * Omitted (`undefined`): defaults to the insert's creation timestamp.\n * `null`: preserves an explicit open-left validity window (no lower\n * bound) — used by interchange import to round-trip a row that was\n * already NULL, instead of re-stamping it to the import's own timestamp.\n */\n validFrom?: string | null;\n validTo?: string;\n}>;\n\n/**\n * Write-only session exposed inside {@link GraphBackend.trustedImport}.\n *\n * The caller has accepted the trusted-import contract: properties, endpoint\n * references, uniqueness, and cardinality are not validated. Backends may\n * therefore use engine-native ingestion primitives that bypass the normal\n * store write pipeline.\n */\nexport type TrustedImportSession = Readonly<{\n insertNodes: (params: readonly InsertNodeParams[]) => Promise<void>;\n insertEdges: (params: readonly InsertEdgeParams[]) => Promise<void>;\n}>;\n\n/**\n * Parameters for updating an edge.\n */\nexport type UpdateEdgeParams = Readonly<{\n graphId: string;\n id: string;\n /**\n * The kind this write ASSERTS the target row already carries.\n *\n * Edge ids are graph-global while public collections are kind-scoped, so a\n * kind-scoped write has an expected kind and must not land on a row carrying\n * a different one. Stating it here puts the predicate in the write\n * statement's own `WHERE`, which is the only placement a concurrent\n * `hardDelete` + recreate cannot slip past: a read-then-write pair keyed on\n * `(graph_id, id)` alone re-resolves that id between the read and the write\n * under PostgreSQL READ COMMITTED. A write whose stated kind does not match\n * affects zero rows.\n *\n * A backend MUST apply the predicate when it is present — silently ignoring\n * it re-opens the window the caller stated it to close, and the ignoring\n * backend looks correct until it is raced.\n * `tests/edge-write-self-verification.test.ts` asserts the behavior against\n * the built-in backends.\n *\n * Omitted where the operation legitimately spans kinds: the node delete\n * cascade removes every connected edge whatever its kind, so it states none.\n * {@link DeleteEdgeParams} and {@link HardDeleteEdgeParams} carry the same\n * field with the same contract.\n */\n kind?: string;\n /**\n * The ENDPOINTS this write asserts the target row already carries, stated\n * only by a write that actually checked them.\n *\n * Kind alone does not pin a row's identity. An edge's endpoints are immutable\n * for a given row, but the ID is not: a concurrent `hardDelete` + recreate\n * under the SAME kind with DIFFERENT endpoints satisfies a kind-only\n * predicate, so an upsert that resolved this id BY endpoints could otherwise\n * write to an edge pointing somewhere it never looked. Carrying them here\n * closes that, on the same terms and in the same `WHERE` as `kind`.\n *\n * All four move together or not at all — they are one assertion. A plain\n * `update` on a kind-scoped collection states none of them, because it\n * resolved the edge by id and kind and made no claim about where it points;\n * predicating on endpoints it never checked would refuse legitimate writes.\n * The same MUST-apply contract as `kind` binds a backend that receives them.\n */\n fromKind?: string;\n fromId?: string;\n toKind?: string;\n toId?: string;\n props: Readonly<Record<string, unknown>>;\n /**\n * Applied when resurrecting a tombstone, where it asserts a COMPLETE window:\n * `validTo` is rewritten alongside it (omitted `validTo` reopens the window).\n * Omitting `validFrom` on a resurrection leaves the stored window in place.\n */\n validFrom?: string | null;\n /**\n * The effective `valid_from` this write asserts the target row already\n * carries. Same three states, same NULL-safety, and the same MUST-apply\n * contract as {@link UpdateNodeParams.expectedValidFrom}; stated by the same\n * kind of caller, one whose verdict READ that bound.\n *\n * Separate from the immutable-identity components above because it is not\n * immutable: a resurrection rewrites `valid_from`. It is asserted for the\n * opposite reason — precisely because it can change, a decision computed from\n * it must be fenced against it having changed.\n */\n expectedValidFrom?: string | null;\n /**\n * The `valid_to` state a constraint decision read. NULL-safe and MUST apply\n * when present, like `expectedValidFrom`; used to fence ended-to-open edges.\n */\n expectedValidTo?: string | null;\n clearDeleted?: boolean;\n}> &\n BackendValidityEndMutation;\n\n/**\n * Parameters for deleting an edge (soft delete).\n */\nexport type DeleteEdgeParams = Readonly<{\n graphId: string;\n id: string;\n /** See {@link UpdateEdgeParams.kind}. */\n kind?: string;\n}>;\n\n/**\n * Parameters for hard deleting a node (permanent removal).\n */\nexport type HardDeleteNodeParams = Readonly<{\n graphId: string;\n kind: string;\n id: string;\n}>;\n\n/**\n * Parameters for hard deleting an edge (permanent removal).\n */\nexport type HardDeleteEdgeParams = Readonly<{\n graphId: string;\n id: string;\n /** See {@link UpdateEdgeParams.kind}. */\n kind?: string;\n}>;\n\n/** Parameters for a batched edge delete (soft or hard). */\nexport type DeleteEdgesBatchParams = Readonly<{\n graphId: string;\n ids: readonly string[];\n}>;\n\n// ============================================================\n// Embedding Parameters\n// ============================================================\n\n/**\n * Parameters for inserting or updating an embedding.\n *\n * `dimensions`, `metric`, and `indexType` together resolve the slot's\n * typed per-`(nodeKind, fieldPath)` storage (the strategy needs the fixed\n * dimension for the column type, and the metric/index type to address the\n * right ANN structure). The store populates them from the schema's\n * `embedding()` declaration via `getEmbeddingDimensions()` /\n * `getEmbeddingIndex()`; the backend constructs a `VectorSlot` from them\n * and stays graph-agnostic.\n */\nexport type UpsertEmbeddingParams = Readonly<{\n graphId: string;\n nodeKind: string;\n nodeId: string;\n fieldPath: string;\n embedding: readonly number[];\n dimensions: number;\n metric: VectorMetric;\n indexType: VectorIndexType;\n}>;\n\n/**\n * One row of a batched embedding upsert.\n */\nexport type UpsertEmbeddingBatchRow = Readonly<{\n nodeId: string;\n embedding: readonly number[];\n}>;\n\n/**\n * Parameters for a batched embedding upsert. Homogeneous per vector slot:\n * one graph, one node kind, one field, many nodes. Duplicate `nodeId`\n * values within a batch are deduplicated last-write-wins by the backend\n * (a multi-row upsert cannot affect one row twice).\n */\nexport type UpsertEmbeddingBatchParams = Readonly<{\n graphId: string;\n nodeKind: string;\n fieldPath: string;\n dimensions: number;\n metric: VectorMetric;\n indexType: VectorIndexType;\n rows: readonly UpsertEmbeddingBatchRow[];\n}>;\n\n/**\n * Parameters for deleting an embedding.\n *\n * `dimensions` / `metric` / `indexType` mirror {@link UpsertEmbeddingParams}\n * so the backend can resolve the slot's typed per-`(nodeKind, fieldPath)`\n * storage and idempotently ensure it exists before the DELETE. That\n * matters because a delete can run before any embedding was ever written\n * (e.g. a node hard-deleted having never carried one), and on Postgres a\n * DELETE against a missing relation inside a transaction aborts the whole\n * transaction. The store populates them from the schema's `embedding()`\n * declaration, exactly as for upserts.\n */\nexport type DeleteEmbeddingParams = Readonly<{\n graphId: string;\n nodeKind: string;\n nodeId: string;\n fieldPath: string;\n dimensions: number;\n metric: VectorMetric;\n indexType: VectorIndexType;\n}>;\n\n/**\n * Parameters for vector similarity search.\n */\nexport type VectorSearchParams = Readonly<{\n graphId: string;\n nodeKind: string;\n fieldPath: string;\n queryEmbedding: readonly number[];\n metric: VectorMetric;\n /**\n * Fixed vector dimension `N` for the searched `(nodeKind, fieldPath)`\n * slot. The store populates it from the schema's `embedding()`\n * declaration via `getEmbeddingDimensions()`; the backend uses it to\n * construct the `VectorSlot` whose typed storage the strategy scans.\n */\n dimensions: number;\n /**\n * Index type materialized for this slot. `\"none\"` means brute-force\n * only; otherwise the strategy may route through its ANN structure.\n * The store populates it from `getEmbeddingIndex()`.\n */\n indexType: VectorIndexType;\n limit: number;\n minScore?: number;\n /**\n * Subquery of eligible node ids (single column, exposed as `node_id`).\n * When present, the search statement computes top-k over this candidate\n * set — the store passes its compiled current-read query here so\n * `where` predicates push down into the search SQL. Exact on engines\n * whose search form takes the filter inside retrieval (pgvector,\n * sqlite-vec, tsvector, FTS5); libSQL's DiskANN cannot pre-filter, so\n * it post-filters an over-fetched ANN set and recall against the\n * candidate set is bounded by that headroom. When absent, the backend\n * restricts to CURRENT nodes of the kind — non-tombstoned AND inside\n * their validity window — matching a `current` read.\n */\n candidates?: SqlFragment;\n /**\n * Rows to skip AFTER ranking (pagination). The engine fetches\n * `limit + offset` ranked candidates and discards the first `offset`.\n */\n offset?: number;\n /**\n * HNSW search frontier for this query (pgvector `hnsw.ef_search`).\n * Sizes the dynamic candidate list the index scan maintains: higher\n * trades latency for recall. The floor for the over-fetch to fill its\n * candidate set is `efSearch >= limit`; ~2–4× is the high-recall\n * target. Applied transaction-locally (`SET LOCAL`) on the Postgres\n * HNSW path only.\n *\n * APPLIED OR REFUSED, never ignored. Read\n * `capabilities.vector.searchFrontierTuning` to know which you get:\n *\n * - `tunable: false` (sqlite-vec, libSQL DiskANN — no per-search frontier\n * knob exists): `UnsupportedBackendCapabilityError`.\n * - a slot whose index type is not the tunable one (pgvector IVFFlat or\n * brute-force): `ConfigurationError`.\n * - `requiresTransactionScope` on a backend reporting\n * `transactions: false` (for example `drizzle-orm/neon-http`):\n * `UnsupportedBackendCapabilityError`, because `SET LOCAL` has no frame\n * to be local to.\n */\n efSearch?: number;\n}>;\n\n/**\n * Result from a vector similarity search.\n */\nexport type VectorSearchResult = Readonly<{\n nodeId: string;\n /**\n * Cosine metric returns similarity score (higher is better).\n * L2 and inner_product return raw distance (lower is better).\n */\n score: number;\n}>;\n\n/**\n * Parameters for a single-statement hybrid (vector + fulltext, RRF-fused)\n * search. The store facade resolves every default before calling — the\n * per-source candidate depths (`k`), the fusion constants, and the shared\n * `candidates` subquery — so the backend composes `SqlFragment` values without policy.\n */\nexport type HybridSearchParams = Readonly<{\n graphId: string;\n nodeKind: string;\n vector: Readonly<{\n fieldPath: string;\n queryEmbedding: readonly number[];\n metric: VectorMetric;\n dimensions: number;\n indexType: VectorIndexType;\n /** Candidate depth of the vector source (rows entering fusion). */\n k: number;\n minScore?: number;\n efSearch?: number;\n }>;\n fulltext: Readonly<{\n query: string;\n mode?: FulltextQueryMode;\n language?: string;\n /** Candidate depth of the fulltext source (rows entering fusion). */\n k: number;\n minScore?: number;\n includeSnippets?: boolean;\n }>;\n fusion: Readonly<{\n /** RRF rank constant (`1 / (k + rank)`). */\n k: number;\n vectorWeight: number;\n fulltextWeight: number;\n }>;\n /** Fused rows to return after `offset`. */\n limit: number;\n /** Fused rows to skip (rank-relative pagination). */\n offset?: number;\n /** See {@link VectorSearchParams.candidates}; applies to both sources. */\n candidates?: SqlFragment;\n}>;\n\n/**\n * One fused hybrid hit, hydrated in the same statement: the node row\n * rides along so the caller needs no follow-up id fetch.\n */\nexport type HybridSearchRow = Readonly<{\n node: NodeRow;\n fusedScore: number;\n vectorRank?: number;\n vectorScore?: number;\n fulltextRank?: number;\n fulltextScore?: number;\n snippet?: string;\n}>;\n\n/**\n * Parameters for creating a vector index.\n */\nexport type CreateVectorIndexParams = Readonly<{\n graphId: string;\n nodeKind: string;\n fieldPath: string;\n dimensions: number;\n metric: VectorMetric;\n indexType: VectorIndexType;\n /** Index-specific parameters */\n indexParams?: Readonly<{\n /** HNSW: max connections per layer */\n m?: number;\n /** HNSW: construction search depth */\n efConstruction?: number;\n /** IVFFlat: number of lists */\n lists?: number;\n }>;\n /**\n * Build the index without taking an `AccessExclusiveLock` on live\n * tables (Postgres `CREATE INDEX CONCURRENTLY`). Mirrors the\n * `concurrent` flag the relational DDL path uses inside\n * `materializeIndexes()`. Cannot be set inside a transaction —\n * callers (`materializeIndexes()`) run at top level. Backends that\n * don't have a CONCURRENTLY equivalent (SQLite) ignore this flag.\n */\n concurrent?: boolean;\n}>;\n\n/**\n * Parameters for dropping a vector index.\n */\nexport type DropVectorIndexParams = Readonly<{\n graphId: string;\n nodeKind: string;\n fieldPath: string;\n}>;\n\n// ============================================================\n// Fulltext Parameters\n// ============================================================\n\n/**\n * Parameters for inserting or updating a fulltext entry.\n *\n * One row per node. Callers concatenate the searchable fields into\n * `content` so a single MATCH query can find terms spread across fields.\n *\n * Note: `createFulltextIndex` / `dropFulltextIndex` were removed as\n * dead code in #PR_E. The fulltext table's canonical index (Postgres\n * GIN on `tsv`, SQLite FTS5 virtual table) is created with the table\n * itself by `bootstrapTables` per the active `FulltextStrategy`;\n * per-kind fulltext indexes are an \"advanced strategy\" surface that\n * doesn't fit the relational-style declaration model and is reserved\n * for future work.\n */\nexport type UpsertFulltextParams = Readonly<{\n graphId: string;\n nodeKind: string;\n nodeId: string;\n content: string;\n language: string;\n}>;\n\n/**\n * Parameters for deleting a single fulltext entry.\n */\nexport type DeleteFulltextParams = Readonly<{\n graphId: string;\n nodeKind: string;\n nodeId: string;\n}>;\n\n/**\n * A single row in a fulltext batch upsert.\n */\nexport type FulltextBatchRow = Readonly<{\n nodeId: string;\n content: string;\n language: string;\n}>;\n\n/**\n * Parameters for a batched fulltext upsert.\n *\n * Homogeneous: one graph, one node kind, many nodes. Duplicate `nodeId`\n * values within a single batch are deduplicated last-write-wins by the\n * builders before SQL generation — Postgres `ON CONFLICT` errors on\n * repeated conflict keys in one statement, and SQLite `DELETE + INSERT`\n * would create duplicate virtual-table rows otherwise.\n */\nexport type UpsertFulltextBatchParams = Readonly<{\n graphId: string;\n nodeKind: string;\n rows: readonly FulltextBatchRow[];\n}>;\n\n/**\n * Parameters for a batched fulltext delete.\n */\nexport type DeleteFulltextBatchParams = Readonly<{\n graphId: string;\n nodeKind: string;\n nodeIds: readonly string[];\n}>;\n\n/**\n * Parameters for fulltext search.\n */\nexport type FulltextSearchParams = Readonly<{\n graphId: string;\n nodeKind: string;\n /** The user-supplied query string. */\n query: string;\n /** How to parse `query`. Default: \"websearch\". */\n mode?: FulltextQueryMode;\n /**\n * Language for query parsing. The store facade resolves the kind's\n * declared language and passes it here, so the tsquery is a plan-time\n * constant (GIN-servable); when absent the backend falls back to the\n * per-row language column.\n * Postgres: passed as the regconfig to `to_tsquery` / `websearch_to_tsquery`.\n * SQLite: informational only (FTS5 tokenizer is fixed at table-create time).\n */\n language?: string;\n /** Max rows to return. */\n limit: number;\n /** Minimum rank to include (backend-dependent units). */\n minScore?: number;\n /** Whether to return a highlighted snippet per match. */\n includeSnippets?: boolean;\n /**\n * Subquery of eligible node ids (single column). When present, the\n * search statement computes top-k over this candidate set — the store\n * passes its compiled current-read query here so `where` predicates,\n * subclass expansion, and valid-time currency all push down into the\n * search SQL. Exact on engines whose search form takes the filter\n * inside retrieval (pgvector, sqlite-vec, tsvector, FTS5); libSQL's\n * DiskANN cannot pre-filter, so it post-filters an over-fetched ANN\n * set and recall against the candidate set is bounded by that\n * headroom. When absent, the backend restricts to CURRENT nodes of\n * the kind — non-tombstoned AND inside their validity window —\n * matching a `current` read.\n */\n candidates?: SqlFragment;\n /**\n * Rows to skip AFTER ranking (pagination). The engine fetches\n * `limit + offset` ranked candidates and discards the first `offset`.\n */\n offset?: number;\n}>;\n\n/**\n * Result from a fulltext search.\n *\n * Score semantics differ by backend; prefer `rank` (1-based) when fusing\n * with another source via RRF.\n */\nexport type FulltextSearchResult = Readonly<{\n nodeId: string;\n /**\n * Backend-native relevance score.\n * Postgres: `ts_rank_cd` (higher is better).\n * SQLite FTS5: negated `bm25()` (higher is better; FTS5 returns lower-is-better).\n */\n score: number;\n /** 1-based rank within the result set, suitable for RRF. */\n rank: number;\n /** Highlighted snippet of the content (if `includeSnippets` was set). */\n snippet?: string;\n}>;\n\n/**\n * Parameters for creating a fulltext index.\n *\n * The canonical index (Postgres GIN on `tsv`, SQLite FTS5 virtual table)\n * is created when the fulltext table itself is created. This is reserved\n * for advanced per-kind specializations.\n */\n\n// ============================================================\n// Index Materialization Status\n// ============================================================\n\n/**\n * Per-deployment record for a single declared index.\n *\n * Identified by `indexName` because SQL index names are physical,\n * database-global identifiers — `graphId` is provenance, not identity.\n * `materializedAt` is null until the first successful CREATE INDEX\n * completes; `lastAttemptedAt` is always set, even on failure.\n */\n/**\n * Parameters for atomically claiming an index build (see\n * `claimIndexMaterialization`). The declaration fields ride along so a\n * fresh claim row is honest while the build is in flight.\n */\nexport type ClaimIndexMaterializationParams = Readonly<{\n indexName: string;\n graphId: string;\n entity: IndexEntity;\n kind: string;\n signature: string;\n schemaVersion: number;\n /** Opaque claim identity; release is token-guarded. */\n token: string;\n /** A claim older than this is stale and may be taken over. */\n leaseMs: number;\n}>;\n\n/** Parameters for releasing a build claim (token-guarded). */\nexport type ReleaseIndexMaterializationClaimParams = Readonly<{\n indexName: string;\n token: string;\n}>;\n\nexport type IndexMaterializationRow = Readonly<{\n indexName: string;\n graphId: string;\n entity: IndexEntity;\n kind: string;\n signature: string;\n schemaVersion: number;\n materializedAt: string | undefined;\n lastAttemptedAt: string;\n lastError: string | undefined;\n}>;\n\n/**\n * Parameters for upserting a materialization attempt.\n *\n * On success: pass `materializedAt` (ISO timestamp) and undefined `error`.\n * On failure: pass undefined `materializedAt` (preserve any existing\n * timestamp from a prior success) and the error message.\n */\nexport type RecordIndexMaterializationParams = Readonly<{\n indexName: string;\n graphId: string;\n entity: IndexEntity;\n kind: string;\n signature: string;\n schemaVersion: number;\n attemptedAt: string;\n /** ISO timestamp on success; undefined on failure (preserves existing). */\n materializedAt: string | undefined;\n /** Error message on failure; undefined on success (clears existing). */\n error: string | undefined;\n}>;\n\n// ============================================================\n// Contribution Materializations (#135 — durable strategy-owned\n// storage marker, sibling of index materializations)\n// ============================================================\n\n/**\n * Durable identity of a strategy-owned table contribution (#129),\n * scoped to a graph. This is the primary key of\n * `typegraph_contribution_materializations`.\n *\n * `logicalName` is the stable slot (\"fulltext\"); `owner` is the\n * producing strategy (\"tsvector\" / \"fts5\"); `tableName` is the resolved\n * physical name (custom per-deployment names must be distinguishable).\n * `graphId` is part of identity here — unlike the index status table\n * where the physical index name is database-global, two graphs can each\n * own a logically-identical fulltext contribution.\n */\nexport type ContributionMaterializationIdentity = Readonly<{\n graphId: string;\n logicalName: string;\n owner: string;\n tableName: string;\n}>;\n\n/**\n * Per-deployment record that a strategy-owned contribution has been\n * durably materialized against this database.\n *\n * `signature` is intentionally NOT part of the identity: a row with the\n * same identity but a different signature means \"materialized artifact\n * is stale/drifted\" — a loud error on the hot path, never a silent\n * re-materialize. `materializedAt` is undefined until the first\n * successful materialization; `lastAttemptedAt` is always set.\n */\nexport type ContributionMaterializationRow = Readonly<{\n graphId: string;\n logicalName: string;\n owner: string;\n tableName: string;\n signature: string;\n materializedAt: string | undefined;\n lastAttemptedAt: string;\n lastError: string | undefined;\n}>;\n\n/**\n * Parameters for upserting a contribution-materialization attempt.\n * Same success/failure contract as\n * {@link RecordIndexMaterializationParams}: on failure pass undefined\n * `materializedAt` so a prior successful timestamp is preserved via\n * COALESCE, and the error message.\n */\nexport type RecordContributionMaterializationParams = Readonly<{\n graphId: string;\n logicalName: string;\n owner: string;\n tableName: string;\n signature: string;\n attemptedAt: string;\n /** ISO timestamp on success; undefined on failure (preserves existing). */\n materializedAt: string | undefined;\n /** Error message on failure; undefined on success (clears existing). */\n error: string | undefined;\n}>;\n\n/**\n * Why a contribution is not usable. Most members are a disagreement\n * between the durable marker and the physical catalog; one\n * (`failed-materialization`) is a state the two agree on and that is\n * broken anyway. A contribution that is genuinely healthy — or that was\n * simply never attempted — is not reported at all.\n *\n * - `orphaned-marker` — the marker records a successful materialization\n * but the physical table is gone (a partial restore, an out-of-band\n * `DROP`, a schema-scoped restore that missed the contribution\n * tables). The state {@link GraphBackend.verifyContributions} exists\n * to surface: nothing on the open path probes the catalog, so this\n * database opens clean and fails at the first dependent read or write.\n * - `missing-marker` — the physical table exists but no marker attests\n * it as initialized (no row, no recorded success, or a recorded\n * failure). Reads and writes are refused with\n * `StoreNotInitializedError` even though storage is present. These\n * three causes share one state because they share one repair; check\n * {@link ContributionDiagnostic.lastError} to tell them apart.\n * - `failed-materialization` — the marker records a *failed* attempt\n * and no table was produced. Marker and catalog agree here, so this\n * is not a disagreement — but it is where a contribution lands when\n * provisioning genuinely broke (the `fts5` module is absent, the role\n * lacks `CREATE`, the extension was never loaded), and calling it\n * healthy on the grounds that both sides agree would be the one\n * answer this method must never give. Distinguished from a\n * contribution that was never attempted, which has no marker row and\n * is correctly silent. {@link ContributionDiagnostic.lastError}\n * carries the reason it failed.\n * - `stale` — the table exists and the marker records a prior success\n * at a different signature (a strategy swap, or a declared embedding\n * dimension that has moved ahead of the provisioned table).\n *\n * **Do not re-frame this union as \"how the marker disagrees with the\n * catalog.\"** It was described that way once, and the description was\n * load-bearing in the wrong direction: under it, a marker recording a\n * failed attempt with no table looks like agreement, therefore not a\n * disagreement, therefore correctly silent — and a contribution that\n * had genuinely broken was reported as healthy. The gap read as\n * principled rather than as an oversight precisely because the framing\n * endorsed it. \"Why the contribution is unusable\" is the question that\n * makes `failed-materialization` obviously belong, and any future\n * tidy-up that narrows the framing back will re-open the same hole.\n */\nexport type ContributionDiagnosticState =\n \"orphaned-marker\" | \"missing-marker\" | \"failed-materialization\" | \"stale\";\n\n/**\n * One contribution that is not usable, reported by\n * {@link GraphBackend.verifyContributions}.\n *\n * The identity fields come from the active contribution declaration and\n * match the durable-marker identity contract, so a caller can route an entry\n * to its repair without reconstructing any internal naming contract. A\n * `missing-marker` entry may have no marker row at all.\n *\n * **Route on {@link ContributionDiagnosticState}, not on whether the\n * entry is a vector slot.** The repair differs per state and the wrong\n * choice destroys data: `store.reembedVectorField` drops and recreates\n * storage, so applying it to a `missing-marker` — where the table is\n * intact and only the bookkeeping is wrong — discards every embedding\n * to fix a marker, and without an `embed` callback it leaves the field\n * empty. Prefer `store.repairContributions()` for `missing-marker` and\n * `failed-materialization`; it resolves the current strategy declarations\n * internally and keeps the normal signature-drift guard enabled. It reports\n * `stale` and `orphaned-marker` as `requires-rebuild`. See the per-state\n * repair table in the troubleshooting guide.\n */\nexport type ContributionDiagnostic = Readonly<{\n /** Producing strategy, e.g. `\"fts5\"` / `\"tsvector\"` / `\"pgvector\"`. */\n owner: string;\n /** Stable logical slot, e.g. `\"fulltext\"`. Never the SQL name. */\n logicalName: string;\n /** Resolved physical table name that was probed. */\n physicalName: string;\n /** Node kind — vector slots only. */\n kind?: string;\n /** Embedding field path — vector slots only. */\n fieldPath?: string;\n state: ContributionDiagnosticState;\n /**\n * The error the marker recorded against its last attempt, when it\n * recorded one. Absent otherwise.\n *\n * This is the part of the picture the catalog cannot supply. `state`\n * says what to do — the states are chosen so that each maps to one\n * repair — while this says *why it broke*, which is a different\n * question and often a different investigation. A failed attempt with no\n * table surfaces as `failed-materialization`; when the table exists but\n * the marker does not attest a successful materialization, it surfaces as\n * `missing-marker`. This field distinguishes only failures whose marker\n * recorded a reason; a missing row has no `lastError`. Present on any state\n * whose marker row carries an error, not just those two states.\n */\n lastError?: string;\n}>;\n\n/**\n * Outcome of one contribution considered by\n * {@link GraphBackend.repairContributions}.\n *\n * Repair targets are always resolved from the backend's current strategy\n * declarations. The diagnostic is returned for operator context only; callers\n * never pass diagnostics or physical DDL back into the repair API.\n */\nexport type ContributionRepairEntry =\n | Readonly<{\n diagnostic: ContributionDiagnostic;\n status: \"repaired\";\n }>\n | Readonly<{\n diagnostic: ContributionDiagnostic;\n status: \"requires-rebuild\";\n }>\n | Readonly<{\n diagnostic: ContributionDiagnostic;\n status: \"failed\";\n error: string;\n }>;\n\n/** Result of a contribution repair pass followed by a fresh verification. */\nexport type ContributionRepairResult = Readonly<{\n results: readonly ContributionRepairEntry[];\n remaining: readonly ContributionDiagnostic[];\n}>;\n\n// ============================================================\n// Contribution readiness probe (#377)\n// ============================================================\n\n/**\n * The search projection a probe entry summarizes. Deliberately the\n * logical *class* rather than one physical table: a caller asking \"is\n * search ready?\" is deciding whether to issue a fulltext or a vector\n * query, and a per-`(kind, field)` breakdown of pgvector tables answers\n * a question they did not ask. The physical detail an operator needs to\n * act is one `verifyContributions()` call away, and\n * {@link ContributionProbeEntry.detail} names the affected slots.\n */\nexport type ContributionProbeContribution = \"fulltext\" | \"vector\";\n\n/**\n * Readiness of one search projection.\n *\n * - `ready` — every contribution backing this projection is attested by\n * its durable marker and present in the catalog. Not a promise about\n * future coherence: a write that lands after the probe returns is\n * outside what any assessment can cover.\n * - `degraded` — at least one contribution is unusable. Dependent operations\n * may be refused with a typed contribution error, fail at the engine boundary\n * when compiled SQL directly references missing storage, or observe\n * incomplete storage.\n * {@link ContributionProbeEntry.detail} says which and why.\n * - `building` — **reserved.** No shipped path publishes it. Recording\n * an in-flight marker would need a fifth\n * {@link ContributionDiagnosticState} and would widen the hot-path\n * gate's verdict set; the destructive rebuild instead runs inside one\n * transaction, so a concurrent probe observes the state before or\n * after it and never a partial one. Reserved rather than removed so a\n * future streaming/async materializer can populate it without a\n * breaking change — treat it as \"not `ready`\" today.\n */\nexport type ContributionProbeState = \"ready\" | \"degraded\" | \"building\";\n\n/**\n * One search projection's readiness, from\n * {@link GraphBackend.probeContributions}.\n */\nexport type ContributionProbeEntry = Readonly<{\n contribution: ContributionProbeContribution;\n state: ContributionProbeState;\n /**\n * Operator-facing summary of why the projection is not `ready` —\n * e.g. `\"fulltext table \\\"typegraph_node_fulltext\\\" is missing\n * (orphaned-marker)\"`. Absent on a `ready` entry.\n *\n * Human-readable and not a stable format: route on `state`, and call\n * `store.verifyContributions()` for the structured per-table\n * diagnostics this string is derived from.\n */\n detail?: string;\n}>;\n\n/**\n * Result of {@link GraphBackend.probeContributions}.\n *\n * A projection with no declared contributions is omitted rather than\n * reported `ready`, so an empty `entries` array means \"nothing to\n * assess\" — a graph with no `searchable()` or `embedding()` fields, or a\n * backend with no contribution support — never \"assessed and healthy\".\n */\nexport type ContributionProbeResult = Readonly<{\n /**\n * The durable graph revision the assessment was taken at, so a caller\n * can place this probe in the graph's committed history.\n *\n * Graph-global, like the clock it reads: it advances on every committed\n * capture from any writer, so an advance between two probes means\n * \"something committed in between\", never \"the write this caller just\n * made landed\". Confirm a specific write by observing the write itself.\n *\n * Absent unless the Store is revision-tracked (`revisionTracking:\n * true` or `history: true`) — the same condition under which\n * `store.revisionNow()` returns a value. This is the one honest shape:\n * a store with no durable revision clock has no revision to stamp, and\n * substituting a wall-clock timestamp or the schema version would be a\n * materially weaker guarantee wearing the name of a stronger one. The\n * schema version in particular does not advance on data writes, so it\n * could not order a probe against a caller's write at all.\n */\n graphRevision?: string;\n entries: readonly ContributionProbeEntry[];\n}>;\n\n// ============================================================\n// Destructive contribution rebuild (#337)\n// ============================================================\n\n/**\n * Which search projection {@link GraphBackend.rebuildContribution}\n * targets. Scoped explicitly at the call site rather than inferred from\n * a diagnostic: the operation destroys storage, so which storage is the\n * caller's decision to state, not TypeGraph's to guess.\n */\nexport type ContributionRebuildScope = ContributionProbeContribution;\n\n/**\n * What a rebuild's `repopulate` callback reconstructed. Reported back so\n * the rebuild result can distinguish \"recreated empty storage\" from\n * \"recreated and refilled\", which is the difference between a finished\n * repair and half of one.\n */\nexport type ContributionRepopulationStats = Readonly<{\n /** Nodes scanned. */\n processed: number;\n /** Content rows written. */\n repopulated: number;\n /** Nodes whose stored `props` could not be read as an object. */\n skipped: number;\n}>;\n\n/** Outcome of one destructive contribution rebuild. */\nexport type ContributionRebuildResult = Readonly<{\n /** Physical tables dropped and recreated, in the order rebuilt. */\n rebuilt: readonly string[];\n /** Nodes scanned while reconstructing content from stored rows. */\n processed: number;\n /** Content rows written into the recreated storage. */\n repopulated: number;\n /**\n * Nodes whose stored `props` could not be read as an object, so no\n * content row could be reconstructed for them. Non-zero means the\n * rebuilt index is missing those nodes; the IDs come back from\n * `store.search.rebuildFulltext()`, which reports them individually.\n */\n skipped: number;\n}>;\n\n// ============================================================\n// Kind Removals (data-cleanup status)\n// ============================================================\n\n/**\n * One row of the per-deployment `typegraph_kind_removals` table:\n * a graph-extension kind that has been removed from the schema and whose\n * data may or may not have been cleaned up yet.\n */\nexport type KindRemovalRow = Readonly<{\n graphId: string;\n kindName: string;\n entity: KindEntity;\n schemaVersion: number;\n /** ISO timestamp when the data-cleanup pass succeeded; undefined while pending. */\n removedAt: string | undefined;\n lastAttemptedAt: string;\n lastError: string | undefined;\n}>;\n\n/**\n * Upsert payload for a kind-removal status row. On removal-commit:\n * pass `removedAt: undefined` (the pending state). On successful\n * data-cleanup: pass `removedAt` set and `error: undefined`. On\n * cleanup failure: pass `removedAt: undefined` (preserves any prior\n * timestamp from a partial success on a different replica) and the\n * error message.\n */\nexport type RecordKindRemovalParams = Readonly<{\n graphId: string;\n kindName: string;\n entity: KindEntity;\n schemaVersion: number;\n attemptedAt: string;\n /** ISO timestamp on success; undefined while pending or on failure. */\n removedAt: string | undefined;\n /** Error message on failure; undefined on success or while pending. */\n error: string | undefined;\n}>;\n\n// ============================================================\n// Query Types\n// ============================================================\n\n/** @internal Capability token for connection-local temporary writes. */\nexport const INTERNAL_TEMPORARY_WRITES: unique symbol = typeGraphGlobalSymbol(\n \"internal-temporary-writes-v1\",\n);\n\n/**\n * Transaction options.\n */\nexport type TransactionOptions = Readonly<{\n /** Transaction isolation level (if supported) */\n isolationLevel?:\n \"read_uncommitted\" | \"read_committed\" | \"repeatable_read\" | \"serializable\";\n /**\n * Transaction access mode (if supported). `read_only` is intended for\n * multi-statement reads that need one snapshot and must not perform writes.\n */\n accessMode?: \"read_only\" | \"read_write\";\n}>;\n\n/** @internal Transaction options available only to TypeGraph-owned execution. */\nexport type InternalTransactionOptions = TransactionOptions &\n Readonly<{\n /**\n * Permit writes only to connection-local temporary state. The iterative\n * operation primitive uses this when an engine rejects temporary DDL in a\n * read-only transaction.\n */\n temporaryWrites?: typeof INTERNAL_TEMPORARY_WRITES;\n }>;\n\n// ============================================================\n// Backend Interface\n// ============================================================\n\n/**\n * The physical names of the four Operational Identity relations: the current\n * assertion ledger, its recorded-time twin, and the two DERIVED relations\n * (closure, separation) rebuilt from the ledger.\n *\n * Named once so the two ports that speak about them —\n * {@link GraphBackend.ensureIdentityTables} and\n * {@link GraphBackend.identityTableDdl} — cannot drift apart.\n */\nexport type IdentityTableNames = Readonly<{\n identityAssertions: string;\n recordedIdentityAssertions: string;\n identityClosure: string;\n identitySeparation: string;\n}>;\n\n/**\n * The GraphBackend interface abstracts database operations.\n *\n * Implementations should provide:\n * - SQLite backend via better-sqlite3 or libsql\n * - PostgreSQL backend via pg or postgres\n *\n * Every function is receiver-free (`this: void`). Implementations must close\n * over their state instead of reading `this`, which makes saved optional\n * capabilities and other detached port calls safe by construction.\n */\nexport type GraphBackend = Readonly<{\n /** The SQL dialect */\n dialect: SqlDialect;\n /** Backend capabilities */\n capabilities: BackendCapabilities;\n /** Table names used by this backend (for query schema auto-derivation) */\n tableNames?: SqlTableNames | undefined;\n /**\n * Optional fulltext strategy override. When present, both the compiler\n * (for `$fulltext.matches()` in query builder) and backend-direct\n * search paths use this instead of the dialect's default strategy —\n * allowing a Postgres backend to ship pg_trgm, ParadeDB, pgroonga etc.\n * When absent, the dialect's default strategy is used.\n */\n fulltextStrategy?: FulltextStrategy | undefined;\n /**\n * Optional vector strategy this backend is wired with. The query\n * compiler reads it (via `compileQuery` options) to emit per-`(kind,\n * field)` relevance scans for `field.similarTo(...)` predicates, and\n * the index-materialization / removal paths read its deterministic\n * `tableName(...)` to address the right physical per-field storage.\n * `undefined` when the backend has no vector support (e.g. a generic\n * SQLite backend without sqlite-vec).\n */\n vectorStrategy?: VectorStrategy | undefined;\n\n // === Node Operations ===\n insertNode: (this: void, params: InsertNodeParams) => Promise<NodeRow>;\n insertNodeNoReturn?: (this: void, params: InsertNodeParams) => Promise<void>;\n insertNodesBatch?: (\n this: void,\n params: readonly InsertNodeParams[],\n ) => Promise<void>;\n insertNodesBatchReturning?: (\n this: void,\n params: readonly InsertNodeParams[],\n ) => Promise<readonly NodeRow[]>;\n updateNode: (this: void, params: UpdateNodeParams) => Promise<NodeRow>;\n updateNodeSet?: (\n this: void,\n params: UpdateNodeSetParams,\n ) => Promise<UpdateNodeSetResult>;\n deleteNode: (this: void, params: DeleteNodeParams) => Promise<void>;\n hardDeleteNode: (this: void, params: HardDeleteNodeParams) => Promise<void>;\n getNode: (\n this: void,\n graphId: string,\n kind: string,\n id: string,\n ) => Promise<NodeRow | undefined>;\n getNodes?: (\n this: void,\n graphId: string,\n kind: string,\n ids: readonly string[],\n ) => Promise<readonly NodeRow[]>;\n\n // === Edge Operations ===\n insertEdge: (this: void, params: InsertEdgeParams) => Promise<EdgeRow>;\n insertEdgeNoReturn?: (this: void, params: InsertEdgeParams) => Promise<void>;\n insertEdgesBatch?: (\n this: void,\n params: readonly InsertEdgeParams[],\n ) => Promise<void>;\n insertEdgesBatchReturning?: (\n this: void,\n params: readonly InsertEdgeParams[],\n ) => Promise<readonly EdgeRow[]>;\n updateEdge: (this: void, params: UpdateEdgeParams) => Promise<EdgeRow>;\n deleteEdge: (this: void, params: DeleteEdgeParams) => Promise<void>;\n hardDeleteEdge: (this: void, params: HardDeleteEdgeParams) => Promise<void>;\n /**\n * Batched {@link deleteEdge}: one soft-delete statement per bind-budget\n * chunk instead of one per edge. Optional — cascade deletes fall back to\n * the per-edge form when unset. Same per-row semantics (idempotent on\n * already-tombstoned rows).\n */\n deleteEdgesBatch?: (\n this: void,\n params: DeleteEdgesBatchParams,\n ) => Promise<void>;\n /** Batched {@link hardDeleteEdge}; see {@link deleteEdgesBatch}. */\n hardDeleteEdgesBatch?: (\n this: void,\n params: DeleteEdgesBatchParams,\n ) => Promise<void>;\n getEdge: (\n this: void,\n graphId: string,\n id: string,\n ) => Promise<EdgeRow | undefined>;\n getEdges?: (\n this: void,\n graphId: string,\n ids: readonly string[],\n ) => Promise<readonly EdgeRow[]>;\n\n // === Edge Cardinality Operations ===\n countEdgesFrom: (this: void, params: CountEdgesFromParams) => Promise<number>;\n edgeExistsBetween: (\n this: void,\n params: EdgeExistsBetweenParams,\n ) => Promise<boolean>;\n\n // === Edge Query Operations ===\n findEdgesConnectedTo: (\n this: void,\n params: FindEdgesConnectedToParams,\n ) => Promise<readonly EdgeRow[]>;\n\n // === Collection Query Operations ===\n findNodesByKind: (\n this: void,\n params: FindNodesByKindParams,\n ) => Promise<readonly NodeRow[]>;\n countNodesByKind: (\n this: void,\n params: CountNodesByKindParams,\n ) => Promise<number>;\n findEdgesByKind: (\n this: void,\n params: FindEdgesByKindParams,\n ) => Promise<readonly EdgeRow[]>;\n /**\n * Reads the edges of a SET of endpoints in one statement per bind-budget\n * chunk — see {@link FindEdgesByEndpointSetParams}.\n *\n * **Optional, and its absence is the capability signal.** `store.edges.<kind>\n * .bulkFindFrom` / `.bulkFindTo` check for this method before issuing any\n * read and refuse with a typed `ConfigurationError` when it is missing,\n * rather than degrading to a per-endpoint loop. A caller reaching for a bulk\n * endpoint read is asking for set-oriented statements; quietly giving them N\n * singleton statements is the surprise the method exists to prevent.\n *\n * Both bundled Drizzle backends implement it. A custom backend that does not\n * simply omits it and the bulk reads refuse; the singleton `findEdgesByKind`\n * path is unaffected.\n */\n findEdgesByEndpointSet?: (\n this: void,\n params: FindEdgesByEndpointSetParams,\n ) => Promise<readonly EdgeRow[]>;\n /**\n * Reads several edge kinds from a heterogeneous set of endpoints without\n * issuing one statement per licensed `(edge kind, endpoint kind)` pair.\n *\n * Optional for custom backends. Store-level heterogeneous bulk reads refuse\n * when it is absent rather than hiding a per-kind/per-endpoint fallback.\n */\n findEdgesByHeterogeneousEndpointSet?: (\n this: void,\n params: FindEdgesByHeterogeneousEndpointSetParams,\n ) => Promise<readonly EdgeRow[]>;\n countEdgesByKind: (\n this: void,\n params: CountEdgesByKindParams,\n ) => Promise<number>;\n\n // === Unique Constraint Operations ===\n insertUnique: (this: void, params: InsertUniqueParams) => Promise<void>;\n /**\n * Batched variant of `insertUnique`: one multi-row statement per chunk\n * with the same per-entry conflict semantics (throws `UniquenessError`\n * for the first entry whose key a different live node holds). Optional —\n * callers fall back to per-entry `insertUnique` when unset.\n */\n insertUniqueBatch?: (\n this: void,\n entries: readonly InsertUniqueParams[],\n ) => Promise<void>;\n deleteUnique: (this: void, params: DeleteUniqueParams) => Promise<void>;\n /**\n * Permanently removes every uniqueness sidecar owned by the specified\n * concrete nodes. Optional capability used by set-based updates before they\n * rebuild reservations from the returned node after-images.\n */\n hardDeleteUniquesByNodeIds?: (\n this: void,\n params: HardDeleteUniquesByNodeIdsParams,\n ) => Promise<void>;\n checkUnique: (\n this: void,\n params: CheckUniqueParams,\n ) => Promise<UniqueRow | undefined>;\n checkUniqueBatch?: (\n this: void,\n params: CheckUniqueBatchParams,\n ) => Promise<readonly UniqueRow[]>;\n\n // === Schema Operations ===\n getActiveSchema: (\n this: void,\n graphId: string,\n ) => Promise<SchemaVersionRow | undefined>;\n getSchemaVersion: (\n this: void,\n graphId: string,\n version: number,\n ) => Promise<SchemaVersionRow | undefined>;\n /**\n * Atomically inserts a new schema version and activates it as a single\n * transactional unit, with optimistic compare-and-swap on the currently\n * active version.\n *\n * - If `expected.kind === \"active\"` and the actual active version\n * differs, throws `StaleVersionError` (caller should refetch and\n * retry).\n * - If a row already exists at `params.version` with the same\n * `schemaHash`, returns it idempotently — reactivating it if it was\n * left inactive by an earlier crashed commit.\n * - If a row already exists at `params.version` with a different\n * `schemaHash`, throws `SchemaContentConflictError`.\n *\n * Requires `capabilities.transactions === true`. On non-transactional\n * backends (e.g. Cloudflare D1, drizzle-orm/neon-http) this method\n * throws `ConfigurationError` rather than running with degraded\n * atomicity that would silently re-introduce the orphan-row crash\n * window the primitive exists to eliminate.\n */\n commitSchemaVersion: (\n this: void,\n params: CommitSchemaVersionParams,\n ) => Promise<SchemaVersionRow>;\n /**\n * Commit a schema version only if every requested kind is empty. The probes\n * and schema CAS run under one backend-owned write fence, preventing a\n * participating schema-managed Store write from landing between the final\n * count and commit. Raw Stores and direct backend writes are not fenced.\n */\n commitSchemaVersionIfKindsEmpty?: (\n this: void,\n params: CommitSchemaVersionParams,\n probes: readonly Readonly<{\n entity: \"node\" | \"edge\";\n kind: string;\n }>[],\n ) => Promise<\n | Readonly<{ status: \"committed\"; row: SchemaVersionRow }>\n | Readonly<{\n status: \"populated\";\n kinds: readonly Readonly<{\n entity: \"node\" | \"edge\";\n kind: string;\n count: number;\n }>[];\n }>\n >;\n /**\n * Acquire the transaction-scoped shared fence for a schema-managed graph\n * write, then verify that the active schema still matches the Store that is\n * issuing it. Official transactional backends provide this method; custom\n * backends may omit it, in which case schema-managed Store writes fail\n * closed rather than racing a schema change. This method must be called on a\n * transaction-scoped backend. PostgreSQL locks the active schema row, so a\n * concurrent change either becomes visible at read committed or raises the\n * database's native serialization failure at stronger isolation.\n *\n * On rejection the thrown `StaleVersionError` reports the active\n * version this transaction can observe after the conflict resolves, so\n * `details.actual` names the version that won rather than the absence the\n * blocked read momentarily saw.\n */\n lockSchemaVersionForWrite?: (\n this: void,\n params: Readonly<{ graphId: string; expectedVersion: number }>,\n ) => Promise<void>;\n /**\n * Internal schema-lifecycle seam for features whose data preflight must\n * commit atomically with the schema CAS. The callback runs in the same\n * write transaction after the schema write fence is acquired and before the\n * version write; it receives no schema-version write methods, so callers\n * cannot bypass the CAS.\n *\n * The preflight target is a {@link SchemaCommitPreflightBackend}: the\n * transaction backend plus the schema-write DDL primitive, because a\n * transition may have to CREATE the storage its own preflight then fills —\n * and creating it outside this transaction would publish it empty whenever\n * the commit is refused.\n *\n * @internal\n */\n commitSchemaVersionWithPreflight?: (\n this: void,\n params: CommitSchemaVersionParams,\n preflight: (target: SchemaCommitPreflightBackend) => Promise<void>,\n ) => Promise<SchemaVersionRow>;\n /**\n * Atomically flips the active schema pointer to an existing version,\n * with optimistic compare-and-swap on the currently active version.\n * Used by `rollbackSchema` and any other \"promote/demote existing\n * version\" workflow. Throws `StaleVersionError` on CAS mismatch and\n * `MigrationError` if the target version row does not exist.\n *\n * Same transactional requirements as `commitSchemaVersion`.\n */\n setActiveVersion: (\n this: void,\n params: SetActiveVersionParams,\n ) => Promise<void>;\n\n /**\n * Run an administrative callback while holding the same per-graph lock as\n * schema commits. The callback receives the transaction-scoped backend, so\n * its reads, DML, and DDL are committed atomically before another schema\n * writer can proceed.\n *\n * This is intentionally absent from {@link TransactionBackend}: nesting a\n * schema-write lock from an already-open transaction can deadlock. It is an\n * optional backend capability so custom backends can decline operations\n * that require a schema fence rather than silently running them unsafely.\n */\n schemaWriteTransaction?: <T>(\n this: void,\n graphId: string,\n fn: (\n tx: TransactionBackend &\n Readonly<{\n executeStatement: NonNullable<TransactionBackend[\"executeStatement\"]>;\n tableExists: (this: void, tableName: string) => Promise<boolean>;\n executeSchemaDdl: (this: void, ddl: string) => Promise<void>;\n deleteSchemaVectorSlotContribution: (\n this: void,\n slot: VectorSlot,\n ) => Promise<void>;\n }>,\n ) => Promise<T>,\n ) => Promise<T>;\n\n // === Embedding Operations (optional - depends on vector capabilities) ===\n upsertEmbedding?: (\n this: void,\n params: UpsertEmbeddingParams,\n ) => Promise<void>;\n /**\n * Batched variant of `upsertEmbedding` for one vector slot. Optional —\n * callers fall back to per-row `upsertEmbedding` when unset.\n */\n upsertEmbeddingBatch?: (\n this: void,\n params: UpsertEmbeddingBatchParams,\n ) => Promise<void>;\n deleteEmbedding?: (\n this: void,\n params: DeleteEmbeddingParams,\n ) => Promise<void>;\n deleteEmbeddingBatch?: (\n this: void,\n params: Omit<DeleteEmbeddingParams, \"nodeId\"> &\n Readonly<{ nodeIds: readonly string[] }>,\n ) => Promise<void>;\n /**\n * KNN search over one `(nodeKind, fieldPath)` slot. Top-k is computed\n * over CURRENT nodes only — non-tombstoned AND inside their validity\n * window, matching a `current` read — so index drift (embedding rows\n * whose node was deleted or expired outside the store pipeline) can\n * neither surface nor crowd current rows out of the top-k. Custom\n * backends implementing this member must honor the same contract. Exact on pgvector >= 0.8 (HNSW via\n * `hnsw.iterative_scan = strict_order`; IVFFlat via\n * `ivfflat.iterative_scan = relaxed_order` plus a re-sort of the\n * bounded set; probe-bounded below 0.8) and sqlite-vec;\n * libSQL's DiskANN over-fetches 4x and post-filters (recall bounded by\n * that headroom).\n */\n vectorSearch?: (\n this: void,\n params: VectorSearchParams,\n ) => Promise<readonly VectorSearchResult[]>;\n createVectorIndex?: (\n this: void,\n params: CreateVectorIndexParams,\n ) => Promise<void>;\n dropVectorIndex?: (\n this: void,\n params: DropVectorIndexParams,\n ) => Promise<void>;\n /**\n * Single-statement hybrid search: both sources, RRF fusion, liveness\n * join, and node hydration composed into ONE statement — replacing the\n * facade's two search round trips plus id-hydration fetch. Optional;\n * the store falls back to the multi-statement path when unset (custom\n * backends, engines without window functions). Same liveness contract\n * as `vectorSearch` / `fulltextSearch`.\n */\n hybridSearch?: (\n this: void,\n params: HybridSearchParams,\n ) => Promise<readonly HybridSearchRow[]>;\n\n // === Fulltext Operations (optional - depends on fulltext capabilities) ===\n upsertFulltext?: (this: void, params: UpsertFulltextParams) => Promise<void>;\n deleteFulltext?: (this: void, params: DeleteFulltextParams) => Promise<void>;\n /**\n * Batched variant of `upsertFulltext`. Optional — callers fall back to\n * per-row `upsertFulltext` when unset.\n */\n upsertFulltextBatch?: (\n this: void,\n params: UpsertFulltextBatchParams,\n ) => Promise<void>;\n /**\n * Batched variant of `deleteFulltext`. Optional — callers fall back to\n * per-row `deleteFulltext` when unset.\n */\n deleteFulltextBatch?: (\n this: void,\n params: DeleteFulltextBatchParams,\n ) => Promise<void>;\n /**\n * Ranked fulltext search over one node kind. Like `vectorSearch`, top-k\n * is computed over CURRENT nodes only — non-tombstoned AND inside their\n * validity window (exact on both engines — plain WHERE, no top-k table\n * function involved). Custom backends implementing this member must\n * honor the same contract.\n */\n fulltextSearch?: (\n this: void,\n params: FulltextSearchParams,\n ) => Promise<readonly FulltextSearchResult[]>;\n\n // === Index Materialization (used by store.materializeIndexes) ===\n /**\n * Idempotently ensure ONLY the `typegraph_index_materializations`\n * table exists — separate from `bootstrapTables` so that\n * `materializeIndexes` doesn't pull in the full base-table DDL set\n * just to access the status table.\n *\n * Why focused: `bootstrapTables` issues 20+ `CREATE TABLE / CREATE\n * INDEX IF NOT EXISTS` statements covering every base table. Two\n * concurrent calls (e.g. two replicas of the same `schema_doc` both\n * starting up and calling `materializeIndexes`) race on\n * Postgres SHARE locks and DEADLOCK. Restricting the ensure-step to\n * the single status table eliminates the cross-table race entirely\n * — concurrent `CREATE TABLE IF NOT EXISTS` for one specific table\n * is well-behaved on Postgres.\n */\n ensureIndexMaterializationsTable?: (this: void) => Promise<void>;\n\n /**\n * Install the PostgreSQL `pg_trgm` extension under a database-global\n * concurrency fence. Relational trigram index materialization calls this\n * before emitting its index DDL; non-PostgreSQL backends omit it.\n */\n ensureTrigramExtension?: (this: void) => Promise<void>;\n\n /**\n * Idempotently ensure ONLY the `typegraph_revision_origins` table exists.\n *\n * Revision-tracked stores use this per-graph, durable random origin together\n * with the recorded clock to prevent two independent stores with coincident\n * timestamps from sharing a merge base anchor. It is focused rather than\n * using `bootstrapTables` so existing deployments can adopt revision anchors\n * without replaying all base-table DDL during a merge read.\n */\n ensureRevisionOriginsTable?: (this: void) => Promise<void>;\n\n /**\n * Idempotently ensure ONLY the three Operational Identity relations exist —\n * the current-assertions table, the recorded-time assertions table, and the\n * derived closure and separation tables — with their indexes and CHECK\n * constraints (CREATE TABLE / CREATE INDEX IF NOT EXISTS).\n *\n * First enablement of identity on an existing populated deployment attaches\n * via `createStore` / `createSqliteBackend` / `createPostgresBackend`, none\n * of which run DDL, so the enablement preflight would otherwise\n * SELECT/DELETE/INSERT tables that do not exist yet. The store calls this\n * before the enablement locks and closure rebuild. Focused rather than\n * `bootstrapTables` for the same concurrency rationale as\n * {@link ensureRevisionOriginsTable}. Stores call it before opening the\n * schema-commit transaction so DDL does not re-enter its per-graph lock.\n * Returns the logical names that were absent before this call. Callers set\n * `provisionMissing` only for safe first enablement; on an already-enabled\n * graph, missing ledger tables are deliberately left absent so a failed open\n * cannot make its next retry silently accept empty replacement storage.\n *\n * @internal\n */\n ensureIdentityTables?: (\n this: void,\n tableNames: IdentityTableNames,\n options: Readonly<{ provisionMissing: boolean }>,\n ) => Promise<readonly string[]>;\n\n /**\n * The idempotent CREATE statements for exactly the identity relations —\n * the same DDL {@link ensureIdentityTables} issues, handed back as data\n * instead of executed.\n *\n * Pure and synchronous: it executes nothing and probes no catalog, so it is\n * safe to call from inside an already-open transaction, where invoking a\n * top-level backend method would re-enter the backend's serialized statement\n * queue and deadlock. That is the whole point. Provisioning a missing DERIVED\n * relation (closure, separation) on an already-enabled graph has to CREATE it\n * and FILL it in one transaction, because a created-but-empty derived\n * relation is readable and answers every question with \"nothing\" — for the\n * separation relation, \"nothing\" means \"not separated\", which is exactly the\n * answer that lets a contradictory merge commit.\n *\n * A backend that omits this cannot offer that atomicity. Callers do not fall\n * back: when a fill is owed, the upgrade is REFUSED with the typed\n * `IDENTITY_UPGRADE_REQUIRES_ATOMIC_DDL` error naming this port — creating\n * and filling back-to-back would publish the readable-empty state the\n * paragraph above forbids.\n *\n * @internal\n */\n identityTableDdl?: (\n this: void,\n tableNames: IdentityTableNames,\n ) => readonly string[];\n\n /**\n * Look up a recorded materialization for a declared index by its\n * physical SQL index name. Returns `undefined` if no row exists.\n */\n getIndexMaterialization?: (\n this: void,\n indexName: string,\n ) => Promise<IndexMaterializationRow | undefined>;\n /**\n * Bulk variant of `getIndexMaterialization`: load every recorded\n * materialization whose `indexName` (status key) is in `statusKeys`,\n * in a single round-trip. Returned rows are unordered — callers index\n * by `indexName`. Optional; consumers fall back to per-key\n * `getIndexMaterialization` when unset.\n */\n getIndexMaterializations?: (\n this: void,\n statusKeys: readonly string[],\n ) => Promise<readonly IndexMaterializationRow[]>;\n /**\n * Upsert a materialization attempt — success or failure. Failure rows\n * preserve any prior `materializedAt` so the historical successful\n * timestamp survives across error windows.\n */\n recordIndexMaterialization?: (\n this: void,\n params: RecordIndexMaterializationParams,\n ) => Promise<void>;\n /**\n * Atomically claims the build of one index across every materializer\n * (process- and pool-safe: the status row's own atomicity is the mutex).\n * Returns true when this caller now holds the claim. Backends whose\n * index builds cannot deadlock across callers (SQLite: engine-level\n * write serialization, no CONCURRENTLY) omit it; `materializeIndexes`\n * then builds without a claim, exactly as before.\n */\n claimIndexMaterialization?: (\n this: void,\n params: ClaimIndexMaterializationParams,\n ) => Promise<boolean>;\n /** Releases a claim taken by `claimIndexMaterialization` (token-guarded). */\n releaseIndexMaterializationClaim?: (\n this: void,\n params: ReleaseIndexMaterializationClaimParams,\n ) => Promise<void>;\n\n // === Contribution Materialization (#135 — durable strategy-owned\n // storage marker, sibling of the index status table) ===\n\n /**\n * Idempotently ensure ONLY the\n * `typegraph_contribution_materializations` table exists. Same\n * focused-bootstrap rationale as `ensureIndexMaterializationsTable`:\n * a single `CREATE TABLE IF NOT EXISTS` is concurrency-safe under\n * replica startup, where the full `bootstrapTables` set risks a\n * Postgres SHARE-lock deadlock.\n */\n ensureContributionMaterializationsTable?: (this: void) => Promise<void>;\n\n /**\n * Look up the durable materialization marker for one strategy-owned\n * contribution identity. Returns `undefined` when no row exists\n * (\"never initialized\").\n */\n getContributionMaterialization?: (\n this: void,\n identity: ContributionMaterializationIdentity,\n ) => Promise<ContributionMaterializationRow | undefined>;\n\n /**\n * Upsert a contribution-materialization attempt — success or failure.\n * Failure rows preserve any prior `materializedAt` via COALESCE so a\n * later failed re-attempt doesn't erase the historical success.\n */\n recordContributionMaterialization?: (\n this: void,\n params: RecordContributionMaterializationParams,\n ) => Promise<void>;\n\n /**\n * Resolve (once per backend instance, cached) and assert the durable\n * materialization markers for every `runtimeEnsure` contribution this\n * backend's strategy declares, for `graphId`. Throws\n * `StoreNotInitializedError` when a marker is missing, stale\n * (signature drift), or recorded a failed last attempt.\n *\n * This is the single read-side gate the fulltext hot-path wrappers\n * and `store.transaction()` consult. It performs ZERO DDL and ZERO\n * marker writes — initialization is the exclusive job of the async\n * boot path (`createStoreWithSchema` → `ensureRuntimeContributions`).\n */\n assertRuntimeContributionsInitialized?: (\n this: void,\n graphId: string,\n ) => Promise<void>;\n\n // === Kind Removal Status ===\n\n /**\n * Bootstraps the per-deployment `typegraph_kind_removals` table so\n * `store.evolve()` can check pending removals and the removal verbs can\n * persist and materialize removal status. Mirrors the focused-bootstrap\n * rationale documented on `ensureIndexMaterializationsTable` — the full\n * `bootstrapTables` touches every base table and risks Postgres SHARE-lock\n * deadlock under concurrent replica startup.\n */\n ensureKindRemovalsTable?: (this: void) => Promise<void>;\n\n /**\n * List graph-extension kind removals whose data-cleanup pass has not yet\n * succeeded for this `graphId`. Returns rows with\n * `removedAt: undefined`. Order is unspecified; callers materialize\n * one-at-a-time and don't depend on it.\n */\n getPendingKindRemovals?: (\n this: void,\n graphId: string,\n ) => Promise<readonly KindRemovalRow[]>;\n\n /**\n * List ALL kind-removal rows for a `graphId` — pending and completed.\n * Used by `materializeRemovals()` reconciliation to detect rows that\n * are missing entirely (the `removeKinds()` crash window) versus\n * already completed. Without this distinction the reconciler would\n * have to upsert every expected historical removal on every call,\n * churning `last_attempted_at` on rows that long since succeeded.\n * Order is unspecified.\n */\n getAllKindRemovals?: (\n this: void,\n graphId: string,\n ) => Promise<readonly KindRemovalRow[]>;\n\n /**\n * Upsert a kind-removal status row. `removedAt: undefined` records\n * the pending state at schema-commit time; `removedAt: <iso>`\n * marks the data cleanup successful. The COALESCE rule on `removedAt`\n * mirrors `recordIndexMaterialization` so a later failure doesn't\n * clobber the historical successful timestamp from another replica.\n */\n recordKindRemoval?: (\n this: void,\n params: RecordKindRemovalParams,\n ) => Promise<void>;\n\n // === Reconciliation Watermark ===\n\n /**\n * Bootstraps the per-deployment `typegraph_reconciliation_markers`\n * table so `materializeRemovals()` can persist reconciliation\n * progress. Same focused-bootstrap rationale as the other status\n * tables — full `bootstrapTables` risks Postgres SHARE-lock\n * deadlock under concurrent replica startup.\n */\n ensureReconciliationMarkersTable?: (this: void) => Promise<void>;\n\n // === Table Contributions (#129) ===\n\n /**\n * Materializes every contribution flagged `runtimeEnsure` — the\n * strategy-owned runtime tables (fulltext today) that drizzle-kit-\n * managed setups don't create. Called once after a successful schema\n * load. Deliberately scoped: base/drizzle-visible tables are\n * `runtimeEnsure: false`, so this does not regress startup into\n * broad DDL/probing across every table.\n *\n * The canonical durable-marker writer (#135): for each runtime\n * contribution it short-circuits when the marker already records a\n * matching signature, otherwise runs the idempotent `createDdl` and\n * records the marker (success or failure) keyed by `graphId`.\n */\n ensureRuntimeContributions?: (this: void, graphId: string) => Promise<void>;\n\n /**\n * Privileged materializer for one embedding `(kind, field)` slot's\n * `ownedTables` contribution(s): creates the per-field vector table and\n * records its durable marker, idempotently. The vector counterpart of\n * `ensureRuntimeContributions` — vectors are per-`(kind, field)` and\n * graph-derived, so the store enumerates slots (via\n * `resolveEmbeddingFields`) and uses the batch counterpart at boot under\n * the privileged role. Pass `{ force: true }` to overwrite the marker\n * at the current signature, bypassing the drift-guard — the sanctioned\n * path `store.reembedVectorField()` uses after recreating storage at a\n * new dimension. Pass `{ onDrift: \"skip\" }` to leave an\n * already-provisioned slot whose shape has since changed untouched\n * (warn + no-op) instead of refusing — the boot/evolve path, so a\n * declared dimension change never blocks startup before the operator\n * can run `reembedVectorField`. Present only on backends wired with a\n * vector strategy.\n */\n ensureVectorSlotContribution?: (\n this: void,\n slot: VectorSlot,\n options?: Readonly<{ force?: boolean; onDrift?: \"throw\" | \"skip\" }>,\n ) => Promise<void>;\n\n /**\n * Batch form of `ensureVectorSlotContribution`, used by privileged boot to\n * resolve every slot's durable markers with one graph-scoped query. The\n * singular method remains available for re-embedding and compatibility.\n */\n ensureVectorSlotContributions?: (\n this: void,\n slots: readonly VectorSlot[],\n options?: Readonly<{ force?: boolean; onDrift?: \"throw\" | \"skip\" }>,\n ) => Promise<void>;\n\n /**\n * SELECT-only gate for one embedding `(kind, field)` slot: asserts the\n * durable marker(s) for the slot's `ownedTables` contribution(s),\n * cached per backend instance. Throws `StoreNotInitializedError` when\n * the slot is missing, stale (signature drift), or recorded a failed\n * last attempt. The vector counterpart of\n * `assertRuntimeContributionsInitialized`, consulted by the verified\n * runtime attach. Performs ZERO DDL and ZERO writes. Present only on\n * backends wired with a vector strategy.\n */\n assertVectorSlotInitialized?: (this: void, slot: VectorSlot) => Promise<void>;\n\n /**\n * Batch form of `assertVectorSlotInitialized`, used by verified attach to\n * resolve every slot's durable markers with one graph-scoped query.\n */\n assertVectorSlotsInitialized?: (\n this: void,\n slots: readonly VectorSlot[],\n ) => Promise<void>;\n\n /**\n * Forget one embedding `(kind, field)` slot's durable contribution\n * marker(s). Called after the slot's per-field table is dropped\n * (vector-field reclaim) so a later `ensureVectorSlotContribution`\n * re-creates the table instead of trusting an orphaned \"initialized\"\n * marker. Does NOT drop the table itself (the caller already did).\n * Present only on backends wired with a vector strategy.\n */\n deleteVectorSlotContribution?: (\n this: void,\n slot: VectorSlot,\n ) => Promise<void>;\n\n /**\n * Diagnostic: compare each contribution currently expected for `graphId`\n * against its durable marker and the physical catalog.\n *\n * Covers the strategy-owned `runtimeEnsure` contributions (fulltext)\n * plus the `ownedTables` contribution(s) of each supplied vector slot.\n * Returns one {@link ContributionDiagnostic} per detected unusable\n * contribution. A recorded failed materialization is unusable even when\n * marker and catalog agree that no table was produced. A contribution with\n * neither a marker nor a table is treated as never attempted and omitted;\n * marker rows outside the current declaration set are not audited. An empty\n * array therefore is not proof that storage was initialized.\n *\n * Deliberately NOT part of the open path. `ensureRuntimeContributions`\n * and `assertRuntimeContributionsInitialized` short-circuit on a\n * per-instance signature cache and then on the marker row alone, which\n * is the right default for a hot path but leaves a database whose\n * contribution tables were dropped out of band opening clean and\n * failing at the first dependent read or write. This method is the explicit,\n * operator-invoked catalog probe that fills that gap: it issues one uncached\n * existence query per distinct physical table and performs ZERO DDL\n * and ZERO writes, so it is safe under a least-privilege runtime role.\n *\n * Present only on backends that can probe their own catalog.\n */\n verifyContributions?: (\n this: void,\n graphId: string,\n vectorSlots: readonly VectorSlot[],\n ) => Promise<readonly ContributionDiagnostic[]>;\n\n /**\n * Re-audit current contribution declarations and non-destructively repair\n * `missing-marker` and `failed-materialization` findings. The backend must\n * resolve every target itself; it must not accept caller-provided physical\n * identities or DDL. `stale` and `orphaned-marker` findings are returned as\n * `requires-rebuild` because repairing either can require destructive data\n * reconstruction.\n *\n * Present only on backends that can both probe their catalog and run the\n * strategy-owned contribution DDL.\n */\n repairContributions?: (\n this: void,\n graphId: string,\n vectorSlots: readonly VectorSlot[],\n ) => Promise<ContributionRepairResult>;\n\n /**\n * Read-only readiness probe: the same marker-versus-catalog audit\n * `verifyContributions` performs, projected onto one entry per search\n * projection. Shares that method's detection logic exactly — a second\n * implementation would be free to disagree with the one the hot-path\n * gate actually consults, and a health check that disagrees with the\n * gate is worse than none.\n *\n * Writes nothing: no DDL, no marker writes, no effect on the\n * per-instance caches the hot path relies on. Safe on a read path, on\n * a replica, and under a least-privilege role.\n *\n * Present only on backends that can probe their catalog; a backend\n * that provisions contributions without this method declares the gap\n * as `capabilities.contributions.probe === false`.\n */\n probeContributions?: (\n this: void,\n graphId: string,\n vectorSlots: readonly VectorSlot[],\n ) => Promise<readonly ContributionProbeEntry[]>;\n\n /**\n * Destructively rebuild one search projection for one graph: clear that\n * graph's rows from the projection's storage, ensure the storage matches\n * the current `createDdl`, reconstruct the graph's content, and stamp the\n * durable marker at the current signature.\n *\n * This is the repair `repairContributions` deliberately refuses to\n * perform, and it must never be reachable from it. A `stale`\n * contribution's table exists at the *old* physical shape, so the\n * idempotent `CREATE ... IF NOT EXISTS` in the ordinary ensure path\n * no-ops; re-stamping the marker there would leave it blessing a table\n * whose shape is wrong, which is exactly what the drift guard exists\n * to prevent. Only a drop makes the recreate meaningful, and destroying\n * content is a decision the caller states rather than one a flag named\n * `force` implies.\n *\n * The fulltext projection's storage is one physical table shared by every\n * graph in the database, while this call is fenced per graph.\n * Implementations must therefore scope the teardown to `graphId` and may\n * drop the storage only when doing so destroys no other graph's rows;\n * when the recorded shape is stale and the drop that would repair it is\n * not available, they refuse (`shared-storage-in-use`) rather than\n * re-stamp a shape nothing verified.\n *\n * Runs the whole sequence inside one transaction under the same\n * per-graph fence as a schema commit, so an interrupted rebuild leaves\n * the contribution exactly as it was rather than attested-but-empty.\n *\n * `repopulate` receives that transaction and reconstructs content from\n * rows TypeGraph already stores. The inversion exists because deciding\n * *what* content a node contributes needs the schema registry, which\n * is a Store-layer concern the backend must not reach into.\n *\n * @param scope which projection to rebuild. Implementations must\n * refuse `\"vector\"`: embeddings live only in the storage this would\n * drop, so there is nothing to reconstruct them from.\n */\n rebuildContribution?: (\n this: void,\n graphId: string,\n scope: ContributionRebuildScope,\n repopulate: (\n target: TransactionBackend,\n ) => Promise<ContributionRepopulationStats>,\n ) => Promise<ContributionRebuildResult>;\n\n /**\n * Bootstraps the fulltext storage table the active `FulltextStrategy`\n * owns. Same focused-bootstrap rationale as the other `ensure*Table`\n * methods: idempotent and concurrency-safe under replica startup.\n *\n * Superseded by `ensureRuntimeContributions()` (#129); retained as\n * a thin back-compat wrapper for backends/callers predating #129. Not\n * machine-`@deprecated` because the manager still calls it as the\n * pre-#129 fallback. #135 removed the remaining hot-path callers and\n * routed this through the durable-marker writer.\n */\n ensureFulltextTable?: (this: void, graphId: string) => Promise<void>;\n\n /**\n * Read the high-water mark schema version for which\n * `materializeRemovals` reconciliation has already verified history\n * for `graphId`. Returns `undefined` when no marker has been\n * recorded yet. Used to skip already-checked transitions in the\n * recovery walk.\n */\n getReconciliationMarker?: (\n this: void,\n graphId: string,\n ) => Promise<number | undefined>;\n\n /**\n * Persist the reconciliation high-water mark for `graphId`. Called\n * after `materializeRemovals` completes a clean walk; subsequent\n * calls walk only versions newer than this marker. Idempotent\n * upsert by `graphId`.\n */\n setReconciliationMarker?: (\n this: void,\n graphId: string,\n version: number,\n ) => Promise<void>;\n\n // === Graph Lifecycle ===\n /**\n * Hard-deletes all data for a graph (nodes, edges, uniques, embeddings, schema versions).\n * Intended for import-replacement workflows. No hooks, no per-row logic.\n */\n clearGraph: (this: void, graphId: string) => Promise<void>;\n\n /**\n * Creates the base TypeGraph tables if they don't already exist.\n *\n * Called automatically by `createStoreWithSchema()` when a fresh database\n * is detected. Users who manage DDL themselves via `createStore()` never\n * hit this path.\n */\n bootstrapTables?: (this: void) => Promise<void>;\n\n /**\n * Refreshes the backend's query-planner statistics.\n *\n * Call this once after a large initial import or bulk backfill. Without\n * up-to-date statistics, the planner can pick suboptimal execution plans\n * — on PostgreSQL this is the difference between a 0.5ms and a 5ms\n * forward traversal; on SQLite it's the difference between 0.9ms and\n * 23ms fulltext search. Autovacuum / background statistics collection\n * will catch up eventually, but calling this explicitly after a bulk\n * load gives you correct latencies immediately.\n *\n * Implementations:\n * - SQLite runs `ANALYZE`, which populates `sqlite_stat1`\n * - PostgreSQL runs `ANALYZE` on the TypeGraph-managed tables\n *\n * Safe to call at any time; costs a few tens of milliseconds on the\n * sizes this library is designed for.\n */\n refreshStatistics: (this: void) => Promise<void>;\n\n /**\n * Runs an intentionally trusted, all-or-nothing initial import.\n *\n * Optional because only backends that can pin one transaction and use a\n * native high-throughput write path expose it. Implementations must reject\n * a database whose TypeGraph node or edge tables already contain rows. They\n * may temporarily remove rebuildable secondary indexes, but must restore\n * them before committing. Any callback, insert, or rebuild failure rolls the\n * entire operation back.\n *\n * This is a top-level-only lifecycle operation and is deliberately absent\n * from {@link TransactionBackend}. When `schemaWrite` is supplied, the\n * import acquires and validates that Store version's managed-write fence\n * before checking emptiness or writing. Omitting it retains the raw,\n * unversioned import behavior.\n */\n trustedImport?: <T>(\n this: void,\n fn: (session: TrustedImportSession) => Promise<T>,\n options?: Readonly<{\n schemaWrite?: Readonly<{ graphId: string; expectedVersion: number }>;\n }>,\n ) => Promise<T>;\n\n // === Query Execution ===\n execute: <T>(this: void, query: CompiledRowsSql) => Promise<readonly T[]>;\n\n /**\n * Execute a non-row-returning SQL statement bound to this backend or\n * transaction. Optional because custom backends may only expose the public\n * row-returning query path; features that need statement execution must\n * capability-check and fail loudly.\n */\n executeStatement?: (this: void, query: CompiledStatementSql) => Promise<void>;\n\n /**\n * Execute an internally compiled statement against connection-local\n * temporary state. History wrappers preserve this path because it cannot\n * mutate graph or history tables; callers cannot construct its branded\n * input through the public API.\n */\n executeTemporaryStatement?: (\n this: void,\n query: CompiledTemporaryStatementSql,\n ) => Promise<void>;\n\n /** Execute pre-compiled SQL text with bound parameters. Available on sync SQLite and pg backends. */\n executeRaw?: <T>(\n this: void,\n sqlText: string,\n params: readonly unknown[],\n ) => Promise<readonly T[]>;\n\n /** Compile a TypeGraph `SqlFragment` to `{ sql, params }` without executing. */\n compileSql?: (\n this: void,\n query: SqlFragment,\n ) => Readonly<{ sql: string; params: readonly unknown[] }>;\n\n /**\n * Execute a DDL statement that returns no rows (CREATE INDEX,\n * CREATE TABLE, ALTER TABLE, etc.). Separate from `executeRaw`\n * because some drivers (better-sqlite3) require `.run()` for DDL\n * and `.all()` for queries — the ambiguity can't be resolved by\n * inspecting the SQL string portably.\n *\n * Postgres path can use this for `CREATE INDEX CONCURRENTLY`, which\n * cannot run inside a transaction. Implementations must execute the\n * statement outside `transaction(...)`.\n */\n executeDdl?: (this: void, ddl: string) => Promise<void>;\n\n // === Transaction ===\n /** Runs TypeGraph operations inside a backend-owned transaction. */\n transaction: <T>(\n this: void,\n fn: (tx: TransactionBackend) => Promise<T>,\n options?: TransactionOptions,\n ) => Promise<T>;\n\n // === Lifecycle ===\n close: (this: void) => Promise<void>;\n}>;\n\n/**\n * Adapter-native transaction interoperability layered on top of the portable\n * TypeGraph backend. Only adapter entrypoints expose this capability.\n */\nexport type AdapterBackend<TNativeTransaction> = GraphBackend &\n Readonly<{\n /**\n * Runs TypeGraph operations and exposes the exact adapter-native handle\n * bound to the same transaction.\n */\n transactionWithNative: <T>(\n this: void,\n fn: (\n tx: TransactionBackend,\n nativeTransaction: TNativeTransaction,\n ) => Promise<T>,\n options?: TransactionOptions,\n ) => Promise<T>;\n /** Adopts a caller-owned, already-open adapter transaction. */\n adoptTransaction: (\n this: void,\n externalTransaction: TNativeTransaction,\n ) => TransactionBackend;\n }>;\n\nexport type BackendIdentity = Pick<\n GraphBackend,\n | \"dialect\"\n | \"capabilities\"\n | \"tableNames\"\n | \"fulltextStrategy\"\n | \"vectorStrategy\"\n>;\n\nexport type NodeEntityReadBackend = Pick<\n GraphBackend,\n \"getNode\" | \"getNodes\" | \"findNodesByKind\" | \"countNodesByKind\"\n>;\n\nexport type NodeEntityWriteBackend = Pick<\n GraphBackend,\n | \"insertNode\"\n | \"insertNodeNoReturn\"\n | \"insertNodesBatch\"\n | \"insertNodesBatchReturning\"\n | \"updateNode\"\n | \"updateNodeSet\"\n | \"deleteNode\"\n | \"hardDeleteNode\"\n>;\n\nexport type EdgeEntityReadBackend = Pick<\n GraphBackend,\n | \"getEdge\"\n | \"getEdges\"\n | \"countEdgesFrom\"\n | \"edgeExistsBetween\"\n | \"findEdgesConnectedTo\"\n | \"findEdgesByKind\"\n | \"findEdgesByEndpointSet\"\n | \"findEdgesByHeterogeneousEndpointSet\"\n | \"countEdgesByKind\"\n>;\n\nexport type EdgeEntityWriteBackend = Pick<\n GraphBackend,\n | \"insertEdge\"\n | \"insertEdgeNoReturn\"\n | \"insertEdgesBatch\"\n | \"insertEdgesBatchReturning\"\n | \"updateEdge\"\n | \"deleteEdge\"\n | \"deleteEdgesBatch\"\n | \"hardDeleteEdge\"\n | \"hardDeleteEdgesBatch\"\n>;\n\nexport type GraphEntityReadBackend = NodeEntityReadBackend &\n EdgeEntityReadBackend;\n\nexport type GraphEntityWriteBackend = NodeEntityWriteBackend &\n EdgeEntityWriteBackend;\n\nexport type UniqueConstraintBackend = Pick<\n GraphBackend,\n | \"insertUnique\"\n | \"insertUniqueBatch\"\n | \"deleteUnique\"\n | \"hardDeleteUniquesByNodeIds\"\n | \"checkUnique\"\n | \"checkUniqueBatch\"\n>;\n\nexport type SchemaReadBackend = Pick<\n GraphBackend,\n \"getActiveSchema\" | \"getSchemaVersion\"\n>;\n\nexport type SchemaCommitBackend = Pick<\n GraphBackend,\n \"commitSchemaVersion\" | \"commitSchemaVersionIfKindsEmpty\" | \"setActiveVersion\"\n>;\n\nexport type SchemaWriteFenceBackend = Pick<\n GraphBackend,\n \"lockSchemaVersionForWrite\"\n>;\n\nexport type VectorOperationBackend = Pick<\n GraphBackend,\n | \"upsertEmbedding\"\n | \"upsertEmbeddingBatch\"\n | \"deleteEmbedding\"\n | \"deleteEmbeddingBatch\"\n | \"vectorSearch\"\n | \"createVectorIndex\"\n | \"dropVectorIndex\"\n>;\n\nexport type FulltextOperationBackend = Pick<\n GraphBackend,\n | \"upsertFulltext\"\n | \"deleteFulltext\"\n | \"upsertFulltextBatch\"\n | \"deleteFulltextBatch\"\n | \"fulltextSearch\"\n>;\n\nexport type IndexMaterializationBackend = Pick<\n GraphBackend,\n | \"ensureIndexMaterializationsTable\"\n | \"getIndexMaterialization\"\n | \"getIndexMaterializations\"\n | \"recordIndexMaterialization\"\n>;\n\nexport type ContributionMaterializationBackend = Pick<\n GraphBackend,\n | \"ensureContributionMaterializationsTable\"\n | \"getContributionMaterialization\"\n | \"recordContributionMaterialization\"\n | \"assertRuntimeContributionsInitialized\"\n | \"ensureRuntimeContributions\"\n | \"ensureFulltextTable\"\n>;\n\nexport type RemovalMaterializationBackend = Pick<\n GraphBackend,\n | \"ensureKindRemovalsTable\"\n | \"getPendingKindRemovals\"\n | \"getAllKindRemovals\"\n | \"recordKindRemoval\"\n | \"ensureReconciliationMarkersTable\"\n | \"getReconciliationMarker\"\n | \"setReconciliationMarker\"\n>;\n\nexport type GraphLifecycleBackend = Pick<\n GraphBackend,\n \"clearGraph\" | \"bootstrapTables\"\n>;\n\nexport type QueryExecutionBackend = Pick<GraphBackend, \"execute\">;\n\nexport type RawQueryExecutionBackend = Pick<\n GraphBackend,\n \"executeRaw\" | \"compileSql\"\n>;\n\nexport type SqlCompilationBackend = Pick<GraphBackend, \"compileSql\">;\n\nexport type RawStatementExecutionBackend = Pick<\n GraphBackend,\n \"executeStatement\" | \"executeTemporaryStatement\" | \"executeDdl\"\n>;\n\nexport type BackendMaintenance = Pick<GraphBackend, \"refreshStatistics\">;\n\nexport type BackendTransactions = Pick<GraphBackend, \"transaction\">;\n\nexport type AdapterBackendTransactions<TNativeTransaction> = Pick<\n AdapterBackend<TNativeTransaction>,\n \"transactionWithNative\" | \"adoptTransaction\"\n>;\n\nexport type BackendLifecycle = Pick<GraphBackend, \"close\">;\n\n/**\n * Read-oriented transaction projection exposed by portable transaction\n * contexts. It preserves snapshot-aware graph reads and TypeGraph-compiled\n * row queries while excluding graph writes, arbitrary raw SQL, DDL,\n * transaction adoption, and lifecycle control.\n */\nexport type TransactionReadBackend = Readonly<\n BackendIdentity &\n GraphEntityReadBackend &\n SchemaReadBackend &\n QueryExecutionBackend &\n SqlCompilationBackend\n>;\n\n/**\n * Transaction backend — a backend scoped to a transaction.\n *\n * This is an explicit facet composition, not `Omit<GraphBackend, ...>`.\n * New top-level-only backend methods therefore do not silently appear on\n * transaction-scoped backends. `commitSchemaVersion`, `setActiveVersion`,\n * `refreshStatistics`, `transaction`, `adoptTransaction`, and `close` stay\n * deliberately absent: their guarantees depend on the top-level backend or\n * lifecycle owner rather than an already-open transaction.\n */\nexport type TransactionBackend = Readonly<\n BackendIdentity &\n GraphEntityReadBackend &\n GraphEntityWriteBackend &\n UniqueConstraintBackend &\n SchemaReadBackend &\n Pick<GraphBackend, \"lockSchemaVersionForWrite\"> &\n VectorOperationBackend &\n FulltextOperationBackend &\n IndexMaterializationBackend &\n ContributionMaterializationBackend &\n RemovalMaterializationBackend &\n GraphLifecycleBackend &\n QueryExecutionBackend &\n RawQueryExecutionBackend &\n RawStatementExecutionBackend\n>;\n\n/**\n * Transaction backend exposed only while the backend's schema-write lock is\n * held. Unlike the ordinary top-level `executeDdl` port, this DDL primitive is\n * explicitly transaction-scoped and must use the callback's transaction.\n *\n * @internal\n */\nexport type SchemaWriteTransactionBackend = TransactionBackend &\n Readonly<{\n executeStatement: NonNullable<TransactionBackend[\"executeStatement\"]>;\n tableExists: (this: void, tableName: string) => Promise<boolean>;\n executeSchemaDdl: (this: void, ddl: string) => Promise<void>;\n deleteSchemaVectorSlotContribution: (\n this: void,\n slot: VectorSlot,\n ) => Promise<void>;\n }>;\n\n/**\n * The target a schema-commit preflight runs against: a transaction backend\n * that MAY also carry the schema-write DDL primitive.\n *\n * Optional, deliberately. Both bundled backends pass the same\n * {@link SchemaWriteTransactionBackend} their `schemaWriteTransaction` exposes,\n * so DDL is available there; a custom backend may implement\n * {@link GraphBackend.commitSchemaVersionWithPreflight} over a transaction that\n * cannot run DDL. Declaring the port as present-or-absent lets a preflight that\n * needs it refuse with a typed error naming the capability, instead of calling\n * `undefined` — or, far worse, skipping the DDL and continuing.\n *\n * @internal\n */\nexport type SchemaCommitPreflightBackend = TransactionBackend &\n Readonly<{\n executeSchemaDdl?: (this: void, ddl: string) => Promise<void>;\n }>;\n\n/**\n * Builds the actual runtime projection exposed as `TransactionContext.backend`.\n * This is an explicit object rather than a type-only narrowing so portable\n * callers cannot discover write or arbitrary-SQL methods on the underlying\n * transaction backend at runtime.\n *\n * @internal\n */\nexport function createTransactionReadBackend(\n backend: TransactionBackend,\n): TransactionReadBackend {\n const getNodes = backend.getNodes;\n const getEdges = backend.getEdges;\n const compileSql = backend.compileSql;\n\n return Object.freeze({\n dialect: backend.dialect,\n capabilities: backend.capabilities,\n ...(backend.tableNames === undefined ?\n {}\n : { tableNames: backend.tableNames }),\n ...(backend.fulltextStrategy === undefined ?\n {}\n : { fulltextStrategy: backend.fulltextStrategy }),\n ...(backend.vectorStrategy === undefined ?\n {}\n : { vectorStrategy: backend.vectorStrategy }),\n getNode: (graphId, kind, id) => backend.getNode(graphId, kind, id),\n ...(getNodes === undefined ?\n {}\n : {\n getNodes: (graphId: string, kind: string, ids: readonly string[]) =>\n getNodes(graphId, kind, ids),\n }),\n getEdge: (graphId, id) => backend.getEdge(graphId, id),\n ...(getEdges === undefined ?\n {}\n : {\n getEdges: (graphId: string, ids: readonly string[]) =>\n getEdges(graphId, ids),\n }),\n countEdgesFrom: (params) => backend.countEdgesFrom(params),\n edgeExistsBetween: (params) => backend.edgeExistsBetween(params),\n findEdgesConnectedTo: (params) => backend.findEdgesConnectedTo(params),\n findNodesByKind: (params) => backend.findNodesByKind(params),\n countNodesByKind: (params) => backend.countNodesByKind(params),\n findEdgesByKind: (params) => backend.findEdgesByKind(params),\n countEdgesByKind: (params) => backend.countEdgesByKind(params),\n getActiveSchema: (graphId) => backend.getActiveSchema(graphId),\n getSchemaVersion: (graphId, version) =>\n backend.getSchemaVersion(graphId, version),\n execute: <T>(query: CompiledRowsSql) => backend.execute<T>(query),\n ...(compileSql === undefined ?\n {}\n : { compileSql: (query: SqlFragment) => compileSql(query) }),\n });\n}\n\ntype ExactBackendOverlay<T extends object, O extends Partial<T>> = O &\n Readonly<Record<Exclude<keyof O, keyof T>, never>>;\n\n// ============================================================\n// Managed Backend Helper\n// ============================================================\n\n/**\n * Overlays selected backend methods without copying the backend object.\n *\n * This is a decoration primitive: every non-overridden target property remains\n * reachable. Never use it to narrow a capability surface; construct an\n * explicit allowlist projection first, then decorate that projection.\n *\n * Backend wrappers use this instead of object spread so proxy backends keep\n * getters and non-enumerable members. GraphBackend functions are receiver-free\n * by contract, so delegated methods are returned unchanged.\n */\nexport function createBackendOverlay<\n T extends GraphBackend | TransactionBackend,\n const O extends Partial<T> = Partial<T>,\n>(target: T, overlay: ExactBackendOverlay<T, O>): T {\n function hasOverlayProperty(property: PropertyKey): boolean {\n return Object.hasOwn(overlay, property);\n }\n\n const decoratedBackend = new Proxy(target, {\n get(targetObject, property) {\n if (hasOverlayProperty(property)) {\n return Reflect.get(overlay, property, overlay);\n }\n return Reflect.get(targetObject, property, targetObject);\n },\n\n has(targetObject, property) {\n return (\n hasOverlayProperty(property) || Reflect.has(targetObject, property)\n );\n },\n\n ownKeys(targetObject) {\n return [\n ...new Set([\n ...Reflect.ownKeys(targetObject),\n ...Reflect.ownKeys(overlay),\n ]),\n ];\n },\n\n getOwnPropertyDescriptor(targetObject, property) {\n if (hasOverlayProperty(property)) {\n const descriptor = Reflect.getOwnPropertyDescriptor(overlay, property);\n if (descriptor === undefined) return;\n return { ...descriptor, configurable: true };\n }\n return Reflect.getOwnPropertyDescriptor(targetObject, property);\n },\n\n set(targetObject, property, value) {\n if (hasOverlayProperty(property)) {\n return Reflect.set(overlay, property, value, overlay);\n }\n return Reflect.set(targetObject, property, value, targetObject);\n },\n\n defineProperty(targetObject, property, attributes) {\n if (hasOverlayProperty(property)) {\n return Reflect.defineProperty(overlay, property, attributes);\n }\n return Reflect.defineProperty(targetObject, property, attributes);\n },\n\n deleteProperty(targetObject, property) {\n if (hasOverlayProperty(property)) {\n return Reflect.deleteProperty(overlay, property);\n }\n return Reflect.deleteProperty(targetObject, property);\n },\n });\n inheritSerializedTransactionResource(decoratedBackend, target);\n return decoratedBackend;\n}\n\n/**\n * Wraps a GraphBackend with idempotent close that also runs a teardown\n * callback (e.g. closing the underlying database connection).\n */\nexport function wrapWithManagedClose<TNativeTransaction>(\n backend: AdapterBackend<TNativeTransaction>,\n teardown: () => void | Promise<void>,\n): AdapterBackend<TNativeTransaction>;\nexport function wrapWithManagedClose(\n backend: GraphBackend,\n teardown: () => void | Promise<void>,\n): GraphBackend;\nexport function wrapWithManagedClose(\n backend: GraphBackend,\n teardown: () => void | Promise<void>,\n): GraphBackend {\n let backendClosed = false;\n let teardownComplete = false;\n let closeInFlight: Promise<void> | undefined;\n\n async function closeManagedResources(): Promise<void> {\n const errors: unknown[] = [];\n if (!backendClosed) {\n try {\n await backend.close();\n backendClosed = true;\n } catch (error) {\n errors.push(error);\n }\n }\n if (!teardownComplete) {\n try {\n await teardown();\n teardownComplete = true;\n } catch (error) {\n errors.push(error);\n }\n }\n\n if (errors.length === 1) throw errors[0];\n if (errors.length > 1) {\n throw new AggregateError(\n errors,\n \"The backend and its managed resource both failed to close.\",\n );\n }\n }\n\n const closeOverlay: Pick<GraphBackend, \"close\"> = {\n async close(): Promise<void> {\n if (backendClosed && teardownComplete) return;\n closeInFlight ??= closeManagedResources().finally(() => {\n closeInFlight = undefined;\n });\n await closeInFlight;\n },\n };\n return createBackendOverlay(backend, closeOverlay);\n}\n\n/**\n * Closes an owned resource after provisioning fails without replacing the\n * original domain error with a secondary cleanup failure.\n */\nexport async function closeAfterFailure(\n resource: Readonly<{ close: () => void | Promise<void> }>,\n provisioningError: unknown,\n): Promise<never> {\n try {\n await resource.close();\n } catch {\n // The provisioning failure remains the actionable error. Managed close is\n // retryable when the resource is returned, but a failed factory has no\n // handle it can safely expose for a second attempt.\n }\n throw provisioningError;\n}\n\n/**\n * Runs `fn` inside a transaction when given a top-level backend that supports\n * one, falling through to a direct invocation otherwise. Lets call sites benefit\n * from atomicity on backends that have transactions while staying functional on\n * backends that don't (Cloudflare D1, `drizzle-orm/neon-http` over HTTP), and\n * avoids opening nested transactions when the caller already holds a\n * transaction-scoped backend. The single-statement race window is already\n * implicit on any backend that reports `transactions: false`; callers that\n * cannot tolerate it must branch on the capability themselves.\n *\n * Pass `fallback` only when the toplevel backend method would recurse\n * — pass the operation-level backend so the no-tx path doesn't loop\n * back through the same toplevel method.\n */\nexport async function runOptionallyInTransaction<T>(\n backend: GraphBackend | TransactionBackend,\n fn: (target: GraphBackend | TransactionBackend) => Promise<T>,\n fallback?: GraphBackend | TransactionBackend,\n): Promise<T> {\n if (\"transaction\" in backend && backend.capabilities.transactions) {\n return backend.transaction((tx) => fn(tx));\n }\n return fn(fallback ?? backend);\n}\n\n// ============================================================\n// Additional Parameter Types\n// ============================================================\n\n/**\n * Parameters for inserting a unique constraint entry.\n */\nexport type InsertUniqueParams = Readonly<{\n graphId: string;\n nodeKind: string;\n constraintName: string;\n key: string;\n nodeId: string;\n concreteKind: string;\n}>;\n\n/**\n * Parameters for deleting a unique constraint entry.\n */\nexport type DeleteUniqueParams = Readonly<{\n graphId: string;\n nodeKind: string;\n constraintName: string;\n key: string;\n}>;\n\n/** Parameters for permanently clearing uniqueness sidecars by node identity. */\nexport type HardDeleteUniquesByNodeIdsParams = Readonly<{\n graphId: string;\n concreteKind: string;\n nodeIds: readonly string[];\n}>;\n\n/**\n * Parameters for checking a unique constraint.\n */\nexport type CheckUniqueParams = Readonly<{\n graphId: string;\n nodeKind: string;\n constraintName: string;\n key: string;\n /** If true, also returns soft-deleted entries. Used by get-or-create operations. */\n includeDeleted?: boolean;\n}>;\n\n/**\n * Parameters for batch-checking unique constraints.\n */\nexport type CheckUniqueBatchParams = Readonly<{\n graphId: string;\n nodeKind: string;\n constraintName: string;\n keys: readonly string[];\n /** If true, also returns soft-deleted entries. Used by get-or-create operations. */\n includeDeleted?: boolean;\n}>;\n\n/**\n * Parameters for inserting a schema version.\n *\n * Used internally by backend implementations. Public callers go through\n * `commitSchemaVersion`, which handles the insert + activate atomically\n * with CAS guarantees.\n */\nexport type InsertSchemaParams = Readonly<{\n graphId: string;\n version: number;\n schemaHash: string;\n schemaDoc: SerializedSchema;\n isActive: boolean;\n}>;\n\n/**\n * The caller's claim about the currently-active schema version, used as\n * the optimistic compare-and-swap guard for `commitSchemaVersion`.\n *\n * - `{ kind: \"initial\" }` — caller is committing the first-ever version\n * for this graph and asserts no active version exists yet.\n * - `{ kind: \"active\", version: N }` — caller observed version N as\n * active and is committing version N+1 against that baseline. The\n * commit fails with `StaleVersionError` if some other writer has\n * advanced or rolled back the pointer in the meantime.\n *\n * Tagged-union form (rather than a magic `version: 0` sentinel) because\n * the two cases have materially different semantics: the initial path\n * skips the \"deactivate prior\" UPDATE, and the no-active-row state is\n * a *valid* expected state, not an out-of-band signal.\n */\nexport type CommitSchemaVersionExpected =\n Readonly<{ kind: \"initial\" }> | Readonly<{ kind: \"active\"; version: number }>;\n\n/**\n * Parameters for `commitSchemaVersion`.\n */\nexport type CommitSchemaVersionParams = Readonly<{\n graphId: string;\n /** CAS guard — see `CommitSchemaVersionExpected`. */\n expected: CommitSchemaVersionExpected;\n /** The new version to insert and activate. */\n version: number;\n schemaHash: string;\n schemaDoc: SerializedSchema;\n}>;\n\nexport type SchemaKindEmptinessProbe = Readonly<{\n entity: \"node\" | \"edge\";\n kind: string;\n}>;\n\nexport type PopulatedSchemaKind = SchemaKindEmptinessProbe &\n Readonly<{ count: number }>;\n\nexport type CommitSchemaVersionIfKindsEmptyResult =\n | Readonly<{ status: \"committed\"; row: SchemaVersionRow }>\n | Readonly<{\n status: \"populated\";\n kinds: readonly PopulatedSchemaKind[];\n }>;\n\nexport type LockSchemaVersionForWriteParams = Readonly<{\n graphId: string;\n expectedVersion: number;\n}>;\n\n/**\n * Optional schema-managed write identity for a trusted import. Supplying it\n * acquires and validates the Store version's transaction-scoped schema fence;\n * omitting it leaves the import outside the versioned guarantee.\n */\nexport type TrustedImportOptions = Readonly<{\n schemaWrite?: Readonly<{ graphId: string; expectedVersion: number }>;\n}>;\n\n/**\n * Parameters for `setActiveVersion`.\n *\n * Flips the active pointer from `expected` to `version` for an existing\n * row. CAS prevents overwriting a concurrent rollback or commit.\n */\nexport type SetActiveVersionParams = Readonly<{\n graphId: string;\n /** CAS guard. Same semantics as `commitSchemaVersion.expected`. */\n expected: CommitSchemaVersionExpected;\n /** The version to mark active. Must already exist. */\n version: number;\n}>;\n\n/**\n * Parameters for counting edges from a source node.\n */\nexport type CountEdgesFromParams = Readonly<{\n graphId: string;\n edgeKind: string;\n fromKind: string;\n fromId: string;\n /** If true, only count edges where valid_to IS NULL */\n activeOnly?: boolean;\n}>;\n\n/**\n * Parameters for checking if an edge exists between two nodes.\n */\nexport type EdgeExistsBetweenParams = Readonly<{\n graphId: string;\n edgeKind: string;\n fromKind: string;\n fromId: string;\n toKind: string;\n toId: string;\n}>;\n\n/**\n * Parameters for finding edges connected to a node.\n */\nexport type FindEdgesConnectedToParams = Readonly<{\n graphId: string;\n nodeKind: string;\n nodeId: string;\n}>;\n\n/**\n * Parameters for finding nodes by kind.\n */\nexport type FindNodesByKindParams = Readonly<{\n graphId: string;\n kind: string;\n /** Max rows to return. */\n limit?: number;\n /** Offset. Present for backward compat; rebuild uses `after` instead. */\n offset?: number;\n /** If true, exclude deleted nodes. Default true. */\n excludeDeleted?: boolean;\n /** Temporal mode for filtering by validity period. */\n temporalMode?: TemporalMode;\n /** Timestamp for \"current\" and \"asOf\" temporal modes. */\n asOf?: string;\n /**\n * Stable ordering for keyset pagination. Default: \"created_at\" (existing\n * behavior). Rebuild should use \"id\" for iteration that is stable under\n * concurrent writes and shared timestamps.\n */\n orderBy?: \"id\" | \"created_at\";\n /**\n * Keyset cursor. Returns rows strictly greater (by `orderBy`) than this\n * value. When `orderBy: \"id\"`, compared lexicographically. Mutually\n * exclusive with `offset` — callers pick one.\n */\n after?: string;\n}>;\n\n/**\n * Parameters for counting nodes by kind.\n */\nexport type CountNodesByKindParams = Readonly<{\n graphId: string;\n kind: string;\n /** If true, exclude deleted nodes. Default true. */\n excludeDeleted?: boolean;\n /** Temporal mode for filtering by validity period. */\n temporalMode?: TemporalMode;\n /** Timestamp for \"current\" and \"asOf\" temporal modes. */\n asOf?: string;\n}>;\n\n/**\n * Parameters for finding edges by kind.\n */\nexport type FindEdgesByKindParams = Readonly<{\n graphId: string;\n kind: string;\n fromKind?: string;\n fromId?: string;\n toKind?: string;\n toId?: string;\n limit?: number;\n offset?: number;\n /** If true, exclude deleted edges. Default true. */\n excludeDeleted?: boolean;\n /** Temporal mode for filtering by validity period. */\n temporalMode?: TemporalMode;\n /** Timestamp for \"current\" and \"asOf\" temporal modes. */\n asOf?: string;\n /**\n * Stable ordering for keyset pagination. Default: \"created_at\" (existing\n * behavior). Use \"id\" for iteration that is stable under shared `created_at`\n * timestamps — the offset path orders by the NON-unique `created_at`, so a\n * full enumeration must page by the unique `id` to avoid skipping a row at a\n * page boundary. Mirrors {@link FindNodesByKindParams.orderBy}.\n */\n orderBy?: \"id\" | \"created_at\";\n /**\n * Keyset cursor. Returns rows strictly greater (by `orderBy`) than this value.\n * When `orderBy: \"id\"`, compared lexicographically. Mutually exclusive with\n * `offset` — callers pick one. Mirrors {@link FindNodesByKindParams.after}.\n */\n after?: string;\n}>;\n\n/**\n * The endpoint side a {@link FindEdgesByEndpointSetParams} read fans out over.\n */\nexport type EdgeEndpointSide = \"from\" | \"to\";\n\n/**\n * Parameters for reading the edges of a SET of endpoints in one statement per\n * bind-budget chunk — the widened form of {@link FindEdgesByKindParams}'s\n * scalar `fromId` / `toId`.\n *\n * Deliberately a SEPARATE parameter type on a separate operation rather than\n * optional fields on `FindEdgesByKindParams`. A backend that did not implement\n * set membership would still type-check while ignoring the id list, and would\n * then return every edge of the kind — which the caller would rebucket into a\n * correct-looking answer at unbounded cost. Splitting the operation makes that\n * failure unreachable: support is detected by the method's presence, before any\n * read is issued.\n *\n * The shape also makes the previously-validated illegal states\n * unrepresentable. One `side` instead of two independent id lists means both\n * endpoints can never fan out at once; no scalar `fromId` / `toId` field means\n * a scalar and a set can never disagree; no `limit` / `offset` / `after` means\n * a global slice can never be requested across a read the backend splits into\n * bind-budget chunks.\n */\nexport type FindEdgesByEndpointSetParams = Readonly<{\n graphId: string;\n kind: string;\n /** Which endpoint column the id set constrains. */\n side: EdgeEndpointSide;\n /**\n * Kind of the fanned-out endpoint. Required: it is the index prefix that\n * makes the set a seek rather than a scan, so a set is always scoped to one\n * endpoint kind and a heterogeneous page costs one read per distinct kind.\n */\n endpointKind: string;\n /**\n * Endpoint NODE ids to match — deliberately not named `ids`, which in the\n * backend params vocabulary means edge ids (see\n * {@link DeleteEdgesBatchParams}); the write-surface assertion in\n * `recorded-capture/write-surface.ts` classifies params structurally and a\n * `{ graphId, ids }` read would be misread as a write.\n *\n * The backend deduplicates before splitting the list across bind-budget\n * chunks — a repeated id spanning two chunks would return its edges twice.\n * An empty list reads nothing and yields no rows.\n */\n endpointIds: readonly string[];\n /**\n * Maximum rows per distinct endpoint id, applied inside the statement (via\n * `ROW_NUMBER()` over the read's own ordering) rather than by the caller.\n * Only meaningful on a backend whose `capabilities.windowFunctions` is true;\n * callers must still cap client-side, so a backend that ignores this returns\n * a superset rather than a wrong answer. Unlike a global `limit`, a\n * per-endpoint cap composes with chunking: each endpoint's rows fall\n * entirely within one chunk.\n */\n limitPerEndpoint?: number;\n /** If true, exclude deleted edges. Default true. */\n excludeDeleted?: boolean;\n /** Temporal mode for filtering by validity period. */\n temporalMode?: TemporalMode;\n /** Timestamp for \"current\" and \"asOf\" temporal modes. */\n asOf?: string;\n}>;\n\n/**\n * Parameters for reading several edge kinds from several endpoint kinds.\n *\n * The endpoint pairs are joined as a relation, so round trips are independent\n * of the number of licensed edge-kind/endpoint-kind combinations. Large source\n * sets may still be split to respect the backend's bind-parameter budget.\n */\nexport type FindEdgesByHeterogeneousEndpointSetParams = Readonly<{\n graphId: string;\n side: EdgeEndpointSide;\n endpoints: readonly Readonly<{ kind: string; id: string }>[];\n edgeKinds: readonly string[];\n limitPerEndpoint?: number;\n /** If true, exclude deleted edges. Default true. */\n excludeDeleted?: boolean;\n /** Temporal mode for filtering by validity period. */\n temporalMode?: TemporalMode;\n /** Timestamp for \"current\" and \"asOf\" temporal modes. */\n asOf?: string;\n}>;\n\n/**\n * Parameters for counting edges by kind.\n */\nexport type CountEdgesByKindParams = Readonly<{\n graphId: string;\n kind: string;\n fromKind?: string;\n fromId?: string;\n toKind?: string;\n toId?: string;\n /** If true, exclude deleted edges. Default true. */\n excludeDeleted?: boolean;\n /** Temporal mode for filtering by validity period. */\n temporalMode?: TemporalMode;\n /** Timestamp for \"current\" and \"asOf\" temporal modes. */\n asOf?: string;\n}>;\n\n// ============================================================\n// Default Capabilities\n// ============================================================\n\n/**\n * Conservative per-statement bound-parameter floor for SQLite. The\n * compiled-in `SQLITE_MAX_VARIABLE_NUMBER` defaulted to 999 before SQLite\n * 3.32.0; drivers whose real ceiling cannot be probed (async/remote\n * connections) keep this floor. Single source of truth for\n * {@link SQLITE_CAPABILITIES} and the SQLite backend's batch math fallback.\n */\nexport const SQLITE_MAX_BIND_PARAMETERS = 999;\n\n/**\n * `SQLITE_MAX_VARIABLE_NUMBER` default since SQLite 3.32.0 (better-sqlite3\n * also compiles it in explicitly). Used when a synchronous driver's probe\n * confirms a modern build.\n */\nexport const MODERN_SQLITE_MAX_BIND_PARAMETERS = 32_766;\n\n/**\n * Cloudflare D1's documented per-statement bound-parameter ceiling. Far\n * below the classic SQLite floor, so D1 detection must cap the budget or\n * batched writes fail at runtime.\n */\nexport const D1_MAX_BIND_PARAMETERS = 100;\n\n/**\n * Cloudflare SQLite-backed Durable Objects' documented per-statement\n * bound-parameter ceiling. The resolved `do-sqlite` execution profile must\n * advertise this limit so every capability-driven batch path stays below it.\n */\nexport const DURABLE_OBJECT_MAX_BIND_PARAMETERS = 100;\n\n/**\n * Safe bound-parameter ceiling shared by every bundled PostgreSQL driver.\n * The protocol count can represent 65535, but postgres.js rejects a statement\n * with 65534 bound values, so backend batch math uses the lower portable limit.\n */\nexport const POSTGRES_MAX_BIND_PARAMETERS = 65_533;\n\n/**\n * Default capabilities for SQLite.\n */\nexport const SQLITE_CAPABILITIES: BackendCapabilities = Object.freeze({\n transactions: true, // SQLite supports transactions\n windowFunctions: true, // SQLite has supported window functions since 3.25.0\n clearValidTo: true,\n returning: true, // SQLite has supported RETURNING since 3.35.0\n maxBindParameters: SQLITE_MAX_BIND_PARAMETERS,\n // Generic SQLite builds do not guarantee ENABLE_MATH_FUNCTIONS. The local\n // better-sqlite3 factory overrides this flag for its bundled build contract.\n graphAnalytics: Object.freeze({ supported: true, mathFunctions: false }),\n});\n\n/**\n * Default capabilities for PostgreSQL.\n */\nexport const POSTGRES_CAPABILITIES: BackendCapabilities = Object.freeze({\n transactions: true, // PostgreSQL supports transactions\n windowFunctions: true, // PostgreSQL supports ROW_NUMBER() and related windows\n clearValidTo: true,\n returning: true, // PostgreSQL has supported RETURNING since 8.2\n maxBindParameters: POSTGRES_MAX_BIND_PARAMETERS,\n graphAnalytics: Object.freeze({ supported: true, mathFunctions: true }),\n});\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/utils/date.ts"],"names":["ValidationError","INVERTED_VALIDITY_WINDOW_CODE","IMMUTABLE_VALIDITY_LOWER_BOUND_CODE"],"mappings":"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","file":"chunk-425IZOQ5.cjs","sourcesContent":["/**\n * Date encoding utilities for consistent storage.\n *\n * Contract: All dates are stored as ISO 8601 strings in UTC.\n * - Always includes milliseconds\n * - Always UTC (Z suffix)\n * - Sorts correctly as strings\n */\n\nimport {\n IMMUTABLE_VALIDITY_LOWER_BOUND_CODE,\n INVERTED_VALIDITY_WINDOW_CODE,\n ValidationError,\n} from \"../errors\";\n\n/**\n * ISO 8601 datetime pattern.\n * Matches formats like:\n * - 2024-01-15T10:30:00.000Z\n * - 2024-01-15T10:30:00Z\n * - 2024-01-15T10:30:00.123Z\n */\nconst ISO_DATE_PATTERN = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d{1,3})?Z$/;\n\n/**\n * Checks if a string is a valid ISO 8601 datetime.\n *\n * @param value - String to validate\n * @returns True if valid ISO 8601 datetime\n */\nexport function isValidIsoDate(value: string): boolean {\n if (!ISO_DATE_PATTERN.test(value)) {\n return false;\n }\n // Also check that Date parsing produces a valid date\n const date = new Date(value);\n return !Number.isNaN(date.getTime());\n}\n\n/**\n * Validates that a string is a valid ISO 8601 datetime.\n * Throws ValidationError if invalid.\n *\n * @param value - String to validate\n * @param fieldName - Name of field for error message\n * @returns The validated string\n * @throws ValidationError if not a valid ISO datetime\n */\nexport function validateIsoDate(value: string, fieldName: string): string {\n if (!isValidIsoDate(value)) {\n throw new ValidationError(\n `Invalid ISO 8601 datetime for \"${fieldName}\": \"${value}\". ` +\n `Expected format: YYYY-MM-DDTHH:mm:ss.sssZ`,\n {\n issues: [\n {\n path: fieldName,\n message: `Invalid ISO 8601 datetime format. Expected: YYYY-MM-DDTHH:mm:ss.sssZ, got: \"${value}\"`,\n },\n ],\n },\n {\n suggestion: `Use a valid ISO 8601 UTC datetime like \"2024-01-15T10:30:00.000Z\"`,\n },\n );\n }\n return value;\n}\n\n/**\n * Strict canonical ISO 8601 pattern: fixed-width UTC with exactly\n * millisecond precision (`YYYY-MM-DDTHH:mm:ss.sssZ`). Unlike\n * {@link ISO_DATE_PATTERN}, which tolerates a missing or variable-width\n * fractional part, this is the only shape whose lexicographic text order\n * matches chronological order — avoiding cases like `\"...:00.1Z\"` (= `.100`)\n * sorting *after* `\"...:00.101Z\"`.\n */\nconst CANONICAL_ISO_DATE_PATTERN =\n /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z$/;\n\nconst ZONELESS_DATETIME_PATTERN =\n /^\\d{4}-\\d{2}-\\d{2}[ T]\\d{2}:\\d{2}(?::\\d{2}(?:\\.\\d+)?)?$/;\nconst OFFSET_DATETIME_PATTERN =\n /^(\\d{4}-\\d{2}-\\d{2})[ T](\\d{2}:\\d{2}(?::\\d{2}(?:\\.\\d+)?)?)(Z|[+-]\\d{2}(?::?\\d{2})?)$/i;\nconst DATE_ONLY_PATTERN = /^\\d{4}-\\d{2}-\\d{2}$/;\n\nfunction normalizedOffset(offset: string): string {\n if (offset.toUpperCase() === \"Z\") return \"Z\";\n if (/^[+-]\\d{2}$/.test(offset)) return `${offset}:00`;\n if (/^[+-]\\d{4}$/.test(offset)) {\n return `${offset.slice(0, 3)}:${offset.slice(3)}`;\n }\n return offset;\n}\n\nfunction databaseTimestampMilliseconds(value: string): number {\n if (ZONELESS_DATETIME_PATTERN.test(value)) {\n return Date.parse(`${value.replace(\" \", \"T\")}Z`);\n }\n const offsetMatch = OFFSET_DATETIME_PATTERN.exec(value);\n if (offsetMatch !== null) {\n const date = offsetMatch[1];\n const time = offsetMatch[2];\n const offset = offsetMatch[3];\n if (date === undefined || time === undefined || offset === undefined) {\n return Number.NaN;\n }\n return Date.parse(`${date}T${time}${normalizedOffset(offset)}`);\n }\n if (DATE_ONLY_PATTERN.test(value)) {\n return Date.parse(`${value}T00:00:00.000Z`);\n }\n return Number.NaN;\n}\n\n/**\n * Canonicalizes a database-driver timestamp without interpreting a zoneless\n * string in the host timezone. PostgreSQL drivers can return values such as\n * `2026-06-25 12:00:00`; TypeGraph treats that shape as UTC so the same stored\n * clock value cannot move across hosts or daylight-saving boundaries.\n *\n * Returns `undefined` for an unsupported or unrepresentable value so callers\n * can raise an error specific to their storage boundary.\n */\nexport function canonicalizeDatabaseTimestamp(\n value: unknown,\n): string | undefined {\n if (value instanceof Date) {\n if (Number.isNaN(value.getTime())) return undefined;\n return value.toISOString();\n }\n if (typeof value !== \"string\") return undefined;\n const milliseconds = databaseTimestampMilliseconds(value);\n if (Number.isNaN(milliseconds)) return undefined;\n return new Date(milliseconds).toISOString();\n}\n\n/**\n * Checks if a string is a canonical UTC ISO 8601 datetime with fixed\n * millisecond width (`YYYY-MM-DDTHH:mm:ss.sssZ`).\n */\nexport function isCanonicalIsoDate(value: string): boolean {\n if (!CANONICAL_ISO_DATE_PATTERN.test(value)) return false;\n const date = new Date(value);\n return !Number.isNaN(date.getTime()) && date.toISOString() === value;\n}\n\n/**\n * Validates that a string is a canonical UTC ISO 8601 datetime with fixed\n * millisecond width. Stricter than {@link validateIsoDate} (which also\n * accepts missing or 1–2 digit milliseconds): required wherever the value\n * is compared **as text** against stored timestamps and must sort\n * chronologically — e.g. a temporal `asOf` coordinate, where a\n * variable-width value would mis-order and silently include or exclude the\n * wrong rows. Produce one with `new Date(value).toISOString()`.\n *\n * @param value - String to validate\n * @param fieldName - Name of field for error message\n * @returns The validated string\n * @throws ValidationError if not a canonical UTC ISO datetime\n */\nexport function validateCanonicalIsoDate(\n value: string,\n fieldName: string,\n): string {\n if (!isCanonicalIsoDate(value)) {\n throw new ValidationError(\n `Invalid canonical ISO 8601 datetime for \"${fieldName}\": \"${value}\". ` +\n `Expected fixed-width UTC: YYYY-MM-DDTHH:mm:ss.sssZ`,\n {\n issues: [\n {\n path: fieldName,\n message: `Expected canonical UTC ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ), got: \"${value}\"`,\n },\n ],\n },\n {\n suggestion: `Use a fixed-width UTC datetime like \"2024-01-15T10:30:00.000Z\" (e.g. new Date(value).toISOString()).`,\n },\n );\n }\n return value;\n}\n\n/**\n * Validates an optional canonical ISO date string. Returns `undefined` for an\n * absent value; otherwise enforces canonical fixed-width UTC ISO 8601 via\n * {@link validateCanonicalIsoDate}. Used for stored validity-window inputs\n * (`validFrom` / `validTo`) so every timestamp the temporal filters compare as\n * text is canonical and sorts chronologically — the same contract the `asOf`\n * read coordinate already enforces.\n */\nexport function validateOptionalCanonicalIsoDate(\n value: string | undefined,\n fieldName: string,\n): string | undefined {\n if (value === undefined) return undefined;\n return validateCanonicalIsoDate(value, fieldName);\n}\n\n/**\n * Whether a CANONICAL stated bound names the same instant a row already stores.\n *\n * The one comparison of a caller's bound against a stored one, so the coalesce\n * dirty check (\"would this write move the window?\") and the write guard (\"can\n * this stated bound be applied?\") can never disagree about what \"the same bound\"\n * means. Two copies of it did disagree — one comparing driver text, one\n * comparing instants — which is how a re-stated window wrote on one backend and\n * coalesced on the other.\n *\n * Only the STORED side is canonicalized. It arrives as the driver rendered it,\n * and the dialects do not render a timestamp the same way (SQLite returns the\n * written text; a Postgres driver may hand back a zoned string equivalent to the\n * canonical form without being identical to it). The stated side needs no\n * canonicalization because every write path validates it as canonical first —\n * which is a PRECONDITION here, not an assumption: a non-canonical stated bound\n * would compare unequal to an equivalent stored instant and must be rejected by\n * {@link validateCanonicalIsoDate} rather than silently treated as a mismatch.\n */\nexport function statedBoundMatchesStored(\n stated: string,\n stored: string | undefined,\n): boolean {\n return stated === canonicalizeDatabaseTimestamp(stored);\n}\n\n/**\n * Whether a stated validity window has NEGATIVE width — a row that stopped\n * being true before it started. The one comparison every window refusal is\n * built on, so paths that must report the fault through their own error type\n * (trusted import) decide it identically to the ones that throw a\n * `ValidationError`.\n *\n * PRECONDITION: both endpoints are canonical fixed-width UTC ISO 8601, which is\n * what makes a lexicographic compare a chronological one. Every caller\n * establishes that before comparing, with no exception: the paths that throw a\n * `ValidationError` reach this through {@link validateOptionalCanonicalIsoDate},\n * and trusted import — which skips schema validation for throughput —\n * format-checks the stated window fields of every streamed row against\n * {@link isCanonicalIsoDate} before reaching here. A bound read back from a row\n * needs no check of its own: it is canonical because the write that stored it\n * was held to this same contract — SQLite hands that text back verbatim, and the\n * PostgreSQL row mapper normalizes the driver's shape back to it. So no caller\n * can compare a value that would mis-sort, here or later against an `asOf`\n * coordinate.\n *\n * ZERO width (`validFrom === validTo`) is not inverted: it is what a\n * same-instant retraction produces at millisecond precision, and the store's own\n * output must round-trip.\n */\nexport function isInvertedValidityWindow(\n validFrom: string | undefined,\n validTo: string | undefined,\n): boolean {\n if (validFrom === undefined || validTo === undefined) return false;\n return validFrom > validTo;\n}\n\n/**\n * Refuses a `validTo` whose EFFECTIVE lower bound would invert the window. On an\n * in-place update the row's stored lower bound is the effective one and must stay\n * <= the new `validTo`; on a resurrection that RESETS `valid_from` it is the\n * write instant, so a lone past `validTo` would be born inverted.\n *\n * A ZERO-width window (`validTo === effectiveValidFrom`) is legal and stays\n * legal: it is what a same-instant retraction produces at millisecond\n * precision, and the identity-import window check sanctions it for the same\n * reason. Only NEGATIVE width refuses.\n *\n * Reached through {@link assertWritableValidityWindow} rather than called\n * directly, so no writer can check the effective bound without also checking a\n * stated pair.\n */\nfunction assertEffectiveValidityLowerBound(\n subject: string,\n effectiveValidFrom: string | undefined,\n validTo: string | undefined,\n): void {\n if (!isInvertedValidityWindow(effectiveValidFrom, validTo)) return;\n throw new ValidationError(\n `Inverted validity window for ${subject}: the effective validFrom \"${effectiveValidFrom}\" is after validTo \"${validTo}\".`,\n {\n issues: [\n {\n path: \"validTo\",\n code: INVERTED_VALIDITY_WINDOW_CODE,\n message: `Expected the effective validFrom <= validTo, got \"${effectiveValidFrom}\" > \"${validTo}\". The effective validFrom is the row's stored lower bound, or the write instant where the write stamps one.`,\n },\n ],\n },\n {\n suggestion:\n \"Provide both validFrom and validTo for a historical window, or drop validTo to keep the row current.\",\n },\n );\n}\n\n/**\n * Rejects an inverted validity window. `validFrom > validTo` describes a row\n * that stopped being true before it started, so no current-mode or `asOf` read\n * can ever observe it — the write succeeds and the row is then reachable only\n * through `includeEnded`, which reads as data loss rather than as the error it\n * is. Call this wherever both endpoints are supplied together; both are\n * canonical ISO 8601 by then, so a lexicographic compare is a chronological\n * compare.\n *\n * @param subject - Human-readable identification of the row, for the message\n * (e.g. `Person \"01H...\"`).\n * @throws ValidationError if both endpoints are present and out of order\n */\nexport function assertOrderedValidityWindow(\n subject: string,\n validFrom: string | undefined,\n validTo: string | undefined,\n): void {\n if (!isInvertedValidityWindow(validFrom, validTo)) return;\n throw new ValidationError(\n `Inverted validity window for ${subject}: validFrom \"${validFrom}\" is after validTo \"${validTo}\".`,\n {\n issues: [\n {\n path: \"validFrom\",\n code: INVERTED_VALIDITY_WINDOW_CODE,\n message: `Expected validFrom <= validTo, got \"${validFrom}\" > \"${validTo}\"`,\n },\n ],\n },\n {\n suggestion:\n \"Pass a validFrom at or before validTo, or omit validTo to leave the window open.\",\n },\n );\n}\n\n/**\n * What an UPDATE will do with the row's validity lower bound — the two facts the\n * write guard needs, stated by the writer that knows them.\n *\n * Every update path must decide `appliesStatedValidFrom` explicitly, because the\n * answer is not a property of the guard's other arguments: the same\n * `(validFrom, storedBound, validTo)` triple is a legal window restatement on a\n * resurrection and an unappliable bound on an in-place update.\n */\nexport type UpdateValidityLowerBound = Readonly<{\n /**\n * The bound the row will hold when the caller states no `validFrom`: the\n * stored `valid_from` for an in-place update, the write instant for a\n * resurrection that stamps a new one, `undefined` where there is no bound to\n * invert against.\n */\n effectiveValidFrom: string | undefined;\n /**\n * Whether the write STORES a stated `validFrom`. False for an in-place update\n * of a live row — `buildUpdateNode` / `buildUpdateEdge` rewrite `valid_from`\n * only on a resurrection — which is what makes a differing stated bound a\n * refusal rather than a silent drop.\n */\n appliesStatedValidFrom: boolean;\n /**\n * Whether `effectiveValidFrom` IS the row's stored `valid_from` — the value an\n * `expectedValidFrom` predicate would name — rather than a bound this write is\n * about to stamp itself.\n *\n * A node resurrection resets `valid_from` to the write instant and passes THAT\n * as the effective bound, so its verdict never looked at what the row holds and\n * a fence on the stored value would predicate the write on something nothing\n * read. An edge retains its bound through a resurrection, so its effective\n * bound is the stored one on both legs.\n */\n effectiveBoundIsStored: boolean;\n}>;\n\n/**\n * The `expectedValidFrom` predicate a write owes its verdict, shaped for\n * spreading straight into the backend's update params.\n *\n * `{}` means \"assert nothing\"; `{ expectedValidFrom: null }` means \"assert the\n * row still has NO lower bound\". The two are different claims and the object\n * form keeps them distinguishable at every call site — see\n * `UpdateNodeParams.expectedValidFrom`.\n */\nexport type ValidityLowerBoundFence = Readonly<{\n expectedValidFrom?: string | null;\n}>;\n\n/**\n * What {@link assertWritableValidityWindow} reports back about the verdict it\n * just reached — not about the window, but about what the verdict DEPENDED ON.\n *\n * THE SINGLE OWNER of the question \"must this write assert the row's stored\n * lower bound?\", and it answers by handing over the predicate ITSELF rather than\n * a flag a caller then turns into one. A write asserts every component its\n * verdict read (see `UpdateNodeParams.expectedValidFrom`), and which components\n * those are is decided by which branches the guard actually took — so a caller\n * that could still spell the fence on its own would eventually spell a different\n * one. Interchange import did exactly that: it asserted the probed\n * `valid_from` unconditionally, over-fencing props-only updates the store paths\n * (correctly) left unfenced. There is now nothing to re-derive.\n *\n * Too FEW assertions is a silent race; too many is a refusal of writes that are\n * legitimate. Both failures come from a second spelling.\n */\nexport type ValidityWindowVerdict = Readonly<{\n /**\n * The fence this verdict obliges the write to carry. Non-empty when the\n * verdict consulted the row's STORED `valid_from` — either by comparing a\n * stated bound against it, or by inverting a lone `validTo` against it — and\n * empty when the caller named no window at all, when the write applies the\n * bound it stated, or when the effective bound was one this write will stamp\n * rather than one the row holds. In those cases the verdict is independent of\n * what the row carries, and predicating the write on it would refuse a\n * concurrent recreate that changed nothing this decision looked at.\n */\n storedLowerBoundFence: ValidityLowerBoundFence;\n}>;\n\n/**\n * Whether an upsert explicitly treats its stated lower bound as\n * create/resurrection-only input.\n *\n * This predicate owns the policy decision for both write execution and\n * unchanged-upsert coalescing, so those paths cannot drift on whether a live\n * row's stored lower bound is preserved.\n */\nexport function preservesImmutableLowerBound(\n policy: \"preserve\" | \"refuse\" | undefined,\n): policy is \"preserve\" {\n return policy === \"preserve\";\n}\n\n/**\n * Refuses a stated `validFrom` the write would not apply.\n *\n * A live row's lower bound is HISTORY: it records when the row started being\n * true, and an in-place update never moves it. Accepting a bound that names a\n * different instant and then dropping it is the API lying — the caller's stated\n * window silently is not the stored one, while the write still spends a version\n * bump and a history row moving nothing. Restating the bound the row already\n * holds stays legal, so a caller that echoes a row's own window back (a merge\n * commit, a re-delivered upsert) is unaffected.\n *\n * Reached through {@link assertWritableValidityWindow} rather than called\n * directly, so no writer can accept a lower bound without declaring whether it\n * applies one.\n */\nfunction assertStatedLowerBoundIsApplicable(\n subject: string,\n statedValidFrom: string,\n storedValidFrom: string | undefined,\n): void {\n if (statedBoundMatchesStored(statedValidFrom, storedValidFrom)) return;\n const storedDescription =\n storedValidFrom === undefined ?\n \"the row has no lower bound\"\n : `the row's stored lower bound is \"${storedValidFrom}\"`;\n throw new ValidationError(\n `Unappliable validFrom for ${subject}: stated \"${statedValidFrom}\", but ${storedDescription} and the row is live.`,\n {\n issues: [\n {\n path: \"validFrom\",\n code: IMMUTABLE_VALIDITY_LOWER_BOUND_CODE,\n message: `A live row's lower bound is history and an in-place update never rewrites it, so \"${statedValidFrom}\" could not be applied.`,\n },\n ],\n },\n {\n suggestion:\n storedValidFrom === undefined ?\n \"Omit validFrom: this row has no lower bound to restate, and only a resurrection can give it one.\"\n : `Restate the stored bound (\"${storedValidFrom}\") or omit validFrom.`,\n },\n );\n}\n\n/**\n * THE window-ordering guard a valid-time UPDATE goes through, identically for\n * nodes and edges. Refuses a window of negative width — a row that stopped\n * being true before it started — whether the caller states both endpoints or\n * only the new end. Also refuses a stated lower bound the write cannot apply,\n * so no update accepts a `validFrom` it will ignore.\n *\n * Three checks, because the failures deserve different messages: the caller who\n * supplied both endpoints out of order gets told about the pair, the caller who\n * stated a lower bound this write cannot store gets told which bound the row\n * holds, and the caller whose lone `validTo` inverts against the bound the row\n * will actually carry gets told which bound that is.\n *\n * The stated pair is judged FIRST — an inverted pair is wrong whatever the row\n * holds — and the applicability of the lower bound before the effective-bound\n * check, so a caller is told their bound will not be stored rather than being\n * told about a bound they did not name.\n *\n * INSERTS use {@link assertOrderedValidityWindow} alone. An insert carrying a\n * lone historical `validTo` means \"born already ended\", and the write instant\n * the backend stamps as `valid_from` is a storage convention rather than a\n * caller assertion — the row is deliberately not current and is read back\n * through `includeEnded`. Only an UPDATE has a lower bound the caller can be\n * held to.\n *\n * @param subject - Human-readable identification of the row, for the message\n * (e.g. `Person \"01H...\"`).\n * @param validFrom - The caller's explicit lower bound, if any.\n * @param lowerBound - What the write will do with the row's lower bound; see\n * {@link UpdateValidityLowerBound}.\n * @returns {@link ValidityWindowVerdict} — the `expectedValidFrom` fence this\n * verdict obliges the write to carry, empty when it read no stored bound.\n * @throws ValidationError with issue code {@link INVERTED_VALIDITY_WINDOW_CODE}\n * or {@link IMMUTABLE_VALIDITY_LOWER_BOUND_CODE}\n */\nexport function assertWritableValidityWindow(\n subject: string,\n validFrom: string | undefined,\n lowerBound: UpdateValidityLowerBound,\n validTo: string | undefined,\n): ValidityWindowVerdict {\n assertOrderedValidityWindow(subject, validFrom, validTo);\n\n // The stated bound is COMPARED against the stored one only here — a write\n // that applies what the caller stated has nothing to compare it to.\n const comparedStatedBound =\n validFrom !== undefined && !lowerBound.appliesStatedValidFrom;\n if (comparedStatedBound) {\n assertStatedLowerBoundIsApplicable(\n subject,\n validFrom,\n lowerBound.effectiveValidFrom,\n );\n }\n\n assertEffectiveValidityLowerBound(\n subject,\n validFrom ?? lowerBound.effectiveValidFrom,\n validTo,\n );\n // ...and the inversion check falls back to the row's bound only when the\n // caller named an end without a start. Note this is true whether or not the\n // row HAS a bound: \"no lower bound to invert against\" is itself a reading of\n // the row, and a recreate that gives the row one changes the answer.\n const comparedStoredBoundAgainstEnd =\n validFrom === undefined && validTo !== undefined;\n\n const readEffectiveLowerBound =\n comparedStatedBound || comparedStoredBoundAgainstEnd;\n return {\n storedLowerBoundFence:\n readEffectiveLowerBound && lowerBound.effectiveBoundIsStored ?\n // eslint-disable-next-line unicorn/no-null -- `expectedValidFrom` distinguishes \"assert IS NULL\" (null) from \"assert nothing\" (an absent key); see UpdateNodeParams.\n { expectedValidFrom: lowerBound.effectiveValidFrom ?? null }\n : {},\n };\n}\n\n/**\n * Encodes a Date to an ISO 8601 string for storage.\n */\nexport function encodeDate(date: Date): string {\n return date.toISOString();\n}\n\n/**\n * Decodes an ISO 8601 string to a Date.\n * Validates the string format first.\n *\n * @throws ValidationError if not a valid ISO datetime\n */\nexport function decodeDate(isoString: string): Date {\n validateIsoDate(isoString, \"date\");\n return new Date(isoString);\n}\n\n/**\n * Returns the current timestamp as an ISO string.\n */\nexport function nowIso(): string {\n return new Date().toISOString();\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/backend/postgres/pglite.ts"],"names":["ConfigurationError","tables","PGlite","generatePostgresMigrationSQL","generatePostgresDDL","drizzle","createPostgresBackend","wrapWithManagedClose","closeAfterFailure"],"mappings":";;;;;;;;;AAwIA,eAAe,4BAAA,GAAmD;AAChE,EAAA,IAAI;AACF,IAAA,MAAM,OAAA,GAAU,MAAM,OAAO,+BAA+B,CAAA;AAC5D,IAAA,OAAO,OAAA,CAAQ,MAAA;AAAA,EACjB,SAAS,KAAA,EAAO;AACd,IAAA,MAAM,IAAIA,oCAAA;AAAA,MACR,2PAAA;AAAA,MAIA,EAAE,OAAA,EAAS,QAAA,EAAU,UAAA,EAAY,+BAAA,EAAgC;AAAA,MACjE,EAAE,OAAO,KAAA;AAAM,KACjB;AAAA,EACF;AACF;AAoBA,eAAsB,wBAAA,CACpB,OAAA,GAAqC,EAAC,EACH;AACnC,EAAA,MAAMC,OAAAA,GAAS,QAAQ,MAAA,IAAUA,wBAAA;AAMjC,EAAA,MAAM,eAAA,GACJ,QAAQ,MAAA,KAAW,KAAA,GACjB,SACC,OAAA,CAAQ,MAAA,IAAW,MAAM,4BAAA,EAA6B;AAC3D,EAAA,MAAM,gBAAgB,eAAA,KAAoB,MAAA;AAO1C,EAAA,MAAM,MAAA,GAAS,MAAMC,aAAA,CAAO,MAAA,CAAO;AAAA,IACjC,GAAI,QAAQ,OAAA,KAAY,MAAA,GAAY,EAAC,GAAI,EAAE,OAAA,EAAS,OAAA,CAAQ,OAAA,EAAQ;AAAA,IACpE,GAAI,eAAA,KAAoB,MAAA,GACtB,EAAC,GACD,EAAE,UAAA,EAAY,EAAE,MAAA,EAAQ,eAAA,EAAgB;AAAE,GAC7C,CAAA;AAED,EAAA,IAAI;AAGF,IAAA,MAAM,YAAA,GACJ,gBACEC,8CAAA,CAA6BF,OAAM,IACnCG,qCAAA,CAAoBH,OAAM,CAAA,CAAE,IAAA,CAAK,MAAM,CAAA;AAC3C,IAAA,MAAM,MAAA,CAAO,KAAK,YAAY,CAAA;AAE9B,IAAA,MAAM,EAAA,GAAKI,iBAAQ,MAAM,CAAA;AACzB,IAAA,MAAM,OAAA,GAAUC,wCAAsB,EAAA,EAAI;AAAA,MACxC,MAAA,EAAAL,OAAAA;AAAA,MACA,GAAI,aAAA,GAAgB,EAAC,GAAI,EAAE,QAAQ,KAAA;AAAM,KAC1C,CAAA;AACD,IAAA,MAAM,cAAA,GAAiBM,sCAAA,CAAqB,OAAA,EAAS,YAAY;AAC/D,MAAA,MAAM,OAAO,KAAA,EAAM;AAAA,IACrB,CAAC,CAAA;AAED,IAAA,OAAO,EAAE,OAAA,EAAS,cAAA,EAAgB,EAAA,EAAI,MAAA,EAAO;AAAA,EAC/C,SAAS,KAAA,EAAO;AACd,IAAA,OAAOC,mCAAA,CAAkB,QAAQ,KAAK,CAAA;AAAA,EACxC;AACF","file":"chunk-5FBRVORR.cjs","sourcesContent":["/**\n * Local PGlite backend — Postgres-in-WASM, running in-process.\n *\n * [PGlite](https://pglite.dev/) is a full Postgres compiled to WebAssembly\n * that runs inside the Node/Bun/Deno/browser process with no server and no\n * native addon. This is the batteries-included Postgres analog of\n * `createLocalSqliteBackend`: it constructs a PGlite instance, loads the\n * pgvector extension, runs the schema DDL, and returns a ready\n * `{ backend, db }` pair whose `close()` disposes the engine.\n *\n * `@electric-sql/pglite` is an optional peer dependency. Vector support\n * additionally needs `@electric-sql/pglite-pgvector` (PGlite ≥ 0.5 ships\n * pgvector as a separate package). Install what you need:\n *\n * ```bash\n * pnpm add @electric-sql/pglite @electric-sql/pglite-pgvector\n * ```\n *\n * @example In-memory database (default, vector enabled)\n * ```typescript\n * import { createLocalPgliteBackend } from \"@nicia-ai/typegraph/adapters/drizzle/postgres/pglite\";\n *\n * const { backend } = await createLocalPgliteBackend();\n * const store = createStore(graph, backend);\n * ```\n *\n * @example Persistent on-disk database\n * ```typescript\n * const { backend, db } = await createLocalPgliteBackend({ dataDir: \"./pgdata\" });\n * ```\n *\n * @example No vector support (skip the pgvector extension)\n * ```typescript\n * const { backend } = await createLocalPgliteBackend({ vector: false });\n * ```\n *\n * PGlite is single-connection and serial: there is no pooling, so concurrent\n * `store.transaction()` calls queue rather than run in parallel.\n */\nimport { type Extension, PGlite } from \"@electric-sql/pglite\";\nimport { drizzle, type PgliteDatabase } from \"drizzle-orm/pglite\";\n\nimport { ConfigurationError } from \"../../errors\";\nexport type { GraphIdentityConfig } from \"../../core/define-graph\";\nimport {\n generatePostgresDDL,\n generatePostgresMigrationSQL,\n} from \"../drizzle/ddl\";\nimport { type AnyPgTransaction } from \"../drizzle/execution\";\nexport type { AnyPgDatabase, AnyPgTransaction } from \"../drizzle/execution\";\nimport {\n createPostgresBackend,\n type PostgresTables,\n tables as defaultTables,\n} from \"../drizzle/postgres\";\nimport {\n type AdapterBackend,\n closeAfterFailure,\n wrapWithManagedClose,\n} from \"../types\";\nexport type {\n ContributionDiagnostic,\n ContributionDiagnosticState,\n ContributionRepairEntry,\n ContributionRepairResult,\n} from \"../types\";\n\n// ============================================================\n// Types\n// ============================================================\n\n/**\n * Options for creating a local PGlite backend.\n */\nexport type LocalPgliteBackendOptions = Readonly<{\n /**\n * PGlite data directory. Omit for an in-memory database (the default).\n * Accepts any PGlite data-dir spec: a filesystem path (`\"./pgdata\"`),\n * `\"memory://\"`, or a browser/runtime-specific scheme such as\n * `\"idb://my-db\"`.\n */\n dataDir?: string;\n\n /**\n * Custom table definitions. Defaults to standard TypeGraph table names.\n */\n tables?: PostgresTables;\n\n /**\n * Controls the pgvector stack:\n *\n * - **omitted (default)** — load pgvector from `@electric-sql/pglite-pgvector`\n * and run `CREATE EXTENSION vector`, so embedding fields work out of the\n * box. Throws a {@link ConfigurationError} if the package isn't installed.\n * - **`false`** — skip the extension entirely; the backend advertises no\n * vector capability (mirroring a SQLite connection without sqlite-vec).\n * Use when a graph carries no embeddings, or to avoid the extension dep.\n * - **a PGlite `Extension`** — bring your own pgvector build or custom\n * extension packaging. Decouples this helper from a specific pgvector\n * package version.\n */\n vector?: false | Extension;\n}>;\n\n/**\n * Result of creating a local PGlite backend.\n */\nexport type LocalPgliteBackendResult = Readonly<{\n /**\n * The GraphBackend instance for use with createStore. Its `close()`\n * disposes the underlying PGlite engine.\n */\n backend: AdapterBackend<AnyPgTransaction>;\n\n /**\n * The underlying Drizzle database instance. Useful for direct SQL access.\n */\n db: PgliteDatabase;\n\n /**\n * The raw PGlite instance, for engine-specific operations such as\n * `dumpDataDir()` / `clone()` not surfaced through Drizzle.\n */\n client: PGlite;\n}>;\n\n// ============================================================\n// Vector extension loading\n// ============================================================\n\n/**\n * Dynamically loads the default pgvector extension from\n * `@electric-sql/pglite-pgvector`. Kept as a runtime import so the package\n * stays an optional peer dependency — callers using `vector: false` or\n * supplying their own extension never need it installed.\n */\nasync function loadDefaultPgvectorExtension(): Promise<Extension> {\n try {\n const module_ = await import(\"@electric-sql/pglite-pgvector\");\n return module_.vector;\n } catch (error) {\n throw new ConfigurationError(\n \"createLocalPgliteBackend needs the pgvector extension but \" +\n \"`@electric-sql/pglite-pgvector` could not be loaded. Install it \" +\n \"(`pnpm add @electric-sql/pglite-pgvector`), pass your own `vector` \" +\n \"extension, or set `vector: false` to disable vector support.\",\n { backend: \"pglite\", dependency: \"@electric-sql/pglite-pgvector\" },\n { cause: error },\n );\n }\n}\n\n// ============================================================\n// Factory Function\n// ============================================================\n\n/**\n * Creates a TypeGraph backend backed by an in-process PGlite database.\n *\n * Constructs the PGlite engine (loading pgvector unless disabled), executes\n * the schema DDL, and wires the standard Postgres backend. The returned\n * backend owns the engine: call `backend.close()` to dispose it.\n *\n * For production Postgres, or a PGlite instance you construct yourself,\n * use `createPostgresBackend` directly with your own Drizzle database — the\n * execution fast path detects PGlite and routes it correctly.\n *\n * @param options - Configuration options\n * @returns Backend, Drizzle database, and raw PGlite client\n */\nexport async function createLocalPgliteBackend(\n options: LocalPgliteBackendOptions = {},\n): Promise<LocalPgliteBackendResult> {\n const tables = options.tables ?? defaultTables;\n\n // pgvector lives in the WASM instance, so the extension must be passed at\n // construction — `CREATE EXTENSION vector` alone can't pull it in. An\n // explicitly supplied extension wins; `false` disables; otherwise load the\n // default package.\n const vectorExtension =\n options.vector === false ?\n undefined\n : (options.vector ?? (await loadDefaultPgvectorExtension()));\n const vectorEnabled = vectorExtension !== undefined;\n\n // Construct via the single options-object form (passing `dataDir` as a\n // field), not the positional `create(dataDir, options)` overload: with a\n // leading `undefined` dataDir, that overload silently drops `extensions`\n // (\"extension vector is not available\"). The options form loads them for\n // both the in-memory and on-disk cases.\n const client = await PGlite.create({\n ...(options.dataDir === undefined ? {} : { dataDir: options.dataDir }),\n ...(vectorExtension === undefined ?\n {}\n : { extensions: { vector: vectorExtension } }),\n });\n\n try {\n // `exec` runs a multi-statement batch (DDL + the pgvector `CREATE\n // EXTENSION` when enabled) in one round trip.\n const migrationSql =\n vectorEnabled ?\n generatePostgresMigrationSQL(tables)\n : generatePostgresDDL(tables).join(\"\\n\\n\");\n await client.exec(migrationSql);\n\n const db = drizzle(client);\n const backend = createPostgresBackend(db, {\n tables,\n ...(vectorEnabled ? {} : { vector: false }),\n });\n const managedBackend = wrapWithManagedClose(backend, async () => {\n await client.close();\n });\n\n return { backend: managedBackend, db, client };\n } catch (error) {\n return closeAfterFailure(client, error);\n }\n}\n"]}