@fragno-dev/db 0.2.1 → 0.3.0

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 (362) hide show
  1. package/.turbo/turbo-build.log +206 -140
  2. package/CHANGELOG.md +67 -0
  3. package/README.md +30 -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 +27 -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 +55 -16
  14. package/dist/adapters/generic-sql/generic-sql-adapter.js.map +1 -1
  15. package/dist/adapters/generic-sql/generic-sql-uow-executor.js +129 -3
  16. package/dist/adapters/generic-sql/generic-sql-uow-executor.js.map +1 -1
  17. package/dist/adapters/generic-sql/migration/dialect/mysql.js +24 -5
  18. package/dist/adapters/generic-sql/migration/dialect/mysql.js.map +1 -1
  19. package/dist/adapters/generic-sql/migration/dialect/postgres.js +6 -5
  20. package/dist/adapters/generic-sql/migration/dialect/postgres.js.map +1 -1
  21. package/dist/adapters/generic-sql/migration/dialect/sqlite.js +21 -10
  22. package/dist/adapters/generic-sql/migration/dialect/sqlite.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 +8 -8
  25. package/dist/adapters/generic-sql/migration/prepared-migrations.js.map +1 -1
  26. package/dist/adapters/generic-sql/migration/sql-generator.js +74 -51
  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 +6 -5
  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 +42 -4
  31. package/dist/adapters/generic-sql/query/cursor-utils.js.map +1 -1
  32. package/dist/adapters/generic-sql/query/generic-sql-uow-operation-compiler.js +25 -17
  33. package/dist/adapters/generic-sql/query/generic-sql-uow-operation-compiler.js.map +1 -1
  34. package/dist/adapters/generic-sql/query/select-builder.js +5 -3
  35. package/dist/adapters/generic-sql/query/select-builder.js.map +1 -1
  36. package/dist/adapters/generic-sql/query/sql-query-compiler.js +15 -12
  37. package/dist/adapters/generic-sql/query/sql-query-compiler.js.map +1 -1
  38. package/dist/adapters/generic-sql/query/where-builder.js +38 -28
  39. package/dist/adapters/generic-sql/query/where-builder.js.map +1 -1
  40. package/dist/adapters/generic-sql/sqlite-storage.d.ts +13 -0
  41. package/dist/adapters/generic-sql/sqlite-storage.d.ts.map +1 -0
  42. package/dist/adapters/generic-sql/sqlite-storage.js +15 -0
  43. package/dist/adapters/generic-sql/sqlite-storage.js.map +1 -0
  44. package/dist/adapters/generic-sql/uow-decoder.js +7 -3
  45. package/dist/adapters/generic-sql/uow-decoder.js.map +1 -1
  46. package/dist/adapters/generic-sql/uow-encoder.js +28 -8
  47. package/dist/adapters/generic-sql/uow-encoder.js.map +1 -1
  48. package/dist/adapters/in-memory/condition-evaluator.js +131 -0
  49. package/dist/adapters/in-memory/condition-evaluator.js.map +1 -0
  50. package/dist/adapters/in-memory/errors.d.ts +13 -0
  51. package/dist/adapters/in-memory/errors.d.ts.map +1 -0
  52. package/dist/adapters/in-memory/errors.js +23 -0
  53. package/dist/adapters/in-memory/errors.js.map +1 -0
  54. package/dist/adapters/in-memory/in-memory-adapter.d.ts +27 -0
  55. package/dist/adapters/in-memory/in-memory-adapter.d.ts.map +1 -0
  56. package/dist/adapters/in-memory/in-memory-adapter.js +176 -0
  57. package/dist/adapters/in-memory/in-memory-adapter.js.map +1 -0
  58. package/dist/adapters/in-memory/in-memory-uow.js +648 -0
  59. package/dist/adapters/in-memory/in-memory-uow.js.map +1 -0
  60. package/dist/adapters/in-memory/index.d.ts +4 -0
  61. package/dist/adapters/in-memory/index.js +4 -0
  62. package/dist/adapters/in-memory/options.d.ts +28 -0
  63. package/dist/adapters/in-memory/options.d.ts.map +1 -0
  64. package/dist/adapters/in-memory/options.js +61 -0
  65. package/dist/adapters/in-memory/options.js.map +1 -0
  66. package/dist/adapters/in-memory/reference-resolution.js +26 -0
  67. package/dist/adapters/in-memory/reference-resolution.js.map +1 -0
  68. package/dist/adapters/in-memory/sorted-array-index.js +129 -0
  69. package/dist/adapters/in-memory/sorted-array-index.js.map +1 -0
  70. package/dist/adapters/in-memory/store.js +71 -0
  71. package/dist/adapters/in-memory/store.js.map +1 -0
  72. package/dist/adapters/in-memory/value-comparison.js +28 -0
  73. package/dist/adapters/in-memory/value-comparison.js.map +1 -0
  74. package/dist/adapters/shared/from-unit-of-work-compiler.js.map +1 -1
  75. package/dist/adapters/shared/uow-operation-compiler.js +11 -11
  76. package/dist/adapters/shared/uow-operation-compiler.js.map +1 -1
  77. package/dist/adapters/sql/index.d.ts +5 -0
  78. package/dist/adapters/sql/index.js +4 -0
  79. package/dist/db-fragment-definition-builder.d.ts +45 -96
  80. package/dist/db-fragment-definition-builder.d.ts.map +1 -1
  81. package/dist/db-fragment-definition-builder.js +121 -99
  82. package/dist/db-fragment-definition-builder.js.map +1 -1
  83. package/dist/dispatchers/cloudflare-do/index.d.ts +26 -0
  84. package/dist/dispatchers/cloudflare-do/index.d.ts.map +1 -0
  85. package/dist/dispatchers/cloudflare-do/index.js +63 -0
  86. package/dist/dispatchers/cloudflare-do/index.js.map +1 -0
  87. package/dist/dispatchers/node/index.d.ts +17 -0
  88. package/dist/dispatchers/node/index.d.ts.map +1 -0
  89. package/dist/dispatchers/node/index.js +59 -0
  90. package/dist/dispatchers/node/index.js.map +1 -0
  91. package/dist/fragments/internal-fragment.d.ts +172 -9
  92. package/dist/fragments/internal-fragment.d.ts.map +1 -1
  93. package/dist/fragments/internal-fragment.js +193 -74
  94. package/dist/fragments/internal-fragment.js.map +1 -1
  95. package/dist/fragments/internal-fragment.routes.js +29 -0
  96. package/dist/fragments/internal-fragment.routes.js.map +1 -0
  97. package/dist/fragments/internal-fragment.schema.d.ts +9 -0
  98. package/dist/fragments/internal-fragment.schema.d.ts.map +1 -0
  99. package/dist/fragments/internal-fragment.schema.js +22 -0
  100. package/dist/fragments/internal-fragment.schema.js.map +1 -0
  101. package/dist/hooks/durable-hooks-processor.d.ts +14 -0
  102. package/dist/hooks/durable-hooks-processor.d.ts.map +1 -0
  103. package/dist/hooks/durable-hooks-processor.js +32 -0
  104. package/dist/hooks/durable-hooks-processor.js.map +1 -0
  105. package/dist/hooks/hooks.d.ts +47 -4
  106. package/dist/hooks/hooks.d.ts.map +1 -1
  107. package/dist/hooks/hooks.js +106 -39
  108. package/dist/hooks/hooks.js.map +1 -1
  109. package/dist/migration-engine/auto-from-schema.js +14 -11
  110. package/dist/migration-engine/auto-from-schema.js.map +1 -1
  111. package/dist/migration-engine/generation-engine.d.ts +16 -10
  112. package/dist/migration-engine/generation-engine.d.ts.map +1 -1
  113. package/dist/migration-engine/generation-engine.js +72 -33
  114. package/dist/migration-engine/generation-engine.js.map +1 -1
  115. package/dist/migration-engine/shared.js.map +1 -1
  116. package/dist/mod.d.ts +17 -10
  117. package/dist/mod.d.ts.map +1 -1
  118. package/dist/mod.js +14 -8
  119. package/dist/mod.js.map +1 -1
  120. package/dist/naming/sql-naming.d.ts +19 -0
  121. package/dist/naming/sql-naming.d.ts.map +1 -0
  122. package/dist/naming/sql-naming.js +116 -0
  123. package/dist/naming/sql-naming.js.map +1 -0
  124. package/dist/node_modules/.pnpm/{rou3@0.7.10 → rou3@0.7.12}/node_modules/rou3/dist/index.js +8 -5
  125. package/dist/node_modules/.pnpm/rou3@0.7.12/node_modules/rou3/dist/index.js.map +1 -0
  126. package/dist/outbox/outbox-builder.js +156 -0
  127. package/dist/outbox/outbox-builder.js.map +1 -0
  128. package/dist/outbox/outbox.d.ts +52 -0
  129. package/dist/outbox/outbox.d.ts.map +1 -0
  130. package/dist/outbox/outbox.js +37 -0
  131. package/dist/outbox/outbox.js.map +1 -0
  132. package/dist/packages/fragno/dist/api/fragment-definition-builder.js +3 -2
  133. package/dist/packages/fragno/dist/api/fragment-definition-builder.js.map +1 -1
  134. package/dist/packages/fragno/dist/api/fragment-instantiator.js +164 -20
  135. package/dist/packages/fragno/dist/api/fragment-instantiator.js.map +1 -1
  136. package/dist/packages/fragno/dist/api/request-input-context.js +67 -0
  137. package/dist/packages/fragno/dist/api/request-input-context.js.map +1 -1
  138. package/dist/packages/fragno/dist/api/route.js +14 -1
  139. package/dist/packages/fragno/dist/api/route.js.map +1 -1
  140. package/dist/packages/fragno/dist/internal/trace-context.js +12 -0
  141. package/dist/packages/fragno/dist/internal/trace-context.js.map +1 -0
  142. package/dist/query/column-defaults.js +20 -4
  143. package/dist/query/column-defaults.js.map +1 -1
  144. package/dist/query/cursor.d.ts +3 -1
  145. package/dist/query/cursor.d.ts.map +1 -1
  146. package/dist/query/cursor.js +45 -14
  147. package/dist/query/cursor.js.map +1 -1
  148. package/dist/query/db-now.d.ts +8 -0
  149. package/dist/query/db-now.d.ts.map +1 -0
  150. package/dist/query/db-now.js +7 -0
  151. package/dist/query/db-now.js.map +1 -0
  152. package/dist/query/serialize/create-sql-serializer.js +3 -2
  153. package/dist/query/serialize/create-sql-serializer.js.map +1 -1
  154. package/dist/query/serialize/dialect/mysql-serializer.js +12 -6
  155. package/dist/query/serialize/dialect/mysql-serializer.js.map +1 -1
  156. package/dist/query/serialize/dialect/postgres-serializer.js +25 -7
  157. package/dist/query/serialize/dialect/postgres-serializer.js.map +1 -1
  158. package/dist/query/serialize/dialect/sqlite-serializer.js +55 -11
  159. package/dist/query/serialize/dialect/sqlite-serializer.js.map +1 -1
  160. package/dist/query/serialize/sql-serializer.js +2 -2
  161. package/dist/query/serialize/sql-serializer.js.map +1 -1
  162. package/dist/query/simple-query-interface.d.ts +6 -1
  163. package/dist/query/simple-query-interface.d.ts.map +1 -1
  164. package/dist/query/unit-of-work/execute-unit-of-work.d.ts +351 -100
  165. package/dist/query/unit-of-work/execute-unit-of-work.d.ts.map +1 -1
  166. package/dist/query/unit-of-work/execute-unit-of-work.js +440 -267
  167. package/dist/query/unit-of-work/execute-unit-of-work.js.map +1 -1
  168. package/dist/query/unit-of-work/unit-of-work.d.ts +67 -22
  169. package/dist/query/unit-of-work/unit-of-work.d.ts.map +1 -1
  170. package/dist/query/unit-of-work/unit-of-work.js +110 -13
  171. package/dist/query/unit-of-work/unit-of-work.js.map +1 -1
  172. package/dist/query/value-decoding.js +8 -5
  173. package/dist/query/value-decoding.js.map +1 -1
  174. package/dist/query/value-encoding.js +29 -9
  175. package/dist/query/value-encoding.js.map +1 -1
  176. package/dist/schema/create.d.ts +40 -14
  177. package/dist/schema/create.d.ts.map +1 -1
  178. package/dist/schema/create.js +82 -42
  179. package/dist/schema/create.js.map +1 -1
  180. package/dist/schema/generate-id.d.ts +20 -0
  181. package/dist/schema/generate-id.d.ts.map +1 -0
  182. package/dist/schema/generate-id.js +28 -0
  183. package/dist/schema/generate-id.js.map +1 -0
  184. package/dist/schema/type-conversion/create-sql-type-mapper.js +3 -2
  185. package/dist/schema/type-conversion/create-sql-type-mapper.js.map +1 -1
  186. package/dist/schema/type-conversion/dialect/sqlite.js +9 -0
  187. package/dist/schema/type-conversion/dialect/sqlite.js.map +1 -1
  188. package/dist/schema/validator.d.ts +10 -0
  189. package/dist/schema/validator.d.ts.map +1 -0
  190. package/dist/schema/validator.js +123 -0
  191. package/dist/schema/validator.js.map +1 -0
  192. package/dist/schema-output/drizzle.d.ts +30 -0
  193. package/dist/schema-output/drizzle.d.ts.map +1 -0
  194. package/dist/{adapters/drizzle/generate.js → schema-output/drizzle.js} +82 -56
  195. package/dist/schema-output/drizzle.js.map +1 -0
  196. package/dist/schema-output/prisma.d.ts +17 -0
  197. package/dist/schema-output/prisma.d.ts.map +1 -0
  198. package/dist/schema-output/prisma.js +296 -0
  199. package/dist/schema-output/prisma.js.map +1 -0
  200. package/dist/util/default-database-adapter.js +61 -0
  201. package/dist/util/default-database-adapter.js.map +1 -0
  202. package/dist/with-database.d.ts +1 -1
  203. package/dist/with-database.d.ts.map +1 -1
  204. package/dist/with-database.js +12 -3
  205. package/dist/with-database.js.map +1 -1
  206. package/package.json +43 -28
  207. package/src/adapters/adapters.ts +30 -24
  208. package/src/adapters/drizzle/migrate-drizzle.test.ts +54 -33
  209. package/src/adapters/drizzle/migration-parity-drizzle-kit.test.ts +599 -0
  210. package/src/adapters/drizzle/test-utils.ts +12 -8
  211. package/src/adapters/generic-sql/driver-config.ts +38 -0
  212. package/src/adapters/generic-sql/generic-sql-adapter.test.ts +5 -5
  213. package/src/adapters/generic-sql/generic-sql-adapter.ts +110 -24
  214. package/src/adapters/generic-sql/generic-sql-uow-executor.test.ts +54 -0
  215. package/src/adapters/generic-sql/generic-sql-uow-executor.ts +231 -3
  216. package/src/adapters/generic-sql/migration/adapter-migration-parity.test.ts +118 -0
  217. package/src/adapters/generic-sql/migration/dialect/mysql.test.ts +26 -8
  218. package/src/adapters/generic-sql/migration/dialect/mysql.ts +46 -8
  219. package/src/adapters/generic-sql/migration/dialect/postgres.test.ts +25 -7
  220. package/src/adapters/generic-sql/migration/dialect/postgres.ts +8 -4
  221. package/src/adapters/generic-sql/migration/dialect/sqlite.test.ts +47 -8
  222. package/src/adapters/generic-sql/migration/dialect/sqlite.ts +27 -12
  223. package/src/adapters/generic-sql/migration/prepared-migrations.test.ts +128 -39
  224. package/src/adapters/generic-sql/migration/prepared-migrations.ts +15 -8
  225. package/src/adapters/generic-sql/migration/sql-generator.ts +142 -65
  226. package/src/adapters/generic-sql/query/create-sql-query-compiler.ts +9 -6
  227. package/src/adapters/generic-sql/query/cursor-utils.test.ts +271 -0
  228. package/src/adapters/generic-sql/query/cursor-utils.ts +41 -6
  229. package/src/adapters/generic-sql/query/generic-sql-uow-operation-compiler.test.ts +27 -27
  230. package/src/adapters/generic-sql/query/generic-sql-uow-operation-compiler.ts +38 -24
  231. package/src/adapters/generic-sql/query/select-builder.test.ts +15 -11
  232. package/src/adapters/generic-sql/query/select-builder.ts +6 -2
  233. package/src/adapters/generic-sql/query/sql-query-compiler.test.ts +52 -2
  234. package/src/adapters/generic-sql/query/sql-query-compiler.ts +50 -15
  235. package/src/adapters/generic-sql/query/where-builder.test.ts +91 -17
  236. package/src/adapters/generic-sql/query/where-builder.ts +90 -38
  237. package/src/adapters/{kysely/kysely-adapter-pglite.test.ts → generic-sql/sql-adapter-pglite-migrations.test.ts} +6 -6
  238. package/src/adapters/generic-sql/sql-adapter-pglite-pagination.test.ts +806 -0
  239. package/src/adapters/{drizzle/drizzle-adapter-pglite.test.ts → generic-sql/sql-adapter-pglite-queries.test.ts} +11 -11
  240. package/src/adapters/generic-sql/{test/generic-drizzle-adapter-sqlite3.test.ts → sql-adapter-sqlite3-driver.test.ts} +49 -35
  241. package/src/adapters/{drizzle/drizzle-adapter-sqlite3.test.ts → generic-sql/sql-adapter-sqlite3-uow.test.ts} +48 -32
  242. package/src/adapters/{kysely/kysely-adapter-sqlocal.test.ts → generic-sql/sql-adapter-sqlocal.test.ts} +6 -6
  243. package/src/adapters/generic-sql/sqlite-storage.ts +20 -0
  244. package/src/adapters/generic-sql/uow-decoder.test.ts +1 -1
  245. package/src/adapters/generic-sql/uow-decoder.ts +21 -3
  246. package/src/adapters/generic-sql/uow-encoder.test.ts +33 -2
  247. package/src/adapters/generic-sql/uow-encoder.ts +50 -11
  248. package/src/adapters/in-memory/condition-evaluator.test.ts +193 -0
  249. package/src/adapters/in-memory/condition-evaluator.ts +275 -0
  250. package/src/adapters/in-memory/errors.ts +20 -0
  251. package/src/adapters/in-memory/in-memory-adapter.ts +277 -0
  252. package/src/adapters/in-memory/in-memory-uow.mutations.test.ts +296 -0
  253. package/src/adapters/in-memory/in-memory-uow.retrieval.test.ts +100 -0
  254. package/src/adapters/in-memory/in-memory-uow.ts +1348 -0
  255. package/src/adapters/in-memory/index.ts +3 -0
  256. package/src/adapters/in-memory/options.test.ts +41 -0
  257. package/src/adapters/in-memory/options.ts +87 -0
  258. package/src/adapters/in-memory/reference-resolution.test.ts +50 -0
  259. package/src/adapters/in-memory/reference-resolution.ts +67 -0
  260. package/src/adapters/in-memory/sorted-array-index.test.ts +123 -0
  261. package/src/adapters/in-memory/sorted-array-index.ts +228 -0
  262. package/src/adapters/in-memory/store.test.ts +68 -0
  263. package/src/adapters/in-memory/store.ts +145 -0
  264. package/src/adapters/in-memory/value-comparison.ts +53 -0
  265. package/src/adapters/in-memory/value-normalization.test.ts +57 -0
  266. package/src/adapters/prisma/prisma-adapter-sqlite3.test.ts +1163 -0
  267. package/src/adapters/shared/from-unit-of-work-compiler.ts +3 -1
  268. package/src/adapters/shared/uow-operation-compiler.ts +26 -16
  269. package/src/adapters/sql/index.ts +12 -0
  270. package/src/db-fragment-definition-builder.test.ts +88 -54
  271. package/src/db-fragment-definition-builder.ts +201 -322
  272. package/src/db-fragment-instantiator.test.ts +169 -101
  273. package/src/db-fragment-integration.test.ts +301 -149
  274. package/src/dispatchers/cloudflare-do/index.test.ts +73 -0
  275. package/src/dispatchers/cloudflare-do/index.ts +104 -0
  276. package/src/dispatchers/node/index.test.ts +91 -0
  277. package/src/dispatchers/node/index.ts +87 -0
  278. package/src/fragments/internal-fragment.routes.ts +42 -0
  279. package/src/fragments/internal-fragment.schema.ts +51 -0
  280. package/src/fragments/internal-fragment.test.ts +730 -274
  281. package/src/fragments/internal-fragment.ts +447 -154
  282. package/src/hooks/durable-hooks-processor.test.ts +117 -0
  283. package/src/hooks/durable-hooks-processor.ts +67 -0
  284. package/src/hooks/hooks.test.ts +411 -259
  285. package/src/hooks/hooks.ts +265 -66
  286. package/src/migration-engine/auto-from-schema.test.ts +14 -14
  287. package/src/migration-engine/auto-from-schema.ts +5 -2
  288. package/src/migration-engine/create.test.ts +2 -2
  289. package/src/migration-engine/generation-engine.test.ts +229 -104
  290. package/src/migration-engine/generation-engine.ts +94 -64
  291. package/src/migration-engine/shared.ts +1 -0
  292. package/src/mod.ts +78 -30
  293. package/src/naming/sql-naming.ts +180 -0
  294. package/src/outbox/outbox-builder.ts +241 -0
  295. package/src/outbox/outbox.test.ts +253 -0
  296. package/src/outbox/outbox.ts +137 -0
  297. package/src/query/column-defaults.ts +41 -3
  298. package/src/query/condition-builder.test.ts +3 -3
  299. package/src/query/cursor.test.ts +116 -18
  300. package/src/query/cursor.ts +75 -26
  301. package/src/query/db-now.ts +6 -0
  302. package/src/query/query-type.test.ts +2 -2
  303. package/src/query/serialize/create-sql-serializer.ts +7 -2
  304. package/src/query/serialize/dialect/mysql-serializer.ts +12 -4
  305. package/src/query/serialize/dialect/postgres-serializer.ts +34 -4
  306. package/src/query/serialize/dialect/sqlite-serializer.test.ts +51 -1
  307. package/src/query/serialize/dialect/sqlite-serializer.ts +92 -9
  308. package/src/query/serialize/sql-serializer.ts +4 -4
  309. package/src/query/simple-query-interface.ts +5 -0
  310. package/src/query/unit-of-work/execute-unit-of-work.test.ts +1512 -1458
  311. package/src/query/unit-of-work/execute-unit-of-work.ts +1708 -596
  312. package/src/query/unit-of-work/tx-builder.test.ts +1041 -0
  313. package/src/query/unit-of-work/unit-of-work-coordinator.test.ts +32 -32
  314. package/src/query/unit-of-work/unit-of-work-types.test.ts +1 -1
  315. package/src/query/unit-of-work/unit-of-work.test.ts +231 -36
  316. package/src/query/unit-of-work/unit-of-work.ts +229 -31
  317. package/src/query/value-decoding.test.ts +13 -2
  318. package/src/query/value-decoding.ts +17 -4
  319. package/src/query/value-encoding.test.ts +85 -2
  320. package/src/query/value-encoding.ts +56 -6
  321. package/src/schema/create.test.ts +129 -42
  322. package/src/schema/create.ts +187 -47
  323. package/src/schema/generate-id.test.ts +57 -0
  324. package/src/schema/generate-id.ts +38 -0
  325. package/src/schema/serialize.test.ts +14 -2
  326. package/src/schema/type-conversion/create-sql-type-mapper.ts +7 -2
  327. package/src/schema/type-conversion/dialect/sqlite.ts +18 -0
  328. package/src/schema/type-conversion/type-mapping.test.ts +25 -1
  329. package/src/schema/validator.test.ts +197 -0
  330. package/src/schema/validator.ts +231 -0
  331. package/src/{adapters/drizzle/generate.test.ts → schema-output/drizzle.test.ts} +179 -129
  332. package/src/{adapters/drizzle/generate.ts → schema-output/drizzle.ts} +143 -93
  333. package/src/schema-output/prisma.test.ts +536 -0
  334. package/src/schema-output/prisma.ts +573 -0
  335. package/src/util/default-database-adapter.ts +106 -0
  336. package/src/with-database.ts +22 -3
  337. package/tsdown.config.ts +6 -4
  338. package/dist/adapters/drizzle/drizzle-adapter.d.ts +0 -20
  339. package/dist/adapters/drizzle/drizzle-adapter.d.ts.map +0 -1
  340. package/dist/adapters/drizzle/drizzle-adapter.js +0 -27
  341. package/dist/adapters/drizzle/drizzle-adapter.js.map +0 -1
  342. package/dist/adapters/drizzle/generate.d.ts +0 -30
  343. package/dist/adapters/drizzle/generate.d.ts.map +0 -1
  344. package/dist/adapters/drizzle/generate.js.map +0 -1
  345. package/dist/adapters/kysely/kysely-adapter.d.ts +0 -19
  346. package/dist/adapters/kysely/kysely-adapter.d.ts.map +0 -1
  347. package/dist/adapters/kysely/kysely-adapter.js +0 -17
  348. package/dist/adapters/kysely/kysely-adapter.js.map +0 -1
  349. package/dist/adapters/shared/table-name-mapper.d.ts +0 -12
  350. package/dist/adapters/shared/table-name-mapper.d.ts.map +0 -1
  351. package/dist/adapters/shared/table-name-mapper.js +0 -43
  352. package/dist/adapters/shared/table-name-mapper.js.map +0 -1
  353. package/dist/node_modules/.pnpm/rou3@0.7.10/node_modules/rou3/dist/index.js.map +0 -1
  354. package/dist/schema-generator/schema-generator.d.ts +0 -15
  355. package/dist/schema-generator/schema-generator.d.ts.map +0 -1
  356. package/src/adapters/drizzle/drizzle-adapter.ts +0 -39
  357. package/src/adapters/kysely/kysely-adapter.ts +0 -27
  358. package/src/adapters/shared/table-name-mapper.ts +0 -50
  359. package/src/schema-generator/schema-generator.ts +0 -12
  360. package/src/shared/config.ts +0 -10
  361. package/src/shared/connection-pool.ts +0 -24
  362. package/src/shared/prisma.ts +0 -45
