@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
@@ -0,0 +1,65 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+
3
+ import { DurableHooksLogger } from "./durable-hooks-logger";
4
+ import {
5
+ getDurableHooksNotifierByNamespace,
6
+ getDurableHooksRuntimeByConfig,
7
+ getDurableHooksRuntimeByNamespace,
8
+ getDurableHooksRuntimeByToken,
9
+ registerDurableHooksRuntime,
10
+ } from "./durable-hooks-runtime";
11
+ import type { HookNotifier, HookProcessorConfig } from "./hooks";
12
+
13
+ let namespaceCounter = 0;
14
+ const defaultInternalFragment = {} as HookProcessorConfig["internalFragment"];
15
+
16
+ function createConfig(
17
+ namespace: string,
18
+ notifier?: HookNotifier,
19
+ internalFragment: HookProcessorConfig["internalFragment"] = defaultInternalFragment,
20
+ ): HookProcessorConfig {
21
+ return {
22
+ namespace,
23
+ hooks: {},
24
+ notifier,
25
+ internalFragment,
26
+ handlerTx: (() => {
27
+ throw new Error("handlerTx should not be called in durable-hooks-runtime tests");
28
+ }) as HookProcessorConfig["handlerTx"],
29
+ };
30
+ }
31
+
32
+ describe("durable hooks runtime registry", () => {
33
+ it("returns the same token when registering the same config twice", () => {
34
+ const namespace = `runtime-same-config-${namespaceCounter++}`;
35
+ const config = createConfig(namespace);
36
+
37
+ const tokenA = registerDurableHooksRuntime(config);
38
+ const tokenB = registerDurableHooksRuntime(config);
39
+
40
+ expect(tokenA).toBe(tokenB);
41
+ expect(getDurableHooksRuntimeByToken(tokenA)?.config).toBe(config);
42
+ expect(getDurableHooksRuntimeByConfig(config)?.token).toBe(tokenA);
43
+ });
44
+
45
+ it("keeps the latest runtime for scoped namespace lookup and warns on duplicates", () => {
46
+ const warnSpy = vi.spyOn(DurableHooksLogger, "warn").mockImplementation(() => {});
47
+ const namespace = `runtime-duplicate-${namespaceCounter++}`;
48
+ const internalFragment = {} as HookProcessorConfig["internalFragment"];
49
+ const firstConfig = createConfig(namespace, undefined, internalFragment);
50
+ const notifier: HookNotifier = {
51
+ notify: vi.fn(),
52
+ };
53
+ const secondConfig = createConfig(namespace, notifier, internalFragment);
54
+
55
+ const firstToken = registerDurableHooksRuntime(firstConfig);
56
+ const secondToken = registerDurableHooksRuntime(secondConfig);
57
+
58
+ expect(firstToken).not.toBe(secondToken);
59
+ expect(warnSpy).toHaveBeenCalledTimes(1);
60
+ expect(getDurableHooksRuntimeByNamespace(namespace, internalFragment)?.token).toBe(secondToken);
61
+ expect(getDurableHooksNotifierByNamespace(namespace, internalFragment)).toBe(notifier);
62
+
63
+ warnSpy.mockRestore();
64
+ });
65
+ });
@@ -0,0 +1,81 @@
1
+ import { DurableHooksLogger } from "./durable-hooks-logger";
2
+ import type { HookProcessorConfig } from "./hooks";
3
+
4
+ type DurableHooksRuntimeState = {
5
+ token: object;
6
+ config: HookProcessorConfig;
7
+ dispatcherRegistered: boolean;
8
+ dispatcherWarningEmitted: boolean;
9
+ };
10
+
11
+ const runtimeByToken = new WeakMap<object, DurableHooksRuntimeState>();
12
+ const runtimeByConfig = new WeakMap<HookProcessorConfig, DurableHooksRuntimeState>();
13
+ const runtimesByInternalFragment = new WeakMap<
14
+ HookProcessorConfig["internalFragment"],
15
+ Map<string, DurableHooksRuntimeState>
16
+ >();
17
+
18
+ function getNamespaceRuntimeMap(
19
+ internalFragment: HookProcessorConfig["internalFragment"],
20
+ createIfMissing = false,
21
+ ) {
22
+ const existing = runtimesByInternalFragment.get(internalFragment);
23
+ if (existing || !createIfMissing) {
24
+ return existing;
25
+ }
26
+ const created = new Map<string, DurableHooksRuntimeState>();
27
+ runtimesByInternalFragment.set(internalFragment, created);
28
+ return created;
29
+ }
30
+
31
+ export function registerDurableHooksRuntime(config: HookProcessorConfig): object {
32
+ const existing = runtimeByConfig.get(config);
33
+ if (existing) {
34
+ return existing.token;
35
+ }
36
+
37
+ const token = {};
38
+ const runtime: DurableHooksRuntimeState = {
39
+ token,
40
+ config,
41
+ dispatcherRegistered: false,
42
+ dispatcherWarningEmitted: false,
43
+ };
44
+
45
+ runtimeByToken.set(token, runtime);
46
+ runtimeByConfig.set(config, runtime);
47
+ const runtimeByNamespace = getNamespaceRuntimeMap(config.internalFragment, true);
48
+ const existingForNamespace = runtimeByNamespace?.get(config.namespace);
49
+ if (existingForNamespace && existingForNamespace.config !== config) {
50
+ DurableHooksLogger.warn("Durable hooks runtime already registered for namespace", {
51
+ namespace: config.namespace,
52
+ });
53
+ }
54
+ runtimeByNamespace?.set(config.namespace, runtime);
55
+
56
+ return token;
57
+ }
58
+
59
+ export function getDurableHooksRuntimeByToken(token: object): DurableHooksRuntimeState | undefined {
60
+ return runtimeByToken.get(token);
61
+ }
62
+
63
+ export function getDurableHooksRuntimeByConfig(
64
+ config: HookProcessorConfig,
65
+ ): DurableHooksRuntimeState | undefined {
66
+ return runtimeByConfig.get(config);
67
+ }
68
+
69
+ export function getDurableHooksRuntimeByNamespace(
70
+ namespace: string,
71
+ internalFragment: HookProcessorConfig["internalFragment"],
72
+ ): DurableHooksRuntimeState | undefined {
73
+ return getNamespaceRuntimeMap(internalFragment)?.get(namespace);
74
+ }
75
+
76
+ export function getDurableHooksNotifierByNamespace(
77
+ namespace: string,
78
+ internalFragment: HookProcessorConfig["internalFragment"],
79
+ ) {
80
+ return getNamespaceRuntimeMap(internalFragment)?.get(namespace)?.config.notifier;
81
+ }
@@ -1,20 +1,33 @@
1
+ import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
2
+
1
3
  import SQLite from "better-sqlite3";
