@fragno-dev/db 0.2.2 → 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 (355) hide show
  1. package/.turbo/turbo-build.log +202 -140
  2. package/CHANGELOG.md +35 -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 +39 -29
  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 +18 -7
  80. package/dist/db-fragment-definition-builder.d.ts.map +1 -1
  81. package/dist/db-fragment-definition-builder.js +116 -54
  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 +79 -2
  92. package/dist/fragments/internal-fragment.d.ts.map +1 -1
  93. package/dist/fragments/internal-fragment.js +150 -32
  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 +42 -1
  106. package/dist/hooks/hooks.d.ts.map +1 -1
  107. package/dist/hooks/hooks.js +72 -6
  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 +15 -8
  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.map +1 -1
  165. package/dist/query/unit-of-work/execute-unit-of-work.js +11 -6
  166. package/dist/query/unit-of-work/execute-unit-of-work.js.map +1 -1
  167. package/dist/query/unit-of-work/unit-of-work.d.ts +50 -14
  168. package/dist/query/unit-of-work/unit-of-work.d.ts.map +1 -1
  169. package/dist/query/unit-of-work/unit-of-work.js +86 -5
  170. package/dist/query/unit-of-work/unit-of-work.js.map +1 -1
  171. package/dist/query/value-decoding.js +9 -6
  172. package/dist/query/value-decoding.js.map +1 -1
  173. package/dist/query/value-encoding.js +29 -9
  174. package/dist/query/value-encoding.js.map +1 -1
  175. package/dist/schema/create.d.ts +38 -14
  176. package/dist/schema/create.d.ts.map +1 -1
  177. package/dist/schema/create.js +81 -42
  178. package/dist/schema/create.js.map +1 -1
  179. package/dist/schema/generate-id.js +2 -2
  180. package/dist/schema/generate-id.js.map +1 -1
  181. package/dist/schema/type-conversion/create-sql-type-mapper.js +3 -2
  182. package/dist/schema/type-conversion/create-sql-type-mapper.js.map +1 -1
  183. package/dist/schema/type-conversion/dialect/sqlite.js +9 -0
  184. package/dist/schema/type-conversion/dialect/sqlite.js.map +1 -1
  185. package/dist/schema/validator.d.ts +10 -0
  186. package/dist/schema/validator.d.ts.map +1 -0
  187. package/dist/schema/validator.js +123 -0
  188. package/dist/schema/validator.js.map +1 -0
  189. package/dist/schema-output/drizzle.d.ts +30 -0
  190. package/dist/schema-output/drizzle.d.ts.map +1 -0
  191. package/dist/{adapters/drizzle/generate.js → schema-output/drizzle.js} +82 -56
  192. package/dist/schema-output/drizzle.js.map +1 -0
  193. package/dist/schema-output/prisma.d.ts +17 -0
  194. package/dist/schema-output/prisma.d.ts.map +1 -0
  195. package/dist/schema-output/prisma.js +296 -0
  196. package/dist/schema-output/prisma.js.map +1 -0
  197. package/dist/util/default-database-adapter.js +61 -0
  198. package/dist/util/default-database-adapter.js.map +1 -0
  199. package/dist/with-database.d.ts +1 -1
  200. package/dist/with-database.d.ts.map +1 -1
  201. package/dist/with-database.js +12 -3
  202. package/dist/with-database.js.map +1 -1
  203. package/package.json +43 -28
  204. package/src/adapters/adapters.ts +30 -24
  205. package/src/adapters/drizzle/migrate-drizzle.test.ts +54 -33
  206. package/src/adapters/drizzle/migration-parity-drizzle-kit.test.ts +599 -0
  207. package/src/adapters/drizzle/test-utils.ts +12 -8
  208. package/src/adapters/generic-sql/driver-config.ts +38 -0
  209. package/src/adapters/generic-sql/generic-sql-adapter.test.ts +5 -5
  210. package/src/adapters/generic-sql/generic-sql-adapter.ts +110 -24
  211. package/src/adapters/generic-sql/generic-sql-uow-executor.test.ts +54 -0
  212. package/src/adapters/generic-sql/generic-sql-uow-executor.ts +231 -3
  213. package/src/adapters/generic-sql/migration/adapter-migration-parity.test.ts +118 -0
  214. package/src/adapters/generic-sql/migration/dialect/mysql.test.ts +26 -8
  215. package/src/adapters/generic-sql/migration/dialect/mysql.ts +46 -8
  216. package/src/adapters/generic-sql/migration/dialect/postgres.test.ts +25 -7
  217. package/src/adapters/generic-sql/migration/dialect/postgres.ts +8 -4
  218. package/src/adapters/generic-sql/migration/dialect/sqlite.test.ts +47 -8
  219. package/src/adapters/generic-sql/migration/dialect/sqlite.ts +27 -12
  220. package/src/adapters/generic-sql/migration/prepared-migrations.test.ts +128 -39
  221. package/src/adapters/generic-sql/migration/prepared-migrations.ts +15 -8
  222. package/src/adapters/generic-sql/migration/sql-generator.ts +142 -65
  223. package/src/adapters/generic-sql/query/create-sql-query-compiler.ts +9 -6
  224. package/src/adapters/generic-sql/query/cursor-utils.test.ts +271 -0
  225. package/src/adapters/generic-sql/query/cursor-utils.ts +41 -6
  226. package/src/adapters/generic-sql/query/generic-sql-uow-operation-compiler.test.ts +27 -27
  227. package/src/adapters/generic-sql/query/generic-sql-uow-operation-compiler.ts +38 -24
  228. package/src/adapters/generic-sql/query/select-builder.test.ts +15 -11
  229. package/src/adapters/generic-sql/query/select-builder.ts +6 -2
  230. package/src/adapters/generic-sql/query/sql-query-compiler.test.ts +52 -2
  231. package/src/adapters/generic-sql/query/sql-query-compiler.ts +50 -15
  232. package/src/adapters/generic-sql/query/where-builder.test.ts +91 -17
  233. package/src/adapters/generic-sql/query/where-builder.ts +90 -38
  234. package/src/adapters/{kysely/kysely-adapter-pglite.test.ts → generic-sql/sql-adapter-pglite-migrations.test.ts} +6 -6
  235. package/src/adapters/generic-sql/sql-adapter-pglite-pagination.test.ts +806 -0
  236. package/src/adapters/{drizzle/drizzle-adapter-pglite.test.ts → generic-sql/sql-adapter-pglite-queries.test.ts} +11 -11
  237. package/src/adapters/generic-sql/{test/generic-drizzle-adapter-sqlite3.test.ts → sql-adapter-sqlite3-driver.test.ts} +10 -10
  238. package/src/adapters/{drizzle/drizzle-adapter-sqlite3.test.ts → generic-sql/sql-adapter-sqlite3-uow.test.ts} +7 -7
  239. package/src/adapters/{kysely/kysely-adapter-sqlocal.test.ts → generic-sql/sql-adapter-sqlocal.test.ts} +6 -6
  240. package/src/adapters/generic-sql/sqlite-storage.ts +20 -0
  241. package/src/adapters/generic-sql/uow-decoder.test.ts +1 -1
  242. package/src/adapters/generic-sql/uow-decoder.ts +21 -3
  243. package/src/adapters/generic-sql/uow-encoder.test.ts +33 -2
  244. package/src/adapters/generic-sql/uow-encoder.ts +50 -11
  245. package/src/adapters/in-memory/condition-evaluator.test.ts +193 -0
  246. package/src/adapters/in-memory/condition-evaluator.ts +275 -0
  247. package/src/adapters/in-memory/errors.ts +20 -0
  248. package/src/adapters/in-memory/in-memory-adapter.ts +277 -0
  249. package/src/adapters/in-memory/in-memory-uow.mutations.test.ts +296 -0
  250. package/src/adapters/in-memory/in-memory-uow.retrieval.test.ts +100 -0
  251. package/src/adapters/in-memory/in-memory-uow.ts +1348 -0
  252. package/src/adapters/in-memory/index.ts +3 -0
  253. package/src/adapters/in-memory/options.test.ts +41 -0
  254. package/src/adapters/in-memory/options.ts +87 -0
  255. package/src/adapters/in-memory/reference-resolution.test.ts +50 -0
  256. package/src/adapters/in-memory/reference-resolution.ts +67 -0
  257. package/src/adapters/in-memory/sorted-array-index.test.ts +123 -0
  258. package/src/adapters/in-memory/sorted-array-index.ts +228 -0
  259. package/src/adapters/in-memory/store.test.ts +68 -0
  260. package/src/adapters/in-memory/store.ts +145 -0
  261. package/src/adapters/in-memory/value-comparison.ts +53 -0
  262. package/src/adapters/in-memory/value-normalization.test.ts +57 -0
  263. package/src/adapters/prisma/prisma-adapter-sqlite3.test.ts +1163 -0
  264. package/src/adapters/shared/from-unit-of-work-compiler.ts +3 -1
  265. package/src/adapters/shared/uow-operation-compiler.ts +26 -16
  266. package/src/adapters/sql/index.ts +12 -0
  267. package/src/db-fragment-definition-builder.test.ts +30 -12
  268. package/src/db-fragment-definition-builder.ts +142 -73
  269. package/src/db-fragment-instantiator.test.ts +105 -13
  270. package/src/db-fragment-integration.test.ts +9 -7
  271. package/src/dispatchers/cloudflare-do/index.test.ts +73 -0
  272. package/src/dispatchers/cloudflare-do/index.ts +104 -0
  273. package/src/dispatchers/node/index.test.ts +91 -0
  274. package/src/dispatchers/node/index.ts +87 -0
  275. package/src/fragments/internal-fragment.routes.ts +42 -0
  276. package/src/fragments/internal-fragment.schema.ts +51 -0
  277. package/src/fragments/internal-fragment.test.ts +458 -8
  278. package/src/fragments/internal-fragment.ts +322 -63
  279. package/src/hooks/durable-hooks-processor.test.ts +117 -0
  280. package/src/hooks/durable-hooks-processor.ts +67 -0
  281. package/src/hooks/hooks.test.ts +165 -5
  282. package/src/hooks/hooks.ts +197 -9
  283. package/src/migration-engine/auto-from-schema.test.ts +14 -14
  284. package/src/migration-engine/auto-from-schema.ts +5 -2
  285. package/src/migration-engine/create.test.ts +2 -2
  286. package/src/migration-engine/generation-engine.test.ts +229 -104
  287. package/src/migration-engine/generation-engine.ts +94 -64
  288. package/src/migration-engine/shared.ts +1 -0
  289. package/src/mod.ts +64 -26
  290. package/src/naming/sql-naming.ts +180 -0
  291. package/src/outbox/outbox-builder.ts +241 -0
  292. package/src/outbox/outbox.test.ts +253 -0
  293. package/src/outbox/outbox.ts +137 -0
  294. package/src/query/column-defaults.ts +41 -3
  295. package/src/query/condition-builder.test.ts +3 -3
  296. package/src/query/cursor.test.ts +116 -18
  297. package/src/query/cursor.ts +75 -26
  298. package/src/query/db-now.ts +6 -0
  299. package/src/query/query-type.test.ts +2 -2
  300. package/src/query/serialize/create-sql-serializer.ts +7 -2
  301. package/src/query/serialize/dialect/mysql-serializer.ts +12 -4
  302. package/src/query/serialize/dialect/postgres-serializer.ts +34 -4
  303. package/src/query/serialize/dialect/sqlite-serializer.test.ts +51 -1
  304. package/src/query/serialize/dialect/sqlite-serializer.ts +92 -9
  305. package/src/query/serialize/sql-serializer.ts +4 -4
  306. package/src/query/simple-query-interface.ts +5 -0
  307. package/src/query/unit-of-work/execute-unit-of-work.test.ts +25 -1
  308. package/src/query/unit-of-work/execute-unit-of-work.ts +25 -8
  309. package/src/query/unit-of-work/unit-of-work-coordinator.test.ts +12 -12
  310. package/src/query/unit-of-work/unit-of-work-types.test.ts +1 -1
  311. package/src/query/unit-of-work/unit-of-work.test.ts +168 -37
  312. package/src/query/unit-of-work/unit-of-work.ts +203 -18
  313. package/src/query/value-decoding.test.ts +13 -2
  314. package/src/query/value-decoding.ts +17 -4
  315. package/src/query/value-encoding.test.ts +85 -2
  316. package/src/query/value-encoding.ts +56 -6
  317. package/src/schema/create.test.ts +129 -42
  318. package/src/schema/create.ts +185 -47
  319. package/src/schema/generate-id.test.ts +2 -2
  320. package/src/schema/generate-id.ts +2 -2
  321. package/src/schema/serialize.test.ts +14 -2
  322. package/src/schema/type-conversion/create-sql-type-mapper.ts +7 -2
  323. package/src/schema/type-conversion/dialect/sqlite.ts +18 -0
  324. package/src/schema/type-conversion/type-mapping.test.ts +25 -1
  325. package/src/schema/validator.test.ts +197 -0
  326. package/src/schema/validator.ts +231 -0
  327. package/src/{adapters/drizzle/generate.test.ts → schema-output/drizzle.test.ts} +179 -129
  328. package/src/{adapters/drizzle/generate.ts → schema-output/drizzle.ts} +143 -93
  329. package/src/schema-output/prisma.test.ts +536 -0
  330. package/src/schema-output/prisma.ts +573 -0
  331. package/src/util/default-database-adapter.ts +106 -0
  332. package/src/with-database.ts +22 -3
  333. package/tsdown.config.ts +6 -4
  334. package/dist/adapters/drizzle/drizzle-adapter.d.ts +0 -20
  335. package/dist/adapters/drizzle/drizzle-adapter.d.ts.map +0 -1
  336. package/dist/adapters/drizzle/drizzle-adapter.js +0 -27
  337. package/dist/adapters/drizzle/drizzle-adapter.js.map +0 -1
  338. package/dist/adapters/drizzle/generate.d.ts +0 -30
  339. package/dist/adapters/drizzle/generate.d.ts.map +0 -1
  340. package/dist/adapters/drizzle/generate.js.map +0 -1
  341. package/dist/adapters/kysely/kysely-adapter.d.ts +0 -19
  342. package/dist/adapters/kysely/kysely-adapter.d.ts.map +0 -1
  343. package/dist/adapters/kysely/kysely-adapter.js +0 -17
  344. package/dist/adapters/kysely/kysely-adapter.js.map +0 -1
  345. package/dist/adapters/shared/table-name-mapper.d.ts +0 -12
  346. package/dist/adapters/shared/table-name-mapper.d.ts.map +0 -1
  347. package/dist/adapters/shared/table-name-mapper.js +0 -43
  348. package/dist/adapters/shared/table-name-mapper.js.map +0 -1
  349. package/dist/node_modules/.pnpm/rou3@0.7.10/node_modules/rou3/dist/index.js.map +0 -1
  350. package/dist/schema-generator/schema-generator.d.ts +0 -15
  351. package/dist/schema-generator/schema-generator.d.ts.map +0 -1
  352. package/src/adapters/drizzle/drizzle-adapter.ts +0 -39
  353. package/src/adapters/kysely/kysely-adapter.ts +0 -27
  354. package/src/adapters/shared/table-name-mapper.ts +0 -50
  355. package/src/schema-generator/schema-generator.ts +0 -12
