@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,126 @@
1
+ type LogFields = Record<string, unknown>;
2
+ type LogFieldsInput = LogFields | (() => LogFields);
3
+
4
+ type LogLevel = "debug" | "info" | "warn" | "error";
5
+ const LOG_LEVEL_PRIORITY: Record<DurableHooksLogLevel, number> = {
6
+ off: 0,
7
+ error: 1,
8
+ warn: 2,
9
+ info: 3,
10
+ debug: 4,
11
+ };
12
+
13
+ const DEFAULT_SCOPE: Required<DurableHooksLoggerConfig> = {
14
+ enabled: true,
15
+ level: "warn",
16
+ };
17
+
18
+ export type DurableHooksLogLevel = "off" | "error" | "warn" | "info" | "debug";
19
+
20
+ export type DurableHooksLoggerConfig = {
21
+ enabled?: boolean;
22
+ level?: DurableHooksLogLevel;
23
+ };
24
+
25
+ type LoggerScope = Required<DurableHooksLoggerConfig>;
26
+
27
+ type LogOptions = {
28
+ namespace?: string;
29
+ fields?: LogFieldsInput;
30
+ };
31
+
32
+ export class DurableHooksLogger {
33
+ static #defaultScope: LoggerScope = { ...DEFAULT_SCOPE };
34
+ static #namespaceScopes = new Map<string, LoggerScope>();
35
+
36
+ static configure(config?: DurableHooksLoggerConfig, namespace?: string): void {
37
+ if (!config) {
38
+ return;
39
+ }
40
+
41
+ const target = namespace
42
+ ? (DurableHooksLogger.#namespaceScopes.get(namespace) ?? {
43
+ ...DurableHooksLogger.#defaultScope,
44
+ })
45
+ : { ...DurableHooksLogger.#defaultScope };
46
+
47
+ if (config.enabled !== undefined) {
48
+ target.enabled = config.enabled;
49
+ }
50
+ if (config.level !== undefined) {
51
+ target.level = config.level;
52
+ }
53
+
54
+ if (namespace) {
55
+ DurableHooksLogger.#namespaceScopes.set(namespace, target);
56
+ return;
57
+ }
58
+
59
+ DurableHooksLogger.#defaultScope = target;
60
+ }
61
+
62
+ static enable(namespace?: string): void {
63
+ DurableHooksLogger.configure({ enabled: true }, namespace);
64
+ }
65
+
66
+ static disable(namespace?: string): void {
67
+ DurableHooksLogger.configure({ enabled: false }, namespace);
68
+ }
69
+
70
+ static setLogLevel(level: DurableHooksLogLevel, namespace?: string): void {
71
+ DurableHooksLogger.configure({ level }, namespace);
72
+ }
73
+
74
+ static debug(message: string, options?: LogOptions): void {
75
+ DurableHooksLogger.#log("debug", message, options);
76
+ }
77
+
78
+ static info(message: string, options?: LogOptions): void {
79
+ DurableHooksLogger.#log("info", message, options);
80
+ }
81
+
82
+ static warn(message: string, options?: LogOptions): void {
83
+ DurableHooksLogger.#log("warn", message, options);
84
+ }
85
+
86
+ static error(message: string, options?: LogOptions): void {
87
+ DurableHooksLogger.#log("error", message, options);
88
+ }
89
+
90
+ static toErrorMessage(error: unknown): string {
91
+ return error instanceof Error ? error.message : String(error);
92
+ }
93
+
94
+ static #log(level: LogLevel, message: string, options?: LogOptions): void {
95
+ const scope = DurableHooksLogger.#resolveScope(options?.namespace);
96
+ if (!scope.enabled) {
97
+ return;
98
+ }
99
+ if (LOG_LEVEL_PRIORITY[level] > LOG_LEVEL_PRIORITY[scope.level]) {
100
+ return;
101
+ }
102
+
103
+ const fields = DurableHooksLogger.#resolveFields(options?.fields);
104
+ const payload = {
105
+ at: new Date().toISOString(),
106
+ ...(options?.namespace ? { namespace: options.namespace } : {}),
107
+ ...fields,
108
+ };
109
+
110
+ console[level](`[fragno-db] ${message}`, payload);
111
+ }
112
+
113
+ static #resolveScope(namespace?: string): LoggerScope {
114
+ if (!namespace) {
115
+ return DurableHooksLogger.#defaultScope;
116
+ }
117
+ return DurableHooksLogger.#namespaceScopes.get(namespace) ?? DurableHooksLogger.#defaultScope;
118
+ }
119
+
120
+ static #resolveFields(fields?: LogFieldsInput): LogFields {
121
+ if (!fields) {
122
+ return {};
123
+ }
124
+ return typeof fields === "function" ? fields() : fields;
125
+ }
126
+ }
@@ -0,0 +1,87 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+
3
+ import { KyselyPGlite } from "kysely-pglite";
4
+
5
+ import { defineFragment, instantiate } from "@fragno-dev/core";
6
+
7
+ import { PGlite } from "@electric-sql/pglite";
8
+
9
+ import { PGLiteDriverConfig } from "../adapters/generic-sql/driver-config";
10
+ import { SqlAdapter } from "../adapters/generic-sql/generic-sql-adapter";
11
+ import { internalSchema } from "../fragments/internal-fragment.schema";
12
+ import { column, idColumn, schema } from "../schema/create";
13
+ import { withDatabase } from "../with-database";
14
+ import { createDurableHooksProcessor } from "./durable-hooks-processor";
15
+
16
+ const testSchema = schema("hook_test", (s) =>
17
+ s.addTable("items", (t) => t.addColumn("id", idColumn()).addColumn("name", column("string"))),
18
+ );
19
+
20
+ async function createPgliteAdapter() {
21
+ const pglite = new PGlite();
22
+ const { dialect } = new KyselyPGlite(pglite);
23
+ const adapter = new SqlAdapter({
24
+ dialect,
25
+ driverConfig: new PGLiteDriverConfig(),
26
+ });
27
+
28
+ const internalMigrations = adapter.prepareMigrations(internalSchema, null);
29
+ await internalMigrations.executeWithDriver(adapter.driver, 0);
30
+
31
+ const testMigrations = adapter.prepareMigrations(testSchema, testSchema.name);
32
+ await testMigrations.executeWithDriver(adapter.driver, 0);
33
+
34
+ return {
35
+ adapter,
36
+ cleanup: async () => {
37
+ await adapter.close();
38
+ },
39
+ };
40
+ }
41
+
42
+ describe("durable hooks with pglite", () => {
43
+ it("keeps durable hooks processing after duplicate hook ids", async () => {
44
+ const hookFn = vi.fn();
45
+ const fragmentDefinition = defineFragment("hook-test")
46
+ .extend(withDatabase(testSchema))
47
+ .provideHooks(({ defineHook }) => ({
48
+ onTest: defineHook(async function (payload: { ok: boolean }) {
49
+ hookFn(payload.ok);
50
+ }),
51
+ }))
52
+ .build();
53
+
54
+ const { adapter, cleanup } = await createPgliteAdapter();
55
+
56
+ try {
57
+ const fragment = instantiate(fragmentDefinition)
58
+ .withConfig({})
59
+ .withRoutes([])
60
+ .withOptions({ databaseAdapter: adapter, durableHooks: { autoSchedule: false } })
61
+ .build();
62
+
63
+ const processor = createDurableHooksProcessor(fragment);
64
+ const hookId = "hook-id-duplicate";
65
+
66
+ const triggerHook = async () => {
67
+ await fragment.inContext(async function () {
68
+ await this.handlerTx()
69
+ .mutate(({ forSchema }) => {
70
+ const uow = forSchema(testSchema);
71
+ uow.triggerHook("onTest", { ok: true }, { id: hookId });
72
+ })
73
+ .execute();
74
+ });
75
+ };
76
+
77
+ await triggerHook();
78
+ await expect(triggerHook()).rejects.toThrow();
79
+
80
+ await processor.drain();
81
+
82
+ expect(hookFn).toHaveBeenCalledTimes(1);
83
+ } finally {
84
+ await cleanup();
85
+ }
86
+ }, 15000);
87
+ });
@@ -1,33 +1,68 @@
1
+ import { beforeAll, 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 } from "vitest";
5
+
4
6
  import { defineFragment, instantiate } from "@fragno-dev/core";
