@fragno-dev/db 0.2.1 → 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 (362) hide show
  1. package/.turbo/turbo-build.log +206 -140
  2. package/CHANGELOG.md +67 -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 +38 -28
  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 +45 -96
  80. package/dist/db-fragment-definition-builder.d.ts.map +1 -1
  81. package/dist/db-fragment-definition-builder.js +121 -99
  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 +172 -9
  92. package/dist/fragments/internal-fragment.d.ts.map +1 -1
  93. package/dist/fragments/internal-fragment.js +193 -74
  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 +47 -4
  106. package/dist/hooks/hooks.d.ts.map +1 -1
  107. package/dist/hooks/hooks.js +106 -39
  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 +17 -10
  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 +351 -100
  165. package/dist/query/unit-of-work/execute-unit-of-work.d.ts.map +1 -1
  166. package/dist/query/unit-of-work/execute-unit-of-work.js +440 -267
  167. package/dist/query/unit-of-work/execute-unit-of-work.js.map +1 -1
  168. package/dist/query/unit-of-work/unit-of-work.d.ts +67 -22
  169. package/dist/query/unit-of-work/unit-of-work.d.ts.map +1 -1
  170. package/dist/query/unit-of-work/unit-of-work.js +110 -13
  171. package/dist/query/unit-of-work/unit-of-work.js.map +1 -1
  172. package/dist/query/value-decoding.js +8 -5
  173. package/dist/query/value-decoding.js.map +1 -1
  174. package/dist/query/value-encoding.js +29 -9
  175. package/dist/query/value-encoding.js.map +1 -1
  176. package/dist/schema/create.d.ts +40 -14
  177. package/dist/schema/create.d.ts.map +1 -1
  178. package/dist/schema/create.js +82 -42
  179. package/dist/schema/create.js.map +1 -1
  180. package/dist/schema/generate-id.d.ts +20 -0
  181. package/dist/schema/generate-id.d.ts.map +1 -0
  182. package/dist/schema/generate-id.js +28 -0
  183. package/dist/schema/generate-id.js.map +1 -0
  184. package/dist/schema/type-conversion/create-sql-type-mapper.js +3 -2
  185. package/dist/schema/type-conversion/create-sql-type-mapper.js.map +1 -1
  186. package/dist/schema/type-conversion/dialect/sqlite.js +9 -0
  187. package/dist/schema/type-conversion/dialect/sqlite.js.map +1 -1
  188. package/dist/schema/validator.d.ts +10 -0
  189. package/dist/schema/validator.d.ts.map +1 -0
  190. package/dist/schema/validator.js +123 -0
  191. package/dist/schema/validator.js.map +1 -0
  192. package/dist/schema-output/drizzle.d.ts +30 -0
  193. package/dist/schema-output/drizzle.d.ts.map +1 -0
  194. package/dist/{adapters/drizzle/generate.js → schema-output/drizzle.js} +82 -56
  195. package/dist/schema-output/drizzle.js.map +1 -0
  196. package/dist/schema-output/prisma.d.ts +17 -0
  197. package/dist/schema-output/prisma.d.ts.map +1 -0
  198. package/dist/schema-output/prisma.js +296 -0
  199. package/dist/schema-output/prisma.js.map +1 -0
  200. package/dist/util/default-database-adapter.js +61 -0
  201. package/dist/util/default-database-adapter.js.map +1 -0
  202. package/dist/with-database.d.ts +1 -1
  203. package/dist/with-database.d.ts.map +1 -1
  204. package/dist/with-database.js +12 -3
  205. package/dist/with-database.js.map +1 -1
  206. package/package.json +43 -28
  207. package/src/adapters/adapters.ts +30 -24
  208. package/src/adapters/drizzle/migrate-drizzle.test.ts +54 -33
  209. package/src/adapters/drizzle/migration-parity-drizzle-kit.test.ts +599 -0
  210. package/src/adapters/drizzle/test-utils.ts +12 -8
  211. package/src/adapters/generic-sql/driver-config.ts +38 -0
  212. package/src/adapters/generic-sql/generic-sql-adapter.test.ts +5 -5
  213. package/src/adapters/generic-sql/generic-sql-adapter.ts +110 -24
  214. package/src/adapters/generic-sql/generic-sql-uow-executor.test.ts +54 -0
  215. package/src/adapters/generic-sql/generic-sql-uow-executor.ts +231 -3
  216. package/src/adapters/generic-sql/migration/adapter-migration-parity.test.ts +118 -0
  217. package/src/adapters/generic-sql/migration/dialect/mysql.test.ts +26 -8
  218. package/src/adapters/generic-sql/migration/dialect/mysql.ts +46 -8
  219. package/src/adapters/generic-sql/migration/dialect/postgres.test.ts +25 -7
  220. package/src/adapters/generic-sql/migration/dialect/postgres.ts +8 -4
  221. package/src/adapters/generic-sql/migration/dialect/sqlite.test.ts +47 -8
  222. package/src/adapters/generic-sql/migration/dialect/sqlite.ts +27 -12
  223. package/src/adapters/generic-sql/migration/prepared-migrations.test.ts +128 -39
  224. package/src/adapters/generic-sql/migration/prepared-migrations.ts +15 -8
  225. package/src/adapters/generic-sql/migration/sql-generator.ts +142 -65
  226. package/src/adapters/generic-sql/query/create-sql-query-compiler.ts +9 -6
  227. package/src/adapters/generic-sql/query/cursor-utils.test.ts +271 -0
  228. package/src/adapters/generic-sql/query/cursor-utils.ts +41 -6
  229. package/src/adapters/generic-sql/query/generic-sql-uow-operation-compiler.test.ts +27 -27
  230. package/src/adapters/generic-sql/query/generic-sql-uow-operation-compiler.ts +38 -24
  231. package/src/adapters/generic-sql/query/select-builder.test.ts +15 -11
  232. package/src/adapters/generic-sql/query/select-builder.ts +6 -2
  233. package/src/adapters/generic-sql/query/sql-query-compiler.test.ts +52 -2
  234. package/src/adapters/generic-sql/query/sql-query-compiler.ts +50 -15
  235. package/src/adapters/generic-sql/query/where-builder.test.ts +91 -17
  236. package/src/adapters/generic-sql/query/where-builder.ts +90 -38
  237. package/src/adapters/{kysely/kysely-adapter-pglite.test.ts → generic-sql/sql-adapter-pglite-migrations.test.ts} +6 -6
  238. package/src/adapters/generic-sql/sql-adapter-pglite-pagination.test.ts +806 -0
  239. package/src/adapters/{drizzle/drizzle-adapter-pglite.test.ts → generic-sql/sql-adapter-pglite-queries.test.ts} +11 -11
  240. package/src/adapters/generic-sql/{test/generic-drizzle-adapter-sqlite3.test.ts → sql-adapter-sqlite3-driver.test.ts} +49 -35
  241. package/src/adapters/{drizzle/drizzle-adapter-sqlite3.test.ts → generic-sql/sql-adapter-sqlite3-uow.test.ts} +48 -32
  242. package/src/adapters/{kysely/kysely-adapter-sqlocal.test.ts → generic-sql/sql-adapter-sqlocal.test.ts} +6 -6
  243. package/src/adapters/generic-sql/sqlite-storage.ts +20 -0
  244. package/src/adapters/generic-sql/uow-decoder.test.ts +1 -1
  245. package/src/adapters/generic-sql/uow-decoder.ts +21 -3
  246. package/src/adapters/generic-sql/uow-encoder.test.ts +33 -2
  247. package/src/adapters/generic-sql/uow-encoder.ts +50 -11
  248. package/src/adapters/in-memory/condition-evaluator.test.ts +193 -0
  249. package/src/adapters/in-memory/condition-evaluator.ts +275 -0
  250. package/src/adapters/in-memory/errors.ts +20 -0
  251. package/src/adapters/in-memory/in-memory-adapter.ts +277 -0
  252. package/src/adapters/in-memory/in-memory-uow.mutations.test.ts +296 -0
  253. package/src/adapters/in-memory/in-memory-uow.retrieval.test.ts +100 -0
  254. package/src/adapters/in-memory/in-memory-uow.ts +1348 -0
  255. package/src/adapters/in-memory/index.ts +3 -0
  256. package/src/adapters/in-memory/options.test.ts +41 -0
  257. package/src/adapters/in-memory/options.ts +87 -0
  258. package/src/adapters/in-memory/reference-resolution.test.ts +50 -0
  259. package/src/adapters/in-memory/reference-resolution.ts +67 -0
  260. package/src/adapters/in-memory/sorted-array-index.test.ts +123 -0
  261. package/src/adapters/in-memory/sorted-array-index.ts +228 -0
  262. package/src/adapters/in-memory/store.test.ts +68 -0
  263. package/src/adapters/in-memory/store.ts +145 -0
  264. package/src/adapters/in-memory/value-comparison.ts +53 -0
  265. package/src/adapters/in-memory/value-normalization.test.ts +57 -0
  266. package/src/adapters/prisma/prisma-adapter-sqlite3.test.ts +1163 -0
  267. package/src/adapters/shared/from-unit-of-work-compiler.ts +3 -1
  268. package/src/adapters/shared/uow-operation-compiler.ts +26 -16
  269. package/src/adapters/sql/index.ts +12 -0
  270. package/src/db-fragment-definition-builder.test.ts +88 -54
  271. package/src/db-fragment-definition-builder.ts +201 -322
  272. package/src/db-fragment-instantiator.test.ts +169 -101
  273. package/src/db-fragment-integration.test.ts +301 -149
  274. package/src/dispatchers/cloudflare-do/index.test.ts +73 -0
  275. package/src/dispatchers/cloudflare-do/index.ts +104 -0
  276. package/src/dispatchers/node/index.test.ts +91 -0
  277. package/src/dispatchers/node/index.ts +87 -0
  278. package/src/fragments/internal-fragment.routes.ts +42 -0
  279. package/src/fragments/internal-fragment.schema.ts +51 -0
  280. package/src/fragments/internal-fragment.test.ts +730 -274
  281. package/src/fragments/internal-fragment.ts +447 -154
  282. package/src/hooks/durable-hooks-processor.test.ts +117 -0
  283. package/src/hooks/durable-hooks-processor.ts +67 -0
  284. package/src/hooks/hooks.test.ts +411 -259
  285. package/src/hooks/hooks.ts +265 -66
  286. package/src/migration-engine/auto-from-schema.test.ts +14 -14
  287. package/src/migration-engine/auto-from-schema.ts +5 -2
  288. package/src/migration-engine/create.test.ts +2 -2
  289. package/src/migration-engine/generation-engine.test.ts +229 -104
  290. package/src/migration-engine/generation-engine.ts +94 -64
  291. package/src/migration-engine/shared.ts +1 -0
  292. package/src/mod.ts +78 -30
  293. package/src/naming/sql-naming.ts +180 -0
  294. package/src/outbox/outbox-builder.ts +241 -0
  295. package/src/outbox/outbox.test.ts +253 -0
  296. package/src/outbox/outbox.ts +137 -0
  297. package/src/query/column-defaults.ts +41 -3
  298. package/src/query/condition-builder.test.ts +3 -3
  299. package/src/query/cursor.test.ts +116 -18
  300. package/src/query/cursor.ts +75 -26
  301. package/src/query/db-now.ts +6 -0
  302. package/src/query/query-type.test.ts +2 -2
  303. package/src/query/serialize/create-sql-serializer.ts +7 -2
  304. package/src/query/serialize/dialect/mysql-serializer.ts +12 -4
  305. package/src/query/serialize/dialect/postgres-serializer.ts +34 -4
  306. package/src/query/serialize/dialect/sqlite-serializer.test.ts +51 -1
  307. package/src/query/serialize/dialect/sqlite-serializer.ts +92 -9
  308. package/src/query/serialize/sql-serializer.ts +4 -4
  309. package/src/query/simple-query-interface.ts +5 -0
  310. package/src/query/unit-of-work/execute-unit-of-work.test.ts +1512 -1458
  311. package/src/query/unit-of-work/execute-unit-of-work.ts +1708 -596
  312. package/src/query/unit-of-work/tx-builder.test.ts +1041 -0
  313. package/src/query/unit-of-work/unit-of-work-coordinator.test.ts +32 -32
  314. package/src/query/unit-of-work/unit-of-work-types.test.ts +1 -1
  315. package/src/query/unit-of-work/unit-of-work.test.ts +231 -36
  316. package/src/query/unit-of-work/unit-of-work.ts +229 -31
  317. package/src/query/value-decoding.test.ts +13 -2
  318. package/src/query/value-decoding.ts +17 -4
  319. package/src/query/value-encoding.test.ts +85 -2
  320. package/src/query/value-encoding.ts +56 -6
  321. package/src/schema/create.test.ts +129 -42
  322. package/src/schema/create.ts +187 -47
  323. package/src/schema/generate-id.test.ts +57 -0
  324. package/src/schema/generate-id.ts +38 -0
  325. package/src/schema/serialize.test.ts +14 -2
  326. package/src/schema/type-conversion/create-sql-type-mapper.ts +7 -2
  327. package/src/schema/type-conversion/dialect/sqlite.ts +18 -0
  328. package/src/schema/type-conversion/type-mapping.test.ts +25 -1
  329. package/src/schema/validator.test.ts +197 -0
  330. package/src/schema/validator.ts +231 -0
  331. package/src/{adapters/drizzle/generate.test.ts → schema-output/drizzle.test.ts} +179 -129
  332. package/src/{adapters/drizzle/generate.ts → schema-output/drizzle.ts} +143 -93
  333. package/src/schema-output/prisma.test.ts +536 -0
  334. package/src/schema-output/prisma.ts +573 -0
  335. package/src/util/default-database-adapter.ts +106 -0
  336. package/src/with-database.ts +22 -3
  337. package/tsdown.config.ts +6 -4
  338. package/dist/adapters/drizzle/drizzle-adapter.d.ts +0 -20
  339. package/dist/adapters/drizzle/drizzle-adapter.d.ts.map +0 -1
  340. package/dist/adapters/drizzle/drizzle-adapter.js +0 -27
  341. package/dist/adapters/drizzle/drizzle-adapter.js.map +0 -1
  342. package/dist/adapters/drizzle/generate.d.ts +0 -30
  343. package/dist/adapters/drizzle/generate.d.ts.map +0 -1
  344. package/dist/adapters/drizzle/generate.js.map +0 -1
  345. package/dist/adapters/kysely/kysely-adapter.d.ts +0 -19
  346. package/dist/adapters/kysely/kysely-adapter.d.ts.map +0 -1
  347. package/dist/adapters/kysely/kysely-adapter.js +0 -17
  348. package/dist/adapters/kysely/kysely-adapter.js.map +0 -1
  349. package/dist/adapters/shared/table-name-mapper.d.ts +0 -12
  350. package/dist/adapters/shared/table-name-mapper.d.ts.map +0 -1
  351. package/dist/adapters/shared/table-name-mapper.js +0 -43
  352. package/dist/adapters/shared/table-name-mapper.js.map +0 -1
  353. package/dist/node_modules/.pnpm/rou3@0.7.10/node_modules/rou3/dist/index.js.map +0 -1
  354. package/dist/schema-generator/schema-generator.d.ts +0 -15
  355. package/dist/schema-generator/schema-generator.d.ts.map +0 -1
  356. package/src/adapters/drizzle/drizzle-adapter.ts +0 -39
  357. package/src/adapters/kysely/kysely-adapter.ts +0 -27
  358. package/src/adapters/shared/table-name-mapper.ts +0 -50
  359. package/src/schema-generator/schema-generator.ts +0 -12
  360. package/src/shared/config.ts +0 -10
  361. package/src/shared/connection-pool.ts +0 -24
  362. package/src/shared/prisma.ts +0 -45
