@fragno-dev/db 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (516) hide show
  1. package/.turbo/turbo-build.log +327 -160
  2. package/CHANGELOG.md +74 -0
  3. package/README.md +24 -0
  4. package/dist/adapters/adapters.d.ts +1 -1
  5. package/dist/adapters/adapters.d.ts.map +1 -1
  6. package/dist/adapters/adapters.js.map +1 -1
  7. package/dist/adapters/generic-sql/generic-sql-adapter.d.ts +0 -3
  8. package/dist/adapters/generic-sql/generic-sql-adapter.d.ts.map +1 -1
  9. package/dist/adapters/generic-sql/generic-sql-adapter.js +11 -12
  10. package/dist/adapters/generic-sql/generic-sql-adapter.js.map +1 -1
  11. package/dist/adapters/generic-sql/generic-sql-uow-executor.js +46 -6
  12. package/dist/adapters/generic-sql/generic-sql-uow-executor.js.map +1 -1
  13. package/dist/adapters/generic-sql/migration/cold-kysely.js.map +1 -1
  14. package/dist/adapters/generic-sql/migration/dialect/mysql.js +1 -1
  15. package/dist/adapters/generic-sql/migration/dialect/mysql.js.map +1 -1
  16. package/dist/adapters/generic-sql/migration/dialect/postgres.js +1 -1
  17. package/dist/adapters/generic-sql/migration/dialect/postgres.js.map +1 -1
  18. package/dist/adapters/generic-sql/migration/dialect/sqlite.js +185 -19
  19. package/dist/adapters/generic-sql/migration/dialect/sqlite.js.map +1 -1
  20. package/dist/adapters/generic-sql/migration/executor.d.ts.map +1 -1
  21. package/dist/adapters/generic-sql/migration/executor.js +30 -3
  22. package/dist/adapters/generic-sql/migration/executor.js.map +1 -1
  23. package/dist/adapters/generic-sql/migration/prepared-migrations.d.ts.map +1 -1
  24. package/dist/adapters/generic-sql/migration/prepared-migrations.js +3 -3
  25. package/dist/adapters/generic-sql/migration/prepared-migrations.js.map +1 -1
  26. package/dist/adapters/generic-sql/migration/sql-generator.js +1 -1
  27. package/dist/adapters/generic-sql/migration/sql-generator.js.map +1 -1
  28. package/dist/adapters/generic-sql/query/create-sql-query-compiler.js +1 -1
  29. package/dist/adapters/generic-sql/query/create-sql-query-compiler.js.map +1 -1
  30. package/dist/adapters/generic-sql/query/cursor-utils.js.map +1 -1
  31. package/dist/adapters/generic-sql/query/db-now-sql.js +27 -0
  32. package/dist/adapters/generic-sql/query/db-now-sql.js.map +1 -0
  33. package/dist/adapters/generic-sql/query/generic-sql-uow-operation-compiler.js +9 -6
  34. package/dist/adapters/generic-sql/query/generic-sql-uow-operation-compiler.js.map +1 -1
  35. package/dist/adapters/generic-sql/query/select-builder.js.map +1 -1
  36. package/dist/adapters/generic-sql/query/sql-query-compiler.js +37 -9
  37. package/dist/adapters/generic-sql/query/sql-query-compiler.js.map +1 -1
  38. package/dist/adapters/generic-sql/query/where-builder.js +24 -20
  39. package/dist/adapters/generic-sql/query/where-builder.js.map +1 -1
  40. package/dist/adapters/generic-sql/uow-decoder.js +1 -1
  41. package/dist/adapters/generic-sql/uow-decoder.js.map +1 -1
  42. package/dist/adapters/generic-sql/uow-encoder.js +8 -9
  43. package/dist/adapters/generic-sql/uow-encoder.js.map +1 -1
  44. package/dist/adapters/in-memory/condition-evaluator.js +10 -6
  45. package/dist/adapters/in-memory/condition-evaluator.js.map +1 -1
  46. package/dist/adapters/in-memory/in-memory-adapter.d.ts.map +1 -1
  47. package/dist/adapters/in-memory/in-memory-adapter.js +45 -25
  48. package/dist/adapters/in-memory/in-memory-adapter.js.map +1 -1
  49. package/dist/adapters/in-memory/in-memory-uow.js +236 -13
  50. package/dist/adapters/in-memory/in-memory-uow.js.map +1 -1
  51. package/dist/adapters/in-memory/options.d.ts +2 -0
  52. package/dist/adapters/in-memory/options.d.ts.map +1 -1
  53. package/dist/adapters/in-memory/options.js +3 -2
  54. package/dist/adapters/in-memory/options.js.map +1 -1
  55. package/dist/adapters/in-memory/reference-resolution.js.map +1 -1
  56. package/dist/adapters/in-memory/store.js +1 -1
  57. package/dist/adapters/in-memory/store.js.map +1 -1
  58. package/dist/adapters/shared/from-unit-of-work-compiler.js +51 -24
  59. package/dist/adapters/shared/from-unit-of-work-compiler.js.map +1 -1
  60. package/dist/adapters/shared/uow-operation-compiler.js.map +1 -1
  61. package/dist/browser/adapters/adapters.d.ts +61 -0
  62. package/dist/browser/adapters/adapters.d.ts.map +1 -0
  63. package/dist/browser/adapters/generic-sql/migration/executor.d.ts +15 -0
  64. package/dist/browser/adapters/generic-sql/migration/executor.d.ts.map +1 -0
  65. package/dist/browser/adapters/generic-sql/migration/prepared-migrations.d.ts +66 -0
  66. package/dist/browser/adapters/generic-sql/migration/prepared-migrations.d.ts.map +1 -0
  67. package/dist/browser/adapters/generic-sql/sqlite-storage.d.ts +11 -0
  68. package/dist/browser/adapters/generic-sql/sqlite-storage.d.ts.map +1 -0
  69. package/dist/browser/adapters/in-memory/in-memory-adapter.d.ts +5 -0
  70. package/dist/browser/adapters/in-memory/index.d.ts +2 -0
  71. package/dist/browser/adapters/in-memory/options.d.ts +1 -0
  72. package/dist/browser/db-fragment-definition-builder.d.ts +237 -0
  73. package/dist/browser/db-fragment-definition-builder.d.ts.map +1 -0
  74. package/dist/browser/durable-hooks.d.ts +3 -0
  75. package/dist/browser/fragments/internal-fragment.d.ts +317 -0
  76. package/dist/browser/fragments/internal-fragment.d.ts.map +1 -0
  77. package/dist/browser/fragments/internal-fragment.schema.d.ts +1 -0
  78. package/dist/browser/hooks/durable-hooks-logger.d.ts +10 -0
  79. package/dist/browser/hooks/durable-hooks-logger.d.ts.map +1 -0
  80. package/dist/browser/hooks/hooks.d.ts +146 -0
  81. package/dist/browser/hooks/hooks.d.ts.map +1 -0
  82. package/dist/browser/id.js +1 -0
  83. package/dist/browser/internal/adapter-registry.d.ts +4 -0
  84. package/dist/browser/internal/outbox-state.d.ts +2 -0
  85. package/dist/browser/mod.d.ts +15 -0
  86. package/dist/browser/mod.d.ts.map +1 -0
  87. package/dist/browser/mod.js +17 -0
  88. package/dist/browser/mod.js.map +1 -0
  89. package/dist/browser/mod2.d.ts +48 -0
  90. package/dist/browser/mod2.d.ts.map +1 -0
  91. package/dist/browser/naming/sql-naming.d.ts +19 -0
  92. package/dist/browser/naming/sql-naming.d.ts.map +1 -0
  93. package/dist/browser/outbox/outbox.d.ts +21 -0
  94. package/dist/browser/outbox/outbox.d.ts.map +1 -0
  95. package/dist/browser/query/column-defaults.js +1 -0
  96. package/dist/browser/query/condition-builder.d.ts +44 -0
  97. package/dist/browser/query/condition-builder.d.ts.map +1 -0
  98. package/dist/browser/query/condition-builder.js +97 -0
  99. package/dist/browser/query/condition-builder.js.map +1 -0
  100. package/dist/browser/query/cursor.d.ts +105 -0
  101. package/dist/browser/query/cursor.d.ts.map +1 -0
  102. package/dist/browser/query/cursor.js +150 -0
  103. package/dist/browser/query/cursor.js.map +1 -0
  104. package/dist/browser/query/db-now.d.ts +22 -0
  105. package/dist/browser/query/db-now.d.ts.map +1 -0
  106. package/dist/browser/query/db-now.js +33 -0
  107. package/dist/browser/query/db-now.js.map +1 -0
  108. package/dist/browser/query/orm/orm.d.ts +18 -0
  109. package/dist/browser/query/orm/orm.d.ts.map +1 -0
  110. package/dist/browser/query/simple-query-interface.d.ts +108 -0
  111. package/dist/browser/query/simple-query-interface.d.ts.map +1 -0
  112. package/dist/browser/query/unit-of-work/execute-unit-of-work.d.ts +423 -0
  113. package/dist/browser/query/unit-of-work/execute-unit-of-work.d.ts.map +1 -0
  114. package/dist/browser/query/unit-of-work/execute-unit-of-work.js +507 -0
  115. package/dist/browser/query/unit-of-work/execute-unit-of-work.js.map +1 -0
  116. package/dist/browser/query/unit-of-work/retry-policy.d.ts +23 -0
  117. package/dist/browser/query/unit-of-work/retry-policy.d.ts.map +1 -0
  118. package/dist/browser/query/unit-of-work/retry-policy.js +40 -0
  119. package/dist/browser/query/unit-of-work/retry-policy.js.map +1 -0
  120. package/dist/browser/query/unit-of-work/unit-of-work.d.ts +703 -0
  121. package/dist/browser/query/unit-of-work/unit-of-work.d.ts.map +1 -0
  122. package/dist/browser/query/unit-of-work/unit-of-work.js +1206 -0
  123. package/dist/browser/query/unit-of-work/unit-of-work.js.map +1 -0
  124. package/dist/browser/query/value-encoding.js +38 -0
  125. package/dist/browser/query/value-encoding.js.map +1 -0
  126. package/dist/browser/schema/create.d.ts +326 -0
  127. package/dist/browser/schema/create.d.ts.map +1 -0
  128. package/dist/browser/schema/create.js +89 -0
  129. package/dist/browser/schema/create.js.map +1 -0
  130. package/dist/browser/schema/generate-id.js +28 -0
  131. package/dist/browser/schema/generate-id.js.map +1 -0
  132. package/dist/browser/shared/providers.d.ts +6 -0
  133. package/dist/browser/shared/providers.d.ts.map +1 -0
  134. package/dist/browser/sql-driver/connection/connection-provider.d.ts +13 -0
  135. package/dist/browser/sql-driver/connection/connection-provider.d.ts.map +1 -0
  136. package/dist/browser/sql-driver/dialect-adapter/dialect-adapter.d.ts +7 -0
  137. package/dist/browser/sql-driver/dialect-adapter/dialect-adapter.d.ts.map +1 -0
  138. package/dist/browser/sql-driver/driver/runtime-driver.d.ts +23 -0
  139. package/dist/browser/sql-driver/driver/runtime-driver.d.ts.map +1 -0
  140. package/dist/browser/sql-driver/query-executor/plugin.d.ts +17 -0
  141. package/dist/browser/sql-driver/query-executor/plugin.d.ts.map +1 -0
  142. package/dist/browser/sql-driver/query-executor/query-executor.d.ts +36 -0
  143. package/dist/browser/sql-driver/query-executor/query-executor.d.ts.map +1 -0
  144. package/dist/browser/sql-driver/sql-driver-adapter.d.ts +29 -0
  145. package/dist/browser/sql-driver/sql-driver-adapter.d.ts.map +1 -0
  146. package/dist/browser/sql-driver/sql-driver.d.ts +38 -0
  147. package/dist/browser/sql-driver/sql-driver.d.ts.map +1 -0
  148. package/dist/browser/sync/commands.d.ts +15 -0
  149. package/dist/browser/sync/commands.d.ts.map +1 -0
  150. package/dist/browser/sync/commands.js +27 -0
  151. package/dist/browser/sync/commands.js.map +1 -0
  152. package/dist/browser/sync/types.d.ts +63 -0
  153. package/dist/browser/sync/types.d.ts.map +1 -0
  154. package/dist/browser/util/types.d.ts +8 -0
  155. package/dist/browser/util/types.d.ts.map +1 -0
  156. package/dist/browser/with-database.d.ts +29 -0
  157. package/dist/browser/with-database.d.ts.map +1 -0
  158. package/dist/client.d.ts +4 -0
  159. package/dist/client.js +5 -0
  160. package/dist/db-fragment-definition-builder.d.ts +85 -28
  161. package/dist/db-fragment-definition-builder.d.ts.map +1 -1
  162. package/dist/db-fragment-definition-builder.js +374 -46
  163. package/dist/db-fragment-definition-builder.js.map +1 -1
  164. package/dist/dispatchers/cloudflare-do/dispatcher.d.ts +20 -0
  165. package/dist/dispatchers/cloudflare-do/dispatcher.d.ts.map +1 -0
  166. package/dist/dispatchers/cloudflare-do/dispatcher.js +147 -0
  167. package/dist/dispatchers/cloudflare-do/dispatcher.js.map +1 -0
  168. package/dist/dispatchers/cloudflare-do/index.d.ts +5 -20
  169. package/dist/dispatchers/cloudflare-do/index.d.ts.map +1 -1
  170. package/dist/dispatchers/cloudflare-do/index.js +23 -55
  171. package/dist/dispatchers/cloudflare-do/index.js.map +1 -1
  172. package/dist/dispatchers/node/dispatcher.d.ts +14 -0
  173. package/dist/dispatchers/node/dispatcher.d.ts.map +1 -0
  174. package/dist/dispatchers/node/dispatcher.js +80 -0
  175. package/dist/dispatchers/node/dispatcher.js.map +1 -0
  176. package/dist/dispatchers/node/index.d.ts +5 -10
  177. package/dist/dispatchers/node/index.d.ts.map +1 -1
  178. package/dist/dispatchers/node/index.js +21 -53
  179. package/dist/dispatchers/node/index.js.map +1 -1
  180. package/dist/durable-hooks.d.ts +31 -0
  181. package/dist/durable-hooks.d.ts.map +1 -0
  182. package/dist/durable-hooks.js +23 -0
  183. package/dist/durable-hooks.js.map +1 -0
  184. package/dist/fragments/internal-fragment.d.ts +128 -27
  185. package/dist/fragments/internal-fragment.d.ts.map +1 -1
  186. package/dist/fragments/internal-fragment.js +125 -78
  187. package/dist/fragments/internal-fragment.js.map +1 -1
  188. package/dist/fragments/internal-fragment.routes.js +138 -3
  189. package/dist/fragments/internal-fragment.routes.js.map +1 -1
  190. package/dist/fragments/internal-fragment.schema.d.ts +7 -1
  191. package/dist/fragments/internal-fragment.schema.d.ts.map +1 -1
  192. package/dist/fragments/internal-fragment.schema.js +18 -1
  193. package/dist/fragments/internal-fragment.schema.js.map +1 -1
  194. package/dist/hooks/durable-hooks-logger.d.ts +10 -0
  195. package/dist/hooks/durable-hooks-logger.d.ts.map +1 -0
  196. package/dist/hooks/durable-hooks-logger.js +75 -0
  197. package/dist/hooks/durable-hooks-logger.js.map +1 -0
  198. package/dist/hooks/durable-hooks-processor.d.ts +1 -14
  199. package/dist/hooks/durable-hooks-processor.js +58 -10
  200. package/dist/hooks/durable-hooks-processor.js.map +1 -1
  201. package/dist/hooks/durable-hooks-runtime.js +44 -0
  202. package/dist/hooks/durable-hooks-runtime.js.map +1 -0
  203. package/dist/hooks/hooks.d.ts +60 -2
  204. package/dist/hooks/hooks.d.ts.map +1 -1
  205. package/dist/hooks/hooks.js +214 -53
  206. package/dist/hooks/hooks.js.map +1 -1
  207. package/dist/id.d.ts +2 -2
  208. package/dist/id.js +2 -2
  209. package/dist/internal/adapter-registry.d.ts +11 -0
  210. package/dist/internal/adapter-registry.d.ts.map +1 -0
  211. package/dist/internal/adapter-registry.js +135 -0
  212. package/dist/internal/adapter-registry.js.map +1 -0
  213. package/dist/internal/outbox-state.d.ts +2 -0
  214. package/dist/internal/outbox-state.js +26 -0
  215. package/dist/internal/outbox-state.js.map +1 -0
  216. package/dist/migration-engine/auto-from-schema.d.ts +33 -0
  217. package/dist/migration-engine/auto-from-schema.d.ts.map +1 -0
  218. package/dist/migration-engine/auto-from-schema.js +210 -27
  219. package/dist/migration-engine/auto-from-schema.js.map +1 -1
  220. package/dist/migration-engine/generation-engine.d.ts.map +1 -1
  221. package/dist/migration-engine/generation-engine.js +17 -5
  222. package/dist/migration-engine/generation-engine.js.map +1 -1
  223. package/dist/migration-engine/shared.d.ts +113 -0
  224. package/dist/migration-engine/shared.d.ts.map +1 -0
  225. package/dist/migration-engine/shared.js.map +1 -1
  226. package/dist/mod.d.ts +12 -11
  227. package/dist/mod.d.ts.map +1 -1
  228. package/dist/mod.js +10 -10
  229. package/dist/mod.js.map +1 -1
  230. package/dist/naming/sql-naming.d.ts.map +1 -1
  231. package/dist/naming/sql-naming.js.map +1 -1
  232. package/dist/outbox/outbox-builder.js.map +1 -1
  233. package/dist/outbox/outbox.d.ts +3 -1
  234. package/dist/outbox/outbox.d.ts.map +1 -1
  235. package/dist/outbox/outbox.js.map +1 -1
  236. package/dist/query/column-defaults.js.map +1 -1
  237. package/dist/query/condition-builder.d.ts +7 -1
  238. package/dist/query/condition-builder.d.ts.map +1 -1
  239. package/dist/query/condition-builder.js +5 -1
  240. package/dist/query/condition-builder.js.map +1 -1
  241. package/dist/query/cursor-client.d.ts +105 -0
  242. package/dist/query/cursor-client.d.ts.map +1 -0
  243. package/dist/query/cursor-client.js +165 -0
  244. package/dist/query/cursor-client.js.map +1 -0
  245. package/dist/query/cursor.d.ts.map +1 -1
  246. package/dist/query/cursor.js +7 -1
  247. package/dist/query/cursor.js.map +1 -1
  248. package/dist/query/db-now.d.ts +15 -1
  249. package/dist/query/db-now.d.ts.map +1 -1
  250. package/dist/query/db-now.js +30 -2
  251. package/dist/query/db-now.js.map +1 -1
  252. package/dist/query/orm/orm.js.map +1 -1
  253. package/dist/query/serialize/create-sql-serializer.js +2 -2
  254. package/dist/query/serialize/create-sql-serializer.js.map +1 -1
  255. package/dist/query/serialize/dialect/mysql-serializer.js.map +1 -1
  256. package/dist/query/serialize/dialect/postgres-serializer.js.map +1 -1
  257. package/dist/query/serialize/dialect/sqlite-serializer.js +6 -2
  258. package/dist/query/serialize/dialect/sqlite-serializer.js.map +1 -1
  259. package/dist/query/simple-query-interface.d.ts +7 -3
  260. package/dist/query/simple-query-interface.d.ts.map +1 -1
  261. package/dist/query/unit-of-work/execute-unit-of-work.d.ts +37 -2
  262. package/dist/query/unit-of-work/execute-unit-of-work.d.ts.map +1 -1
  263. package/dist/query/unit-of-work/execute-unit-of-work.js +39 -18
  264. package/dist/query/unit-of-work/execute-unit-of-work.js.map +1 -1
  265. package/dist/query/unit-of-work/unit-of-work.d.ts +42 -16
  266. package/dist/query/unit-of-work/unit-of-work.d.ts.map +1 -1
  267. package/dist/query/unit-of-work/unit-of-work.js +50 -6
  268. package/dist/query/unit-of-work/unit-of-work.js.map +1 -1
  269. package/dist/query/value-decoding.js +8 -1
  270. package/dist/query/value-decoding.js.map +1 -1
  271. package/dist/query/value-encoding.js.map +1 -1
  272. package/dist/schema/create.d.ts +69 -25
  273. package/dist/schema/create.d.ts.map +1 -1
  274. package/dist/schema/create.js +91 -16
  275. package/dist/schema/create.js.map +1 -1
  276. package/dist/schema/type-conversion/create-sql-type-mapper.js +1 -1
  277. package/dist/schema/type-conversion/create-sql-type-mapper.js.map +1 -1
  278. package/dist/schema/type-conversion/dialect/sqlite.js.map +1 -1
  279. package/dist/schema/validator.d.ts.map +1 -1
  280. package/dist/schema/validator.js.map +1 -1
  281. package/dist/schema-output/drizzle.d.ts.map +1 -1
  282. package/dist/schema-output/drizzle.js +8 -6
  283. package/dist/schema-output/drizzle.js.map +1 -1
  284. package/dist/schema-output/prisma.d.ts.map +1 -1
  285. package/dist/schema-output/prisma.js +21 -10
  286. package/dist/schema-output/prisma.js.map +1 -1
  287. package/dist/sql-driver/dialects/durable-object-dialect.js +3 -9
  288. package/dist/sql-driver/dialects/durable-object-dialect.js.map +1 -1
  289. package/dist/sql-driver/query-executor/default-query-executor.js.map +1 -1
  290. package/dist/sql-driver/query-executor/query-executor-base.js.map +1 -1
  291. package/dist/sql-driver/sql-driver-adapter.js.map +1 -1
  292. package/dist/sql-driver/sql.js.map +1 -1
  293. package/dist/sync/commands.d.ts +15 -0
  294. package/dist/sync/commands.d.ts.map +1 -0
  295. package/dist/sync/commands.js +27 -0
  296. package/dist/sync/commands.js.map +1 -0
  297. package/dist/sync/index.d.ts +4 -0
  298. package/dist/sync/index.js +4 -0
  299. package/dist/sync/read-tracking.d.ts +25 -0
  300. package/dist/sync/read-tracking.d.ts.map +1 -0
  301. package/dist/sync/read-tracking.js +148 -0
  302. package/dist/sync/read-tracking.js.map +1 -0
  303. package/dist/sync/submit.js +213 -0
  304. package/dist/sync/submit.js.map +1 -0
  305. package/dist/sync/types.d.ts +63 -0
  306. package/dist/sync/types.d.ts.map +1 -0
  307. package/dist/util/default-database-adapter.js +6 -1
  308. package/dist/util/default-database-adapter.js.map +1 -1
  309. package/dist/with-database.d.ts +3 -6
  310. package/dist/with-database.d.ts.map +1 -1
  311. package/dist/with-database.js +7 -15
  312. package/dist/with-database.js.map +1 -1
  313. package/package.json +33 -41
  314. package/src/adapters/adapters.ts +5 -4
  315. package/src/adapters/drizzle/migrate-drizzle.test.ts +46 -9
  316. package/src/adapters/drizzle/migration-parity-drizzle-kit.test.ts +5 -3
  317. package/src/adapters/drizzle/test-utils.ts +2 -1
  318. package/src/adapters/generic-sql/generic-sql-adapter.test.ts +5 -3
  319. package/src/adapters/generic-sql/generic-sql-adapter.ts +21 -24
  320. package/src/adapters/generic-sql/generic-sql-uow-executor.test.ts +1 -0
  321. package/src/adapters/generic-sql/generic-sql-uow-executor.ts +81 -15
  322. package/src/adapters/generic-sql/migration/adapter-migration-parity.test.ts +4 -2
  323. package/src/adapters/generic-sql/migration/cold-kysely.ts +1 -0
  324. package/src/adapters/generic-sql/migration/dialect/mysql.test.ts +3 -2
  325. package/src/adapters/generic-sql/migration/dialect/mysql.ts +1 -0
  326. package/src/adapters/generic-sql/migration/dialect/postgres.test.ts +5 -4
  327. package/src/adapters/generic-sql/migration/dialect/postgres.ts +2 -1
  328. package/src/adapters/generic-sql/migration/dialect/sqlite.test.ts +795 -3
  329. package/src/adapters/generic-sql/migration/dialect/sqlite.ts +385 -57
  330. package/src/adapters/generic-sql/migration/executor.test.ts +52 -0
  331. package/src/adapters/generic-sql/migration/executor.ts +47 -4
  332. package/src/adapters/generic-sql/migration/prepared-migrations.test.ts +117 -14
  333. package/src/adapters/generic-sql/migration/prepared-migrations.ts +9 -8
  334. package/src/adapters/generic-sql/migration/sql-generator.ts +5 -3
  335. package/src/adapters/generic-sql/query/create-sql-query-compiler.ts +3 -3
  336. package/src/adapters/generic-sql/query/cursor-utils.test.ts +3 -2
  337. package/src/adapters/generic-sql/query/cursor-utils.ts +1 -1
  338. package/src/adapters/generic-sql/query/db-now-sql.ts +49 -0
  339. package/src/adapters/generic-sql/query/generic-sql-uow-operation-compiler.test.ts +144 -8
  340. package/src/adapters/generic-sql/query/generic-sql-uow-operation-compiler.ts +16 -17
  341. package/src/adapters/generic-sql/query/select-builder.test.ts +1 -0
  342. package/src/adapters/generic-sql/query/select-builder.ts +2 -2
  343. package/src/adapters/generic-sql/query/sql-query-compiler.test.ts +24 -5
  344. package/src/adapters/generic-sql/query/sql-query-compiler.ts +83 -13
  345. package/src/adapters/generic-sql/query/where-builder.test.ts +7 -5
  346. package/src/adapters/generic-sql/query/where-builder.ts +48 -29
  347. package/src/adapters/generic-sql/sql-adapter-pglite-migrations.test.ts +6 -15
  348. package/src/adapters/generic-sql/sql-adapter-pglite-pagination.test.ts +52 -7
  349. package/src/adapters/generic-sql/sql-adapter-pglite-queries.test.ts +9 -6
  350. package/src/adapters/generic-sql/sql-adapter-sqlite3-driver.test.ts +273 -5
  351. package/src/adapters/generic-sql/sql-adapter-sqlite3-uow.test.ts +123 -6
  352. package/src/adapters/generic-sql/sql-adapter-sqlocal.test.ts +4 -2
  353. package/src/adapters/generic-sql/uow-decoder.test.ts +4 -3
  354. package/src/adapters/generic-sql/uow-decoder.ts +3 -3
  355. package/src/adapters/generic-sql/uow-encoder.test.ts +4 -2
  356. package/src/adapters/generic-sql/uow-encoder.ts +14 -18
  357. package/src/adapters/in-memory/condition-evaluator.test.ts +2 -1
  358. package/src/adapters/in-memory/condition-evaluator.ts +9 -4
  359. package/src/adapters/in-memory/in-memory-adapter.ts +155 -44
  360. package/src/adapters/in-memory/in-memory-uow.mutations.test.ts +50 -2
  361. package/src/adapters/in-memory/in-memory-uow.retrieval.test.ts +158 -3
  362. package/src/adapters/in-memory/in-memory-uow.ts +402 -26
  363. package/src/adapters/in-memory/options.test.ts +1 -0
  364. package/src/adapters/in-memory/options.ts +5 -1
  365. package/src/adapters/in-memory/outbox.test.ts +361 -0
  366. package/src/adapters/in-memory/reference-resolution.test.ts +3 -2
  367. package/src/adapters/in-memory/reference-resolution.ts +2 -2
  368. package/src/adapters/in-memory/sorted-array-index.test.ts +1 -0
  369. package/src/adapters/in-memory/store.test.ts +1 -0
  370. package/src/adapters/in-memory/store.ts +3 -3
  371. package/src/adapters/in-memory/value-normalization.test.ts +1 -0
  372. package/src/adapters/prisma/prisma-adapter-sqlite3.test.ts +51 -7
  373. package/src/adapters/shared/from-unit-of-work-compiler.ts +156 -46
  374. package/src/adapters/shared/uow-operation-compiler.ts +3 -3
  375. package/src/browser/mod.ts +64 -0
  376. package/src/client.ts +19 -0
  377. package/src/db-fragment-definition-builder.test.ts +821 -47
  378. package/src/db-fragment-definition-builder.ts +857 -110
  379. package/src/db-fragment-instantiator.test.ts +114 -90
  380. package/src/db-fragment-integration.test.ts +9 -6
  381. package/src/dispatchers/cloudflare-do/dispatcher.ts +204 -0
  382. package/src/dispatchers/cloudflare-do/index.test.ts +145 -12
  383. package/src/dispatchers/cloudflare-do/index.ts +49 -90
  384. package/src/dispatchers/node/dispatcher.ts +112 -0
  385. package/src/dispatchers/node/index.test.ts +43 -14
  386. package/src/dispatchers/node/index.ts +38 -75
  387. package/src/durable-hooks.test.ts +80 -0
  388. package/src/durable-hooks.ts +67 -0
  389. package/src/fragments/internal-fragment.routes.test.ts +570 -0
  390. package/src/fragments/internal-fragment.routes.ts +297 -5
  391. package/src/fragments/internal-fragment.schema.ts +45 -1
  392. package/src/fragments/internal-fragment.test.ts +223 -251
  393. package/src/fragments/internal-fragment.ts +278 -154
  394. package/src/hooks/durable-hooks-logger.ts +126 -0
  395. package/src/hooks/durable-hooks-processor.pglite.test.ts +87 -0
  396. package/src/hooks/durable-hooks-processor.test.ts +179 -14
  397. package/src/hooks/durable-hooks-processor.ts +120 -14
  398. package/src/hooks/durable-hooks-runtime.test.ts +65 -0
  399. package/src/hooks/durable-hooks-runtime.ts +81 -0
  400. package/src/hooks/hooks.test.ts +314 -53
  401. package/src/hooks/hooks.ts +360 -81
  402. package/src/id.test.ts +34 -0
  403. package/src/id.ts +1 -3
  404. package/src/internal/adapter-registry.test.ts +93 -0
  405. package/src/internal/adapter-registry.ts +239 -0
  406. package/src/internal/outbox-state.ts +43 -0
  407. package/src/migration-engine/auto-from-schema.test.ts +93 -0
  408. package/src/migration-engine/auto-from-schema.ts +360 -42
  409. package/src/migration-engine/create.test.ts +2 -1
  410. package/src/migration-engine/create.ts +1 -1
  411. package/src/migration-engine/generation-engine.test.ts +66 -9
  412. package/src/migration-engine/generation-engine.ts +31 -10
  413. package/src/migration-engine/shared.ts +13 -0
  414. package/src/mod.ts +45 -27
  415. package/src/naming/sql-naming.ts +1 -0
  416. package/src/outbox/outbox-builder.ts +2 -2
  417. package/src/outbox/outbox.test.ts +216 -45
  418. package/src/outbox/outbox.ts +3 -1
  419. package/src/query/column-defaults.ts +1 -1
  420. package/src/query/condition-builder.test.ts +15 -0
  421. package/src/query/condition-builder.ts +7 -0
  422. package/src/query/cursor-client.test.ts +70 -0
  423. package/src/query/cursor-client.ts +263 -0
  424. package/src/query/cursor.test.ts +3 -2
  425. package/src/query/cursor.ts +15 -3
  426. package/src/query/db-now.ts +69 -2
  427. package/src/query/orm/orm.ts +2 -2
  428. package/src/query/query-type.test.ts +2 -1
  429. package/src/query/serialize/create-sql-serializer.ts +3 -3
  430. package/src/query/serialize/dialect/mysql-serializer.ts +1 -1
  431. package/src/query/serialize/dialect/postgres-serializer.ts +1 -1
  432. package/src/query/serialize/dialect/sqlite-serializer.test.ts +39 -2
  433. package/src/query/serialize/dialect/sqlite-serializer.ts +18 -5
  434. package/src/query/simple-query-interface.ts +10 -4
  435. package/src/query/unit-of-work/execute-unit-of-work.test.ts +347 -9
  436. package/src/query/unit-of-work/execute-unit-of-work.ts +63 -20
  437. package/src/query/unit-of-work/retry-policy.test.ts +1 -0
  438. package/src/query/unit-of-work/tx-builder.test.ts +73 -1
  439. package/src/query/unit-of-work/unit-of-work-coordinator.test.ts +5 -4
  440. package/src/query/unit-of-work/unit-of-work-types.test.ts +41 -11
  441. package/src/query/unit-of-work/unit-of-work.test.ts +28 -2
  442. package/src/query/unit-of-work/unit-of-work.ts +105 -19
  443. package/src/query/value-decoding.test.ts +50 -2
  444. package/src/query/value-decoding.ts +17 -4
  445. package/src/query/value-encoding.test.ts +1 -0
  446. package/src/query/value-encoding.ts +1 -1
  447. package/src/schema/create.test.ts +164 -5
  448. package/src/schema/create.ts +222 -24
  449. package/src/schema/generate-id.test.ts +1 -0
  450. package/src/schema/serialize.test.ts +4 -3
  451. package/src/schema/type-conversion/create-sql-type-mapper.ts +1 -1
  452. package/src/schema/type-conversion/dialect/sqlite.ts +2 -2
  453. package/src/schema/type-conversion/type-mapping.test.ts +2 -1
  454. package/src/schema/validator.test.ts +4 -2
  455. package/src/schema/validator.ts +1 -0
  456. package/src/schema-output/drizzle.test.ts +72 -19
  457. package/src/schema-output/drizzle.ts +24 -18
  458. package/src/schema-output/prisma.test.ts +172 -14
  459. package/src/schema-output/prisma.ts +34 -14
  460. package/src/sql-driver/better-sqlite3.test.ts +5 -3
  461. package/src/sql-driver/dialects/durable-object-dialect.ts +3 -8
  462. package/src/sql-driver/query-executor/default-query-executor.ts +1 -1
  463. package/src/sql-driver/query-executor/query-executor-base.ts +1 -1
  464. package/src/sql-driver/query-executor/query-executor.ts +1 -1
  465. package/src/sql-driver/sql-driver-adapter.ts +2 -2
  466. package/src/sql-driver/sql.ts +2 -1
  467. package/src/sql-driver/sqlocal.test.ts +4 -2
  468. package/src/sync/commands.test.ts +39 -0
  469. package/src/sync/commands.ts +51 -0
  470. package/src/sync/conflict-checker.test.ts +450 -0
  471. package/src/sync/conflict-checker.ts +248 -0
  472. package/src/sync/index.ts +14 -0
  473. package/src/sync/plan.ts +9 -0
  474. package/src/sync/read-tracking.test.ts +177 -0
  475. package/src/sync/read-tracking.ts +287 -0
  476. package/src/sync/submit.test.ts +205 -0
  477. package/src/sync/submit.ts +328 -0
  478. package/src/sync/types.ts +80 -0
  479. package/src/util/default-database-adapter.ts +15 -2
  480. package/src/with-database.ts +20 -50
  481. package/tsconfig.json +1 -1
  482. package/tsdown.config.ts +38 -26
  483. package/vitest.config.ts +1 -0
  484. package/dist/hooks/durable-hooks-processor.d.ts.map +0 -1
  485. package/dist/node_modules/.pnpm/rou3@0.7.12/node_modules/rou3/dist/index.js +0 -168
  486. package/dist/node_modules/.pnpm/rou3@0.7.12/node_modules/rou3/dist/index.js.map +0 -1
  487. package/dist/packages/fragno/dist/api/bind-services.js +0 -20
  488. package/dist/packages/fragno/dist/api/bind-services.js.map +0 -1
  489. package/dist/packages/fragno/dist/api/error.js +0 -48
  490. package/dist/packages/fragno/dist/api/error.js.map +0 -1
  491. package/dist/packages/fragno/dist/api/fragment-definition-builder.js +0 -321
  492. package/dist/packages/fragno/dist/api/fragment-definition-builder.js.map +0 -1
  493. package/dist/packages/fragno/dist/api/fragment-instantiator.js +0 -669
  494. package/dist/packages/fragno/dist/api/fragment-instantiator.js.map +0 -1
  495. package/dist/packages/fragno/dist/api/fragno-response.js +0 -73
  496. package/dist/packages/fragno/dist/api/fragno-response.js.map +0 -1
  497. package/dist/packages/fragno/dist/api/internal/response-stream.js +0 -81
  498. package/dist/packages/fragno/dist/api/internal/response-stream.js.map +0 -1
  499. package/dist/packages/fragno/dist/api/internal/route.js +0 -10
  500. package/dist/packages/fragno/dist/api/internal/route.js.map +0 -1
  501. package/dist/packages/fragno/dist/api/mutable-request-state.js +0 -97
  502. package/dist/packages/fragno/dist/api/mutable-request-state.js.map +0 -1
  503. package/dist/packages/fragno/dist/api/request-context-storage.js +0 -43
  504. package/dist/packages/fragno/dist/api/request-context-storage.js.map +0 -1
  505. package/dist/packages/fragno/dist/api/request-input-context.js +0 -185
  506. package/dist/packages/fragno/dist/api/request-input-context.js.map +0 -1
  507. package/dist/packages/fragno/dist/api/request-middleware.js +0 -83
  508. package/dist/packages/fragno/dist/api/request-middleware.js.map +0 -1
  509. package/dist/packages/fragno/dist/api/request-output-context.js +0 -119
  510. package/dist/packages/fragno/dist/api/request-output-context.js.map +0 -1
  511. package/dist/packages/fragno/dist/api/route.js +0 -30
  512. package/dist/packages/fragno/dist/api/route.js.map +0 -1
  513. package/dist/packages/fragno/dist/internal/symbols.js +0 -10
  514. package/dist/packages/fragno/dist/internal/symbols.js.map +0 -1
  515. package/dist/packages/fragno/dist/internal/trace-context.js +0 -12
  516. package/dist/packages/fragno/dist/internal/trace-context.js.map +0 -1
