@fragno-dev/db 0.2.2 → 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 (587) hide show
  1. package/.turbo/turbo-build.log +404 -175
  2. package/CHANGELOG.md +109 -0
  3. package/README.md +54 -9
  4. package/dist/adapters/adapters.d.ts +23 -21
  5. package/dist/adapters/adapters.d.ts.map +1 -1
  6. package/dist/adapters/adapters.js.map +1 -1
  7. package/dist/adapters/generic-sql/driver-config.d.ts +16 -1
  8. package/dist/adapters/generic-sql/driver-config.d.ts.map +1 -1
  9. package/dist/adapters/generic-sql/driver-config.js +23 -1
  10. package/dist/adapters/generic-sql/driver-config.js.map +1 -1
  11. package/dist/adapters/generic-sql/generic-sql-adapter.d.ts +24 -9
  12. package/dist/adapters/generic-sql/generic-sql-adapter.d.ts.map +1 -1
  13. package/dist/adapters/generic-sql/generic-sql-adapter.js +60 -22
  14. package/dist/adapters/generic-sql/generic-sql-adapter.js.map +1 -1
  15. package/dist/adapters/generic-sql/generic-sql-uow-executor.js +169 -3
  16. package/dist/adapters/generic-sql/generic-sql-uow-executor.js.map +1 -1
  17. package/dist/adapters/generic-sql/migration/cold-kysely.js.map +1 -1
  18. package/dist/adapters/generic-sql/migration/dialect/mysql.js +25 -6
  19. package/dist/adapters/generic-sql/migration/dialect/mysql.js.map +1 -1
  20. package/dist/adapters/generic-sql/migration/dialect/postgres.js +7 -6
  21. package/dist/adapters/generic-sql/migration/dialect/postgres.js.map +1 -1
  22. package/dist/adapters/generic-sql/migration/dialect/sqlite.js +193 -16
  23. package/dist/adapters/generic-sql/migration/dialect/sqlite.js.map +1 -1
  24. package/dist/adapters/generic-sql/migration/executor.d.ts.map +1 -1
  25. package/dist/adapters/generic-sql/migration/executor.js +30 -3
  26. package/dist/adapters/generic-sql/migration/executor.js.map +1 -1
  27. package/dist/adapters/generic-sql/migration/prepared-migrations.d.ts.map +1 -1
  28. package/dist/adapters/generic-sql/migration/prepared-migrations.js +9 -9
  29. package/dist/adapters/generic-sql/migration/prepared-migrations.js.map +1 -1
  30. package/dist/adapters/generic-sql/migration/sql-generator.js +75 -52
  31. package/dist/adapters/generic-sql/migration/sql-generator.js.map +1 -1
  32. package/dist/adapters/generic-sql/query/create-sql-query-compiler.js +7 -6
  33. package/dist/adapters/generic-sql/query/create-sql-query-compiler.js.map +1 -1
  34. package/dist/adapters/generic-sql/query/cursor-utils.js +42 -4
  35. package/dist/adapters/generic-sql/query/cursor-utils.js.map +1 -1
  36. package/dist/adapters/generic-sql/query/db-now-sql.js +27 -0
  37. package/dist/adapters/generic-sql/query/db-now-sql.js.map +1 -0
  38. package/dist/adapters/generic-sql/query/generic-sql-uow-operation-compiler.js +32 -21
  39. package/dist/adapters/generic-sql/query/generic-sql-uow-operation-compiler.js.map +1 -1
  40. package/dist/adapters/generic-sql/query/select-builder.js +5 -3
  41. package/dist/adapters/generic-sql/query/select-builder.js.map +1 -1
  42. package/dist/adapters/generic-sql/query/sql-query-compiler.js +49 -18
  43. package/dist/adapters/generic-sql/query/sql-query-compiler.js.map +1 -1
  44. package/dist/adapters/generic-sql/query/where-builder.js +43 -29
  45. package/dist/adapters/generic-sql/query/where-builder.js.map +1 -1
  46. package/dist/adapters/generic-sql/sqlite-storage.d.ts +13 -0
  47. package/dist/adapters/generic-sql/sqlite-storage.d.ts.map +1 -0
  48. package/dist/adapters/generic-sql/sqlite-storage.js +15 -0
  49. package/dist/adapters/generic-sql/sqlite-storage.js.map +1 -0
  50. package/dist/adapters/generic-sql/uow-decoder.js +6 -2
  51. package/dist/adapters/generic-sql/uow-decoder.js.map +1 -1
  52. package/dist/adapters/generic-sql/uow-encoder.js +27 -8
  53. package/dist/adapters/generic-sql/uow-encoder.js.map +1 -1
  54. package/dist/adapters/in-memory/condition-evaluator.js +135 -0
  55. package/dist/adapters/in-memory/condition-evaluator.js.map +1 -0
  56. package/dist/adapters/in-memory/errors.d.ts +13 -0
  57. package/dist/adapters/in-memory/errors.d.ts.map +1 -0
  58. package/dist/adapters/in-memory/errors.js +23 -0
  59. package/dist/adapters/in-memory/errors.js.map +1 -0
  60. package/dist/adapters/in-memory/in-memory-adapter.d.ts +27 -0
  61. package/dist/adapters/in-memory/in-memory-adapter.d.ts.map +1 -0
  62. package/dist/adapters/in-memory/in-memory-adapter.js +196 -0
  63. package/dist/adapters/in-memory/in-memory-adapter.js.map +1 -0
  64. package/dist/adapters/in-memory/in-memory-uow.js +871 -0
  65. package/dist/adapters/in-memory/in-memory-uow.js.map +1 -0
  66. package/dist/adapters/in-memory/index.d.ts +4 -0
  67. package/dist/adapters/in-memory/index.js +4 -0
  68. package/dist/adapters/in-memory/options.d.ts +30 -0
  69. package/dist/adapters/in-memory/options.d.ts.map +1 -0
  70. package/dist/adapters/in-memory/options.js +62 -0
  71. package/dist/adapters/in-memory/options.js.map +1 -0
  72. package/dist/adapters/in-memory/reference-resolution.js +26 -0
  73. package/dist/adapters/in-memory/reference-resolution.js.map +1 -0
  74. package/dist/adapters/in-memory/sorted-array-index.js +129 -0
  75. package/dist/adapters/in-memory/sorted-array-index.js.map +1 -0
  76. package/dist/adapters/in-memory/store.js +71 -0
  77. package/dist/adapters/in-memory/store.js.map +1 -0
  78. package/dist/adapters/in-memory/value-comparison.js +28 -0
  79. package/dist/adapters/in-memory/value-comparison.js.map +1 -0
  80. package/dist/adapters/shared/from-unit-of-work-compiler.js +51 -24
  81. package/dist/adapters/shared/from-unit-of-work-compiler.js.map +1 -1
  82. package/dist/adapters/shared/uow-operation-compiler.js +11 -11
  83. package/dist/adapters/shared/uow-operation-compiler.js.map +1 -1
  84. package/dist/adapters/sql/index.d.ts +5 -0
  85. package/dist/adapters/sql/index.js +4 -0
  86. package/dist/browser/adapters/adapters.d.ts +61 -0
  87. package/dist/browser/adapters/adapters.d.ts.map +1 -0
  88. package/dist/browser/adapters/generic-sql/migration/executor.d.ts +15 -0
  89. package/dist/browser/adapters/generic-sql/migration/executor.d.ts.map +1 -0
  90. package/dist/browser/adapters/generic-sql/migration/prepared-migrations.d.ts +66 -0
  91. package/dist/browser/adapters/generic-sql/migration/prepared-migrations.d.ts.map +1 -0
  92. package/dist/browser/adapters/generic-sql/sqlite-storage.d.ts +11 -0
  93. package/dist/browser/adapters/generic-sql/sqlite-storage.d.ts.map +1 -0
  94. package/dist/browser/adapters/in-memory/in-memory-adapter.d.ts +5 -0
  95. package/dist/browser/adapters/in-memory/index.d.ts +2 -0
  96. package/dist/browser/adapters/in-memory/options.d.ts +1 -0
  97. package/dist/browser/db-fragment-definition-builder.d.ts +237 -0
  98. package/dist/browser/db-fragment-definition-builder.d.ts.map +1 -0
  99. package/dist/browser/durable-hooks.d.ts +3 -0
  100. package/dist/browser/fragments/internal-fragment.d.ts +317 -0
  101. package/dist/browser/fragments/internal-fragment.d.ts.map +1 -0
  102. package/dist/browser/fragments/internal-fragment.schema.d.ts +1 -0
  103. package/dist/browser/hooks/durable-hooks-logger.d.ts +10 -0
  104. package/dist/browser/hooks/durable-hooks-logger.d.ts.map +1 -0
  105. package/dist/browser/hooks/hooks.d.ts +146 -0
  106. package/dist/browser/hooks/hooks.d.ts.map +1 -0
  107. package/dist/browser/id.js +1 -0
  108. package/dist/browser/internal/adapter-registry.d.ts +4 -0
  109. package/dist/browser/internal/outbox-state.d.ts +2 -0
  110. package/dist/browser/mod.d.ts +15 -0
  111. package/dist/browser/mod.d.ts.map +1 -0
  112. package/dist/browser/mod.js +17 -0
  113. package/dist/browser/mod.js.map +1 -0
  114. package/dist/browser/mod2.d.ts +48 -0
  115. package/dist/browser/mod2.d.ts.map +1 -0
  116. package/dist/browser/naming/sql-naming.d.ts +19 -0
  117. package/dist/browser/naming/sql-naming.d.ts.map +1 -0
  118. package/dist/browser/outbox/outbox.d.ts +21 -0
  119. package/dist/browser/outbox/outbox.d.ts.map +1 -0
  120. package/dist/browser/query/column-defaults.js +1 -0
  121. package/dist/browser/query/condition-builder.d.ts +44 -0
  122. package/dist/browser/query/condition-builder.d.ts.map +1 -0
  123. package/dist/browser/query/condition-builder.js +97 -0
  124. package/dist/browser/query/condition-builder.js.map +1 -0
  125. package/dist/browser/query/cursor.d.ts +105 -0
  126. package/dist/browser/query/cursor.d.ts.map +1 -0
  127. package/dist/browser/query/cursor.js +150 -0
  128. package/dist/browser/query/cursor.js.map +1 -0
  129. package/dist/browser/query/db-now.d.ts +22 -0
  130. package/dist/browser/query/db-now.d.ts.map +1 -0
  131. package/dist/browser/query/db-now.js +33 -0
  132. package/dist/browser/query/db-now.js.map +1 -0
  133. package/dist/browser/query/orm/orm.d.ts +18 -0
  134. package/dist/browser/query/orm/orm.d.ts.map +1 -0
  135. package/dist/browser/query/simple-query-interface.d.ts +108 -0
  136. package/dist/browser/query/simple-query-interface.d.ts.map +1 -0
  137. package/dist/browser/query/unit-of-work/execute-unit-of-work.d.ts +423 -0
  138. package/dist/browser/query/unit-of-work/execute-unit-of-work.d.ts.map +1 -0
  139. package/dist/browser/query/unit-of-work/execute-unit-of-work.js +507 -0
  140. package/dist/browser/query/unit-of-work/execute-unit-of-work.js.map +1 -0
  141. package/dist/browser/query/unit-of-work/retry-policy.d.ts +23 -0
  142. package/dist/browser/query/unit-of-work/retry-policy.d.ts.map +1 -0
  143. package/dist/browser/query/unit-of-work/retry-policy.js +40 -0
  144. package/dist/browser/query/unit-of-work/retry-policy.js.map +1 -0
  145. package/dist/browser/query/unit-of-work/unit-of-work.d.ts +703 -0
  146. package/dist/browser/query/unit-of-work/unit-of-work.d.ts.map +1 -0
  147. package/dist/browser/query/unit-of-work/unit-of-work.js +1206 -0
  148. package/dist/browser/query/unit-of-work/unit-of-work.js.map +1 -0
  149. package/dist/browser/query/value-encoding.js +38 -0
  150. package/dist/browser/query/value-encoding.js.map +1 -0
  151. package/dist/browser/schema/create.d.ts +326 -0
  152. package/dist/browser/schema/create.d.ts.map +1 -0
  153. package/dist/browser/schema/create.js +89 -0
  154. package/dist/browser/schema/create.js.map +1 -0
  155. package/dist/browser/schema/generate-id.js +28 -0
  156. package/dist/browser/schema/generate-id.js.map +1 -0
  157. package/dist/browser/shared/providers.d.ts +6 -0
  158. package/dist/browser/shared/providers.d.ts.map +1 -0
  159. package/dist/browser/sql-driver/connection/connection-provider.d.ts +13 -0
  160. package/dist/browser/sql-driver/connection/connection-provider.d.ts.map +1 -0
  161. package/dist/browser/sql-driver/dialect-adapter/dialect-adapter.d.ts +7 -0
  162. package/dist/browser/sql-driver/dialect-adapter/dialect-adapter.d.ts.map +1 -0
  163. package/dist/browser/sql-driver/driver/runtime-driver.d.ts +23 -0
  164. package/dist/browser/sql-driver/driver/runtime-driver.d.ts.map +1 -0
  165. package/dist/browser/sql-driver/query-executor/plugin.d.ts +17 -0
  166. package/dist/browser/sql-driver/query-executor/plugin.d.ts.map +1 -0
  167. package/dist/browser/sql-driver/query-executor/query-executor.d.ts +36 -0
  168. package/dist/browser/sql-driver/query-executor/query-executor.d.ts.map +1 -0
  169. package/dist/browser/sql-driver/sql-driver-adapter.d.ts +29 -0
  170. package/dist/browser/sql-driver/sql-driver-adapter.d.ts.map +1 -0
  171. package/dist/browser/sql-driver/sql-driver.d.ts +38 -0
  172. package/dist/browser/sql-driver/sql-driver.d.ts.map +1 -0
  173. package/dist/browser/sync/commands.d.ts +15 -0
  174. package/dist/browser/sync/commands.d.ts.map +1 -0
  175. package/dist/browser/sync/commands.js +27 -0
  176. package/dist/browser/sync/commands.js.map +1 -0
  177. package/dist/browser/sync/types.d.ts +63 -0
  178. package/dist/browser/sync/types.d.ts.map +1 -0
  179. package/dist/browser/util/types.d.ts +8 -0
  180. package/dist/browser/util/types.d.ts.map +1 -0
  181. package/dist/browser/with-database.d.ts +29 -0
  182. package/dist/browser/with-database.d.ts.map +1 -0
  183. package/dist/client.d.ts +4 -0
  184. package/dist/client.js +5 -0
  185. package/dist/db-fragment-definition-builder.d.ts +101 -33
  186. package/dist/db-fragment-definition-builder.d.ts.map +1 -1
  187. package/dist/db-fragment-definition-builder.js +450 -60
  188. package/dist/db-fragment-definition-builder.js.map +1 -1
  189. package/dist/dispatchers/cloudflare-do/dispatcher.d.ts +20 -0
  190. package/dist/dispatchers/cloudflare-do/dispatcher.d.ts.map +1 -0
  191. package/dist/dispatchers/cloudflare-do/dispatcher.js +147 -0
  192. package/dist/dispatchers/cloudflare-do/dispatcher.js.map +1 -0
  193. package/dist/dispatchers/cloudflare-do/index.d.ts +11 -0
  194. package/dist/dispatchers/cloudflare-do/index.d.ts.map +1 -0
  195. package/dist/dispatchers/cloudflare-do/index.js +31 -0
  196. package/dist/dispatchers/cloudflare-do/index.js.map +1 -0
  197. package/dist/dispatchers/node/dispatcher.d.ts +14 -0
  198. package/dist/dispatchers/node/dispatcher.d.ts.map +1 -0
  199. package/dist/dispatchers/node/dispatcher.js +80 -0
  200. package/dist/dispatchers/node/dispatcher.js.map +1 -0
  201. package/dist/dispatchers/node/index.d.ts +12 -0
  202. package/dist/dispatchers/node/index.d.ts.map +1 -0
  203. package/dist/dispatchers/node/index.js +27 -0
  204. package/dist/dispatchers/node/index.js.map +1 -0
  205. package/dist/durable-hooks.d.ts +31 -0
  206. package/dist/durable-hooks.d.ts.map +1 -0
  207. package/dist/durable-hooks.js +23 -0
  208. package/dist/durable-hooks.js.map +1 -0
  209. package/dist/fragments/internal-fragment.d.ts +186 -8
  210. package/dist/fragments/internal-fragment.d.ts.map +1 -1
  211. package/dist/fragments/internal-fragment.js +203 -38
  212. package/dist/fragments/internal-fragment.js.map +1 -1
  213. package/dist/fragments/internal-fragment.routes.js +164 -0
  214. package/dist/fragments/internal-fragment.routes.js.map +1 -0
  215. package/dist/fragments/internal-fragment.schema.d.ts +15 -0
  216. package/dist/fragments/internal-fragment.schema.d.ts.map +1 -0
  217. package/dist/fragments/internal-fragment.schema.js +39 -0
  218. package/dist/fragments/internal-fragment.schema.js.map +1 -0
  219. package/dist/hooks/durable-hooks-logger.d.ts +10 -0
  220. package/dist/hooks/durable-hooks-logger.d.ts.map +1 -0
  221. package/dist/hooks/durable-hooks-logger.js +75 -0
  222. package/dist/hooks/durable-hooks-logger.js.map +1 -0
  223. package/dist/hooks/durable-hooks-processor.d.ts +1 -0
  224. package/dist/hooks/durable-hooks-processor.js +80 -0
  225. package/dist/hooks/durable-hooks-processor.js.map +1 -0
  226. package/dist/hooks/durable-hooks-runtime.js +44 -0
  227. package/dist/hooks/durable-hooks-runtime.js.map +1 -0
  228. package/dist/hooks/hooks.d.ts +100 -1
  229. package/dist/hooks/hooks.d.ts.map +1 -1
  230. package/dist/hooks/hooks.js +254 -27
  231. package/dist/hooks/hooks.js.map +1 -1
  232. package/dist/id.d.ts +2 -2
  233. package/dist/id.js +2 -2
  234. package/dist/internal/adapter-registry.d.ts +11 -0
  235. package/dist/internal/adapter-registry.d.ts.map +1 -0
  236. package/dist/internal/adapter-registry.js +135 -0
  237. package/dist/internal/adapter-registry.js.map +1 -0
  238. package/dist/internal/outbox-state.d.ts +2 -0
  239. package/dist/internal/outbox-state.js +26 -0
  240. package/dist/internal/outbox-state.js.map +1 -0
  241. package/dist/migration-engine/auto-from-schema.d.ts +33 -0
  242. package/dist/migration-engine/auto-from-schema.d.ts.map +1 -0
  243. package/dist/migration-engine/auto-from-schema.js +223 -37
  244. package/dist/migration-engine/auto-from-schema.js.map +1 -1
  245. package/dist/migration-engine/generation-engine.d.ts +16 -10
  246. package/dist/migration-engine/generation-engine.d.ts.map +1 -1
  247. package/dist/migration-engine/generation-engine.js +86 -35
  248. package/dist/migration-engine/generation-engine.js.map +1 -1
  249. package/dist/migration-engine/shared.d.ts +113 -0
  250. package/dist/migration-engine/shared.d.ts.map +1 -0
  251. package/dist/migration-engine/shared.js.map +1 -1
  252. package/dist/mod.d.ts +20 -12
  253. package/dist/mod.d.ts.map +1 -1
  254. package/dist/mod.js +18 -12
  255. package/dist/mod.js.map +1 -1
  256. package/dist/naming/sql-naming.d.ts +19 -0
  257. package/dist/naming/sql-naming.d.ts.map +1 -0
  258. package/dist/naming/sql-naming.js +116 -0
  259. package/dist/naming/sql-naming.js.map +1 -0
  260. package/dist/outbox/outbox-builder.js +156 -0
  261. package/dist/outbox/outbox-builder.js.map +1 -0
  262. package/dist/outbox/outbox.d.ts +54 -0
  263. package/dist/outbox/outbox.d.ts.map +1 -0
  264. package/dist/outbox/outbox.js +37 -0
  265. package/dist/outbox/outbox.js.map +1 -0
  266. package/dist/query/column-defaults.js +20 -4
  267. package/dist/query/column-defaults.js.map +1 -1
  268. package/dist/query/condition-builder.d.ts +7 -1
  269. package/dist/query/condition-builder.d.ts.map +1 -1
  270. package/dist/query/condition-builder.js +5 -1
  271. package/dist/query/condition-builder.js.map +1 -1
  272. package/dist/query/cursor-client.d.ts +105 -0
  273. package/dist/query/cursor-client.d.ts.map +1 -0
  274. package/dist/query/cursor-client.js +165 -0
  275. package/dist/query/cursor-client.js.map +1 -0
  276. package/dist/query/cursor.d.ts +3 -1
  277. package/dist/query/cursor.d.ts.map +1 -1
  278. package/dist/query/cursor.js +51 -14
  279. package/dist/query/cursor.js.map +1 -1
  280. package/dist/query/db-now.d.ts +22 -0
  281. package/dist/query/db-now.d.ts.map +1 -0
  282. package/dist/query/db-now.js +35 -0
  283. package/dist/query/db-now.js.map +1 -0
  284. package/dist/query/orm/orm.js.map +1 -1
  285. package/dist/query/serialize/create-sql-serializer.js +5 -4
  286. package/dist/query/serialize/create-sql-serializer.js.map +1 -1
  287. package/dist/query/serialize/dialect/mysql-serializer.js +12 -6
  288. package/dist/query/serialize/dialect/mysql-serializer.js.map +1 -1
  289. package/dist/query/serialize/dialect/postgres-serializer.js +25 -7
  290. package/dist/query/serialize/dialect/postgres-serializer.js.map +1 -1
  291. package/dist/query/serialize/dialect/sqlite-serializer.js +60 -12
  292. package/dist/query/serialize/dialect/sqlite-serializer.js.map +1 -1
  293. package/dist/query/serialize/sql-serializer.js +2 -2
  294. package/dist/query/serialize/sql-serializer.js.map +1 -1
  295. package/dist/query/simple-query-interface.d.ts +13 -4
  296. package/dist/query/simple-query-interface.d.ts.map +1 -1
  297. package/dist/query/unit-of-work/execute-unit-of-work.d.ts +37 -2
  298. package/dist/query/unit-of-work/execute-unit-of-work.d.ts.map +1 -1
  299. package/dist/query/unit-of-work/execute-unit-of-work.js +50 -24
  300. package/dist/query/unit-of-work/execute-unit-of-work.js.map +1 -1
  301. package/dist/query/unit-of-work/unit-of-work.d.ts +92 -30
  302. package/dist/query/unit-of-work/unit-of-work.d.ts.map +1 -1
  303. package/dist/query/unit-of-work/unit-of-work.js +136 -11
  304. package/dist/query/unit-of-work/unit-of-work.js.map +1 -1
  305. package/dist/query/value-decoding.js +16 -6
  306. package/dist/query/value-decoding.js.map +1 -1
  307. package/dist/query/value-encoding.js +29 -9
  308. package/dist/query/value-encoding.js.map +1 -1
  309. package/dist/schema/create.d.ts +103 -35
  310. package/dist/schema/create.d.ts.map +1 -1
  311. package/dist/schema/create.js +172 -58
  312. package/dist/schema/create.js.map +1 -1
  313. package/dist/schema/generate-id.js +2 -2
  314. package/dist/schema/generate-id.js.map +1 -1
  315. package/dist/schema/type-conversion/create-sql-type-mapper.js +4 -3
  316. package/dist/schema/type-conversion/create-sql-type-mapper.js.map +1 -1
  317. package/dist/schema/type-conversion/dialect/sqlite.js +9 -0
  318. package/dist/schema/type-conversion/dialect/sqlite.js.map +1 -1
  319. package/dist/schema/validator.d.ts +10 -0
  320. package/dist/schema/validator.d.ts.map +1 -0
  321. package/dist/schema/validator.js +123 -0
  322. package/dist/schema/validator.js.map +1 -0
  323. package/dist/schema-output/drizzle.d.ts +30 -0
  324. package/dist/schema-output/drizzle.d.ts.map +1 -0
  325. package/dist/{adapters/drizzle/generate.js → schema-output/drizzle.js} +88 -60
  326. package/dist/schema-output/drizzle.js.map +1 -0
  327. package/dist/schema-output/prisma.d.ts +17 -0
  328. package/dist/schema-output/prisma.d.ts.map +1 -0
  329. package/dist/schema-output/prisma.js +307 -0
  330. package/dist/schema-output/prisma.js.map +1 -0
  331. package/dist/sql-driver/dialects/durable-object-dialect.js +3 -9
  332. package/dist/sql-driver/dialects/durable-object-dialect.js.map +1 -1
  333. package/dist/sql-driver/query-executor/default-query-executor.js.map +1 -1
  334. package/dist/sql-driver/query-executor/query-executor-base.js.map +1 -1
  335. package/dist/sql-driver/sql-driver-adapter.js.map +1 -1
  336. package/dist/sql-driver/sql.js.map +1 -1
  337. package/dist/sync/commands.d.ts +15 -0
  338. package/dist/sync/commands.d.ts.map +1 -0
  339. package/dist/sync/commands.js +27 -0
  340. package/dist/sync/commands.js.map +1 -0
  341. package/dist/sync/index.d.ts +4 -0
  342. package/dist/sync/index.js +4 -0
  343. package/dist/sync/read-tracking.d.ts +25 -0
  344. package/dist/sync/read-tracking.d.ts.map +1 -0
  345. package/dist/sync/read-tracking.js +148 -0
  346. package/dist/sync/read-tracking.js.map +1 -0
  347. package/dist/sync/submit.js +213 -0
  348. package/dist/sync/submit.js.map +1 -0
  349. package/dist/sync/types.d.ts +63 -0
  350. package/dist/sync/types.d.ts.map +1 -0
  351. package/dist/util/default-database-adapter.js +66 -0
  352. package/dist/util/default-database-adapter.js.map +1 -0
  353. package/dist/with-database.d.ts +3 -6
  354. package/dist/with-database.d.ts.map +1 -1
  355. package/dist/with-database.js +8 -7
  356. package/dist/with-database.js.map +1 -1
  357. package/package.json +62 -55
  358. package/src/adapters/adapters.ts +33 -26
  359. package/src/adapters/drizzle/migrate-drizzle.test.ts +99 -41
  360. package/src/adapters/drizzle/migration-parity-drizzle-kit.test.ts +601 -0
  361. package/src/adapters/drizzle/test-utils.ts +13 -8
  362. package/src/adapters/generic-sql/driver-config.ts +38 -0
  363. package/src/adapters/generic-sql/generic-sql-adapter.test.ts +10 -8
  364. package/src/adapters/generic-sql/generic-sql-adapter.ts +117 -34
  365. package/src/adapters/generic-sql/generic-sql-uow-executor.test.ts +55 -0
  366. package/src/adapters/generic-sql/generic-sql-uow-executor.ts +297 -3
  367. package/src/adapters/generic-sql/migration/adapter-migration-parity.test.ts +120 -0
  368. package/src/adapters/generic-sql/migration/cold-kysely.ts +1 -0
  369. package/src/adapters/generic-sql/migration/dialect/mysql.test.ts +27 -8
  370. package/src/adapters/generic-sql/migration/dialect/mysql.ts +47 -8
  371. package/src/adapters/generic-sql/migration/dialect/postgres.test.ts +28 -9
  372. package/src/adapters/generic-sql/migration/dialect/postgres.ts +9 -4
  373. package/src/adapters/generic-sql/migration/dialect/sqlite.test.ts +839 -8
  374. package/src/adapters/generic-sql/migration/dialect/sqlite.ts +396 -53
  375. package/src/adapters/generic-sql/migration/executor.test.ts +52 -0
  376. package/src/adapters/generic-sql/migration/executor.ts +47 -4
  377. package/src/adapters/generic-sql/migration/prepared-migrations.test.ts +238 -46
  378. package/src/adapters/generic-sql/migration/prepared-migrations.ts +21 -13
  379. package/src/adapters/generic-sql/migration/sql-generator.ts +145 -66
  380. package/src/adapters/generic-sql/query/create-sql-query-compiler.ts +11 -8
  381. package/src/adapters/generic-sql/query/cursor-utils.test.ts +272 -0
  382. package/src/adapters/generic-sql/query/cursor-utils.ts +42 -7
  383. package/src/adapters/generic-sql/query/db-now-sql.ts +49 -0
  384. package/src/adapters/generic-sql/query/generic-sql-uow-operation-compiler.test.ts +171 -35
  385. package/src/adapters/generic-sql/query/generic-sql-uow-operation-compiler.ts +53 -40
  386. package/src/adapters/generic-sql/query/select-builder.test.ts +16 -11
  387. package/src/adapters/generic-sql/query/select-builder.ts +7 -3
  388. package/src/adapters/generic-sql/query/sql-query-compiler.test.ts +75 -6
  389. package/src/adapters/generic-sql/query/sql-query-compiler.ts +129 -24
  390. package/src/adapters/generic-sql/query/where-builder.test.ts +96 -20
  391. package/src/adapters/generic-sql/query/where-builder.ts +112 -41
  392. package/src/adapters/{kysely/kysely-adapter-pglite.test.ts → generic-sql/sql-adapter-pglite-migrations.test.ts} +11 -20
  393. package/src/adapters/generic-sql/sql-adapter-pglite-pagination.test.ts +851 -0
  394. package/src/adapters/{drizzle/drizzle-adapter-pglite.test.ts → generic-sql/sql-adapter-pglite-queries.test.ts} +18 -15
  395. package/src/adapters/generic-sql/{test/generic-drizzle-adapter-sqlite3.test.ts → sql-adapter-sqlite3-driver.test.ts} +282 -14
  396. package/src/adapters/{drizzle/drizzle-adapter-sqlite3.test.ts → generic-sql/sql-adapter-sqlite3-uow.test.ts} +129 -12
  397. package/src/adapters/{kysely/kysely-adapter-sqlocal.test.ts → generic-sql/sql-adapter-sqlocal.test.ts} +9 -7
  398. package/src/adapters/generic-sql/sqlite-storage.ts +20 -0
  399. package/src/adapters/generic-sql/uow-decoder.test.ts +5 -4
  400. package/src/adapters/generic-sql/uow-decoder.ts +23 -5
  401. package/src/adapters/generic-sql/uow-encoder.test.ts +36 -3
  402. package/src/adapters/generic-sql/uow-encoder.ts +48 -13
  403. package/src/adapters/in-memory/condition-evaluator.test.ts +194 -0
  404. package/src/adapters/in-memory/condition-evaluator.ts +280 -0
  405. package/src/adapters/in-memory/errors.ts +20 -0
  406. package/src/adapters/in-memory/in-memory-adapter.ts +388 -0
  407. package/src/adapters/in-memory/in-memory-uow.mutations.test.ts +344 -0
  408. package/src/adapters/in-memory/in-memory-uow.retrieval.test.ts +255 -0
  409. package/src/adapters/in-memory/in-memory-uow.ts +1724 -0
  410. package/src/adapters/in-memory/index.ts +3 -0
  411. package/src/adapters/in-memory/options.test.ts +42 -0
  412. package/src/adapters/in-memory/options.ts +91 -0
  413. package/src/adapters/in-memory/outbox.test.ts +361 -0
  414. package/src/adapters/in-memory/reference-resolution.test.ts +51 -0
  415. package/src/adapters/in-memory/reference-resolution.ts +67 -0
  416. package/src/adapters/in-memory/sorted-array-index.test.ts +124 -0
  417. package/src/adapters/in-memory/sorted-array-index.ts +228 -0
  418. package/src/adapters/in-memory/store.test.ts +69 -0
  419. package/src/adapters/in-memory/store.ts +145 -0
  420. package/src/adapters/in-memory/value-comparison.ts +53 -0
  421. package/src/adapters/in-memory/value-normalization.test.ts +58 -0
  422. package/src/adapters/prisma/prisma-adapter-sqlite3.test.ts +1207 -0
  423. package/src/adapters/shared/from-unit-of-work-compiler.ts +159 -47
  424. package/src/adapters/shared/uow-operation-compiler.ts +28 -18
  425. package/src/adapters/sql/index.ts +12 -0
  426. package/src/browser/mod.ts +64 -0
  427. package/src/client.ts +19 -0
  428. package/src/db-fragment-definition-builder.test.ts +845 -53
  429. package/src/db-fragment-definition-builder.ts +911 -95
  430. package/src/db-fragment-instantiator.test.ts +210 -94
  431. package/src/db-fragment-integration.test.ts +17 -12
  432. package/src/dispatchers/cloudflare-do/dispatcher.ts +204 -0
  433. package/src/dispatchers/cloudflare-do/index.test.ts +206 -0
  434. package/src/dispatchers/cloudflare-do/index.ts +63 -0
  435. package/src/dispatchers/node/dispatcher.ts +112 -0
  436. package/src/dispatchers/node/index.test.ts +120 -0
  437. package/src/dispatchers/node/index.ts +50 -0
  438. package/src/durable-hooks.test.ts +80 -0
  439. package/src/durable-hooks.ts +67 -0
  440. package/src/fragments/internal-fragment.routes.test.ts +570 -0
  441. package/src/fragments/internal-fragment.routes.ts +334 -0
  442. package/src/fragments/internal-fragment.schema.ts +95 -0
  443. package/src/fragments/internal-fragment.test.ts +505 -83
  444. package/src/fragments/internal-fragment.ts +453 -70
  445. package/src/hooks/durable-hooks-logger.ts +126 -0
  446. package/src/hooks/durable-hooks-processor.pglite.test.ts +87 -0
  447. package/src/hooks/durable-hooks-processor.test.ts +282 -0
  448. package/src/hooks/durable-hooks-processor.ts +173 -0
  449. package/src/hooks/durable-hooks-runtime.test.ts +65 -0
  450. package/src/hooks/durable-hooks-runtime.ts +81 -0
  451. package/src/hooks/hooks.test.ts +455 -34
  452. package/src/hooks/hooks.ts +501 -34
  453. package/src/id.test.ts +34 -0
  454. package/src/id.ts +1 -3
  455. package/src/internal/adapter-registry.test.ts +93 -0
  456. package/src/internal/adapter-registry.ts +239 -0
  457. package/src/internal/outbox-state.ts +43 -0
  458. package/src/migration-engine/auto-from-schema.test.ts +107 -14
  459. package/src/migration-engine/auto-from-schema.ts +365 -44
  460. package/src/migration-engine/create.test.ts +4 -3
  461. package/src/migration-engine/create.ts +1 -1
  462. package/src/migration-engine/generation-engine.test.ts +292 -110
  463. package/src/migration-engine/generation-engine.ts +117 -66
  464. package/src/migration-engine/shared.ts +14 -0
  465. package/src/mod.ts +95 -39
  466. package/src/naming/sql-naming.ts +181 -0
  467. package/src/outbox/outbox-builder.ts +241 -0
  468. package/src/outbox/outbox.test.ts +424 -0
  469. package/src/outbox/outbox.ts +139 -0
  470. package/src/query/column-defaults.ts +42 -4
  471. package/src/query/condition-builder.test.ts +18 -3
  472. package/src/query/condition-builder.ts +7 -0
  473. package/src/query/cursor-client.test.ts +70 -0
  474. package/src/query/cursor-client.ts +263 -0
  475. package/src/query/cursor.test.ts +119 -20
  476. package/src/query/cursor.ts +88 -27
  477. package/src/query/db-now.ts +73 -0
  478. package/src/query/orm/orm.ts +2 -2
  479. package/src/query/query-type.test.ts +4 -3
  480. package/src/query/serialize/create-sql-serializer.ts +10 -5
  481. package/src/query/serialize/dialect/mysql-serializer.ts +13 -5
  482. package/src/query/serialize/dialect/postgres-serializer.ts +35 -5
  483. package/src/query/serialize/dialect/sqlite-serializer.test.ts +90 -3
  484. package/src/query/serialize/dialect/sqlite-serializer.ts +108 -12
  485. package/src/query/serialize/sql-serializer.ts +4 -4
  486. package/src/query/simple-query-interface.ts +15 -4
  487. package/src/query/unit-of-work/execute-unit-of-work.test.ts +372 -10
  488. package/src/query/unit-of-work/execute-unit-of-work.ts +87 -27
  489. package/src/query/unit-of-work/retry-policy.test.ts +1 -0
  490. package/src/query/unit-of-work/tx-builder.test.ts +73 -1
  491. package/src/query/unit-of-work/unit-of-work-coordinator.test.ts +17 -16
  492. package/src/query/unit-of-work/unit-of-work-types.test.ts +42 -12
  493. package/src/query/unit-of-work/unit-of-work.test.ts +196 -39
  494. package/src/query/unit-of-work/unit-of-work.ts +309 -38
  495. package/src/query/value-decoding.test.ts +63 -4
  496. package/src/query/value-decoding.ts +32 -6
  497. package/src/query/value-encoding.test.ts +86 -2
  498. package/src/query/value-encoding.ts +56 -6
  499. package/src/schema/create.test.ts +293 -47
  500. package/src/schema/create.ts +406 -70
  501. package/src/schema/generate-id.test.ts +3 -2
  502. package/src/schema/generate-id.ts +2 -2
  503. package/src/schema/serialize.test.ts +18 -5
  504. package/src/schema/type-conversion/create-sql-type-mapper.ts +8 -3
  505. package/src/schema/type-conversion/dialect/sqlite.ts +18 -0
  506. package/src/schema/type-conversion/type-mapping.test.ts +26 -1
  507. package/src/schema/validator.test.ts +199 -0
  508. package/src/schema/validator.ts +232 -0
  509. package/src/{adapters/drizzle/generate.test.ts → schema-output/drizzle.test.ts} +232 -129
  510. package/src/{adapters/drizzle/generate.ts → schema-output/drizzle.ts} +155 -99
  511. package/src/schema-output/prisma.test.ts +694 -0
  512. package/src/schema-output/prisma.ts +593 -0
  513. package/src/sql-driver/better-sqlite3.test.ts +5 -3
  514. package/src/sql-driver/dialects/durable-object-dialect.ts +3 -8
  515. package/src/sql-driver/query-executor/default-query-executor.ts +1 -1
  516. package/src/sql-driver/query-executor/query-executor-base.ts +1 -1
  517. package/src/sql-driver/query-executor/query-executor.ts +1 -1
  518. package/src/sql-driver/sql-driver-adapter.ts +2 -2
  519. package/src/sql-driver/sql.ts +2 -1
  520. package/src/sql-driver/sqlocal.test.ts +4 -2
  521. package/src/sync/commands.test.ts +39 -0
  522. package/src/sync/commands.ts +51 -0
  523. package/src/sync/conflict-checker.test.ts +450 -0
  524. package/src/sync/conflict-checker.ts +248 -0
  525. package/src/sync/index.ts +14 -0
  526. package/src/sync/plan.ts +9 -0
  527. package/src/sync/read-tracking.test.ts +177 -0
  528. package/src/sync/read-tracking.ts +287 -0
  529. package/src/sync/submit.test.ts +205 -0
  530. package/src/sync/submit.ts +328 -0
  531. package/src/sync/types.ts +80 -0
  532. package/src/util/default-database-adapter.ts +119 -0
  533. package/src/with-database.ts +20 -31
  534. package/tsconfig.json +1 -1
  535. package/tsdown.config.ts +38 -24
  536. package/vitest.config.ts +1 -0
  537. package/dist/adapters/drizzle/drizzle-adapter.d.ts +0 -20
  538. package/dist/adapters/drizzle/drizzle-adapter.d.ts.map +0 -1
  539. package/dist/adapters/drizzle/drizzle-adapter.js +0 -27
  540. package/dist/adapters/drizzle/drizzle-adapter.js.map +0 -1
  541. package/dist/adapters/drizzle/generate.d.ts +0 -30
  542. package/dist/adapters/drizzle/generate.d.ts.map +0 -1
  543. package/dist/adapters/drizzle/generate.js.map +0 -1
  544. package/dist/adapters/kysely/kysely-adapter.d.ts +0 -19
  545. package/dist/adapters/kysely/kysely-adapter.d.ts.map +0 -1
  546. package/dist/adapters/kysely/kysely-adapter.js +0 -17
  547. package/dist/adapters/kysely/kysely-adapter.js.map +0 -1
  548. package/dist/adapters/shared/table-name-mapper.d.ts +0 -12
  549. package/dist/adapters/shared/table-name-mapper.d.ts.map +0 -1
  550. package/dist/adapters/shared/table-name-mapper.js +0 -43
  551. package/dist/adapters/shared/table-name-mapper.js.map +0 -1
  552. package/dist/node_modules/.pnpm/rou3@0.7.10/node_modules/rou3/dist/index.js +0 -165
  553. package/dist/node_modules/.pnpm/rou3@0.7.10/node_modules/rou3/dist/index.js.map +0 -1
  554. package/dist/packages/fragno/dist/api/bind-services.js +0 -20
  555. package/dist/packages/fragno/dist/api/bind-services.js.map +0 -1
  556. package/dist/packages/fragno/dist/api/error.js +0 -48
  557. package/dist/packages/fragno/dist/api/error.js.map +0 -1
  558. package/dist/packages/fragno/dist/api/fragment-definition-builder.js +0 -320
  559. package/dist/packages/fragno/dist/api/fragment-definition-builder.js.map +0 -1
  560. package/dist/packages/fragno/dist/api/fragment-instantiator.js +0 -525
  561. package/dist/packages/fragno/dist/api/fragment-instantiator.js.map +0 -1
  562. package/dist/packages/fragno/dist/api/fragno-response.js +0 -73
  563. package/dist/packages/fragno/dist/api/fragno-response.js.map +0 -1
  564. package/dist/packages/fragno/dist/api/internal/response-stream.js +0 -81
  565. package/dist/packages/fragno/dist/api/internal/response-stream.js.map +0 -1
  566. package/dist/packages/fragno/dist/api/internal/route.js +0 -10
  567. package/dist/packages/fragno/dist/api/internal/route.js.map +0 -1
  568. package/dist/packages/fragno/dist/api/mutable-request-state.js +0 -97
  569. package/dist/packages/fragno/dist/api/mutable-request-state.js.map +0 -1
  570. package/dist/packages/fragno/dist/api/request-context-storage.js +0 -43
  571. package/dist/packages/fragno/dist/api/request-context-storage.js.map +0 -1
  572. package/dist/packages/fragno/dist/api/request-input-context.js +0 -118
  573. package/dist/packages/fragno/dist/api/request-input-context.js.map +0 -1
  574. package/dist/packages/fragno/dist/api/request-middleware.js +0 -83
  575. package/dist/packages/fragno/dist/api/request-middleware.js.map +0 -1
  576. package/dist/packages/fragno/dist/api/request-output-context.js +0 -119
  577. package/dist/packages/fragno/dist/api/request-output-context.js.map +0 -1
  578. package/dist/packages/fragno/dist/api/route.js +0 -17
  579. package/dist/packages/fragno/dist/api/route.js.map +0 -1
  580. package/dist/packages/fragno/dist/internal/symbols.js +0 -10
  581. package/dist/packages/fragno/dist/internal/symbols.js.map +0 -1
  582. package/dist/schema-generator/schema-generator.d.ts +0 -15
  583. package/dist/schema-generator/schema-generator.d.ts.map +0 -1
  584. package/src/adapters/drizzle/drizzle-adapter.ts +0 -39
  585. package/src/adapters/kysely/kysely-adapter.ts +0 -27
  586. package/src/adapters/shared/table-name-mapper.ts +0 -50
  587. package/src/schema-generator/schema-generator.ts +0 -12
