@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
@@ -2,17 +2,26 @@ import SQLite from "better-sqlite3";
2
2
  import { SqliteDialect } from "kysely";
3
3
  import { beforeAll, describe, expect, it, vi } from "vitest";
4
4
  import { instantiate } from "@fragno-dev/core";
5
- import { prepareHookMutations, processHooks, type HooksMap, type HookContext } from "./hooks";
5
+ import {
6
+ prepareHookMutations,
7
+ processHooks,
8
+ type HooksMap,
9
+ type HookContext,
10
+ type HookHandlerTx,
11
+ } from "./hooks";
6
12
  import { internalFragmentDef, internalSchema } from "../fragments/internal-fragment";
7
13
  import type { FragnoPublicConfigWithDatabase } from "../db-fragment-definition-builder";
8
- import { DrizzleAdapter } from "../adapters/drizzle/drizzle-adapter";
14
+ import { SqlAdapter } from "../adapters/generic-sql/generic-sql-adapter";
9
15
  import { BetterSQLite3DriverConfig } from "../adapters/generic-sql/driver-config";
10
16
  import { ExponentialBackoffRetryPolicy, NoRetryPolicy } from "../query/unit-of-work/retry-policy";
11
17
  import type { FragnoId } from "../schema/create";
12
18
 
13
19
  describe("Hook System", () => {
20
+ const handlerTx = (() => {
21
+ throw new Error("handlerTx not configured for hooks test");
22
+ }) as HookHandlerTx;
14
23
  let sqliteDatabase: SQLite.Database;
15
- let adapter: DrizzleAdapter;
24
+ let adapter: SqlAdapter;
16
25
  let internalFragment: ReturnType<typeof instantiateFragment>;
17
26
 
18
27
  function instantiateFragment(options: FragnoPublicConfigWithDatabase) {
@@ -26,18 +35,19 @@ describe("Hook System", () => {
26
35
  database: sqliteDatabase,
27
36
  });
28
37
 
29
- adapter = new DrizzleAdapter({
38
+ adapter = new SqlAdapter({
30
39
  dialect,
31
40
  driverConfig: new BetterSQLite3DriverConfig(),
32
41
  });
33
42
 
34
43
  {
35
- const migrations = adapter.prepareMigrations(internalSchema, "");
44
+ const migrations = adapter.prepareMigrations(internalSchema, null);
36
45
  await migrations.executeWithDriver(adapter.driver, 0);
37
46
  }
38
47
 
39
48
  const options: FragnoPublicConfigWithDatabase = {
40
49
  databaseAdapter: adapter,
50
+ databaseNamespace: null,
41
51
  };
42
52
 
43
53
  internalFragment = instantiateFragment(options);
@@ -72,6 +82,7 @@ describe("Hook System", () => {
72
82
  hooks,
73
83
  namespace,
74
84
  internalFragment,
85
+ handlerTx,
75
86
  defaultRetryPolicy: new ExponentialBackoffRetryPolicy({ maxRetries: 5 }),
76
87
  });
77
88
  })
@@ -118,6 +129,7 @@ describe("Hook System", () => {
118
129
  hooks,
119
130
  namespace,
120
131
  internalFragment,
132
+ handlerTx,
121
133
  defaultRetryPolicy: new NoRetryPolicy(),
122
134
  });
123
135
  })
@@ -160,6 +172,7 @@ describe("Hook System", () => {
160
172
  hooks,
161
173
  namespace,
162
174
  internalFragment,
175
+ handlerTx,
163
176
  defaultRetryPolicy: new ExponentialBackoffRetryPolicy({ maxRetries: 10 }),
164
177
  });
165
178
  })
@@ -177,6 +190,84 @@ describe("Hook System", () => {
177
190
 
178
191
  expect(events[0]?.maxAttempts).toBe(1);
179
192
  });
