@fragno-dev/db 0.3.0 → 0.4.1

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 (516) hide show
  1. package/.turbo/turbo-build.log +327 -160
  2. package/CHANGELOG.md +74 -0
  3. package/README.md +24 -0
  4. package/dist/adapters/adapters.d.ts +1 -1
  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/generic-sql-adapter.d.ts +0 -3
  8. package/dist/adapters/generic-sql/generic-sql-adapter.d.ts.map +1 -1
  9. package/dist/adapters/generic-sql/generic-sql-adapter.js +11 -12
  10. package/dist/adapters/generic-sql/generic-sql-adapter.js.map +1 -1
  11. package/dist/adapters/generic-sql/generic-sql-uow-executor.js +46 -6
  12. package/dist/adapters/generic-sql/generic-sql-uow-executor.js.map +1 -1
  13. package/dist/adapters/generic-sql/migration/cold-kysely.js.map +1 -1
  14. package/dist/adapters/generic-sql/migration/dialect/mysql.js +1 -1
  15. package/dist/adapters/generic-sql/migration/dialect/mysql.js.map +1 -1
  16. package/dist/adapters/generic-sql/migration/dialect/postgres.js +1 -1
  17. package/dist/adapters/generic-sql/migration/dialect/postgres.js.map +1 -1
  18. package/dist/adapters/generic-sql/migration/dialect/sqlite.js +185 -19
  19. package/dist/adapters/generic-sql/migration/dialect/sqlite.js.map +1 -1
  20. package/dist/adapters/generic-sql/migration/executor.d.ts.map +1 -1
  21. package/dist/adapters/generic-sql/migration/executor.js +30 -3
  22. package/dist/adapters/generic-sql/migration/executor.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 +3 -3
  25. package/dist/adapters/generic-sql/migration/prepared-migrations.js.map +1 -1
  26. package/dist/adapters/generic-sql/migration/sql-generator.js +1 -1
  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 +1 -1
  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.map +1 -1
  31. package/dist/adapters/generic-sql/query/db-now-sql.js +27 -0
  32. package/dist/adapters/generic-sql/query/db-now-sql.js.map +1 -0
  33. package/dist/adapters/generic-sql/query/generic-sql-uow-operation-compiler.js +9 -6
  34. package/dist/adapters/generic-sql/query/generic-sql-uow-operation-compiler.js.map +1 -1
  35. package/dist/adapters/generic-sql/query/select-builder.js.map +1 -1
  36. package/dist/adapters/generic-sql/query/sql-query-compiler.js +37 -9
  37. package/dist/adapters/generic-sql/query/sql-query-compiler.js.map +1 -1
  38. package/dist/adapters/generic-sql/query/where-builder.js +24 -20
  39. package/dist/adapters/generic-sql/query/where-builder.js.map +1 -1
  40. package/dist/adapters/generic-sql/uow-decoder.js +1 -1
  41. package/dist/adapters/generic-sql/uow-decoder.js.map +1 -1
  42. package/dist/adapters/generic-sql/uow-encoder.js +8 -9
  43. package/dist/adapters/generic-sql/uow-encoder.js.map +1 -1
  44. package/dist/adapters/in-memory/condition-evaluator.js +10 -6
  45. package/dist/adapters/in-memory/condition-evaluator.js.map +1 -1
  46. package/dist/adapters/in-memory/in-memory-adapter.d.ts.map +1 -1
  47. package/dist/adapters/in-memory/in-memory-adapter.js +45 -25
  48. package/dist/adapters/in-memory/in-memory-adapter.js.map +1 -1
  49. package/dist/adapters/in-memory/in-memory-uow.js +236 -13
  50. package/dist/adapters/in-memory/in-memory-uow.js.map +1 -1
  51. package/dist/adapters/in-memory/options.d.ts +2 -0
  52. package/dist/adapters/in-memory/options.d.ts.map +1 -1
  53. package/dist/adapters/in-memory/options.js +3 -2
  54. package/dist/adapters/in-memory/options.js.map +1 -1
  55. package/dist/adapters/in-memory/reference-resolution.js.map +1 -1
  56. package/dist/adapters/in-memory/store.js +1 -1
  57. package/dist/adapters/in-memory/store.js.map +1 -1
  58. package/dist/adapters/shared/from-unit-of-work-compiler.js +51 -24
  59. package/dist/adapters/shared/from-unit-of-work-compiler.js.map +1 -1
  60. package/dist/adapters/shared/uow-operation-compiler.js.map +1 -1
  61. package/dist/browser/adapters/adapters.d.ts +61 -0
  62. package/dist/browser/adapters/adapters.d.ts.map +1 -0
  63. package/dist/browser/adapters/generic-sql/migration/executor.d.ts +15 -0
  64. package/dist/browser/adapters/generic-sql/migration/executor.d.ts.map +1 -0
  65. package/dist/browser/adapters/generic-sql/migration/prepared-migrations.d.ts +66 -0
  66. package/dist/browser/adapters/generic-sql/migration/prepared-migrations.d.ts.map +1 -0
  67. package/dist/browser/adapters/generic-sql/sqlite-storage.d.ts +11 -0
  68. package/dist/browser/adapters/generic-sql/sqlite-storage.d.ts.map +1 -0
  69. package/dist/browser/adapters/in-memory/in-memory-adapter.d.ts +5 -0
  70. package/dist/browser/adapters/in-memory/index.d.ts +2 -0
  71. package/dist/browser/adapters/in-memory/options.d.ts +1 -0
  72. package/dist/browser/db-fragment-definition-builder.d.ts +237 -0
  73. package/dist/browser/db-fragment-definition-builder.d.ts.map +1 -0
  74. package/dist/browser/durable-hooks.d.ts +3 -0
  75. package/dist/browser/fragments/internal-fragment.d.ts +317 -0
  76. package/dist/browser/fragments/internal-fragment.d.ts.map +1 -0
  77. package/dist/browser/fragments/internal-fragment.schema.d.ts +1 -0
  78. package/dist/browser/hooks/durable-hooks-logger.d.ts +10 -0
  79. package/dist/browser/hooks/durable-hooks-logger.d.ts.map +1 -0
  80. package/dist/browser/hooks/hooks.d.ts +146 -0
  81. package/dist/browser/hooks/hooks.d.ts.map +1 -0
  82. package/dist/browser/id.js +1 -0
  83. package/dist/browser/internal/adapter-registry.d.ts +4 -0
  84. package/dist/browser/internal/outbox-state.d.ts +2 -0
  85. package/dist/browser/mod.d.ts +15 -0
  86. package/dist/browser/mod.d.ts.map +1 -0
  87. package/dist/browser/mod.js +17 -0
  88. package/dist/browser/mod.js.map +1 -0
  89. package/dist/browser/mod2.d.ts +48 -0
  90. package/dist/browser/mod2.d.ts.map +1 -0
  91. package/dist/browser/naming/sql-naming.d.ts +19 -0
  92. package/dist/browser/naming/sql-naming.d.ts.map +1 -0
  93. package/dist/browser/outbox/outbox.d.ts +21 -0
  94. package/dist/browser/outbox/outbox.d.ts.map +1 -0
  95. package/dist/browser/query/column-defaults.js +1 -0
  96. package/dist/browser/query/condition-builder.d.ts +44 -0
  97. package/dist/browser/query/condition-builder.d.ts.map +1 -0
  98. package/dist/browser/query/condition-builder.js +97 -0
  99. package/dist/browser/query/condition-builder.js.map +1 -0
  100. package/dist/browser/query/cursor.d.ts +105 -0
  101. package/dist/browser/query/cursor.d.ts.map +1 -0
  102. package/dist/browser/query/cursor.js +150 -0
  103. package/dist/browser/query/cursor.js.map +1 -0
  104. package/dist/browser/query/db-now.d.ts +22 -0
  105. package/dist/browser/query/db-now.d.ts.map +1 -0
  106. package/dist/browser/query/db-now.js +33 -0
  107. package/dist/browser/query/db-now.js.map +1 -0
  108. package/dist/browser/query/orm/orm.d.ts +18 -0
  109. package/dist/browser/query/orm/orm.d.ts.map +1 -0
  110. package/dist/browser/query/simple-query-interface.d.ts +108 -0
  111. package/dist/browser/query/simple-query-interface.d.ts.map +1 -0
  112. package/dist/browser/query/unit-of-work/execute-unit-of-work.d.ts +423 -0
  113. package/dist/browser/query/unit-of-work/execute-unit-of-work.d.ts.map +1 -0
  114. package/dist/browser/query/unit-of-work/execute-unit-of-work.js +507 -0
  115. package/dist/browser/query/unit-of-work/execute-unit-of-work.js.map +1 -0
  116. package/dist/browser/query/unit-of-work/retry-policy.d.ts +23 -0
  117. package/dist/browser/query/unit-of-work/retry-policy.d.ts.map +1 -0
  118. package/dist/browser/query/unit-of-work/retry-policy.js +40 -0
  119. package/dist/browser/query/unit-of-work/retry-policy.js.map +1 -0
  120. package/dist/browser/query/unit-of-work/unit-of-work.d.ts +703 -0
  121. package/dist/browser/query/unit-of-work/unit-of-work.d.ts.map +1 -0
  122. package/dist/browser/query/unit-of-work/unit-of-work.js +1206 -0
  123. package/dist/browser/query/unit-of-work/unit-of-work.js.map +1 -0
  124. package/dist/browser/query/value-encoding.js +38 -0
  125. package/dist/browser/query/value-encoding.js.map +1 -0
  126. package/dist/browser/schema/create.d.ts +326 -0
  127. package/dist/browser/schema/create.d.ts.map +1 -0
  128. package/dist/browser/schema/create.js +89 -0
  129. package/dist/browser/schema/create.js.map +1 -0
  130. package/dist/browser/schema/generate-id.js +28 -0
  131. package/dist/browser/schema/generate-id.js.map +1 -0
  132. package/dist/browser/shared/providers.d.ts +6 -0
  133. package/dist/browser/shared/providers.d.ts.map +1 -0
  134. package/dist/browser/sql-driver/connection/connection-provider.d.ts +13 -0
  135. package/dist/browser/sql-driver/connection/connection-provider.d.ts.map +1 -0
  136. package/dist/browser/sql-driver/dialect-adapter/dialect-adapter.d.ts +7 -0
  137. package/dist/browser/sql-driver/dialect-adapter/dialect-adapter.d.ts.map +1 -0
  138. package/dist/browser/sql-driver/driver/runtime-driver.d.ts +23 -0
  139. package/dist/browser/sql-driver/driver/runtime-driver.d.ts.map +1 -0
  140. package/dist/browser/sql-driver/query-executor/plugin.d.ts +17 -0
  141. package/dist/browser/sql-driver/query-executor/plugin.d.ts.map +1 -0
  142. package/dist/browser/sql-driver/query-executor/query-executor.d.ts +36 -0
  143. package/dist/browser/sql-driver/query-executor/query-executor.d.ts.map +1 -0
  144. package/dist/browser/sql-driver/sql-driver-adapter.d.ts +29 -0
  145. package/dist/browser/sql-driver/sql-driver-adapter.d.ts.map +1 -0
  146. package/dist/browser/sql-driver/sql-driver.d.ts +38 -0
  147. package/dist/browser/sql-driver/sql-driver.d.ts.map +1 -0
  148. package/dist/browser/sync/commands.d.ts +15 -0
  149. package/dist/browser/sync/commands.d.ts.map +1 -0
  150. package/dist/browser/sync/commands.js +27 -0
  151. package/dist/browser/sync/commands.js.map +1 -0
  152. package/dist/browser/sync/types.d.ts +63 -0
  153. package/dist/browser/sync/types.d.ts.map +1 -0
  154. package/dist/browser/util/types.d.ts +8 -0
  155. package/dist/browser/util/types.d.ts.map +1 -0
  156. package/dist/browser/with-database.d.ts +29 -0
  157. package/dist/browser/with-database.d.ts.map +1 -0
  158. package/dist/client.d.ts +4 -0
  159. package/dist/client.js +5 -0
  160. package/dist/db-fragment-definition-builder.d.ts +85 -28
  161. package/dist/db-fragment-definition-builder.d.ts.map +1 -1
  162. package/dist/db-fragment-definition-builder.js +374 -46
  163. package/dist/db-fragment-definition-builder.js.map +1 -1
  164. package/dist/dispatchers/cloudflare-do/dispatcher.d.ts +20 -0
  165. package/dist/dispatchers/cloudflare-do/dispatcher.d.ts.map +1 -0
  166. package/dist/dispatchers/cloudflare-do/dispatcher.js +147 -0
  167. package/dist/dispatchers/cloudflare-do/dispatcher.js.map +1 -0
  168. package/dist/dispatchers/cloudflare-do/index.d.ts +5 -20
  169. package/dist/dispatchers/cloudflare-do/index.d.ts.map +1 -1
  170. package/dist/dispatchers/cloudflare-do/index.js +23 -55
  171. package/dist/dispatchers/cloudflare-do/index.js.map +1 -1
  172. package/dist/dispatchers/node/dispatcher.d.ts +14 -0
  173. package/dist/dispatchers/node/dispatcher.d.ts.map +1 -0
  174. package/dist/dispatchers/node/dispatcher.js +80 -0
  175. package/dist/dispatchers/node/dispatcher.js.map +1 -0
  176. package/dist/dispatchers/node/index.d.ts +5 -10
  177. package/dist/dispatchers/node/index.d.ts.map +1 -1
  178. package/dist/dispatchers/node/index.js +21 -53
  179. package/dist/dispatchers/node/index.js.map +1 -1
  180. package/dist/durable-hooks.d.ts +31 -0
  181. package/dist/durable-hooks.d.ts.map +1 -0
  182. package/dist/durable-hooks.js +23 -0
  183. package/dist/durable-hooks.js.map +1 -0
  184. package/dist/fragments/internal-fragment.d.ts +128 -27
  185. package/dist/fragments/internal-fragment.d.ts.map +1 -1
  186. package/dist/fragments/internal-fragment.js +125 -78
  187. package/dist/fragments/internal-fragment.js.map +1 -1
  188. package/dist/fragments/internal-fragment.routes.js +138 -3
  189. package/dist/fragments/internal-fragment.routes.js.map +1 -1
  190. package/dist/fragments/internal-fragment.schema.d.ts +7 -1
  191. package/dist/fragments/internal-fragment.schema.d.ts.map +1 -1
  192. package/dist/fragments/internal-fragment.schema.js +18 -1
  193. package/dist/fragments/internal-fragment.schema.js.map +1 -1
  194. package/dist/hooks/durable-hooks-logger.d.ts +10 -0
  195. package/dist/hooks/durable-hooks-logger.d.ts.map +1 -0
  196. package/dist/hooks/durable-hooks-logger.js +75 -0
  197. package/dist/hooks/durable-hooks-logger.js.map +1 -0
  198. package/dist/hooks/durable-hooks-processor.d.ts +1 -14
  199. package/dist/hooks/durable-hooks-processor.js +58 -10
  200. package/dist/hooks/durable-hooks-processor.js.map +1 -1
  201. package/dist/hooks/durable-hooks-runtime.js +44 -0
  202. package/dist/hooks/durable-hooks-runtime.js.map +1 -0
  203. package/dist/hooks/hooks.d.ts +60 -2
  204. package/dist/hooks/hooks.d.ts.map +1 -1
  205. package/dist/hooks/hooks.js +214 -53
  206. package/dist/hooks/hooks.js.map +1 -1
  207. package/dist/id.d.ts +2 -2
  208. package/dist/id.js +2 -2
  209. package/dist/internal/adapter-registry.d.ts +11 -0
  210. package/dist/internal/adapter-registry.d.ts.map +1 -0
  211. package/dist/internal/adapter-registry.js +135 -0
  212. package/dist/internal/adapter-registry.js.map +1 -0
  213. package/dist/internal/outbox-state.d.ts +2 -0
  214. package/dist/internal/outbox-state.js +26 -0
  215. package/dist/internal/outbox-state.js.map +1 -0
  216. package/dist/migration-engine/auto-from-schema.d.ts +33 -0
  217. package/dist/migration-engine/auto-from-schema.d.ts.map +1 -0
  218. package/dist/migration-engine/auto-from-schema.js +210 -27
  219. package/dist/migration-engine/auto-from-schema.js.map +1 -1
  220. package/dist/migration-engine/generation-engine.d.ts.map +1 -1
  221. package/dist/migration-engine/generation-engine.js +17 -5
  222. package/dist/migration-engine/generation-engine.js.map +1 -1
  223. package/dist/migration-engine/shared.d.ts +113 -0
  224. package/dist/migration-engine/shared.d.ts.map +1 -0
  225. package/dist/migration-engine/shared.js.map +1 -1
  226. package/dist/mod.d.ts +12 -11
  227. package/dist/mod.d.ts.map +1 -1
  228. package/dist/mod.js +10 -10
  229. package/dist/mod.js.map +1 -1
  230. package/dist/naming/sql-naming.d.ts.map +1 -1
  231. package/dist/naming/sql-naming.js.map +1 -1
  232. package/dist/outbox/outbox-builder.js.map +1 -1
  233. package/dist/outbox/outbox.d.ts +3 -1
  234. package/dist/outbox/outbox.d.ts.map +1 -1
  235. package/dist/outbox/outbox.js.map +1 -1
  236. package/dist/query/column-defaults.js.map +1 -1
  237. package/dist/query/condition-builder.d.ts +7 -1
  238. package/dist/query/condition-builder.d.ts.map +1 -1
  239. package/dist/query/condition-builder.js +5 -1
  240. package/dist/query/condition-builder.js.map +1 -1
  241. package/dist/query/cursor-client.d.ts +105 -0
  242. package/dist/query/cursor-client.d.ts.map +1 -0
  243. package/dist/query/cursor-client.js +165 -0
  244. package/dist/query/cursor-client.js.map +1 -0
  245. package/dist/query/cursor.d.ts.map +1 -1
  246. package/dist/query/cursor.js +7 -1
  247. package/dist/query/cursor.js.map +1 -1
  248. package/dist/query/db-now.d.ts +15 -1
  249. package/dist/query/db-now.d.ts.map +1 -1
  250. package/dist/query/db-now.js +30 -2
  251. package/dist/query/db-now.js.map +1 -1
  252. package/dist/query/orm/orm.js.map +1 -1
  253. package/dist/query/serialize/create-sql-serializer.js +2 -2
  254. package/dist/query/serialize/create-sql-serializer.js.map +1 -1
  255. package/dist/query/serialize/dialect/mysql-serializer.js.map +1 -1
  256. package/dist/query/serialize/dialect/postgres-serializer.js.map +1 -1
  257. package/dist/query/serialize/dialect/sqlite-serializer.js +6 -2
  258. package/dist/query/serialize/dialect/sqlite-serializer.js.map +1 -1
  259. package/dist/query/simple-query-interface.d.ts +7 -3
  260. package/dist/query/simple-query-interface.d.ts.map +1 -1
  261. package/dist/query/unit-of-work/execute-unit-of-work.d.ts +37 -2
  262. package/dist/query/unit-of-work/execute-unit-of-work.d.ts.map +1 -1
  263. package/dist/query/unit-of-work/execute-unit-of-work.js +39 -18
  264. package/dist/query/unit-of-work/execute-unit-of-work.js.map +1 -1
  265. package/dist/query/unit-of-work/unit-of-work.d.ts +42 -16
  266. package/dist/query/unit-of-work/unit-of-work.d.ts.map +1 -1
  267. package/dist/query/unit-of-work/unit-of-work.js +50 -6
  268. package/dist/query/unit-of-work/unit-of-work.js.map +1 -1
  269. package/dist/query/value-decoding.js +8 -1
  270. package/dist/query/value-decoding.js.map +1 -1
  271. package/dist/query/value-encoding.js.map +1 -1
  272. package/dist/schema/create.d.ts +69 -25
  273. package/dist/schema/create.d.ts.map +1 -1
  274. package/dist/schema/create.js +91 -16
  275. package/dist/schema/create.js.map +1 -1
  276. package/dist/schema/type-conversion/create-sql-type-mapper.js +1 -1
  277. package/dist/schema/type-conversion/create-sql-type-mapper.js.map +1 -1
  278. package/dist/schema/type-conversion/dialect/sqlite.js.map +1 -1
  279. package/dist/schema/validator.d.ts.map +1 -1
  280. package/dist/schema/validator.js.map +1 -1
  281. package/dist/schema-output/drizzle.d.ts.map +1 -1
  282. package/dist/schema-output/drizzle.js +8 -6
  283. package/dist/schema-output/drizzle.js.map +1 -1
  284. package/dist/schema-output/prisma.d.ts.map +1 -1
  285. package/dist/schema-output/prisma.js +21 -10
  286. package/dist/schema-output/prisma.js.map +1 -1
  287. package/dist/sql-driver/dialects/durable-object-dialect.js +3 -9
  288. package/dist/sql-driver/dialects/durable-object-dialect.js.map +1 -1
  289. package/dist/sql-driver/query-executor/default-query-executor.js.map +1 -1
  290. package/dist/sql-driver/query-executor/query-executor-base.js.map +1 -1
  291. package/dist/sql-driver/sql-driver-adapter.js.map +1 -1
  292. package/dist/sql-driver/sql.js.map +1 -1
  293. package/dist/sync/commands.d.ts +15 -0
  294. package/dist/sync/commands.d.ts.map +1 -0
  295. package/dist/sync/commands.js +27 -0
  296. package/dist/sync/commands.js.map +1 -0
  297. package/dist/sync/index.d.ts +4 -0
  298. package/dist/sync/index.js +4 -0
  299. package/dist/sync/read-tracking.d.ts +25 -0
  300. package/dist/sync/read-tracking.d.ts.map +1 -0
  301. package/dist/sync/read-tracking.js +148 -0
  302. package/dist/sync/read-tracking.js.map +1 -0
  303. package/dist/sync/submit.js +213 -0
  304. package/dist/sync/submit.js.map +1 -0
  305. package/dist/sync/types.d.ts +63 -0
  306. package/dist/sync/types.d.ts.map +1 -0
  307. package/dist/util/default-database-adapter.js +6 -1
  308. package/dist/util/default-database-adapter.js.map +1 -1
  309. package/dist/with-database.d.ts +3 -6
  310. package/dist/with-database.d.ts.map +1 -1
  311. package/dist/with-database.js +7 -15
  312. package/dist/with-database.js.map +1 -1
  313. package/package.json +33 -41
  314. package/src/adapters/adapters.ts +5 -4
  315. package/src/adapters/drizzle/migrate-drizzle.test.ts +46 -9
  316. package/src/adapters/drizzle/migration-parity-drizzle-kit.test.ts +5 -3
  317. package/src/adapters/drizzle/test-utils.ts +2 -1
  318. package/src/adapters/generic-sql/generic-sql-adapter.test.ts +5 -3
  319. package/src/adapters/generic-sql/generic-sql-adapter.ts +21 -24
  320. package/src/adapters/generic-sql/generic-sql-uow-executor.test.ts +1 -0
  321. package/src/adapters/generic-sql/generic-sql-uow-executor.ts +81 -15
  322. package/src/adapters/generic-sql/migration/adapter-migration-parity.test.ts +4 -2
  323. package/src/adapters/generic-sql/migration/cold-kysely.ts +1 -0
  324. package/src/adapters/generic-sql/migration/dialect/mysql.test.ts +3 -2
  325. package/src/adapters/generic-sql/migration/dialect/mysql.ts +1 -0
  326. package/src/adapters/generic-sql/migration/dialect/postgres.test.ts +5 -4
  327. package/src/adapters/generic-sql/migration/dialect/postgres.ts +2 -1
  328. package/src/adapters/generic-sql/migration/dialect/sqlite.test.ts +795 -3
  329. package/src/adapters/generic-sql/migration/dialect/sqlite.ts +385 -57
  330. package/src/adapters/generic-sql/migration/executor.test.ts +52 -0
  331. package/src/adapters/generic-sql/migration/executor.ts +47 -4
  332. package/src/adapters/generic-sql/migration/prepared-migrations.test.ts +117 -14
  333. package/src/adapters/generic-sql/migration/prepared-migrations.ts +9 -8
  334. package/src/adapters/generic-sql/migration/sql-generator.ts +5 -3
  335. package/src/adapters/generic-sql/query/create-sql-query-compiler.ts +3 -3
  336. package/src/adapters/generic-sql/query/cursor-utils.test.ts +3 -2
  337. package/src/adapters/generic-sql/query/cursor-utils.ts +1 -1
  338. package/src/adapters/generic-sql/query/db-now-sql.ts +49 -0
  339. package/src/adapters/generic-sql/query/generic-sql-uow-operation-compiler.test.ts +144 -8
  340. package/src/adapters/generic-sql/query/generic-sql-uow-operation-compiler.ts +16 -17
  341. package/src/adapters/generic-sql/query/select-builder.test.ts +1 -0
  342. package/src/adapters/generic-sql/query/select-builder.ts +2 -2
  343. package/src/adapters/generic-sql/query/sql-query-compiler.test.ts +24 -5
  344. package/src/adapters/generic-sql/query/sql-query-compiler.ts +83 -13
  345. package/src/adapters/generic-sql/query/where-builder.test.ts +7 -5
  346. package/src/adapters/generic-sql/query/where-builder.ts +48 -29
  347. package/src/adapters/generic-sql/sql-adapter-pglite-migrations.test.ts +6 -15
  348. package/src/adapters/generic-sql/sql-adapter-pglite-pagination.test.ts +52 -7
  349. package/src/adapters/generic-sql/sql-adapter-pglite-queries.test.ts +9 -6
  350. package/src/adapters/generic-sql/sql-adapter-sqlite3-driver.test.ts +273 -5
  351. package/src/adapters/generic-sql/sql-adapter-sqlite3-uow.test.ts +123 -6
  352. package/src/adapters/generic-sql/sql-adapter-sqlocal.test.ts +4 -2
  353. package/src/adapters/generic-sql/uow-decoder.test.ts +4 -3
  354. package/src/adapters/generic-sql/uow-decoder.ts +3 -3
  355. package/src/adapters/generic-sql/uow-encoder.test.ts +4 -2
  356. package/src/adapters/generic-sql/uow-encoder.ts +14 -18
  357. package/src/adapters/in-memory/condition-evaluator.test.ts +2 -1
  358. package/src/adapters/in-memory/condition-evaluator.ts +9 -4
  359. package/src/adapters/in-memory/in-memory-adapter.ts +155 -44
  360. package/src/adapters/in-memory/in-memory-uow.mutations.test.ts +50 -2
  361. package/src/adapters/in-memory/in-memory-uow.retrieval.test.ts +158 -3
  362. package/src/adapters/in-memory/in-memory-uow.ts +402 -26
  363. package/src/adapters/in-memory/options.test.ts +1 -0
  364. package/src/adapters/in-memory/options.ts +5 -1
  365. package/src/adapters/in-memory/outbox.test.ts +361 -0
  366. package/src/adapters/in-memory/reference-resolution.test.ts +3 -2
  367. package/src/adapters/in-memory/reference-resolution.ts +2 -2
  368. package/src/adapters/in-memory/sorted-array-index.test.ts +1 -0
  369. package/src/adapters/in-memory/store.test.ts +1 -0
  370. package/src/adapters/in-memory/store.ts +3 -3
  371. package/src/adapters/in-memory/value-normalization.test.ts +1 -0
  372. package/src/adapters/prisma/prisma-adapter-sqlite3.test.ts +51 -7
  373. package/src/adapters/shared/from-unit-of-work-compiler.ts +156 -46
  374. package/src/adapters/shared/uow-operation-compiler.ts +3 -3
  375. package/src/browser/mod.ts +64 -0
  376. package/src/client.ts +19 -0
  377. package/src/db-fragment-definition-builder.test.ts +821 -47
  378. package/src/db-fragment-definition-builder.ts +857 -110
  379. package/src/db-fragment-instantiator.test.ts +114 -90
  380. package/src/db-fragment-integration.test.ts +9 -6
  381. package/src/dispatchers/cloudflare-do/dispatcher.ts +204 -0
  382. package/src/dispatchers/cloudflare-do/index.test.ts +145 -12
  383. package/src/dispatchers/cloudflare-do/index.ts +49 -90
  384. package/src/dispatchers/node/dispatcher.ts +112 -0
  385. package/src/dispatchers/node/index.test.ts +43 -14
  386. package/src/dispatchers/node/index.ts +38 -75
  387. package/src/durable-hooks.test.ts +80 -0
  388. package/src/durable-hooks.ts +67 -0
  389. package/src/fragments/internal-fragment.routes.test.ts +570 -0
  390. package/src/fragments/internal-fragment.routes.ts +297 -5
  391. package/src/fragments/internal-fragment.schema.ts +45 -1
  392. package/src/fragments/internal-fragment.test.ts +223 -251
  393. package/src/fragments/internal-fragment.ts +278 -154
  394. package/src/hooks/durable-hooks-logger.ts +126 -0
  395. package/src/hooks/durable-hooks-processor.pglite.test.ts +87 -0
  396. package/src/hooks/durable-hooks-processor.test.ts +179 -14
  397. package/src/hooks/durable-hooks-processor.ts +120 -14
  398. package/src/hooks/durable-hooks-runtime.test.ts +65 -0
  399. package/src/hooks/durable-hooks-runtime.ts +81 -0
  400. package/src/hooks/hooks.test.ts +314 -53
  401. package/src/hooks/hooks.ts +360 -81
  402. package/src/id.test.ts +34 -0
  403. package/src/id.ts +1 -3
  404. package/src/internal/adapter-registry.test.ts +93 -0
  405. package/src/internal/adapter-registry.ts +239 -0
  406. package/src/internal/outbox-state.ts +43 -0
  407. package/src/migration-engine/auto-from-schema.test.ts +93 -0
  408. package/src/migration-engine/auto-from-schema.ts +360 -42
  409. package/src/migration-engine/create.test.ts +2 -1
  410. package/src/migration-engine/create.ts +1 -1
  411. package/src/migration-engine/generation-engine.test.ts +66 -9
  412. package/src/migration-engine/generation-engine.ts +31 -10
  413. package/src/migration-engine/shared.ts +13 -0
  414. package/src/mod.ts +45 -27
  415. package/src/naming/sql-naming.ts +1 -0
  416. package/src/outbox/outbox-builder.ts +2 -2
  417. package/src/outbox/outbox.test.ts +216 -45
  418. package/src/outbox/outbox.ts +3 -1
  419. package/src/query/column-defaults.ts +1 -1
  420. package/src/query/condition-builder.test.ts +15 -0
  421. package/src/query/condition-builder.ts +7 -0
  422. package/src/query/cursor-client.test.ts +70 -0
  423. package/src/query/cursor-client.ts +263 -0
  424. package/src/query/cursor.test.ts +3 -2
  425. package/src/query/cursor.ts +15 -3
  426. package/src/query/db-now.ts +69 -2
  427. package/src/query/orm/orm.ts +2 -2
  428. package/src/query/query-type.test.ts +2 -1
  429. package/src/query/serialize/create-sql-serializer.ts +3 -3
  430. package/src/query/serialize/dialect/mysql-serializer.ts +1 -1
  431. package/src/query/serialize/dialect/postgres-serializer.ts +1 -1
  432. package/src/query/serialize/dialect/sqlite-serializer.test.ts +39 -2
  433. package/src/query/serialize/dialect/sqlite-serializer.ts +18 -5
  434. package/src/query/simple-query-interface.ts +10 -4
  435. package/src/query/unit-of-work/execute-unit-of-work.test.ts +347 -9
  436. package/src/query/unit-of-work/execute-unit-of-work.ts +63 -20
  437. package/src/query/unit-of-work/retry-policy.test.ts +1 -0
  438. package/src/query/unit-of-work/tx-builder.test.ts +73 -1
  439. package/src/query/unit-of-work/unit-of-work-coordinator.test.ts +5 -4
  440. package/src/query/unit-of-work/unit-of-work-types.test.ts +41 -11
  441. package/src/query/unit-of-work/unit-of-work.test.ts +28 -2
  442. package/src/query/unit-of-work/unit-of-work.ts +105 -19
  443. package/src/query/value-decoding.test.ts +50 -2
  444. package/src/query/value-decoding.ts +17 -4
  445. package/src/query/value-encoding.test.ts +1 -0
  446. package/src/query/value-encoding.ts +1 -1
  447. package/src/schema/create.test.ts +164 -5
  448. package/src/schema/create.ts +222 -24
  449. package/src/schema/generate-id.test.ts +1 -0
  450. package/src/schema/serialize.test.ts +4 -3
  451. package/src/schema/type-conversion/create-sql-type-mapper.ts +1 -1
  452. package/src/schema/type-conversion/dialect/sqlite.ts +2 -2
  453. package/src/schema/type-conversion/type-mapping.test.ts +2 -1
  454. package/src/schema/validator.test.ts +4 -2
  455. package/src/schema/validator.ts +1 -0
  456. package/src/schema-output/drizzle.test.ts +72 -19
  457. package/src/schema-output/drizzle.ts +24 -18
  458. package/src/schema-output/prisma.test.ts +172 -14
  459. package/src/schema-output/prisma.ts +34 -14
  460. package/src/sql-driver/better-sqlite3.test.ts +5 -3
  461. package/src/sql-driver/dialects/durable-object-dialect.ts +3 -8
  462. package/src/sql-driver/query-executor/default-query-executor.ts +1 -1
  463. package/src/sql-driver/query-executor/query-executor-base.ts +1 -1
  464. package/src/sql-driver/query-executor/query-executor.ts +1 -1
  465. package/src/sql-driver/sql-driver-adapter.ts +2 -2
  466. package/src/sql-driver/sql.ts +2 -1
  467. package/src/sql-driver/sqlocal.test.ts +4 -2
  468. package/src/sync/commands.test.ts +39 -0
  469. package/src/sync/commands.ts +51 -0
  470. package/src/sync/conflict-checker.test.ts +450 -0
  471. package/src/sync/conflict-checker.ts +248 -0
  472. package/src/sync/index.ts +14 -0
  473. package/src/sync/plan.ts +9 -0
  474. package/src/sync/read-tracking.test.ts +177 -0
  475. package/src/sync/read-tracking.ts +287 -0
  476. package/src/sync/submit.test.ts +205 -0
  477. package/src/sync/submit.ts +328 -0
  478. package/src/sync/types.ts +80 -0
  479. package/src/util/default-database-adapter.ts +15 -2
  480. package/src/with-database.ts +20 -50
  481. package/tsconfig.json +1 -1
  482. package/tsdown.config.ts +38 -26
  483. package/vitest.config.ts +1 -0
  484. package/dist/hooks/durable-hooks-processor.d.ts.map +0 -1
  485. package/dist/node_modules/.pnpm/rou3@0.7.12/node_modules/rou3/dist/index.js +0 -168
  486. package/dist/node_modules/.pnpm/rou3@0.7.12/node_modules/rou3/dist/index.js.map +0 -1
  487. package/dist/packages/fragno/dist/api/bind-services.js +0 -20
  488. package/dist/packages/fragno/dist/api/bind-services.js.map +0 -1
  489. package/dist/packages/fragno/dist/api/error.js +0 -48
  490. package/dist/packages/fragno/dist/api/error.js.map +0 -1
  491. package/dist/packages/fragno/dist/api/fragment-definition-builder.js +0 -321
  492. package/dist/packages/fragno/dist/api/fragment-definition-builder.js.map +0 -1
  493. package/dist/packages/fragno/dist/api/fragment-instantiator.js +0 -669
  494. package/dist/packages/fragno/dist/api/fragment-instantiator.js.map +0 -1
  495. package/dist/packages/fragno/dist/api/fragno-response.js +0 -73
  496. package/dist/packages/fragno/dist/api/fragno-response.js.map +0 -1
  497. package/dist/packages/fragno/dist/api/internal/response-stream.js +0 -81
  498. package/dist/packages/fragno/dist/api/internal/response-stream.js.map +0 -1
  499. package/dist/packages/fragno/dist/api/internal/route.js +0 -10
  500. package/dist/packages/fragno/dist/api/internal/route.js.map +0 -1
  501. package/dist/packages/fragno/dist/api/mutable-request-state.js +0 -97
  502. package/dist/packages/fragno/dist/api/mutable-request-state.js.map +0 -1
  503. package/dist/packages/fragno/dist/api/request-context-storage.js +0 -43
  504. package/dist/packages/fragno/dist/api/request-context-storage.js.map +0 -1
  505. package/dist/packages/fragno/dist/api/request-input-context.js +0 -185
  506. package/dist/packages/fragno/dist/api/request-input-context.js.map +0 -1
  507. package/dist/packages/fragno/dist/api/request-middleware.js +0 -83
  508. package/dist/packages/fragno/dist/api/request-middleware.js.map +0 -1
  509. package/dist/packages/fragno/dist/api/request-output-context.js +0 -119
  510. package/dist/packages/fragno/dist/api/request-output-context.js.map +0 -1
  511. package/dist/packages/fragno/dist/api/route.js +0 -30
  512. package/dist/packages/fragno/dist/api/route.js.map +0 -1
  513. package/dist/packages/fragno/dist/internal/symbols.js +0 -10
  514. package/dist/packages/fragno/dist/internal/symbols.js.map +0 -1
  515. package/dist/packages/fragno/dist/internal/trace-context.js +0 -12
  516. package/dist/packages/fragno/dist/internal/trace-context.js.map +0 -1