@@ -1,40 +1,150 @@
1
- import type { AnySchema } from "./schema/create";
2
- import type { SimpleQueryInterface } from "./query/simple-query-interface";
3
- import type { DatabaseAdapter } from "./adapters/adapters";
4
- import type { IUnitOfWork } from "./query/unit-of-work/unit-of-work";
1
+ import { FragnoApiError } from "@fragno-dev/core/api";
2
+
5
3
  import type {
6
4
  RequestThisContext,
7
5
  FragnoPublicConfig,
8
- AnyFragnoInstantiatedFragment,
6
+ AnyRouteOrFactory,
7
+ FragnoRouteConfig,
8
+ BoundServices,
9
9
  } from "@fragno-dev/core";
10
10
  import {
11
11
  FragmentDefinitionBuilder,
12
12
  type FragmentDefinition,
13
13
  type ServiceConstructorFn,
14
14
  } from "@fragno-dev/core";
15
+
16
+ import type { DatabaseAdapter, DatabaseContextStorage } from "./adapters/adapters";
17
+ import type { InternalFragmentInstance } from "./fragments/internal-fragment";
18
+ import { DurableHooksLogger } from "./hooks/durable-hooks-logger";
15
19
  import {
16
- createServiceTxBuilder,
17
- createHandlerTxBuilder,
18
- ServiceTxBuilder,
19
- HandlerTxBuilder,
20
- type ExecuteTxOptions,
21
- } from "./query/unit-of-work/execute-unit-of-work";
20
+ getDurableHooksRuntimeByConfig,
21
+ getDurableHooksRuntimeByNamespace,
22
+ registerDurableHooksRuntime,
23
+ } from "./hooks/durable-hooks-runtime";
22
24
  import {
23
25
  prepareHookMutations,
24
- processHooks,
25
26
  type HooksMap,
26
27
  type HookFn,
27
28
  type HookContext,
29
+ type HookProcessorConfig,
30
+ type HookNotifier,
31
+ type HookNotifyContext,
32
+ type DurableHooksProcessingOptions,
33
+ createDurableHooksRunner,
28
34
  } from "./hooks/hooks";