2
4
  import { SqliteDialect } from "kysely";
3
- import { beforeAll, describe, expect, it, vi } from "vitest";
5
+
4
6
  import { instantiate } from "@fragno-dev/core";
7
+
8
+ import { BetterSQLite3DriverConfig } from "../adapters/generic-sql/driver-config";
9
+ import { SqlAdapter } from "../adapters/generic-sql/generic-sql-adapter";
10
+ import type { FragnoPublicConfigWithDatabase } from "../db-fragment-definition-builder";
11
+ import { internalFragmentDef, internalSchema } from "../fragments/internal-fragment";
12
+ import { getRegistryForAdapterSync } from "../internal/adapter-registry";
13
+ import { ConcurrencyConflictError } from "../query/unit-of-work/execute-unit-of-work";
14
+ import { ExponentialBackoffRetryPolicy, NoRetryPolicy } from "../query/unit-of-work/retry-policy";
15
+ import { FragnoId } from "../schema/create";
16
+ import { DurableHooksLogger } from "./durable-hooks-logger";
5
17
  import {
6
18
  prepareHookMutations,
7
19
  processHooks,
20
+ createDurableHooksRunner,
8
21
  type HooksMap,
9
22
  type HookContext,
10
23
  type HookHandlerTx,
11
24
  } from "./hooks";