@@ -8,45 +8,20 @@ import {
8
8
  type FragnoPublicConfigWithDatabase,
9
9
  type ImplicitDatabaseDependencies,
10
10
  } from "../db-fragment-definition-builder";
11
- import type { FragnoId } from "../schema/create";
12
- import { schema, idColumn, column } from "../schema/create";
11
+ import { FragnoId } from "../schema/create";
13
12
  import type { RetryPolicy } from "../query/unit-of-work/retry-policy";
13
+ import { dbNow } from "../query/db-now";
14
+ import {
15
+ internalSchema,
16
+ SETTINGS_NAMESPACE,
17
+ SETTINGS_TABLE_NAME,
18
+ } from "./internal-fragment.schema";
14
19
 
15
- // Constants for Fragno's internal settings table
16
- export const SETTINGS_TABLE_NAME = "fragno_db_settings" as const;
17
- // FIXME: In some places we simply use empty string "" as namespace, which is not correct.
18
- export const SETTINGS_NAMESPACE = "fragno-db-settings" as const;
19
-
20
- export const internalSchema = schema((s) => {
21
- return s
22
- .addTable(SETTINGS_TABLE_NAME, (t) => {
23
- return t
24
- .addColumn("id", idColumn())
25
- .addColumn("key", column("string"))
26
- .addColumn("value", column("string"))
27
- .createIndex("unique_key", ["key"], { unique: true });
28
- })
29
- .addTable("fragno_hooks", (t) => {
30
- return t
31
- .addColumn("id", idColumn())
32
- .addColumn("namespace", column("string"))
33
- .addColumn("hookName", column("string"))
34
- .addColumn("payload", column("json"))
35
- .addColumn("status", column("string")) // "pending" | "processing" | "completed" | "failed"
36
- .addColumn("attempts", column("integer").defaultTo(0))
37
- .addColumn("maxAttempts", column("integer").defaultTo(5))
38
- .addColumn("lastAttemptAt", column("timestamp").nullable())
39
- .addColumn("nextRetryAt", column("timestamp").nullable())
40
- .addColumn("error", column("string").nullable())
41
- .addColumn(
42
- "createdAt",
43
- column("timestamp").defaultTo((b) => b.now()),
44
- )
45
- .addColumn("nonce", column("string"))
46
- .createIndex("idx_namespace_status_retry", ["namespace", "status", "nextRetryAt"])
47
- .createIndex("idx_nonce", ["nonce"]);
48
- });
49
- });
20
+ export {
21
+ internalSchema,
22
+ SETTINGS_NAMESPACE,
23
+ SETTINGS_TABLE_NAME,
24
+ } from "./internal-fragment.schema";
50
25
 