29
- import type { InternalFragmentInstance } from "./fragments/internal-fragment";
35
+ import { sanitizeNamespace } from "./naming/sql-naming";
36
+ import type { SimpleQueryInterface } from "./query/simple-query-interface";
37
+ import {
38
+ createServiceTxBuilder,
39
+ createHandlerTxBuilder,
40
+ ServiceTxBuilder,
41
+ HandlerTxBuilder,
42
+ type AwaitedPromisesInObject,
43
+ type ExtractServiceFinalResults,
44
+ type ExecuteTxOptions,
45
+ type TxResult,
46
+ } from "./query/unit-of-work/execute-unit-of-work";
47
+ import type { IUnitOfWork } from "./query/unit-of-work/unit-of-work";
48
+ import type { AnySchema } from "./schema/create";
49
+ import type { SyncCommandRegistry, SyncCommandTargetRegistration } from "./sync/types";
50
+ import { resolveDatabaseAdapter } from "./util/default-database-adapter";
51
+ type RegistrySchemaInfo = {
52
+ name: string;
53
+ namespace: string | null;
54
+ version: number;
55
+ tables: string[];
56
+ };
57
+
58
+ type RegistryFragmentMeta = {
59
+ name: string;
60
+ mountRoute: string;
61
+ };
62
+
63
+ type ExtractServiceFinalResultOrSingle<T> = T extends readonly (
64
+ | TxResult<unknown, unknown>
65
+ | undefined
66
+ )[]
67
+ ? AwaitedPromisesInObject<ExtractServiceFinalResults<T>>
68
+ : T extends undefined
69
+ ? undefined
70
+ : AwaitedPromisesInObject<ExtractServiceFinalResults<readonly [T]>>[0];
71
+
72
+ type RegistryResolver = {
73
+ getRegistryForAdapterSync: <TUOWConfig>(adapter: DatabaseAdapter<TUOWConfig>) => {
74
+ registerSchema: (
75
+ schema: RegistrySchemaInfo,
76
+ fragment: RegistryFragmentMeta,
77
+ options?: { outboxEnabled?: boolean },
78
+ ) => void;
79
+ registerSyncCommands: (registration: SyncCommandTargetRegistration) => void;
80
+ };
81
+ getInternalFragment: <TUOWConfig>(
82
+ adapter: DatabaseAdapter<TUOWConfig>,
83
+ ) => InternalFragmentInstance;
84
+ };
85
+
86
+ type HooksFactoryContext<TConfig> = {
87
+ config: TConfig;
88
+ options: FragnoPublicConfigWithDatabase;
89
+ deps: unknown;
90
+ services: unknown;
91
+ serviceDeps: unknown;
92
+ };
93
+
94
+ type AnyHttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "HEAD" | "OPTIONS";
95
+
96
+ type AnyFragnoRouteConfig = FragnoRouteConfig<
97
+ AnyHttpMethod,
98
+ string,
99
+ undefined,
100
+ undefined,
101
+ string,
102
+ string,
103
+ RequestThisContext
104
+ >;
30
105
 
