@fragno-dev/db 0.2.2 → 0.3.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 (355) hide show
  1. package/.turbo/turbo-build.log +202 -140
  2. package/CHANGELOG.md +35 -0
  3. package/README.md +30 -9
  4. package/dist/adapters/adapters.d.ts +23 -21
  5. package/dist/adapters/adapters.d.ts.map +1 -1
  6. package/dist/adapters/adapters.js.map +1 -1
  7. package/dist/adapters/generic-sql/driver-config.d.ts +16 -1
  8. package/dist/adapters/generic-sql/driver-config.d.ts.map +1 -1
  9. package/dist/adapters/generic-sql/driver-config.js +23 -1
  10. package/dist/adapters/generic-sql/driver-config.js.map +1 -1
  11. package/dist/adapters/generic-sql/generic-sql-adapter.d.ts +27 -9
  12. package/dist/adapters/generic-sql/generic-sql-adapter.d.ts.map +1 -1
  13. package/dist/adapters/generic-sql/generic-sql-adapter.js +55 -16
  14. package/dist/adapters/generic-sql/generic-sql-adapter.js.map +1 -1
  15. package/dist/adapters/generic-sql/generic-sql-uow-executor.js +129 -3
  16. package/dist/adapters/generic-sql/generic-sql-uow-executor.js.map +1 -1
  17. package/dist/adapters/generic-sql/migration/dialect/mysql.js +24 -5
  18. package/dist/adapters/generic-sql/migration/dialect/mysql.js.map +1 -1
  19. package/dist/adapters/generic-sql/migration/dialect/postgres.js +6 -5
  20. package/dist/adapters/generic-sql/migration/dialect/postgres.js.map +1 -1
  21. package/dist/adapters/generic-sql/migration/dialect/sqlite.js +21 -10
  22. package/dist/adapters/generic-sql/migration/dialect/sqlite.js.map +1 -1
  23. package/dist/adapters/generic-sql/migration/prepared-migrations.d.ts.map +1 -1
  24. package/dist/adapters/generic-sql/migration/prepared-migrations.js +8 -8
  25. package/dist/adapters/generic-sql/migration/prepared-migrations.js.map +1 -1
  26. package/dist/adapters/generic-sql/migration/sql-generator.js +74 -51
  27. package/dist/adapters/generic-sql/migration/sql-generator.js.map +1 -1
  28. package/dist/adapters/generic-sql/query/create-sql-query-compiler.js +6 -5
  29. package/dist/adapters/generic-sql/query/create-sql-query-compiler.js.map +1 -1
  30. package/dist/adapters/generic-sql/query/cursor-utils.js +42 -4
  31. package/dist/adapters/generic-sql/query/cursor-utils.js.map +1 -1
  32. package/dist/adapters/generic-sql/query/generic-sql-uow-operation-compiler.js +25 -17
  33. package/dist/adapters/generic-sql/query/generic-sql-uow-operation-compiler.js.map +1 -1
  34. package/dist/adapters/generic-sql/query/select-builder.js +5 -3
  35. package/dist/adapters/generic-sql/query/select-builder.js.map +1 -1
  36. package/dist/adapters/generic-sql/query/sql-query-compiler.js +15 -12
  37. package/dist/adapters/generic-sql/query/sql-query-compiler.js.map +1 -1
  38. package/dist/adapters/generic-sql/query/where-builder.js +39 -29
  39. package/dist/adapters/generic-sql/query/where-builder.js.map +1 -1
  40. package/dist/adapters/generic-sql/sqlite-storage.d.ts +13 -0
  41. package/dist/adapters/generic-sql/sqlite-storage.d.ts.map +1 -0
  42. package/dist/adapters/generic-sql/sqlite-storage.js +15 -0
  43. package/dist/adapters/generic-sql/sqlite-storage.js.map +1 -0
  44. package/dist/adapters/generic-sql/uow-decoder.js +7 -3
  45. package/dist/adapters/generic-sql/uow-decoder.js.map +1 -1
  46. package/dist/adapters/generic-sql/uow-encoder.js +28 -8
  47. package/dist/adapters/generic-sql/uow-encoder.js.map +1 -1
  48. package/dist/adapters/in-memory/condition-evaluator.js +131 -0
  49. package/dist/adapters/in-memory/condition-evaluator.js.map +1 -0
  50. package/dist/adapters/in-memory/errors.d.ts +13 -0
  51. package/dist/adapters/in-memory/errors.d.ts.map +1 -0
  52. package/dist/adapters/in-memory/errors.js +23 -0
  53. package/dist/adapters/in-memory/errors.js.map +1 -0
  54. package/dist/adapters/in-memory/in-memory-adapter.d.ts +27 -0
  55. package/dist/adapters/in-memory/in-memory-adapter.d.ts.map +1 -0
  56. package/dist/adapters/in-memory/in-memory-adapter.js +176 -0
  57. package/dist/adapters/in-memory/in-memory-adapter.js.map +1 -0
  58. package/dist/adapters/in-memory/in-memory-uow.js +648 -0
  59. package/dist/adapters/in-memory/in-memory-uow.js.map +1 -0
  60. package/dist/adapters/in-memory/index.d.ts +4 -0
  61. package/dist/adapters/in-memory/index.js +4 -0
  62. package/dist/adapters/in-memory/options.d.ts +28 -0
  63. package/dist/adapters/in-memory/options.d.ts.map +1 -0
  64. package/dist/adapters/in-memory/options.js +61 -0
  65. package/dist/adapters/in-memory/options.js.map +1 -0
  66. package/dist/adapters/in-memory/reference-resolution.js +26 -0
  67. package/dist/adapters/in-memory/reference-resolution.js.map +1 -0
  68. package/dist/adapters/in-memory/sorted-array-index.js +129 -0
  69. package/dist/adapters/in-memory/sorted-array-index.js.map +1 -0
  70. package/dist/adapters/in-memory/store.js +71 -0
  71. package/dist/adapters/in-memory/store.js.map +1 -0
  72. package/dist/adapters/in-memory/value-comparison.js +28 -0
  73. package/dist/adapters/in-memory/value-comparison.js.map +1 -0
  74. package/dist/adapters/shared/from-unit-of-work-compiler.js.map +1 -1
  75. package/dist/adapters/shared/uow-operation-compiler.js +11 -11
  76. package/dist/adapters/shared/uow-operation-compiler.js.map +1 -1
  77. package/dist/adapters/sql/index.d.ts +5 -0
  78. package/dist/adapters/sql/index.js +4 -0
  79. package/dist/db-fragment-definition-builder.d.ts +18 -7
  80. package/dist/db-fragment-definition-builder.d.ts.map +1 -1
  81. package/dist/db-fragment-definition-builder.js +116 -54
  82. package/dist/db-fragment-definition-builder.js.map +1 -1
  83. package/dist/dispatchers/cloudflare-do/index.d.ts +26 -0
  84. package/dist/dispatchers/cloudflare-do/index.d.ts.map +1 -0
  85. package/dist/dispatchers/cloudflare-do/index.js +63 -0
  86. package/dist/dispatchers/cloudflare-do/index.js.map +1 -0
  87. package/dist/dispatchers/node/index.d.ts +17 -0
  88. package/dist/dispatchers/node/index.d.ts.map +1 -0
  89. package/dist/dispatchers/node/index.js +59 -0
  90. package/dist/dispatchers/node/index.js.map +1 -0
  91. package/dist/fragments/internal-fragment.d.ts +79 -2
  92. package/dist/fragments/internal-fragment.d.ts.map +1 -1
  93. package/dist/fragments/internal-fragment.js +150 -32
  94. package/dist/fragments/internal-fragment.js.map +1 -1
  95. package/dist/fragments/internal-fragment.routes.js +29 -0
  96. package/dist/fragments/internal-fragment.routes.js.map +1 -0
  97. package/dist/fragments/internal-fragment.schema.d.ts +9 -0
  98. package/dist/fragments/internal-fragment.schema.d.ts.map +1 -0
  99. package/dist/fragments/internal-fragment.schema.js +22 -0
  100. package/dist/fragments/internal-fragment.schema.js.map +1 -0
  101. package/dist/hooks/durable-hooks-processor.d.ts +14 -0
  102. package/dist/hooks/durable-hooks-processor.d.ts.map +1 -0
  103. package/dist/hooks/durable-hooks-processor.js +32 -0
  104. package/dist/hooks/durable-hooks-processor.js.map +1 -0
  105. package/dist/hooks/hooks.d.ts +42 -1
  106. package/dist/hooks/hooks.d.ts.map +1 -1
  107. package/dist/hooks/hooks.js +72 -6
  108. package/dist/hooks/hooks.js.map +1 -1
  109. package/dist/migration-engine/auto-from-schema.js +14 -11
  110. package/dist/migration-engine/auto-from-schema.js.map +1 -1
  111. package/dist/migration-engine/generation-engine.d.ts +16 -10
  112. package/dist/migration-engine/generation-engine.d.ts.map +1 -1
  113. package/dist/migration-engine/generation-engine.js +72 -33
  114. package/dist/migration-engine/generation-engine.js.map +1 -1
  115. package/dist/migration-engine/shared.js.map +1 -1
  116. package/dist/mod.d.ts +15 -8
  117. package/dist/mod.d.ts.map +1 -1
  118. package/dist/mod.js +14 -8
  119. package/dist/mod.js.map +1 -1
  120. package/dist/naming/sql-naming.d.ts +19 -0
  121. package/dist/naming/sql-naming.d.ts.map +1 -0
  122. package/dist/naming/sql-naming.js +116 -0
  123. package/dist/naming/sql-naming.js.map +1 -0
  124. package/dist/node_modules/.pnpm/{rou3@0.7.10 → rou3@0.7.12}/node_modules/rou3/dist/index.js +8 -5
  125. package/dist/node_modules/.pnpm/rou3@0.7.12/node_modules/rou3/dist/index.js.map +1 -0
  126. package/dist/outbox/outbox-builder.js +156 -0
  127. package/dist/outbox/outbox-builder.js.map +1 -0
  128. package/dist/outbox/outbox.d.ts +52 -0
  129. package/dist/outbox/outbox.d.ts.map +1 -0
  130. package/dist/outbox/outbox.js +37 -0
  131. package/dist/outbox/outbox.js.map +1 -0
  132. package/dist/packages/fragno/dist/api/fragment-definition-builder.js +3 -2
  133. package/dist/packages/fragno/dist/api/fragment-definition-builder.js.map +1 -1
  134. package/dist/packages/fragno/dist/api/fragment-instantiator.js +164 -20
  135. package/dist/packages/fragno/dist/api/fragment-instantiator.js.map +1 -1
  136. package/dist/packages/fragno/dist/api/request-input-context.js +67 -0
  137. package/dist/packages/fragno/dist/api/request-input-context.js.map +1 -1
  138. package/dist/packages/fragno/dist/api/route.js +14 -1
  139. package/dist/packages/fragno/dist/api/route.js.map +1 -1
  140. package/dist/packages/fragno/dist/internal/trace-context.js +12 -0
  141. package/dist/packages/fragno/dist/internal/trace-context.js.map +1 -0
  142. package/dist/query/column-defaults.js +20 -4
  143. package/dist/query/column-defaults.js.map +1 -1
  144. package/dist/query/cursor.d.ts +3 -1
  145. package/dist/query/cursor.d.ts.map +1 -1
  146. package/dist/query/cursor.js +45 -14
  147. package/dist/query/cursor.js.map +1 -1
  148. package/dist/query/db-now.d.ts +8 -0
  149. package/dist/query/db-now.d.ts.map +1 -0
  150. package/dist/query/db-now.js +7 -0
  151. package/dist/query/db-now.js.map +1 -0
  152. package/dist/query/serialize/create-sql-serializer.js +3 -2
  153. package/dist/query/serialize/create-sql-serializer.js.map +1 -1
  154. package/dist/query/serialize/dialect/mysql-serializer.js +12 -6
  155. package/dist/query/serialize/dialect/mysql-serializer.js.map +1 -1
  156. package/dist/query/serialize/dialect/postgres-serializer.js +25 -7
  157. package/dist/query/serialize/dialect/postgres-serializer.js.map +1 -1
  158. package/dist/query/serialize/dialect/sqlite-serializer.js +55 -11
  159. package/dist/query/serialize/dialect/sqlite-serializer.js.map +1 -1
  160. package/dist/query/serialize/sql-serializer.js +2 -2
  161. package/dist/query/serialize/sql-serializer.js.map +1 -1
  162. package/dist/query/simple-query-interface.d.ts +6 -1
  163. package/dist/query/simple-query-interface.d.ts.map +1 -1
  164. package/dist/query/unit-of-work/execute-unit-of-work.d.ts.map +1 -1
  165. package/dist/query/unit-of-work/execute-unit-of-work.js +11 -6
  166. package/dist/query/unit-of-work/execute-unit-of-work.js.map +1 -1
  167. package/dist/query/unit-of-work/unit-of-work.d.ts +50 -14
  168. package/dist/query/unit-of-work/unit-of-work.d.ts.map +1 -1
  169. package/dist/query/unit-of-work/unit-of-work.js +86 -5
  170. package/dist/query/unit-of-work/unit-of-work.js.map +1 -1
  171. package/dist/query/value-decoding.js +9 -6
  172. package/dist/query/value-decoding.js.map +1 -1
  173. package/dist/query/value-encoding.js +29 -9
  174. package/dist/query/value-encoding.js.map +1 -1
  175. package/dist/schema/create.d.ts +38 -14
  176. package/dist/schema/create.d.ts.map +1 -1
  177. package/dist/schema/create.js +81 -42
  178. package/dist/schema/create.js.map +1 -1
  179. package/dist/schema/generate-id.js +2 -2
  180. package/dist/schema/generate-id.js.map +1 -1
  181. package/dist/schema/type-conversion/create-sql-type-mapper.js +3 -2
  182. package/dist/schema/type-conversion/create-sql-type-mapper.js.map +1 -1
  183. package/dist/schema/type-conversion/dialect/sqlite.js +9 -0
  184. package/dist/schema/type-conversion/dialect/sqlite.js.map +1 -1
  185. package/dist/schema/validator.d.ts +10 -0
  186. package/dist/schema/validator.d.ts.map +1 -0
  187. package/dist/schema/validator.js +123 -0
  188. package/dist/schema/validator.js.map +1 -0
  189. package/dist/schema-output/drizzle.d.ts +30 -0
  190. package/dist/schema-output/drizzle.d.ts.map +1 -0
  191. package/dist/{adapters/drizzle/generate.js → schema-output/drizzle.js} +82 -56
  192. package/dist/schema-output/drizzle.js.map +1 -0
  193. package/dist/schema-output/prisma.d.ts +17 -0
  194. package/dist/schema-output/prisma.d.ts.map +1 -0
  195. package/dist/schema-output/prisma.js +296 -0
  196. package/dist/schema-output/prisma.js.map +1 -0
  197. package/dist/util/default-database-adapter.js +61 -0
  198. package/dist/util/default-database-adapter.js.map +1 -0
  199. package/dist/with-database.d.ts +1 -1
  200. package/dist/with-database.d.ts.map +1 -1
  201. package/dist/with-database.js +12 -3
  202. package/dist/with-database.js.map +1 -1
  203. package/package.json +43 -28
  204. package/src/adapters/adapters.ts +30 -24
  205. package/src/adapters/drizzle/migrate-drizzle.test.ts +54 -33
  206. package/src/adapters/drizzle/migration-parity-drizzle-kit.test.ts +599 -0
  207. package/src/adapters/drizzle/test-utils.ts +12 -8
  208. package/src/adapters/generic-sql/driver-config.ts +38 -0
  209. package/src/adapters/generic-sql/generic-sql-adapter.test.ts +5 -5
  210. package/src/adapters/generic-sql/generic-sql-adapter.ts +110 -24
  211. package/src/adapters/generic-sql/generic-sql-uow-executor.test.ts +54 -0
  212. package/src/adapters/generic-sql/generic-sql-uow-executor.ts +231 -3
  213. package/src/adapters/generic-sql/migration/adapter-migration-parity.test.ts +118 -0
  214. package/src/adapters/generic-sql/migration/dialect/mysql.test.ts +26 -8
  215. package/src/adapters/generic-sql/migration/dialect/mysql.ts +46 -8
  216. package/src/adapters/generic-sql/migration/dialect/postgres.test.ts +25 -7
  217. package/src/adapters/generic-sql/migration/dialect/postgres.ts +8 -4
  218. package/src/adapters/generic-sql/migration/dialect/sqlite.test.ts +47 -8
  219. package/src/adapters/generic-sql/migration/dialect/sqlite.ts +27 -12
  220. package/src/adapters/generic-sql/migration/prepared-migrations.test.ts +128 -39
  221. package/src/adapters/generic-sql/migration/prepared-migrations.ts +15 -8
  222. package/src/adapters/generic-sql/migration/sql-generator.ts +142 -65
  223. package/src/adapters/generic-sql/query/create-sql-query-compiler.ts +9 -6
  224. package/src/adapters/generic-sql/query/cursor-utils.test.ts +271 -0
  225. package/src/adapters/generic-sql/query/cursor-utils.ts +41 -6
  226. package/src/adapters/generic-sql/query/generic-sql-uow-operation-compiler.test.ts +27 -27
  227. package/src/adapters/generic-sql/query/generic-sql-uow-operation-compiler.ts +38 -24
  228. package/src/adapters/generic-sql/query/select-builder.test.ts +15 -11
  229. package/src/adapters/generic-sql/query/select-builder.ts +6 -2
  230. package/src/adapters/generic-sql/query/sql-query-compiler.test.ts +52 -2
  231. package/src/adapters/generic-sql/query/sql-query-compiler.ts +50 -15
  232. package/src/adapters/generic-sql/query/where-builder.test.ts +91 -17
  233. package/src/adapters/generic-sql/query/where-builder.ts +90 -38
  234. package/src/adapters/{kysely/kysely-adapter-pglite.test.ts → generic-sql/sql-adapter-pglite-migrations.test.ts} +6 -6
  235. package/src/adapters/generic-sql/sql-adapter-pglite-pagination.test.ts +806 -0
  236. package/src/adapters/{drizzle/drizzle-adapter-pglite.test.ts → generic-sql/sql-adapter-pglite-queries.test.ts} +11 -11
  237. package/src/adapters/generic-sql/{test/generic-drizzle-adapter-sqlite3.test.ts → sql-adapter-sqlite3-driver.test.ts} +10 -10
  238. package/src/adapters/{drizzle/drizzle-adapter-sqlite3.test.ts → generic-sql/sql-adapter-sqlite3-uow.test.ts} +7 -7
  239. package/src/adapters/{kysely/kysely-adapter-sqlocal.test.ts → generic-sql/sql-adapter-sqlocal.test.ts} +6 -6
  240. package/src/adapters/generic-sql/sqlite-storage.ts +20 -0
  241. package/src/adapters/generic-sql/uow-decoder.test.ts +1 -1
  242. package/src/adapters/generic-sql/uow-decoder.ts +21 -3
  243. package/src/adapters/generic-sql/uow-encoder.test.ts +33 -2
  244. package/src/adapters/generic-sql/uow-encoder.ts +50 -11
  245. package/src/adapters/in-memory/condition-evaluator.test.ts +193 -0
  246. package/src/adapters/in-memory/condition-evaluator.ts +275 -0
  247. package/src/adapters/in-memory/errors.ts +20 -0
  248. package/src/adapters/in-memory/in-memory-adapter.ts +277 -0
  249. package/src/adapters/in-memory/in-memory-uow.mutations.test.ts +296 -0
  250. package/src/adapters/in-memory/in-memory-uow.retrieval.test.ts +100 -0
  251. package/src/adapters/in-memory/in-memory-uow.ts +1348 -0
  252. package/src/adapters/in-memory/index.ts +3 -0
  253. package/src/adapters/in-memory/options.test.ts +41 -0
  254. package/src/adapters/in-memory/options.ts +87 -0
  255. package/src/adapters/in-memory/reference-resolution.test.ts +50 -0
  256. package/src/adapters/in-memory/reference-resolution.ts +67 -0
  257. package/src/adapters/in-memory/sorted-array-index.test.ts +123 -0
  258. package/src/adapters/in-memory/sorted-array-index.ts +228 -0
  259. package/src/adapters/in-memory/store.test.ts +68 -0
  260. package/src/adapters/in-memory/store.ts +145 -0
  261. package/src/adapters/in-memory/value-comparison.ts +53 -0
  262. package/src/adapters/in-memory/value-normalization.test.ts +57 -0
  263. package/src/adapters/prisma/prisma-adapter-sqlite3.test.ts +1163 -0
  264. package/src/adapters/shared/from-unit-of-work-compiler.ts +3 -1
  265. package/src/adapters/shared/uow-operation-compiler.ts +26 -16
  266. package/src/adapters/sql/index.ts +12 -0
  267. package/src/db-fragment-definition-builder.test.ts +30 -12
  268. package/src/db-fragment-definition-builder.ts +142 -73
  269. package/src/db-fragment-instantiator.test.ts +105 -13
  270. package/src/db-fragment-integration.test.ts +9 -7
  271. package/src/dispatchers/cloudflare-do/index.test.ts +73 -0
  272. package/src/dispatchers/cloudflare-do/index.ts +104 -0
  273. package/src/dispatchers/node/index.test.ts +91 -0
  274. package/src/dispatchers/node/index.ts +87 -0
  275. package/src/fragments/internal-fragment.routes.ts +42 -0
  276. package/src/fragments/internal-fragment.schema.ts +51 -0
  277. package/src/fragments/internal-fragment.test.ts +458 -8
  278. package/src/fragments/internal-fragment.ts +322 -63
  279. package/src/hooks/durable-hooks-processor.test.ts +117 -0
  280. package/src/hooks/durable-hooks-processor.ts +67 -0
  281. package/src/hooks/hooks.test.ts +165 -5
  282. package/src/hooks/hooks.ts +197 -9
  283. package/src/migration-engine/auto-from-schema.test.ts +14 -14
  284. package/src/migration-engine/auto-from-schema.ts +5 -2
  285. package/src/migration-engine/create.test.ts +2 -2
  286. package/src/migration-engine/generation-engine.test.ts +229 -104
  287. package/src/migration-engine/generation-engine.ts +94 -64
  288. package/src/migration-engine/shared.ts +1 -0
  289. package/src/mod.ts +64 -26
  290. package/src/naming/sql-naming.ts +180 -0
  291. package/src/outbox/outbox-builder.ts +241 -0
  292. package/src/outbox/outbox.test.ts +253 -0
  293. package/src/outbox/outbox.ts +137 -0
  294. package/src/query/column-defaults.ts +41 -3
  295. package/src/query/condition-builder.test.ts +3 -3
  296. package/src/query/cursor.test.ts +116 -18
  297. package/src/query/cursor.ts +75 -26
  298. package/src/query/db-now.ts +6 -0
  299. package/src/query/query-type.test.ts +2 -2
  300. package/src/query/serialize/create-sql-serializer.ts +7 -2
  301. package/src/query/serialize/dialect/mysql-serializer.ts +12 -4
  302. package/src/query/serialize/dialect/postgres-serializer.ts +34 -4
  303. package/src/query/serialize/dialect/sqlite-serializer.test.ts +51 -1
  304. package/src/query/serialize/dialect/sqlite-serializer.ts +92 -9
  305. package/src/query/serialize/sql-serializer.ts +4 -4
  306. package/src/query/simple-query-interface.ts +5 -0
  307. package/src/query/unit-of-work/execute-unit-of-work.test.ts +25 -1
  308. package/src/query/unit-of-work/execute-unit-of-work.ts +25 -8
  309. package/src/query/unit-of-work/unit-of-work-coordinator.test.ts +12 -12
  310. package/src/query/unit-of-work/unit-of-work-types.test.ts +1 -1
  311. package/src/query/unit-of-work/unit-of-work.test.ts +168 -37
  312. package/src/query/unit-of-work/unit-of-work.ts +203 -18
  313. package/src/query/value-decoding.test.ts +13 -2
  314. package/src/query/value-decoding.ts +17 -4
  315. package/src/query/value-encoding.test.ts +85 -2
  316. package/src/query/value-encoding.ts +56 -6
  317. package/src/schema/create.test.ts +129 -42
  318. package/src/schema/create.ts +185 -47
  319. package/src/schema/generate-id.test.ts +2 -2
  320. package/src/schema/generate-id.ts +2 -2
  321. package/src/schema/serialize.test.ts +14 -2
  322. package/src/schema/type-conversion/create-sql-type-mapper.ts +7 -2
  323. package/src/schema/type-conversion/dialect/sqlite.ts +18 -0
  324. package/src/schema/type-conversion/type-mapping.test.ts +25 -1
  325. package/src/schema/validator.test.ts +197 -0
  326. package/src/schema/validator.ts +231 -0
  327. package/src/{adapters/drizzle/generate.test.ts → schema-output/drizzle.test.ts} +179 -129
  328. package/src/{adapters/drizzle/generate.ts → schema-output/drizzle.ts} +143 -93
  329. package/src/schema-output/prisma.test.ts +536 -0
  330. package/src/schema-output/prisma.ts +573 -0
  331. package/src/util/default-database-adapter.ts +106 -0
  332. package/src/with-database.ts +22 -3
  333. package/tsdown.config.ts +6 -4
  334. package/dist/adapters/drizzle/drizzle-adapter.d.ts +0 -20
  335. package/dist/adapters/drizzle/drizzle-adapter.d.ts.map +0 -1
  336. package/dist/adapters/drizzle/drizzle-adapter.js +0 -27
  337. package/dist/adapters/drizzle/drizzle-adapter.js.map +0 -1
  338. package/dist/adapters/drizzle/generate.d.ts +0 -30
  339. package/dist/adapters/drizzle/generate.d.ts.map +0 -1
  340. package/dist/adapters/drizzle/generate.js.map +0 -1
  341. package/dist/adapters/kysely/kysely-adapter.d.ts +0 -19
  342. package/dist/adapters/kysely/kysely-adapter.d.ts.map +0 -1
  343. package/dist/adapters/kysely/kysely-adapter.js +0 -17
  344. package/dist/adapters/kysely/kysely-adapter.js.map +0 -1
  345. package/dist/adapters/shared/table-name-mapper.d.ts +0 -12
  346. package/dist/adapters/shared/table-name-mapper.d.ts.map +0 -1
  347. package/dist/adapters/shared/table-name-mapper.js +0 -43
  348. package/dist/adapters/shared/table-name-mapper.js.map +0 -1
  349. package/dist/node_modules/.pnpm/rou3@0.7.10/node_modules/rou3/dist/index.js.map +0 -1
  350. package/dist/schema-generator/schema-generator.d.ts +0 -15
  351. package/dist/schema-generator/schema-generator.d.ts.map +0 -1
  352. package/src/adapters/drizzle/drizzle-adapter.ts +0 -39
  353. package/src/adapters/kysely/kysely-adapter.ts +0 -27
  354. package/src/adapters/shared/table-name-mapper.ts +0 -50
  355. package/src/schema-generator/schema-generator.ts +0 -12