@@ -1,19 +1,34 @@
1
+ import { describe, it, expect, vi, expectTypeOf } from "vitest";
2
+
1
3
  import fs from "node:fs";
2
4
  import os from "node:os";
3
5
  import path from "node:path";
4
- import { describe, it, expect, vi, expectTypeOf } from "vitest";
5
- import { defineFragment } from "@fragno-dev/core";
6
+
7
+ import { RequestContextStorage } from "@fragno-dev/core/internal/request-context-storage";
8
+ import SQLite from "better-sqlite3";
9
+ import { SqliteDialect } from "kysely";
10
+
11
+ import { defineFragment, instantiate } from "@fragno-dev/core";
12
+
13
+ import type { DatabaseAdapter, DatabaseContextStorage } from "./adapters/adapters";
14
+ import { BetterSQLite3DriverConfig } from "./adapters/generic-sql/driver-config";
15
+ import { SqlAdapter } from "./adapters/generic-sql/generic-sql-adapter";
6
16
  import {
7
17
  DatabaseFragmentDefinitionBuilder,
8
18
  type ImplicitDatabaseDependencies,
9
19
  } from "./db-fragment-definition-builder";
10
- import { withDatabase } from "./with-database";
11
- import { schema, column, idColumn } from "./schema/create";
20
+ import { internalSchema } from "./fragments/internal-fragment";
21
+ import { getDurableHooksRuntimeByToken } from "./hooks/durable-hooks-runtime";
22
+ import type { HookFn } from "./hooks/hooks";
23
+ import * as hooks from "./hooks/hooks";
24
+ import { getInternalFragment, getRegistryForAdapterSync } from "./internal/adapter-registry";
25
+ import { suffixNamingStrategy, sanitizeNamespace } from "./naming/sql-naming";
12
26
  import type { SimpleQueryInterface } from "./query/simple-query-interface";