12
- import { internalFragmentDef, internalSchema } from "../fragments/internal-fragment";
13
- import type { FragnoPublicConfigWithDatabase } from "../db-fragment-definition-builder";
14
- import { SqlAdapter } from "../adapters/generic-sql/generic-sql-adapter";
15
- import { BetterSQLite3DriverConfig } from "../adapters/generic-sql/driver-config";
16
- import { ExponentialBackoffRetryPolicy, NoRetryPolicy } from "../query/unit-of-work/retry-policy";
17
- import type { FragnoId } from "../schema/create";
25
+
26
+ const TEST_NS = "test";
27
+
28
+ type OptionsWithAdapter = FragnoPublicConfigWithDatabase & {
29
+ databaseAdapter: SqlAdapter;
30
+ };
18
31
 
19
32
  describe("Hook System", () => {
20
33
  const handlerTx = (() => {
@@ -24,8 +37,11 @@ describe("Hook System", () => {
24
37
  let adapter: SqlAdapter;
25
38
  let internalFragment: ReturnType<typeof instantiateFragment>;
26
39
 
27
- function instantiateFragment(options: FragnoPublicConfigWithDatabase) {
28
- return instantiate(internalFragmentDef).withConfig({}).withOptions(options).build();
40
+ function instantiateFragment(options: OptionsWithAdapter) {
41
+ return instantiate(internalFragmentDef)
42
+ .withConfig({ registry: getRegistryForAdapterSync(options.databaseAdapter) })
43
+ .withOptions(options)
44
+ .build();
29
45
  }
30
46
 
31
47
  beforeAll(async () => {
@@ -41,13 +57,13 @@ describe("Hook System", () => {
41
57
  });
42
58
 
43
59
  {
44
- const migrations = adapter.prepareMigrations(internalSchema, null);
60
+ const migrations = adapter.prepareMigrations(internalSchema, TEST_NS);
45
61
  await migrations.executeWithDriver(adapter.driver, 0);
46
62
  }
47
63
 
48
- const options: FragnoPublicConfigWithDatabase = {
64
+ const options: OptionsWithAdapter = {
49
65
  databaseAdapter: adapter,
50
- databaseNamespace: null,
66
+ databaseNamespace: TEST_NS,
51
67
  };
52
68
 
53
69
  internalFragment = instantiateFragment(options);
@@ -58,8 +74,11 @@ describe("Hook System", () => {
58
74
  }, 12000);
59
75
 
60
76
  describe("prepareHookMutations", () => {
77
+ beforeEach(() => {
78
+ sqliteDatabase.exec("DELETE FROM fragno_hooks_test");
79
+ });
80
+
61
81
  it("should create hook records for triggered hooks", async () => {
62
- const namespace = "test-namespace";
63
82
  const hooks: HooksMap = {
64
83
  onTest: vi.fn(),
65
84
  };
@@ -78,13 +97,11 @@ describe("Hook System", () => {
78
97
  uow.triggerHook("onTest", { data: "test" });
79
98
 
80
99
  // Prepare hook mutations
81
- prepareHookMutations(uow, {
82
- hooks,
83
- namespace,
100
+ prepareHookMutations(
101
+ uow,
84
102
  internalFragment,
85
- handlerTx,
86
- defaultRetryPolicy: new ExponentialBackoffRetryPolicy({ maxRetries: 5 }),
87
- });
103
+ new ExponentialBackoffRetryPolicy({ maxRetries: 5 }),
104
+ );
88
105
  })
89
106
  .execute();
90
107
  });
@@ -97,7 +114,7 @@ describe("Hook System", () => {
97
114
  const events = await internalFragment.inContext(async function () {
98
115
  return await this.handlerTx()
99
116
  .withServiceCalls(
100
- () => [internalFragment.services.hookService.getPendingHookEvents(namespace)] as const,
117
+ () => [internalFragment.services.hookService.getHooksByNamespace(TEST_NS)] as const,
101
118
  )
102
119
  .transform(({ serviceResult: [result] }) => result)
103
120
  .execute();
@@ -112,8 +129,97 @@ describe("Hook System", () => {
112
129
  });
113
130
  });
114
131
 
132
+ it("should store a provided hook id", async () => {
133
+ const namespace = internalFragment.$internal.deps.namespace ?? internalSchema.name;
134
+ const hookId = "hook-id-1";
135
+ const hooks: HooksMap = {
136
+ onId: vi.fn(),
137
+ };
138
+
139
+ await internalFragment.inContext(async function () {
140
+ await this.handlerTx()
141
+ .mutate(({ forSchema }) => {
142
+ const uow = forSchema(internalSchema, hooks);
143
+
144
+ uow.triggerHook("onId", { data: "test" }, { id: hookId });
145
+
146
+ prepareHookMutations(
147
+ uow,
148
+ internalFragment,
149
+ new ExponentialBackoffRetryPolicy({ maxRetries: 5 }),
150
+ );
151
+ })
152
+ .execute();
153
+ });
154
+
155
+ const events = await internalFragment.inContext(async function () {
156
+ return await this.handlerTx()
157
+ .withServiceCalls(
158
+ () => [internalFragment.services.hookService.getHooksByNamespace(namespace)] as const,
159
+ )
160
+ .transform(({ serviceResult: [result] }) => result)
161
+ .execute();
162
+ });
163
+
164
+ expect(events).toHaveLength(1);
165
+ expect(events[0]?.id.externalId).toBe(hookId);
166
+ });
167
+
168
+ it("should fail when a hook id already exists", async () => {
169
+ const namespace = internalFragment.$internal.deps.namespace ?? internalSchema.name;
170
+ const hookId = "hook-id-conflict";
171
+ const hooks: HooksMap = {
172
+ onIdConflict: vi.fn(),
173
+ };
174
+
175
+ await internalFragment.inContext(async function () {
176
+ await this.handlerTx()
177
+ .mutate(({ forSchema }) => {
178
+ const uow = forSchema(internalSchema, hooks);
179
+
180
+ uow.triggerHook("onIdConflict", { data: "first" }, { id: hookId });
181
+
182
+ prepareHookMutations(
183
+ uow,
184
+ internalFragment,
185
+ new ExponentialBackoffRetryPolicy({ maxRetries: 5 }),
186
+ );
187
+ })
188
+ .execute();
189
+ });
190
+
191
+ await expect(
192
+ internalFragment.inContext(async function () {
193
+ await this.handlerTx()
194
+ .mutate(({ forSchema }) => {
195
+ const uow = forSchema(internalSchema, hooks);
196
+
197
+ uow.triggerHook("onIdConflict", { data: "second" }, { id: hookId });
198
+
199
+ prepareHookMutations(
200
+ uow,
201
+ internalFragment,
202
+ new ExponentialBackoffRetryPolicy({ maxRetries: 5 }),
203
+ );
204
+ })
205
+ .execute();
206
+ }),
207
+ ).rejects.toThrow();
208
+
209
+ const events = await internalFragment.inContext(async function () {
210
+ return await this.handlerTx()
211
+ .withServiceCalls(
212
+ () => [internalFragment.services.hookService.getHooksByNamespace(namespace)] as const,
213
+ )
214
+ .transform(({ serviceResult: [result] }) => result)
215
+ .execute();
216
+ });
217
+
218
+ expect(events).toHaveLength(1);
219
+ expect(events[0]?.id.externalId).toBe(hookId);
220
+ });
221
+
115
222
  it("should set maxAttempts to 1 when retry policy does not retry", async () => {
116
- const namespace = "test-no-retry";
117
223
  const hooks: HooksMap = {
118
224
  onNoRetry: vi.fn(),
119
225
  };
@@ -125,13 +231,7 @@ describe("Hook System", () => {
125
231
 
126
232
  uow.triggerHook("onNoRetry", { data: "test" });
127
233
 
128
- prepareHookMutations(uow, {
129
- hooks,
130
- namespace,
131
- internalFragment,
132
- handlerTx,
133
- defaultRetryPolicy: new NoRetryPolicy(),
134
- });
234
+ prepareHookMutations(uow, internalFragment, new NoRetryPolicy());
135
235
  })
136
236
  .execute();
137
237
  });
@@ -139,7 +239,7 @@ describe("Hook System", () => {
139
239
  const events = await internalFragment.inContext(async function () {
140
240
  return await this.handlerTx()
141
241
  .withServiceCalls(
142
- () => [internalFragment.services.hookService.getPendingHookEvents(namespace)] as const,
242
+ () => [internalFragment.services.hookService.getHooksByNamespace(TEST_NS)] as const,
143
243
  )
144
244
  .transform(({ serviceResult: [result] }) => result)
145
245
  .execute();
@@ -150,7 +250,6 @@ describe("Hook System", () => {
150
250
  });
151
251
 
152
252
  it("should use custom retry policy from trigger options", async () => {
153
- const namespace = "test-custom-retry";
154
253
  const hooks: HooksMap = {
155
254
  onCustomRetry: vi.fn(),
156
255
  };
@@ -168,13 +267,11 @@ describe("Hook System", () => {
168
267
  },
169
268
  );
170
269
 
171
- prepareHookMutations(uow, {
172
- hooks,
173
- namespace,
270
+ prepareHookMutations(
271
+ uow,
174
272
  internalFragment,
175
- handlerTx,
176
- defaultRetryPolicy: new ExponentialBackoffRetryPolicy({ maxRetries: 10 }),
177
- });
273
+ new ExponentialBackoffRetryPolicy({ maxRetries: 10 }),
274
+ );
178
275
  })
179
276
  .execute();
180
277
  });
@@ -182,7 +279,7 @@ describe("Hook System", () => {
182
279
  const events = await internalFragment.inContext(async function () {
183
280
  return await this.handlerTx()
184
281
  .withServiceCalls(
185
- () => [internalFragment.services.hookService.getPendingHookEvents(namespace)] as const,
282
+ () => [internalFragment.services.hookService.getHooksByNamespace(TEST_NS)] as const,
186
283
  )
187
284
  .transform(({ serviceResult: [result] }) => result)
188
285
  .execute();
@@ -192,7 +289,6 @@ describe("Hook System", () => {
192
289
  });
193
290
 
194
291
  it("should set nextRetryAt when processAt is in the future", async () => {
195
- const namespace = "test-process-at-future";
196
292
  const hooks: HooksMap = {
197
293
  onScheduled: vi.fn(),
198
294
  };
@@ -205,13 +301,11 @@ describe("Hook System", () => {
205
301
 
206
302
  uow.triggerHook("onScheduled", { data: "test" }, { processAt: futureTime });
207
303
 
208
- prepareHookMutations(uow, {
209
- hooks,
210
- namespace,
304
+ prepareHookMutations(
305
+ uow,
211
306
  internalFragment,
212
- handlerTx,
213
- defaultRetryPolicy: new ExponentialBackoffRetryPolicy({ maxRetries: 5 }),
214
- });
307
+ new ExponentialBackoffRetryPolicy({ maxRetries: 5 }),
308
+ );
215
309
  })
216
310
  .execute();
217
311
  });
@@ -219,7 +313,7 @@ describe("Hook System", () => {
219
313
  const events = await internalFragment.inContext(async function () {
220
314
  return await this.handlerTx()
221
315
  .withServiceCalls(
222
- () => [internalFragment.services.hookService.getHooksByNamespace(namespace)] as const,
316
+ () => [internalFragment.services.hookService.getHooksByNamespace(TEST_NS)] as const,
223
317
  )
224
318
  .transform(({ serviceResult: [result] }) => result)
225
319
  .execute();
@@ -231,7 +325,6 @@ describe("Hook System", () => {
231
325
  });
232
326
 
233
327
  it("should keep processAt in the past while remaining immediately eligible", async () => {
234
- const namespace = "test-process-at-past";
235
328
  const hooks: HooksMap = {
236
329
  onImmediate: vi.fn(),
237
330
  };
@@ -244,13 +337,11 @@ describe("Hook System", () => {
244
337
 
245
338
  uow.triggerHook("onImmediate", { data: "test" }, { processAt: pastTime });
246
339
 
247
- prepareHookMutations(uow, {
248
- hooks,
249
- namespace,
340
+ prepareHookMutations(
341
+ uow,
250
342
  internalFragment,
251
- handlerTx,
252
- defaultRetryPolicy: new ExponentialBackoffRetryPolicy({ maxRetries: 5 }),
253
- });
343
+ new ExponentialBackoffRetryPolicy({ maxRetries: 5 }),
344
+ );
254
345
  })
255
346
  .execute();
256
347
  });
@@ -258,7 +349,7 @@ describe("Hook System", () => {
258
349
  const events = await internalFragment.inContext(async function () {
259
350
  return await this.handlerTx()
260
351
  .withServiceCalls(
261
- () => [internalFragment.services.hookService.getHooksByNamespace(namespace)] as const,
352
+ () => [internalFragment.services.hookService.getHooksByNamespace(TEST_NS)] as const,
262
353
  )
263
354
  .transform(({ serviceResult: [result] }) => result)
264
355
  .execute();
@@ -268,6 +359,54 @@ describe("Hook System", () => {
268
359
  expect(events[0]?.nextRetryAt).toBeInstanceOf(Date);
269
360
  expect(events[0]?.nextRetryAt?.getTime()).toBe(pastTime.getTime());
270
361
  });
362
+
363
+ it("should preserve full hookName in queued hooks log summary when it contains colons", async () => {
364
+ const hookName = "on:segment:created";
365
+ const hooks: HooksMap = {
366
+ [hookName]: vi.fn(),
367
+ };
368
+ const debugSpy = vi.spyOn(DurableHooksLogger, "debug").mockImplementation(() => {});
369
+
370
+ try {
371
+ await internalFragment.inContext(async function () {
372
+ await this.handlerTx()
373
+ .mutate(({ forSchema }) => {
374
+ const uow = forSchema(internalSchema, hooks);
375
+ uow.triggerHook(hookName, { data: "test" });
376
+ prepareHookMutations(
377
+ uow,
378
+ internalFragment,
379
+ new ExponentialBackoffRetryPolicy({ maxRetries: 5 }),
380
+ );
381
+ })
382
+ .execute();
383
+ });
384
+
385
+ const queuedCall = debugSpy.mock.calls.find(
386
+ ([message]) => message === "Durable hooks queued",
387
+ );
388
+ expect(queuedCall).toBeDefined();
389
+
390
+ const fields = queuedCall?.[1]?.fields;
391
+ expect(typeof fields).toBe("function");
392
+
393
+ const summary = (
394
+ fields as () => {
395
+ hooks: Array<{ namespace: string; hookName: string; count: number }>;
396
+ total: number;
397
+ }
398
+ )();
399
+
400
+ expect(summary.hooks).toContainEqual({
401
+ namespace: TEST_NS,
402
+ hookName,
403
+ count: 1,
404
+ });
405
+ expect(summary.total).toBe(1);
406
+ } finally {
407
+ debugSpy.mockRestore();
408
+ }
409
+ });
271
410
  });
272
411
 
273
412
  describe("processHooks", () => {
@@ -333,6 +472,79 @@ describe("Hook System", () => {
333
472
  expect(result?.lastAttemptAt).toBeInstanceOf(Date);
334
473
  });
335
474
 
475
+ it("should surface conflicts when a hook mutates its own record", async () => {
476
+ const namespace = "test-claim-conflict";
477
+ let eventId!: FragnoId;
478
+
479
+ const hookFn = vi.fn(async (payload: { externalId: string; version: number }) => {
480
+ const conflictId = new FragnoId({
481
+ externalId: payload.externalId,
482
+ version: payload.version,
483
+ });
484
+ await internalFragment.inContext(async function () {
485
+ await this.handlerTx()
486
+ .mutate(({ forSchema }) => {
487
+ const uow = forSchema(internalSchema);
488
+ uow.update("fragno_hooks", conflictId, (b) => b.set({ status: "processing" }));
489
+ })
490
+ .execute();
491
+ });
492
+ });
493
+
494
+ const hooks: HooksMap = {
495
+ onConflict: hookFn,
496
+ };
497
+
498
+ await internalFragment.inContext(async function () {
499
+ const createdId = await this.handlerTx()
500
+ .mutate(({ forSchema }) => {
501
+ const uow = forSchema(internalSchema);
502
+ return uow.create("fragno_hooks", {
503
+ id: "hook-conflict-id",
504
+ namespace,
505
+ hookName: "onConflict",
506
+ payload: { externalId: "hook-conflict-id", version: 0 },
507
+ status: "pending",
508
+ attempts: 0,
509
+ maxAttempts: 5,
510
+ lastAttemptAt: null,
511
+ nextRetryAt: null,
512
+ error: null,
513
+ nonce: "test-nonce-conflict",
514
+ });
515
+ })
516
+ .execute();
517
+ eventId = createdId;
518
+ });
519
+
520
+ await expect(
521
+ processHooks({
522
+ hooks,
523
+ namespace,
524
+ internalFragment,
525
+ handlerTx,
526
+ defaultRetryPolicy: new ExponentialBackoffRetryPolicy({ maxRetries: 3 }),
527
+ }),
528
+ ).rejects.toThrow(ConcurrencyConflictError);
529
+
530
+ expect(hookFn).toHaveBeenCalledOnce();
531
+ expect(hookFn).toHaveBeenCalledWith({
532
+ externalId: eventId.externalId,
533
+ version: eventId.version,
534
+ });
535
+
536
+ const event = await internalFragment.inContext(async function () {
537
+ return await this.handlerTx()
538
+ .withServiceCalls(
539
+ () => [internalFragment.services.hookService.getHookById(eventId)] as const,
540
+ )
541
+ .transform(({ serviceResult: [result] }) => result)
542
+ .execute();
543
+ });
544
+
545
+ expect(event?.status).toBe("processing");
546
+ });
547
+
336
548
  it("should mark failed hooks for retry", async () => {
337
549
  const namespace = "test-failure";
338
550
  const hookFn = vi.fn().mockRejectedValue(new Error("Hook failed"));
@@ -736,4 +948,53 @@ describe("Hook System", () => {
736
948
  expect(hookFn).not.toHaveBeenCalled();
737
949
  });
738
950
  });
951
+
952
+ describe("createDurableHooksRunner", () => {
953
+ it("should return the total processed count across queued reruns", async () => {
954
+ const namespace = "test-runner-queued";
955
+ let resolveHook!: () => void;
956
+ const hookDone = new Promise<void>((resolve) => {
957
+ resolveHook = resolve;
958
+ });
959
+ const hookFn = vi.fn(async () => {
960
+ await hookDone;
961
+ });
962
+
963
+ await internalFragment.inContext(async function () {
964
+ await this.handlerTx()
965
+ .mutate(({ forSchema }) => {
966
+ const uow = forSchema(internalSchema);
967
+ uow.create("fragno_hooks", {
968
+ namespace,
969
+ hookName: "onQueued",
970
+ payload: { ok: true },
971
+ status: "pending",
972
+ attempts: 0,
973
+ maxAttempts: 1,
974
+ lastAttemptAt: null,
975
+ nextRetryAt: null,
976
+ error: null,
977
+ nonce: "runner-queued-nonce",
978
+ });
979
+ })
980
+ .execute();
981
+ });
982
+
983
+ const runner = createDurableHooksRunner({
984
+ hooks: { onQueued: hookFn },
985
+ namespace,
986
+ internalFragment,
987
+ handlerTx,
988
+ defaultRetryPolicy: new NoRetryPolicy(),
989
+ });
990
+
991
+ const firstRun = runner.processDue();
992
+ const queuedRun = runner.processDue();
993
+ resolveHook();
994
+
995
+ await expect(firstRun).resolves.toBe(1);
996
+ await expect(queuedRun).resolves.toBe(1);
997
+ expect(hookFn).toHaveBeenCalledTimes(1);
998
+ });
999
+ });
739
1000
  });