@@ -1,3 +1,9 @@
1
+ import {
2
+ schemaNamingStrategy,
3
+ suffixNamingStrategy,
4
+ type SqlNamingStrategy,
5
+ } from "../../naming/sql-naming";
6
+
1
7
  export const supportedDatabases = ["sqlite", "postgresql", "mysql"] as const;
2
8
  export type SupportedDatabase = (typeof supportedDatabases)[number];
3
9
 
@@ -18,6 +24,7 @@ export abstract class DriverConfig<T extends SupportedDriverType = SupportedDriv
18
24
 
19
25
  abstract readonly supportsReturning: boolean;
20
26
  abstract readonly supportsJson: boolean;
27
+ abstract readonly outboxVersionstampStrategy: OutboxVersionstampStrategy;
21
28
 
22
29
  /**
23
30
  * Column name for internal ID in RETURNING results.
@@ -25,10 +32,17 @@ export abstract class DriverConfig<T extends SupportedDriverType = SupportedDriv
25
32
  */
26
33
  abstract readonly internalIdColumn: string | undefined;
27
34
 
35
+ /**
36
+ * SQLite storage selection is handled by adapters, not driver config.
37
+ */
28
38
  get supportsRowsAffected(): boolean {
29
39
  return !!this.extractAffectedRows;
30
40
  }