13
- import type { DatabaseAdapter } from "./adapters/adapters";
14
27
  import * as executeUnitOfWork from "./query/unit-of-work/execute-unit-of-work";
15
- import { RequestContextStorage } from "@fragno-dev/core/internal/request-context-storage";
16
- import { suffixNamingStrategy } from "./naming/sql-naming";
28
+ import type { IUnitOfWork } from "./query/unit-of-work/unit-of-work";
29
+ import { schema, column, idColumn } from "./schema/create";
30
+ import { defineSyncCommands } from "./sync/commands";
31
+ import { withDatabase } from "./with-database";
17
32
 
18
33
  // Create a test schema
19
34
  const testSchema = schema("test", (s) => {
@@ -29,14 +44,17 @@ type TestSchema = typeof testSchema;
29
44
 
30
45
  // Mock database adapter
31
46
  function createMockAdapter(): DatabaseAdapter {
47
+ const createMockUow = () => ({
48
+ forSchema: vi.fn(),
49
+ executeRetrieve: vi.fn(),
50
+ executeMutations: vi.fn(),
51
+ registerSchema: vi.fn(),
52
+ reset: vi.fn(),
53
+ });
54
+
32
55
  const mockdb = {
33
- createUnitOfWork: vi.fn(() => ({
34
- forSchema: vi.fn(),
35
- executeRetrieve: vi.fn(),
36
- executeMutations: vi.fn(),
37
- registerSchema: vi.fn(),
38
- reset: vi.fn(),
39
- })),
56
+ createUnitOfWork: vi.fn(() => createMockUow()),
57
+ createBaseUnitOfWork: vi.fn(() => createMockUow()),
40
58
  } as unknown as SimpleQueryInterface<TestSchema>;
41
59
 
42
60
  return {
@@ -87,8 +105,8 @@ describe("DatabaseFragmentDefinitionBuilder", () => {
87
105
 
88
106
  const definition = defineFragment("test-frag")
89
107
  .extend(withDatabase(testSchema))
90
- .withDependencies(({ db }) => {
91
- expect(db).toBeDefined();
108
+ .withDependencies(({ databaseAdapter }) => {
109
+ expect(databaseAdapter).toBeDefined();
92
110
  return {
93
111
  customDep: "value",
94
112
  };
@@ -101,7 +119,8 @@ describe("DatabaseFragmentDefinitionBuilder", () => {
101
119
  });
102
120
 
103
121
  expect(deps.customDep).toBe("value");
104
- expect(deps.db).toBeDefined();
122
+ expect(deps.databaseAdapter).toBeDefined();
123
+ expect(deps.createUnitOfWork).toBeDefined();
105
124
  expect(deps.schema).toBeDefined();
106
125
  });
107
126
 
@@ -121,7 +140,8 @@ describe("DatabaseFragmentDefinitionBuilder", () => {
121
140
  });
122
141
 
123
142
  expect(deps.apiKey).toBe("key123");
124
- expect(deps.db).toBeDefined();
143
+ expect(deps.databaseAdapter).toBeDefined();
144
+ expect(deps.createUnitOfWork).toBeDefined();
125
145
  expect(deps.schema).toBeDefined();
126
146
  });
127
147
 
@@ -131,7 +151,7 @@ describe("DatabaseFragmentDefinitionBuilder", () => {
131
151
  const definition = defineFragment("test-frag")
132
152
  .extend(withDatabase(testSchema))
133
153
  .providesService("users", ({ deps }) => {
134
- expect(deps.db).toBeDefined();
154
+ expect(deps.databaseAdapter).toBeDefined();
135
155
  return {
136
156
  list: () => "users list",
137
157
  };
@@ -164,7 +184,7 @@ describe("DatabaseFragmentDefinitionBuilder", () => {
164
184
  }))
165
185
  .extend(withDatabase(testSchema))
166
186
  .providesService("users", ({ deps }) => {
167
- expect(deps.db).toBeDefined();
187
+ expect(deps.databaseAdapter).toBeDefined();
168
188
  return {
169
189
  list: () => "users list",
170
190
  };
@@ -238,7 +258,7 @@ describe("DatabaseFragmentDefinitionBuilder", () => {
238
258
  const definition = defineFragment("test-frag")
239
259
  .extend(withDatabase(testSchema))
240
260
  .providesBaseService(({ deps }) => {
241
- expect(deps.db).toBeDefined();
261
+ expect(deps.databaseAdapter).toBeDefined();
242
262
  return {
243
263
  healthCheck: () => "healthy",
244
264
  };
@@ -271,7 +291,7 @@ describe("DatabaseFragmentDefinitionBuilder", () => {
271
291
  }))
272
292
  .extend(withDatabase(testSchema))
273
293
  .providesBaseService(({ deps }) => {
274
- expect(deps.db).toBeDefined();
294
+ expect(deps.databaseAdapter).toBeDefined();
275
295
  return {
276
296
  status: () => "ok",
277
297
  };
@@ -306,8 +326,8 @@ describe("DatabaseFragmentDefinitionBuilder", () => {
306
326
 
307
327
  const definition = defineFragment("test-frag")
308
328
  .extend(withDatabase(testSchema))
309
- .withDependencies(({ db }) => {
310
- expect(db).toBeDefined();
329
+ .withDependencies(({ databaseAdapter }) => {
330
+ expect(databaseAdapter).toBeDefined();
311
331
  return {
312
332
  apiKey: "secret",
313
333
  };
@@ -322,7 +342,7 @@ describe("DatabaseFragmentDefinitionBuilder", () => {
322
342
  },
323
343
  }))
324
344
  .providesService("data", ({ deps }) => {
325
- expect(deps.db).toBeDefined();
345
+ expect(deps.databaseAdapter).toBeDefined();
326
346
  return {
327
347
  fetch: () => `Fetching with ${deps.apiKey}`,
328
348
  };
@@ -375,15 +395,15 @@ describe("DatabaseFragmentDefinitionBuilder", () => {
375
395
  baseConfig: "config1",
376
396
  }))
377
397
  .extend(withDatabase(testSchema))
378
- .withDependencies(({ db }) => {
379
- expect(db).toBeDefined();
398
+ .withDependencies(({ databaseAdapter }) => {
399
+ expect(databaseAdapter).toBeDefined();
380
400
  // Second withDependencies replaces the first one
381
401
  return {
382
402
  enhancedConfig: "enhanced",
383
403
  };
384
404
  })
385
405
  .providesService("combined", ({ deps }) => {
386
- expect(deps.db).toBeDefined();
406
+ expect(deps.databaseAdapter).toBeDefined();
387
407
  return {
388
408
  getConfig: () => deps.enhancedConfig,
389
409
  };
@@ -398,7 +418,7 @@ describe("DatabaseFragmentDefinitionBuilder", () => {
398
418
  // Only the second withDependencies is used (plus implicit deps)
399
419
  expect(deps).not.toHaveProperty("baseConfig");
400
420
  expect(deps.enhancedConfig).toBe("enhanced");
401
- expect(deps.db).toBeDefined();
421
+ expect(deps.databaseAdapter).toBeDefined();
402
422
 
403
423
  const combinedService = definition.namedServices!.combined({
404
424
  config: {},
@@ -428,9 +448,9 @@ describe("DatabaseFragmentDefinitionBuilder", () => {
428
448
  };
429
449
  })
430
450
  .extend(withDatabase(testSchema))
431
- .withDependencies(({ config, db }) => {
451
+ .withDependencies(({ config, databaseAdapter }) => {
432
452
  expectTypeOf(config).toExtend<MyConfig>();
433
- expect(db).toBeDefined();
453
+ expect(databaseAdapter).toBeDefined();
434
454
  // Second withDependencies replaces the first, so combine them here
435
455
  return {
436
456
  connectionTimeout: config.timeout,
@@ -446,7 +466,7 @@ describe("DatabaseFragmentDefinitionBuilder", () => {
446
466
 
447
467
  expect(deps.connectionTimeout).toBe(5000);
448
468
  expect(deps.dbConnectionString).toBe("postgres://localhost");
449
- expect(deps.db).toBeDefined();
469
+ expect(deps.databaseAdapter).toBeDefined();
450
470
  });
451
471
 
452
472
  it("recommended pattern: extend first then configure", () => {
@@ -463,8 +483,8 @@ describe("DatabaseFragmentDefinitionBuilder", () => {
463
483
  // Best practice: .extend() early, then add all your features
464
484
  const definition = defineFragment("best-practice")
465
485
  .extend(withDatabase(testSchema))
466
- .withDependencies(({ db }) => {
467
- expect(db).toBeDefined();
486
+ .withDependencies(({ databaseAdapter }) => {
487
+ expect(databaseAdapter).toBeDefined();
468
488
  return {
469
489
  apiKey: "secret",
470
490
  timeout: 5000,
@@ -474,7 +494,7 @@ describe("DatabaseFragmentDefinitionBuilder", () => {
474
494
  .usesOptionalService<"auth", AuthService>("auth")
475
495
  .providesBaseService(({ deps }) => ({
476
496
  healthCheck: () => {
477
- expect(deps.db).toBeDefined();
497
+ expect(deps.databaseAdapter).toBeDefined();
478
498
  return `OK (timeout: ${deps.timeout})`;
479
499
  },
480
500
  }))
@@ -497,7 +517,7 @@ describe("DatabaseFragmentDefinitionBuilder", () => {
497
517
  // Verify all dependencies including implicit ones
498
518
  expect(deps.apiKey).toBe("secret");
499
519
  expect(deps.timeout).toBe(5000);
500
- expect(deps.db).toBeDefined();
520
+ expect(deps.databaseAdapter).toBeDefined();
501
521
  expect(deps.schema).toBeDefined();
502
522
  expect(deps.createUnitOfWork).toBeDefined();
503
523
 
@@ -537,8 +557,7 @@ describe("DatabaseFragmentDefinitionBuilder", () => {
537
557
  const mockAdapter = createMockAdapter();
538
558
 
539
559
  const definition = withDatabase(testSchema)(defineFragment("db-frag"))
540
- .withDependencies(({ db, databaseAdapter }) => {
541
- expect(db).toBeDefined();
560
+ .withDependencies(({ databaseAdapter }) => {
542
561
  expect(databaseAdapter).toBeDefined();
543
562
  return {
544
563
  customDep: "test",
@@ -555,7 +574,7 @@ describe("DatabaseFragmentDefinitionBuilder", () => {
555
574
  });
556
575
 
557
576
  // Should have implicit deps
558
- expect(deps).toHaveProperty("db");
577
+ expect(deps).toHaveProperty("databaseAdapter");
559
578
  expect(deps).toHaveProperty("schema");
560
579
  expect(deps).toHaveProperty("createUnitOfWork");
561
580
  expect(deps).toHaveProperty("customDep");
@@ -576,7 +595,7 @@ describe("DatabaseFragmentDefinitionBuilder", () => {
576
595
  });
577
596
 
578
597
  // Check implicit dependencies structure
579
- expect(deps.db).toBeDefined();
598
+ expect(deps.databaseAdapter).toBeDefined();
580
599
  expect(deps.schema).toBeDefined();
581
600
  expect(typeof deps.createUnitOfWork).toBe("function");
582
601
  });
@@ -590,7 +609,7 @@ describe("DatabaseFragmentDefinitionBuilder", () => {
590
609
  .withDependencies(() => ({ apiKey: "key" }))
591
610
  .providesBaseService(({ deps, defineService }) => {
592
611
  expect(deps.apiKey).toBe("key");
593
- expect(deps.db).toBeDefined();
612
+ expect(deps.databaseAdapter).toBeDefined();
594
613
  expect(defineService).toBeDefined();
595
614
 
596
615
  return defineService({
@@ -687,6 +706,43 @@ describe("DatabaseFragmentDefinitionBuilder", () => {
687
706
  });
688
707
  });
689
708
 
709
+ describe("withSyncCommands", () => {
710
+ it("registers sync commands in adapter registry", async () => {
711
+ const sqlite = new SQLite(":memory:");
712
+ const adapter = new SqlAdapter({
713
+ dialect: new SqliteDialect({ database: sqlite }),
714
+ driverConfig: new BetterSQLite3DriverConfig(),
715
+ });
716
+
717
+ const registry = getRegistryForAdapterSync(adapter);
718
+ const syncCommands = defineSyncCommands({ schema: testSchema }).create(
719
+ ({ defineCommand }) => [
720
+ defineCommand({
721
+ name: "ping",
722
+ handler: async () => undefined,
723
+ }),
724
+ ],
725
+ );
726
+
727
+ const definition = defineFragment("sync-frag")
728
+ .extend(withDatabase(testSchema))
729
+ .withSyncCommands(syncCommands)
730
+ .build();
731
+
732
+ definition.dependencies!({
733
+ config: {},
734
+ options: { databaseAdapter: adapter },
735
+ });
736
+
737
+ const resolved = registry.resolveSyncCommand("sync-frag", testSchema.name, "ping");
738
+ expect(resolved?.command).toBe(syncCommands.commands.get("ping"));
739
+ expect(resolved?.namespace).toBe(sanitizeNamespace(testSchema.name));
740
+
741
+ await adapter.close();
742
+ sqlite.close();
743
+ });
744
+ });
745
+
690
746
  describe("createRequestStorage and createThisContext", () => {
691
747
  it("should create request storage with UnitOfWork", () => {
692
748
  const mockAdapter = createMockAdapter();
@@ -826,7 +882,7 @@ describe("DatabaseFragmentDefinitionBuilder", () => {
826
882
  options: {},
827
883
  });
828
884
 
829
- expect(deps.db).toBeDefined();
885
+ expect(deps.databaseAdapter).toBeDefined();
830
886
  expect(deps.createUnitOfWork).toBeDefined();
831
887
  } finally {
832
888
  if (previous === undefined) {
@@ -860,7 +916,7 @@ describe("DatabaseFragmentDefinitionBuilder", () => {
860
916
  >();
861
917
 
862
918
  expect(deps.customDep).toBe("test");
863
- expect(deps.db).toBeDefined();
919
+ expect(deps.databaseAdapter).toBeDefined();
864
920
  });
865
921
  });
866
922
 
@@ -888,21 +944,31 @@ describe("DatabaseFragmentDefinitionBuilder", () => {
888
944
  getStore: () => ({
889
945
  uow: mockAdapter.createQueryEngine(testSchema, "test").createUnitOfWork(),
890
946
  }),
891
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
892
- } as any;
947
+ } as unknown as RequestContextStorage<DatabaseContextStorage>;
893
948
 
894
949
  // Spy on createServiceTxBuilder
895
950
  const createServiceTxBuilderSpy = vi.spyOn(executeUnitOfWork, "createServiceTxBuilder");
896
951
 
952
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
953
+ const deps = {} as any;
954
+
897
955
  // Get the contexts which includes serviceTx
898
956
  const contexts = definition.createThisContext!({
899
957
  config: {},
900
958
  options: { databaseAdapter: mockAdapter },
901
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
902
- deps: {} as any,
959
+ deps,
903
960
  storage: mockStorage,
904
961
  });
905
962
 
963
+ // Initialize hooks with services so serviceTx receives the hooks map.
964
+ definition.internalDataFactory?.({
965
+ config: {},
966
+ options: { databaseAdapter: mockAdapter },
967
+ deps,
968
+ services: {} as Record<string, never>,
969
+ serviceDeps: {} as Record<string, never>,
970
+ });
971
+
906
972
  // Call serviceTx - this should pass hooks to createServiceTxBuilder
907
973
  contexts.serviceContext.serviceTx(testSchema);
908
974
 
@@ -918,4 +984,712 @@ describe("DatabaseFragmentDefinitionBuilder", () => {
918
984
  createServiceTxBuilderSpy.mockRestore();
919
985
  });
920
986
  });
987
+
988
+ describe("provideHooks services access", () => {
989
+ it("should provide bound services to hooks factory", () => {
990
+ const mockAdapter = createMockAdapter();
991
+ let observed: string | null = null;
992
+
993
+ type TestHooks = {
994
+ onPing: HookFn;
995
+ };
996
+
997
+ const definition = defineFragment("db-frag-hooks-services")
998
+ .extend(withDatabase(testSchema))
999
+ .providesBaseService(() => ({
1000
+ ping: () => "pong",
1001
+ }))
1002
+ .provideHooks<TestHooks>(({ defineHook, services }) => {
1003
+ observed = services.ping();
1004
+ return {
1005
+ onPing: defineHook(async function () {
1006
+ // no-op
1007
+ }),
1008
+ };
1009
+ })
1010
+ .build();
1011
+
1012
+ const deps = definition.dependencies!({
1013
+ config: {},
1014
+ options: { databaseAdapter: mockAdapter },
1015
+ });
1016
+
1017
+ const internalData = definition.internalDataFactory?.({
1018
+ config: {},
1019
+ options: { databaseAdapter: mockAdapter },
1020
+ deps,
1021
+ services: { ping: () => "pong" },
1022
+ serviceDeps: {},
1023
+ }) as { durableHooksToken?: object } | undefined;
1024
+
1025
+ expect(observed).toBe("pong");
1026
+ expect(internalData?.durableHooksToken).toBeDefined();
1027
+ const runtime = internalData?.durableHooksToken
1028
+ ? getDurableHooksRuntimeByToken(internalData.durableHooksToken)
1029
+ : undefined;
1030
+ expect(runtime?.config.hooks).toBeDefined();
1031
+ expect(runtime?.config.hooks).toHaveProperty("onPing");
1032
+ });
1033
+ });
1034
+
1035
+ describe("durable hooks waitUntil forwarding", () => {
1036
+ it("forwards request waitUntil to notifier context", async () => {
1037
+ const mockAdapter = createMockAdapter();
1038
+ const requestSourceSymbol = Symbol.for("fragno-request-source");
1039
+ const requestRouteSymbol = Symbol.for("fragno-request-route");
1040
+ const requestWaitUntilSymbol = Symbol.for("fragno-request-wait-until");
1041
+ const notifySpy = vi.fn().mockResolvedValue(undefined);
1042
+ const waitUntilSpy = vi.fn();
1043
+
1044
+ type TestHooks = {
1045
+ onPing: HookFn<{ value: string }>;
1046
+ };
1047
+
1048
+ const definition = defineFragment("db-frag-hooks-waituntil")
1049
+ .extend(withDatabase(testSchema))
1050
+ .provideHooks<TestHooks>(({ defineHook }) => ({
1051
+ onPing: defineHook(async function () {
1052
+ // no-op
1053
+ }),
1054
+ }))
1055
+ .build();
1056
+
1057
+ const deps = definition.dependencies!({
1058
+ config: {},
1059
+ options: { databaseAdapter: mockAdapter },
1060
+ });
1061
+
1062
+ const internalData = definition.internalDataFactory?.({
1063
+ config: {},
1064
+ options: { databaseAdapter: mockAdapter },
1065
+ deps,
1066
+ services: {} as Record<string, never>,
1067
+ serviceDeps: {} as Record<string, never>,
1068
+ }) as { durableHooksToken?: object } | undefined;
1069
+
1070
+ const runtime = internalData?.durableHooksToken
1071
+ ? getDurableHooksRuntimeByToken(internalData.durableHooksToken)
1072
+ : undefined;
1073
+ if (!runtime) {
1074
+ throw new Error("Durable hooks runtime missing");
1075
+ }
1076
+ runtime.config.notifier = {
1077
+ notify: notifySpy,
1078
+ };
1079
+
1080
+ const createHandlerTxBuilderSpy = vi
1081
+ .spyOn(executeUnitOfWork, "createHandlerTxBuilder")
1082
+ .mockReturnValue(
1083
+ {} as unknown as ReturnType<typeof executeUnitOfWork.createHandlerTxBuilder>,
1084
+ );
1085
+
1086
+ const mockStorage = {
1087
+ getStore: () => ({
1088
+ uow: mockAdapter.createQueryEngine(testSchema, "test").createUnitOfWork(),
1089
+ [requestSourceSymbol]: "route",
1090
+ [requestRouteSymbol]: { method: "POST", path: "/users" },
1091
+ [requestWaitUntilSymbol]: waitUntilSpy,
1092
+ }),
1093
+ } as unknown as RequestContextStorage<DatabaseContextStorage>;
1094
+
1095
+ const contexts = definition.createThisContext!({
1096
+ config: {},
1097
+ options: { databaseAdapter: mockAdapter },
1098
+ deps,
1099
+ storage: mockStorage,
1100
+ });
1101
+
1102
+ contexts.handlerContext.handlerTx();
1103
+ const callArgs = createHandlerTxBuilderSpy.mock.calls[0]?.[0];
1104
+ const mockUow = {
1105
+ getTriggeredHooks: () => [
1106
+ {
1107
+ namespace: runtime.config.namespace,
1108
+ hookName: "onPing",
1109
+ payload: { value: "ping" },
1110
+ },
1111
+ ],
1112
+ } as unknown as IUnitOfWork;
1113
+ await callArgs?.onAfterMutate?.(mockUow);
1114
+
1115
+ expect(notifySpy).toHaveBeenCalledTimes(1);
1116
+ const notifyContext = notifySpy.mock.calls[0]?.[0] as
1117
+ | { route?: string; source?: string; waitUntil?: unknown }
1118
+ | undefined;
1119
+ expect(notifyContext).toMatchObject({
1120
+ source: "request",
1121
+ route: "POST /users",
1122
+ });
1123
+ expect(notifyContext?.waitUntil).toBe(waitUntilSpy);
1124
+ expect(waitUntilSpy).toHaveBeenCalledTimes(1);
1125
+
1126
+ createHandlerTxBuilderSpy.mockRestore();
1127
+ });
1128
+
1129
+ it("forwards inherited request waitUntil for hook mutations", async () => {
1130
+ const mockAdapter = createMockAdapter();
1131
+ const requestWaitUntilSymbol = Symbol.for("fragno-request-wait-until");
1132
+ const notifySpy = vi.fn().mockResolvedValue(undefined);
1133
+ const waitUntilSpy = vi.fn();
1134
+
1135
+ type TestHooks = {
1136
+ onPing: HookFn<{ value: string }>;
1137
+ };
1138
+
1139
+ const definition = defineFragment("db-frag-hooks-hook-waituntil")
1140
+ .extend(withDatabase(testSchema))
1141
+ .provideHooks<TestHooks>(({ defineHook }) => ({
1142
+ onPing: defineHook(async function () {
1143
+ // no-op
1144
+ }),
1145
+ }))
1146
+ .build();
1147
+
1148
+ const deps = definition.dependencies!({
1149
+ config: {},
1150
+ options: { databaseAdapter: mockAdapter },
1151
+ });
1152
+
1153
+ const internalData = definition.internalDataFactory?.({
1154
+ config: {},
1155
+ options: { databaseAdapter: mockAdapter },
1156
+ deps,
1157
+ services: {} as Record<string, never>,
1158
+ serviceDeps: {} as Record<string, never>,
1159
+ }) as { durableHooksToken?: object } | undefined;
1160
+
1161
+ const runtime = internalData?.durableHooksToken
1162
+ ? getDurableHooksRuntimeByToken(internalData.durableHooksToken)
1163
+ : undefined;
1164
+ if (!runtime) {
1165
+ throw new Error("Durable hooks runtime missing");
1166
+ }
1167
+ runtime.config.notifier = {
1168
+ notify: notifySpy,
1169
+ };
1170
+
1171
+ const createHandlerTxBuilderSpy = vi
1172
+ .spyOn(executeUnitOfWork, "createHandlerTxBuilder")
1173
+ .mockImplementation((options) => {
1174
+ return {
1175
+ execute: vi.fn(async () => {
1176
+ const mockUow = {
1177
+ getTriggeredHooks: () => [
1178
+ {
1179
+ namespace: runtime.config.namespace,
1180
+ hookName: "onPing",
1181
+ payload: { value: "ping" },
1182
+ },
1183
+ ],
1184
+ } as unknown as IUnitOfWork;
1185
+ await options.onAfterMutate?.(mockUow);
1186
+ }),
1187
+ } as unknown as ReturnType<typeof executeUnitOfWork.createHandlerTxBuilder>;
1188
+ });
1189
+
1190
+ try {
1191
+ const storage = mockAdapter.contextStorage as RequestContextStorage<DatabaseContextStorage>;
1192
+ await storage.runWithInitializer(
1193
+ () =>
1194
+ ({
1195
+ uow: mockAdapter.createQueryEngine(testSchema, "test").createUnitOfWork(),
1196
+ [requestWaitUntilSymbol]: waitUntilSpy,
1197
+ }) as DatabaseContextStorage,
1198
+ async () => {
1199
+ await runtime.config.handlerTx().execute();
1200
+ },
1201
+ );
1202
+
1203
+ expect(notifySpy).toHaveBeenCalledTimes(1);
1204
+ const notifyContext = notifySpy.mock.calls[0]?.[0] as
1205
+ | { source?: string; waitUntil?: unknown }
1206
+ | undefined;
1207
+ expect(notifyContext?.source).toBe("hook");
1208
+ expect(notifyContext?.waitUntil).toBe(waitUntilSpy);
1209
+ expect(waitUntilSpy).toHaveBeenCalledTimes(1);
1210
+ } finally {
1211
+ createHandlerTxBuilderSpy.mockRestore();
1212
+ }
1213
+ });
1214
+ });
1215
+
1216
+ describe("durable hooks cross-namespace scheduling", () => {
1217
+ it("should schedule hooks triggered in another namespace when mutation runs inside a hook", async () => {
1218
+ const sqlite = new SQLite(":memory:");
1219
+ const adapter = new SqlAdapter({
1220
+ dialect: new SqliteDialect({ database: sqlite }),
1221
+ driverConfig: new BetterSQLite3DriverConfig(),
1222
+ });
1223
+
1224
+ const schemaA = schema("hooks_alpha", (s) =>
1225
+ s.addTable("items", (t) =>
1226
+ t.addColumn("id", idColumn()).addColumn("name", column("string")),
1227
+ ),
1228
+ );
1229
+ const schemaB = schema("hooks_beta", (s) =>
1230
+ s.addTable("items", (t) =>
1231
+ t.addColumn("id", idColumn()).addColumn("name", column("string")),
1232
+ ),
1233
+ );
1234
+
1235
+ type HooksA = {
1236
+ onAlpha: (payload: { value: string }) => void;
1237
+ };
1238
+ type HooksB = {
1239
+ onBeta: (payload: { value: string }) => void;
1240
+ };
1241
+
1242
+ const hooksB: HooksB = {
1243
+ onBeta: () => {},
1244
+ };
1245
+
1246
+ const namespaceA = sanitizeNamespace(schemaA.name);
1247
+ const namespaceB = sanitizeNamespace(schemaB.name);
1248
+
1249
+ const fragmentADef = defineFragment("frag-hooks-a")
1250
+ .extend(withDatabase(schemaA))
1251
+ .provideHooks<HooksA>(({ defineHook }) => ({
1252
+ onAlpha: defineHook(async function () {
1253
+ await this.handlerTx({
1254
+ onBeforeMutate: (uow) => {
1255
+ uow.registerSchema(schemaB, namespaceB);
1256
+ },
1257
+ })
1258
+ .mutate(({ forSchema }) => {
1259
+ const other = forSchema(schemaB, hooksB);
1260
+ other.triggerHook("onBeta", { value: "beta" });
1261
+ })
1262
+ .execute();
1263
+ }),
1264
+ }))
1265
+ .build();
1266
+
1267
+ const fragmentBDef = defineFragment("frag-hooks-b")
1268
+ .extend(withDatabase(schemaB))
1269
+ .provideHooks<HooksB>(({ defineHook }) => ({
1270
+ onBeta: defineHook(async function () {
1271
+ // no-op
1272
+ }),
1273
+ }))
1274
+ .build();
1275
+
1276
+ try {
1277
+ const internalMigrations = adapter.prepareMigrations(internalSchema, null);
1278
+ await internalMigrations.executeWithDriver(adapter.driver, 0);
1279
+
1280
+ const migrationsA = adapter.prepareMigrations(schemaA, namespaceA);
1281
+ await migrationsA.executeWithDriver(adapter.driver, 0);
1282
+
1283
+ const migrationsB = adapter.prepareMigrations(schemaB, namespaceB);
1284
+ await migrationsB.executeWithDriver(adapter.driver, 0);
1285
+
1286
+ const fragmentA = instantiate(fragmentADef)
1287
+ .withConfig({})
1288
+ .withOptions({ databaseAdapter: adapter })
1289
+ .build();
1290
+ const fragmentB = instantiate(fragmentBDef)
1291
+ .withConfig({})
1292
+ .withOptions({ databaseAdapter: adapter })
1293
+ .build();
1294
+
1295
+ const tokenA = (fragmentA.$internal as { durableHooksToken?: object }).durableHooksToken;
1296
+ const tokenB = (fragmentB.$internal as { durableHooksToken?: object }).durableHooksToken;
1297
+
1298
+ expect(tokenA).toBeDefined();
1299
+ expect(tokenB).toBeDefined();
1300
+
1301
+ const runtimeA = tokenA ? getDurableHooksRuntimeByToken(tokenA) : undefined;
1302
+ const runtimeB = tokenB ? getDurableHooksRuntimeByToken(tokenB) : undefined;
1303
+
1304
+ if (!runtimeA || !runtimeB) {
1305
+ throw new Error("Durable hooks runtime missing");
1306
+ }
1307
+
1308
+ const notifySpy = vi.fn();
1309
+ runtimeB.config.notifier = {
1310
+ notify: notifySpy,
1311
+ };
1312
+
1313
+ const internalFragment = getInternalFragment(adapter);
1314
+ await internalFragment.inContext(async function () {
1315
+ await this.handlerTx()
1316
+ .mutate(({ forSchema }) => {
1317
+ const uow = forSchema(internalSchema);
1318
+ uow.create("fragno_hooks", {
1319
+ namespace: namespaceA,
1320
+ hookName: "onAlpha",
1321
+ payload: { value: "alpha" },
1322
+ status: "pending",
1323
+ attempts: 0,
1324
+ maxAttempts: 1,
1325
+ lastAttemptAt: null,
1326
+ nextRetryAt: null,
1327
+ error: null,
1328
+ nonce: "test-nonce",
1329
+ });
1330
+ })
1331
+ .execute();
1332
+ });
1333
+
1334
+ const runnerA = runtimeA.config.runner ?? hooks.createDurableHooksRunner(runtimeA.config);
1335
+ runtimeA.config.runner = runnerA;
1336
+ await runnerA.processDue();
1337
+
1338
+ expect(notifySpy).toHaveBeenCalled();
1339
+ } finally {
1340
+ await adapter.close();
1341
+ sqlite.close();
1342
+ }
1343
+ });
1344
+
1345
+ it("should skip cross-namespace notify when target namespace has autoSchedule=false", async () => {
1346
+ const sqlite = new SQLite(":memory:");
1347
+ const adapter = new SqlAdapter({
1348
+ dialect: new SqliteDialect({ database: sqlite }),
1349
+ driverConfig: new BetterSQLite3DriverConfig(),
1350
+ });
1351
+
1352
+ const schemaA = schema("hooks_alpha_autoschedule", (s) =>
1353
+ s.addTable("items", (t) =>
1354
+ t.addColumn("id", idColumn()).addColumn("name", column("string")),
1355
+ ),
1356
+ );
1357
+ const schemaB = schema("hooks_beta_autoschedule", (s) =>
1358
+ s.addTable("items", (t) =>
1359
+ t.addColumn("id", idColumn()).addColumn("name", column("string")),
1360
+ ),
1361
+ );
1362
+
1363
+ type HooksA = {
1364
+ onAlpha: (payload: { value: string }) => void;
1365
+ };
1366
+ type HooksB = {
1367
+ onBeta: (payload: { value: string }) => void;
1368
+ };
1369
+
1370
+ const hooksB: HooksB = {
1371
+ onBeta: () => {},
1372
+ };
1373
+
1374
+ const namespaceA = sanitizeNamespace(schemaA.name);
1375
+ const namespaceB = sanitizeNamespace(schemaB.name);
1376
+
1377
+ const fragmentADef = defineFragment("frag-hooks-autoschedule-a")
1378
+ .extend(withDatabase(schemaA))
1379
+ .provideHooks<HooksA>(({ defineHook }) => ({
1380
+ onAlpha: defineHook(async function () {
1381
+ await this.handlerTx({
1382
+ onBeforeMutate: (uow) => {
1383
+ uow.registerSchema(schemaB, namespaceB);
1384
+ },
1385
+ })
1386
+ .mutate(({ forSchema }) => {
1387
+ const other = forSchema(schemaB, hooksB);
1388
+ other.triggerHook("onBeta", { value: "beta" });
1389
+ })
1390
+ .execute();
1391
+ }),
1392
+ }))
1393
+ .build();
1394
+
1395
+ const fragmentBDef = defineFragment("frag-hooks-autoschedule-b")
1396
+ .extend(withDatabase(schemaB))
1397
+ .provideHooks<HooksB>(({ defineHook }) => ({
1398
+ onBeta: defineHook(async function () {
1399
+ // no-op
1400
+ }),
1401
+ }))
1402
+ .build();
1403
+
1404
+ try {
1405
+ const internalMigrations = adapter.prepareMigrations(internalSchema, null);
1406
+ await internalMigrations.executeWithDriver(adapter.driver, 0);
1407
+
1408
+ const migrationsA = adapter.prepareMigrations(schemaA, namespaceA);
1409
+ await migrationsA.executeWithDriver(adapter.driver, 0);
1410
+
1411
+ const migrationsB = adapter.prepareMigrations(schemaB, namespaceB);
1412
+ await migrationsB.executeWithDriver(adapter.driver, 0);
1413
+
1414
+ const fragmentA = instantiate(fragmentADef)
1415
+ .withConfig({})
1416
+ .withOptions({ databaseAdapter: adapter })
1417
+ .build();
1418
+ const fragmentB = instantiate(fragmentBDef)
1419
+ .withConfig({})
1420
+ .withOptions({ databaseAdapter: adapter, durableHooks: { autoSchedule: false } })
1421
+ .build();
1422
+
1423
+ const tokenA = (fragmentA.$internal as { durableHooksToken?: object }).durableHooksToken;
1424
+ const tokenB = (fragmentB.$internal as { durableHooksToken?: object }).durableHooksToken;
1425
+
1426
+ expect(tokenA).toBeDefined();
1427
+ expect(tokenB).toBeDefined();
1428
+
1429
+ const runtimeA = tokenA ? getDurableHooksRuntimeByToken(tokenA) : undefined;
1430
+ const runtimeB = tokenB ? getDurableHooksRuntimeByToken(tokenB) : undefined;
1431
+
1432
+ if (!runtimeA || !runtimeB) {
1433
+ throw new Error("Durable hooks runtime missing");
1434
+ }
1435
+
1436
+ const notifySpy = vi.fn();
1437
+ runtimeB.config.notifier = {
1438
+ notify: notifySpy,
1439
+ };
1440
+
1441
+ const internalFragment = getInternalFragment(adapter);
1442
+ await internalFragment.inContext(async function () {
1443
+ await this.handlerTx()
1444
+ .mutate(({ forSchema }) => {
1445
+ const uow = forSchema(internalSchema);
1446
+ uow.create("fragno_hooks", {
1447
+ namespace: namespaceA,
1448
+ hookName: "onAlpha",
1449
+ payload: { value: "alpha" },
1450
+ status: "pending",
1451
+ attempts: 0,
1452
+ maxAttempts: 1,
1453
+ lastAttemptAt: null,
1454
+ nextRetryAt: null,
1455
+ error: null,
1456
+ nonce: "auto-schedule-test-nonce",
1457
+ });
1458
+ })
1459
+ .execute();
1460
+ });
1461
+
1462
+ const runnerA = runtimeA.config.runner ?? hooks.createDurableHooksRunner(runtimeA.config);
1463
+ runtimeA.config.runner = runnerA;
1464
+ await runnerA.processDue();
1465
+
1466
+ expect(notifySpy).not.toHaveBeenCalled();
1467
+
1468
+ const queuedHooks = await internalFragment.inContext(async function () {
1469
+ return await this.handlerTx()
1470
+ .withServiceCalls(
1471
+ () =>
1472
+ [internalFragment.services.hookService.getHooksByNamespace(namespaceB)] as const,
1473
+ )
1474
+ .transform(({ serviceResult: [result] }) => result)
1475
+ .execute();
1476
+ });
1477
+
1478
+ const pendingBetaHooks = queuedHooks.filter(
1479
+ (hook) => hook.status === "pending" && hook.hookName === "onBeta",
1480
+ );
1481
+ expect(pendingBetaHooks.length).toBeGreaterThan(0);
1482
+ } finally {
1483
+ await adapter.close();
1484
+ sqlite.close();
1485
+ }
1486
+ });
1487
+
1488
+ it("should notify the matching runtime instance when namespace has multiple runtimes", async () => {
1489
+ const sqliteA = new SQLite(":memory:");
1490
+ const sqliteB = new SQLite(":memory:");
1491
+ const adapterA = new SqlAdapter({
1492
+ dialect: new SqliteDialect({ database: sqliteA }),
1493
+ driverConfig: new BetterSQLite3DriverConfig(),
1494
+ });
1495
+ const adapterB = new SqlAdapter({
1496
+ dialect: new SqliteDialect({ database: sqliteB }),
1497
+ driverConfig: new BetterSQLite3DriverConfig(),
1498
+ });
1499
+
1500
+ const unique = `${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
1501
+ const schemaA = schema(`hooks_alpha_instance_${unique}`, (s) =>
1502
+ s.addTable("items", (t) =>
1503
+ t.addColumn("id", idColumn()).addColumn("name", column("string")),
1504
+ ),
1505
+ );
1506
+ const schemaB = schema(`hooks_shared_${unique}`, (s) =>
1507
+ s.addTable("items", (t) =>
1508
+ t.addColumn("id", idColumn()).addColumn("name", column("string")),
1509
+ ),
1510
+ );
1511
+
1512
+ type HooksA = {
1513
+ onAlpha: (payload: { value: string }) => void;
1514
+ };
1515
+ type HooksB = {
1516
+ onBeta: (payload: { value: string }) => void;
1517
+ };
1518
+
1519
+ const hooksB: HooksB = {
1520
+ onBeta: () => {},
1521
+ };
1522
+
1523
+ const namespaceA = sanitizeNamespace(schemaA.name);
1524
+ const namespaceB = sanitizeNamespace(schemaB.name);
1525
+
1526
+ const fragmentADef = defineFragment("frag-hooks-instance-a")
1527
+ .extend(withDatabase(schemaA))
1528
+ .provideHooks<HooksA>(({ defineHook }) => ({
1529
+ onAlpha: defineHook(async function () {
1530
+ await this.handlerTx({
1531
+ onBeforeMutate: (uow) => {
1532
+ uow.registerSchema(schemaB, namespaceB);
1533
+ },
1534
+ })
1535
+ .mutate(({ forSchema }) => {
1536
+ const other = forSchema(schemaB, hooksB);
1537
+ other.triggerHook("onBeta", { value: "beta" });
1538
+ })
1539
+ .execute();
1540
+ }),
1541
+ }))
1542
+ .build();
1543
+
1544
+ const fragmentBDef = defineFragment("frag-hooks-shared")
1545
+ .extend(withDatabase(schemaB))
1546
+ .provideHooks<HooksB>(({ defineHook }) => ({
1547
+ onBeta: defineHook(async function () {
1548
+ // no-op
1549
+ }),
1550
+ }))
1551
+ .build();
1552
+
1553
+ try {
1554
+ const internalMigrationsA = adapterA.prepareMigrations(internalSchema, null);
1555
+ await internalMigrationsA.executeWithDriver(adapterA.driver, 0);
1556
+ const schemaAMigrations = adapterA.prepareMigrations(schemaA, namespaceA);
1557
+ await schemaAMigrations.executeWithDriver(adapterA.driver, 0);
1558
+ const schemaBMigrationsA = adapterA.prepareMigrations(schemaB, namespaceB);
1559
+ await schemaBMigrationsA.executeWithDriver(adapterA.driver, 0);
1560
+
1561
+ const internalMigrationsB = adapterB.prepareMigrations(internalSchema, null);
1562
+ await internalMigrationsB.executeWithDriver(adapterB.driver, 0);
1563
+ const schemaBMigrationsB = adapterB.prepareMigrations(schemaB, namespaceB);
1564
+ await schemaBMigrationsB.executeWithDriver(adapterB.driver, 0);
1565
+
1566
+ const fragmentA = instantiate(fragmentADef)
1567
+ .withConfig({})
1568
+ .withOptions({ databaseAdapter: adapterA })
1569
+ .build();
1570
+ const fragmentBForAdapterA = instantiate(fragmentBDef)
1571
+ .withConfig({})
1572
+ .withOptions({ databaseAdapter: adapterA })
1573
+ .build();
1574
+ const fragmentBForAdapterB = instantiate(fragmentBDef)
1575
+ .withConfig({})
1576
+ .withOptions({ databaseAdapter: adapterB })
1577
+ .build();
1578
+
1579
+ const tokenA = (fragmentA.$internal as { durableHooksToken?: object }).durableHooksToken;
1580
+ const tokenBForAdapterA = (fragmentBForAdapterA.$internal as { durableHooksToken?: object })
1581
+ .durableHooksToken;
1582
+ const tokenBForAdapterB = (fragmentBForAdapterB.$internal as { durableHooksToken?: object })
1583
+ .durableHooksToken;
1584
+
1585
+ expect(tokenA).toBeDefined();
1586
+ expect(tokenBForAdapterA).toBeDefined();
1587
+ expect(tokenBForAdapterB).toBeDefined();
1588
+
1589
+ const runtimeA = tokenA ? getDurableHooksRuntimeByToken(tokenA) : undefined;
1590
+ const runtimeBForAdapterA = tokenBForAdapterA
1591
+ ? getDurableHooksRuntimeByToken(tokenBForAdapterA)
1592
+ : undefined;
1593
+ const runtimeBForAdapterB = tokenBForAdapterB
1594
+ ? getDurableHooksRuntimeByToken(tokenBForAdapterB)
1595
+ : undefined;
1596
+
1597
+ if (!runtimeA || !runtimeBForAdapterA || !runtimeBForAdapterB) {
1598
+ throw new Error("Durable hooks runtime missing");
1599
+ }
1600
+
1601
+ const notifySpyForAdapterA = vi.fn();
1602
+ const notifySpyForAdapterB = vi.fn();
1603
+ runtimeBForAdapterA.config.notifier = {
1604
+ notify: notifySpyForAdapterA,
1605
+ };
1606
+ runtimeBForAdapterB.config.notifier = {
1607
+ notify: notifySpyForAdapterB,
1608
+ };
1609
+
1610
+ const internalFragment = getInternalFragment(adapterA);
1611
+ await internalFragment.inContext(async function () {
1612
+ await this.handlerTx()
1613
+ .mutate(({ forSchema }) => {
1614
+ const uow = forSchema(internalSchema);
1615
+ uow.create("fragno_hooks", {
1616
+ namespace: namespaceA,
1617
+ hookName: "onAlpha",
1618
+ payload: { value: "alpha" },
1619
+ status: "pending",
1620
+ attempts: 0,
1621
+ maxAttempts: 1,
1622
+ lastAttemptAt: null,
1623
+ nextRetryAt: null,
1624
+ error: null,
1625
+ nonce: "instance-test-nonce",
1626
+ });
1627
+ })
1628
+ .execute();
1629
+ });
1630
+
1631
+ const runnerA = runtimeA.config.runner ?? hooks.createDurableHooksRunner(runtimeA.config);
1632
+ runtimeA.config.runner = runnerA;
1633
+ await runnerA.processDue();
1634
+
1635
+ expect(notifySpyForAdapterA).toHaveBeenCalled();
1636
+ expect(notifySpyForAdapterB).not.toHaveBeenCalled();
1637
+ } finally {
1638
+ await adapterA.close();
1639
+ await adapterB.close();
1640
+ sqliteA.close();
1641
+ sqliteB.close();
1642
+ }
1643
+ });
1644
+ });
1645
+
1646
+ describe("handlerTx plan mode", () => {
1647
+ it("should suppress hook mutations in plan mode", () => {
1648
+ const mockAdapter = createMockAdapter();
1649
+
1650
+ type TestHooks = {
1651
+ onUserCreated: (payload: { email: string }) => void;
1652
+ };
1653
+
1654
+ const definition = withDatabase(testSchema)(defineFragment("db-frag-plan-mode"))
1655
+ .provideHooks<TestHooks>(({ defineHook }) => ({
1656
+ onUserCreated: defineHook(function () {
1657
+ // no-op
1658
+ }),
1659
+ }))
1660
+ .build();
1661
+
1662
+ const mockStorage = {
1663
+ getStore: () => ({
1664
+ uow: mockAdapter.createQueryEngine(testSchema, "test").createUnitOfWork(),
1665
+ }),
1666
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1667
+ } as any;
1668
+
1669
+ const createHandlerTxBuilderSpy = vi
1670
+ .spyOn(executeUnitOfWork, "createHandlerTxBuilder")
1671
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1672
+ .mockReturnValue({} as any);
1673
+
1674
+ const prepareHookMutationsSpy = vi.spyOn(hooks, "prepareHookMutations");
1675
+
1676
+ const contexts = definition.createThisContext!({
1677
+ config: {},
1678
+ options: { databaseAdapter: mockAdapter },
1679
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1680
+ deps: {} as any,
1681
+ storage: mockStorage,
1682
+ });
1683
+
1684
+ contexts.handlerContext.handlerTx({ planMode: true });
1685
+
1686
+ const callArgs = createHandlerTxBuilderSpy.mock.calls[0]?.[0];
1687
+ callArgs?.onBeforeMutate?.({} as unknown as IUnitOfWork);
1688
+
1689
+ expect(prepareHookMutationsSpy).not.toHaveBeenCalled();
1690
+
1691
+ prepareHookMutationsSpy.mockRestore();
1692
+ createHandlerTxBuilderSpy.mockRestore();
1693
+ });
1694
+ });
921
1695
  });