@@ -1,3 +1,5 @@
1
+ import type { InternalFragmentInstance } from "../fragments/internal-fragment";
2
+ import { dbNow, isDbNow, type DbNow } from "../query/db-now";
1
3
  import type {
2
4
  ExecuteTxOptions,
3
5
  HandlerTxBuilder,
@@ -5,9 +7,8 @@ import type {
5
7
  import type { RetryPolicy } from "../query/unit-of-work/retry-policy";
6
8
  import { ExponentialBackoffRetryPolicy } from "../query/unit-of-work/retry-policy";
7
9
  import type { IUnitOfWork } from "../query/unit-of-work/unit-of-work";
8
- import type { InternalFragmentInstance } from "../fragments/internal-fragment";
9
- import type { TxResult } from "../query/unit-of-work/execute-unit-of-work";
10
10
  import type { FragnoId } from "../schema/create";
11
+ import { DurableHooksLogger, type DurableHooksLoggerConfig } from "./durable-hooks-logger";
11
12
 
12
13
  /**
13
14
  * Context available in hook functions via `this`.
@@ -19,12 +20,58 @@ export interface HookContext {
19
20
  * Use this for idempotency checks in your hook implementation.
20
21
  */
21
22
  idempotencyKey: string;
23
+ /**
24
+ * Hook event identifier (versioned FragnoId).
25
+ */
26
+ hookId: FragnoId;
27
+ /**
28
+ * Hook name for this event.
29
+ */
30
+ hookName: string;
31
+ /**
32
+ * Current status of the hook event.
33
+ */
34
+ status: HookStatus;
35
+ /**
36
+ * Attempt count for this hook event.
37
+ */
38
+ attempts: number;
39
+ /**
40
+ * Maximum attempts configured for this hook event.
41
+ */
42
+ maxAttempts: number;
43
+ /**
44
+ * Timestamp of the last attempt (if any).
45
+ */
46
+ lastAttemptAt: Date | null;
47
+ /**
48
+ * Next scheduled retry timestamp (if any).
49
+ */
50
+ nextRetryAt: Date | null;
51
+ /**
52
+ * When the hook event was created.
53
+ */
54
+ createdAt: Date;
22
55
  /**
23
56
  * Create a handler transaction builder to run atomic operations.
24
57
  */
25
58
  handlerTx: HookHandlerTx;
26
59
  }
27
60
 
61
+ export const hookStatusValues = ["pending", "processing", "completed", "failed"] as const;
62
+
63
+ export type HookStatus = (typeof hookStatusValues)[number];
64
+
65
+ export const isHookStatus = (value: unknown): value is HookStatus =>
66
+ typeof value === "string" && hookStatusValues.includes(value as HookStatus);
67
+
68
+ const assertHookStatus = (value: unknown): HookStatus => {
69
+ if (isHookStatus(value)) {
70
+ return value;
71
+ }
72
+ throw new Error(`Invalid hook status: ${String(value)}`);
73
+ };
74
+
28
75
  /**
29
76
  * A hook function signature.
30
77
  * Hooks receive a typed payload and access context via `this`.
@@ -52,11 +99,16 @@ export interface TriggerHookOptions {
52
99
  * If not provided, uses the default retry policy.
53
100
  */
54
101
  retryPolicy?: RetryPolicy;
102
+ /**
103
+ * Optional hook event ID. When provided, the hook insert will fail if a hook
104
+ * with the same ID already exists.
105
+ */
106
+ id?: string | FragnoId;
55
107
  /**
56
108
  * Absolute time for the first attempt. If in the future, the hook is
57
109
  * scheduled for that time; if in the past, it runs immediately.
58
110
  */
59
- processAt?: Date;
111
+ processAt?: Date | DbNow;
60
112
  }