31
41
 
42
+ get defaultNamingStrategy(): SqlNamingStrategy {
43
+ return defaultNamingStrategyForDatabase(this.databaseType);
44
+ }
45
+
32
46
  /**
33
47
  * Extract the number of affected rows from a query result.
34
48
  * Only implemented for drivers that support affected rows reporting.
@@ -40,12 +54,31 @@ export abstract class DriverConfig<T extends SupportedDriverType = SupportedDriv
40
54
  extractAffectedRows?(result: Record<string, unknown>): bigint;
41
55
  }
42
56
 
57
+ export const defaultNamingStrategyForDatabase = (
58
+ databaseType: SupportedDatabase,
59
+ ): SqlNamingStrategy => {
60
+ switch (databaseType) {
61
+ case "postgresql":
62
+ return schemaNamingStrategy;
63
+ case "sqlite":
64
+ case "mysql":
65
+ default:
66
+ return suffixNamingStrategy;
67
+ }
68
+ };
69
+
70
+ export type OutboxVersionstampStrategy =
71
+ | "update-returning"
72
+ | "insert-on-conflict-returning"
73
+ | "insert-on-duplicate-last-insert-id";
74
+
43
75
  export class SQLocalDriverConfig extends DriverConfig<"sqlocal"> {
44
76
  override readonly driverType = "sqlocal";
45
77
  override readonly databaseType = "sqlite";
46
78
  override readonly supportsReturning = true;
47
79
  override readonly supportsJson = false;
48
80
  override readonly internalIdColumn = "_internalId";
81
+ override readonly outboxVersionstampStrategy = "insert-on-conflict-returning";
49
82
  }
50
83
 
51
84
  export class CloudflareDurableObjectsDriverConfig extends DriverConfig<"cloudflare_durable_objects"> {
@@ -54,6 +87,7 @@ export class CloudflareDurableObjectsDriverConfig extends DriverConfig<"cloudfla
54
87
  override readonly supportsReturning = true;
55
88
  override readonly supportsJson = false;
56
89
  override readonly internalIdColumn = "_internalId";
90
+ override readonly outboxVersionstampStrategy = "insert-on-conflict-returning";
57
91
  }
58
92
 
59
93
  export class BetterSQLite3DriverConfig extends DriverConfig<"better-sqlite3"> {
@@ -62,6 +96,7 @@ export class BetterSQLite3DriverConfig extends DriverConfig<"better-sqlite3"> {
62
96
  override readonly supportsReturning = true;
63
97
  override readonly supportsJson = false;
64
98
  override readonly internalIdColumn = "_internalId";
99
+ override readonly outboxVersionstampStrategy = "insert-on-conflict-returning";
65
100
 
66
101
  override extractAffectedRows(result: Record<string, unknown>): bigint {
67
102
  if ("numAffectedRows" in result) {
@@ -86,6 +121,7 @@ export class NodePostgresDriverConfig extends DriverConfig<"pg"> {
86
121
  override readonly supportsReturning = true;
87
122
  override readonly supportsJson = true;
88
123
  override readonly internalIdColumn = "_internalId";
124
+ override readonly outboxVersionstampStrategy = "insert-on-conflict-returning";
89
125
 
90
126
  override extractAffectedRows(result: Record<string, unknown>): bigint {
91
127
  if ("numAffectedRows" in result) {
@@ -118,6 +154,7 @@ export class PGLiteDriverConfig extends DriverConfig<"pglite"> {
118
154
  override readonly supportsReturning = true;
119
155
  override readonly supportsJson = true;
120
156
  override readonly internalIdColumn = "_internalId";
157
+ override readonly outboxVersionstampStrategy = "insert-on-conflict-returning";
121
158
 
122
159
  override extractAffectedRows(result: Record<string, unknown>): bigint {
123
160
  if ("affectedRows" in result) {
@@ -141,4 +178,5 @@ export class MySQL2DriverConfig extends DriverConfig<"mysql2"> {
141
178
  override readonly supportsReturning = false;
142
179
  override readonly supportsJson = true;
143
180
  override readonly internalIdColumn = undefined;
181
+ override readonly outboxVersionstampStrategy = "insert-on-duplicate-last-insert-id";
144
182
  }
@@ -1,12 +1,12 @@
1
1
  import { SQLocalKysely } from "sqlocal/kysely";
2
2
  import { assert, describe, expect, it } from "vitest";
3
3
  import { SQLocalDriverConfig } from "./driver-config";
4
- import { GenericSQLAdapter } from "./generic-sql-adapter";
4
+ import { SqlAdapter } from "./generic-sql-adapter";
5
5
  import { column, idColumn, schema } from "../../schema/create";
6
6
  import { internalSchema } from "../../fragments/internal-fragment";
7
7
 
8
- describe("GenericSQLAdapter", () => {
9
- const testSchema = schema((s) => {
8
+ describe("SqlAdapter", () => {
9
+ const testSchema = schema("test", (s) => {
10
10
  return s.addTable("products", (t) => {
11
11
  return t
12
12
  .addColumn("id", idColumn())
@@ -16,11 +16,11 @@ describe("GenericSQLAdapter", () => {
16
16
  });
17
17
  });
18
18
 
19
- it("Should be able to query using GenericSQLAdapter", async () => {
19
+ it("Should be able to query using SqlAdapter", async () => {
20
20
  const { dialect } = new SQLocalKysely(":memory:");
21
21
  const driverConfig = new SQLocalDriverConfig();
22
22
 
23
- const adapter = new GenericSQLAdapter({ dialect, driverConfig });
23
+ const adapter = new SqlAdapter({ dialect, driverConfig });
24
24
 
25
25
  // Create settings table first (needed for version tracking)
26
26
  const settingsMigrations = adapter.prepareMigrations(internalSchema, "");
@@ -4,13 +4,13 @@ import {
4
4
  fragnoDatabaseAdapterVersionFakeSymbol,
5
5
  type DatabaseAdapter,
6
6
  type DatabaseContextStorage,
7
- type TableNameMapper,
7
+ type DatabaseAdapterMetadata,
8
+ type SQLiteProfile,
8
9
  } from "../adapters";
9
10
  import type { CompiledQuery, Dialect, QueryResult } from "../../sql-driver/sql-driver";
10
11
  import { SqlDriverAdapter } from "../../sql-driver/sql-driver-adapter";
11
12
  import { sql } from "../../sql-driver/sql";
12
- import type { AnySchema } from "../../schema/create";
13
- import { createTableNameMapper } from "../shared/table-name-mapper";
13
+ import type { AnyColumn, AnySchema } from "../../schema/create";
14
14
  import type { SimpleQueryInterface } from "../../query/simple-query-interface";
15
15
  import { createExecutor } from "./generic-sql-uow-executor";
16
16
  import { UnitOfWorkDecoder } from "./uow-decoder";
@@ -22,34 +22,87 @@ import {
22
22
  fromUnitOfWorkCompiler,
23
23
  type UnitOfWorkFactory,
24
24
  } from "../shared/from-unit-of-work-compiler";
25
+ import type { UOWInstrumentation } from "../../query/unit-of-work/unit-of-work";
26
+ import type { SQLiteStorageMode } from "./sqlite-storage";
27
+ import { sqliteStorageDefault, sqliteStoragePrisma } from "./sqlite-storage";
28
+ import type { OutboxConfig } from "../../outbox/outbox";
29
+ import { createSQLSerializer } from "../../query/serialize/create-sql-serializer";
30
+ import {
31
+ createNamingResolver,
32
+ type NamingResolver,
33
+ type SqlNamingStrategy,
34
+ } from "../../naming/sql-naming";
25
35
 
26
36
  export interface UnitOfWorkConfig {
27
37
  onQuery?: (query: CompiledQuery) => void;
28
38
  dryRun?: boolean;
39
+ instrumentation?: UOWInstrumentation;
29
40
  }
30
41
 
31
- export interface GenericSQLOptions {
42
+ export interface SqlAdapterOptions {
32
43
  dialect: Dialect;
33
44
  driverConfig: DriverConfig;
34
45
  uowConfig?: UnitOfWorkConfig;
46
+ outbox?: OutboxConfig;
47
+ sqliteProfile?: SQLiteProfile;
48
+ sqliteStorageMode?: SQLiteStorageMode;
49
+ namingStrategy?: SqlNamingStrategy;
35
50
  }
36
51
 
37
- export class GenericSQLAdapter implements DatabaseAdapter<UnitOfWorkConfig> {
52
+ export const sqliteProfiles: Record<SQLiteProfile, SQLiteStorageMode> = {
53
+ default: sqliteStorageDefault,
54
+ prisma: sqliteStoragePrisma,
55
+ };
56
+
57
+ export class SqlAdapter implements DatabaseAdapter<UnitOfWorkConfig> {
38
58
  readonly dialect: Dialect;
39
59
  readonly driverConfig: DriverConfig;
40
60
  readonly uowConfig?: UnitOfWorkConfig;
61
+ readonly outbox?: OutboxConfig;
62
+ readonly sqliteStorageMode?: SQLiteStorageMode;
63
+ readonly sqliteProfile?: SQLiteProfile;
64
+ readonly adapterMetadata: DatabaseAdapterMetadata;
65
+ readonly namingStrategy: SqlNamingStrategy;
41
66
 
42
- #schemaNamespaceMap = new WeakMap<AnySchema, string>();
67
+ #schemaNamespaceMap = new WeakMap<AnySchema, string | null>();
43
68
  #contextStorage: RequestContextStorage<DatabaseContextStorage>;
44
69
 
45
70
  #driver: SqlDriverAdapter;
46
71
 
47
- constructor({ dialect, driverConfig, uowConfig }: GenericSQLOptions) {
72
+ constructor({
73
+ dialect,
74
+ driverConfig,
75
+ uowConfig,
76
+ outbox,
77
+ sqliteProfile,
78
+ sqliteStorageMode,
79
+ namingStrategy,
80
+ }: SqlAdapterOptions) {
48
81
  this.dialect = dialect;
49
82
  this.driverConfig = driverConfig;
50
83
  this.uowConfig = uowConfig;
84
+ this.outbox = outbox;
85
+ this.namingStrategy = namingStrategy ?? driverConfig.defaultNamingStrategy;
86
+ const resolvedProfile = sqliteProfile ?? "default";
87
+
88
+ if (sqliteStorageMode && sqliteProfile) {
89
+ throw new Error("sqliteStorageMode cannot be used together with sqliteProfile.");
90
+ }
91
+
92
+ this.sqliteStorageMode =
93
+ driverConfig.databaseType === "sqlite"
94
+ ? (sqliteStorageMode ?? sqliteProfiles[resolvedProfile])
95
+ : undefined;
96
+ this.sqliteProfile =
97
+ driverConfig.databaseType === "sqlite" && !sqliteStorageMode ? resolvedProfile : undefined;
98
+
99
+ this.adapterMetadata = {
100
+ databaseType: driverConfig.databaseType,
101
+ sqliteProfile: this.sqliteProfile,
102
+ sqliteStorageMode: this.sqliteStorageMode,
103
+ };
51
104
 
52
- this.#schemaNamespaceMap = new WeakMap<AnySchema, string>();
105
+ this.#schemaNamespaceMap = new WeakMap<AnySchema, string | null>();
53
106
  this.#contextStorage = new RequestContextStorage();
54
107
 
55
108
  this.#driver = new SqlDriverAdapter(dialect);
@@ -60,11 +113,11 @@ export class GenericSQLAdapter implements DatabaseAdapter<UnitOfWorkConfig> {
60
113
  }
61
114
 
62
115
  get [fragnoDatabaseAdapterNameFakeSymbol](): string {
63
- return "generic-sql";
116
+ return "sql";
64
117
  }
65
118
 
66
119
  get [fragnoDatabaseAdapterVersionFakeSymbol](): number {
67
- return 0;
120
+ return 1;
68
121
  }
69
122
 
70
123
  get contextStorage(): RequestContextStorage<DatabaseContextStorage> {
@@ -77,23 +130,23 @@ export class GenericSQLAdapter implements DatabaseAdapter<UnitOfWorkConfig> {
77
130
 
78
131
  async isConnectionHealthy(): Promise<boolean> {
79
132
  const result = await this.#driver.executeQuery(sql`SELECT 1 as healthy`.compile(this.dialect));
80
- return result.rows[0]["healthy"] === 1;
133
+ const healthyValue = result.rows[0]?.["healthy"];
134
+ return healthyValue === 1 || healthyValue === 1n || healthyValue === "1";
81
135
  }
82
136
 
83
- prepareMigrations<T extends AnySchema>(schema: T, namespace: string): PreparedMigrations {
137
+ prepareMigrations<T extends AnySchema>(schema: T, namespace: string | null): PreparedMigrations {
138
+ const resolver = createNamingResolver(schema, namespace, this.namingStrategy);
84
139
  return createPreparedMigrations({
85
140
  schema,
86
- namespace,
141
+ namespace: namespace ?? schema.name,
87
142
  database: this.driverConfig.databaseType,
88
- mapper: namespace ? this.createTableNameMapper(namespace) : undefined,
143
+ driverConfig: this.driverConfig,
144
+ sqliteStorageMode: this.sqliteStorageMode,
145
+ resolver,
89
146
  driver: this.#driver,
90
147
  });
91
148
  }
92
149
 
93
- createTableNameMapper(namespace: string): TableNameMapper {
94
- return createTableNameMapper(namespace, false);
95
- }
96
-
97
150
  async getSchemaVersion(namespace: string): Promise<string | undefined> {
98
151
  const key = `${namespace}.schema_version`;
99
152
  const query = sql`SELECT value FROM fragno_db_settings WHERE key = ${key};`.compile(
@@ -125,22 +178,55 @@ export class GenericSQLAdapter implements DatabaseAdapter<UnitOfWorkConfig> {
125
178
 
126
179
  createQueryEngine<T extends AnySchema>(
127
180
  schema: T,
128
- namespace: string,
181
+ namespace: string | null,
129
182
  ): SimpleQueryInterface<T, UnitOfWorkConfig> {
130
183
  this.#schemaNamespaceMap.set(schema, namespace);
184
+ const resolver = createNamingResolver(schema, namespace, this.namingStrategy);
131
185
 
132
- const operationCompiler = new GenericSQLUOWOperationCompiler(this.driverConfig, (ns) =>
133
- ns ? this.createTableNameMapper(ns) : undefined,
186
+ const operationCompiler = new GenericSQLUOWOperationCompiler(
187
+ this.driverConfig,
188
+ this.sqliteStorageMode,
189
+ (schemaForResolver, namespaceForResolver): NamingResolver =>
190
+ createNamingResolver(schemaForResolver, namespaceForResolver, this.namingStrategy),
134
191
  );
135
192
 
136
193
  const factory: UnitOfWorkFactory = {
137
194
  compiler: createUOWCompilerFromOperationCompiler(operationCompiler),
138
- executor: createExecutor(this.#driver, this.driverConfig, false),
139
- decoder: new UnitOfWorkDecoder(this.driverConfig),
195
+ executor: createExecutor(this.#driver, this.driverConfig, {
196
+ dialect: this.dialect,
197
+ dryRun: false,
198
+ outbox: this.outbox,
199
+ namingStrategy: this.namingStrategy,
200
+ }),
201
+ decoder: new UnitOfWorkDecoder(this.driverConfig, this.sqliteStorageMode, resolver),
140
202
  uowConfig: this.uowConfig,
141
203
  schemaNamespaceMap: this.#schemaNamespaceMap,
142
204
  };
143
205
 
144
- return fromUnitOfWorkCompiler(schema, factory);
206
+ const queryEngine = fromUnitOfWorkCompiler(schema, factory) as SimpleQueryInterface<
207
+ T,
208
+ UnitOfWorkConfig
209
+ >;
210
+
211
+ const serializer = createSQLSerializer(this.driverConfig, this.sqliteStorageMode);
212
+ const timestampColumn = { type: "timestamp" } as AnyColumn;
213
+
214
+ return {
215
+ ...queryEngine,
216
+ now: async () => {
217
+ const result = await this.#driver.executeQuery(
218
+ sql`SELECT CURRENT_TIMESTAMP as now`.compile(this.dialect),
219
+ );
220
+ const rawValue = result.rows[0]?.["now"];
221
+ if (rawValue === undefined || rawValue === null) {
222
+ throw new Error("Failed to fetch database time");
223
+ }
224
+ return serializer.deserialize(rawValue, timestampColumn) as Date;
225
+ },
226
+ };
145
227
  }
146
228
  }
229
+
230
+ export type { SQLiteStorageMode } from "./sqlite-storage";
231
+ export { sqliteStorageDefault, sqliteStoragePrisma } from "./sqlite-storage";
232
+ export type { OutboxConfig } from "../../outbox/outbox";
@@ -0,0 +1,54 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import type { CompiledMutation } from "../../query/unit-of-work/unit-of-work";
3
+ import type { CompiledQuery, Dialect } from "../../sql-driver/sql-driver";
4
+ import type { SqlDriverAdapter } from "../../sql-driver/sql-driver-adapter";
5
+ import { NodePostgresDriverConfig } from "./driver-config";
6
+ import { executeMutation } from "./generic-sql-uow-executor";
7
+
8
+ const createError = (code: string) => {
9
+ const error = new Error(`DB_ERROR_${code}`);
10
+ (error as { code?: string }).code = code;
11
+ return error;
12
+ };
13
+
14
+ const createAdapterThatThrows = (error: Error) =>
15
+ ({
16
+ transaction: async (
17
+ callback: (trx: { executeQuery: () => Promise<unknown> }) => Promise<unknown>,
18
+ ) =>
19
+ await callback({
20
+ executeQuery: async () => {
21
+ throw error;
22
+ },
23
+ }),
24
+ }) as unknown as SqlDriverAdapter;
25
+
26
+ describe("executeMutation", () => {
27
+ const dialect = {
28
+ createAdapter: () => ({}) as Dialect["createAdapter"] extends () => infer T ? T : never,
29
+ createDriver: () => ({}) as Dialect["createDriver"] extends () => infer T ? T : never,
30
+ createQueryCompiler: () =>
31
+ ({}) as Dialect["createQueryCompiler"] extends () => infer T ? T : never,
32
+ } satisfies Dialect;
33
+ const compiledQuery: CompiledQuery = {
34
+ sql: "SELECT 1",
35
+ parameters: [],
36
+ };
37
+
38
+ const mutationBatch: CompiledMutation<CompiledQuery>[] = [
39
+ {
40
+ op: "update",
41
+ query: compiledQuery,
42
+ expectedAffectedRows: null,
43
+ expectedReturnedRows: null,
44
+ },
45
+ ];
46
+
47
+ it.each(["40001", "40P01"])("returns success=false on SQLSTATE %s", async (code) => {
48
+ const adapter = createAdapterThatThrows(createError(code));
49
+ const result = await executeMutation(adapter, new NodePostgresDriverConfig(), mutationBatch, {
50
+ dialect,
51
+ });
52
+ expect(result.success).toBe(false);
53
+ });
54
+ });
@@ -3,10 +3,33 @@ import type {
3
3
  MutationResult,
4
4
  UOWExecutor,
5
5
  } from "../../query/unit-of-work/unit-of-work";
6
- import type { CompiledQuery } from "../../sql-driver/sql-driver";
6
+ import type { CompiledQuery, Dialect } from "../../sql-driver/sql-driver";
7
7
  import type { SqlDriverAdapter } from "../../sql-driver/sql-driver-adapter";
8
8
  import type { DriverConfig } from "./driver-config";
9
9
  import { ResultInterpreter } from "./result-interpreter";
10
+ import { sql } from "../../sql-driver/sql";
11
+ import { createId } from "../../id";
12
+ import superjson from "superjson";
13
+ import { createColdKysely } from "./migration/cold-kysely";
14
+ import { SETTINGS_NAMESPACE, internalSchema } from "../../fragments/internal-fragment.schema";
15
+ import {
16
+ type OutboxConfig,
17
+ type OutboxRefLookup,
18
+ type OutboxRefMap,
19
+ encodeVersionstamp,
20
+ parseOutboxVersionValue,
21
+ versionstampToHex,
22
+ } from "../../outbox/outbox";
23
+ import { buildOutboxPlan, finalizeOutboxPayload } from "../../outbox/outbox-builder";
24
+ import { createSQLSerializer } from "../../query/serialize/create-sql-serializer";
25
+ import { type SqlNamingStrategy } from "../../naming/sql-naming";
26
+
27
+ export interface ExecutorOptions {
28
+ dryRun?: boolean;
29
+ dialect: Dialect;
30
+ outbox?: OutboxConfig;
31
+ namingStrategy?: SqlNamingStrategy;
32
+ }
10
33
 
11
34
  export async function executeRetrieval(
12
35
  adapter: SqlDriverAdapter,
@@ -32,6 +55,7 @@ export async function executeMutation(
32
55
  adapter: SqlDriverAdapter,
33
56
  driverConfig: DriverConfig,
34
57
  mutationBatch: CompiledMutation<CompiledQuery>[],
58
+ options: ExecutorOptions,
35
59
  ): Promise<MutationResult> {
36
60
  if (mutationBatch.length === 0) {
37
61
  return { success: true, createdInternalIds: [] };
@@ -39,9 +63,24 @@ export async function executeMutation(
39
63
 
40
64
  const createdInternalIds: (bigint | null)[] = [];
41
65
  const resultInterpreter = new ResultInterpreter(driverConfig);
66
+ const outboxEnabled = options.outbox?.enabled ?? false;
67
+ const namingStrategy = options.namingStrategy ?? driverConfig.defaultNamingStrategy;
68
+
69
+ const outboxOperations = outboxEnabled
70
+ ? mutationBatch.flatMap((mutation) => (mutation.operation ? [mutation.operation] : []))
71
+ : [];
72
+
73
+ const outboxPlan = outboxOperations.length > 0 ? buildOutboxPlan(outboxOperations) : null;
74
+ const shouldWriteOutbox = outboxEnabled && outboxPlan !== null && outboxPlan.drafts.length > 0;
42
75
 
43
76
  try {
44
77
  await adapter.transaction(async (tx) => {
78
+ let outboxVersion: bigint | null = null;
79
+
80
+ if (shouldWriteOutbox) {
81
+ outboxVersion = await reserveOutboxVersion(tx, driverConfig, options.dialect);
82
+ }
83
+
45
84
  for (const compiledMutation of mutationBatch) {
46
85
  const result = await tx.executeQuery(compiledMutation.query);
47
86
 
@@ -86,6 +125,31 @@ export async function executeMutation(
86
125
  }
87
126
  }
88
127
  }
128
+
129
+ if (shouldWriteOutbox && outboxPlan && outboxVersion !== null) {
130
+ const uowId = mutationBatch[0]?.uowId;
131
+ if (!uowId) {
132
+ throw new Error("Outbox mutation batch is missing uowId.");
133
+ }
134
+
135
+ const refMap = await resolveOutboxRefMap(
136
+ tx,
137
+ driverConfig,
138
+ outboxPlan.lookups,
139
+ namingStrategy,
140
+ );
141
+ const payload = finalizeOutboxPayload(outboxPlan, outboxVersion);
142
+ const payloadSerialized = superjson.serialize(payload);
143
+ const versionstamp = versionstampToHex(encodeVersionstamp(outboxVersion, 0));
144
+
145
+ await insertOutboxRow(tx, driverConfig, {
146
+ id: createId(),
147
+ versionstamp,
148
+ uowId,
149
+ payload: payloadSerialized,
150
+ refMap,
151
+ });
152
+ }
89
153
  });
90
154
 
91
155
  return { success: true, createdInternalIds };
@@ -96,6 +160,15 @@ export async function executeMutation(
96
160
  return { success: false };
97
161
  }
98
162
 
163
+ const errorCode =
164
+ typeof error === "object" && error !== null && "code" in error
165
+ ? (error as { code?: unknown }).code
166
+ : undefined;
167
+
168
+ if (errorCode === "40001" || errorCode === "40P01") {
169
+ return { success: false };
170
+ }
171
+
99
172
  // Other database errors should be thrown
100
173
  throw error;
101
174
  }
@@ -104,8 +177,10 @@ export async function executeMutation(
104
177
  export function createExecutor(
105
178
  adapter: SqlDriverAdapter,
106
179
  driverConfig: DriverConfig,
107
- dryRun?: boolean,
180
+ options: ExecutorOptions,
108
181
  ): UOWExecutor<CompiledQuery, unknown> {
182
+ const dryRun = options.dryRun ?? false;
183
+
109
184
  return {
110
185
  async executeRetrievalPhase(retrievalBatch: CompiledQuery[]) {
111
186
  // In dryRun mode, skip execution and return empty results
@@ -124,7 +199,160 @@ export function createExecutor(
124
199
  };
125
200
  }
126
201
 
127
- return executeMutation(adapter, driverConfig, mutationBatch);
202
+ return executeMutation(adapter, driverConfig, mutationBatch, options);
128
203
  },
129
204
  };
130
205
  }
206
+
207
+ async function reserveOutboxVersion(
208
+ tx: SqlDriverAdapter,
209
+ driverConfig: DriverConfig,
210
+ dialect: Dialect,
211
+ ): Promise<bigint> {
212
+ const key = `${SETTINGS_NAMESPACE}.outbox_version`;
213
+ const id = createId();
214
+
215
+ switch (driverConfig.outboxVersionstampStrategy) {
216
+ case "insert-on-conflict-returning": {
217
+ const query =
218
+ driverConfig.databaseType === "postgresql"
219
+ ? sql`
220
+ insert into fragno_db_settings (id, key, value)
221
+ values (${id}, ${key}, '0')
222
+ on conflict (key) do update
223
+ set value = (fragno_db_settings.value::bigint + 1)::text
224
+ returning value;
225
+ `
226
+ : sql`
227
+ insert into fragno_db_settings (id, key, value)
228
+ values (${id}, ${key}, '0')
229
+ on conflict (key) do update
230
+ set value = cast(fragno_db_settings.value as integer) + 1
231
+ returning value;
232
+ `;
233
+
234
+ const result = await tx.executeQuery(query.compile(dialect));
235
+ const value = result.rows[0]?.["value"];
236
+ return parseOutboxVersionValue(value);
237
+ }
238
+ case "update-returning": {
239
+ const query =
240
+ driverConfig.databaseType === "postgresql"
241
+ ? sql`
242
+ update fragno_db_settings
243
+ set value = (fragno_db_settings.value::bigint + 1)::text
244
+ where key = ${key}
245
+ returning value;
246
+ `
247
+ : sql`
248
+ update fragno_db_settings
249
+ set value = cast(fragno_db_settings.value as integer) + 1
250
+ where key = ${key}
251
+ returning value;
252
+ `;
253
+
254
+ const result = await tx.executeQuery(query.compile(dialect));
255
+ const value = result.rows[0]?.["value"];
256
+ if (value === undefined) {
257
+ throw new Error("Outbox version row was not found for update-returning strategy.");
258
+ }
259
+ return parseOutboxVersionValue(value);
260
+ }
261
+ case "insert-on-duplicate-last-insert-id": {
262
+ const insertQuery = sql`
263
+ insert into fragno_db_settings (id, key, value)
264
+ values (${id}, ${key}, LAST_INSERT_ID(0))
265
+ on duplicate key update value = LAST_INSERT_ID(cast(value as unsigned) + 1);
266
+ `;
267
+
268
+ await tx.executeQuery(insertQuery.compile(dialect));
269
+
270
+ const selectQuery = sql`select LAST_INSERT_ID() as value;`;
271
+ const result = await tx.executeQuery(selectQuery.compile(dialect));
272
+ const value = result.rows[0]?.["value"];
273
+ return parseOutboxVersionValue(value);
274
+ }
275
+ }
276
+ }
277
+
278
+ async function resolveOutboxRefMap(
279
+ tx: SqlDriverAdapter,
280
+ driverConfig: DriverConfig,
281
+ lookups: OutboxRefLookup[],
282
+ namingStrategy: SqlNamingStrategy,
283
+ ): Promise<OutboxRefMap | undefined> {
284
+ if (lookups.length === 0) {
285
+ return undefined;
286
+ }
287
+
288
+ const refMap: OutboxRefMap = {};
289
+ const db = createColdKysely(driverConfig.databaseType);
290
+
291
+ for (const lookup of lookups) {
292
+ const namespace = lookup.namespace ?? null;
293
+ const logicalTable = lookup.table.name;
294
+ const schemaName =
295
+ namingStrategy.namespaceScope === "schema" && namespace && namespace.length > 0
296
+ ? namingStrategy.namespaceToSchema(namespace)
297
+ : null;
298
+ const scopedDb = schemaName ? db.withSchema(schemaName) : db;
299
+ const tableName = namingStrategy.tableName(logicalTable, namespace);
300
+ const internalColumn = namingStrategy.columnName(
301
+ lookup.table.getInternalIdColumn().name,
302
+ logicalTable,
303
+ );
304
+ const externalColumn = namingStrategy.columnName(lookup.table.getIdColumn().name, logicalTable);
305
+
306
+ const query = scopedDb
307
+ .selectFrom(tableName)
308
+ .select(externalColumn)
309
+ .where(internalColumn, "=", lookup.internalId)
310
+ .compile();
311
+
312
+ const result = await tx.executeQuery(query);
313
+ const row = result.rows[0] as Record<string, unknown> | undefined;
314
+ const externalId = row?.[externalColumn];
315
+
316
+ if (typeof externalId !== "string") {
317
+ throw new Error(
318
+ `Failed to resolve outbox reference for ${tableName}.${internalColumn}=${String(lookup.internalId)}`,
319
+ );
320
+ }
321
+
322
+ refMap[lookup.key] = externalId;
323
+ }
324
+
325
+ return Object.keys(refMap).length > 0 ? refMap : undefined;
326
+ }
327
+
328
+ async function insertOutboxRow(
329
+ tx: SqlDriverAdapter,
330
+ driverConfig: DriverConfig,
331
+ options: {
332
+ id: string;
333
+ versionstamp: string;
334
+ uowId: string;
335
+ payload: { json: unknown; meta?: Record<string, unknown> };
336
+ refMap?: OutboxRefMap;
337
+ },
338
+ ): Promise<void> {
339
+ const { id, versionstamp, uowId, payload, refMap } = options;
340
+ const refMapValue = refMap ?? null;
341
+ const serializer = createSQLSerializer(driverConfig);
342
+ const outboxTable = internalSchema.tables.fragno_db_outbox;
343
+ const values = { id, versionstamp, uowId, payload, refMap: refMapValue };
344
+ const serializedValues: Record<string, unknown> = {};
345
+ for (const [key, value] of Object.entries(values)) {
346
+ const col = outboxTable.columns[key];
347
+ if (!col) {
348
+ serializedValues[key] = value;
349
+ continue;
350
+ }
351
+ serializedValues[col.name] = serializer.serialize(value, col);
352
+ }
353
+ const db = createColdKysely(driverConfig.databaseType);
354
+
355
+ const query = db.insertInto("fragno_db_outbox").values(serializedValues).compile();
356
+
357
+ await tx.executeQuery(query);
358
+ }