51
26
  // This uses DatabaseFragmentDefinitionBuilder directly
52
27
  // to avoid circular dependency (it doesn't need to link to itself)
@@ -64,49 +39,67 @@ export const internalFragmentDef = new DatabaseFragmentDefinitionBuilder(
64
39
  DatabaseRequestStorage
65
40
  >("$fragno-internal-fragment"),
66
41
  internalSchema,
67
- "", // intentionally blank namespace so there is no prefix
68
42
  )
43
+ .providesBaseService(({ deps }) => ({
44
+ getDbNow: async () => {
45
+ if (deps.db.now) {
46
+ return deps.db.now();
47
+ }
48
+ return new Date();
49
+ },
50
+ }))
69
51
  .providesService("settingsService", ({ defineService }) => {
70
52
  return defineService({
71
- async get(
72
- namespace: string,
73
- key: string,
74
- ): Promise<{ id: FragnoId; key: string; value: string } | undefined> {
53
+ /**
54
+ * Get a setting by namespace and key.
55
+ */
56
+ get(namespace: string, key: string) {
75
57
  const fullKey = `${namespace}.${key}`;
76
- const uow = this.uow(internalSchema).find(SETTINGS_TABLE_NAME, (b) =>
77
- b.whereIndex("unique_key", (eb) => eb("key", "=", fullKey)),
78
- );
79
- const [results] = await uow.retrievalPhase;
80
- return results?.[0];
58
+ return this.serviceTx(internalSchema)
59
+ .retrieve((uow) =>
60
+ uow.findFirst(SETTINGS_TABLE_NAME, (b) =>
61
+ b.whereIndex("unique_key", (eb) => eb("key", "=", fullKey)),
62
+ ),
63
+ )
64
+ .transformRetrieve(
65
+ ([result]): { id: FragnoId; key: string; value: string } | undefined =>
66
+ result ?? undefined,
67
+ )
68
+ .build();
81
69
  },
82
70
 
83
- async set(namespace: string, key: string, value: string) {
71
+ /**
72
+ * Set a setting value by namespace and key.
73
+ */
74
+ set(namespace: string, key: string, value: string) {
84
75
  const fullKey = `${namespace}.${key}`;
85
- const uow = this.uow(internalSchema);
86
-
87
- // First, find if the key already exists
88
- const findUow = uow.find(SETTINGS_TABLE_NAME, (b) =>
89
- b.whereIndex("unique_key", (eb) => eb("key", "=", fullKey)),
90
- );
91
- const [existing] = await findUow.retrievalPhase;
92
-
93
- if (existing?.[0]) {
94
- uow.update(SETTINGS_TABLE_NAME, existing[0].id, (b) => b.set({ value }).check());
95
- } else {
96
- uow.create(SETTINGS_TABLE_NAME, {
97
- key: fullKey,
98
- value,
99
- });
100
- }
101
-
102
- // Await mutation phase - will throw if mutation fails
103
- await uow.mutationPhase;
76
+ return this.serviceTx(internalSchema)
77
+ .retrieve((uow) =>
78
+ uow.findFirst(SETTINGS_TABLE_NAME, (b) =>
79
+ b.whereIndex("unique_key", (eb) => eb("key", "=", fullKey)),
80
+ ),
81
+ )
82
+ .transformRetrieve(([result]) => result)
83
+ .mutate(({ uow, retrieveResult }) => {
84
+ if (retrieveResult) {
85
+ uow.update(SETTINGS_TABLE_NAME, retrieveResult.id, (b) => b.set({ value }).check());
86
+ } else {
87
+ uow.create(SETTINGS_TABLE_NAME, {
88
+ key: fullKey,
89
+ value,
90
+ });
91
+ }
92
+ })
93
+ .build();
104
94
  },
105
95
 
106
- async delete(id: FragnoId) {
107
- const uow = this.uow(internalSchema);
108
- uow.delete(SETTINGS_TABLE_NAME, id);
109
- await uow.mutationPhase;
96
+ /**
97
+ * Delete a setting by ID.
98
+ */
99
+ delete(id: FragnoId) {
100
+ return this.serviceTx(internalSchema)
101
+ .mutate(({ uow }) => uow.delete(SETTINGS_TABLE_NAME, id))
102
+ .build();
110
103
  },
111
104
  });
112
105
  })
@@ -116,52 +109,255 @@ export const internalFragmentDef = new DatabaseFragmentDefinitionBuilder(
116
109
  * Get pending hook events for processing.
117
110
  * Returns all pending events for the given namespace that are ready to be processed.
118
111
  */
119
- async getPendingHookEvents(namespace: string): Promise<
120
- {
121
- id: FragnoId;
122
- hookName: string;
123
- payload: unknown;
124
- attempts: number;
125
- maxAttempts: number;
126
- nonce: string;
127
- }[]
128
- > {
129
- const uow = this.uow(internalSchema).find("fragno_hooks", (b) =>
130
- b.whereIndex("idx_namespace_status_retry", (eb) =>
131
- eb.and(eb("namespace", "=", namespace), eb("status", "=", "pending")),
132
- ),
133
- );
134
-
135
- const [events] = await uow.retrievalPhase;
136
-
137
- // Filter for pending status and events ready for retry
138
- const now = new Date();
139
- const ready = events.filter((event) => {
140
- // FIXME(Wilco): this should be handled by the database query, but there seems to be an issue.
141
- if (!event.nextRetryAt) {
142
- return true; // Newly created events (nextRetryAt = null) are ready
143
- }
144
- return event.nextRetryAt <= now; // Only include if retry time has passed
145
- });
146
-
147
- return ready.map((event) => ({
148
- id: event.id,
149
- hookName: event.hookName,
150
- payload: event.payload,
151
- attempts: event.attempts,
152
- maxAttempts: event.maxAttempts,
153
- nonce: event.nonce,
154
- }));
112
+ getPendingHookEvents(namespace: string) {
113
+ const now = dbNow();
114
+ return this.serviceTx(internalSchema)
115
+ .retrieve((uow) =>
116
+ uow.find("fragno_hooks", (b) =>
117
+ b.whereIndex("idx_namespace_status_retry", (eb) =>
118
+ eb.and(
119
+ eb("namespace", "=", namespace),
120
+ eb("status", "=", "pending"),
121
+ eb.or(eb.isNull("nextRetryAt"), eb("nextRetryAt", "<=", now)),
122
+ ),
123
+ ),
124
+ ),
125
+ )
126
+ .transformRetrieve(([events]) => {
127
+ return events.map((event) => ({
128
+ id: event.id,
129
+ hookName: event.hookName,
130
+ payload: event.payload as unknown,
131
+ attempts: event.attempts,
132
+ maxAttempts: event.maxAttempts,
133
+ idempotencyKey: event.nonce,
134
+ }));
135
+ })
136
+ .build();
137
+ },
138
+
139
+ /**
140
+ * Claim pending hook events for processing.
141
+ * Returns ready events and marks them as processing in the same transaction.
142
+ */
143
+ claimPendingHookEvents(namespace: string) {
144
+ const now = dbNow();
145
+ return this.serviceTx(internalSchema)
146
+ .retrieve((uow) =>
147
+ uow.find("fragno_hooks", (b) =>
148
+ b.whereIndex("idx_namespace_status_retry", (eb) =>
149
+ eb.and(
150
+ eb("namespace", "=", namespace),
151
+ eb("status", "=", "pending"),
152
+ eb.or(eb.isNull("nextRetryAt"), eb("nextRetryAt", "<=", now)),
153
+ ),
154
+ ),
155
+ ),
156
+ )
157
+ .transformRetrieve(([events]) => {
158
+ return events.map((event) => ({
159
+ id: event.id,
160
+ hookName: event.hookName,
161
+ payload: event.payload,
162
+ attempts: event.attempts,
163
+ maxAttempts: event.maxAttempts,
164
+ idempotencyKey: event.nonce,
165
+ }));
166
+ })
167
+ .mutate(({ uow, retrieveResult }) => {
168
+ if (retrieveResult.length === 0) {
169
+ return;
170
+ }
171
+ for (const event of retrieveResult) {
172
+ uow.update("fragno_hooks", event.id, (b) =>
173
+ b.set({ status: "processing", lastAttemptAt: now }).check(),
174
+ );
175
+ }
176
+ })
177
+ .transform(({ retrieveResult }) =>
178
+ retrieveResult.map((event) => ({
179
+ ...event,
180
+ id: new FragnoId({
181
+ externalId: event.id.externalId,
182
+ internalId: event.id.internalId,
183
+ version: event.id.version + 1,
184
+ }),
185
+ })),
186
+ )
187
+ .build();
188
+ },
189
+
190
+ /**
191
+ * Re-queue hook events that have been stuck in processing for too long.
192
+ */
193
+ requeueStuckProcessingHooks(namespace: string, staleBefore: Date) {
194
+ return this.serviceTx(internalSchema)
195
+ .retrieve((uow) =>
196
+ uow.find("fragno_hooks", (b) =>
197
+ b.whereIndex("idx_namespace_status_retry", (eb) =>
198
+ eb.and(eb("namespace", "=", namespace), eb("status", "=", "processing")),
199
+ ),
200
+ ),
201
+ )
202
+ .transformRetrieve(([events]) => {
203
+ const stuck = events.filter((event) => {
204
+ if (!event.lastAttemptAt) {
205
+ return true;
206
+ }
207
+ return event.lastAttemptAt <= staleBefore;
208
+ });
209
+
210
+ return stuck.map((event) => ({
211
+ id: event.id,
212
+ hookName: event.hookName,
213
+ attempts: event.attempts,
214
+ maxAttempts: event.maxAttempts,
215
+ lastAttemptAt: event.lastAttemptAt,
216
+ nextRetryAt: event.nextRetryAt,
217
+ }));
218
+ })
219
+ .mutate(({ uow, retrieveResult }) => {
220
+ for (const event of retrieveResult) {
221
+ uow.update("fragno_hooks", event.id, (b) =>
222
+ b.set({ status: "pending", nextRetryAt: null }).check(),
223
+ );
224
+ }
225
+ })
226
+ .transform(({ retrieveResult }) => retrieveResult)
227
+ .build();
228
+ },
229
+
230
+ /**
231
+ * Get the next time a processing hook becomes stale.
232
+ */
233
+ getNextProcessingStaleAt(namespace: string, timeoutMinutes: number, now?: Date) {
234
+ return this.serviceTx(internalSchema)
235
+ .retrieve((uow) =>
236
+ uow.find("fragno_hooks", (b) =>
237
+ b.whereIndex("idx_namespace_status_retry", (eb) =>
238
+ eb.and(eb("namespace", "=", namespace), eb("status", "=", "processing")),
239
+ ),
240
+ ),
241
+ )
242
+ .transformRetrieve(([events]) => {
243
+ if (events.length === 0) {
244
+ return null;
245
+ }
246
+
247
+ const baseNow = now ?? new Date();
248
+ const nowMs = baseNow.getTime();
249
+ const timeoutMs = timeoutMinutes * 60_000;
250
+ let earliestStaleAt: Date | null = null;
251
+
252
+ for (const event of events) {
253
+ if (!event.lastAttemptAt) {
254
+ return baseNow;
255
+ }
256
+
257
+ const staleAtMs = event.lastAttemptAt.getTime() + timeoutMs;
258
+ if (staleAtMs <= nowMs) {
259
+ return baseNow;
260
+ }
261
+
262
+ const staleAt = new Date(staleAtMs);
263
+ if (!earliestStaleAt || staleAt < earliestStaleAt) {
264
+ earliestStaleAt = staleAt;
265
+ }
266
+ }
267
+
268
+ return earliestStaleAt;
269
+ })
270
+ .build();
271
+ },
272
+
273
+ /**
274
+ * Get the earliest pending hook wake time for a namespace.
275
+ * Optionally considers processing hooks becoming stale when timeoutMinutes is provided.
276
+ */
277
+ getNextHookWakeAt(namespace: string, timeoutMinutes?: number | false, now?: Date) {
278
+ const baseNow = now ?? new Date();
279
+ const includeProcessing = typeof timeoutMinutes === "number" && timeoutMinutes > 0;
280
+ const timeoutMs = includeProcessing ? timeoutMinutes * 60_000 : 0;
281
+
282
+ return this.serviceTx(internalSchema)
283
+ .retrieve((uow) =>
284
+ uow.find("fragno_hooks", (b) =>
285
+ b
286
+ .whereIndex("idx_namespace_status_retry", (eb) => {
287
+ if (includeProcessing) {
288
+ return eb.and(
289
+ eb("namespace", "=", namespace),
290
+ eb.or(eb("status", "=", "pending"), eb("status", "=", "processing")),
291
+ );
292
+ }
293
+ return eb.and(eb("namespace", "=", namespace), eb("status", "=", "pending"));
294
+ })
295
+ .select(["status", "nextRetryAt", "lastAttemptAt"]),
296
+ ),
297
+ )
298
+ .transformRetrieve(([events]) => {
299
+ if (events.length === 0) {
300
+ return null;
301
+ }
302
+
303
+ const nowMs = baseNow.getTime();
304
+ let earliestPendingAt: Date | null = null;
305
+ let earliestStaleAt: Date | null = null;
306
+
307
+ for (const event of events) {
308
+ if (event.status === "pending") {
309
+ const nextRetryAt = event.nextRetryAt;
310
+ if (!nextRetryAt || nextRetryAt.getTime() <= nowMs) {
311
+ return baseNow;
312
+ }
313
+ if (!earliestPendingAt || nextRetryAt < earliestPendingAt) {
314
+ earliestPendingAt = nextRetryAt;
315
+ }
316
+ continue;
317
+ }
318
+
319
+ if (!includeProcessing || event.status !== "processing") {
320
+ continue;
321
+ }
322
+
323
+ const lastAttemptAt = event.lastAttemptAt;
324
+ if (!lastAttemptAt) {
325
+ return baseNow;
326
+ }
327
+
328
+ const staleAtMs = lastAttemptAt.getTime() + timeoutMs;
329
+ if (staleAtMs <= nowMs) {
330
+ return baseNow;
331
+ }
332
+
333
+ const staleAt = new Date(staleAtMs);
334
+ if (!earliestStaleAt || staleAt < earliestStaleAt) {
335
+ earliestStaleAt = staleAt;
336
+ }
337
+ }
338
+
339
+ if (!earliestPendingAt) {
340
+ return earliestStaleAt ?? null;
341
+ }
342
+ if (!earliestStaleAt) {
343
+ return earliestPendingAt;
344
+ }
345
+ return earliestPendingAt <= earliestStaleAt ? earliestPendingAt : earliestStaleAt;
346
+ })
347
+ .build();
155
348
  },
156
349
 
157
350
  /**
158
351
  * Mark a hook event as completed.
159
352
  */
160
- markHookCompleted(eventId: FragnoId): void {
161
- const uow = this.uow(internalSchema);
162
- uow.update("fragno_hooks", eventId, (b) =>
163
- b.set({ status: "completed", lastAttemptAt: new Date() }).check(),
164
- );
353
+ markHookCompleted(eventId: FragnoId) {
354
+ return this.serviceTx(internalSchema)
355
+ .mutate(({ uow }) =>
356
+ uow.update("fragno_hooks", eventId, (b) =>
357
+ b.set({ status: "completed", lastAttemptAt: dbNow() }).check(),
358
+ ),
359
+ )
360
+ .build();
165
361
  },
166
362
 
167
363
  /**
@@ -172,48 +368,121 @@ export const internalFragmentDef = new DatabaseFragmentDefinitionBuilder(
172
368
  error: string,
173
369
  attempts: number,
174
370
  retryPolicy: RetryPolicy,
175
- ): void {
176
- const uow = this.uow(internalSchema);
177
-
371
+ now?: Date,
372
+ ) {
178
373
  const newAttempts = attempts + 1;
179
374
  const shouldRetry = retryPolicy.shouldRetry(newAttempts - 1);
180
375
 
181
- if (shouldRetry) {
182
- const delayMs = retryPolicy.getDelayMs(newAttempts - 1);
183
- const nextRetryAt = new Date(Date.now() + delayMs);
184
- uow.update("fragno_hooks", eventId, (b) =>
185
- b
186
- .set({
187
- status: "pending",
188
- attempts: newAttempts,
189
- lastAttemptAt: new Date(),
190
- nextRetryAt,
191
- error,
192
- })
193
- .check(),
194
- );
195
- } else {
196
- uow.update("fragno_hooks", eventId, (b) =>
197
- b
198
- .set({
199
- status: "failed",
200
- attempts: newAttempts,
201
- lastAttemptAt: new Date(),
202
- error,
203
- })
204
- .check(),
205
- );
206
- }
376
+ return this.serviceTx(internalSchema)
377
+ .mutate(({ uow }) => {
378
+ if (shouldRetry) {
379
+ const delayMs = retryPolicy.getDelayMs(newAttempts - 1);
380
+ const baseNow = now ?? new Date();
381
+ const nextRetryAt = new Date(baseNow.getTime() + delayMs);
382
+ uow.update("fragno_hooks", eventId, (b) =>
383
+ b
384
+ .set({
385
+ status: "pending",
386
+ attempts: newAttempts,
387
+ lastAttemptAt: dbNow(),
388
+ nextRetryAt,
389
+ error,
390
+ })
391
+ .check(),
392
+ );
393
+ } else {
394
+ uow.update("fragno_hooks", eventId, (b) =>
395
+ b
396
+ .set({
397
+ status: "failed",
398
+ attempts: newAttempts,
399
+ lastAttemptAt: dbNow(),
400
+ error,
401
+ })
402
+ .check(),
403
+ );
404
+ }
405
+ })
406
+ .build();
207
407
  },
208
408
 
209
409
  /**
210
410
  * Mark a hook event as processing (to prevent concurrent execution).
211
411
  */
212
- markHookProcessing(eventId: FragnoId): void {
213
- const uow = this.uow(internalSchema);
214
- uow.update("fragno_hooks", eventId, (b) =>
215
- b.set({ status: "processing", lastAttemptAt: new Date() }).check(),
216
- );
412
+ markHookProcessing(eventId: FragnoId) {
413
+ return this.serviceTx(internalSchema)
414
+ .mutate(({ uow }) =>
415
+ uow.update("fragno_hooks", eventId, (b) =>
416
+ b.set({ status: "processing", lastAttemptAt: dbNow() }).check(),
417
+ ),
418
+ )
419
+ .build();
420
+ },
421
+
422
+ /**
423
+ * Get a hook event by ID (for testing/verification purposes).
424
+ */
425
+ getHookById(eventId: FragnoId) {
426
+ return this.serviceTx(internalSchema)
427
+ .retrieve((uow) =>
428
+ uow.findFirst("fragno_hooks", (b) =>
429
+ b.whereIndex("primary", (eb) => eb("id", "=", eventId)),
430
+ ),
431
+ )
432
+ .transformRetrieve(([result]) => result ?? undefined)
433
+ .build();
434
+ },
435
+
436
+ /**
437
+ * Get all hook events for a namespace (for testing/verification purposes).
438
+ */
439
+ getHooksByNamespace(namespace: string) {
440
+ return this.serviceTx(internalSchema)
441
+ .retrieve((uow) =>
442
+ uow.find("fragno_hooks", (b) =>
443
+ b.whereIndex("idx_namespace_status_retry", (eb) => eb("namespace", "=", namespace)),
444
+ ),
445
+ )
446
+ .transformRetrieve(([events]) => events)
447
+ .build();
448
+ },
449
+ });
450
+ })
451
+ .providesService("outboxService", ({ defineService }) => {
452
+ return defineService({
453
+ /**
454
+ * List outbox entries ordered by versionstamp (ascending).
455
+ */
456
+ list({ afterVersionstamp, limit }: { afterVersionstamp?: string; limit?: number } = {}) {
457
+ const afterValue = afterVersionstamp?.toLowerCase();
458
+
459
+ return this.serviceTx(internalSchema)
460
+ .retrieve((uow) =>
461
+ uow.find("fragno_db_outbox", (b) => {
462
+ let builder = afterValue
463
+ ? b.whereIndex("idx_outbox_versionstamp", (eb) =>
464
+ eb("versionstamp", ">", afterValue),
465
+ )
466
+ : b.whereIndex("idx_outbox_versionstamp");
467
+
468
+ builder = builder.orderByIndex("idx_outbox_versionstamp", "asc");
469
+ if (limit !== undefined) {
470
+ builder = builder.pageSize(limit);
471
+ }
472
+ return builder;
473
+ }),
474
+ )
475
+ .transformRetrieve(([entries]) =>
476
+ entries.map((entry) => ({
477
+ id: entry.id,
478
+ versionstamp: entry.versionstamp,
479
+ uowId: entry.uowId,
480
+ payload: entry.payload,
481
+ refMap: entry.refMap ?? undefined,
482
+ createdAt: entry.createdAt,
483
+ })),
484
+ )
485
+ .build();
217
486
  },
218
487
  });
219
488
  })
@@ -232,16 +501,40 @@ export async function getSchemaVersionFromDatabase(
232
501
  namespace: string,
233
502
  ): Promise<number> {
234
503
  try {
235
- const version = await fragment.inContext(async function () {
236
- const version = await this.uow(async ({ executeRetrieve }) => {
237
- const version = fragment.services.settingsService.get(namespace, "schema_version");
238
- await executeRetrieve();
239
- return version;
504
+ const readSchemaVersion = async (targetNamespace: string): Promise<number | undefined> => {
505
+ const setting = await fragment.inContext(async function () {
506
+ return await this.handlerTx()
507
+ .withServiceCalls(
508
+ () =>
509
+ [fragment.services.settingsService.get(targetNamespace, "schema_version")] as const,
510
+ )
511
+ .transform(({ serviceResult: [result] }) => result)
512
+ .execute();
240
513
  });
514
+ if (!setting) {
515
+ return undefined;
516
+ }
517
+ const parsed = parseInt(setting.value, 10);
518
+ return Number.isNaN(parsed) ? undefined : parsed;
519
+ };
241
520
 
242
- return version ? parseInt(version.value, 10) : 0;
243
- });
244
- return version;
521
+ const primary = await readSchemaVersion(namespace);
522
+ if (primary !== undefined) {
523
+ return primary;
524
+ }
525
+
526
+ // Back-compat: some installs stored internal schema version under a different namespace.
527
+ // Check the alternate key (empty string ↔ schema name) so we find the version either way.
528
+ const legacyNamespace =
529
+ namespace === "" ? internalSchema.name : namespace === internalSchema.name ? "" : null;
530
+ if (legacyNamespace !== null) {
531
+ const legacy = await readSchemaVersion(legacyNamespace);
532
+ if (legacy !== undefined) {
533
+ return legacy;
534
+ }
535
+ }
536
+
537
+ return 0;
245
538
  } catch {
246
539
  return 0;
247
540
  }