@@ -2,7 +2,6 @@ import type { AnySchema } from "./schema/create";
2
2
  import type { SimpleQueryInterface } from "./query/simple-query-interface";
3
3
  import type { DatabaseAdapter } from "./adapters/adapters";
4
4
  import type { IUnitOfWork } from "./query/unit-of-work/unit-of-work";
5
- import { TypedUnitOfWork, UnitOfWork } from "./query/unit-of-work/unit-of-work";
6
5
  import type {
7
6
  RequestThisContext,
8
7
  FragnoPublicConfig,
@@ -14,31 +13,42 @@ import {
14
13
  type ServiceConstructorFn,
15
14
  } from "@fragno-dev/core";
16
15
  import {
17
- executeRestrictedUnitOfWork,
18
- executeTxArray,
19
- executeTxCallbacks,
20
- executeServiceTx,
21
- type AwaitedPromisesInObject,
22
- type ExecuteRestrictedUnitOfWorkOptions,
23
- type HandlerTxCallbacks,
24
- type ServiceTxCallbacks,
16
+ createServiceTxBuilder,
17
+ createHandlerTxBuilder,
18
+ ServiceTxBuilder,
19
+ HandlerTxBuilder,
20
+ type ExecuteTxOptions,
25
21
  } from "./query/unit-of-work/execute-unit-of-work";
26
22
  import {
27
23
  prepareHookMutations,
28
- processHooks,
29
24
  type HooksMap,
30
25
  type HookFn,
31
26
  type HookContext,
27
+ type HookProcessorConfig,
28
+ type DurableHooksProcessingOptions,
29
+ createHookScheduler,
32
30
  } from "./hooks/hooks";
33
31
  import type { InternalFragmentInstance } from "./fragments/internal-fragment";
32
+ import { resolveDatabaseAdapter } from "./util/default-database-adapter";
33
+ import { sanitizeNamespace } from "./naming/sql-naming";
34
34
 
35
35
  /**
36
- * Extended FragnoPublicConfig that includes a database adapter.
37
- * Use this type when creating fragments with database support.
36
+ * Extended FragnoPublicConfig for database fragments.
37
+ * If databaseAdapter is omitted and better-sqlite3 is available, a default SQLite adapter is used.
38
38
  */
39
39
  export type FragnoPublicConfigWithDatabase = FragnoPublicConfig & {
40
40
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
41
- databaseAdapter: DatabaseAdapter<any>;
41
+ databaseAdapter?: DatabaseAdapter<any>;
42
+ /**
43
+ * Optional durable hooks processing configuration.
44
+ */
45
+ durableHooks?: DurableHooksProcessingOptions;
46
+ /**
47
+ * Optional override for database namespace. If provided (including null), it is used as-is
48
+ * without sanitization — the caller is responsible for providing a valid namespace.
49
+ * When omitted, defaults to a sanitized version of schema.name.
50
+ */
51
+ databaseNamespace?: string | null;
42
52
  };
43
53
 
44
54
  /**
@@ -48,6 +58,7 @@ export type FragnoPublicConfigWithDatabase = FragnoPublicConfig & {
48
58
  export type ImplicitDatabaseDependencies<TSchema extends AnySchema> = {
49
59
  /**
50
60
  * Database query engine for the fragment's schema.
61
+ * @deprecated Prefer handlerTx/serviceTx instead of direct db usage.
51
62
  */
52
63
  db: SimpleQueryInterface<TSchema>;
53
64
  /**
@@ -57,7 +68,7 @@ export type ImplicitDatabaseDependencies<TSchema extends AnySchema> = {
57
68
  /**
58
69
  * The database namespace for this fragment.
59
70
  */
60
- namespace: string;
71
+ namespace: string | null;
61
72
  /**
62
73
  * Create a new Unit of Work for database operations.
63
74
  */
@@ -69,35 +80,38 @@ export type ImplicitDatabaseDependencies<TSchema extends AnySchema> = {
69
80
  */
70
81
  export type DatabaseServiceContext<THooks extends HooksMap> = RequestThisContext & {
71
82
  /**
72
- * Get a typed, restricted Unit of Work for the given schema.
73
- * @param schema - Schema to get a typed view for
74
- * @returns TypedUnitOfWork (restricted version without execute methods)
75
- */
76
- uow<TSchema extends AnySchema>(schema: TSchema): TypedUnitOfWork<TSchema, [], unknown, THooks>;
77
-
78
- /**
79
- * Execute a transaction with two-phase callbacks (retrieve + mutate).
80
- *
81
- * @param schema - Schema to use for the transaction
82
- * @param callbacks - Object containing retrieve and mutate callbacks
83
- * @returns Promise resolving to the mutation result with promises awaited 1 level deep
83
+ * Create a service-level transaction builder using the fluent API.
84
+ * Returns a builder that can be chained with withServiceCalls, retrieve,
85
+ * transformRetrieve, mutate, transform, and build.
84
86
  *
85
87
  * @example
86
88
  * ```ts
87
- * return this.tx(schema, {
88
- * retrieve: (uow) => uow.findFirst("users", ...),
89
- * mutate: async (uow, [user]) => {
90
- * await validateUser(user);
91
- * uow.update("users", user.id, ...).check();
92
- * return { ok: true };
93
- * }
94
- * });
89
+ * return this.serviceTx(schema)
90
+ * .withServiceCalls(() => [otherService.getData()])
91
+ * .retrieve((uow) => uow.find("users", ...))
92
+ * .transformRetrieve(([users]) => users[0])
93
+ * .mutate(({ uow, retrieveResult, serviceIntermediateResult }) =>
94
+ * uow.create("records", { ... })
95
+ * )
96
+ * .transform(({ mutateResult, serviceResult }) => ({ id: mutateResult }))
97
+ * .build();
95
98
  * ```
96
99
  */
97
- tx<TSchema extends AnySchema, TRetrievalResults extends unknown[], TMutationResult = void>(
100
+ serviceTx<TSchema extends AnySchema>(
98
101
  schema: TSchema,
99
- callbacks: ServiceTxCallbacks<TSchema, TRetrievalResults, TMutationResult, THooks>,
100
- ): Promise<AwaitedPromisesInObject<TMutationResult>>;
102
+ ): ServiceTxBuilder<
103
+ TSchema,
104
+ readonly [],
105
+ [],
106
+ [],
107
+ unknown,
108
+ unknown,
109
+ false,
110
+ false,
111
+ false,
112
+ false,
113
+ THooks
114
+ >;
101
115
  };
102
116
 
103
117
  /**
@@ -105,92 +119,24 @@ export type DatabaseServiceContext<THooks extends HooksMap> = RequestThisContext
105
119
  */
106
120
  export type DatabaseHandlerContext<THooks extends HooksMap = {}> = RequestThisContext & {
107
121
  /**
108
- * Execute a Unit of Work with explicit phase control and automatic retry support.
109
- * This method provides an API where users call forSchema to create a schema-specific
110
- * UOW, then call executeRetrieve() and executeMutate() to execute the phases. The entire
111
- * callback is re-executed on optimistic concurrency conflicts, ensuring retries work properly.
112
- * Automatically provides the UOW factory from context.
113
- * Promises in the returned object are awaited 1 level deep.
114
- *
115
- * @param callback - Async function that receives a context with forSchema, executeRetrieve, executeMutate, nonce, and currentAttempt
116
- * @param options - Optional configuration for retry policy and abort signal
117
- * @returns Promise resolving to the callback's return value with promises awaited 1 level deep
118
- * @throws Error if retries are exhausted or callback throws an error
122
+ * Create a handler-level transaction builder using the fluent API.
123
+ * Returns a builder that can be chained with withServiceCalls, retrieve,
124
+ * transformRetrieve, mutate, transform, and execute.
119
125
  *
120
126
  * @example
121
127
  * ```ts
122
- * const result = await this.uow(async ({ forSchema, executeRetrieve, executeMutate, nonce, currentAttempt }) => {
123
- * const uow = forSchema(schema);
124
- * const userId = uow.create("users", { name: "John" });
125
- *
126
- * // Execute retrieval phase
127
- * await executeRetrieve();
128
- *
129
- * const profileId = uow.create("profiles", { userId });
130
- *
131
- * // Execute mutation phase
132
- * await executeMutate();
133
- *
134
- * return { userId, profileId };
135
- * });
136
- * ```
137
- */
138
- uow<TResult>(
139
- callback: (context: {
140
- forSchema: <TSchema extends AnySchema, H extends HooksMap = THooks>(
141
- schema: TSchema,
142
- hooks?: H,
143
- ) => TypedUnitOfWork<TSchema, [], unknown, H>;
144
- executeRetrieve: () => Promise<void>;
145
- executeMutate: () => Promise<void>;
146
- nonce: string;
147
- currentAttempt: number;
148
- }) => Promise<TResult> | TResult,
149
- options?: Omit<ExecuteRestrictedUnitOfWorkOptions, "createUnitOfWork">,
150
- ): Promise<AwaitedPromisesInObject<TResult>>;
151
-
152
- /**
153
- * Execute a transaction with automatic retry support.
154
- * Provides two overloads: array syntax (common case) and callback syntax (advanced).
155
- *
156
- * Array syntax - pass a factory function that returns service calls:
157
- * ```ts
158
- * const [transfer, notify] = await this.tx(
159
- * () => [
160
- * services.transfer({ from, to, amount }),
161
- * services.notify({ userId, message })
162
- * ]
163
- * );
164
- * ```
165
- *
166
- * Callback syntax - for handlers that need direct UOW access:
167
- * ```ts
168
- * const result = await this.tx({
169
- * retrieve: ({ forSchema }) => {
170
- * const uow = forSchema(schema);
171
- * uow.find("users", ...);
172
- * return services.transfer({ ... });
173
- * },
174
- * mutate: ({ forSchema }, transferPromise) => {
175
- * const uow = forSchema(schema);
176
- * uow.create("auditLog", { ... });
177
- * return transferPromise;
178
- * }
179
- * });
128
+ * const result = await this.handlerTx()
129
+ * .withServiceCalls(() => [userService.getUser(id)])
130
+ * .mutate(({ forSchema, idempotencyKey, currentAttempt, serviceIntermediateResult }) => {
131
+ * return forSchema(ordersSchema).create("orders", { ... });
132
+ * })
133
+ * .transform(({ mutateResult, serviceResult }) => ({ ... }))
134
+ * .execute();
180
135
  * ```
181
- *
182
- * Note: Handler callbacks are synchronous only to prevent accidentally awaiting services in wrong place.
183
136
  */
184
- // Overload 1: Array syntax (common case) - accepts a factory
185
- tx<T extends readonly unknown[]>(
186
- servicesFactory: () => readonly [...{ [K in keyof T]: Promise<T[K]> }],
187
- options?: Omit<ExecuteRestrictedUnitOfWorkOptions, "createUnitOfWork">,
188
- ): Promise<{ [K in keyof T]: T[K] }>;
189
- // Overload 2: Callback syntax (advanced, sync callbacks only)
190
- tx<TRetrieveResult, TMutationResult>(
191
- callbacks: HandlerTxCallbacks<TRetrieveResult, TMutationResult, THooks>,
192
- options?: Omit<ExecuteRestrictedUnitOfWorkOptions, "createUnitOfWork">,
193
- ): Promise<AwaitedPromisesInObject<TMutationResult>>;
137
+ handlerTx(
138
+ options?: Omit<ExecuteTxOptions, "createUnitOfWork">,
139
+ ): HandlerTxBuilder<readonly [], [], [], unknown, unknown, false, false, false, false, THooks>;
194
140
  };
195
141
 
196
142
  /**
@@ -214,21 +160,23 @@ export type DatabaseFragmentContext<TSchema extends AnySchema> = {
214
160
  function createDatabaseContext<TSchema extends AnySchema>(
215
161
  options: FragnoPublicConfigWithDatabase,
216
162
  schema: TSchema,
217
- namespace: string,
218
163
  ): DatabaseFragmentContext<TSchema> {
219
- const databaseAdapter = options.databaseAdapter;
220
-
221
- if (!databaseAdapter) {
222
- throw new Error(
223
- "Database fragment requires a database adapter to be provided in options.databaseAdapter",
224
- );
225
- }
164
+ const databaseAdapter = resolveDatabaseAdapter(options, schema);
226
165
 
166
+ const namespace = resolveDatabaseNamespace(options, schema);
227
167
  const db = databaseAdapter.createQueryEngine(schema, namespace);
228
168
 
229
169
  return { databaseAdapter, db };
230
170
  }
231
171
 
172
+ function resolveDatabaseNamespace<TSchema extends AnySchema>(
173
+ options: FragnoPublicConfigWithDatabase,
174
+ schema: TSchema,
175
+ ): string | null {
176
+ const hasOverride = options.databaseNamespace !== undefined;
177
+ return hasOverride ? (options.databaseNamespace ?? null) : sanitizeNamespace(schema.name);
178
+ }
179
+
232
180
  /**
233
181
  * Storage type for database fragments - stores the Unit of Work.
234
182
  */
@@ -240,7 +188,7 @@ export type DatabaseRequestStorage = {
240
188
  * Builder for database fragments that wraps the core fragment builder
241
189
  * and provides database-specific functionality.
242
190
  *
243
- * Database fragments always require FragnoPublicConfigWithDatabase (which includes databaseAdapter).
191
+ * Database fragments use FragnoPublicConfigWithDatabase and default the adapter when possible.
244
192
  */
245
193
  export class DatabaseFragmentDefinitionBuilder<
246
194
  TSchema extends AnySchema,
@@ -270,7 +218,6 @@ export class DatabaseFragmentDefinitionBuilder<
270
218
  TLinkedFragments
271
219
  >;
272
220
  #schema: TSchema;
273
- #namespace: string;
274
221
  #hooksFactory?: (context: { config: TConfig; options: FragnoPublicConfigWithDatabase }) => THooks;
275
222
 
276
223
  constructor(
@@ -288,7 +235,6 @@ export class DatabaseFragmentDefinitionBuilder<
288
235
  TLinkedFragments
289
236
  >,
290
237
  schema: TSchema,
291
- namespace?: string,
292
238
  hooksFactory?: (context: {
293
239
  config: TConfig;
294
240
  options: FragnoPublicConfigWithDatabase;
@@ -296,7 +242,6 @@ export class DatabaseFragmentDefinitionBuilder<
296
242
  ) {
297
243
  this.#baseBuilder = baseBuilder;
298
244
  this.#schema = schema;
299
- this.#namespace = namespace ?? baseBuilder.name;
300
245
  this.#hooksFactory = hooksFactory;
301
246
  }
302
247
 
@@ -326,7 +271,8 @@ export class DatabaseFragmentDefinitionBuilder<
326
271
  > {
327
272
  // Wrap user function to inject DB context
328
273
  const wrappedFn = (context: { config: TConfig; options: FragnoPublicConfigWithDatabase }) => {
329
- const dbContext = createDatabaseContext(context.options, this.#schema, this.#namespace);
274
+ const dbContext = createDatabaseContext(context.options, this.#schema);
275
+ const namespace = resolveDatabaseNamespace(context.options, this.#schema);
330
276
 
331
277
  // Call user function with enriched context
332
278
  const userDeps = fn({
@@ -341,7 +287,7 @@ export class DatabaseFragmentDefinitionBuilder<
341
287
  const implicitDeps: ImplicitDatabaseDependencies<TSchema> = {
342
288
  db: dbContext.db,
343
289
  schema: this.#schema,
344
- namespace: this.#namespace,
290
+ namespace,
345
291
  createUnitOfWork: createUow,
346
292
  };
347
293
 
@@ -354,12 +300,7 @@ export class DatabaseFragmentDefinitionBuilder<
354
300
  // Create new base builder with wrapped function
355
301
  const newBaseBuilder = this.#baseBuilder.withDependencies(wrappedFn);
356
302
 
357
- return new DatabaseFragmentDefinitionBuilder(
358
- newBaseBuilder,
359
- this.#schema,
360
- this.#namespace,
361
- this.#hooksFactory,
362
- );
303
+ return new DatabaseFragmentDefinitionBuilder(newBaseBuilder, this.#schema, this.#hooksFactory);
363
304
  }
364
305
 
365
306
  providesBaseService<TNewService>(
@@ -387,12 +328,7 @@ export class DatabaseFragmentDefinitionBuilder<
387
328
  > {
388
329
  const newBaseBuilder = this.#baseBuilder.providesBaseService<TNewService>(fn);
389
330
 
390
- return new DatabaseFragmentDefinitionBuilder(
391
- newBaseBuilder,
392
- this.#schema,
393
- this.#namespace,
394
- this.#hooksFactory,
395
- );
331
+ return new DatabaseFragmentDefinitionBuilder(newBaseBuilder, this.#schema, this.#hooksFactory);
396
332
  }
397
333
 
398
334
  providesService<TServiceName extends string, TService>(
@@ -424,12 +360,7 @@ export class DatabaseFragmentDefinitionBuilder<
424
360
  fn,
425
361
  );
426
362
 
427
- return new DatabaseFragmentDefinitionBuilder(
428
- newBaseBuilder,
429
- this.#schema,
430
- this.#namespace,
431
- this.#hooksFactory,
432
- );
363
+ return new DatabaseFragmentDefinitionBuilder(newBaseBuilder, this.#schema, this.#hooksFactory);
433
364
  }
434
365
 
435
366
  /**
@@ -468,12 +399,7 @@ export class DatabaseFragmentDefinitionBuilder<
468
399
  fn,
469
400
  );
470
401
 
471
- return new DatabaseFragmentDefinitionBuilder(
472
- newBaseBuilder,
473
- this.#schema,
474
- this.#namespace,
475
- this.#hooksFactory,
476
- );
402
+ return new DatabaseFragmentDefinitionBuilder(newBaseBuilder, this.#schema, this.#hooksFactory);
477
403
  }
478
404
 
479
405
  /**
@@ -487,7 +413,7 @@ export class DatabaseFragmentDefinitionBuilder<
487
413
  * ```ts
488
414
  * .provideHooks(({ defineHook, config }) => ({
489
415
  * onSubscribe: defineHook(async function (payload: { email: string }) {
490
- * // 'this' context available (HookServiceContext with nonce)
416
+ * // 'this' context available (HookServiceContext with idempotencyKey)
491
417
  * await config.onSubscribe?.(payload.email);
492
418
  * }),
493
419
  * }))
@@ -537,7 +463,6 @@ export class DatabaseFragmentDefinitionBuilder<
537
463
  const newBuilder = new DatabaseFragmentDefinitionBuilder(
538
464
  this.#baseBuilder,
539
465
  this.#schema,
540
- this.#namespace,
541
466
  ) as unknown as DatabaseFragmentDefinitionBuilder<
542
467
  TSchema,
543
468
  TConfig,
@@ -578,12 +503,7 @@ export class DatabaseFragmentDefinitionBuilder<
578
503
  > {
579
504
  const newBaseBuilder = this.#baseBuilder.usesService<TServiceName, TService>(serviceName);
580
505
 
581
- return new DatabaseFragmentDefinitionBuilder(
582
- newBaseBuilder,
583
- this.#schema,
584
- this.#namespace,
585
- this.#hooksFactory,
586
- );
506
+ return new DatabaseFragmentDefinitionBuilder(newBaseBuilder, this.#schema, this.#hooksFactory);
587
507
  }
588
508
 
589
509
  /**
@@ -609,12 +529,7 @@ export class DatabaseFragmentDefinitionBuilder<
609
529
  serviceName,
610
530
  );
611
531
 
612
- return new DatabaseFragmentDefinitionBuilder(
613
- newBaseBuilder,
614
- this.#schema,
615
- this.#namespace,
616
- this.#hooksFactory,
617
- );
532
+ return new DatabaseFragmentDefinitionBuilder(newBaseBuilder, this.#schema, this.#hooksFactory);
618
533
  }
619
534
 
620
535
  /**
@@ -663,12 +578,13 @@ export class DatabaseFragmentDefinitionBuilder<
663
578
  }
664
579
  }
665
580
 
666
- const { db } = createDatabaseContext(context.options, this.#schema, this.#namespace);
581
+ const { db } = createDatabaseContext(context.options, this.#schema);
582
+ const namespace = resolveDatabaseNamespace(context.options, this.#schema);
667
583
 
668
584
  const implicitDeps: ImplicitDatabaseDependencies<TSchema> = {
669
585
  db,
670
586
  schema: this.#schema,
671
- namespace: this.#namespace,
587
+ namespace,
672
588
  createUnitOfWork: () => db.createUnitOfWork(),
673
589
  };
674
590
 
@@ -681,7 +597,7 @@ export class DatabaseFragmentDefinitionBuilder<
681
597
  // Use the adapter's shared context storage (all fragments using the same adapter share this storage)
682
598
  const builderWithExternalStorage = this.#baseBuilder.withExternalRequestStorage(
683
599
  ({ options }) => {
684
- const dbContext = createDatabaseContext(options, this.#schema, this.#namespace);
600
+ const dbContext = createDatabaseContext(options, this.#schema);
685
601
  return dbContext.databaseAdapter.contextStorage;
686
602
  },
687
603
  );
@@ -690,7 +606,7 @@ export class DatabaseFragmentDefinitionBuilder<
690
606
  const builderWithStorage = builderWithExternalStorage.withRequestStorage(
691
607
  ({ options }): DatabaseRequestStorage => {
692
608
  // Create database context - needed here to create the UOW
693
- const dbContextForStorage = createDatabaseContext(options, this.#schema, this.#namespace);
609
+ const dbContextForStorage = createDatabaseContext(options, this.#schema);
694
610
 
695
611
  // Create a new Unit of Work for this request
696
612
  const uow: IUnitOfWork = dbContextForStorage.db.createUnitOfWork();
@@ -706,68 +622,109 @@ export class DatabaseFragmentDefinitionBuilder<
706
622
  options: FragnoPublicConfigWithDatabase;
707
623
  }) => InternalFragmentInstance;
708
624
 
625
+ // Cache per instantiated fragment (deps object is unique per instantiation).
626
+ const hooksConfigCache = new WeakMap<object, HookProcessorConfig<THooks>>();
627
+
628
+ const createHooksConfig = (context: {
629
+ config: TConfig;
630
+ options: FragnoPublicConfigWithDatabase;
631
+ deps: TDeps;
632
+ }) => {
633
+ if (!this.#hooksFactory) {
634
+ return undefined;
635
+ }
636
+ const depsKey =
637
+ typeof context.deps === "object" && context.deps !== null
638
+ ? (context.deps as object)
639
+ : undefined;
640
+ const cachedHooksConfig = depsKey ? hooksConfigCache.get(depsKey) : undefined;
641
+ if (cachedHooksConfig) {
642
+ return cachedHooksConfig;
643
+ }
644
+
645
+ const namespace = resolveDatabaseNamespace(context.options, this.#schema);
646
+ const namespaceKey = namespace ?? this.#schema.name;
647
+ const durableHooksOptions = context.options.durableHooks;
648
+ const baseAdapter = resolveDatabaseAdapter(context.options, this.#schema);
649
+ const hookAdapter = baseAdapter.getHookProcessingAdapter?.() ?? baseAdapter;
650
+ const hookOptions =
651
+ hookAdapter === baseAdapter
652
+ ? context.options
653
+ : { ...context.options, databaseAdapter: hookAdapter };
654
+ const dbContextForHooks = createDatabaseContext(hookOptions, this.#schema);
655
+ const hooksConfig: HookProcessorConfig<THooks> = {
656
+ hooks: this.#hooksFactory(context),
657
+ namespace: namespaceKey,
658
+ internalFragment: internalFragmentFactory({
659
+ config: context.config,
660
+ options: hookOptions,
661
+ }),
662
+ handlerTx: (execOptions?: Omit<ExecuteTxOptions, "createUnitOfWork">) => {
663
+ const userOnBeforeMutate = execOptions?.onBeforeMutate;
664
+ const userOnAfterMutate = execOptions?.onAfterMutate;
665
+ return createHandlerTxBuilder<THooks>({
666
+ ...execOptions,
667
+ createUnitOfWork: () => {
668
+ const uow = dbContextForHooks.db.createUnitOfWork();
669
+ uow.registerSchema(
670
+ hooksConfig.internalFragment.$internal.deps.schema,
671
+ hooksConfig.internalFragment.$internal.deps.namespace,
672
+ );
673
+ return uow;
674
+ },
675
+ onBeforeMutate: (uow) => {
676
+ prepareHookMutations(uow, hooksConfig);
677
+ if (userOnBeforeMutate) {
678
+ userOnBeforeMutate(uow);
679
+ }
680
+ },
681
+ onAfterMutate: async (uow) => {
682
+ void hooksConfig.scheduler?.schedule().catch((error) => {
683
+ console.error("Durable hooks processing failed", error);
684
+ });
685
+ if (userOnAfterMutate) {
686
+ await userOnAfterMutate(uow);
687
+ }
688
+ },
689
+ });
690
+ },
691
+ stuckProcessingTimeoutMinutes: durableHooksOptions?.stuckProcessingTimeoutMinutes,
692
+ onStuckProcessingHooks: durableHooksOptions?.onStuckProcessingHooks,
693
+ };
694
+ hooksConfig.scheduler = createHookScheduler(hooksConfig);
695
+ if (depsKey) {
696
+ hooksConfigCache.set(depsKey, hooksConfig);
697
+ }
698
+ return hooksConfig;
699
+ };
700
+
709
701
  const builderWithContext = builderWithStorage.withThisContext<
710
702
  DatabaseServiceContext<THooks>,
711
703
  DatabaseHandlerContext<THooks>
712
- >(({ storage, config, options }) => {
704
+ >(({ storage, config, options, deps }) => {
713
705
  // Create hooks config if hooks factory is defined
714
- const hooksConfig = this.#hooksFactory
715
- ? {
716
- hooks: this.#hooksFactory({ config, options }),
717
- namespace: this.#namespace,
718
- internalFragment: internalFragmentFactory({ config, options }),
719
- }
720
- : undefined;
721
-
722
- function forSchema<TSchema extends AnySchema>(
723
- schema: TSchema,
724
- ): TypedUnitOfWork<TSchema, [], unknown, THooks> {
725
- const uow = storage.getStore()?.uow;
726
- if (!uow) {
727
- throw new Error(
728
- "No UnitOfWork in context. Service must be called within a route handler OR using `withUnitOfWork`.",
729
- );
730
- }
731
-
732
- return uow.restrict().forSchema<TSchema, THooks>(schema);
733
- }
706
+ const hooksConfig = createHooksConfig({ config, options, deps });
707
+ const internalFragment =
708
+ hooksConfig?.internalFragment ??
709
+ (internalFragmentFactory ? internalFragmentFactory({ config, options }) : undefined);
734
710
 
735
- const serviceTx = async <
736
- TSchema extends AnySchema,
737
- TRetrievalResults extends unknown[],
738
- TMutationResult = void,
739
- >(
740
- schema: TSchema,
741
- callbacks: ServiceTxCallbacks<TSchema, TRetrievalResults, TMutationResult, THooks>,
742
- ): Promise<AwaitedPromisesInObject<TMutationResult>> => {
711
+ // Builder API: serviceTx using createServiceTxBuilder
712
+ function serviceTx<TSchema extends AnySchema>(schema: TSchema) {
743
713
  const uow = storage.getStore()?.uow;
744
714
  if (!uow) {
745
715
  throw new Error(
746
716
  "No UnitOfWork in context. Service must be called within a route handler OR using `withUnitOfWork`.",
747
717
  );
748
718
  }
749
-
750
- return executeServiceTx(schema, callbacks, uow);
751
- };
719
+ return createServiceTxBuilder<TSchema, THooks>(schema, uow, hooksConfig?.hooks);
720
+ }
752
721
 
753
722
  const serviceContext: DatabaseServiceContext<THooks> = {
754
- uow: forSchema,
755
- tx: serviceTx,
723
+ serviceTx,
756
724
  };
757
725
 
758
- async function uow<TResult>(
759
- callback: (context: {
760
- forSchema: <S extends AnySchema, H extends HooksMap = THooks>(
761
- schema: S,
762
- hooks?: H,
763
- ) => TypedUnitOfWork<S, [], unknown, H>;
764
- executeRetrieve: () => Promise<void>;
765
- executeMutate: () => Promise<void>;
766
- nonce: string;
767
- currentAttempt: number;
768
- }) => Promise<TResult> | TResult,
769
- execOptions?: Omit<ExecuteRestrictedUnitOfWorkOptions, "createUnitOfWork">,
770
- ): Promise<AwaitedPromisesInObject<TResult>> {
726
+ // Builder API: handlerTx using createHandlerTxBuilder
727
+ function handlerTx(execOptions?: Omit<ExecuteTxOptions, "createUnitOfWork">) {
771
728
  const currentStorage = storage.getStore();
772
729
  if (!currentStorage) {
773
730
  throw new Error(
@@ -775,34 +732,20 @@ export class DatabaseFragmentDefinitionBuilder<
775
732
  );
776
733
  }
777
734
 
778
- const wrappedCallback = async (context: {
779
- forSchema: <S extends AnySchema, H extends HooksMap = THooks>(
780
- schema: S,
781
- hooks?: H,
782
- ) => TypedUnitOfWork<S, [], unknown, H>;
783
- executeRetrieve: () => Promise<void>;
784
- executeMutate: () => Promise<void>;
785
- nonce: string;
786
- currentAttempt: number;
787
- }): Promise<TResult> => {
788
- return callback(context);
789
- };
790
-
791
735
  const userOnBeforeMutate = execOptions?.onBeforeMutate;
792
- const userOnSuccess = execOptions?.onSuccess;
736
+ const userOnAfterMutate = execOptions?.onAfterMutate;
793
737
 
794
- return executeRestrictedUnitOfWork(wrappedCallback, {
738
+ return createHandlerTxBuilder<THooks>({
795
739
  ...execOptions,
796
740
  createUnitOfWork: () => {
797
741
  currentStorage.uow.reset();
798
- if (hooksConfig) {
742
+ if (internalFragment) {
799
743
  currentStorage.uow.registerSchema(
800
- hooksConfig.internalFragment.$internal.deps.schema,
801
- hooksConfig.internalFragment.$internal.deps.namespace,
744
+ internalFragment.$internal.deps.schema,
745
+ internalFragment.$internal.deps.namespace,
802
746
  );
803
747
  }
804
- // Safe cast: currentStorage.uow is always a UnitOfWork instance
805
- return currentStorage.uow as UnitOfWork;
748
+ return currentStorage.uow;
806
749
  },
807
750
  onBeforeMutate: (uow) => {
808
751
  if (hooksConfig) {
@@ -812,94 +755,21 @@ export class DatabaseFragmentDefinitionBuilder<
812
755
  userOnBeforeMutate(uow);
813
756
  }
814
757
  },
815
- onSuccess: async (uow) => {
816
- if (hooksConfig) {
817
- await processHooks(hooksConfig);
758
+ onAfterMutate: async (uow) => {
759
+ if (hooksConfig?.scheduler) {
760
+ void hooksConfig.scheduler.schedule().catch((error) => {
761
+ console.error("Durable hooks processing failed", error);
762
+ });
818
763
  }
819
- if (userOnSuccess) {
820
- await userOnSuccess(uow);
764
+ if (userOnAfterMutate) {
765
+ await userOnAfterMutate(uow);
821
766
  }
822
767
  },
823
768
  });
824
769
  }
825
770
 
826
- // Handler tx method with two overloads
827
- function handlerTx<T extends readonly unknown[]>(
828
- servicesFactory: () => readonly [...{ [K in keyof T]: Promise<T[K]> }],
829
- execOptions?: Omit<ExecuteRestrictedUnitOfWorkOptions, "createUnitOfWork">,
830
- ): Promise<{ [K in keyof T]: T[K] }>;
831
- function handlerTx<TRetrieveResult, TMutationResult>(
832
- callbacks: HandlerTxCallbacks<TRetrieveResult, TMutationResult, THooks>,
833
- execOptions?: Omit<ExecuteRestrictedUnitOfWorkOptions, "createUnitOfWork">,
834
- ): Promise<AwaitedPromisesInObject<TMutationResult>>;
835
- async function handlerTx<T extends readonly unknown[], TRetrieveResult, TMutationResult>(
836
- factoryOrCallbacks:
837
- | (() => readonly [...{ [K in keyof T]: Promise<T[K]> }])
838
- | HandlerTxCallbacks<TRetrieveResult, TMutationResult, THooks>,
839
- execOptions?: Omit<ExecuteRestrictedUnitOfWorkOptions, "createUnitOfWork">,
840
- ): Promise<{ [K in keyof T]: T[K] } | AwaitedPromisesInObject<TMutationResult>> {
841
- const currentStorage = storage.getStore();
842
- if (!currentStorage) {
843
- throw new Error(
844
- "No storage in context. Handler must be called within a request context.",
845
- );
846
- }
847
-
848
- const userOnBeforeMutate = execOptions?.onBeforeMutate;
849
- const userOnSuccess = execOptions?.onSuccess;
850
-
851
- const createUow = () => {
852
- currentStorage.uow.reset();
853
-
854
- if (hooksConfig) {
855
- currentStorage.uow.registerSchema(
856
- hooksConfig.internalFragment.$internal.deps.schema,
857
- hooksConfig.internalFragment.$internal.deps.namespace,
858
- );
859
- }
860
- // Safe cast: currentStorage.uow is always a UnitOfWork instance
861
- return currentStorage.uow as UnitOfWork;
862
- };
863
-
864
- const options: ExecuteRestrictedUnitOfWorkOptions = {
865
- ...execOptions,
866
- createUnitOfWork: createUow,
867
- onBeforeMutate: (uow) => {
868
- if (hooksConfig) {
869
- prepareHookMutations(uow, hooksConfig);
870
- }
871
- if (userOnBeforeMutate) {
872
- userOnBeforeMutate(uow);
873
- }
874
- },
875
- onSuccess: async (uow) => {
876
- if (hooksConfig) {
877
- await processHooks(hooksConfig);
878
- }
879
- if (userOnSuccess) {
880
- await userOnSuccess(uow);
881
- }
882
- },
883
- };
884
-
885
- // Check if it's a function (array syntax factory) or callbacks object (callback syntax)
886
- if (typeof factoryOrCallbacks === "function") {
887
- // Array syntax - factoryOrCallbacks is a factory function
888
- return executeTxArray(
889
- factoryOrCallbacks as () => readonly [...{ [K in keyof T]: Promise<T[K]> }],
890
- options,
891
- ) as Promise<{ [K in keyof T]: T[K] }>;
892
- } else {
893
- return executeTxCallbacks(
894
- factoryOrCallbacks as HandlerTxCallbacks<TRetrieveResult, TMutationResult, THooks>,
895
- options,
896
- );
897
- }
898
- }
899
-
900
771
  const handlerContext: DatabaseHandlerContext<THooks> = {
901
- uow,
902
- tx: handlerTx,
772
+ handlerTx,
903
773
  };
904
774
 
905
775
  return { serviceContext, handlerContext };
@@ -907,6 +777,15 @@ export class DatabaseFragmentDefinitionBuilder<
907
777
 
908
778
  // Build the final definition
909
779
  const finalDef = builderWithContext.build();
780
+ if (this.#hooksFactory) {
781
+ finalDef.internalDataFactory = ({ config, options, deps }) => ({
782
+ durableHooks: createHooksConfig({
783
+ config: config as TConfig,
784
+ options: options as FragnoPublicConfigWithDatabase,
785
+ deps: deps as TDeps,
786
+ }),
787
+ });
788
+ }
910
789
 
911
790
  // Return the complete definition with proper typing and dependencies
912
791
  return {