31
106
  /**
32
- * Extended FragnoPublicConfig that includes a database adapter.
33
- * Use this type when creating fragments with database support.
107
+ * Extended FragnoPublicConfig for database fragments.
108
+ * If databaseAdapter is omitted and better-sqlite3 is available, a default SQLite adapter is used.
34
109
  */
35
110
  export type FragnoPublicConfigWithDatabase = FragnoPublicConfig & {
36
111
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
37
- databaseAdapter: DatabaseAdapter<any>;
112
+ databaseAdapter?: DatabaseAdapter<any>;
113
+ /**
114
+ * Optional guard to limit database roundtrips per request (primarily for tests).
115
+ * When enabled, retrieve-only and mutate-only handlerTx().execute() calls are
116
+ * counted separately (one of each by default).
117
+ * Applied only for route handlers (not inContext).
118
+ */
119
+ dbRoundtripGuard?: boolean | DbRoundtripGuardConfig;
120
+ /**
121
+ * Optional outbox configuration for this fragment.
122
+ */
123
+ outbox?: {
124
+ enabled?: boolean;
125
+ };
126
+ /**
127
+ * Optional durable hooks processing configuration.
128
+ */
129
+ durableHooks?: DurableHooksProcessingOptions;
130
+ /**
131
+ * Optional override for database namespace. If provided (including null), it is used as-is
132
+ * without sanitization — the caller is responsible for providing a valid namespace.
133
+ * When omitted, defaults to a sanitized version of schema.name.
134
+ */
135
+ databaseNamespace?: string | null;
136
+ };
137
+
138
+ /**
139
+ * Configuration for limiting database roundtrips per request.
140
+ */
141
+ export type DbRoundtripGuardConfig = {
142
+ /**
143
+ * Maximum allowed retrieve-only and mutate-only handlerTx().execute() calls per request.
144
+ * Each type is tracked separately.
145
+ * Defaults to 1 when the guard is enabled.
146
+ */
147
+ maxRoundtrips?: number;
38
148
  };
39
149
 
40
150
  /**
@@ -43,9 +153,9 @@ export type FragnoPublicConfigWithDatabase = FragnoPublicConfig & {
43
153
  */