@@ -1,22 +1,14 @@
1
1
  import { FragmentDefinitionBuilder } from "../packages/fragno/dist/api/fragment-definition-builder.js";
2
+ import { FragnoId } from "../schema/create.js";
3
+ import { SETTINGS_NAMESPACE, SETTINGS_TABLE_NAME, internalSchema } from "./internal-fragment.schema.js";
4
+ import { dbNow } from "../query/db-now.js";
2
5
  import { DatabaseFragmentDefinitionBuilder } from "../db-fragment-definition-builder.js";
3
- import { column, idColumn, schema } from "../schema/create.js";
4
6
 
5
7
  //#region src/fragments/internal-fragment.ts
6
- const SETTINGS_TABLE_NAME = "fragno_db_settings";
7
- const SETTINGS_NAMESPACE = "fragno-db-settings";
8
- const internalSchema = schema((s) => {
9
- return s.addTable(SETTINGS_TABLE_NAME, (t) => {
10
- return t.addColumn("id", idColumn()).addColumn("key", column("string")).addColumn("value", column("string")).createIndex("unique_key", ["key"], { unique: true });
11
- }).addTable("fragno_hooks", (t) => {
12
- return t.addColumn("id", idColumn()).addColumn("namespace", column("string")).addColumn("hookName", column("string")).addColumn("payload", column("json")).addColumn("status", column("string")).addColumn("attempts", column("integer").defaultTo(0)).addColumn("maxAttempts", column("integer").defaultTo(5)).addColumn("lastAttemptAt", column("timestamp").nullable()).addColumn("nextRetryAt", column("timestamp").nullable()).addColumn("error", column("string").nullable()).addColumn("createdAt", column("timestamp").defaultTo((b) => b.now())).addColumn("nonce", column("string")).createIndex("idx_namespace_status_retry", [
13
- "namespace",
14
- "status",
15
- "nextRetryAt"
16
- ]).createIndex("idx_nonce", ["nonce"]);
17
- });
18
- });
19
- const internalFragmentDef = new DatabaseFragmentDefinitionBuilder(new FragmentDefinitionBuilder("$fragno-internal-fragment"), internalSchema, "").providesService("settingsService", ({ defineService }) => {
8
+ const internalFragmentDef = new DatabaseFragmentDefinitionBuilder(new FragmentDefinitionBuilder("$fragno-internal-fragment"), internalSchema).providesBaseService(({ deps }) => ({ getDbNow: async () => {
9
+ if (deps.db.now) return deps.db.now();
10
+ return /* @__PURE__ */ new Date();
11
+ } })).providesService("settingsService", ({ defineService }) => {
20
12
  return defineService({
21
13
  get(namespace, key) {
22
14
  const fullKey = `${namespace}.${key}`;
@@ -39,12 +31,9 @@ const internalFragmentDef = new DatabaseFragmentDefinitionBuilder(new FragmentDe
39
31
  }).providesService("hookService", ({ defineService }) => {
40
32
  return defineService({
41
33
  getPendingHookEvents(namespace) {
42
- return this.serviceTx(internalSchema).retrieve((uow) => uow.find("fragno_hooks", (b) => b.whereIndex("idx_namespace_status_retry", (eb) => eb.and(eb("namespace", "=", namespace), eb("status", "=", "pending"))))).transformRetrieve(([events]) => {
43
- const now = /* @__PURE__ */ new Date();
44
- return events.filter((event) => {
45
- if (!event.nextRetryAt) return true;
46
- return event.nextRetryAt <= now;
47
- }).map((event) => ({
34
+ const now = dbNow();
35
+ return this.serviceTx(internalSchema).retrieve((uow) => uow.find("fragno_hooks", (b) => b.whereIndex("idx_namespace_status_retry", (eb) => eb.and(eb("namespace", "=", namespace), eb("status", "=", "pending"), eb.or(eb.isNull("nextRetryAt"), eb("nextRetryAt", "<=", now)))))).transformRetrieve(([events]) => {
36
+ return events.map((event) => ({
48
37
  id: event.id,
49
38
  hookName: event.hookName,
50
39
  payload: event.payload,
@@ -54,30 +43,130 @@ const internalFragmentDef = new DatabaseFragmentDefinitionBuilder(new FragmentDe
54
43
  }));
55
44
  }).build();
56
45
  },
46
+ claimPendingHookEvents(namespace) {
47
+ const now = dbNow();
48
+ return this.serviceTx(internalSchema).retrieve((uow) => uow.find("fragno_hooks", (b) => b.whereIndex("idx_namespace_status_retry", (eb) => eb.and(eb("namespace", "=", namespace), eb("status", "=", "pending"), eb.or(eb.isNull("nextRetryAt"), eb("nextRetryAt", "<=", now)))))).transformRetrieve(([events]) => {
49
+ return events.map((event) => ({
50
+ id: event.id,
51
+ hookName: event.hookName,
52
+ payload: event.payload,
53
+ attempts: event.attempts,
54
+ maxAttempts: event.maxAttempts,
55
+ idempotencyKey: event.nonce
56
+ }));
57
+ }).mutate(({ uow, retrieveResult }) => {
58
+ if (retrieveResult.length === 0) return;
59
+ for (const event of retrieveResult) uow.update("fragno_hooks", event.id, (b) => b.set({
60
+ status: "processing",
61
+ lastAttemptAt: now
62
+ }).check());
63
+ }).transform(({ retrieveResult }) => retrieveResult.map((event) => ({
64
+ ...event,
65
+ id: new FragnoId({
66
+ externalId: event.id.externalId,
67
+ internalId: event.id.internalId,
68
+ version: event.id.version + 1
69
+ })
70
+ }))).build();
71
+ },
72
+ requeueStuckProcessingHooks(namespace, staleBefore) {
73
+ return this.serviceTx(internalSchema).retrieve((uow) => uow.find("fragno_hooks", (b) => b.whereIndex("idx_namespace_status_retry", (eb) => eb.and(eb("namespace", "=", namespace), eb("status", "=", "processing"))))).transformRetrieve(([events]) => {
74
+ return events.filter((event) => {
75
+ if (!event.lastAttemptAt) return true;
76
+ return event.lastAttemptAt <= staleBefore;
77
+ }).map((event) => ({
78
+ id: event.id,
79
+ hookName: event.hookName,
80
+ attempts: event.attempts,
81
+ maxAttempts: event.maxAttempts,
82
+ lastAttemptAt: event.lastAttemptAt,
83
+ nextRetryAt: event.nextRetryAt
84
+ }));
85
+ }).mutate(({ uow, retrieveResult }) => {
86
+ for (const event of retrieveResult) uow.update("fragno_hooks", event.id, (b) => b.set({
87
+ status: "pending",
88
+ nextRetryAt: null
89
+ }).check());
90
+ }).transform(({ retrieveResult }) => retrieveResult).build();
91
+ },
92
+ getNextProcessingStaleAt(namespace, timeoutMinutes, now) {
93
+ return this.serviceTx(internalSchema).retrieve((uow) => uow.find("fragno_hooks", (b) => b.whereIndex("idx_namespace_status_retry", (eb) => eb.and(eb("namespace", "=", namespace), eb("status", "=", "processing"))))).transformRetrieve(([events]) => {
94
+ if (events.length === 0) return null;
95
+ const baseNow = now ?? /* @__PURE__ */ new Date();
96
+ const nowMs = baseNow.getTime();
97
+ const timeoutMs = timeoutMinutes * 6e4;
98
+ let earliestStaleAt = null;
99
+ for (const event of events) {
100
+ if (!event.lastAttemptAt) return baseNow;
101
+ const staleAtMs = event.lastAttemptAt.getTime() + timeoutMs;
102
+ if (staleAtMs <= nowMs) return baseNow;
103
+ const staleAt = new Date(staleAtMs);
104
+ if (!earliestStaleAt || staleAt < earliestStaleAt) earliestStaleAt = staleAt;
105
+ }
106
+ return earliestStaleAt;
107
+ }).build();
108
+ },
109
+ getNextHookWakeAt(namespace, timeoutMinutes, now) {
110
+ const baseNow = now ?? /* @__PURE__ */ new Date();
111
+ const includeProcessing = typeof timeoutMinutes === "number" && timeoutMinutes > 0;
112
+ const timeoutMs = includeProcessing ? timeoutMinutes * 6e4 : 0;
113
+ return this.serviceTx(internalSchema).retrieve((uow) => uow.find("fragno_hooks", (b) => b.whereIndex("idx_namespace_status_retry", (eb) => {
114
+ if (includeProcessing) return eb.and(eb("namespace", "=", namespace), eb.or(eb("status", "=", "pending"), eb("status", "=", "processing")));
115
+ return eb.and(eb("namespace", "=", namespace), eb("status", "=", "pending"));
116
+ }).select([
117
+ "status",
118
+ "nextRetryAt",
119
+ "lastAttemptAt"
120
+ ]))).transformRetrieve(([events]) => {
121
+ if (events.length === 0) return null;
122
+ const nowMs = baseNow.getTime();
123
+ let earliestPendingAt = null;
124
+ let earliestStaleAt = null;
125
+ for (const event of events) {
126
+ if (event.status === "pending") {
127
+ const nextRetryAt = event.nextRetryAt;
128
+ if (!nextRetryAt || nextRetryAt.getTime() <= nowMs) return baseNow;
129
+ if (!earliestPendingAt || nextRetryAt < earliestPendingAt) earliestPendingAt = nextRetryAt;
130
+ continue;
131
+ }
132
+ if (!includeProcessing || event.status !== "processing") continue;
133
+ const lastAttemptAt = event.lastAttemptAt;
134
+ if (!lastAttemptAt) return baseNow;
135
+ const staleAtMs = lastAttemptAt.getTime() + timeoutMs;
136
+ if (staleAtMs <= nowMs) return baseNow;
137
+ const staleAt = new Date(staleAtMs);
138
+ if (!earliestStaleAt || staleAt < earliestStaleAt) earliestStaleAt = staleAt;
139
+ }
140
+ if (!earliestPendingAt) return earliestStaleAt ?? null;
141
+ if (!earliestStaleAt) return earliestPendingAt;
142
+ return earliestPendingAt <= earliestStaleAt ? earliestPendingAt : earliestStaleAt;
143
+ }).build();
144
+ },
57
145
  markHookCompleted(eventId) {
58
146
  return this.serviceTx(internalSchema).mutate(({ uow }) => uow.update("fragno_hooks", eventId, (b) => b.set({
59
147
  status: "completed",
60
- lastAttemptAt: /* @__PURE__ */ new Date()
148
+ lastAttemptAt: dbNow()
61
149
  }).check())).build();
62
150
  },
63
- markHookFailed(eventId, error, attempts, retryPolicy) {
151
+ markHookFailed(eventId, error, attempts, retryPolicy, now) {
64
152
  const newAttempts = attempts + 1;
65
153
  const shouldRetry = retryPolicy.shouldRetry(newAttempts - 1);
66
154
  return this.serviceTx(internalSchema).mutate(({ uow }) => {
67
155
  if (shouldRetry) {
68
156
  const delayMs = retryPolicy.getDelayMs(newAttempts - 1);
69
- const nextRetryAt = new Date(Date.now() + delayMs);
157
+ const baseNow = now ?? /* @__PURE__ */ new Date();
158
+ const nextRetryAt = new Date(baseNow.getTime() + delayMs);
70
159
  uow.update("fragno_hooks", eventId, (b) => b.set({
71
160
  status: "pending",
72
161
  attempts: newAttempts,
73
- lastAttemptAt: /* @__PURE__ */ new Date(),
162
+ lastAttemptAt: dbNow(),
74
163
  nextRetryAt,
75
164
  error
76
165
  }).check());
77
166
  } else uow.update("fragno_hooks", eventId, (b) => b.set({
78
167
  status: "failed",
79
168
  attempts: newAttempts,
80
- lastAttemptAt: /* @__PURE__ */ new Date(),
169
+ lastAttemptAt: dbNow(),
81
170
  error
82
171
  }).check());
83
172
  }).build();
@@ -85,7 +174,7 @@ const internalFragmentDef = new DatabaseFragmentDefinitionBuilder(new FragmentDe
85
174
  markHookProcessing(eventId) {
86
175
  return this.serviceTx(internalSchema).mutate(({ uow }) => uow.update("fragno_hooks", eventId, (b) => b.set({
87
176
  status: "processing",
88
- lastAttemptAt: /* @__PURE__ */ new Date()
177
+ lastAttemptAt: dbNow()
89
178
  }).check())).build();
90
179
  },
91
180
  getHookById(eventId) {
@@ -95,18 +184,47 @@ const internalFragmentDef = new DatabaseFragmentDefinitionBuilder(new FragmentDe
95
184
  return this.serviceTx(internalSchema).retrieve((uow) => uow.find("fragno_hooks", (b) => b.whereIndex("idx_namespace_status_retry", (eb) => eb("namespace", "=", namespace)))).transformRetrieve(([events]) => events).build();
96
185
  }
97
186
  });
187
+ }).providesService("outboxService", ({ defineService }) => {
188
+ return defineService({ list({ afterVersionstamp, limit } = {}) {
189
+ const afterValue = afterVersionstamp?.toLowerCase();
190
+ return this.serviceTx(internalSchema).retrieve((uow) => uow.find("fragno_db_outbox", (b) => {
191
+ let builder = afterValue ? b.whereIndex("idx_outbox_versionstamp", (eb) => eb("versionstamp", ">", afterValue)) : b.whereIndex("idx_outbox_versionstamp");
192
+ builder = builder.orderByIndex("idx_outbox_versionstamp", "asc");
193
+ if (limit !== void 0) builder = builder.pageSize(limit);
194
+ return builder;
195
+ })).transformRetrieve(([entries]) => entries.map((entry) => ({
196
+ id: entry.id,
197
+ versionstamp: entry.versionstamp,
198
+ uowId: entry.uowId,
199
+ payload: entry.payload,
200
+ refMap: entry.refMap ?? void 0,
201
+ createdAt: entry.createdAt
202
+ }))).build();
203
+ } });
98
204
  }).build();
99
205
  async function getSchemaVersionFromDatabase(fragment, namespace) {
100
206
  try {
101
- const setting = await fragment.inContext(async function() {
102
- return await this.handlerTx().withServiceCalls(() => [fragment.services.settingsService.get(namespace, "schema_version")]).transform(({ serviceResult: [result] }) => result).execute();
103
- });
104
- return setting ? parseInt(setting.value, 10) : 0;
207
+ const readSchemaVersion = async (targetNamespace) => {
208
+ const setting = await fragment.inContext(async function() {
209
+ return await this.handlerTx().withServiceCalls(() => [fragment.services.settingsService.get(targetNamespace, "schema_version")]).transform(({ serviceResult: [result] }) => result).execute();
210
+ });
211
+ if (!setting) return;
212
+ const parsed = parseInt(setting.value, 10);
213
+ return Number.isNaN(parsed) ? void 0 : parsed;
214
+ };
215
+ const primary = await readSchemaVersion(namespace);
216
+ if (primary !== void 0) return primary;
217
+ const legacyNamespace = namespace === "" ? internalSchema.name : namespace === internalSchema.name ? "" : null;
218
+ if (legacyNamespace !== null) {
219
+ const legacy = await readSchemaVersion(legacyNamespace);
220
+ if (legacy !== void 0) return legacy;
221
+ }
222
+ return 0;
105
223
  } catch {
106
224
  return 0;
107
225
  }
108
226
  }
109
227
 
110
228
  //#endregion
111
- export { SETTINGS_NAMESPACE, SETTINGS_TABLE_NAME, getSchemaVersionFromDatabase, internalFragmentDef, internalSchema };
229
+ export { getSchemaVersionFromDatabase, internalFragmentDef };
112
230
  //# sourceMappingURL=internal-fragment.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"internal-fragment.js","names":[],"sources":["../../src/fragments/internal-fragment.ts"],"sourcesContent":["import { FragmentDefinitionBuilder } from \"@fragno-dev/core\";\nimport type { InstantiatedFragmentFromDefinition } from \"@fragno-dev/core\";\nimport {\n DatabaseFragmentDefinitionBuilder,\n type DatabaseHandlerContext,\n type DatabaseRequestStorage,\n type DatabaseServiceContext,\n type FragnoPublicConfigWithDatabase,\n type ImplicitDatabaseDependencies,\n} from \"../db-fragment-definition-builder\";\nimport type { FragnoId } from \"../schema/create\";\nimport { schema, idColumn, column } from \"../schema/create\";\nimport type { RetryPolicy } from \"../query/unit-of-work/retry-policy\";\n\n// Constants for Fragno's internal settings table\nexport const SETTINGS_TABLE_NAME = \"fragno_db_settings\" as const;\n// FIXME: In some places we simply use empty string \"\" as namespace, which is not correct.\nexport const SETTINGS_NAMESPACE = \"fragno-db-settings\" as const;\n\nexport const internalSchema = schema((s) => {\n return s\n .addTable(SETTINGS_TABLE_NAME, (t) => {\n return t\n .addColumn(\"id\", idColumn())\n .addColumn(\"key\", column(\"string\"))\n .addColumn(\"value\", column(\"string\"))\n .createIndex(\"unique_key\", [\"key\"], { unique: true });\n })\n .addTable(\"fragno_hooks\", (t) => {\n return t\n .addColumn(\"id\", idColumn())\n .addColumn(\"namespace\", column(\"string\"))\n .addColumn(\"hookName\", column(\"string\"))\n .addColumn(\"payload\", column(\"json\"))\n .addColumn(\"status\", column(\"string\")) // \"pending\" | \"processing\" | \"completed\" | \"failed\"\n .addColumn(\"attempts\", column(\"integer\").defaultTo(0))\n .addColumn(\"maxAttempts\", column(\"integer\").defaultTo(5))\n .addColumn(\"lastAttemptAt\", column(\"timestamp\").nullable())\n .addColumn(\"nextRetryAt\", column(\"timestamp\").nullable())\n .addColumn(\"error\", column(\"string\").nullable())\n .addColumn(\n \"createdAt\",\n column(\"timestamp\").defaultTo((b) => b.now()),\n )\n .addColumn(\"nonce\", column(\"string\"))\n .createIndex(\"idx_namespace_status_retry\", [\"namespace\", \"status\", \"nextRetryAt\"])\n .createIndex(\"idx_nonce\", [\"nonce\"]);\n });\n});\n\n// This uses DatabaseFragmentDefinitionBuilder directly\n// to avoid circular dependency (it doesn't need to link to itself)\nexport const internalFragmentDef = new DatabaseFragmentDefinitionBuilder(\n new FragmentDefinitionBuilder<\n {},\n FragnoPublicConfigWithDatabase,\n ImplicitDatabaseDependencies<typeof internalSchema>,\n {},\n {},\n {},\n {},\n DatabaseServiceContext<{}>,\n DatabaseHandlerContext,\n DatabaseRequestStorage\n >(\"$fragno-internal-fragment\"),\n internalSchema,\n \"\", // intentionally blank namespace so there is no prefix\n)\n .providesService(\"settingsService\", ({ defineService }) => {\n return defineService({\n /**\n * Get a setting by namespace and key.\n */\n get(namespace: string, key: string) {\n const fullKey = `${namespace}.${key}`;\n return this.serviceTx(internalSchema)\n .retrieve((uow) =>\n uow.findFirst(SETTINGS_TABLE_NAME, (b) =>\n b.whereIndex(\"unique_key\", (eb) => eb(\"key\", \"=\", fullKey)),\n ),\n )\n .transformRetrieve(\n ([result]): { id: FragnoId; key: string; value: string } | undefined =>\n result ?? undefined,\n )\n .build();\n },\n\n /**\n * Set a setting value by namespace and key.\n */\n set(namespace: string, key: string, value: string) {\n const fullKey = `${namespace}.${key}`;\n return this.serviceTx(internalSchema)\n .retrieve((uow) =>\n uow.findFirst(SETTINGS_TABLE_NAME, (b) =>\n b.whereIndex(\"unique_key\", (eb) => eb(\"key\", \"=\", fullKey)),\n ),\n )\n .transformRetrieve(([result]) => result)\n .mutate(({ uow, retrieveResult }) => {\n if (retrieveResult) {\n uow.update(SETTINGS_TABLE_NAME, retrieveResult.id, (b) => b.set({ value }).check());\n } else {\n uow.create(SETTINGS_TABLE_NAME, {\n key: fullKey,\n value,\n });\n }\n })\n .build();\n },\n\n /**\n * Delete a setting by ID.\n */\n delete(id: FragnoId) {\n return this.serviceTx(internalSchema)\n .mutate(({ uow }) => uow.delete(SETTINGS_TABLE_NAME, id))\n .build();\n },\n });\n })\n .providesService(\"hookService\", ({ defineService }) => {\n return defineService({\n /**\n * Get pending hook events for processing.\n * Returns all pending events for the given namespace that are ready to be processed.\n */\n getPendingHookEvents(namespace: string) {\n return this.serviceTx(internalSchema)\n .retrieve((uow) =>\n uow.find(\"fragno_hooks\", (b) =>\n b.whereIndex(\"idx_namespace_status_retry\", (eb) =>\n eb.and(eb(\"namespace\", \"=\", namespace), eb(\"status\", \"=\", \"pending\")),\n ),\n ),\n )\n .transformRetrieve(([events]) => {\n const now = new Date();\n // FIXME(Wilco): this should be handled by the database query, but there seems to be an issue.\n const ready = events.filter((event) => {\n if (!event.nextRetryAt) {\n return true; // Newly created events (nextRetryAt = null) are ready\n }\n return event.nextRetryAt <= now; // Only include if retry time has passed\n });\n\n return ready.map((event) => ({\n id: event.id,\n hookName: event.hookName,\n payload: event.payload as unknown,\n attempts: event.attempts,\n maxAttempts: event.maxAttempts,\n idempotencyKey: event.nonce,\n }));\n })\n .build();\n },\n\n /**\n * Mark a hook event as completed.\n */\n markHookCompleted(eventId: FragnoId) {\n return this.serviceTx(internalSchema)\n .mutate(({ uow }) =>\n uow.update(\"fragno_hooks\", eventId, (b) =>\n b.set({ status: \"completed\", lastAttemptAt: new Date() }).check(),\n ),\n )\n .build();\n },\n\n /**\n * Mark a hook event as failed and schedule next retry.\n */\n markHookFailed(eventId: FragnoId, error: string, attempts: number, retryPolicy: RetryPolicy) {\n const newAttempts = attempts + 1;\n const shouldRetry = retryPolicy.shouldRetry(newAttempts - 1);\n\n return this.serviceTx(internalSchema)\n .mutate(({ uow }) => {\n if (shouldRetry) {\n const delayMs = retryPolicy.getDelayMs(newAttempts - 1);\n const nextRetryAt = new Date(Date.now() + delayMs);\n uow.update(\"fragno_hooks\", eventId, (b) =>\n b\n .set({\n status: \"pending\",\n attempts: newAttempts,\n lastAttemptAt: new Date(),\n nextRetryAt,\n error,\n })\n .check(),\n );\n } else {\n uow.update(\"fragno_hooks\", eventId, (b) =>\n b\n .set({\n status: \"failed\",\n attempts: newAttempts,\n lastAttemptAt: new Date(),\n error,\n })\n .check(),\n );\n }\n })\n .build();\n },\n\n /**\n * Mark a hook event as processing (to prevent concurrent execution).\n */\n markHookProcessing(eventId: FragnoId) {\n return this.serviceTx(internalSchema)\n .mutate(({ uow }) =>\n uow.update(\"fragno_hooks\", eventId, (b) =>\n b.set({ status: \"processing\", lastAttemptAt: new Date() }).check(),\n ),\n )\n .build();\n },\n\n /**\n * Get a hook event by ID (for testing/verification purposes).\n */\n getHookById(eventId: FragnoId) {\n return this.serviceTx(internalSchema)\n .retrieve((uow) =>\n uow.findFirst(\"fragno_hooks\", (b) =>\n b.whereIndex(\"primary\", (eb) => eb(\"id\", \"=\", eventId)),\n ),\n )\n .transformRetrieve(([result]) => result ?? undefined)\n .build();\n },\n\n /**\n * Get all hook events for a namespace (for testing/verification purposes).\n */\n getHooksByNamespace(namespace: string) {\n return this.serviceTx(internalSchema)\n .retrieve((uow) =>\n uow.find(\"fragno_hooks\", (b) =>\n b.whereIndex(\"idx_namespace_status_retry\", (eb) => eb(\"namespace\", \"=\", namespace)),\n ),\n )\n .transformRetrieve(([events]) => events)\n .build();\n },\n });\n })\n .build();\n\n/**\n * Type representing an instantiated internal fragment.\n * This is the fragment that manages Fragno's internal settings table.\n */\nexport type InternalFragmentInstance = InstantiatedFragmentFromDefinition<\n typeof internalFragmentDef\n>;\n\nexport async function getSchemaVersionFromDatabase(\n fragment: InternalFragmentInstance,\n namespace: string,\n): Promise<number> {\n try {\n const setting = await fragment.inContext(async function () {\n return await this.handlerTx()\n .withServiceCalls(\n () => [fragment.services.settingsService.get(namespace, \"schema_version\")] as const,\n )\n .transform(({ serviceResult: [result] }) => result)\n .execute();\n });\n return setting ? parseInt(setting.value, 10) : 0;\n } catch {\n return 0;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"internal-fragment.js","names":["earliestStaleAt: Date | null","earliestPendingAt: Date | null"],"sources":["../../src/fragments/internal-fragment.ts"],"sourcesContent":["import { FragmentDefinitionBuilder } from \"@fragno-dev/core\";\nimport type { InstantiatedFragmentFromDefinition } from \"@fragno-dev/core\";\nimport {\n DatabaseFragmentDefinitionBuilder,\n type DatabaseHandlerContext,\n type DatabaseRequestStorage,\n type DatabaseServiceContext,\n type FragnoPublicConfigWithDatabase,\n type ImplicitDatabaseDependencies,\n} from \"../db-fragment-definition-builder\";\nimport { FragnoId } from \"../schema/create\";\nimport type { RetryPolicy } from \"../query/unit-of-work/retry-policy\";\nimport { dbNow } from \"../query/db-now\";\nimport {\n internalSchema,\n SETTINGS_NAMESPACE,\n SETTINGS_TABLE_NAME,\n} from \"./internal-fragment.schema\";\n\nexport {\n internalSchema,\n SETTINGS_NAMESPACE,\n SETTINGS_TABLE_NAME,\n} from \"./internal-fragment.schema\";\n\n// This uses DatabaseFragmentDefinitionBuilder directly\n// to avoid circular dependency (it doesn't need to link to itself)\nexport const internalFragmentDef = new DatabaseFragmentDefinitionBuilder(\n new FragmentDefinitionBuilder<\n {},\n FragnoPublicConfigWithDatabase,\n ImplicitDatabaseDependencies<typeof internalSchema>,\n {},\n {},\n {},\n {},\n DatabaseServiceContext<{}>,\n DatabaseHandlerContext,\n DatabaseRequestStorage\n >(\"$fragno-internal-fragment\"),\n internalSchema,\n)\n .providesBaseService(({ deps }) => ({\n getDbNow: async () => {\n if (deps.db.now) {\n return deps.db.now();\n }\n return new Date();\n },\n }))\n .providesService(\"settingsService\", ({ defineService }) => {\n return defineService({\n /**\n * Get a setting by namespace and key.\n */\n get(namespace: string, key: string) {\n const fullKey = `${namespace}.${key}`;\n return this.serviceTx(internalSchema)\n .retrieve((uow) =>\n uow.findFirst(SETTINGS_TABLE_NAME, (b) =>\n b.whereIndex(\"unique_key\", (eb) => eb(\"key\", \"=\", fullKey)),\n ),\n )\n .transformRetrieve(\n ([result]): { id: FragnoId; key: string; value: string } | undefined =>\n result ?? undefined,\n )\n .build();\n },\n\n /**\n * Set a setting value by namespace and key.\n */\n set(namespace: string, key: string, value: string) {\n const fullKey = `${namespace}.${key}`;\n return this.serviceTx(internalSchema)\n .retrieve((uow) =>\n uow.findFirst(SETTINGS_TABLE_NAME, (b) =>\n b.whereIndex(\"unique_key\", (eb) => eb(\"key\", \"=\", fullKey)),\n ),\n )\n .transformRetrieve(([result]) => result)\n .mutate(({ uow, retrieveResult }) => {\n if (retrieveResult) {\n uow.update(SETTINGS_TABLE_NAME, retrieveResult.id, (b) => b.set({ value }).check());\n } else {\n uow.create(SETTINGS_TABLE_NAME, {\n key: fullKey,\n value,\n });\n }\n })\n .build();\n },\n\n /**\n * Delete a setting by ID.\n */\n delete(id: FragnoId) {\n return this.serviceTx(internalSchema)\n .mutate(({ uow }) => uow.delete(SETTINGS_TABLE_NAME, id))\n .build();\n },\n });\n })\n .providesService(\"hookService\", ({ defineService }) => {\n return defineService({\n /**\n * Get pending hook events for processing.\n * Returns all pending events for the given namespace that are ready to be processed.\n */\n getPendingHookEvents(namespace: string) {\n const now = dbNow();\n return this.serviceTx(internalSchema)\n .retrieve((uow) =>\n uow.find(\"fragno_hooks\", (b) =>\n b.whereIndex(\"idx_namespace_status_retry\", (eb) =>\n eb.and(\n eb(\"namespace\", \"=\", namespace),\n eb(\"status\", \"=\", \"pending\"),\n eb.or(eb.isNull(\"nextRetryAt\"), eb(\"nextRetryAt\", \"<=\", now)),\n ),\n ),\n ),\n )\n .transformRetrieve(([events]) => {\n return events.map((event) => ({\n id: event.id,\n hookName: event.hookName,\n payload: event.payload as unknown,\n attempts: event.attempts,\n maxAttempts: event.maxAttempts,\n idempotencyKey: event.nonce,\n }));\n })\n .build();\n },\n\n /**\n * Claim pending hook events for processing.\n * Returns ready events and marks them as processing in the same transaction.\n */\n claimPendingHookEvents(namespace: string) {\n const now = dbNow();\n return this.serviceTx(internalSchema)\n .retrieve((uow) =>\n uow.find(\"fragno_hooks\", (b) =>\n b.whereIndex(\"idx_namespace_status_retry\", (eb) =>\n eb.and(\n eb(\"namespace\", \"=\", namespace),\n eb(\"status\", \"=\", \"pending\"),\n eb.or(eb.isNull(\"nextRetryAt\"), eb(\"nextRetryAt\", \"<=\", now)),\n ),\n ),\n ),\n )\n .transformRetrieve(([events]) => {\n return events.map((event) => ({\n id: event.id,\n hookName: event.hookName,\n payload: event.payload,\n attempts: event.attempts,\n maxAttempts: event.maxAttempts,\n idempotencyKey: event.nonce,\n }));\n })\n .mutate(({ uow, retrieveResult }) => {\n if (retrieveResult.length === 0) {\n return;\n }\n for (const event of retrieveResult) {\n uow.update(\"fragno_hooks\", event.id, (b) =>\n b.set({ status: \"processing\", lastAttemptAt: now }).check(),\n );\n }\n })\n .transform(({ retrieveResult }) =>\n retrieveResult.map((event) => ({\n ...event,\n id: new FragnoId({\n externalId: event.id.externalId,\n internalId: event.id.internalId,\n version: event.id.version + 1,\n }),\n })),\n )\n .build();\n },\n\n /**\n * Re-queue hook events that have been stuck in processing for too long.\n */\n requeueStuckProcessingHooks(namespace: string, staleBefore: Date) {\n return this.serviceTx(internalSchema)\n .retrieve((uow) =>\n uow.find(\"fragno_hooks\", (b) =>\n b.whereIndex(\"idx_namespace_status_retry\", (eb) =>\n eb.and(eb(\"namespace\", \"=\", namespace), eb(\"status\", \"=\", \"processing\")),\n ),\n ),\n )\n .transformRetrieve(([events]) => {\n const stuck = events.filter((event) => {\n if (!event.lastAttemptAt) {\n return true;\n }\n return event.lastAttemptAt <= staleBefore;\n });\n\n return stuck.map((event) => ({\n id: event.id,\n hookName: event.hookName,\n attempts: event.attempts,\n maxAttempts: event.maxAttempts,\n lastAttemptAt: event.lastAttemptAt,\n nextRetryAt: event.nextRetryAt,\n }));\n })\n .mutate(({ uow, retrieveResult }) => {\n for (const event of retrieveResult) {\n uow.update(\"fragno_hooks\", event.id, (b) =>\n b.set({ status: \"pending\", nextRetryAt: null }).check(),\n );\n }\n })\n .transform(({ retrieveResult }) => retrieveResult)\n .build();\n },\n\n /**\n * Get the next time a processing hook becomes stale.\n */\n getNextProcessingStaleAt(namespace: string, timeoutMinutes: number, now?: Date) {\n return this.serviceTx(internalSchema)\n .retrieve((uow) =>\n uow.find(\"fragno_hooks\", (b) =>\n b.whereIndex(\"idx_namespace_status_retry\", (eb) =>\n eb.and(eb(\"namespace\", \"=\", namespace), eb(\"status\", \"=\", \"processing\")),\n ),\n ),\n )\n .transformRetrieve(([events]) => {\n if (events.length === 0) {\n return null;\n }\n\n const baseNow = now ?? new Date();\n const nowMs = baseNow.getTime();\n const timeoutMs = timeoutMinutes * 60_000;\n let earliestStaleAt: Date | null = null;\n\n for (const event of events) {\n if (!event.lastAttemptAt) {\n return baseNow;\n }\n\n const staleAtMs = event.lastAttemptAt.getTime() + timeoutMs;\n if (staleAtMs <= nowMs) {\n return baseNow;\n }\n\n const staleAt = new Date(staleAtMs);\n if (!earliestStaleAt || staleAt < earliestStaleAt) {\n earliestStaleAt = staleAt;\n }\n }\n\n return earliestStaleAt;\n })\n .build();\n },\n\n /**\n * Get the earliest pending hook wake time for a namespace.\n * Optionally considers processing hooks becoming stale when timeoutMinutes is provided.\n */\n getNextHookWakeAt(namespace: string, timeoutMinutes?: number | false, now?: Date) {\n const baseNow = now ?? new Date();\n const includeProcessing = typeof timeoutMinutes === \"number\" && timeoutMinutes > 0;\n const timeoutMs = includeProcessing ? timeoutMinutes * 60_000 : 0;\n\n return this.serviceTx(internalSchema)\n .retrieve((uow) =>\n uow.find(\"fragno_hooks\", (b) =>\n b\n .whereIndex(\"idx_namespace_status_retry\", (eb) => {\n if (includeProcessing) {\n return eb.and(\n eb(\"namespace\", \"=\", namespace),\n eb.or(eb(\"status\", \"=\", \"pending\"), eb(\"status\", \"=\", \"processing\")),\n );\n }\n return eb.and(eb(\"namespace\", \"=\", namespace), eb(\"status\", \"=\", \"pending\"));\n })\n .select([\"status\", \"nextRetryAt\", \"lastAttemptAt\"]),\n ),\n )\n .transformRetrieve(([events]) => {\n if (events.length === 0) {\n return null;\n }\n\n const nowMs = baseNow.getTime();\n let earliestPendingAt: Date | null = null;\n let earliestStaleAt: Date | null = null;\n\n for (const event of events) {\n if (event.status === \"pending\") {\n const nextRetryAt = event.nextRetryAt;\n if (!nextRetryAt || nextRetryAt.getTime() <= nowMs) {\n return baseNow;\n }\n if (!earliestPendingAt || nextRetryAt < earliestPendingAt) {\n earliestPendingAt = nextRetryAt;\n }\n continue;\n }\n\n if (!includeProcessing || event.status !== \"processing\") {\n continue;\n }\n\n const lastAttemptAt = event.lastAttemptAt;\n if (!lastAttemptAt) {\n return baseNow;\n }\n\n const staleAtMs = lastAttemptAt.getTime() + timeoutMs;\n if (staleAtMs <= nowMs) {\n return baseNow;\n }\n\n const staleAt = new Date(staleAtMs);\n if (!earliestStaleAt || staleAt < earliestStaleAt) {\n earliestStaleAt = staleAt;\n }\n }\n\n if (!earliestPendingAt) {\n return earliestStaleAt ?? null;\n }\n if (!earliestStaleAt) {\n return earliestPendingAt;\n }\n return earliestPendingAt <= earliestStaleAt ? earliestPendingAt : earliestStaleAt;\n })\n .build();\n },\n\n /**\n * Mark a hook event as completed.\n */\n markHookCompleted(eventId: FragnoId) {\n return this.serviceTx(internalSchema)\n .mutate(({ uow }) =>\n uow.update(\"fragno_hooks\", eventId, (b) =>\n b.set({ status: \"completed\", lastAttemptAt: dbNow() }).check(),\n ),\n )\n .build();\n },\n\n /**\n * Mark a hook event as failed and schedule next retry.\n */\n markHookFailed(\n eventId: FragnoId,\n error: string,\n attempts: number,\n retryPolicy: RetryPolicy,\n now?: Date,\n ) {\n const newAttempts = attempts + 1;\n const shouldRetry = retryPolicy.shouldRetry(newAttempts - 1);\n\n return this.serviceTx(internalSchema)\n .mutate(({ uow }) => {\n if (shouldRetry) {\n const delayMs = retryPolicy.getDelayMs(newAttempts - 1);\n const baseNow = now ?? new Date();\n const nextRetryAt = new Date(baseNow.getTime() + delayMs);\n uow.update(\"fragno_hooks\", eventId, (b) =>\n b\n .set({\n status: \"pending\",\n attempts: newAttempts,\n lastAttemptAt: dbNow(),\n nextRetryAt,\n error,\n })\n .check(),\n );\n } else {\n uow.update(\"fragno_hooks\", eventId, (b) =>\n b\n .set({\n status: \"failed\",\n attempts: newAttempts,\n lastAttemptAt: dbNow(),\n error,\n })\n .check(),\n );\n }\n })\n .build();\n },\n\n /**\n * Mark a hook event as processing (to prevent concurrent execution).\n */\n markHookProcessing(eventId: FragnoId) {\n return this.serviceTx(internalSchema)\n .mutate(({ uow }) =>\n uow.update(\"fragno_hooks\", eventId, (b) =>\n b.set({ status: \"processing\", lastAttemptAt: dbNow() }).check(),\n ),\n )\n .build();\n },\n\n /**\n * Get a hook event by ID (for testing/verification purposes).\n */\n getHookById(eventId: FragnoId) {\n return this.serviceTx(internalSchema)\n .retrieve((uow) =>\n uow.findFirst(\"fragno_hooks\", (b) =>\n b.whereIndex(\"primary\", (eb) => eb(\"id\", \"=\", eventId)),\n ),\n )\n .transformRetrieve(([result]) => result ?? undefined)\n .build();\n },\n\n /**\n * Get all hook events for a namespace (for testing/verification purposes).\n */\n getHooksByNamespace(namespace: string) {\n return this.serviceTx(internalSchema)\n .retrieve((uow) =>\n uow.find(\"fragno_hooks\", (b) =>\n b.whereIndex(\"idx_namespace_status_retry\", (eb) => eb(\"namespace\", \"=\", namespace)),\n ),\n )\n .transformRetrieve(([events]) => events)\n .build();\n },\n });\n })\n .providesService(\"outboxService\", ({ defineService }) => {\n return defineService({\n /**\n * List outbox entries ordered by versionstamp (ascending).\n */\n list({ afterVersionstamp, limit }: { afterVersionstamp?: string; limit?: number } = {}) {\n const afterValue = afterVersionstamp?.toLowerCase();\n\n return this.serviceTx(internalSchema)\n .retrieve((uow) =>\n uow.find(\"fragno_db_outbox\", (b) => {\n let builder = afterValue\n ? b.whereIndex(\"idx_outbox_versionstamp\", (eb) =>\n eb(\"versionstamp\", \">\", afterValue),\n )\n : b.whereIndex(\"idx_outbox_versionstamp\");\n\n builder = builder.orderByIndex(\"idx_outbox_versionstamp\", \"asc\");\n if (limit !== undefined) {\n builder = builder.pageSize(limit);\n }\n return builder;\n }),\n )\n .transformRetrieve(([entries]) =>\n entries.map((entry) => ({\n id: entry.id,\n versionstamp: entry.versionstamp,\n uowId: entry.uowId,\n payload: entry.payload,\n refMap: entry.refMap ?? undefined,\n createdAt: entry.createdAt,\n })),\n )\n .build();\n },\n });\n })\n .build();\n\n/**\n * Type representing an instantiated internal fragment.\n * This is the fragment that manages Fragno's internal settings table.\n */\nexport type InternalFragmentInstance = InstantiatedFragmentFromDefinition<\n typeof internalFragmentDef\n>;\n\nexport async function getSchemaVersionFromDatabase(\n fragment: InternalFragmentInstance,\n namespace: string,\n): Promise<number> {\n try {\n const readSchemaVersion = async (targetNamespace: string): Promise<number | undefined> => {\n const setting = await fragment.inContext(async function () {\n return await this.handlerTx()\n .withServiceCalls(\n () =>\n [fragment.services.settingsService.get(targetNamespace, \"schema_version\")] as const,\n )\n .transform(({ serviceResult: [result] }) => result)\n .execute();\n });\n if (!setting) {\n return undefined;\n }\n const parsed = parseInt(setting.value, 10);\n return Number.isNaN(parsed) ? undefined : parsed;\n };\n\n const primary = await readSchemaVersion(namespace);\n if (primary !== undefined) {\n return primary;\n }\n\n // Back-compat: some installs stored internal schema version under a different namespace.\n // Check the alternate key (empty string ↔ schema name) so we find the version either way.\n const legacyNamespace =\n namespace === \"\" ? internalSchema.name : namespace === internalSchema.name ? \"\" : null;\n if (legacyNamespace !== null) {\n const legacy = await readSchemaVersion(legacyNamespace);\n if (legacy !== undefined) {\n return legacy;\n }\n }\n\n return 0;\n } catch {\n return 0;\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import { defineRoutes } from "../packages/fragno/dist/api/route.js";
2
+ import { internalFragmentDef } from "./internal-fragment.js";
3
+
4
+ //#region src/fragments/internal-fragment.routes.ts
5
+ const internalFragmentRoutes = defineRoutes(internalFragmentDef).create(({ defineRoute, services }) => [defineRoute({
6
+ method: "GET",
7
+ path: "/outbox",
8
+ handler: async function(input, { json }) {
9
+ const afterVersionstamp = input.query.get("afterVersionstamp") ?? void 0;
10
+ const limitValue = input.query.get("limit");
11
+ let limit;
12
+ if (limitValue !== null) {
13
+ const parsed = Number.parseInt(limitValue, 10);
14
+ if (!Number.isFinite(parsed) || parsed < 1) return json({
15
+ error: "Invalid limit query parameter.",
16
+ code: "INVALID_LIMIT"
17
+ }, { status: 400 });
18
+ limit = parsed;
19
+ }
20
+ return json(await this.handlerTx().withServiceCalls(() => [services.outboxService.list({
21
+ afterVersionstamp,
22
+ limit
23
+ })]).transform(({ serviceResult: [result] }) => result).execute());
24
+ }
25
+ })]);
26
+
27
+ //#endregion
28
+ export { internalFragmentRoutes };
29
+ //# sourceMappingURL=internal-fragment.routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"internal-fragment.routes.js","names":["limit: number | undefined"],"sources":["../../src/fragments/internal-fragment.routes.ts"],"sourcesContent":["import { defineRoutes } from \"@fragno-dev/core\";\nimport { internalFragmentDef } from \"./internal-fragment\";\n\nexport const internalFragmentRoutes = defineRoutes(internalFragmentDef).create(\n ({ defineRoute, services }) => [\n defineRoute({\n method: \"GET\",\n path: \"/outbox\",\n handler: async function (input, { json }) {\n // We intentionally skip input/output schemas here to keep the internal route lightweight.\n // Query params are validated manually and the response shape is stable (OutboxEntry[]),\n // while the public API surface is still gated behind adapter config.\n const afterVersionstamp = input.query.get(\"afterVersionstamp\") ?? undefined;\n const limitValue = input.query.get(\"limit\");\n let limit: number | undefined;\n\n if (limitValue !== null) {\n const parsed = Number.parseInt(limitValue, 10);\n if (!Number.isFinite(parsed) || parsed < 1) {\n return json(\n {\n error: \"Invalid limit query parameter.\",\n code: \"INVALID_LIMIT\",\n },\n { status: 400 },\n );\n }\n limit = parsed;\n }\n\n const entries = await this.handlerTx()\n .withServiceCalls(\n () => [services.outboxService.list({ afterVersionstamp, limit })] as const,\n )\n .transform(({ serviceResult: [result] }) => result)\n .execute();\n\n return json(entries);\n },\n }),\n ],\n);\n"],"mappings":";;;;AAGA,MAAa,yBAAyB,aAAa,oBAAoB,CAAC,QACrE,EAAE,aAAa,eAAe,CAC7B,YAAY;CACV,QAAQ;CACR,MAAM;CACN,SAAS,eAAgB,OAAO,EAAE,QAAQ;EAIxC,MAAM,oBAAoB,MAAM,MAAM,IAAI,oBAAoB,IAAI;EAClE,MAAM,aAAa,MAAM,MAAM,IAAI,QAAQ;EAC3C,IAAIA;AAEJ,MAAI,eAAe,MAAM;GACvB,MAAM,SAAS,OAAO,SAAS,YAAY,GAAG;AAC9C,OAAI,CAAC,OAAO,SAAS,OAAO,IAAI,SAAS,EACvC,QAAO,KACL;IACE,OAAO;IACP,MAAM;IACP,EACD,EAAE,QAAQ,KAAK,CAChB;AAEH,WAAQ;;AAUV,SAAO,KAPS,MAAM,KAAK,WAAW,CACnC,uBACO,CAAC,SAAS,cAAc,KAAK;GAAE;GAAmB;GAAO,CAAC,CAAC,CAClE,CACA,WAAW,EAAE,eAAe,CAAC,cAAc,OAAO,CAClD,SAAS,CAEQ;;CAEvB,CAAC,CACH,CACF"}
@@ -0,0 +1,9 @@
1
+ import { DbNow } from "../query/db-now.js";
2
+ import { AnyColumn, AnyRelation, Column, FragnoId, IdColumn, Index, Schema, Table } from "../schema/create.js";
3
+ import "../mod.js";
4
+
5
+ //#region src/fragments/internal-fragment.schema.d.ts
6
+ declare const internalSchema: Schema<Record<"fragno_db_settings", Table<Record<string, AnyColumn> & Record<"id", IdColumn<"varchar(30)", string | FragnoId | null, FragnoId>> & Record<"key", Column<"string", string, string>> & Record<"value", Column<"string", string, string>>, Record<string, AnyRelation>, Record<string, Index<AnyColumn[], readonly string[]>> & Record<"unique_key", Index<readonly [Column<"string", string, string>] & AnyColumn[], readonly ["key"]>>>> & Record<"fragno_hooks", Table<Record<string, AnyColumn> & Record<"id", IdColumn<"varchar(30)", string | FragnoId | null, FragnoId>> & Record<"namespace", Column<"string", string, string>> & Record<"hookName", Column<"string", string, string>> & Record<"payload", Column<"json", unknown, unknown>> & Record<"status", Column<"string", string, string>> & Record<"attempts", Column<"integer", number | null, number>> & Record<"maxAttempts", Column<"integer", number | null, number>> & Record<"lastAttemptAt", Column<"timestamp", (DbNow | Date) | null, Date | null>> & Record<"nextRetryAt", Column<"timestamp", (DbNow | Date) | null, Date | null>> & Record<"error", Column<"string", string | null, string | null>> & Record<"createdAt", Column<"timestamp", (DbNow | Date) | null, Date>> & Record<"nonce", Column<"string", string, string>>, Record<string, AnyRelation>, Record<string, Index<AnyColumn[], readonly string[]>> & Record<"idx_namespace_status_retry", Index<readonly [Column<"string", string, string>, Column<"string", string, string>, Column<"timestamp", (DbNow | Date) | null, Date | null>] & AnyColumn[], readonly ["namespace", "status", "nextRetryAt"]>> & Record<"idx_nonce", Index<readonly [Column<"string", string, string>] & AnyColumn[], readonly ["nonce"]>>>> & Record<"fragno_db_outbox", Table<Record<string, AnyColumn> & Record<"id", IdColumn<"varchar(30)", string | FragnoId | null, FragnoId>> & Record<"versionstamp", Column<"string", string, string>> & Record<"uowId", Column<"string", string, string>> & Record<"payload", Column<"json", unknown, unknown>> & Record<"refMap", Column<"json", unknown, unknown>> & Record<"createdAt", Column<"timestamp", (DbNow | Date) | null, Date>>, Record<string, AnyRelation>, Record<string, Index<AnyColumn[], readonly string[]>> & Record<"idx_outbox_versionstamp", Index<readonly [Column<"string", string, string>] & AnyColumn[], readonly ["versionstamp"]>> & Record<"idx_outbox_uow", Index<readonly [Column<"string", string, string>] & AnyColumn[], readonly ["uowId"]>>>>>;
7
+ //#endregion
8
+ export { internalSchema };
9
+ //# sourceMappingURL=internal-fragment.schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"internal-fragment.schema.d.ts","names":[],"sources":["../../src/fragments/internal-fragment.schema.ts"],"sourcesContent":[],"mappings":";;;;;cAOa,gBAAc,OAAA,6BAAA,MAAA,eA2CzB,SAAA,IA3CyB,aAAA,iCAAA,QAAA,SAAA,QAAA,KAAA,cAAA,oCAAA,gBAAA,mCAAA,eAAA,WAAA,GAAA,eAAA,MAAA,SAAA,0BAAA,qBAAA,gBAAA,oCAAA,SAAA,2BAAA,uBAAA,MAAA,eAAA,SAAA,IAAA,aAAA,iCAAA,QAAA,SAAA,QAAA,KAAA,oBAAA,oCAAA,mBAAA,oCAAA,kBAAA,oCAAA,iBAAA,oCAAA,mBAAA,4CAAA,sBAAA,4CAAA,wBAAA,qBAAA,KAAA,GAAA,cAAA,gBAAA,sBAAA,qBAAA,KAAA,GAAA,cAAA,gBAAA,gBAAA,kDAAA,oBAAA,qBAAA,KAAA,GAAA,cAAA,SAAA,gBAAA,mCAAA,eAAA,WAAA,GAAA,eAAA,MAAA,SAAA,0BAAA,qCAAA,gBAAA,kCAAA,kCAAA,qBAAA,KAAA,GAAA,cAAA,gBAAA,SAAA,wDAAA,oBAAA,gBAAA,oCAAA,SAAA,6BAAA,2BAAA,MAAA,eAAA,SAAA,IAAA,aAAA,iCAAA,QAAA,SAAA,QAAA,KAAA,uBAAA,oCAAA,gBAAA,oCAAA,kBAAA,oCAAA,iBAAA,oCAAA,oBAAA,qBAAA,KAAA,GAAA,cAAA,QAAA,eAAA,WAAA,GAAA,eAAA,MAAA,SAAA,0BAAA,kCAAA,gBAAA,oCAAA,SAAA,kCAAA,yBAAA,gBAAA,oCAAA,SAAA"}
@@ -0,0 +1,22 @@
1
+ import { column, idColumn, schema } from "../schema/create.js";
2
+
3
+ //#region src/fragments/internal-fragment.schema.ts
4
+ const SETTINGS_TABLE_NAME = "fragno_db_settings";
5
+ const SETTINGS_NAMESPACE = "fragno-db-settings";
6
+ const internalSchema = schema("fragno_internal", (s) => {
7
+ return s.addTable(SETTINGS_TABLE_NAME, (t) => {
8
+ return t.addColumn("id", idColumn()).addColumn("key", column("string")).addColumn("value", column("string")).createIndex("unique_key", ["key"], { unique: true });
9
+ }).addTable("fragno_hooks", (t) => {
10
+ return t.addColumn("id", idColumn()).addColumn("namespace", column("string")).addColumn("hookName", column("string")).addColumn("payload", column("json")).addColumn("status", column("string")).addColumn("attempts", column("integer").defaultTo(0)).addColumn("maxAttempts", column("integer").defaultTo(5)).addColumn("lastAttemptAt", column("timestamp").nullable()).addColumn("nextRetryAt", column("timestamp").nullable()).addColumn("error", column("string").nullable()).addColumn("createdAt", column("timestamp").defaultTo((b) => b.now())).addColumn("nonce", column("string")).createIndex("idx_namespace_status_retry", [
11
+ "namespace",
12
+ "status",
13
+ "nextRetryAt"
14
+ ]).createIndex("idx_nonce", ["nonce"]);
15
+ }).addTable("fragno_db_outbox", (t) => {
16
+ return t.addColumn("id", idColumn()).addColumn("versionstamp", column("string")).addColumn("uowId", column("string")).addColumn("payload", column("json")).addColumn("refMap", column("json").nullable()).addColumn("createdAt", column("timestamp").defaultTo((b) => b.now())).createIndex("idx_outbox_versionstamp", ["versionstamp"], { unique: true }).createIndex("idx_outbox_uow", ["uowId"]);
17
+ });
18
+ });
19
+
20
+ //#endregion
21
+ export { SETTINGS_NAMESPACE, SETTINGS_TABLE_NAME, internalSchema };
22
+ //# sourceMappingURL=internal-fragment.schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"internal-fragment.schema.js","names":[],"sources":["../../src/fragments/internal-fragment.schema.ts"],"sourcesContent":["import { schema, idColumn, column } from \"../schema/create\";\n\n// Constants for Fragno's internal settings table\nexport const SETTINGS_TABLE_NAME = \"fragno_db_settings\" as const;\n// FIXME: In some places we simply use empty string \"\" as namespace, which is not correct.\nexport const SETTINGS_NAMESPACE = \"fragno-db-settings\" as const;\n\nexport const internalSchema = schema(\"fragno_internal\", (s) => {\n return s\n .addTable(SETTINGS_TABLE_NAME, (t) => {\n return t\n .addColumn(\"id\", idColumn())\n .addColumn(\"key\", column(\"string\"))\n .addColumn(\"value\", column(\"string\"))\n .createIndex(\"unique_key\", [\"key\"], { unique: true });\n })\n .addTable(\"fragno_hooks\", (t) => {\n return t\n .addColumn(\"id\", idColumn())\n .addColumn(\"namespace\", column(\"string\"))\n .addColumn(\"hookName\", column(\"string\"))\n .addColumn(\"payload\", column(\"json\"))\n .addColumn(\"status\", column(\"string\")) // \"pending\" | \"processing\" | \"completed\" | \"failed\"\n .addColumn(\"attempts\", column(\"integer\").defaultTo(0))\n .addColumn(\"maxAttempts\", column(\"integer\").defaultTo(5))\n .addColumn(\"lastAttemptAt\", column(\"timestamp\").nullable())\n .addColumn(\"nextRetryAt\", column(\"timestamp\").nullable())\n .addColumn(\"error\", column(\"string\").nullable())\n .addColumn(\n \"createdAt\",\n column(\"timestamp\").defaultTo((b) => b.now()),\n )\n .addColumn(\"nonce\", column(\"string\"))\n .createIndex(\"idx_namespace_status_retry\", [\"namespace\", \"status\", \"nextRetryAt\"])\n .createIndex(\"idx_nonce\", [\"nonce\"]);\n })\n .addTable(\"fragno_db_outbox\", (t) => {\n return t\n .addColumn(\"id\", idColumn())\n .addColumn(\"versionstamp\", column(\"string\"))\n .addColumn(\"uowId\", column(\"string\"))\n .addColumn(\"payload\", column(\"json\"))\n .addColumn(\"refMap\", column(\"json\").nullable())\n .addColumn(\n \"createdAt\",\n column(\"timestamp\").defaultTo((b) => b.now()),\n )\n .createIndex(\"idx_outbox_versionstamp\", [\"versionstamp\"], { unique: true })\n .createIndex(\"idx_outbox_uow\", [\"uowId\"]);\n });\n});\n"],"mappings":";;;AAGA,MAAa,sBAAsB;AAEnC,MAAa,qBAAqB;AAElC,MAAa,iBAAiB,OAAO,oBAAoB,MAAM;AAC7D,QAAO,EACJ,SAAS,sBAAsB,MAAM;AACpC,SAAO,EACJ,UAAU,MAAM,UAAU,CAAC,CAC3B,UAAU,OAAO,OAAO,SAAS,CAAC,CAClC,UAAU,SAAS,OAAO,SAAS,CAAC,CACpC,YAAY,cAAc,CAAC,MAAM,EAAE,EAAE,QAAQ,MAAM,CAAC;GACvD,CACD,SAAS,iBAAiB,MAAM;AAC/B,SAAO,EACJ,UAAU,MAAM,UAAU,CAAC,CAC3B,UAAU,aAAa,OAAO,SAAS,CAAC,CACxC,UAAU,YAAY,OAAO,SAAS,CAAC,CACvC,UAAU,WAAW,OAAO,OAAO,CAAC,CACpC,UAAU,UAAU,OAAO,SAAS,CAAC,CACrC,UAAU,YAAY,OAAO,UAAU,CAAC,UAAU,EAAE,CAAC,CACrD,UAAU,eAAe,OAAO,UAAU,CAAC,UAAU,EAAE,CAAC,CACxD,UAAU,iBAAiB,OAAO,YAAY,CAAC,UAAU,CAAC,CAC1D,UAAU,eAAe,OAAO,YAAY,CAAC,UAAU,CAAC,CACxD,UAAU,SAAS,OAAO,SAAS,CAAC,UAAU,CAAC,CAC/C,UACC,aACA,OAAO,YAAY,CAAC,WAAW,MAAM,EAAE,KAAK,CAAC,CAC9C,CACA,UAAU,SAAS,OAAO,SAAS,CAAC,CACpC,YAAY,8BAA8B;GAAC;GAAa;GAAU;GAAc,CAAC,CACjF,YAAY,aAAa,CAAC,QAAQ,CAAC;GACtC,CACD,SAAS,qBAAqB,MAAM;AACnC,SAAO,EACJ,UAAU,MAAM,UAAU,CAAC,CAC3B,UAAU,gBAAgB,OAAO,SAAS,CAAC,CAC3C,UAAU,SAAS,OAAO,SAAS,CAAC,CACpC,UAAU,WAAW,OAAO,OAAO,CAAC,CACpC,UAAU,UAAU,OAAO,OAAO,CAAC,UAAU,CAAC,CAC9C,UACC,aACA,OAAO,YAAY,CAAC,WAAW,MAAM,EAAE,KAAK,CAAC,CAC9C,CACA,YAAY,2BAA2B,CAAC,eAAe,EAAE,EAAE,QAAQ,MAAM,CAAC,CAC1E,YAAY,kBAAkB,CAAC,QAAQ,CAAC;GAC3C;EACJ"}
@@ -0,0 +1,14 @@
1
+ import { AnySchema } from "../schema/create.js";
2
+ import { AnyFragnoInstantiatedDatabaseFragment } from "../mod.js";
3
+
4
+ //#region src/hooks/durable-hooks-processor.d.ts
5
+ type DurableHooksProcessor = {
6
+ process: () => Promise<number>;
7
+ getNextWakeAt: () => Promise<Date | null>;
8
+ drain: () => Promise<void>;
9
+ namespace: string;
10
+ };
11
+ declare function createDurableHooksProcessor<TSchema extends AnySchema>(fragment: AnyFragnoInstantiatedDatabaseFragment<TSchema>): DurableHooksProcessor | null;
12
+ //#endregion
13
+ export { DurableHooksProcessor, createDurableHooksProcessor };
14
+ //# sourceMappingURL=durable-hooks-processor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"durable-hooks-processor.d.ts","names":[],"sources":["../../src/hooks/durable-hooks-processor.ts"],"sourcesContent":[],"mappings":";;;;KAIY,qBAAA;iBACK;EADL,aAAA,EAAA,GAAA,GAEW,OAFU,CAEF,IAFE,GAAA,IAAA,CAAA;EAChB,KAAA,EAAA,GAAA,GAEF,OAFE,CAAA,IAAA,CAAA;EACc,SAAA,EAAA,MAAA;CAAR;AACR,iBAoBC,2BApBD,CAAA,gBAoB6C,SApB7C,CAAA,CAAA,QAAA,EAqBH,qCArBG,CAqBmC,OArBnC,CAAA,CAAA,EAsBZ,qBAtBY,GAAA,IAAA"}
@@ -0,0 +1,32 @@
1
+ import { createHookScheduler } from "./hooks.js";
2
+
3
+ //#region src/hooks/durable-hooks-processor.ts
4
+ const DEFAULT_STUCK_PROCESSING_TIMEOUT_MINUTES = 10;
5
+ function resolveStuckProcessingTimeoutMinutes(value) {
6
+ if (value === false) return false;
7
+ if (typeof value === "number") return value > 0 ? value : false;
8
+ return DEFAULT_STUCK_PROCESSING_TIMEOUT_MINUTES;
9
+ }
10
+ function createDurableHooksProcessor(fragment) {
11
+ const durableHooks = fragment.$internal.durableHooks;
12
+ if (!durableHooks) return null;
13
+ const { namespace, internalFragment } = durableHooks;
14
+ const stuckProcessingTimeoutMinutes = resolveStuckProcessingTimeoutMinutes(durableHooks.stuckProcessingTimeoutMinutes);
15
+ const scheduler = durableHooks.scheduler ?? (durableHooks.scheduler = createHookScheduler(durableHooks));
16
+ return {
17
+ namespace,
18
+ process: async () => scheduler.schedule(),
19
+ drain: async () => scheduler.drain(),
20
+ getNextWakeAt: async () => {
21
+ const services = internalFragment.services;
22
+ const now = services.getDbNow ? await services.getDbNow() : /* @__PURE__ */ new Date();
23
+ return await internalFragment.inContext(async function() {
24
+ return await this.handlerTx().withServiceCalls(() => [internalFragment.services.hookService.getNextHookWakeAt(namespace, stuckProcessingTimeoutMinutes, now)]).transform(({ serviceResult: [result] }) => result).execute();
25
+ });
26
+ }
27
+ };
28
+ }
29
+
30
+ //#endregion
31
+ export { createDurableHooksProcessor };
32
+ //# sourceMappingURL=durable-hooks-processor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"durable-hooks-processor.js","names":[],"sources":["../../src/hooks/durable-hooks-processor.ts"],"sourcesContent":["import type { AnySchema } from \"../schema/create\";\nimport type { AnyFragnoInstantiatedDatabaseFragment } from \"../mod\";\nimport { createHookScheduler, type HookProcessorConfig } from \"./hooks\";\n\nexport type DurableHooksProcessor = {\n process: () => Promise<number>;\n getNextWakeAt: () => Promise<Date | null>;\n drain: () => Promise<void>;\n namespace: string;\n};\n\ntype DurableHooksInternal = {\n durableHooks?: HookProcessorConfig;\n};\n\nconst DEFAULT_STUCK_PROCESSING_TIMEOUT_MINUTES = 10;\n\nfunction resolveStuckProcessingTimeoutMinutes(value: number | false | undefined): number | false {\n if (value === false) {\n return false;\n }\n if (typeof value === \"number\") {\n return value > 0 ? value : false;\n }\n return DEFAULT_STUCK_PROCESSING_TIMEOUT_MINUTES;\n}\n\nexport function createDurableHooksProcessor<TSchema extends AnySchema>(\n fragment: AnyFragnoInstantiatedDatabaseFragment<TSchema>,\n): DurableHooksProcessor | null {\n const durableHooks = (fragment.$internal as DurableHooksInternal).durableHooks;\n if (!durableHooks) {\n return null;\n }\n\n const { namespace, internalFragment } = durableHooks;\n const stuckProcessingTimeoutMinutes = resolveStuckProcessingTimeoutMinutes(\n durableHooks.stuckProcessingTimeoutMinutes,\n );\n const scheduler =\n durableHooks.scheduler ?? (durableHooks.scheduler = createHookScheduler(durableHooks));\n\n return {\n namespace,\n process: async () => scheduler.schedule(),\n drain: async () => scheduler.drain(),\n getNextWakeAt: async () => {\n const services = internalFragment.services as { getDbNow?: () => Promise<Date> };\n const now = services.getDbNow ? await services.getDbNow() : new Date();\n return await internalFragment.inContext(async function () {\n return await this.handlerTx()\n .withServiceCalls(\n () =>\n [\n internalFragment.services.hookService.getNextHookWakeAt(\n namespace,\n stuckProcessingTimeoutMinutes,\n now,\n ),\n ] as const,\n )\n .transform(({ serviceResult: [result] }) => result)\n .execute();\n });\n },\n };\n}\n"],"mappings":";;;AAeA,MAAM,2CAA2C;AAEjD,SAAS,qCAAqC,OAAmD;AAC/F,KAAI,UAAU,MACZ,QAAO;AAET,KAAI,OAAO,UAAU,SACnB,QAAO,QAAQ,IAAI,QAAQ;AAE7B,QAAO;;AAGT,SAAgB,4BACd,UAC8B;CAC9B,MAAM,eAAgB,SAAS,UAAmC;AAClE,KAAI,CAAC,aACH,QAAO;CAGT,MAAM,EAAE,WAAW,qBAAqB;CACxC,MAAM,gCAAgC,qCACpC,aAAa,8BACd;CACD,MAAM,YACJ,aAAa,cAAc,aAAa,YAAY,oBAAoB,aAAa;AAEvF,QAAO;EACL;EACA,SAAS,YAAY,UAAU,UAAU;EACzC,OAAO,YAAY,UAAU,OAAO;EACpC,eAAe,YAAY;GACzB,MAAM,WAAW,iBAAiB;GAClC,MAAM,MAAM,SAAS,WAAW,MAAM,SAAS,UAAU,mBAAG,IAAI,MAAM;AACtE,UAAO,MAAM,iBAAiB,UAAU,iBAAkB;AACxD,WAAO,MAAM,KAAK,WAAW,CAC1B,uBAEG,CACE,iBAAiB,SAAS,YAAY,kBACpC,WACA,+BACA,IACD,CACF,CACJ,CACA,WAAW,EAAE,eAAe,CAAC,cAAc,OAAO,CAClD,SAAS;KACZ;;EAEL"}
@@ -1,4 +1,6 @@
1
+ import { FragnoId } from "../schema/create.js";
1
2
  import { RetryPolicy } from "../query/unit-of-work/retry-policy.js";
3
+ import { ExecuteTxOptions, HandlerTxBuilder } from "../query/unit-of-work/execute-unit-of-work.js";
2
4
  import "../fragments/internal-fragment.js";
3
5
  import "../query/unit-of-work/unit-of-work.js";
4
6
 
@@ -14,6 +16,10 @@ interface HookContext {
14
16
  * Use this for idempotency checks in your hook implementation.
15
17
  */
16
18
  idempotencyKey: string;
19
+ /**
20
+ * Create a handler transaction builder to run atomic operations.
21
+ */
22
+ handlerTx: HookHandlerTx;
17
23
  }
18
24
  /**
19
25
  * A hook function signature.
@@ -38,6 +44,11 @@ interface TriggerHookOptions {
38
44
  * If not provided, uses the default retry policy.
39
45
  */
40
46
  retryPolicy?: RetryPolicy;
47
+ /**
48
+ * Absolute time for the first attempt. If in the future, the hook is
49
+ * scheduled for that time; if in the past, it runs immediately.
50
+ */
51
+ processAt?: Date;
41
52
  }
42
53
  /**
43
54
  * Internal representation of a triggered hook.
@@ -48,6 +59,36 @@ interface TriggeredHook {
48
59
  payload: unknown;
49
60
  options?: TriggerHookOptions;
50
61
  }
62
+ type StuckHookProcessingTimeoutMinutes = number | false;
63
+ type StuckHookProcessingEvent = {
64
+ id: FragnoId;
65
+ hookName: string;
66
+ attempts: number;
67
+ maxAttempts: number;
68
+ lastAttemptAt: Date | null;
69
+ nextRetryAt: Date | null;
70
+ };
71
+ type StuckHookProcessingInfo = {
72
+ namespace: string;
73
+ timeoutMinutes: number;
74
+ events: StuckHookProcessingEvent[];
75
+ };
76
+ type DurableHooksProcessingOptions = {
77
+ /**
78
+ * Re-queue hooks that have been in `processing` for at least this many minutes.
79
+ * Use `false` to disable stuck-processing recovery entirely.
80
+ * Values <= 0 are treated as `false`.
81
+ *
82
+ * Default: 10 minutes.
83
+ */
84
+ stuckProcessingTimeoutMinutes?: StuckHookProcessingTimeoutMinutes;
85
+ /**
86
+ * Called when stuck processing hooks are detected and re-queued.
87
+ * Invoked after the hooks are moved back to `pending`.
88
+ */
89
+ onStuckProcessingHooks?: (info: StuckHookProcessingInfo) => void;
90
+ };
91
+ type HookHandlerTx = (execOptions?: Omit<ExecuteTxOptions, "createUnitOfWork">) => HandlerTxBuilder<readonly [], [], [], unknown, unknown, false, false, false, false, HooksMap>;
51
92
  //#endregion
52
- export { HookContext, HookFn, HookPayload, HooksMap, TriggerHookOptions, TriggeredHook };
93
+ export { DurableHooksProcessingOptions, HookContext, HookFn, HookPayload, HooksMap, StuckHookProcessingEvent, StuckHookProcessingInfo, StuckHookProcessingTimeoutMinutes, TriggerHookOptions, TriggeredHook };
53
94
  //# sourceMappingURL=hooks.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.d.ts","names":[],"sources":["../../src/hooks/hooks.ts"],"sourcesContent":[],"mappings":";;;;;;;;AAUA;AAYA;AAOY,UAnBK,WAAA,CAmBqB;EAK1B;;;;EAA8C,cAAA,EAAA,MAAA;AAK1D;AAYA;;;;KA7BY,uCAAuC,oBAAoB;;;;;KAO3D,QAAA,GAAW,eAAe;;;;KAK1B,iBAAiB,UAAU,kBAAkB;;;;UAKxC,kBAAA;;;;;gBAKD;;;;;;UAOC,aAAA;;;YAGL"}
1
+ {"version":3,"file":"hooks.d.ts","names":[],"sources":["../../src/hooks/hooks.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;AAeA;AAgBA;AAOY,UAvBK,WAAA,CAuBqB;EAK1B;;;;EAA8C,cAAA,EAAA,MAAA;EAKzC;AAiBjB;AAuCA;EAEY,SAAA,EAlFC,aAkFD;;;;;AASZ;AAMY,KA1FA,MA0FA,CAAA,WAAA,OAA6B,CAAA,GAAA,CAAA,OAQP,EAlGiB,QAkGjB,EAAA,GAAA,IAAA,GAlGqC,OAkGrC,CAAA,IAAA,CAKA;AAGlC;;;;AAEK,KArGO,QAAA,GAAW,MAqGlB,CAAA,MAAA,EArGiC,MAqGjC,CAAA,GAAA,CAAA,CAAA;;;;KAhGO,iBAAiB,UAAU,kBAAkB;;;;UAKxC,kBAAA;;;;;gBAKD;;;;;cAKF;;;;;;UAOG,aAAA;;;YAGL;;KAoCA,iCAAA;KAEA,wBAAA;MACN;;;;iBAIW;eACF;;KAGH,uBAAA;;;UAGF;;KAGE,6BAAA;;;;;;;;kCAQsB;;;;;kCAKA;;KAGtB,aAAA,kBACI,KAAK,0CAChB,oFAAoF"}