193
+
194
+ it("should set nextRetryAt when processAt is in the future", async () => {
195
+ const namespace = "test-process-at-future";
196
+ const hooks: HooksMap = {
197
+ onScheduled: vi.fn(),
198
+ };
199
+ const futureTime = new Date(Date.now() + 60000);
200
+
201
+ await internalFragment.inContext(async function () {
202
+ await this.handlerTx()
203
+ .mutate(({ forSchema }) => {
204
+ const uow = forSchema(internalSchema, hooks);
205
+
206
+ uow.triggerHook("onScheduled", { data: "test" }, { processAt: futureTime });
207
+
208
+ prepareHookMutations(uow, {
209
+ hooks,
210
+ namespace,
211
+ internalFragment,
212
+ handlerTx,
213
+ defaultRetryPolicy: new ExponentialBackoffRetryPolicy({ maxRetries: 5 }),
214
+ });
215
+ })
216
+ .execute();
217
+ });
218
+
219
+ const events = await internalFragment.inContext(async function () {
220
+ return await this.handlerTx()
221
+ .withServiceCalls(
222
+ () => [internalFragment.services.hookService.getHooksByNamespace(namespace)] as const,
223
+ )
224
+ .transform(({ serviceResult: [result] }) => result)
225
+ .execute();
226
+ });
227
+
228
+ expect(events).toHaveLength(1);
229
+ expect(events[0]?.nextRetryAt).toBeInstanceOf(Date);
230
+ expect(events[0]?.nextRetryAt?.getTime()).toBe(futureTime.getTime());
231
+ });
232
+
233
+ it("should keep processAt in the past while remaining immediately eligible", async () => {
234
+ const namespace = "test-process-at-past";
235
+ const hooks: HooksMap = {
236
+ onImmediate: vi.fn(),
237
+ };
238
+ const pastTime = new Date(Date.now() - 60000);
239
+
240
+ await internalFragment.inContext(async function () {
241
+ await this.handlerTx()
242
+ .mutate(({ forSchema }) => {
243
+ const uow = forSchema(internalSchema, hooks);
244
+
245
+ uow.triggerHook("onImmediate", { data: "test" }, { processAt: pastTime });
246
+
247
+ prepareHookMutations(uow, {
248
+ hooks,
249
+ namespace,
250
+ internalFragment,
251
+ handlerTx,
252
+ defaultRetryPolicy: new ExponentialBackoffRetryPolicy({ maxRetries: 5 }),
253
+ });
254
+ })
255
+ .execute();
256
+ });
257
+
258
+ const events = await internalFragment.inContext(async function () {
259
+ return await this.handlerTx()
260
+ .withServiceCalls(
261
+ () => [internalFragment.services.hookService.getHooksByNamespace(namespace)] as const,
262
+ )
263
+ .transform(({ serviceResult: [result] }) => result)
264
+ .execute();
265
+ });
266
+
267
+ expect(events).toHaveLength(1);
268
+ expect(events[0]?.nextRetryAt).toBeInstanceOf(Date);
269
+ expect(events[0]?.nextRetryAt?.getTime()).toBe(pastTime.getTime());
270
+ });
180
271
  });
181
272
 