5
- import { withDatabase } from "../with-database";
6
- import { schema, column, idColumn } from "../schema/create";
7
- import { SqlAdapter } from "../adapters/generic-sql/generic-sql-adapter";
7
+
8
8
  import { BetterSQLite3DriverConfig } from "../adapters/generic-sql/driver-config";
9
+ import { SqlAdapter } from "../adapters/generic-sql/generic-sql-adapter";
9
10
  import { internalSchema } from "../fragments/internal-fragment";
10
- import { createDurableHooksProcessor } from "./durable-hooks-processor";
11
+ import { getInternalFragment } from "../internal/adapter-registry";
12
+ import type { TxResult } from "../query/unit-of-work/execute-unit-of-work";
13
+ import { schema, column, idColumn } from "../schema/create";
14
+ import { withDatabase } from "../with-database";
15
+ import {
16
+ createDurableHooksProcessor,
17
+ createDurableHooksProcessorGroup,
18
+ createDurableHooksProcessorGroupFromProcessors,
19
+ } from "./durable-hooks-processor";
11
20
 
12
21
  const testSchema = schema("test", (s) =>
13
22
  s.addTable("items", (t) => t.addColumn("id", idColumn()).addColumn("name", column("string"))),
14
23
  );
15
24
 
25
+ const boundServiceHookSpy = vi.fn<(count: number) => void>();
26
+ let getItemCountService: (() => TxResult<number>) | undefined;
27
+
16
28
  const testFragmentDefinition = defineFragment("test")
