@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,17 +2,26 @@ import SQLite from "better-sqlite3";
2
2
  import { SqliteDialect } from "kysely";
3
3
  import { beforeAll, describe, expect, it, vi } from "vitest";
4
4
  import { instantiate } from "@fragno-dev/core";
5
- import { prepareHookMutations, processHooks, type HooksMap, type HookContext } from "./hooks";
5
+ import {
6
+ prepareHookMutations,
7
+ processHooks,
8
+ type HooksMap,
9
+ type HookContext,
10
+ type HookHandlerTx,
11
+ } from "./hooks";
6
12
  import { internalFragmentDef, internalSchema } from "../fragments/internal-fragment";
7
13
  import type { FragnoPublicConfigWithDatabase } from "../db-fragment-definition-builder";
8
- import { DrizzleAdapter } from "../adapters/drizzle/drizzle-adapter";
14
+ import { SqlAdapter } from "../adapters/generic-sql/generic-sql-adapter";
9
15
  import { BetterSQLite3DriverConfig } from "../adapters/generic-sql/driver-config";
10
16
  import { ExponentialBackoffRetryPolicy, NoRetryPolicy } from "../query/unit-of-work/retry-policy";
11
17
  import type { FragnoId } from "../schema/create";
12
18
 
13
19
  describe("Hook System", () => {
20
+ const handlerTx = (() => {
21
+ throw new Error("handlerTx not configured for hooks test");
22
+ }) as HookHandlerTx;
14
23
  let sqliteDatabase: SQLite.Database;
15
- let adapter: DrizzleAdapter;
24
+ let adapter: SqlAdapter;
16
25
  let internalFragment: ReturnType<typeof instantiateFragment>;
17
26
 
18
27
  function instantiateFragment(options: FragnoPublicConfigWithDatabase) {
@@ -26,18 +35,19 @@ describe("Hook System", () => {
26
35
  database: sqliteDatabase,
27
36
  });
28
37
 
29
- adapter = new DrizzleAdapter({
38
+ adapter = new SqlAdapter({
30
39
  dialect,
31
40
  driverConfig: new BetterSQLite3DriverConfig(),
32
41
  });
33
42
 
34
43
  {
35
- const migrations = adapter.prepareMigrations(internalSchema, "");
44
+ const migrations = adapter.prepareMigrations(internalSchema, null);
36
45
  await migrations.executeWithDriver(adapter.driver, 0);
37
46
  }
38
47
 
39
48
  const options: FragnoPublicConfigWithDatabase = {
40
49
  databaseAdapter: adapter,
50
+ databaseNamespace: null,
41
51
  };
42
52
 
43
53
  internalFragment = instantiateFragment(options);
@@ -57,8 +67,11 @@ describe("Hook System", () => {
57
67
  const onBeforeMutate = vi.fn();
58
68
 
59
69
  await internalFragment.inContext(async function () {
60
- await this.uow(
61
- async ({ forSchema, executeMutate }) => {
70
+ await this.handlerTx({
71
+ onAfterMutate: onSuccess,
72
+ onBeforeMutate,
73
+ })
74
+ .mutate(({ forSchema }) => {
62
75
  const uow = forSchema(internalSchema, hooks);
63
76
 
64
77
  // Trigger a hook
@@ -69,16 +82,11 @@ describe("Hook System", () => {
69
82
  hooks,
70
83
  namespace,
71
84
  internalFragment,
85
+ handlerTx,
72
86
  defaultRetryPolicy: new ExponentialBackoffRetryPolicy({ maxRetries: 5 }),
73
87
  });
74
-
75
- await executeMutate();
76
- },
77
- {
78
- onSuccess,
79
- onBeforeMutate,
80
- },
81
- );
88
+ })
89
+ .execute();
82
90
  });
83
91
 
84
92
  // Verify callbacks were executed
@@ -87,11 +95,12 @@ describe("Hook System", () => {
87
95
 
88
96
  // Verify hook was created
89
97
  const events = await internalFragment.inContext(async function () {
90
- return await this.uow(async ({ executeRetrieve }) => {
91
- const result = internalFragment.services.hookService.getPendingHookEvents(namespace);
92
- await executeRetrieve();
93
- return result;
94
- });
98
+ return await this.handlerTx()
99
+ .withServiceCalls(
100
+ () => [internalFragment.services.hookService.getPendingHookEvents(namespace)] as const,
101
+ )
102
+ .transform(({ serviceResult: [result] }) => result)
103
+ .execute();
95
104
  });
96
105
 
97
106
  expect(events).toHaveLength(1);
@@ -110,28 +119,30 @@ describe("Hook System", () => {
110
119
  };
111
120
 
112
121
  await internalFragment.inContext(async function () {
113
- await this.uow(async ({ forSchema, executeMutate }) => {
114
- const uow = forSchema(internalSchema, hooks);
115
-
116
- uow.triggerHook("onNoRetry", { data: "test" });
122
+ await this.handlerTx()
123
+ .mutate(({ forSchema }) => {
124
+ const uow = forSchema(internalSchema, hooks);
117
125
 
118
- prepareHookMutations(uow, {
119
- hooks,
120
- namespace,
121
- internalFragment,
122
- defaultRetryPolicy: new NoRetryPolicy(),
123
- });
126
+ uow.triggerHook("onNoRetry", { data: "test" });
124
127
 
125
- await executeMutate();
126
- });
128
+ prepareHookMutations(uow, {
129
+ hooks,
130
+ namespace,
131
+ internalFragment,
132
+ handlerTx,
133
+ defaultRetryPolicy: new NoRetryPolicy(),
134
+ });
135
+ })
136
+ .execute();
127
137
  });
128
138
 
129
139
  const events = await internalFragment.inContext(async function () {
130
- return await this.uow(async ({ executeRetrieve }) => {
131
- const result = internalFragment.services.hookService.getPendingHookEvents(namespace);
132
- await executeRetrieve();
133
- return result;
134
- });
140
+ return await this.handlerTx()
141
+ .withServiceCalls(
142
+ () => [internalFragment.services.hookService.getPendingHookEvents(namespace)] as const,
143
+ )
144
+ .transform(({ serviceResult: [result] }) => result)
145
+ .execute();
135
146
  });
136
147
 
137
148
  expect(events).toHaveLength(1);
@@ -145,38 +156,118 @@ describe("Hook System", () => {
145
156
  };
146
157
 
147
158
  await internalFragment.inContext(async function () {
148
- await this.uow(async ({ forSchema, executeMutate }) => {
149
- const uow = forSchema(internalSchema, hooks);
150
-
151
- uow.triggerHook(
152
- "onCustomRetry",
153
- { data: "test" },
154
- {
155
- retryPolicy: new NoRetryPolicy(),
156
- },
157
- );
159
+ await this.handlerTx()
160
+ .mutate(({ forSchema }) => {
161
+ const uow = forSchema(internalSchema, hooks);
158
162
 
159
- prepareHookMutations(uow, {
160
- hooks,
161
- namespace,
162
- internalFragment,
163
- defaultRetryPolicy: new ExponentialBackoffRetryPolicy({ maxRetries: 10 }),
164
- });
163
+ uow.triggerHook(
164
+ "onCustomRetry",
165
+ { data: "test" },
166
+ {
167
+ retryPolicy: new NoRetryPolicy(),
168
+ },
169
+ );
165
170
 
166
- await executeMutate();
167
- });
171
+ prepareHookMutations(uow, {
172
+ hooks,
173
+ namespace,
174
+ internalFragment,
175
+ handlerTx,
176
+ defaultRetryPolicy: new ExponentialBackoffRetryPolicy({ maxRetries: 10 }),
177
+ });
178
+ })
179
+ .execute();
168
180
  });
169
181
 
170
182
  const events = await internalFragment.inContext(async function () {
171
- return await this.uow(async ({ executeRetrieve }) => {
172
- const result = internalFragment.services.hookService.getPendingHookEvents(namespace);
173
- await executeRetrieve();
174
- return result;
175
- });
183
+ return await this.handlerTx()
184
+ .withServiceCalls(
185
+ () => [internalFragment.services.hookService.getPendingHookEvents(namespace)] as const,
186
+ )
187
+ .transform(({ serviceResult: [result] }) => result)
188
+ .execute();
176
189
  });
177
190
 
178
191
  expect(events[0]?.maxAttempts).toBe(1);
179
192
  });
193
+
194
+ it("should set nextRetryAt when processAt is in the future", async () => {
195
+ const namespace = "test-process-at-future";
196
+ const hooks: HooksMap = {
197
+ onScheduled: vi.fn(),
198
+ };
199
+ const futureTime = new Date(Date.now() + 60000);
200
+
201
+ await internalFragment.inContext(async function () {
202
+ await this.handlerTx()
203
+ .mutate(({ forSchema }) => {
204
+ const uow = forSchema(internalSchema, hooks);
205
+
206
+ uow.triggerHook("onScheduled", { data: "test" }, { processAt: futureTime });
207
+
208
+ prepareHookMutations(uow, {
209
+ hooks,
210
+ namespace,
211
+ internalFragment,
212
+ handlerTx,
213
+ defaultRetryPolicy: new ExponentialBackoffRetryPolicy({ maxRetries: 5 }),
214
+ });
215
+ })
216
+ .execute();
217
+ });
218
+
219
+ const events = await internalFragment.inContext(async function () {
220
+ return await this.handlerTx()
221
+ .withServiceCalls(
222
+ () => [internalFragment.services.hookService.getHooksByNamespace(namespace)] as const,
223
+ )
224
+ .transform(({ serviceResult: [result] }) => result)
225
+ .execute();
226
+ });
227
+
228
+ expect(events).toHaveLength(1);
229
+ expect(events[0]?.nextRetryAt).toBeInstanceOf(Date);
230
+ expect(events[0]?.nextRetryAt?.getTime()).toBe(futureTime.getTime());
231
+ });
232
+
233
+ it("should keep processAt in the past while remaining immediately eligible", async () => {
234
+ const namespace = "test-process-at-past";
235
+ const hooks: HooksMap = {
236
+ onImmediate: vi.fn(),
237
+ };
238
+ const pastTime = new Date(Date.now() - 60000);
239
+
240
+ await internalFragment.inContext(async function () {
241
+ await this.handlerTx()
242
+ .mutate(({ forSchema }) => {
243
+ const uow = forSchema(internalSchema, hooks);
244
+
245
+ uow.triggerHook("onImmediate", { data: "test" }, { processAt: pastTime });
246
+
247
+ prepareHookMutations(uow, {
248
+ hooks,
249
+ namespace,
250
+ internalFragment,
251
+ handlerTx,
252
+ defaultRetryPolicy: new ExponentialBackoffRetryPolicy({ maxRetries: 5 }),
253
+ });
254
+ })
255
+ .execute();
256
+ });
257
+
258
+ const events = await internalFragment.inContext(async function () {
259
+ return await this.handlerTx()
260
+ .withServiceCalls(
261
+ () => [internalFragment.services.hookService.getHooksByNamespace(namespace)] as const,
262
+ )
263
+ .transform(({ serviceResult: [result] }) => result)
264
+ .execute();
265
+ });
266
+
267
+ expect(events).toHaveLength(1);
268
+ expect(events[0]?.nextRetryAt).toBeInstanceOf(Date);
269
+ expect(events[0]?.nextRetryAt?.getTime()).toBe(pastTime.getTime());
270
+ });
180
271
  });
181
272
 
182
273
  describe("processHooks", () => {
@@ -191,22 +282,24 @@ describe("Hook System", () => {
191
282
 
192
283
  // Create a pending hook event
193
284
  await internalFragment.inContext(async function () {
194
- await this.uow(async ({ forSchema, executeMutate }) => {
195
- const uow = forSchema(internalSchema);
196
- eventId = uow.create("fragno_hooks", {
197
- namespace,
198
- hookName: "onSuccess",
199
- payload: { email: "test@example.com" },
200
- status: "pending",
201
- attempts: 0,
202
- maxAttempts: 5,
203
- lastAttemptAt: null,
204
- nextRetryAt: null,
205
- error: null,
206
- nonce: "test-nonce",
207
- });
208
- await executeMutate();
209
- });
285
+ const createdId = await this.handlerTx()
286
+ .mutate(({ forSchema }) => {
287
+ const uow = forSchema(internalSchema);
288
+ return uow.create("fragno_hooks", {
289
+ namespace,
290
+ hookName: "onSuccess",
291
+ payload: { email: "test@example.com" },
292
+ status: "pending",
293
+ attempts: 0,
294
+ maxAttempts: 5,
295
+ lastAttemptAt: null,
296
+ nextRetryAt: null,
297
+ error: null,
298
+ nonce: "test-nonce",
299
+ });
300
+ })
301
+ .execute();
302
+ eventId = createdId;
210
303
  });
211
304
 
212
305
  // Process hooks
@@ -214,6 +307,7 @@ describe("Hook System", () => {
214
307
  hooks,
215
308
  namespace,
216
309
  internalFragment,
310
+ handlerTx,
217
311
  defaultRetryPolicy: new ExponentialBackoffRetryPolicy({ maxRetries: 3 }),
218
312
  });
219
313
 
@@ -223,19 +317,16 @@ describe("Hook System", () => {
223
317
 
224
318
  // Verify hook context (this)
225
319
  const hookContext = hookFn.mock.contexts[0] as HookContext;
226
- expect(hookContext.nonce).toBe("test-nonce");
320
+ expect(hookContext.idempotencyKey).toBe("test-nonce");
227
321
 
228
322
  // Verify event was marked as completed
229
323
  const result = await internalFragment.inContext(async function () {
230
- return await this.uow(async ({ forSchema, executeRetrieve }) => {
231
- const uow = forSchema(internalSchema);
232
- const findUow = uow.find("fragno_hooks", (b) =>
233
- b.whereIndex("primary", (eb) => eb("id", "=", eventId)),
234
- );
235
- await executeRetrieve();
236
- const [events] = await findUow.retrievalPhase;
237
- return events?.[0];
238
- });
324
+ return await this.handlerTx()
325
+ .withServiceCalls(
326
+ () => [internalFragment.services.hookService.getHookById(eventId)] as const,
327
+ )
328
+ .transform(({ serviceResult: [event] }) => event)
329
+ .execute();
239
330
  });
240
331
 
241
332
  expect(result?.status).toBe("completed");
@@ -252,43 +343,43 @@ describe("Hook System", () => {
252
343
  let eventId: FragnoId;
253
344
 
254
345
  await internalFragment.inContext(async function () {
255
- await this.uow(async ({ forSchema, executeMutate }) => {
256
- const uow = forSchema(internalSchema);
257
- eventId = uow.create("fragno_hooks", {
258
- namespace,
259
- hookName: "onFailure",
260
- payload: { data: "test" },
261
- status: "pending",
262
- attempts: 0,
263
- maxAttempts: 5,
264
- lastAttemptAt: null,
265
- nextRetryAt: null,
266
- error: null,
267
- nonce: "test-nonce",
268
- });
269
- await executeMutate();
270
- });
346
+ const createdId = await this.handlerTx()
347
+ .mutate(({ forSchema }) => {
348
+ const uow = forSchema(internalSchema);
349
+ return uow.create("fragno_hooks", {
350
+ namespace,
351
+ hookName: "onFailure",
352
+ payload: { data: "test" },
353
+ status: "pending",
354
+ attempts: 0,
355
+ maxAttempts: 5,
356
+ lastAttemptAt: null,
357
+ nextRetryAt: null,
358
+ error: null,
359
+ nonce: "test-nonce",
360
+ });
361
+ })
362
+ .execute();
363
+ eventId = createdId;
271
364
  });
272
365
 
273
366
  await processHooks({
274
367
  hooks,
275
368
  namespace,
276
369
  internalFragment,
370
+ handlerTx,
277
371
  defaultRetryPolicy: new ExponentialBackoffRetryPolicy({ maxRetries: 3 }),
278
372
  });
279
373
 
280
374
  expect(hookFn).toHaveBeenCalledOnce();
281
375
 
282
376
  const result = await internalFragment.inContext(async function () {
283
- return await this.uow(async ({ forSchema, executeRetrieve }) => {
284
- const uow = forSchema(internalSchema);
285
- const findUow = uow.find("fragno_hooks", (b) =>
286
- b.whereIndex("primary", (eb) => eb("id", "=", eventId)),
287
- );
288
- await executeRetrieve();
289
- const [events] = await findUow.retrievalPhase;
290
- return events?.[0];
291
- });
377
+ return await this.handlerTx()
378
+ .withServiceCalls(
379
+ () => [internalFragment.services.hookService.getHookById(eventId)] as const,
380
+ )
381
+ .transform(({ serviceResult: [event] }) => event)
382
+ .execute();
292
383
  });
293
384
 
294
385
  expect(result?.status).toBe("pending");
@@ -307,41 +398,41 @@ describe("Hook System", () => {
307
398
  let eventId: FragnoId;
308
399
 
309
400
  await internalFragment.inContext(async function () {
310
- await this.uow(async ({ forSchema, executeMutate }) => {
311
- const uow = forSchema(internalSchema);
312
- eventId = uow.create("fragno_hooks", {
313
- namespace,
314
- hookName: "onMaxRetries",
315
- payload: { data: "test" },
316
- status: "pending",
317
- attempts: 0,
318
- maxAttempts: 1,
319
- lastAttemptAt: null,
320
- nextRetryAt: null,
321
- error: null,
322
- nonce: "test-nonce",
323
- });
324
- await executeMutate();
325
- });
401
+ const createdId = await this.handlerTx()
402
+ .mutate(({ forSchema }) => {
403
+ const uow = forSchema(internalSchema);
404
+ return uow.create("fragno_hooks", {
405
+ namespace,
406
+ hookName: "onMaxRetries",
407
+ payload: { data: "test" },
408
+ status: "pending",
409
+ attempts: 0,
410
+ maxAttempts: 1,
411
+ lastAttemptAt: null,
412
+ nextRetryAt: null,
413
+ error: null,
414
+ nonce: "test-nonce",
415
+ });
416
+ })
417
+ .execute();
418
+ eventId = createdId;
326
419
  });
327
420
 
328
421
  await processHooks({
329
422
  hooks,
330
423
  namespace,
331
424
  internalFragment,
425
+ handlerTx,
332
426
  defaultRetryPolicy: new NoRetryPolicy(),
333
427
  });
334
428
 
335
429
  const result = await internalFragment.inContext(async function () {
336
- return await this.uow(async ({ forSchema, executeRetrieve }) => {
337
- const uow = forSchema(internalSchema);
338
- const findUow = uow.find("fragno_hooks", (b) =>
339
- b.whereIndex("primary", (eb) => eb("id", "=", eventId)),
340
- );
341
- await executeRetrieve();
342
- const [events] = await findUow.retrievalPhase;
343
- return events?.[0];
344
- });
430
+ return await this.handlerTx()
431
+ .withServiceCalls(
432
+ () => [internalFragment.services.hookService.getHookById(eventId)] as const,
433
+ )
434
+ .transform(({ serviceResult: [event] }) => event)
435
+ .execute();
345
436
  });
346
437
 
347
438
  expect(result?.status).toBe("failed");
@@ -358,47 +449,109 @@ describe("Hook System", () => {
358
449
  let eventId: FragnoId;
359
450
 
360
451
  await internalFragment.inContext(async function () {
361
- await this.uow(async ({ forSchema, executeMutate }) => {
362
- const uow = forSchema(internalSchema);
363
- eventId = uow.create("fragno_hooks", {
364
- namespace,
365
- hookName: "onMissing",
366
- payload: { data: "test" },
367
- status: "pending",
368
- attempts: 0,
369
- maxAttempts: 1,
370
- lastAttemptAt: null,
371
- nextRetryAt: null,
372
- error: null,
373
- nonce: "test-nonce",
374
- });
375
- await executeMutate();
376
- });
452
+ const createdId = await this.handlerTx()
453
+ .mutate(({ forSchema }) => {
454
+ const uow = forSchema(internalSchema);
455
+ return uow.create("fragno_hooks", {
456
+ namespace,
457
+ hookName: "onMissing",
458
+ payload: { data: "test" },
459
+ status: "pending",
460
+ attempts: 0,
461
+ maxAttempts: 1,
462
+ lastAttemptAt: null,
463
+ nextRetryAt: null,
464
+ error: null,
465
+ nonce: "test-nonce",
466
+ });
467
+ })
468
+ .execute();
469
+ eventId = createdId;
377
470
  });
378
471
 
379
472
  await processHooks({
380
473
  hooks,
381
474
  namespace,
382
475
  internalFragment,
476
+ handlerTx,
383
477
  defaultRetryPolicy: new NoRetryPolicy(),
384
478
  });
385
479
 
386
480
  const result = await internalFragment.inContext(async function () {
387
- return await this.uow(async ({ forSchema, executeRetrieve }) => {
388
- const uow = forSchema(internalSchema);
389
- const findUow = uow.find("fragno_hooks", (b) =>
390
- b.whereIndex("primary", (eb) => eb("id", "=", eventId)),
391
- );
392
- await executeRetrieve();
393
- const [events] = await findUow.retrievalPhase;
394
- return events?.[0];
395
- });
481
+ return await this.handlerTx()
482
+ .withServiceCalls(
483
+ () => [internalFragment.services.hookService.getHookById(eventId)] as const,
484
+ )
485
+ .transform(({ serviceResult: [event] }) => event)
486
+ .execute();
396
487
  });
397
488
 
398
489
  expect(result?.status).toBe("failed");
399
490
  expect(result?.error).toBe("Hook 'onMissing' not found in hooks map");
400
491
  });
401
492
 
493
+ it("should re-queue stuck processing hooks and call the handler", async () => {
494
+ const namespace = "test-stuck-processing";
495
+ const hookFn = vi.fn();
496
+ const hooks: HooksMap = {
497
+ onStuck: hookFn,
498
+ };
499
+ const onStuckProcessingHooks = vi.fn();
500
+
501
+ let eventId: FragnoId;
502
+
503
+ await internalFragment.inContext(async function () {
504
+ const createdId = await this.handlerTx()
505
+ .mutate(({ forSchema }) => {
506
+ const uow = forSchema(internalSchema);
507
+ return uow.create("fragno_hooks", {
508
+ namespace,
509
+ hookName: "onStuck",
510
+ payload: { ok: true },
511
+ status: "processing",
512
+ attempts: 0,
513
+ maxAttempts: 5,
514
+ lastAttemptAt: new Date(Date.now() - 20 * 60_000),
515
+ nextRetryAt: null,
516
+ error: null,
517
+ nonce: "test-nonce",
518
+ });
519
+ })
520
+ .execute();
521
+ eventId = createdId;
522
+ });
523
+
524
+ await processHooks({
525
+ hooks,
526
+ namespace,
527
+ internalFragment,
528
+ handlerTx,
529
+ stuckProcessingTimeoutMinutes: 1,
530
+ onStuckProcessingHooks,
531
+ });
532
+
533
+ expect(hookFn).toHaveBeenCalledOnce();
534
+ expect(onStuckProcessingHooks).toHaveBeenCalledOnce();
535
+ expect(onStuckProcessingHooks).toHaveBeenCalledWith(
536
+ expect.objectContaining({
537
+ namespace,
538
+ timeoutMinutes: 1,
539
+ events: [expect.objectContaining({ hookName: "onStuck" })],
540
+ }),
541
+ );
542
+
543
+ const result = await internalFragment.inContext(async function () {
544
+ return await this.handlerTx()
545
+ .withServiceCalls(
546
+ () => [internalFragment.services.hookService.getHookById(eventId)] as const,
547
+ )
548
+ .transform(({ serviceResult: [event] }) => event)
549
+ .execute();
550
+ });
551
+
552
+ expect(result?.status).toBe("completed");
553
+ });
554
+
402
555
  it("should process multiple hooks in parallel", async () => {
403
556
  const namespace = "test-parallel";
404
557
  const hook1 = vi.fn();
@@ -411,52 +564,54 @@ describe("Hook System", () => {
411
564
  };
412
565
 
413
566
  await internalFragment.inContext(async function () {
414
- await this.uow(async ({ forSchema, executeMutate }) => {
415
- const uow = forSchema(internalSchema);
416
- uow.create("fragno_hooks", {
417
- namespace,
418
- hookName: "onHook1",
419
- payload: { id: 1 },
420
- status: "pending",
421
- attempts: 0,
422
- maxAttempts: 5,
423
- lastAttemptAt: null,
424
- nextRetryAt: null,
425
- error: null,
426
- nonce: "nonce-1",
427
- });
428
- uow.create("fragno_hooks", {
429
- namespace,
430
- hookName: "onHook2",
431
- payload: { id: 2 },
432
- status: "pending",
433
- attempts: 0,
434
- maxAttempts: 5,
435
- lastAttemptAt: null,
436
- nextRetryAt: null,
437
- error: null,
438
- nonce: "nonce-2",
439
- });
440
- uow.create("fragno_hooks", {
441
- namespace,
442
- hookName: "onHook3",
443
- payload: { id: 3 },
444
- status: "pending",
445
- attempts: 0,
446
- maxAttempts: 5,
447
- lastAttemptAt: null,
448
- nextRetryAt: null,
449
- error: null,
450
- nonce: "nonce-3",
451
- });
452
- await executeMutate();
453
- });
567
+ await this.handlerTx()
568
+ .mutate(({ forSchema }) => {
569
+ const uow = forSchema(internalSchema);
570
+ uow.create("fragno_hooks", {
571
+ namespace,
572
+ hookName: "onHook1",
573
+ payload: { id: 1 },
574
+ status: "pending",
575
+ attempts: 0,
576
+ maxAttempts: 5,
577
+ lastAttemptAt: null,
578
+ nextRetryAt: null,
579
+ error: null,
580
+ nonce: "nonce-1",
581
+ });
582
+ uow.create("fragno_hooks", {
583
+ namespace,
584
+ hookName: "onHook2",
585
+ payload: { id: 2 },
586
+ status: "pending",
587
+ attempts: 0,
588
+ maxAttempts: 5,
589
+ lastAttemptAt: null,
590
+ nextRetryAt: null,
591
+ error: null,
592
+ nonce: "nonce-2",
593
+ });
594
+ uow.create("fragno_hooks", {
595
+ namespace,
596
+ hookName: "onHook3",
597
+ payload: { id: 3 },
598
+ status: "pending",
599
+ attempts: 0,
600
+ maxAttempts: 5,
601
+ lastAttemptAt: null,
602
+ nextRetryAt: null,
603
+ error: null,
604
+ nonce: "nonce-3",
605
+ });
606
+ })
607
+ .execute();
454
608
  });
455
609
 
456
610
  await processHooks({
457
611
  hooks,
458
612
  namespace,
459
613
  internalFragment,
614
+ handlerTx,
460
615
  defaultRetryPolicy: new ExponentialBackoffRetryPolicy({ maxRetries: 3 }),
461
616
  });
462
617
 
@@ -466,15 +621,12 @@ describe("Hook System", () => {
466
621
 
467
622
  // Verify all were marked as completed
468
623
  const events = await internalFragment.inContext(async function () {
469
- return await this.uow(async ({ forSchema, executeRetrieve }) => {
470
- const uow = forSchema(internalSchema);
471
- const findUow = uow.find("fragno_hooks", (b) =>
472
- b.whereIndex("idx_namespace_status_retry", (eb) => eb("namespace", "=", namespace)),
473
- );
474
- await executeRetrieve();
475
- const [results] = await findUow.retrievalPhase;
476
- return results;
477
- });
624
+ return await this.handlerTx()
625
+ .withServiceCalls(
626
+ () => [internalFragment.services.hookService.getHooksByNamespace(namespace)] as const,
627
+ )
628
+ .transform(({ serviceResult: [result] }) => result)
629
+ .execute();
478
630
  });
479
631
 
480
632
  const completed = events.filter((e) => e.status === "completed");
@@ -493,52 +645,54 @@ describe("Hook System", () => {
493
645
  };
494
646
 
495
647
  await internalFragment.inContext(async function () {
496
- await this.uow(async ({ forSchema, executeMutate }) => {
497
- const uow = forSchema(internalSchema);
498
- uow.create("fragno_hooks", {
499
- namespace,
500
- hookName: "onHook1",
501
- payload: { id: 1 },
502
- status: "pending",
503
- attempts: 0,
504
- maxAttempts: 5,
505
- lastAttemptAt: null,
506
- nextRetryAt: null,
507
- error: null,
508
- nonce: "nonce-1",
509
- });
510
- uow.create("fragno_hooks", {
511
- namespace,
512
- hookName: "onHook2",
513
- payload: { id: 2 },
514
- status: "pending",
515
- attempts: 0,
516
- maxAttempts: 5,
517
- lastAttemptAt: null,
518
- nextRetryAt: null,
519
- error: null,
520
- nonce: "nonce-2",
521
- });
522
- uow.create("fragno_hooks", {
523
- namespace,
524
- hookName: "onHook3",
525
- payload: { id: 3 },
526
- status: "pending",
527
- attempts: 0,
528
- maxAttempts: 5,
529
- lastAttemptAt: null,
530
- nextRetryAt: null,
531
- error: null,
532
- nonce: "nonce-3",
533
- });
534
- await executeMutate();
535
- });
648
+ await this.handlerTx()
649
+ .mutate(({ forSchema }) => {
650
+ const uow = forSchema(internalSchema);
651
+ uow.create("fragno_hooks", {
652
+ namespace,
653
+ hookName: "onHook1",
654
+ payload: { id: 1 },
655
+ status: "pending",
656
+ attempts: 0,
657
+ maxAttempts: 5,
658
+ lastAttemptAt: null,
659
+ nextRetryAt: null,
660
+ error: null,
661
+ nonce: "nonce-1",
662
+ });
663
+ uow.create("fragno_hooks", {
664
+ namespace,
665
+ hookName: "onHook2",
666
+ payload: { id: 2 },
667
+ status: "pending",
668
+ attempts: 0,
669
+ maxAttempts: 5,
670
+ lastAttemptAt: null,
671
+ nextRetryAt: null,
672
+ error: null,
673
+ nonce: "nonce-2",
674
+ });
675
+ uow.create("fragno_hooks", {
676
+ namespace,
677
+ hookName: "onHook3",
678
+ payload: { id: 3 },
679
+ status: "pending",
680
+ attempts: 0,
681
+ maxAttempts: 5,
682
+ lastAttemptAt: null,
683
+ nextRetryAt: null,
684
+ error: null,
685
+ nonce: "nonce-3",
686
+ });
687
+ })
688
+ .execute();
536
689
  });
537
690
 
538
691
  await processHooks({
539
692
  hooks,
540
693
  namespace,
541
694
  internalFragment,
695
+ handlerTx,
542
696
  defaultRetryPolicy: new ExponentialBackoffRetryPolicy({ maxRetries: 3 }),
543
697
  });
544
698
 
@@ -548,15 +702,12 @@ describe("Hook System", () => {
548
702
 
549
703
  // Verify hook1 and hook3 were completed, hook2 was marked for retry
550
704
  const events = await internalFragment.inContext(async function () {
551
- return await this.uow(async ({ forSchema, executeRetrieve }) => {
552
- const uow = forSchema(internalSchema);
553
- const findUow = uow.find("fragno_hooks", (b) =>
554
- b.whereIndex("idx_namespace_status_retry", (eb) => eb("namespace", "=", namespace)),
555
- );
556
- await executeRetrieve();
557
- const [results] = await findUow.retrievalPhase;
558
- return results;
559
- });
705
+ return await this.handlerTx()
706
+ .withServiceCalls(
707
+ () => [internalFragment.services.hookService.getHooksByNamespace(namespace)] as const,
708
+ )
709
+ .transform(({ serviceResult: [result] }) => result)
710
+ .execute();
560
711
  });
561
712
 
562
713
  const completed = events.filter((e) => e.status === "completed");
@@ -578,6 +729,7 @@ describe("Hook System", () => {
578
729
  hooks,
579
730
  namespace,
580
731
  internalFragment,
732
+ handlerTx,
581
733
  defaultRetryPolicy: new ExponentialBackoffRetryPolicy({ maxRetries: 3 }),
582
734
  });
583
735