61
113
 
62
114
  /**
@@ -64,11 +116,32 @@ export interface TriggerHookOptions {
64
116
  * Stored in the Unit of Work before execution.
65
117
  */
66
118
  export interface TriggeredHook {
119
+ namespace: string;
67
120
  hookName: string;
68
121
  payload: unknown;
69
122
  options?: TriggerHookOptions;
70
123
  }
71
124
 
125
+ export type HookNotifySource = "request" | "hook" | "alarm";
126
+
127
+ export type HookNotifyContext = {
128
+ source: HookNotifySource;
129
+ route?: string | null;
130
+ waitUntil?: (promise: Promise<unknown>) => void;
131
+ };
132
+
133
+ export type HookNotifier = {
134
+ notify: (context: HookNotifyContext) => void | Promise<void>;
135
+ };
136
+
137
+ export type DurableHooksRunner = {
138
+ processDue: () => Promise<number>;
139
+ drain: () => Promise<void>;
140
+ };
141
+
142
+ /**
143
+ * @deprecated Use DurableHooksRunner.
144
+ */
72
145
  export type HookScheduler = {
73
146
  schedule: () => Promise<number>;
74
147
  drain: () => Promise<void>;
@@ -78,12 +151,25 @@ export type HookScheduler = {
78
151
  * Configuration for hook processing.
79
152
  */
80
153
  export interface HookProcessorConfig<THooks extends HooksMap = HooksMap> {
81
- hooks: THooks;
154
+ hooks?: THooks;
82
155
  namespace: string;
83
156
  internalFragment: InternalFragmentInstance;
84
157
  handlerTx: HookHandlerTx;
85
158
  /**
86
- * Internal hook scheduler used to coordinate processing/drain.
159
+ * Whether post-mutate notifications should auto-schedule processing.
160
+ * Defaults to true.
161
+ */
162
+ autoSchedule?: boolean;
163
+ /**
164
+ * Internal hook runner used to coordinate processing/drain.
165
+ */
166
+ runner?: DurableHooksRunner;
167
+ /**
168
+ * Post-commit durable hooks notifier.
169
+ */
170
+ notifier?: HookNotifier;
171
+ /**
172
+ * @deprecated Use `runner`.
87
173
  */
88
174
  scheduler?: HookScheduler;
89
175
  defaultRetryPolicy?: RetryPolicy;
@@ -128,11 +214,21 @@ export type DurableHooksProcessingOptions = {
128
214
  * Default: 10 minutes.
129
215
  */
130
216
  stuckProcessingTimeoutMinutes?: StuckHookProcessingTimeoutMinutes;
217
+ /**
218
+ * Automatically schedule hook processing after successful mutations.
219
+ * Defaults to true. Set false to disable auto scheduling (manual drain only).
220
+ */
221
+ autoSchedule?: boolean;
131
222
  /**
132
223
  * Called when stuck processing hooks are detected and re-queued.
133
224
  * Invoked after the hooks are moved back to `pending`.
134
225
  */
135
226
  onStuckProcessingHooks?: (info: StuckHookProcessingInfo) => void;
227
+ /**
228
+ * Durable hooks logging controls.
229
+ * Defaults: enabled=true, level="warn".
230
+ */
231
+ logging?: DurableHooksLoggerConfig;
136
232
  };
137
233
 
138
234
  export type HookHandlerTx = (
@@ -157,12 +253,16 @@ function resolveStuckProcessingTimeoutMinutes(
157
253
  * Add hook events as mutation operations to the UOW.
158
254
  * This should be called before executeMutations() so hook records are created
159
255
  * in the same transaction as the user's mutations.
256
+ *
257
+ * Each triggered hook carries its own namespace (set at trigger-time), so this
258
+ * function works regardless of which fragment's handler is executing. When a
259
+ * `config` is provided its `defaultRetryPolicy` is used as fallback.
160
260
  */
161
- export function prepareHookMutations<THooks extends HooksMap>(
162
- uow: IUnitOfWork,
163
- config: HookProcessorConfig<THooks>,
261
+ export function prepareHookMutations(
262
+ uow: Pick<IUnitOfWork, "getTriggeredHooks" | "forSchema" | "idempotencyKey">,
263
+ internalFragment: InternalFragmentInstance,
264
+ defaultRetryPolicy?: RetryPolicy,
164
265
  ): void {
165
- const { namespace, internalFragment, defaultRetryPolicy } = config;
166
266
  const retryPolicy = defaultRetryPolicy ?? new ExponentialBackoffRetryPolicy({ maxRetries: 5 });
167
267
 
168
268
  const triggeredHooks = uow.getTriggeredHooks();
@@ -171,16 +271,45 @@ export function prepareHookMutations<THooks extends HooksMap>(
171
271
  return;
172
272
  }
173
273
 
274
+ const primaryNamespace = triggeredHooks[0]?.namespace;
275
+ DurableHooksLogger.debug("Durable hooks queued", {
276
+ namespace: primaryNamespace,
277
+ fields: () => {
278
+ const hookSummary = new Map<string, { namespace: string; hookName: string; count: number }>();
279
+ for (const hook of triggeredHooks) {
280
+ const key = `${hook.namespace}:${hook.hookName}`;
281
+ const summary = hookSummary.get(key);
282
+ if (summary) {
283
+ summary.count += 1;
284
+ continue;
285
+ }
286
+ hookSummary.set(key, {
287
+ namespace: hook.namespace,
288
+ hookName: hook.hookName,
289
+ count: 1,
290
+ });
291
+ }
292
+ const hooks = Array.from(hookSummary.values());
293
+ return {
294
+ hooks,
295
+ total: triggeredHooks.length,
296
+ };
297
+ },
298
+ });
299
+
174
300
  const internalSchema = internalFragment.$internal.deps.schema;
175
301
  const internalUow = uow.forSchema(internalSchema);
176
302
 
177
303
  for (const hook of triggeredHooks) {
178
304
  const hookRetryPolicy = hook.options?.retryPolicy ?? retryPolicy;
179
305
  const maxAttempts = hookRetryPolicy.shouldRetry(4) ? 5 : 1;
180
- const processAt = hook.options?.processAt ? new Date(hook.options.processAt) : null;
306
+ const rawProcessAt = hook.options?.processAt ?? null;
307
+ const processAt: Date | DbNow | null =
308
+ rawProcessAt === null ? null : isDbNow(rawProcessAt) ? rawProcessAt : new Date(rawProcessAt);
181
309
  const nextRetryAt = processAt ?? null;
182
- internalUow.create("fragno_hooks", {
183
- namespace,
310
+ const hookValues = {
311
+ ...(hook.options?.id !== undefined ? { id: hook.options.id } : {}),
312
+ namespace: hook.namespace,
184
313
  hookName: hook.hookName,
185
314
  payload: hook.payload,
186
315
  status: "pending",
@@ -190,7 +319,9 @@ export function prepareHookMutations<THooks extends HooksMap>(
190
319
  nextRetryAt,
191
320
  error: null,
192
321
  nonce: uow.idempotencyKey,
193
- });
322
+ };
323
+
324
+ internalUow.create("fragno_hooks", hookValues);
194
325
  }
195
326
  }
196
327
 
@@ -202,68 +333,113 @@ export async function processHooks<THooks extends HooksMap>(
202
333
  config: HookProcessorConfig<THooks>,
203
334
  ): Promise<number> {
204
335
  const { hooks, namespace, internalFragment, defaultRetryPolicy } = config;
336
+ if (!hooks) {
337
+ throw new Error("Hook processor hooks not initialized.");
338
+ }
205
339
  const retryPolicy = defaultRetryPolicy ?? new ExponentialBackoffRetryPolicy({ maxRetries: 5 });
206
340
  const stuckProcessingTimeoutMinutes = resolveStuckProcessingTimeoutMinutes(
207
341
  config.stuckProcessingTimeoutMinutes,
208
342
  );
209
- const getDbNow = async () => {
210
- const services = internalFragment.services as { getDbNow?: () => Promise<Date> };
211
- if (services.getDbNow) {
212
- return services.getDbNow();
213
- }
214
- return new Date();
215
- };
216
- const dbNow = await getDbNow();
217
-
218
- if (stuckProcessingTimeoutMinutes !== false) {
219
- const staleBefore = new Date(dbNow.getTime() - stuckProcessingTimeoutMinutes * 60_000);
220
- const stuckEvents = await internalFragment.inContext(async function () {
221
- return await this.handlerTx()
222
- .withServiceCalls(
223
- () =>
224
- [
225
- internalFragment.services.hookService.requeueStuckProcessingHooks(
226
- namespace,
227
- staleBefore,
228
- ),
229
- ] as const,
230
- )
231
- .transform(({ serviceResult: [events] }) => events)
232
- .execute();
233
- });
234
-
235
- if (stuckEvents.length > 0) {
236
- try {
237
- config.onStuckProcessingHooks?.({
238
- namespace,
239
- timeoutMinutes: stuckProcessingTimeoutMinutes,
240
- events: stuckEvents,
241
- });
242
- } catch (error) {
243
- console.error("Error calling onStuckProcessingHooks", error);
244
- }
245
- }
246
- }
247
-
248
- // Claim pending events in the same transaction to avoid double-processing.
249
- const pendingEvents = await internalFragment.inContext(async function () {
343
+ const internalSchema = internalFragment.$internal.deps.schema;
344
+ const includeStuckProcessing = stuckProcessingTimeoutMinutes !== false;
345
+ const staleBefore = includeStuckProcessing
346
+ ? dbNow().plus({ minutes: -stuckProcessingTimeoutMinutes })
347
+ : null;
348
+ let claimedEvents: Array<{
349
+ id: FragnoId;
350
+ hookName: string;
351
+ payload: unknown;
352
+ status: HookStatus;
353
+ attempts: number;
354
+ maxAttempts: number;
355
+ idempotencyKey: string;
356
+ lastAttemptAt: Date | null;
357
+ nextRetryAt: Date | null;
358
+ createdAt: Date;
359
+ }> = [];
360
+ let stuckEvents: StuckHookProcessingEvent[] = [];
361
+
362
+ const result = await internalFragment.inContext(async function () {
250
363
  return await this.handlerTx()
251
- .withServiceCalls(
252
- () => [internalFragment.services.hookService.claimPendingHookEvents(namespace)] as const,
253
- )
254
- .transform(({ serviceResult: [events] }) => events)
364
+ .withServiceCalls(() => {
365
+ const pending = internalFragment.services.hookService.claimPendingHookEvents(namespace);
366
+ const stuck = includeStuckProcessing
367
+ ? internalFragment.services.hookService.claimStuckProcessingHookEvents(
368
+ namespace,
369
+ staleBefore!,
370
+ )
371
+ : undefined;
372
+ return [pending, stuck] as const;
373
+ })
374
+ .transform(({ serviceResult: [pendingEvents, stuckResult] }) => ({
375
+ pendingEvents,
376
+ stuckResult,
377
+ }))
255
378
  .execute();
256
379
  });
257
380
 
258
- if (pendingEvents.length === 0) {
381
+ const pendingCount = result.pendingEvents.length;
382
+ const stuckClaimedCount = result.stuckResult?.events.length ?? 0;
383
+ const stuckRequeuedCount = result.stuckResult?.stuckEvents.length ?? 0;
384
+ const staleBeforeApprox = includeStuckProcessing
385
+ ? new Date(Date.now() - stuckProcessingTimeoutMinutes * 60_000).toISOString()
386
+ : null;
387
+ DurableHooksLogger.debug("Durable hooks claimed", {
388
+ namespace,
389
+ fields: {
390
+ pending: pendingCount,
391
+ stuck: stuckClaimedCount,
392
+ requeued: stuckRequeuedCount,
393
+ includeStuckProcessing,
394
+ staleBeforeApprox,
395
+ },
396
+ });
397
+
398
+ claimedEvents = [...result.pendingEvents, ...(result.stuckResult?.events ?? [])].map((event) => ({
399
+ ...event,
400
+ status: assertHookStatus(event.status),
401
+ }));
402
+ stuckEvents = result.stuckResult?.stuckEvents ?? [];
403
+
404
+ if (includeStuckProcessing && stuckEvents.length > 0) {
405
+ try {
406
+ config.onStuckProcessingHooks?.({
407
+ namespace,
408
+ timeoutMinutes: stuckProcessingTimeoutMinutes,
409
+ events: stuckEvents,
410
+ });
411
+ } catch (error) {
412
+ DurableHooksLogger.error("Error calling onStuckProcessingHooks", {
413
+ namespace,
414
+ fields: {
415
+ error: DurableHooksLogger.toErrorMessage(error),
416
+ },
417
+ });
418
+ }
419
+ }
420
+
421
+ if (claimedEvents.length === 0) {
422
+ DurableHooksLogger.debug("Durable hooks idle", {
423
+ namespace,
424
+ });
259
425
  return 0;
260
426
  }
261
427
 
262
428
  // Process events (async work outside transaction)
263
429
  const processedEvents = await Promise.allSettled(
264
- pendingEvents.map(async (event) => {
430
+ claimedEvents.map(async (event) => {
265
431
  const hookFn = hooks[event.hookName];
266
432
  if (!hookFn) {
433
+ DurableHooksLogger.error("Hook missing", {
434
+ namespace,
435
+ fields: {
436
+ eventId: event.id,
437
+ hookName: event.hookName,
438
+ attempts: event.attempts,
439
+ maxAttempts: event.maxAttempts,
440
+ createdAt: event.createdAt.toISOString(),
441
+ },
442
+ });
267
443
  return {
268
444
  eventId: event.id,
269
445
  status: "failed" as const,
@@ -274,17 +450,56 @@ export async function processHooks<THooks extends HooksMap>(
274
450
  }
275
451
 
276
452
  try {
453
+ const startedAt = Date.now();
454
+ DurableHooksLogger.debug("Hook start", {
455
+ namespace,
456
+ fields: {
457
+ eventId: event.id,
458
+ hookName: event.hookName,
459
+ status: event.status,
460
+ attempts: event.attempts,
461
+ maxAttempts: event.maxAttempts,
462
+ createdAt: event.createdAt.toISOString(),
463
+ nextRetryAt: event.nextRetryAt ? event.nextRetryAt.toISOString() : null,
464
+ lastAttemptAt: event.lastAttemptAt ? event.lastAttemptAt.toISOString() : null,
465
+ },
466
+ });
277
467
  const hookContext: HookContext = {
278
468
  idempotencyKey: event.idempotencyKey,
469
+ hookId: event.id,
470
+ hookName: event.hookName,
471
+ status: event.status,
472
+ attempts: event.attempts,
473
+ maxAttempts: event.maxAttempts,
474
+ lastAttemptAt: event.lastAttemptAt,
475
+ nextRetryAt: event.nextRetryAt,
476
+ createdAt: event.createdAt,
279
477
  handlerTx: config.handlerTx,
280
478
  };
281
479
  await hookFn.call(hookContext, event.payload);
480
+ DurableHooksLogger.debug("Hook completed", {
481
+ namespace,
482
+ fields: {
483
+ eventId: event.id,
484
+ hookName: event.hookName,
485
+ ms: Date.now() - startedAt,
486
+ },
487
+ });
282
488
  return {
283
489
  eventId: event.id,
284
490
  status: "completed" as const,
285
491
  };
286
492
  } catch (error) {
287
493
  const errorMessage = error instanceof Error ? error.message : String(error);
494
+ DurableHooksLogger.error("Hook failed", {
495
+ namespace,
496
+ fields: {
497
+ eventId: event.id.toJSON(),
498
+ hookName: event.hookName,
499
+ error: errorMessage,
500
+ idempotencyKey: event.idempotencyKey,
501
+ },
502
+ });
288
503
  return {
289
504
  eventId: event.id,
290
505
  status: "failed" as const,
@@ -299,31 +514,61 @@ export async function processHooks<THooks extends HooksMap>(
299
514
  // Mark events as completed/failed
300
515
  await internalFragment.inContext(async function () {
301
516
  await this.handlerTx()
302
- .withServiceCalls(() => {
303
- const txResults: TxResult<void>[] = [];
517
+ .mutate(({ forSchema }) => {
518
+ const uow = forSchema(internalSchema);
519
+ const now = dbNow();
520
+
304
521
  for (const processedEvent of processedEvents) {
305
522
  if (processedEvent.status === "rejected") {
523
+ DurableHooksLogger.error("Hook processing promise rejected", {
524
+ namespace,
525
+ fields: {
526
+ error: DurableHooksLogger.toErrorMessage(processedEvent.reason),
527
+ },
528
+ });
306
529
  continue;
307
530
  }
308
531
 
309
532
  const { eventId, status } = processedEvent.value;
310
533
 
311
534
  if (status === "completed") {
312
- txResults.push(internalFragment.services.hookService.markHookCompleted(eventId));
313
- } else if (status === "failed") {
314
- const { error, attempts } = processedEvent.value;
315
- txResults.push(
316
- internalFragment.services.hookService.markHookFailed(
317
- eventId,
318
- error,
319
- attempts,
320
- retryPolicy,
321
- dbNow,
322
- ),
535
+ uow.update("fragno_hooks", eventId, (b) =>
536
+ b.set({ status: "completed", lastAttemptAt: now }).check(),
537
+ );
538
+ continue;
539
+ }
540
+
541
+ const { error, attempts } = processedEvent.value;
542
+ const newAttempts = attempts + 1;
543
+ const shouldRetry = retryPolicy.shouldRetry(newAttempts - 1);
544
+
545
+ if (shouldRetry) {
546
+ const delayMs = retryPolicy.getDelayMs(newAttempts - 1);
547
+ const nextRetryAt = now.plus({ ms: delayMs });
548
+ uow.update("fragno_hooks", eventId, (b) =>
549
+ b
550
+ .set({
551
+ status: "pending",
552
+ attempts: newAttempts,
553
+ lastAttemptAt: now,
554
+ nextRetryAt,
555
+ error,
556
+ })
557
+ .check(),
558
+ );
559
+ } else {
560
+ uow.update("fragno_hooks", eventId, (b) =>
561
+ b
562
+ .set({
563
+ status: "failed",
564
+ attempts: newAttempts,
565
+ lastAttemptAt: now,
566
+ error,
567
+ })
568
+ .check(),
323
569
  );
324
570
  }
325
571
  }
326
- return txResults;
327
572
  })
328
573
  .execute();
329
574
  });
@@ -333,15 +578,37 @@ export async function processHooks<THooks extends HooksMap>(
333
578
  0,
334
579
  );
335
580
 
581
+ const failed = processedEvents
582
+ .filter((result) => result.status === "fulfilled" && result.value.status === "failed")
583
+ .map((result) => {
584
+ if (result.status !== "fulfilled") {
585
+ return null;
586
+ }
587
+ return {
588
+ eventId: result.value.eventId,
589
+ error: result.value.error ?? null,
590
+ };
591
+ })
592
+ .filter(Boolean);
593
+
594
+ DurableHooksLogger.debug("Durable hooks processed", {
595
+ namespace,
596
+ fields: {
597
+ processed: processedCount,
598
+ failed: failed.length,
599
+ failures: failed.length > 0 ? failed : null,
600
+ },
601
+ });
602
+
336
603
  return processedCount;
337
604
  }
338
605
 
339
- export function createHookScheduler(config: HookProcessorConfig): HookScheduler {
606
+ export function createDurableHooksRunner(config: HookProcessorConfig): DurableHooksRunner {
340
607
  let processing = false;
341
608
  let queued = false;
342
609
  let currentPromise: Promise<number> | null = null;
343
610
 
344
- const schedule = async () => {
611
+ const processDue = async () => {
345
612
  if (processing) {
346
613
  queued = true;
347
614
  return currentPromise ?? Promise.resolve(0);
@@ -349,16 +616,17 @@ export function createHookScheduler(config: HookProcessorConfig): HookScheduler
349
616
 
350
617
  processing = true;
351
618
  currentPromise = (async () => {
352
- let lastCount = 0;
619
+ let totalProcessed = 0;
353
620
  try {
354
621
  do {
355
622
  queued = false;
356
- lastCount = await processHooks(config);
623
+ totalProcessed += await processHooks(config);
357
624
  } while (queued);
358
- return lastCount;
625
+ return totalProcessed;
359
626
  } finally {
360
627
  processing = false;
361
628
  queued = false;
629
+ currentPromise = null;
362
630
  }
363
631
  })();
364
632
 
@@ -367,12 +635,23 @@ export function createHookScheduler(config: HookProcessorConfig): HookScheduler
367
635
 
368
636
  const drain = async () => {
369
637
  while (true) {
370
- const processed = await schedule();
638
+ const processed = await processDue();
371
639
  if (processed === 0) {
372
640
  return;
373
641
  }
374
642
  }
375
643
  };
376
644
 
377
- return { schedule, drain };
645
+ return { processDue, drain };
646
+ }
647
+
648
+ /**
649
+ * @deprecated Use createDurableHooksRunner.
650
+ */
651
+ export function createHookScheduler(config: HookProcessorConfig): HookScheduler {
652
+ const runner = createDurableHooksRunner(config);
653
+ return {
654
+ schedule: () => runner.processDue(),
655
+ drain: () => runner.drain(),
656
+ };
378
657
  }
package/src/id.test.ts ADDED
@@ -0,0 +1,34 @@
1
+ import { afterEach, describe, expect, it, vi } from "vitest";
2
+
3
+ import { createId, init } from "./id";
4
+
5
+ const idPattern = /^[a-z][0-9a-z]*$/;
6
+
7
+ const createCounter = (start = 0) => {
8
+ let value = start;
9
+ return () => value++;
10
+ };
11
+
12
+ describe("db id", () => {
13
+ afterEach(() => {
14
+ vi.useRealTimers();
15
+ vi.restoreAllMocks();
16
+ });
17
+
18
+ it("re-exports the shared createId implementation", () => {
19
+ const id = createId();
20
+
21
+ expect(id).toHaveLength(24);
22
+ expect(id).toMatch(idPattern);
23
+ });
24
+
25
+ it("re-exports init for deterministic generators", () => {
26
+ vi.useFakeTimers();
27
+ vi.setSystemTime(new Date("2020-01-01T00:00:00.000Z"));
28
+
29
+ const first = init({ random: () => 0, counter: createCounter(0), fingerprint: "fingerprint" });
30
+ const second = init({ random: () => 0, counter: createCounter(0), fingerprint: "fingerprint" });
31
+
32
+ expect(first()).toBe(second());
33
+ });
34
+ });
package/src/id.ts CHANGED
@@ -1,3 +1 @@
1
- import { createId } from "@paralleldrive/cuid2";
2
-
3
- export { createId };
1
+ export { createId, init } from "@fragno-dev/core";