44
154
  export type ImplicitDatabaseDependencies<TSchema extends AnySchema> = {
45
155
  /**
46
- * Database query engine for the fragment's schema.
156
+ * Database adapter instance.
47
157
  */
48
- db: SimpleQueryInterface<TSchema>;
158
+ databaseAdapter: DatabaseAdapter<any>; // eslint-disable-line @typescript-eslint/no-explicit-any
49
159
  /**
50
160
  * The schema definition for this fragment.
51
161
  */
@@ -53,7 +163,7 @@ export type ImplicitDatabaseDependencies<TSchema extends AnySchema> = {
53
163
  /**
54
164
  * The database namespace for this fragment.
55
165
  */
56
- namespace: string;
166
+ namespace: string | null;
57
167
  /**
58
168
  * Create a new Unit of Work for database operations.
59
169
  */
@@ -122,19 +232,34 @@ export type DatabaseHandlerContext<THooks extends HooksMap = {}> = RequestThisCo
122
232
  handlerTx(
123
233
  options?: Omit<ExecuteTxOptions, "createUnitOfWork">,
124
234
  ): HandlerTxBuilder<readonly [], [], [], unknown, unknown, false, false, false, false, THooks>;
235
+
236
+ /**
237
+ * Execute multiple service calls in a handler context and return their final results.
238
+ */
239
+ callServices<
240
+ TServiceCalls extends
241
+ | TxResult<unknown, unknown>
242
+ | undefined
243
+ | readonly (TxResult<unknown, unknown> | undefined)[],
244
+ >(
245
+ /**
246
+ * Factory to create service calls inside the active context.
247
+ */
248
+ serviceCalls: () => TServiceCalls,
249
+ ): Promise<ExtractServiceFinalResultOrSingle<TServiceCalls>>;
125
250
  };
126
251
 
127
252
  /**
128
253
  * Database fragment context provided to user callbacks.
129
254
  */
130
- export type DatabaseFragmentContext<TSchema extends AnySchema> = {
255
+ export type DatabaseFragmentContext = {
131
256
  /**
132
257
  * Database adapter instance.
133
258
  */
134
259
  databaseAdapter: DatabaseAdapter<any>; // eslint-disable-line @typescript-eslint/no-explicit-any
135
- /**
136
- * ORM query engine for the fragment's schema.
137
- */
260
+ };
261
+
262
+ type DatabaseFragmentContextInternal<TSchema extends AnySchema> = DatabaseFragmentContext & {
138
263
  db: SimpleQueryInterface<TSchema>;
139
264
  };
140
265
 
@@ -145,19 +270,310 @@ export type DatabaseFragmentContext<TSchema extends AnySchema> = {
145
270
  function createDatabaseContext<TSchema extends AnySchema>(
146
271
  options: FragnoPublicConfigWithDatabase,
147
272
  schema: TSchema,
273
+ ): DatabaseFragmentContextInternal<TSchema> {
274
+ const databaseAdapter = resolveDatabaseAdapter(options, schema);
275
+
276
+ const namespace = resolveDatabaseNamespace(options, schema);
277
+ const db = databaseAdapter.createQueryEngine(schema, namespace);
278
+
279
+ return { databaseAdapter, db };
280
+ }
281
+
282
+ function resolveDatabaseNamespace<TSchema extends AnySchema>(
283
+ options: FragnoPublicConfigWithDatabase,
284
+ schema: TSchema,
285
+ ): string | null {
286
+ const hasOverride = options.databaseNamespace !== undefined;
287
+ return hasOverride ? (options.databaseNamespace ?? null) : sanitizeNamespace(schema.name);
288
+ }
289
+
290
+ function resolveMountRoute(name: string, mountRoute?: string): string {
291
+ const resolved = mountRoute ?? `/api/${name}`;
292
+ return resolved.endsWith("/") ? resolved.slice(0, -1) : resolved;
293
+ }
294
+
295
+ const dbRoundtripGuardStateSymbol = Symbol("fragno-db-roundtrip-guard");
296
+ const requestSourceSymbol = Symbol.for("fragno-request-source");
297
+ const requestRouteSymbol = Symbol.for("fragno-request-route");
298
+ const requestWaitUntilSymbol = Symbol.for("fragno-request-wait-until");
299
+ const roundtripGuardDocsUrl = "https://fragno.dev/docs/fragno/for-library-authors/rules-of-fragno";
300
+
301
+ type DbRoundtripGuardState = {
302
+ retrieveCount: number;
303
+ mutateCount: number;
304
+ maxRoundtrips: number;
305
+ };
306
+
307
+ type DurableHooksLogContext = HookNotifyContext;
308
+
309
+ type AnyHandlerTxBuilder = HandlerTxBuilder<
310
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
311
+ any,
312
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
313
+ any,
314
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
315
+ any,
316
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
317
+ any,
318
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
319
+ any,
320
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
321
+ any,
322
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
323
+ any,
324
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
325
+ any,
326
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
327
+ any,
328
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
329
+ any
330
+ >;
331
+
332
+ function wrapHandlerTxBuilderWithRoundtripGuard<
333
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
334
+ TBuilder extends HandlerTxBuilder<any, any, any, any, any, any, any, any, any, any>,
335
+ >(builder: TBuilder, onExecute: () => void): TBuilder {
336
+ const wrappedBuilders = new WeakSet<AnyHandlerTxBuilder>();
337
+
338
+ const applyExecuteGuard = (target: TBuilder): TBuilder => {
339
+ if (wrappedBuilders.has(target)) {
340
+ return target;
341
+ }
342
+ wrappedBuilders.add(target);
343
+ const execute = target.execute.bind(target);
344
+ target.execute = () => {
345
+ onExecute();
346
+ return execute();
347
+ };
348
+ return target;
349
+ };
350
+
351
+ const wrap = (target: TBuilder): TBuilder => {
352
+ const guarded = applyExecuteGuard(target);
353
+ return new Proxy(guarded, {
354
+ get(obj, prop, receiver) {
355
+ const value = Reflect.get(obj, prop, receiver);
356
+ if (typeof value !== "function") {
357
+ return value;
358
+ }
359
+ if (prop === "execute") {
360
+ return value;
361
+ }
362
+ return (...args: unknown[]) => {
363
+ const result = value.apply(obj, args);
364
+ if (result instanceof HandlerTxBuilder) {
365
+ return wrap(result as TBuilder);
366
+ }
367
+ return result;
368
+ };
369
+ },
370
+ }) as TBuilder;
371
+ };
372
+
373
+ return wrap(builder);
374
+ }
375
+
376
+ function buildDurableHooksLogContext(context?: DurableHooksLogContext) {
377
+ const logContext: Record<string, unknown> = {};
378
+ if (context?.route !== undefined) {
379
+ logContext["route"] = context.route;
380
+ }
381
+ if (context?.source !== undefined) {
382
+ logContext["source"] = context.source;
383
+ }
384
+ return logContext;
385
+ }
386
+
387
+ function notifyDurableHooks(
388
+ notifier: HookNotifier,
148
389
  namespace: string,
149
- ): DatabaseFragmentContext<TSchema> {
150
- const databaseAdapter = options.databaseAdapter;
390
+ notifyContext: HookNotifyContext,
391
+ logContextFields: Record<string, unknown>,
392
+ options?: { crossNamespace?: boolean },
393
+ ) {
394
+ const crossNamespace = options?.crossNamespace ?? false;
395
+ const suffix = crossNamespace ? " (cross-namespace)" : "";
396
+ const notifyStart = Date.now();
397
+ DurableHooksLogger.debug(`Durable hooks notify requested${suffix}`, {
398
+ namespace,
399
+ fields: logContextFields,
400
+ });
401
+ const notifyPromise = Promise.resolve()
402
+ .then(() => notifier.notify(notifyContext))
403
+ .then((processed) => {
404
+ DurableHooksLogger.debug(`Durable hooks notify completed${suffix}`, {
405
+ namespace,
406
+ fields: {
407
+ result: processed === undefined ? null : processed,
408
+ ms: Date.now() - notifyStart,
409
+ ...logContextFields,
410
+ },
411
+ });
412
+ })
413
+ .catch((error) => {
414
+ DurableHooksLogger.error(`Durable hooks notify failed${suffix}`, {
415
+ namespace,
416
+ fields: {
417
+ error: DurableHooksLogger.toErrorMessage(error),
418
+ ...logContextFields,
419
+ },
420
+ });
421
+ });
422
+
423
+ if (notifyContext.waitUntil) {
424
+ notifyContext.waitUntil(notifyPromise);
425
+ return;
426
+ }
427
+ void notifyPromise;
428
+ }
151
429
 
152
- if (!databaseAdapter) {
153
- throw new Error(
154
- "Database fragment requires a database adapter to be provided in options.databaseAdapter",
430
+ function notifyDurableHooksAfterMutate<THooks extends HooksMap>({
431
+ uow,
432
+ hooksConfig,
433
+ internalFragment,
434
+ autoSchedule,
435
+ planMode,
436
+ logContext,
437
+ }: {
438
+ uow: IUnitOfWork;
439
+ hooksConfig?: HookProcessorConfig<THooks>;
440
+ internalFragment?: InternalFragmentInstance;
441
+ autoSchedule: boolean;
442
+ planMode: boolean;
443
+ logContext?: DurableHooksLogContext;
444
+ }) {
445
+ if (planMode) {
446
+ return;
447
+ }
448
+
449
+ const notifyContext: HookNotifyContext = {
450
+ source: logContext?.source ?? "request",
451
+ route: logContext?.route,
452
+ waitUntil: logContext?.waitUntil,
453
+ };
454
+
455
+ const logContextFields = buildDurableHooksLogContext(logContext);
456
+ const triggeredHooks = uow.getTriggeredHooks();
457
+ const ownNamespaceTriggeredCount = hooksConfig
458
+ ? triggeredHooks.filter((hook) => hook.namespace === hooksConfig.namespace).length
459
+ : 0;
460
+
461
+ if (hooksConfig?.notifier && autoSchedule && ownNamespaceTriggeredCount > 0) {
462
+ notifyDurableHooks(
463
+ hooksConfig.notifier,
464
+ hooksConfig.namespace,
465
+ notifyContext,
466
+ logContextFields,
155
467
  );
468
+ } else if (hooksConfig && !autoSchedule && ownNamespaceTriggeredCount > 0) {
469
+ DurableHooksLogger.debug("Durable hooks notify skipped (autoSchedule=false)", {
470
+ namespace: hooksConfig.namespace,
471
+ fields: {
472
+ queued: ownNamespaceTriggeredCount,
473
+ ...logContextFields,
474
+ },
475
+ });
476
+ } else if (hooksConfig && !hooksConfig.notifier && ownNamespaceTriggeredCount > 0) {
477
+ DurableHooksLogger.debug("Durable hooks notify skipped (notifier missing)", {
478
+ namespace: hooksConfig.namespace,
479
+ fields: {
480
+ queued: ownNamespaceTriggeredCount,
481
+ ...logContextFields,
482
+ },
483
+ });
156
484
  }
157
485
 
158
- const db = databaseAdapter.createQueryEngine(schema, namespace);
486
+ if (triggeredHooks.length === 0) {
487
+ return;
488
+ }
159
489
 
160
- return { databaseAdapter, db };
490
+ const namespaces = new Set<string>();
491
+ const triggeredCountByNamespace = new Map<string, number>();
492
+ for (const hook of triggeredHooks) {
493
+ namespaces.add(hook.namespace);
494
+ triggeredCountByNamespace.set(
495
+ hook.namespace,
496
+ (triggeredCountByNamespace.get(hook.namespace) ?? 0) + 1,
497
+ );
498
+ }
499
+ if (hooksConfig) {
500
+ namespaces.delete(hooksConfig.namespace);
501
+ }
502
+
503
+ for (const namespace of namespaces) {
504
+ if (!internalFragment) {
505
+ DurableHooksLogger.debug("Durable hooks notifier missing scope for namespace", {
506
+ namespace,
507
+ fields: logContextFields,
508
+ });
509
+ continue;
510
+ }
511
+
512
+ const runtime = getDurableHooksRuntimeByNamespace(namespace, internalFragment);
513
+ if (!runtime) {
514
+ DurableHooksLogger.debug("Durable hooks notifier missing for namespace", {
515
+ namespace,
516
+ fields: logContextFields,
517
+ });
518
+ continue;
519
+ }
520
+ if (runtime.config.autoSchedule === false) {
521
+ DurableHooksLogger.debug("Durable hooks notify skipped (autoSchedule=false)", {
522
+ namespace,
523
+ fields: {
524
+ queued: triggeredCountByNamespace.get(namespace) ?? 0,
525
+ ...logContextFields,
526
+ },
527
+ });
528
+ continue;
529
+ }
530
+ const notifier = runtime.config.notifier;
531
+ if (!notifier) {
532
+ DurableHooksLogger.debug("Durable hooks notifier missing for namespace", {
533
+ namespace,
534
+ fields: logContextFields,
535
+ });
536
+ continue;
537
+ }
538
+ notifyDurableHooks(notifier, namespace, notifyContext, logContextFields, {
539
+ crossNamespace: true,
540
+ });
541
+ }
542
+ }
543
+
544
+ function resolveDbRoundtripGuard(
545
+ options: FragnoPublicConfigWithDatabase,
546
+ ): DbRoundtripGuardState | null {
547
+ const guard = options.dbRoundtripGuard;
548
+ if (!guard) {
549
+ return null;
550
+ }
551
+
552
+ if (guard === true) {
553
+ return { retrieveCount: 0, mutateCount: 0, maxRoundtrips: 1 };
554
+ }
555
+
556
+ return { retrieveCount: 0, mutateCount: 0, maxRoundtrips: guard.maxRoundtrips ?? 1 };
557
+ }
558
+
559
+ function getDbRoundtripGuardState(
560
+ storage: DatabaseContextStorage,
561
+ guard: DbRoundtripGuardState,
562
+ ): DbRoundtripGuardState {
563
+ const storageWithGuard = storage as DatabaseContextStorage & {
564
+ [dbRoundtripGuardStateSymbol]?: DbRoundtripGuardState;
565
+ };
566
+ if (!storageWithGuard[dbRoundtripGuardStateSymbol]) {
567
+ storageWithGuard[dbRoundtripGuardStateSymbol] = { ...guard };
568
+ } else {
569
+ storageWithGuard[dbRoundtripGuardStateSymbol]!.maxRoundtrips = guard.maxRoundtrips;
570
+ }
571
+ return storageWithGuard[dbRoundtripGuardStateSymbol]!;
572
+ }
573
+
574
+ function isRouteRequest(storage: DatabaseContextStorage): boolean {
575
+ const source = (storage as Record<symbol, unknown>)[requestSourceSymbol];
576
+ return source === "route";
161
577
  }
162
578
 
163
579
  /**
@@ -171,7 +587,7 @@ export type DatabaseRequestStorage = {
171
587
  * Builder for database fragments that wraps the core fragment builder
172
588
  * and provides database-specific functionality.
173
589
  *
174
- * Database fragments always require FragnoPublicConfigWithDatabase (which includes databaseAdapter).
590
+ * Database fragments use FragnoPublicConfigWithDatabase and default the adapter when possible.
175
591
  */
176
592
  export class DatabaseFragmentDefinitionBuilder<
177
593
  TSchema extends AnySchema,
@@ -184,7 +600,7 @@ export class DatabaseFragmentDefinitionBuilder<
184
600
  THooks extends HooksMap = {},
185
601
  TServiceThisContext extends RequestThisContext = DatabaseHandlerContext,
186
602
  THandlerThisContext extends RequestThisContext = DatabaseHandlerContext,
187
- TLinkedFragments extends Record<string, AnyFragnoInstantiatedFragment> = {},
603
+ TInternalRoutes extends readonly AnyRouteOrFactory[] = readonly [],
188
604
  > {
189
605
  // Store the base builder - we'll replace its storage and context setup when building
190
606
  #baseBuilder: FragmentDefinitionBuilder<
@@ -198,11 +614,12 @@ export class DatabaseFragmentDefinitionBuilder<
198
614
  TServiceThisContext,
199
615
  THandlerThisContext,
200
616
  DatabaseRequestStorage,
201
- TLinkedFragments
617
+ TInternalRoutes
202
618
  >;
203
619
  #schema: TSchema;
204
- #namespace: string;
205
- #hooksFactory?: (context: { config: TConfig; options: FragnoPublicConfigWithDatabase }) => THooks;
620
+ #hooksFactory?: (context: HooksFactoryContext<TConfig>) => THooks;
621
+ #syncRegistry?: SyncCommandRegistry;
622
+ #registryResolver?: RegistryResolver;
206
623
 
207
624
  constructor(
208
625
  baseBuilder: FragmentDefinitionBuilder<
@@ -216,30 +633,28 @@ export class DatabaseFragmentDefinitionBuilder<
216
633
  TServiceThisContext,
217
634
  THandlerThisContext,
218
635
  DatabaseRequestStorage,
219
- TLinkedFragments
636
+ TInternalRoutes
220
637
  >,
221
638
  schema: TSchema,
222
- namespace?: string,
223
- hooksFactory?: (context: {
224
- config: TConfig;
225
- options: FragnoPublicConfigWithDatabase;
226
- }) => THooks,
639
+ hooksFactory?: (context: HooksFactoryContext<TConfig>) => THooks,
640
+ syncRegistry?: SyncCommandRegistry,
641
+ registryResolver?: RegistryResolver,
227
642
  ) {
228
643
  this.#baseBuilder = baseBuilder;
229
644
  this.#schema = schema;
230
- this.#namespace = namespace ?? baseBuilder.name;
231
645
  this.#hooksFactory = hooksFactory;
646
+ this.#syncRegistry = syncRegistry;
647
+ this.#registryResolver = registryResolver;
232
648
  }
233
649
 
234
650
  /**
235
651
  * Define dependencies for this database fragment.
236
- * The context includes database adapter and ORM instance.
652
+ * The context includes the database adapter.
237
653
  */
238
654
  withDependencies<TNewDeps>(
239
655
  fn: (context: {
240
656
  config: TConfig;
241
657
  options: FragnoPublicConfigWithDatabase;
242
- db: SimpleQueryInterface<TSchema>;
243
658
  databaseAdapter: DatabaseAdapter<any>; // eslint-disable-line @typescript-eslint/no-explicit-any
244
659
  }) => TNewDeps,
245
660
  ): DatabaseFragmentDefinitionBuilder<
@@ -253,26 +668,26 @@ export class DatabaseFragmentDefinitionBuilder<
253
668
  THooks,
254
669
  TServiceThisContext,
255
670
  THandlerThisContext,
256
- TLinkedFragments
671
+ TInternalRoutes
257
672
  > {
258
673
  // Wrap user function to inject DB context
259
674
  const wrappedFn = (context: { config: TConfig; options: FragnoPublicConfigWithDatabase }) => {
260
- const dbContext = createDatabaseContext(context.options, this.#schema, this.#namespace);
675
+ const dbContext = createDatabaseContext(context.options, this.#schema);
676
+ const namespace = resolveDatabaseNamespace(context.options, this.#schema);
261
677
 
262
678
  // Call user function with enriched context
263
679
  const userDeps = fn({
264
680
  config: context.config,
265
681
  options: context.options,
266
- db: dbContext.db,
267
682
  databaseAdapter: dbContext.databaseAdapter,
268
683
  });
269
684
 
270
685
  // Create implicit dependencies
271
686
  const createUow = () => dbContext.db.createUnitOfWork();
272
687
  const implicitDeps: ImplicitDatabaseDependencies<TSchema> = {
273
- db: dbContext.db,
688
+ databaseAdapter: dbContext.databaseAdapter,
274
689
  schema: this.#schema,
275
- namespace: this.#namespace,
690
+ namespace,
276
691
  createUnitOfWork: createUow,
277
692
  };
278
693
 
@@ -288,8 +703,9 @@ export class DatabaseFragmentDefinitionBuilder<
288
703
  return new DatabaseFragmentDefinitionBuilder(
289
704
  newBaseBuilder,
290
705
  this.#schema,
291
- this.#namespace,
292
706
  this.#hooksFactory,
707
+ this.#syncRegistry,
708
+ this.#registryResolver,
293
709
  );
294
710
  }
295
711
 
@@ -314,15 +730,16 @@ export class DatabaseFragmentDefinitionBuilder<
314
730
  THooks,
315
731
  TServiceThisContext,
316
732
  THandlerThisContext,
317
- TLinkedFragments
733
+ TInternalRoutes
318
734
  > {
319
735
  const newBaseBuilder = this.#baseBuilder.providesBaseService<TNewService>(fn);
320
736
 
321
737
  return new DatabaseFragmentDefinitionBuilder(
322
738
  newBaseBuilder,
323
739
  this.#schema,
324
- this.#namespace,
325
740
  this.#hooksFactory,
741
+ this.#syncRegistry,
742
+ this.#registryResolver,
326
743
  );
327
744
  }
328
745
 
@@ -348,7 +765,7 @@ export class DatabaseFragmentDefinitionBuilder<
348
765
  THooks,
349
766
  TServiceThisContext,
350
767
  THandlerThisContext,
351
- TLinkedFragments
768
+ TInternalRoutes
352
769
  > {
353
770
  const newBaseBuilder = this.#baseBuilder.providesService<TServiceName, TService>(
354
771
  serviceName,
@@ -358,8 +775,9 @@ export class DatabaseFragmentDefinitionBuilder<
358
775
  return new DatabaseFragmentDefinitionBuilder(
359
776
  newBaseBuilder,
360
777
  this.#schema,
361
- this.#namespace,
362
778
  this.#hooksFactory,
779
+ this.#syncRegistry,
780
+ this.#registryResolver,
363
781
  );
364
782
  }
365
783
 
@@ -392,7 +810,7 @@ export class DatabaseFragmentDefinitionBuilder<
392
810
  THooks,
393
811
  TServiceThisContext,
394
812
  THandlerThisContext,
395
- TLinkedFragments
813
+ TInternalRoutes
396
814
  > {
397
815
  const newBaseBuilder = this.#baseBuilder.providesPrivateService<TServiceName, TService>(
398
816
  serviceName,
@@ -402,8 +820,9 @@ export class DatabaseFragmentDefinitionBuilder<
402
820
  return new DatabaseFragmentDefinitionBuilder(
403
821
  newBaseBuilder,
404
822
  this.#schema,
405
- this.#namespace,
406
823
  this.#hooksFactory,
824
+ this.#syncRegistry,
825
+ this.#registryResolver,
407
826
  );
408
827
  }
409
828
 
@@ -428,6 +847,9 @@ export class DatabaseFragmentDefinitionBuilder<
428
847
  fn: (context: {
429
848
  config: TConfig;
430
849
  options: FragnoPublicConfigWithDatabase;
850
+ deps: TDeps;
851
+ services: BoundServices<TBaseServices & TServices>;
852
+ serviceDeps: TServiceDependencies;
431
853
  defineHook: <TPayload>(
432
854
  hook: (this: HookContext, payload: TPayload) => void | Promise<void>,
433
855
  ) => HookFn<TPayload>;
@@ -443,7 +865,7 @@ export class DatabaseFragmentDefinitionBuilder<
443
865
  TNewHooks,
444
866
  DatabaseServiceContext<TNewHooks>,
445
867
  THandlerThisContext,
446
- TLinkedFragments
868
+ TInternalRoutes
447
869
  > {
448
870
  const defineHook = <TPayload>(
449
871
  hook: (this: HookContext, payload: TPayload) => void | Promise<void>,
@@ -452,13 +874,13 @@ export class DatabaseFragmentDefinitionBuilder<
452
874
  };
453
875
 
454
876
  // Store the hooks factory - it will be called in build() with config/options
455
- const hooksFactory = (context: {
456
- config: TConfig;
457
- options: FragnoPublicConfigWithDatabase;
458
- }) => {
877
+ const hooksFactory = (context: HooksFactoryContext<TConfig>) => {
459
878
  return fn({
460
879
  config: context.config,
461
880
  options: context.options,
881
+ deps: context.deps as TDeps,
882
+ services: context.services as BoundServices<TBaseServices & TServices>,
883
+ serviceDeps: context.serviceDeps as TServiceDependencies,
462
884
  defineHook,
463
885
  });
464
886
  };
@@ -468,7 +890,9 @@ export class DatabaseFragmentDefinitionBuilder<
468
890
  const newBuilder = new DatabaseFragmentDefinitionBuilder(
469
891
  this.#baseBuilder,
470
892
  this.#schema,
471
- this.#namespace,
893
+ this.#hooksFactory,
894
+ this.#syncRegistry,
895
+ this.#registryResolver,
472
896
  ) as unknown as DatabaseFragmentDefinitionBuilder<
473
897
  TSchema,
474
898
  TConfig,
@@ -480,7 +904,7 @@ export class DatabaseFragmentDefinitionBuilder<
480
904
  TNewHooks,
481
905
  DatabaseServiceContext<TNewHooks>,
482
906
  THandlerThisContext,
483
- TLinkedFragments
907
+ TInternalRoutes
484
908
  >;
485
909
 
486
910
  newBuilder.#hooksFactory = hooksFactory;
@@ -488,6 +912,39 @@ export class DatabaseFragmentDefinitionBuilder<
488
912
  return newBuilder;
489
913
  }
490
914
 
915
+ /**
916
+ * Register sync command definitions for this fragment.
917
+ */
918
+ withSyncCommands(
919
+ registry: SyncCommandRegistry,
920
+ ): DatabaseFragmentDefinitionBuilder<
921
+ TSchema,
922
+ TConfig,
923
+ TDeps,
924
+ TBaseServices,
925
+ TServices,
926
+ TServiceDependencies,
927
+ TPrivateServices,
928
+ THooks,
929
+ TServiceThisContext,
930
+ THandlerThisContext,
931
+ TInternalRoutes
932
+ > {
933
+ if (registry.schemaName !== this.#schema.name) {
934
+ throw new Error(
935
+ `Sync command registry schema name "${registry.schemaName}" does not match fragment schema "${this.#schema.name}".`,
936
+ );
937
+ }
938
+
939
+ return new DatabaseFragmentDefinitionBuilder(
940
+ this.#baseBuilder,
941
+ this.#schema,
942
+ this.#hooksFactory,
943
+ registry,
944
+ this.#registryResolver,
945
+ );
946
+ }
947
+
491
948
  /**
492
949
  * Declare that this fragment uses a required service provided by the runtime.
493
950
  * Delegates to the base builder.
@@ -505,15 +962,16 @@ export class DatabaseFragmentDefinitionBuilder<
505
962
  THooks,
506
963
  TServiceThisContext,
507
964
  THandlerThisContext,
508
- TLinkedFragments
965
+ TInternalRoutes
509
966
  > {
510
967
  const newBaseBuilder = this.#baseBuilder.usesService<TServiceName, TService>(serviceName);
511
968
 
512
969
  return new DatabaseFragmentDefinitionBuilder(
513
970
  newBaseBuilder,
514
971
  this.#schema,
515
- this.#namespace,
516
972
  this.#hooksFactory,
973
+ this.#syncRegistry,
974
+ this.#registryResolver,
517
975
  );
518
976
  }
519
977
 
@@ -534,7 +992,7 @@ export class DatabaseFragmentDefinitionBuilder<
534
992
  THooks,
535
993
  TServiceThisContext,
536
994
  THandlerThisContext,
537
- TLinkedFragments
995
+ TInternalRoutes
538
996
  > {
539
997
  const newBaseBuilder = this.#baseBuilder.usesOptionalService<TServiceName, TService>(
540
998
  serviceName,
@@ -543,8 +1001,9 @@ export class DatabaseFragmentDefinitionBuilder<
543
1001
  return new DatabaseFragmentDefinitionBuilder(
544
1002
  newBaseBuilder,
545
1003
  this.#schema,
546
- this.#namespace,
547
1004
  this.#hooksFactory,
1005
+ this.#syncRegistry,
1006
+ this.#registryResolver,
548
1007
  );
549
1008
  }
550
1009
 
@@ -564,7 +1023,7 @@ export class DatabaseFragmentDefinitionBuilder<
564
1023
  DatabaseServiceContext<THooks>,
565
1024
  DatabaseHandlerContext<THooks>,
566
1025
  DatabaseRequestStorage,
567
- TLinkedFragments
1026
+ TInternalRoutes
568
1027
  > {
569
1028
  const baseDef = this.#baseBuilder.build();
570
1029
 
@@ -594,12 +1053,44 @@ export class DatabaseFragmentDefinitionBuilder<
594
1053
  }
595
1054
  }
596
1055
 
597
- const { db } = createDatabaseContext(context.options, this.#schema, this.#namespace);
1056
+ const dbContext = createDatabaseContext(context.options, this.#schema);
1057
+ const { db } = dbContext;
1058
+ const namespace = resolveDatabaseNamespace(context.options, this.#schema);
1059
+ const dryRun = process.env["FRAGNO_INIT_DRY_RUN"] === "true";
1060
+ const isInternalFragment = baseDef.name === "$fragno-internal-fragment";
1061
+
1062
+ if (!dryRun && !isInternalFragment && this.#registryResolver) {
1063
+ const registry = this.#registryResolver.getRegistryForAdapterSync(
1064
+ dbContext.databaseAdapter,
1065
+ );
1066
+ const outboxEnabled = context.options.outbox?.enabled ?? false;
1067
+ registry.registerSchema(
1068
+ {
1069
+ name: this.#schema.name,
1070
+ namespace,
1071
+ version: this.#schema.version,
1072
+ tables: Object.keys(this.#schema.tables).sort(),
1073
+ },
1074
+ {
1075
+ name: baseDef.name,
1076
+ mountRoute: resolveMountRoute(baseDef.name, context.options.mountRoute),
1077
+ },
1078
+ { outboxEnabled },
1079
+ );
1080
+ if (this.#syncRegistry) {
1081
+ registry.registerSyncCommands({
1082
+ fragmentName: baseDef.name,
1083
+ schemaName: this.#syncRegistry.schemaName,
1084
+ namespace,
1085
+ commands: this.#syncRegistry.commands,
1086
+ });
1087
+ }
1088
+ }
598
1089
 
599
1090
  const implicitDeps: ImplicitDatabaseDependencies<TSchema> = {
600
- db,
1091
+ databaseAdapter: dbContext.databaseAdapter,
601
1092
  schema: this.#schema,
602
- namespace: this.#namespace,
1093
+ namespace,
603
1094
  createUnitOfWork: () => db.createUnitOfWork(),
604
1095
  };
605
1096
 
@@ -612,7 +1103,7 @@ export class DatabaseFragmentDefinitionBuilder<
612
1103
  // Use the adapter's shared context storage (all fragments using the same adapter share this storage)
613
1104
  const builderWithExternalStorage = this.#baseBuilder.withExternalRequestStorage(
614
1105
  ({ options }) => {
615
- const dbContext = createDatabaseContext(options, this.#schema, this.#namespace);
1106
+ const dbContext = createDatabaseContext(options, this.#schema);
616
1107
  return dbContext.databaseAdapter.contextStorage;
617
1108
  },
618
1109
  );
@@ -621,34 +1112,173 @@ export class DatabaseFragmentDefinitionBuilder<
621
1112
  const builderWithStorage = builderWithExternalStorage.withRequestStorage(
622
1113
  ({ options }): DatabaseRequestStorage => {
623
1114
  // Create database context - needed here to create the UOW
624
- const dbContextForStorage = createDatabaseContext(options, this.#schema, this.#namespace);
1115
+ const dbContextForStorage = createDatabaseContext(options, this.#schema);
625
1116
 
626
- // Create a new Unit of Work for this request
627
- const uow: IUnitOfWork = dbContextForStorage.db.createUnitOfWork();
1117
+ const uow = dbContextForStorage.db.createBaseUnitOfWork();
628
1118
 
629
1119
  return { uow };
630
1120
  },
631
1121
  );
632
1122
 
633
- // Get the internal fragment factory from linked fragments (added by withDatabase)
634
- // Cast is safe: withDatabase() guarantees this fragment exists and has the correct type
635
- const internalFragmentFactory = baseDef.linkedFragments?.["_fragno_internal"] as (context: {
1123
+ // Cache per instantiated fragment (deps object is unique per instantiation).
1124
+ const hooksConfigCache = new WeakMap<object, HookProcessorConfig<THooks>>();
1125
+
1126
+ const createHooksConfig = (context: {
636
1127
  config: TConfig;
637
1128
  options: FragnoPublicConfigWithDatabase;
638
- }) => InternalFragmentInstance;
1129
+ deps: TDeps;
1130
+ services?: BoundServices<TBaseServices & TServices>;
1131
+ serviceDeps?: TServiceDependencies;
1132
+ }) => {
1133
+ if (!this.#hooksFactory) {
1134
+ return undefined;
1135
+ }
1136
+ const depsKey =
1137
+ typeof context.deps === "object" && context.deps !== null
1138
+ ? (context.deps as object)
1139
+ : undefined;
1140
+ const namespace = resolveDatabaseNamespace(context.options, this.#schema);
1141
+ const namespaceKey = namespace ?? this.#schema.name;
1142
+ const durableHooksOptions = context.options.durableHooks;
1143
+ DurableHooksLogger.configure(durableHooksOptions?.logging, namespaceKey);
1144
+ const cachedHooksConfig = depsKey ? hooksConfigCache.get(depsKey) : undefined;
1145
+ if (cachedHooksConfig) {
1146
+ if (!cachedHooksConfig.hooks && context.services) {
1147
+ cachedHooksConfig.hooks = this.#hooksFactory({
1148
+ config: context.config,
1149
+ options: context.options,
1150
+ deps: context.deps,
1151
+ services: context.services,
1152
+ serviceDeps: context.serviceDeps ?? ({} as TServiceDependencies),
1153
+ });
1154
+ }
1155
+ return cachedHooksConfig;
1156
+ }
1157
+
1158
+ const autoSchedule = durableHooksOptions?.autoSchedule !== false;
1159
+ const baseAdapter = resolveDatabaseAdapter(context.options, this.#schema);
1160
+ const hookAdapter = baseAdapter.getHookProcessingAdapter?.() ?? baseAdapter;
1161
+ const hookOptions =
1162
+ hookAdapter === baseAdapter
1163
+ ? context.options
1164
+ : { ...context.options, databaseAdapter: hookAdapter };
1165
+ const registryResolver = this.#registryResolver;
1166
+ if (!registryResolver) {
1167
+ throw new Error("Adapter registry resolver is missing for durable hooks.");
1168
+ }
1169
+ const dbContextForHooks = createDatabaseContext(hookOptions, this.#schema);
1170
+ const hookContextStorage = dbContextForHooks.databaseAdapter.contextStorage;
1171
+ const hooksConfig: HookProcessorConfig<THooks> = {
1172
+ hooks: context.services
1173
+ ? this.#hooksFactory({
1174
+ config: context.config,
1175
+ options: context.options,
1176
+ deps: context.deps,
1177
+ services: context.services,
1178
+ serviceDeps: context.serviceDeps ?? ({} as TServiceDependencies),
1179
+ })
1180
+ : undefined,
1181
+ namespace: namespaceKey,
1182
+ internalFragment: registryResolver.getInternalFragment(hookAdapter),
1183
+ autoSchedule,
1184
+ handlerTx: (execOptions?: Omit<ExecuteTxOptions, "createUnitOfWork">) => {
1185
+ const userOnBeforeMutate = execOptions?.onBeforeMutate;
1186
+ const userOnAfterMutate = execOptions?.onAfterMutate;
1187
+ const planMode = execOptions?.planMode ?? false;
1188
+ let storageRef: DatabaseContextStorage | null = null;
1189
+ const getHookWaitUntil = () => {
1190
+ if (!hookContextStorage.hasStore()) {
1191
+ return undefined;
1192
+ }
1193
+ return (
1194
+ hookContextStorage.getStore() as DatabaseContextStorage & {
1195
+ [requestWaitUntilSymbol]?: (promise: Promise<unknown>) => void;
1196
+ }
1197
+ )[requestWaitUntilSymbol];
1198
+ };
1199
+ return createHandlerTxBuilder<THooks>(
1200
+ {
1201
+ ...execOptions,
1202
+ createUnitOfWork: () => {
1203
+ const baseUow = dbContextForHooks.db.createBaseUnitOfWork();
1204
+ baseUow.registerSchema(
1205
+ hooksConfig.internalFragment.$internal.deps.schema,
1206
+ hooksConfig.internalFragment.$internal.deps.namespace,
1207
+ );
1208
+ if (storageRef) {
1209
+ storageRef.uow = baseUow;
1210
+ }
1211
+ return baseUow;
1212
+ },
1213
+ onBeforeMutate: (uow) => {
1214
+ if (!planMode) {
1215
+ prepareHookMutations(
1216
+ uow,
1217
+ hooksConfig.internalFragment,
1218
+ hooksConfig.defaultRetryPolicy,
1219
+ );
1220
+ }
1221
+ if (userOnBeforeMutate) {
1222
+ userOnBeforeMutate(uow);
1223
+ }
1224
+ },
1225
+ onAfterMutate: async (uow) => {
1226
+ notifyDurableHooksAfterMutate({
1227
+ uow,
1228
+ hooksConfig,
1229
+ internalFragment: hooksConfig.internalFragment,
1230
+ autoSchedule,
1231
+ planMode,
1232
+ logContext: { source: "hook", waitUntil: getHookWaitUntil() },
1233
+ });
1234
+ if (userOnAfterMutate) {
1235
+ await userOnAfterMutate(uow);
1236
+ }
1237
+ },
1238
+ },
1239
+ undefined,
1240
+ (run) =>
1241
+ hookContextStorage.runWithInitializer(() => {
1242
+ const inheritedWaitUntil = getHookWaitUntil();
1243
+ storageRef = { uow: null as unknown as DatabaseContextStorage["uow"] };
1244
+ if (inheritedWaitUntil) {
1245
+ (
1246
+ storageRef as DatabaseContextStorage & {
1247
+ [requestWaitUntilSymbol]?: (promise: Promise<unknown>) => void;
1248
+ }
1249
+ )[requestWaitUntilSymbol] = inheritedWaitUntil;
1250
+ }
1251
+ return storageRef;
1252
+ }, run),
1253
+ );
1254
+ },
1255
+ stuckProcessingTimeoutMinutes: durableHooksOptions?.stuckProcessingTimeoutMinutes,
1256
+ onStuckProcessingHooks: durableHooksOptions?.onStuckProcessingHooks,
1257
+ };
1258
+ hooksConfig.runner = createDurableHooksRunner(hooksConfig);
1259
+ registerDurableHooksRuntime(hooksConfig);
1260
+ if (depsKey) {
1261
+ hooksConfigCache.set(depsKey, hooksConfig);
1262
+ }
1263
+ return hooksConfig;
1264
+ };
1265
+
1266
+ const isInternalFragment = baseDef.name === "$fragno-internal-fragment";
639
1267
 
640
1268
  const builderWithContext = builderWithStorage.withThisContext<
641
1269
  DatabaseServiceContext<THooks>,
642
1270
  DatabaseHandlerContext<THooks>
643
- >(({ storage, config, options }) => {
1271
+ >(({ storage, config, options, deps }) => {
644
1272
  // Create hooks config if hooks factory is defined
645
- const hooksConfig = this.#hooksFactory
646
- ? {
647
- hooks: this.#hooksFactory({ config, options }),
648
- namespace: this.#namespace,
649
- internalFragment: internalFragmentFactory({ config, options }),
650
- }
651
- : undefined;
1273
+ const hooksConfig = createHooksConfig({ config, options, deps });
1274
+ const autoSchedule = options.durableHooks?.autoSchedule !== false;
1275
+ const registryResolver = this.#registryResolver;
1276
+ const databaseAdapter =
1277
+ (deps as ImplicitDatabaseDependencies<TSchema>).databaseAdapter ??
1278
+ resolveDatabaseAdapter(options, this.#schema);
1279
+ const internalFragment = isInternalFragment
1280
+ ? undefined
1281
+ : (hooksConfig?.internalFragment ?? registryResolver?.getInternalFragment(databaseAdapter));
652
1282
 
653
1283
  // Builder API: serviceTx using createServiceTxBuilder
654
1284
  function serviceTx<TSchema extends AnySchema>(schema: TSchema) {
@@ -676,40 +1306,191 @@ export class DatabaseFragmentDefinitionBuilder<
676
1306
 
677
1307
  const userOnBeforeMutate = execOptions?.onBeforeMutate;
678
1308
  const userOnAfterMutate = execOptions?.onAfterMutate;
1309
+ const userOnAfterRetrieve = execOptions?.onAfterRetrieve;
1310
+ const planMode = execOptions?.planMode ?? false;
1311
+ const roundtripGuard = isRouteRequest(currentStorage)
1312
+ ? resolveDbRoundtripGuard(options)
1313
+ : null;
1314
+ const roundtripState = roundtripGuard
1315
+ ? getDbRoundtripGuardState(currentStorage, roundtripGuard)
1316
+ : null;
1317
+ const routeInfo = (
1318
+ currentStorage as DatabaseContextStorage & {
1319
+ [requestRouteSymbol]?: { method?: string; path?: string };
1320
+ }
1321
+ )[requestRouteSymbol];
1322
+ const routeWaitUntil = (
1323
+ currentStorage as DatabaseContextStorage & {
1324
+ [requestWaitUntilSymbol]?: (promise: Promise<unknown>) => void;
1325
+ }
1326
+ )[requestWaitUntilSymbol];
1327
+ const routeLabel =
1328
+ routeInfo && routeInfo.method && routeInfo.path
1329
+ ? `${routeInfo.method} ${routeInfo.path}`
1330
+ : null;
1331
+ const routeSuffix = routeLabel ? ` (route: ${routeLabel})` : "";
1332
+ const buildRoundtripError = (kind: "retrieve" | "mutate") =>
1333
+ new FragnoApiError(
1334
+ {
1335
+ message:
1336
+ `[fragno-db] Fragment "${baseDef.name}" executed more than ` +
1337
+ `${roundtripState?.maxRoundtrips ?? 1} ${kind} ` +
1338
+ `database roundtrip(s) in a single request${routeSuffix}. ` +
1339
+ "Combine reads/writes into one handlerTx() or increase dbRoundtripGuard. " +
1340
+ `See ${roundtripGuardDocsUrl}`,
1341
+ code: "DB_ROUNDTRIP_LIMIT_EXCEEDED",
1342
+ },
1343
+ 500,
1344
+ );
1345
+ const roundtripExecutionState = roundtripState
1346
+ ? { countedRetrieve: false, countedMutate: false }
1347
+ : null;
679
1348
 
680
- return createHandlerTxBuilder<THooks>({
1349
+ const resetRoundtripExecutionState = () => {
1350
+ if (!roundtripExecutionState) {
1351
+ return;
1352
+ }
1353
+ roundtripExecutionState.countedRetrieve = false;
1354
+ roundtripExecutionState.countedMutate = false;
1355
+ };
1356
+
1357
+ const guardOnAfterRetrieve = roundtripState
1358
+ ? async (uow: IUnitOfWork, results: unknown[]) => {
1359
+ if (
1360
+ roundtripExecutionState &&
1361
+ !roundtripExecutionState.countedRetrieve &&
1362
+ uow.getRetrievalOperations().length > 0
1363
+ ) {
1364
+ roundtripExecutionState.countedRetrieve = true;
1365
+ roundtripState.retrieveCount += 1;
1366
+ if (roundtripState.retrieveCount > roundtripState.maxRoundtrips) {
1367
+ throw buildRoundtripError("retrieve");
1368
+ }
1369
+ }
1370
+
1371
+ if (userOnAfterRetrieve) {
1372
+ await userOnAfterRetrieve(uow, results);
1373
+ }
1374
+ }
1375
+ : userOnAfterRetrieve;
1376
+
1377
+ const builder = createHandlerTxBuilder<THooks>({
681
1378
  ...execOptions,
682
1379
  createUnitOfWork: () => {
683
1380
  currentStorage.uow.reset();
684
- if (hooksConfig) {
1381
+ if (internalFragment) {
685
1382
  currentStorage.uow.registerSchema(
686
- hooksConfig.internalFragment.$internal.deps.schema,
687
- hooksConfig.internalFragment.$internal.deps.namespace,
1383
+ internalFragment.$internal.deps.schema,
1384
+ internalFragment.$internal.deps.namespace,
688
1385
  );
689
1386
  }
690
1387
  return currentStorage.uow;
691
1388
  },
692
1389
  onBeforeMutate: (uow) => {
693
- if (hooksConfig) {
694
- prepareHookMutations(uow, hooksConfig);
1390
+ if (internalFragment && !planMode) {
1391
+ prepareHookMutations(uow, internalFragment, hooksConfig?.defaultRetryPolicy);
695
1392
  }
696
1393
  if (userOnBeforeMutate) {
697
1394
  userOnBeforeMutate(uow);
698
1395
  }
1396
+ if (
1397
+ roundtripState &&
1398
+ roundtripExecutionState &&
1399
+ !roundtripExecutionState.countedMutate &&
1400
+ uow.getMutationOperations().length > 0
1401
+ ) {
1402
+ roundtripExecutionState.countedMutate = true;
1403
+ roundtripState.mutateCount += 1;
1404
+ if (roundtripState.mutateCount > roundtripState.maxRoundtrips) {
1405
+ throw buildRoundtripError("mutate");
1406
+ }
1407
+ }
699
1408
  },
1409
+ onAfterRetrieve: guardOnAfterRetrieve,
700
1410
  onAfterMutate: async (uow) => {
701
- if (hooksConfig) {
702
- await processHooks(hooksConfig);
1411
+ notifyDurableHooksAfterMutate({
1412
+ uow,
1413
+ hooksConfig,
1414
+ internalFragment,
1415
+ autoSchedule,
1416
+ planMode,
1417
+ logContext: { route: routeLabel, source: "request", waitUntil: routeWaitUntil },
1418
+ });
1419
+ if (hooksConfig && !planMode) {
1420
+ const runtimeState = getDurableHooksRuntimeByConfig(hooksConfig);
1421
+ if (
1422
+ runtimeState &&
1423
+ !runtimeState.dispatcherRegistered &&
1424
+ !runtimeState.dispatcherWarningEmitted
1425
+ ) {
1426
+ const hasHooks = uow
1427
+ .getTriggeredHooks()
1428
+ .some((hook) => hook.namespace === hooksConfig.namespace);
1429
+ if (hasHooks) {
1430
+ runtimeState.dispatcherWarningEmitted = true;
1431
+ DurableHooksLogger.warn("Durable hooks dispatcher not configured for fragment", {
1432
+ namespace: hooksConfig.namespace,
1433
+ fields: {
1434
+ guidance:
1435
+ "Hooks will only run during requests; scheduled/retry hooks may stall. " +
1436
+ "Create a dispatcher with createDurableHooksProcessor([...]) from " +
1437
+ "`@fragno-dev/db/dispatchers/node` or `@fragno-dev/db/dispatchers/cloudflare-do`.",
1438
+ },
1439
+ });
1440
+ }
1441
+ }
703
1442
  }
704
1443
  if (userOnAfterMutate) {
705
1444
  await userOnAfterMutate(uow);
706
1445
  }
707
1446
  },
708
1447
  });
1448
+
1449
+ if (roundtripState) {
1450
+ const guardedBuilder = wrapHandlerTxBuilderWithRoundtripGuard(
1451
+ builder,
1452
+ resetRoundtripExecutionState,
1453
+ );
1454
+ return guardedBuilder;
1455
+ }
1456
+
1457
+ return builder;
1458
+ }
1459
+
1460
+ function callServices<
1461
+ TServiceCalls extends
1462
+ | TxResult<unknown, unknown>
1463
+ | undefined
1464
+ | readonly (TxResult<unknown, unknown> | undefined)[],
1465
+ >(
1466
+ serviceCalls: () => TServiceCalls,
1467
+ ): Promise<ExtractServiceFinalResultOrSingle<TServiceCalls>> {
1468
+ let callWasArray = false;
1469
+ const resultPromise = handlerTx()
1470
+ .withServiceCalls(() => {
1471
+ const calls = serviceCalls();
1472
+ callWasArray = Array.isArray(calls);
1473
+ return (callWasArray ? calls : [calls]) as readonly (
1474
+ | TxResult<unknown, unknown>
1475
+ | undefined
1476
+ )[];
1477
+ })
1478
+ .execute();
1479
+
1480
+ return resultPromise.then((result) =>
1481
+ callWasArray
1482
+ ? (result as ExtractServiceFinalResultOrSingle<TServiceCalls>)
1483
+ : (
1484
+ result as AwaitedPromisesInObject<
1485
+ ExtractServiceFinalResults<readonly [TServiceCalls]>
1486
+ >
1487
+ )[0],
1488
+ ) as Promise<ExtractServiceFinalResultOrSingle<TServiceCalls>>;
709
1489
  }
710
1490
 
711
1491
  const handlerContext: DatabaseHandlerContext<THooks> = {
712
1492
  handlerTx,
1493
+ callServices,
713
1494
  };
714
1495
 
715
1496
  return { serviceContext, handlerContext };
@@ -717,6 +1498,41 @@ export class DatabaseFragmentDefinitionBuilder<
717
1498
 
718
1499
  // Build the final definition
719
1500
  const finalDef = builderWithContext.build();
1501
+ if (this.#hooksFactory) {
1502
+ finalDef.internalDataFactory = ({ config, options, deps, services, serviceDeps }) => {
1503
+ const hooksConfig = createHooksConfig({
1504
+ config: config as TConfig,
1505
+ options: options as FragnoPublicConfigWithDatabase,
1506
+ deps: deps as TDeps,
1507
+ services: services as BoundServices<TBaseServices & TServices>,
1508
+ serviceDeps: serviceDeps as TServiceDependencies,
1509
+ });
1510
+ if (!hooksConfig) {
1511
+ return {};
1512
+ }
1513
+ return {
1514
+ durableHooksToken: registerDurableHooksRuntime(hooksConfig),
1515
+ };
1516
+ };
1517
+ }
1518
+ if (this.#registryResolver) {
1519
+ const registryInternalRoutes = ({ deps }: { deps: TDeps }) => {
1520
+ const databaseAdapter = (deps as ImplicitDatabaseDependencies<TSchema>).databaseAdapter;
1521
+ if (!databaseAdapter) {
1522
+ throw new Error("Database adapter is missing for internal routes.");
1523
+ }
1524
+ const internalFragment = this.#registryResolver!.getInternalFragment(databaseAdapter);
1525
+ if (!internalFragment) {
1526
+ return [];
1527
+ }
1528
+ return (internalFragment.routes ?? []) as readonly AnyFragnoRouteConfig[];
1529
+ };
1530
+ const mergedInternalRoutes = [
1531
+ ...(finalDef.internalRoutes ?? []),
1532
+ registryInternalRoutes,
1533
+ ] as readonly AnyRouteOrFactory[];
1534
+ finalDef.internalRoutes = mergedInternalRoutes as TInternalRoutes;
1535
+ }
720
1536
 
721
1537
  // Return the complete definition with proper typing and dependencies
722
1538
  return {