182
273
  describe("processHooks", () => {
@@ -216,6 +307,7 @@ describe("Hook System", () => {
216
307
  hooks,
217
308
  namespace,
218
309
  internalFragment,
310
+ handlerTx,
219
311
  defaultRetryPolicy: new ExponentialBackoffRetryPolicy({ maxRetries: 3 }),
220
312
  });
221
313
 
@@ -275,6 +367,7 @@ describe("Hook System", () => {
275
367
  hooks,
276
368
  namespace,
277
369
  internalFragment,
370
+ handlerTx,
278
371
  defaultRetryPolicy: new ExponentialBackoffRetryPolicy({ maxRetries: 3 }),
279
372
  });
280
373
 
@@ -329,6 +422,7 @@ describe("Hook System", () => {
329
422
  hooks,
330
423
  namespace,
331
424
  internalFragment,
425
+ handlerTx,
332
426
  defaultRetryPolicy: new NoRetryPolicy(),
333
427
  });
334
428
 
@@ -379,6 +473,7 @@ describe("Hook System", () => {
379
473
  hooks,
380
474
  namespace,
381
475
  internalFragment,
476
+ handlerTx,
382
477
  defaultRetryPolicy: new NoRetryPolicy(),
383
478
  });
384
479
 
@@ -395,6 +490,68 @@ describe("Hook System", () => {
395
490
  expect(result?.error).toBe("Hook 'onMissing' not found in hooks map");
396
491
  });
397
492
 
493
+ it("should re-queue stuck processing hooks and call the handler", async () => {
494
+ const namespace = "test-stuck-processing";
495
+ const hookFn = vi.fn();
496
+ const hooks: HooksMap = {
497
+ onStuck: hookFn,
498
+ };
499
+ const onStuckProcessingHooks = vi.fn();
500
+
501
+ let eventId: FragnoId;
502
+
503
+ await internalFragment.inContext(async function () {
504
+ const createdId = await this.handlerTx()
505
+ .mutate(({ forSchema }) => {
506
+ const uow = forSchema(internalSchema);
507
+ return uow.create("fragno_hooks", {
508
+ namespace,
509
+ hookName: "onStuck",
510
+ payload: { ok: true },
511
+ status: "processing",
512
+ attempts: 0,
513
+ maxAttempts: 5,
514
+ lastAttemptAt: new Date(Date.now() - 20 * 60_000),
515
+ nextRetryAt: null,
516
+ error: null,
517
+ nonce: "test-nonce",
518
+ });
519
+ })
520
+ .execute();
521
+ eventId = createdId;
522
+ });
523
+
524
+ await processHooks({
525
+ hooks,
526
+ namespace,
527
+ internalFragment,
528
+ handlerTx,
529
+ stuckProcessingTimeoutMinutes: 1,
530
+ onStuckProcessingHooks,
531
+ });
532
+
533
+ expect(hookFn).toHaveBeenCalledOnce();
534
+ expect(onStuckProcessingHooks).toHaveBeenCalledOnce();
535
+ expect(onStuckProcessingHooks).toHaveBeenCalledWith(
536
+ expect.objectContaining({
537
+ namespace,
538
+ timeoutMinutes: 1,
539
+ events: [expect.objectContaining({ hookName: "onStuck" })],
540
+ }),
541
+ );
542
+
543
+ const result = await internalFragment.inContext(async function () {
544
+ return await this.handlerTx()
545
+ .withServiceCalls(
546
+ () => [internalFragment.services.hookService.getHookById(eventId)] as const,
547
+ )
548
+ .transform(({ serviceResult: [event] }) => event)
549
+ .execute();
550
+ });
551
+
552
+ expect(result?.status).toBe("completed");
553
+ });
554
+
398
555
  it("should process multiple hooks in parallel", async () => {
399
556
  const namespace = "test-parallel";
400
557
  const hook1 = vi.fn();
@@ -454,6 +611,7 @@ describe("Hook System", () => {
454
611
  hooks,
455
612
  namespace,
456
613
  internalFragment,
614
+ handlerTx,
457
615
  defaultRetryPolicy: new ExponentialBackoffRetryPolicy({ maxRetries: 3 }),
458
616
  });
459
617
 
@@ -534,6 +692,7 @@ describe("Hook System", () => {
534
692
  hooks,
535
693
  namespace,
536
694
  internalFragment,
695
+ handlerTx,
537
696
  defaultRetryPolicy: new ExponentialBackoffRetryPolicy({ maxRetries: 3 }),
538
697
  });
539
698
 
@@ -570,6 +729,7 @@ describe("Hook System", () => {
570
729
  hooks,
571
730
  namespace,
572
731
  internalFragment,
732
+ handlerTx,
573
733
  defaultRetryPolicy: new ExponentialBackoffRetryPolicy({ maxRetries: 3 }),
574
734
  });
575
735
 
@@ -1,8 +1,13 @@
1
+ import type {
2
+ ExecuteTxOptions,
3
+ HandlerTxBuilder,
4
+ } from "../query/unit-of-work/execute-unit-of-work";
1
5
  import type { RetryPolicy } from "../query/unit-of-work/retry-policy";
2
6
  import { ExponentialBackoffRetryPolicy } from "../query/unit-of-work/retry-policy";
3
7
  import type { IUnitOfWork } from "../query/unit-of-work/unit-of-work";
4
8
  import type { InternalFragmentInstance } from "../fragments/internal-fragment";
5
9
  import type { TxResult } from "../query/unit-of-work/execute-unit-of-work";
10
+ import type { FragnoId } from "../schema/create";
6
11
 
7
12
  /**
8
13
  * Context available in hook functions via `this`.
@@ -14,6 +19,10 @@ export interface HookContext {
14
19
  * Use this for idempotency checks in your hook implementation.
15
20
  */
16
21
  idempotencyKey: string;
22
+ /**
23
+ * Create a handler transaction builder to run atomic operations.
24
+ */
25
+ handlerTx: HookHandlerTx;
17
26
  }
18
27
 
19
28
  /**
@@ -43,6 +52,11 @@ export interface TriggerHookOptions {
43
52
  * If not provided, uses the default retry policy.
44
53
  */
45
54
  retryPolicy?: RetryPolicy;
55
+ /**
56
+ * Absolute time for the first attempt. If in the future, the hook is
57
+ * scheduled for that time; if in the past, it runs immediately.
58
+ */
59
+ processAt?: Date;
46
60
  }
47
61
 
48
62
  /**
@@ -55,14 +69,88 @@ export interface TriggeredHook {
55
69
  options?: TriggerHookOptions;
56
70
  }
57
71
 
72
+ export type HookScheduler = {
73
+ schedule: () => Promise<number>;
74
+ drain: () => Promise<void>;
75
+ };
76
+
58
77
  /**
59
78
  * Configuration for hook processing.
60
79
  */
61
- export interface HookProcessorConfig {
62
- hooks: HooksMap;
80
+ export interface HookProcessorConfig<THooks extends HooksMap = HooksMap> {
81
+ hooks: THooks;
63
82
  namespace: string;
64
83
  internalFragment: InternalFragmentInstance;
84
+ handlerTx: HookHandlerTx;
85
+ /**
86
+ * Internal hook scheduler used to coordinate processing/drain.
87
+ */
88
+ scheduler?: HookScheduler;
65
89
  defaultRetryPolicy?: RetryPolicy;
90
+ /**
91
+ * Re-queue hooks that have been in `processing` for at least this many minutes.
92
+ * Use `false` to disable stuck-processing recovery entirely.
93
+ * Values <= 0 are treated as `false`.
94
+ *
95
+ * Default: 10 minutes.
96
+ */
97
+ stuckProcessingTimeoutMinutes?: StuckHookProcessingTimeoutMinutes;
98
+ /**
99
+ * Called when stuck processing hooks are detected and re-queued.
100
+ * Invoked after the hooks are moved back to `pending`.
101
+ */
102
+ onStuckProcessingHooks?: (info: StuckHookProcessingInfo) => void;
103
+ }
104
+
105
+ export type StuckHookProcessingTimeoutMinutes = number | false;
106
+
107
+ export type StuckHookProcessingEvent = {
108
+ id: FragnoId;
109
+ hookName: string;
110
+ attempts: number;
111
+ maxAttempts: number;
112
+ lastAttemptAt: Date | null;
113
+ nextRetryAt: Date | null;
114
+ };
115
+
116
+ export type StuckHookProcessingInfo = {
117
+ namespace: string;
118
+ timeoutMinutes: number;
119
+ events: StuckHookProcessingEvent[];
120
+ };
121
+
122
+ export type DurableHooksProcessingOptions = {
123
+ /**
124
+ * Re-queue hooks that have been in `processing` for at least this many minutes.
125
+ * Use `false` to disable stuck-processing recovery entirely.
126
+ * Values <= 0 are treated as `false`.
127
+ *
128
+ * Default: 10 minutes.
129
+ */
130
+ stuckProcessingTimeoutMinutes?: StuckHookProcessingTimeoutMinutes;
131
+ /**
132
+ * Called when stuck processing hooks are detected and re-queued.
133
+ * Invoked after the hooks are moved back to `pending`.
134
+ */
135
+ onStuckProcessingHooks?: (info: StuckHookProcessingInfo) => void;
136
+ };
137
+
138
+ export type HookHandlerTx = (
139
+ execOptions?: Omit<ExecuteTxOptions, "createUnitOfWork">,
140
+ ) => HandlerTxBuilder<readonly [], [], [], unknown, unknown, false, false, false, false, HooksMap>;
141
+
142
+ const DEFAULT_STUCK_PROCESSING_TIMEOUT_MINUTES = 10;
143
+
144
+ function resolveStuckProcessingTimeoutMinutes(
145
+ value: StuckHookProcessingTimeoutMinutes | undefined,
146
+ ): number | false {
147
+ if (value === false) {
148
+ return false;
149
+ }
150
+ if (typeof value === "number") {
151
+ return value > 0 ? value : false;
152
+ }
153
+ return DEFAULT_STUCK_PROCESSING_TIMEOUT_MINUTES;
66
154
  }
67
155
 
68
156
  /**
@@ -70,7 +158,10 @@ export interface HookProcessorConfig {
70
158
  * This should be called before executeMutations() so hook records are created
71
159
  * in the same transaction as the user's mutations.
72
160
  */
73
- export function prepareHookMutations(uow: IUnitOfWork, config: HookProcessorConfig): void {
161
+ export function prepareHookMutations<THooks extends HooksMap>(
162
+ uow: IUnitOfWork,
163
+ config: HookProcessorConfig<THooks>,
164
+ ): void {
74
165
  const { namespace, internalFragment, defaultRetryPolicy } = config;
75
166
  const retryPolicy = defaultRetryPolicy ?? new ExponentialBackoffRetryPolicy({ maxRetries: 5 });
76
167
 
@@ -86,6 +177,8 @@ export function prepareHookMutations(uow: IUnitOfWork, config: HookProcessorConf
86
177
  for (const hook of triggeredHooks) {
87
178
  const hookRetryPolicy = hook.options?.retryPolicy ?? retryPolicy;
88
179
  const maxAttempts = hookRetryPolicy.shouldRetry(4) ? 5 : 1;
180
+ const processAt = hook.options?.processAt ? new Date(hook.options.processAt) : null;
181
+ const nextRetryAt = processAt ?? null;
89
182
  internalUow.create("fragno_hooks", {
90
183
  namespace,
91
184
  hookName: hook.hookName,
@@ -94,7 +187,7 @@ export function prepareHookMutations(uow: IUnitOfWork, config: HookProcessorConf
94
187
  attempts: 0,
95
188
  maxAttempts,
96
189
  lastAttemptAt: null,
97
- nextRetryAt: null,
190
+ nextRetryAt,
98
191
  error: null,
99
192
  nonce: uow.idempotencyKey,
100
193
  });
@@ -105,22 +198,65 @@ export function prepareHookMutations(uow: IUnitOfWork, config: HookProcessorConf
105
198
  * Process pending hook events after the transaction has committed.
106
199
  * This should be called in the onSuccess callback after executeMutations().
107
200
  */
108
- export async function processHooks(config: HookProcessorConfig): Promise<void> {
201
+ export async function processHooks<THooks extends HooksMap>(
202
+ config: HookProcessorConfig<THooks>,
203
+ ): Promise<number> {
109
204
  const { hooks, namespace, internalFragment, defaultRetryPolicy } = config;
110
205
  const retryPolicy = defaultRetryPolicy ?? new ExponentialBackoffRetryPolicy({ maxRetries: 5 });
206
+ const stuckProcessingTimeoutMinutes = resolveStuckProcessingTimeoutMinutes(
207
+ config.stuckProcessingTimeoutMinutes,
208
+ );
209
+ const getDbNow = async () => {
210
+ const services = internalFragment.services as { getDbNow?: () => Promise<Date> };
211
+ if (services.getDbNow) {
212
+ return services.getDbNow();
213
+ }
214
+ return new Date();
215
+ };
216
+ const dbNow = await getDbNow();
111
217
 
112
- // Get pending events
218
+ if (stuckProcessingTimeoutMinutes !== false) {
219
+ const staleBefore = new Date(dbNow.getTime() - stuckProcessingTimeoutMinutes * 60_000);
220
+ const stuckEvents = await internalFragment.inContext(async function () {
221
+ return await this.handlerTx()
222
+ .withServiceCalls(
223
+ () =>
224
+ [
225
+ internalFragment.services.hookService.requeueStuckProcessingHooks(
226
+ namespace,
227
+ staleBefore,
228
+ ),
229
+ ] as const,
230
+ )
231
+ .transform(({ serviceResult: [events] }) => events)
232
+ .execute();
233
+ });
234
+
235
+ if (stuckEvents.length > 0) {
236
+ try {
237
+ config.onStuckProcessingHooks?.({
238
+ namespace,
239
+ timeoutMinutes: stuckProcessingTimeoutMinutes,
240
+ events: stuckEvents,
241
+ });
242
+ } catch (error) {
243
+ console.error("Error calling onStuckProcessingHooks", error);
244
+ }
245
+ }
246
+ }
247
+
248
+ // Claim pending events in the same transaction to avoid double-processing.
113
249
  const pendingEvents = await internalFragment.inContext(async function () {
114
250
  return await this.handlerTx()
115
251
  .withServiceCalls(
116
- () => [internalFragment.services.hookService.getPendingHookEvents(namespace)] as const,
252
+ () => [internalFragment.services.hookService.claimPendingHookEvents(namespace)] as const,
117
253
  )
118
254
  .transform(({ serviceResult: [events] }) => events)
119
255
  .execute();
120
256
  });
121
257
 
122
258
  if (pendingEvents.length === 0) {
123
- return;
259
+ return 0;
124
260
  }
125
261
 
126
262
  // Process events (async work outside transaction)
@@ -138,7 +274,10 @@ export async function processHooks(config: HookProcessorConfig): Promise<void> {
138
274
  }
139
275
 
140
276
  try {
141
- const hookContext: HookContext = { idempotencyKey: event.idempotencyKey };
277
+ const hookContext: HookContext = {
278
+ idempotencyKey: event.idempotencyKey,
279
+ handlerTx: config.handlerTx,
280
+ };
142
281
  await hookFn.call(hookContext, event.payload);
143
282
  return {
144
283
  eventId: event.id,
@@ -179,6 +318,7 @@ export async function processHooks(config: HookProcessorConfig): Promise<void> {
179
318
  error,
180
319
  attempts,
181
320
  retryPolicy,
321
+ dbNow,
182
322
  ),
183
323
  );
184
324
  }
@@ -187,4 +327,52 @@ export async function processHooks(config: HookProcessorConfig): Promise<void> {
187
327
  })
188
328
  .execute();
189
329
  });
330
+
331
+ const processedCount = processedEvents.reduce(
332
+ (count, result) => count + (result.status === "fulfilled" ? 1 : 0),
333
+ 0,
334
+ );
335
+
336
+ return processedCount;
337
+ }
338
+
339
+ export function createHookScheduler(config: HookProcessorConfig): HookScheduler {
340
+ let processing = false;
341
+ let queued = false;
342
+ let currentPromise: Promise<number> | null = null;
343
+
344
+ const schedule = async () => {
345
+ if (processing) {
346
+ queued = true;
347
+ return currentPromise ?? Promise.resolve(0);
348
+ }
349
+
350
+ processing = true;
351
+ currentPromise = (async () => {
352
+ let lastCount = 0;
353
+ try {
354
+ do {
355
+ queued = false;
356
+ lastCount = await processHooks(config);
357
+ } while (queued);
358
+ return lastCount;
359
+ } finally {
360
+ processing = false;
361
+ queued = false;
362
+ }
363
+ })();
364
+
365
+ return currentPromise;
366
+ };
367
+
368
+ const drain = async () => {
369
+ while (true) {
370
+ const processed = await schedule();
371
+ if (processed === 0) {
372
+ return;
373
+ }
374
+ }
375
+ };
376
+
377
+ return { schedule, drain };
190
378
  }
@@ -4,7 +4,7 @@ import { generateMigrationFromSchema } from "./auto-from-schema";
4
4
 
5
5
  describe("generateMigrationFromSchema", () => {
6
6
  it("should generate create-table operation for new tables", () => {
7
- const mySchema = schema((s) => {
7
+ const mySchema = schema("my", (s) => {
8
8
  return s
9
9
  .addTable("users", (t) => {
10
10
  return t.addColumn("id", idColumn()).addColumn("name", column("string"));
@@ -35,7 +35,7 @@ describe("generateMigrationFromSchema", () => {
35
35
  });
36
36
 
37
37
  it("should generate multiple table operations in sequence", () => {
38
- const mySchema = schema((s) => {
38
+ const mySchema = schema("my", (s) => {
39
39
  return s
40
40
  .addTable("users", (t) => {
41
41
  return t.addColumn("id", idColumn()).addColumn("name", column("string"));
@@ -79,7 +79,7 @@ describe("generateMigrationFromSchema", () => {
79
79
  });
80
80
 
81
81
  it("should generate add-foreign-key operation for new foreign keys", () => {
82
- const mySchema = schema((s) => {
82
+ const mySchema = schema("my", (s) => {
83
83
  return s
84
84
  .addTable("users", (t) => {
85
85
  return t.addColumn("id", idColumn());
@@ -108,7 +108,7 @@ describe("generateMigrationFromSchema", () => {
108
108
  const fkOp = operations[0];
109
109
  if (fkOp.type === "add-foreign-key") {
110
110
  expect(fkOp.value).toMatchObject({
111
- name: "posts_users_author_fk",
111
+ name: "author",
112
112
  referencedTable: "users",
113
113
  columns: ["authorId"],
114
114
  referencedColumns: ["_internalId"],
@@ -117,7 +117,7 @@ describe("generateMigrationFromSchema", () => {
117
117
  });
118
118
 
119
119
  it("should generate add-index operation for indexes added via alterTable", () => {
120
- const mySchema = schema((s) => {
120
+ const mySchema = schema("my", (s) => {
121
121
  return s
122
122
  .addTable("users", (t) => {
123
123
  return t.addColumn("id", idColumn()).addColumn("email", column("string"));
@@ -141,7 +141,7 @@ describe("generateMigrationFromSchema", () => {
141
141
  });
142
142
 
143
143
  it("should generate mixed operations for tables and foreign keys", () => {
144
- const mySchema = schema((s) => {
144
+ const mySchema = schema("my", (s) => {
145
145
  return s
146
146
  .addTable("users", (t) => {
147
147
  return t.addColumn("id", idColumn()).addColumn("name", column("string"));
@@ -166,7 +166,7 @@ describe("generateMigrationFromSchema", () => {
166
166
  });
167
167
 
168
168
  it("should generate mixed operations for tables, indexes, and foreign keys", () => {
169
- const mySchema = schema((s) => {
169
+ const mySchema = schema("my", (s) => {
170
170
  return s
171
171
  .addTable("users", (t) => {
172
172
  return t.addColumn("id", idColumn()).addColumn("email", column("string"));
@@ -195,7 +195,7 @@ describe("generateMigrationFromSchema", () => {
195
195
  });
196
196
 
197
197
  it("should generate no operations when version range is empty", () => {
198
- const mySchema = schema((s) => {
198
+ const mySchema = schema("my", (s) => {
199
199
  return s.addTable("users", (t) => {
200
200
  return t.addColumn("id", idColumn()).addColumn("name", column("string"));
201
201
  });
@@ -207,7 +207,7 @@ describe("generateMigrationFromSchema", () => {
207
207
  });
208
208
 
209
209
  it("should throw error when fromVersion exceeds schema version", () => {
210
- const mySchema = schema((s) => s.addTable("users", (t) => t.addColumn("id", idColumn())));
210
+ const mySchema = schema("my", (s) => s.addTable("users", (t) => t.addColumn("id", idColumn())));
211
211
 
212
212
  expect(() => {
213
213
  generateMigrationFromSchema(mySchema, 999, 1000);
@@ -215,7 +215,7 @@ describe("generateMigrationFromSchema", () => {
215
215
  });
216
216
 
217
217
  it("should throw error when toVersion exceeds schema version", () => {
218
- const mySchema = schema((s) => s.addTable("users", (t) => t.addColumn("id", idColumn())));
218
+ const mySchema = schema("my", (s) => s.addTable("users", (t) => t.addColumn("id", idColumn())));
219
219
 
220
220
  expect(() => {
221
221
  generateMigrationFromSchema(mySchema, 0, 999);
@@ -223,7 +223,7 @@ describe("generateMigrationFromSchema", () => {
223
223
  });
224
224
 
225
225
  it("should throw error when trying to migrate backwards", () => {
226
- const mySchema = schema((s) => s.addTable("users", (t) => t.addColumn("id", idColumn())));
226
+ const mySchema = schema("my", (s) => s.addTable("users", (t) => t.addColumn("id", idColumn())));
227
227
 
228
228
  expect(() => {
229
229
  generateMigrationFromSchema(mySchema, 1, 0);
@@ -231,7 +231,7 @@ describe("generateMigrationFromSchema", () => {
231
231
  });
232
232
 
233
233
  it("should throw error for negative fromVersion", () => {
234
- const mySchema = schema((s) => s.addTable("users", (t) => t.addColumn("id", idColumn())));
234
+ const mySchema = schema("my", (s) => s.addTable("users", (t) => t.addColumn("id", idColumn())));
235
235
 
236
236
  expect(() => {
237
237
  generateMigrationFromSchema(mySchema, -1, 1);
@@ -239,7 +239,7 @@ describe("generateMigrationFromSchema", () => {
239
239
  });
240
240
 
241
241
  it("should only create constraints for references, not for referenceColumns", () => {
242
- const mySchema = schema((s) => {
242
+ const mySchema = schema("my", (s) => {
243
243
  return s
244
244
  .addTable("posts", (t) => {
245
245
  return t
@@ -276,7 +276,7 @@ describe("generateMigrationFromSchema", () => {
276
276
  });
277
277
 
278
278
  it("should not create duplicate foreign key constraints", () => {
279
- const mySchema = schema((s) => {
279
+ const mySchema = schema("my", (s) => {
280
280
  return s
281
281
  .addTable("users", (t) => t.addColumn("id", idColumn()))
282
282
  .addTable("posts", (t) =>