17
29
  .extend(withDatabase(testSchema))
30
+ .providesBaseService(({ defineService }) =>
31
+ defineService({
32
+ getItemCount() {
33
+ return this.serviceTx(testSchema)
34
+ .retrieve((uow) => uow.find("items", (b) => b.whereIndex("primary")))
35
+ .transformRetrieve(([items]) => items.length)
36
+ .build();
37
+ },
38
+ }),
39
+ )
18
40
  .provideHooks(({ defineHook }) => ({
19
41
  onTest: defineHook(async function () {}),
42
+ onTestUsesBoundService: defineHook(async function () {
43
+ const itemCount = await this.handlerTx()
44
+ .withServiceCalls(() => [getItemCountService!()] as const)
45
+ .transform(({ serviceResult: [count] }) => count)
46
+ .execute();
47
+
48
+ boundServiceHookSpy(itemCount);
49
+ }),
20
50
  }))
21
51
  .build();
22
52
 
53
+ const noHooksFragmentDefinition = defineFragment("no-hooks")
54
+ .extend(withDatabase(testSchema))
55
+ .build();
56
+
23
57
  describe("createDurableHooksProcessor", () => {
24
58
  let adapter: SqlAdapter;
25
- let fragment: ReturnType<typeof instantiateFragment>;
26
59
 
27
60
  function instantiateFragment(options: { databaseAdapter: SqlAdapter }) {
28
61
  return instantiate(testFragmentDefinition).withConfig({}).withOptions(options).build();
29
62
  }
30
63
 
64
+ let fragment: ReturnType<typeof instantiateFragment>;
65
+
31
66
  beforeAll(async () => {
32
67
  const sqliteDatabase = new SQLite(":memory:");
33
68
  const dialect = new SqliteDialect({ database: sqliteDatabase });
@@ -44,6 +79,7 @@ describe("createDurableHooksProcessor", () => {
44
79
  await testMigrations.executeWithDriver(adapter.driver, 0);
45
80
 
46
81
  fragment = instantiateFragment({ databaseAdapter: adapter });
82
+ getItemCountService = fragment.services.getItemCount;
47
83
 
48
84
  return async () => {
49
85
  await adapter.close();
@@ -54,7 +90,7 @@ describe("createDurableHooksProcessor", () => {
54
90
  const processor = createDurableHooksProcessor(fragment);
55
91
  expect(processor).not.toBeNull();
56
92
 
57
- const internalFragment = fragment.$internal.linkedFragments._fragno_internal;
93
+ const internalFragment = getInternalFragment(adapter);
58
94
  await internalFragment.inContext(async function () {
59
95
  await this.handlerTx()
60
96
  .mutate(({ forSchema }) => {
@@ -75,10 +111,10 @@ describe("createDurableHooksProcessor", () => {
75
111
  .execute();
76
112
  });
77
113
 
78
- const wakeAt = await processor!.getNextWakeAt();
114
+ const wakeAt = await processor.getNextWakeAt();
79
115
  expect(wakeAt).toBeInstanceOf(Date);
80
116
 
81
- const processed = await processor!.process();
117
+ const processed = await processor.processDue();
82
118
  expect(processed).toBe(1);
83
119
  });
84
120
 
@@ -86,9 +122,8 @@ describe("createDurableHooksProcessor", () => {
86
122
  const processor = createDurableHooksProcessor(fragment);
87
123
  expect(processor).not.toBeNull();
88
124
 
89
- const internalFragment = fragment.$internal.linkedFragments._fragno_internal;
90
- const services = internalFragment.services as { getDbNow?: () => Promise<Date> };
91
- const baseNow = services.getDbNow ? await services.getDbNow() : new Date();
125
+ const internalFragment = getInternalFragment(adapter);
126
+ const baseNow = new Date();
92
127
 
93
128
  await internalFragment.inContext(async function () {
94
129
  await this.handlerTx()
@@ -110,8 +145,138 @@ describe("createDurableHooksProcessor", () => {
110
145
  .execute();
111
146
  });
112
147
 
113
- const wakeAt = await processor!.getNextWakeAt();
148
+ const wakeAt = await processor.getNextWakeAt();
114
149
  expect(wakeAt).toBeInstanceOf(Date);
115
- expect(wakeAt!.getTime()).toBeLessThanOrEqual(baseNow.getTime());
150
+ expect(wakeAt!.getTime()).toBeLessThanOrEqual(Date.now());
151
+ });
152
+
153
+ it("allows bound fragment services to run inside durable hooks", async () => {
154
+ boundServiceHookSpy.mockReset();
155
+
156
+ await fragment.inContext(async function () {
157
+ await this.handlerTx()
158
+ .mutate(({ forSchema }) => {
159
+ const uow = forSchema(testSchema);
160
+ uow.create("items", {
161
+ name: "bound-service-item",
162
+ });
163
+ uow.triggerHook("onTestUsesBoundService", {});
164
+ })
165
+ .execute();
166
+ });
167
+
168
+ const processor = createDurableHooksProcessor(fragment);
169
+ await processor.drain();
170
+
171
+ expect(boundServiceHookSpy).toHaveBeenCalledOnce();
172
+ expect(boundServiceHookSpy).toHaveBeenCalledWith(1);
173
+ });
174
+
175
+ it("throws when fragment has no hooks configured", () => {
176
+ const noHooksFragment = instantiate(noHooksFragmentDefinition)
177
+ .withConfig({})
178
+ .withOptions({ databaseAdapter: adapter })
179
+ .build();
180
+
181
+ expect(() => createDurableHooksProcessor(noHooksFragment)).toThrow(
182
+ '[fragno-db] Durable hooks not configured for fragment "no-hooks".',
183
+ );
184
+ });
185
+
186
+ it("skips fragments without hooks when creating a group", () => {
187
+ const noHooksFragment = instantiate(noHooksFragmentDefinition)
188
+ .withConfig({})
189
+ .withOptions({ databaseAdapter: adapter })
190
+ .build();
191
+
192
+ const processor = createDurableHooksProcessorGroup([noHooksFragment, fragment]);
193
+ expect(processor).not.toBeNull();
194
+ expect(processor.namespace).toBe("test");
195
+ });
196
+ });
197
+
198
+ describe("createDurableHooksProcessorGroupFromProcessors", () => {
199
+ const makeProcessor = (overrides: Partial<ReturnType<typeof createProcessorStub>> = {}) => ({
200
+ ...createProcessorStub(),
201
+ ...overrides,
202
+ });
203
+
204
+ function createProcessorStub() {
205
+ const processDue = vi.fn().mockResolvedValue(0);
206
+ return {
207
+ namespace: "test",
208
+ processDue,
209
+ process: processDue,
210
+ getNextWakeAt: vi.fn().mockResolvedValue(null),
211
+ drain: vi.fn().mockResolvedValue(undefined),
212
+ };
213
+ }
214
+
215
+ it("returns null when there are no processors", () => {
216
+ expect(() => createDurableHooksProcessorGroupFromProcessors([])).toThrow(
217
+ "No processors provided for durable hooks processing.",
218
+ );
219
+ });
220
+
221
+ it("returns the same processor when only one is provided", () => {
222
+ const processor = makeProcessor({ namespace: "solo" });
223
+ const group = createDurableHooksProcessorGroupFromProcessors([processor]);
224
+ expect(group).toBe(processor);
225
+ });
226
+
227
+ it("aggregates processing results and reports errors", async () => {
228
+ const error = new Error("processor failed");
229
+ const onError = vi.fn();
230
+ const processorA = makeProcessor({
231
+ namespace: "a",
232
+ processDue: vi.fn().mockResolvedValue(2),
233
+ drain: vi.fn().mockResolvedValue(undefined),
234
+ });
235
+ const processorB = makeProcessor({
236
+ namespace: "b",
237
+ processDue: vi.fn().mockRejectedValue(error),
238
+ drain: vi.fn().mockRejectedValue(error),
239
+ });
240
+
241
+ const group = createDurableHooksProcessorGroupFromProcessors([processorA, processorB], {
242
+ onError,
243
+ });
244
+ expect(group).not.toBeNull();
245
+
246
+ const processed = await group.processDue();
247
+ expect(processed).toBe(2);
248
+ expect(onError).toHaveBeenCalledWith(error);
249
+
250
+ await group.drain();
251
+ expect(onError).toHaveBeenCalledTimes(2);
252
+ });
253
+
254
+ it("returns the earliest wake time and ignores failures", async () => {
255
+ const error = new Error("wake failed");
256
+ const onError = vi.fn();
257
+ const early = new Date("2024-01-01T00:00:00Z");
258
+ const late = new Date("2024-01-01T00:00:10Z");
259
+
260
+ const processorA = makeProcessor({
261
+ namespace: "a",
262
+ getNextWakeAt: vi.fn().mockResolvedValue(late),
263
+ });
264
+ const processorB = makeProcessor({
265
+ namespace: "b",
266
+ getNextWakeAt: vi.fn().mockResolvedValue(early),
267
+ });
268
+ const processorC = makeProcessor({
269
+ namespace: "c",
270
+ getNextWakeAt: vi.fn().mockRejectedValue(error),
271
+ });
272
+
273
+ const group = createDurableHooksProcessorGroupFromProcessors(
274
+ [processorA, processorB, processorC],
275
+ { onError },
276
+ );
277
+
278
+ const wakeAt = await group.getNextWakeAt();
279
+ expect(wakeAt).toEqual(early);
280
+ expect(onError).toHaveBeenCalledWith(error);
116
281
  });
117
282
  });
@@ -1,16 +1,25 @@
1
- import type { AnySchema } from "../schema/create";
2
1
  import type { AnyFragnoInstantiatedDatabaseFragment } from "../mod";
3
- import { createHookScheduler, type HookProcessorConfig } from "./hooks";
2
+ import type { AnySchema } from "../schema/create";
3
+ import { getDurableHooksRuntimeByToken } from "./durable-hooks-runtime";
4
+ import { createDurableHooksRunner } from "./hooks";
4
5
 
5
6
  export type DurableHooksProcessor = {
7
+ processDue: () => Promise<number>;
8
+ /**
9
+ * @deprecated Use processDue().
10
+ */
6
11
  process: () => Promise<number>;
7
12
  getNextWakeAt: () => Promise<Date | null>;
8
13
  drain: () => Promise<void>;
9
14
  namespace: string;
10
15
  };
11
16
 
17
+ export type DurableHooksProcessorGroupOptions = {
18
+ onError?: (error: unknown) => void;
19
+ };
20
+
12
21
  type DurableHooksInternal = {
13
- durableHooks?: HookProcessorConfig;
22
+ durableHooksToken?: object;
14
23
  };
15
24
 
16
25
  const DEFAULT_STUCK_PROCESSING_TIMEOUT_MINUTES = 10;
@@ -25,28 +34,47 @@ function resolveStuckProcessingTimeoutMinutes(value: number | false | undefined)
25
34
  return DEFAULT_STUCK_PROCESSING_TIMEOUT_MINUTES;
26
35
  }
27
36
 
37
+ function hasDurableHooksConfigured(
38
+ fragment: AnyFragnoInstantiatedDatabaseFragment,
39
+ ): fragment is AnyFragnoInstantiatedDatabaseFragment {
40
+ const internal = fragment.$internal as DurableHooksInternal | undefined;
41
+ return Boolean(internal?.durableHooksToken);
42
+ }
43
+
28
44
  export function createDurableHooksProcessor<TSchema extends AnySchema>(
29
45
  fragment: AnyFragnoInstantiatedDatabaseFragment<TSchema>,
30
- ): DurableHooksProcessor | null {
31
- const durableHooks = (fragment.$internal as DurableHooksInternal).durableHooks;
32
- if (!durableHooks) {
33
- return null;
46
+ ): DurableHooksProcessor {
47
+ const durableHooksToken = (fragment.$internal as DurableHooksInternal).durableHooksToken;
48
+ if (!durableHooksToken) {
49
+ throw new Error(`[fragno-db] Durable hooks not configured for fragment "${fragment.name}".`);
50
+ }
51
+ const runtime = getDurableHooksRuntimeByToken(durableHooksToken);
52
+ if (!runtime) {
53
+ throw new Error(`[fragno-db] Durable hooks runtime missing for fragment "${fragment.name}".`);
34
54
  }
55
+ runtime.dispatcherRegistered = true;
56
+
57
+ const durableHooks = runtime.config;
35
58
 
36
59
  const { namespace, internalFragment } = durableHooks;
37
60
  const stuckProcessingTimeoutMinutes = resolveStuckProcessingTimeoutMinutes(
38
61
  durableHooks.stuckProcessingTimeoutMinutes,
39
62
  );
40
- const scheduler =
41
- durableHooks.scheduler ?? (durableHooks.scheduler = createHookScheduler(durableHooks));
63
+ const runner =
64
+ durableHooks.runner ??
65
+ (durableHooks.runner = durableHooks.scheduler
66
+ ? {
67
+ processDue: () => durableHooks.scheduler!.schedule(),
68
+ drain: () => durableHooks.scheduler!.drain(),
69
+ }
70
+ : createDurableHooksRunner(durableHooks));
42
71
 
43
72
  return {
44
73
  namespace,
45
- process: async () => scheduler.schedule(),
46
- drain: async () => scheduler.drain(),
74
+ processDue: async () => runner.processDue(),
75
+ process: async () => runner.processDue(),
76
+ drain: async () => runner.drain(),
47
77
  getNextWakeAt: async () => {
48
- const services = internalFragment.services as { getDbNow?: () => Promise<Date> };
49
- const now = services.getDbNow ? await services.getDbNow() : new Date();
50
78
  return await internalFragment.inContext(async function () {
51
79
  return await this.handlerTx()
52
80
  .withServiceCalls(
@@ -55,7 +83,6 @@ export function createDurableHooksProcessor<TSchema extends AnySchema>(
55
83
  internalFragment.services.hookService.getNextHookWakeAt(
56
84
  namespace,
57
85
  stuckProcessingTimeoutMinutes,
58
- now,
59
86
  ),
60
87
  ] as const,
61
88
  )
@@ -65,3 +92,82 @@ export function createDurableHooksProcessor<TSchema extends AnySchema>(
65
92
  },
66
93
  };
67
94
  }
95
+
96
+ export function createDurableHooksProcessorGroup(
97
+ fragments: readonly AnyFragnoInstantiatedDatabaseFragment[],
98
+ options: DurableHooksProcessorGroupOptions = {},
99
+ ): DurableHooksProcessor {
100
+ const configuredFragments = fragments.filter(hasDurableHooksConfigured);
101
+ if (configuredFragments.length === 0) {
102
+ throw new Error("[fragno-db] No fragments provided for durable hooks processing.");
103
+ }
104
+ const processors = configuredFragments.map((fragment) => createDurableHooksProcessor(fragment));
105
+
106
+ return createDurableHooksProcessorGroupFromProcessors(processors, options);
107
+ }
108
+
109
+ export function createDurableHooksProcessorGroupFromProcessors(
110
+ processors: readonly DurableHooksProcessor[],
111
+ options: DurableHooksProcessorGroupOptions = {},
112
+ ): DurableHooksProcessor {
113
+ if (processors.length === 0) {
114
+ throw new Error("[fragno-db] No processors provided for durable hooks processing.");
115
+ }
116
+ if (processors.length === 1) {
117
+ return processors[0];
118
+ }
119
+
120
+ const onError = options.onError ?? (() => {});
121
+ const namespace = processors.map((processor) => processor.namespace).join(",");
122
+
123
+ const processDue = async () => {
124
+ const results = await Promise.allSettled(
125
+ processors.map(async (processor) => await processor.processDue()),
126
+ );
127
+ let processed = 0;
128
+ for (const result of results) {
129
+ if (result.status === "fulfilled") {
130
+ processed += result.value;
131
+ } else {
132
+ onError(result.reason);
133
+ }
134
+ }
135
+ return processed;
136
+ };
137
+
138
+ return {
139
+ namespace,
140
+ processDue,
141
+ process: processDue,
142
+ drain: async () => {
143
+ const results = await Promise.allSettled(
144
+ processors.map(async (processor) => await processor.drain()),
145
+ );
146
+ for (const result of results) {
147
+ if (result.status === "rejected") {
148
+ onError(result.reason);
149
+ }
150
+ }
151
+ },
152
+ getNextWakeAt: async () => {
153
+ const results = await Promise.allSettled(
154
+ processors.map(async (processor) => await processor.getNextWakeAt()),
155
+ );
156
+ let nextWakeAt: Date | null = null;
157
+ for (const result of results) {
158
+ if (result.status === "fulfilled") {
159
+ const wakeAt = result.value;
160
+ if (!wakeAt) {
161
+ continue;
162
+ }
163
+ if (!nextWakeAt || wakeAt.getTime() < nextWakeAt.getTime()) {
164
+ nextWakeAt = wakeAt;
165
+ }
166
+ } else {
167
+ onError(result.reason);
168
+ }
169
+ }
170
+ return nextWakeAt;
171
+ },
172
+ };
173
+ }