@fragno-dev/db 0.1.14 → 0.2.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 (445) hide show
  1. package/.turbo/turbo-build.log +242 -139
  2. package/CHANGELOG.md +47 -0
  3. package/README.md +123 -8
  4. package/dist/adapters/adapters.d.ts +19 -5
  5. package/dist/adapters/adapters.d.ts.map +1 -1
  6. package/dist/adapters/adapters.js.map +1 -1
  7. package/dist/adapters/drizzle/drizzle-adapter.d.ts +6 -19
  8. package/dist/adapters/drizzle/drizzle-adapter.d.ts.map +1 -1
  9. package/dist/adapters/drizzle/drizzle-adapter.js +7 -47
  10. package/dist/adapters/drizzle/drizzle-adapter.js.map +1 -1
  11. package/dist/adapters/drizzle/generate.d.ts +7 -1
  12. package/dist/adapters/drizzle/generate.d.ts.map +1 -1
  13. package/dist/adapters/drizzle/generate.js +46 -45
  14. package/dist/adapters/drizzle/generate.js.map +1 -1
  15. package/dist/adapters/generic-sql/driver-config.d.ts +74 -0
  16. package/dist/adapters/generic-sql/driver-config.d.ts.map +1 -0
  17. package/dist/adapters/generic-sql/driver-config.js +94 -0
  18. package/dist/adapters/generic-sql/driver-config.js.map +1 -0
  19. package/dist/adapters/generic-sql/generic-sql-adapter.d.ts +43 -0
  20. package/dist/adapters/generic-sql/generic-sql-adapter.d.ts.map +1 -0
  21. package/dist/adapters/generic-sql/generic-sql-adapter.js +87 -0
  22. package/dist/adapters/generic-sql/generic-sql-adapter.js.map +1 -0
  23. package/dist/adapters/generic-sql/generic-sql-uow-executor.js +67 -0
  24. package/dist/adapters/generic-sql/generic-sql-uow-executor.js.map +1 -0
  25. package/dist/adapters/generic-sql/migration/cold-kysely.js +33 -0
  26. package/dist/adapters/generic-sql/migration/cold-kysely.js.map +1 -0
  27. package/dist/adapters/generic-sql/migration/dialect/mysql.js +60 -0
  28. package/dist/adapters/generic-sql/migration/dialect/mysql.js.map +1 -0
  29. package/dist/adapters/generic-sql/migration/dialect/postgres.js +59 -0
  30. package/dist/adapters/generic-sql/migration/dialect/postgres.js.map +1 -0
  31. package/dist/adapters/generic-sql/migration/dialect/sqlite.js +96 -0
  32. package/dist/adapters/generic-sql/migration/dialect/sqlite.js.map +1 -0
  33. package/dist/adapters/generic-sql/migration/executor.d.ts +15 -0
  34. package/dist/adapters/generic-sql/migration/executor.d.ts.map +1 -0
  35. package/dist/adapters/generic-sql/migration/executor.js +18 -0
  36. package/dist/adapters/generic-sql/migration/executor.js.map +1 -0
  37. package/dist/adapters/generic-sql/migration/prepared-migrations.d.ts +66 -0
  38. package/dist/adapters/generic-sql/migration/prepared-migrations.d.ts.map +1 -0
  39. package/dist/adapters/generic-sql/migration/prepared-migrations.js +68 -0
  40. package/dist/adapters/generic-sql/migration/prepared-migrations.js.map +1 -0
  41. package/dist/adapters/generic-sql/migration/sql-generator.js +212 -0
  42. package/dist/adapters/generic-sql/migration/sql-generator.js.map +1 -0
  43. package/dist/adapters/generic-sql/query/create-sql-query-compiler.js +32 -0
  44. package/dist/adapters/generic-sql/query/create-sql-query-compiler.js.map +1 -0
  45. package/dist/adapters/generic-sql/query/cursor-utils.js +37 -0
  46. package/dist/adapters/generic-sql/query/cursor-utils.js.map +1 -0
  47. package/dist/adapters/generic-sql/query/dialect/mysql.js +33 -0
  48. package/dist/adapters/generic-sql/query/dialect/mysql.js.map +1 -0
  49. package/dist/adapters/generic-sql/query/dialect/postgres.js +32 -0
  50. package/dist/adapters/generic-sql/query/dialect/postgres.js.map +1 -0
  51. package/dist/adapters/generic-sql/query/dialect/sqlite.js +32 -0
  52. package/dist/adapters/generic-sql/query/dialect/sqlite.js.map +1 -0
  53. package/dist/adapters/generic-sql/query/generic-sql-uow-operation-compiler.js +152 -0
  54. package/dist/adapters/generic-sql/query/generic-sql-uow-operation-compiler.js.map +1 -0
  55. package/dist/adapters/generic-sql/query/select-builder.js +69 -0
  56. package/dist/adapters/generic-sql/query/select-builder.js.map +1 -0
  57. package/dist/adapters/generic-sql/query/sql-query-compiler.js +145 -0
  58. package/dist/adapters/generic-sql/query/sql-query-compiler.js.map +1 -0
  59. package/dist/adapters/generic-sql/query/where-builder.js +129 -0
  60. package/dist/adapters/generic-sql/query/where-builder.js.map +1 -0
  61. package/dist/adapters/generic-sql/result-interpreter.js +74 -0
  62. package/dist/adapters/generic-sql/result-interpreter.js.map +1 -0
  63. package/dist/adapters/generic-sql/uow-decoder.js +105 -0
  64. package/dist/adapters/generic-sql/uow-decoder.js.map +1 -0
  65. package/dist/adapters/generic-sql/uow-encoder.js +93 -0
  66. package/dist/adapters/generic-sql/uow-encoder.js.map +1 -0
  67. package/dist/adapters/kysely/kysely-adapter.d.ts +5 -16
  68. package/dist/adapters/kysely/kysely-adapter.d.ts.map +1 -1
  69. package/dist/adapters/kysely/kysely-adapter.js +6 -159
  70. package/dist/adapters/kysely/kysely-adapter.js.map +1 -1
  71. package/dist/adapters/{drizzle/drizzle-query.js → shared/from-unit-of-work-compiler.js} +48 -62
  72. package/dist/adapters/shared/from-unit-of-work-compiler.js.map +1 -0
  73. package/dist/adapters/{kysely/kysely-shared.d.ts → shared/table-name-mapper.d.ts} +3 -2
  74. package/dist/adapters/shared/table-name-mapper.d.ts.map +1 -0
  75. package/dist/adapters/shared/table-name-mapper.js +43 -0
  76. package/dist/adapters/shared/table-name-mapper.js.map +1 -0
  77. package/dist/adapters/shared/uow-operation-compiler.js +105 -0
  78. package/dist/adapters/shared/uow-operation-compiler.js.map +1 -0
  79. package/dist/db-fragment-definition-builder.d.ts +186 -0
  80. package/dist/db-fragment-definition-builder.d.ts.map +1 -0
  81. package/dist/db-fragment-definition-builder.js +207 -0
  82. package/dist/db-fragment-definition-builder.js.map +1 -0
  83. package/dist/fragments/internal-fragment.d.ts +53 -0
  84. package/dist/fragments/internal-fragment.d.ts.map +1 -0
  85. package/dist/fragments/internal-fragment.js +111 -0
  86. package/dist/fragments/internal-fragment.js.map +1 -0
  87. package/dist/hooks/hooks.d.ts +51 -0
  88. package/dist/hooks/hooks.d.ts.map +1 -0
  89. package/dist/hooks/hooks.js +88 -0
  90. package/dist/hooks/hooks.js.map +1 -0
  91. package/dist/migration-engine/generation-engine.d.ts +0 -2
  92. package/dist/migration-engine/generation-engine.d.ts.map +1 -1
  93. package/dist/migration-engine/generation-engine.js +38 -56
  94. package/dist/migration-engine/generation-engine.js.map +1 -1
  95. package/dist/mod.d.ts +35 -23
  96. package/dist/mod.d.ts.map +1 -1
  97. package/dist/mod.js +48 -45
  98. package/dist/mod.js.map +1 -1
  99. package/dist/node_modules/.pnpm/rou3@0.7.10/node_modules/rou3/dist/index.js +165 -0
  100. package/dist/node_modules/.pnpm/rou3@0.7.10/node_modules/rou3/dist/index.js.map +1 -0
  101. package/dist/packages/fragno/dist/api/bind-services.js +20 -0
  102. package/dist/packages/fragno/dist/api/bind-services.js.map +1 -0
  103. package/dist/packages/fragno/dist/api/error.js +48 -0
  104. package/dist/packages/fragno/dist/api/error.js.map +1 -0
  105. package/dist/packages/fragno/dist/api/fragment-definition-builder.js +320 -0
  106. package/dist/packages/fragno/dist/api/fragment-definition-builder.js.map +1 -0
  107. package/dist/packages/fragno/dist/api/fragment-instantiator.js +525 -0
  108. package/dist/packages/fragno/dist/api/fragment-instantiator.js.map +1 -0
  109. package/dist/packages/fragno/dist/api/fragno-response.js +73 -0
  110. package/dist/packages/fragno/dist/api/fragno-response.js.map +1 -0
  111. package/dist/packages/fragno/dist/api/internal/response-stream.js +81 -0
  112. package/dist/packages/fragno/dist/api/internal/response-stream.js.map +1 -0
  113. package/dist/packages/fragno/dist/api/internal/route.js +10 -0
  114. package/dist/packages/fragno/dist/api/internal/route.js.map +1 -0
  115. package/dist/packages/fragno/dist/api/mutable-request-state.js +97 -0
  116. package/dist/packages/fragno/dist/api/mutable-request-state.js.map +1 -0
  117. package/dist/packages/fragno/dist/api/request-context-storage.js +43 -0
  118. package/dist/packages/fragno/dist/api/request-context-storage.js.map +1 -0
  119. package/dist/packages/fragno/dist/api/request-input-context.js +118 -0
  120. package/dist/packages/fragno/dist/api/request-input-context.js.map +1 -0
  121. package/dist/packages/fragno/dist/api/request-middleware.js +83 -0
  122. package/dist/packages/fragno/dist/api/request-middleware.js.map +1 -0
  123. package/dist/packages/fragno/dist/api/request-output-context.js +119 -0
  124. package/dist/packages/fragno/dist/api/request-output-context.js.map +1 -0
  125. package/dist/packages/fragno/dist/api/route.js +17 -0
  126. package/dist/packages/fragno/dist/api/route.js.map +1 -0
  127. package/dist/packages/fragno/dist/internal/symbols.js +10 -0
  128. package/dist/packages/fragno/dist/internal/symbols.js.map +1 -0
  129. package/dist/query/column-defaults.js +27 -0
  130. package/dist/query/column-defaults.js.map +1 -0
  131. package/dist/query/cursor.d.ts +14 -6
  132. package/dist/query/cursor.d.ts.map +1 -1
  133. package/dist/query/cursor.js +16 -7
  134. package/dist/query/cursor.js.map +1 -1
  135. package/dist/query/orm/orm.d.ts +1 -1
  136. package/dist/query/orm/orm.js.map +1 -1
  137. package/dist/query/serialize/create-sql-serializer.js +30 -0
  138. package/dist/query/serialize/create-sql-serializer.js.map +1 -0
  139. package/dist/query/serialize/dialect/mysql-serializer.js +87 -0
  140. package/dist/query/serialize/dialect/mysql-serializer.js.map +1 -0
  141. package/dist/query/serialize/dialect/postgres-serializer.js +80 -0
  142. package/dist/query/serialize/dialect/postgres-serializer.js.map +1 -0
  143. package/dist/query/serialize/dialect/sqlite-serializer.js +93 -0
  144. package/dist/query/serialize/dialect/sqlite-serializer.js.map +1 -0
  145. package/dist/query/serialize/sql-serializer.js +67 -0
  146. package/dist/query/serialize/sql-serializer.js.map +1 -0
  147. package/dist/query/{query.d.ts → simple-query-interface.d.ts} +6 -6
  148. package/dist/query/simple-query-interface.d.ts.map +1 -0
  149. package/dist/query/unit-of-work/execute-unit-of-work.d.ts +133 -0
  150. package/dist/query/unit-of-work/execute-unit-of-work.d.ts.map +1 -0
  151. package/dist/query/unit-of-work/execute-unit-of-work.js +197 -0
  152. package/dist/query/unit-of-work/execute-unit-of-work.js.map +1 -0
  153. package/dist/query/unit-of-work/retry-policy.d.ts +88 -0
  154. package/dist/query/unit-of-work/retry-policy.d.ts.map +1 -0
  155. package/dist/query/unit-of-work/retry-policy.js +61 -0
  156. package/dist/query/unit-of-work/retry-policy.js.map +1 -0
  157. package/dist/query/{unit-of-work.d.ts → unit-of-work/unit-of-work.d.ts} +145 -58
  158. package/dist/query/unit-of-work/unit-of-work.d.ts.map +1 -0
  159. package/dist/query/{unit-of-work.js → unit-of-work/unit-of-work.js} +435 -198
  160. package/dist/query/unit-of-work/unit-of-work.js.map +1 -0
  161. package/dist/query/value-decoding.js +71 -0
  162. package/dist/query/value-decoding.js.map +1 -0
  163. package/dist/query/value-encoding.js +124 -0
  164. package/dist/query/value-encoding.js.map +1 -0
  165. package/dist/schema/create.d.ts +3 -0
  166. package/dist/schema/create.d.ts.map +1 -1
  167. package/dist/schema/create.js +4 -0
  168. package/dist/schema/create.js.map +1 -1
  169. package/dist/schema/type-conversion/create-sql-type-mapper.js +29 -0
  170. package/dist/schema/type-conversion/create-sql-type-mapper.js.map +1 -0
  171. package/dist/schema/type-conversion/dialect/mysql.js +57 -0
  172. package/dist/schema/type-conversion/dialect/mysql.js.map +1 -0
  173. package/dist/schema/type-conversion/dialect/postgres.js +56 -0
  174. package/dist/schema/type-conversion/dialect/postgres.js.map +1 -0
  175. package/dist/schema/type-conversion/dialect/sqlite.js +52 -0
  176. package/dist/schema/type-conversion/dialect/sqlite.js.map +1 -0
  177. package/dist/schema/type-conversion/type-mapping.js +63 -0
  178. package/dist/schema/type-conversion/type-mapping.js.map +1 -0
  179. package/dist/sql-driver/connection/connection-provider.d.ts +13 -0
  180. package/dist/sql-driver/connection/connection-provider.d.ts.map +1 -0
  181. package/dist/sql-driver/connection/connection-provider.js +19 -0
  182. package/dist/sql-driver/connection/connection-provider.js.map +1 -0
  183. package/dist/sql-driver/connection/single-connection-provider.js +23 -0
  184. package/dist/sql-driver/connection/single-connection-provider.js.map +1 -0
  185. package/dist/sql-driver/dialect-adapter/dialect-adapter.d.ts +7 -0
  186. package/dist/sql-driver/dialect-adapter/dialect-adapter.d.ts.map +1 -0
  187. package/dist/sql-driver/dialects/dialects.d.ts +2 -0
  188. package/dist/sql-driver/dialects/dialects.js +3 -0
  189. package/dist/sql-driver/dialects/durable-object-dialect.d.ts +72 -0
  190. package/dist/sql-driver/dialects/durable-object-dialect.d.ts.map +1 -0
  191. package/dist/sql-driver/dialects/durable-object-dialect.js +130 -0
  192. package/dist/sql-driver/dialects/durable-object-dialect.js.map +1 -0
  193. package/dist/sql-driver/driver/runtime-driver.d.ts +23 -0
  194. package/dist/sql-driver/driver/runtime-driver.d.ts.map +1 -0
  195. package/dist/sql-driver/driver/runtime-driver.js +56 -0
  196. package/dist/sql-driver/driver/runtime-driver.js.map +1 -0
  197. package/dist/sql-driver/query-executor/default-query-executor.js +26 -0
  198. package/dist/sql-driver/query-executor/default-query-executor.js.map +1 -0
  199. package/dist/sql-driver/query-executor/plugin.d.ts +17 -0
  200. package/dist/sql-driver/query-executor/plugin.d.ts.map +1 -0
  201. package/dist/sql-driver/query-executor/query-executor-base.js +25 -0
  202. package/dist/sql-driver/query-executor/query-executor-base.js.map +1 -0
  203. package/dist/sql-driver/query-executor/query-executor.d.ts +36 -0
  204. package/dist/sql-driver/query-executor/query-executor.d.ts.map +1 -0
  205. package/dist/sql-driver/sql-driver-adapter.d.ts +29 -0
  206. package/dist/sql-driver/sql-driver-adapter.d.ts.map +1 -0
  207. package/dist/sql-driver/sql-driver-adapter.js +68 -0
  208. package/dist/sql-driver/sql-driver-adapter.js.map +1 -0
  209. package/dist/sql-driver/sql-driver.d.ts +38 -0
  210. package/dist/sql-driver/sql-driver.d.ts.map +1 -0
  211. package/dist/sql-driver/sql-driver.js +1 -0
  212. package/dist/sql-driver/sql.js +50 -0
  213. package/dist/sql-driver/sql.js.map +1 -0
  214. package/dist/with-database.d.ts +32 -0
  215. package/dist/with-database.d.ts.map +1 -0
  216. package/dist/with-database.js +34 -0
  217. package/dist/with-database.js.map +1 -0
  218. package/package.json +43 -9
  219. package/src/adapters/adapters.ts +23 -4
  220. package/src/adapters/drizzle/drizzle-adapter-pglite.test.ts +140 -185
  221. package/src/adapters/drizzle/{drizzle-adapter-sqlite.test.ts → drizzle-adapter-sqlite3.test.ts} +187 -55
  222. package/src/adapters/drizzle/drizzle-adapter.ts +14 -93
  223. package/src/adapters/drizzle/generate.test.ts +102 -269
  224. package/src/adapters/drizzle/generate.ts +89 -63
  225. package/src/adapters/drizzle/migrate-drizzle.test.ts +19 -0
  226. package/src/adapters/drizzle/shared.ts +0 -34
  227. package/src/adapters/drizzle/test-utils.ts +36 -5
  228. package/src/adapters/generic-sql/README.md +14 -0
  229. package/src/adapters/generic-sql/driver-config.ts +144 -0
  230. package/src/adapters/generic-sql/generic-sql-adapter.test.ts +50 -0
  231. package/src/adapters/generic-sql/generic-sql-adapter.ts +146 -0
  232. package/src/adapters/generic-sql/generic-sql-uow-executor.ts +130 -0
  233. package/src/adapters/generic-sql/migration/cold-kysely.ts +55 -0
  234. package/src/adapters/{kysely/migration/execute-mysql.test.ts → generic-sql/migration/dialect/mysql.test.ts} +342 -484
  235. package/src/adapters/generic-sql/migration/dialect/mysql.ts +104 -0
  236. package/src/adapters/generic-sql/migration/dialect/postgres.test.ts +1008 -0
  237. package/src/adapters/generic-sql/migration/dialect/postgres.ts +113 -0
  238. package/src/adapters/{kysely/migration/execute-sqlite.test.ts → generic-sql/migration/dialect/sqlite.test.ts} +307 -510
  239. package/src/adapters/generic-sql/migration/dialect/sqlite.ts +189 -0
  240. package/src/adapters/generic-sql/migration/executor.ts +33 -0
  241. package/src/adapters/generic-sql/migration/prepared-migrations.test.ts +661 -0
  242. package/src/adapters/generic-sql/migration/prepared-migrations.ts +214 -0
  243. package/src/adapters/generic-sql/migration/sql-generator.ts +413 -0
  244. package/src/adapters/generic-sql/query/create-sql-query-compiler.ts +36 -0
  245. package/src/adapters/generic-sql/query/cursor-utils.ts +56 -0
  246. package/src/adapters/generic-sql/query/dialect/mysql.ts +34 -0
  247. package/src/adapters/generic-sql/query/dialect/postgres.ts +32 -0
  248. package/src/adapters/generic-sql/query/dialect/sqlite.ts +32 -0
  249. package/src/adapters/generic-sql/query/generic-sql-uow-operation-compiler.test.ts +1568 -0
  250. package/src/adapters/generic-sql/query/generic-sql-uow-operation-compiler.ts +314 -0
  251. package/src/adapters/generic-sql/query/select-builder.test.ts +256 -0
  252. package/src/adapters/generic-sql/query/select-builder.ts +137 -0
  253. package/src/adapters/generic-sql/query/sql-query-compiler.test.ts +195 -0
  254. package/src/adapters/generic-sql/query/sql-query-compiler.ts +367 -0
  255. package/src/adapters/generic-sql/query/where-builder.test.ts +744 -0
  256. package/src/adapters/generic-sql/query/where-builder.ts +211 -0
  257. package/src/adapters/generic-sql/result-interpreter.ts +102 -0
  258. package/src/adapters/generic-sql/test/generic-drizzle-adapter-sqlite3.test.ts +899 -0
  259. package/src/adapters/generic-sql/uow-decoder.test.ts +399 -0
  260. package/src/adapters/generic-sql/uow-decoder.ts +152 -0
  261. package/src/adapters/generic-sql/uow-encoder.test.ts +183 -0
  262. package/src/adapters/generic-sql/uow-encoder.ts +131 -0
  263. package/src/adapters/kysely/kysely-adapter-pglite.test.ts +90 -96
  264. package/src/adapters/kysely/kysely-adapter-sqlocal.test.ts +215 -0
  265. package/src/adapters/kysely/kysely-adapter.ts +10 -242
  266. package/src/adapters/{drizzle/drizzle-query.ts → shared/from-unit-of-work-compiler.ts} +111 -106
  267. package/src/adapters/shared/table-name-mapper.ts +50 -0
  268. package/src/adapters/shared/uow-operation-compiler.ts +211 -0
  269. package/src/db-fragment-definition-builder.test.ts +887 -0
  270. package/src/db-fragment-definition-builder.ts +737 -0
  271. package/src/db-fragment-instantiator.test.ts +543 -0
  272. package/src/db-fragment-integration.test.ts +406 -0
  273. package/src/fragments/internal-fragment.test.ts +549 -0
  274. package/src/fragments/internal-fragment.ts +249 -0
  275. package/src/hooks/hooks.test.ts +575 -0
  276. package/src/hooks/hooks.ts +179 -0
  277. package/src/migration-engine/generation-engine.test.ts +60 -27
  278. package/src/migration-engine/generation-engine.ts +99 -92
  279. package/src/mod.ts +139 -78
  280. package/src/query/column-defaults.ts +49 -0
  281. package/src/query/cursor.test.ts +147 -3
  282. package/src/query/cursor.ts +25 -8
  283. package/src/query/orm/orm.ts +1 -1
  284. package/src/query/query-type.test.ts +9 -9
  285. package/src/query/serialize/create-sql-serializer.ts +34 -0
  286. package/src/query/serialize/dialect/mysql-serializer.ts +142 -0
  287. package/src/query/serialize/dialect/postgres-serializer.ts +129 -0
  288. package/src/query/serialize/dialect/sqlite-serializer.test.ts +251 -0
  289. package/src/query/serialize/dialect/sqlite-serializer.ts +156 -0
  290. package/src/query/serialize/sql-serializer.ts +143 -0
  291. package/src/query/{query.ts → simple-query-interface.ts} +4 -4
  292. package/src/query/unit-of-work/execute-unit-of-work.test.ts +1310 -0
  293. package/src/query/unit-of-work/execute-unit-of-work.ts +504 -0
  294. package/src/query/unit-of-work/retry-policy.test.ts +217 -0
  295. package/src/query/unit-of-work/retry-policy.ts +141 -0
  296. package/src/query/unit-of-work/unit-of-work-coordinator.test.ts +831 -0
  297. package/src/query/{unit-of-work-types.test.ts → unit-of-work/unit-of-work-types.test.ts} +7 -5
  298. package/src/query/unit-of-work/unit-of-work.test.ts +1716 -0
  299. package/src/query/{unit-of-work.ts → unit-of-work/unit-of-work.ts} +716 -420
  300. package/src/query/{result-transform.test.ts → value-decoding.test.ts} +45 -298
  301. package/src/query/value-decoding.ts +113 -0
  302. package/src/query/value-encoding.test.ts +390 -0
  303. package/src/query/value-encoding.ts +168 -0
  304. package/src/schema/create.test.ts +5 -1
  305. package/src/schema/create.ts +5 -0
  306. package/src/schema/serialize.test.ts +165 -407
  307. package/src/schema/type-conversion/create-sql-type-mapper.ts +28 -0
  308. package/src/schema/type-conversion/dialect/mysql.ts +64 -0
  309. package/src/schema/type-conversion/dialect/postgres.ts +62 -0
  310. package/src/schema/type-conversion/dialect/sqlite.ts +63 -0
  311. package/src/schema/type-conversion/type-mapping.test.ts +137 -0
  312. package/src/schema/type-conversion/type-mapping.ts +153 -0
  313. package/src/shared/connection-pool.ts +5 -5
  314. package/src/sql-driver/better-sqlite3.test.ts +126 -0
  315. package/src/sql-driver/connection/connection-provider.ts +27 -0
  316. package/src/sql-driver/connection/single-connection-provider.ts +42 -0
  317. package/src/sql-driver/dialect-adapter/dialect-adapter.ts +9 -0
  318. package/src/sql-driver/dialect-adapter/sqlite-dialect-adapter.ts +7 -0
  319. package/src/sql-driver/dialects/dialects.ts +1 -0
  320. package/src/sql-driver/dialects/durable-object-dialect.ts +260 -0
  321. package/src/sql-driver/driver/runtime-driver.ts +91 -0
  322. package/src/sql-driver/query-executor/default-query-executor.ts +38 -0
  323. package/src/sql-driver/query-executor/plugin.ts +22 -0
  324. package/src/sql-driver/query-executor/query-executor-base.ts +53 -0
  325. package/src/sql-driver/query-executor/query-executor.ts +44 -0
  326. package/src/sql-driver/sql-driver-adapter.ts +96 -0
  327. package/src/sql-driver/sql-driver.ts +53 -0
  328. package/src/sql-driver/sql.ts +57 -0
  329. package/src/sql-driver/sqlocal.test.ts +117 -0
  330. package/src/with-database.ts +152 -0
  331. package/tsdown.config.ts +8 -2
  332. package/dist/adapters/drizzle/drizzle-connection-pool.js +0 -40
  333. package/dist/adapters/drizzle/drizzle-connection-pool.js.map +0 -1
  334. package/dist/adapters/drizzle/drizzle-query.d.ts +0 -23
  335. package/dist/adapters/drizzle/drizzle-query.d.ts.map +0 -1
  336. package/dist/adapters/drizzle/drizzle-query.js.map +0 -1
  337. package/dist/adapters/drizzle/drizzle-uow-compiler.d.ts +0 -10
  338. package/dist/adapters/drizzle/drizzle-uow-compiler.d.ts.map +0 -1
  339. package/dist/adapters/drizzle/drizzle-uow-compiler.js +0 -315
  340. package/dist/adapters/drizzle/drizzle-uow-compiler.js.map +0 -1
  341. package/dist/adapters/drizzle/drizzle-uow-decoder.js +0 -116
  342. package/dist/adapters/drizzle/drizzle-uow-decoder.js.map +0 -1
  343. package/dist/adapters/drizzle/drizzle-uow-executor.js +0 -149
  344. package/dist/adapters/drizzle/drizzle-uow-executor.js.map +0 -1
  345. package/dist/adapters/drizzle/join-column-utils.js +0 -28
  346. package/dist/adapters/drizzle/join-column-utils.js.map +0 -1
  347. package/dist/adapters/drizzle/shared.d.ts +0 -14
  348. package/dist/adapters/drizzle/shared.d.ts.map +0 -1
  349. package/dist/adapters/drizzle/shared.js +0 -35
  350. package/dist/adapters/drizzle/shared.js.map +0 -1
  351. package/dist/adapters/kysely/kysely-connection-pool.js +0 -41
  352. package/dist/adapters/kysely/kysely-connection-pool.js.map +0 -1
  353. package/dist/adapters/kysely/kysely-query-builder.js +0 -321
  354. package/dist/adapters/kysely/kysely-query-builder.js.map +0 -1
  355. package/dist/adapters/kysely/kysely-query-compiler.js +0 -66
  356. package/dist/adapters/kysely/kysely-query-compiler.js.map +0 -1
  357. package/dist/adapters/kysely/kysely-query.d.ts +0 -22
  358. package/dist/adapters/kysely/kysely-query.d.ts.map +0 -1
  359. package/dist/adapters/kysely/kysely-query.js +0 -223
  360. package/dist/adapters/kysely/kysely-query.js.map +0 -1
  361. package/dist/adapters/kysely/kysely-shared.d.ts.map +0 -1
  362. package/dist/adapters/kysely/kysely-shared.js +0 -18
  363. package/dist/adapters/kysely/kysely-shared.js.map +0 -1
  364. package/dist/adapters/kysely/kysely-uow-compiler.js +0 -170
  365. package/dist/adapters/kysely/kysely-uow-compiler.js.map +0 -1
  366. package/dist/adapters/kysely/kysely-uow-executor.js +0 -89
  367. package/dist/adapters/kysely/kysely-uow-executor.js.map +0 -1
  368. package/dist/adapters/kysely/migration/execute-base.js +0 -128
  369. package/dist/adapters/kysely/migration/execute-base.js.map +0 -1
  370. package/dist/adapters/kysely/migration/execute-factory.js +0 -34
  371. package/dist/adapters/kysely/migration/execute-factory.js.map +0 -1
  372. package/dist/adapters/kysely/migration/execute-mssql.js +0 -112
  373. package/dist/adapters/kysely/migration/execute-mssql.js.map +0 -1
  374. package/dist/adapters/kysely/migration/execute-mysql.js +0 -93
  375. package/dist/adapters/kysely/migration/execute-mysql.js.map +0 -1
  376. package/dist/adapters/kysely/migration/execute-postgres.js +0 -104
  377. package/dist/adapters/kysely/migration/execute-postgres.js.map +0 -1
  378. package/dist/adapters/kysely/migration/execute-sqlite.js +0 -123
  379. package/dist/adapters/kysely/migration/execute-sqlite.js.map +0 -1
  380. package/dist/adapters/kysely/migration/execute.js +0 -34
  381. package/dist/adapters/kysely/migration/execute.js.map +0 -1
  382. package/dist/bind-services.d.ts +0 -7
  383. package/dist/bind-services.d.ts.map +0 -1
  384. package/dist/bind-services.js +0 -14
  385. package/dist/bind-services.js.map +0 -1
  386. package/dist/fragment.d.ts +0 -173
  387. package/dist/fragment.d.ts.map +0 -1
  388. package/dist/fragment.js +0 -191
  389. package/dist/fragment.js.map +0 -1
  390. package/dist/migration-engine/create.d.ts +0 -37
  391. package/dist/migration-engine/create.d.ts.map +0 -1
  392. package/dist/migration-engine/create.js +0 -58
  393. package/dist/migration-engine/create.js.map +0 -1
  394. package/dist/migration-engine/shared.d.ts +0 -112
  395. package/dist/migration-engine/shared.d.ts.map +0 -1
  396. package/dist/query/query.d.ts.map +0 -1
  397. package/dist/query/result-transform.js +0 -168
  398. package/dist/query/result-transform.js.map +0 -1
  399. package/dist/query/unit-of-work.d.ts.map +0 -1
  400. package/dist/query/unit-of-work.js.map +0 -1
  401. package/dist/schema/serialize.js +0 -106
  402. package/dist/schema/serialize.js.map +0 -1
  403. package/dist/shared/settings-schema.js +0 -36
  404. package/dist/shared/settings-schema.js.map +0 -1
  405. package/src/adapters/drizzle/drizzle-adapter.test.ts +0 -170
  406. package/src/adapters/drizzle/drizzle-connection-pool.ts +0 -66
  407. package/src/adapters/drizzle/drizzle-query.test.ts +0 -499
  408. package/src/adapters/drizzle/drizzle-uow-compiler.test.ts +0 -1383
  409. package/src/adapters/drizzle/drizzle-uow-compiler.ts +0 -636
  410. package/src/adapters/drizzle/drizzle-uow-decoder.ts +0 -218
  411. package/src/adapters/drizzle/drizzle-uow-executor.ts +0 -276
  412. package/src/adapters/drizzle/join-column-utils.test.ts +0 -79
  413. package/src/adapters/drizzle/join-column-utils.ts +0 -39
  414. package/src/adapters/kysely/kysely-connection-pool.ts +0 -70
  415. package/src/adapters/kysely/kysely-query-builder.test.ts +0 -1344
  416. package/src/adapters/kysely/kysely-query-builder.ts +0 -666
  417. package/src/adapters/kysely/kysely-query-compiler.ts +0 -132
  418. package/src/adapters/kysely/kysely-query.test.ts +0 -498
  419. package/src/adapters/kysely/kysely-query.ts +0 -390
  420. package/src/adapters/kysely/kysely-shared.ts +0 -23
  421. package/src/adapters/kysely/kysely-uow-compiler.test.ts +0 -998
  422. package/src/adapters/kysely/kysely-uow-compiler.ts +0 -318
  423. package/src/adapters/kysely/kysely-uow-executor.ts +0 -145
  424. package/src/adapters/kysely/kysely-uow-joins.test.ts +0 -811
  425. package/src/adapters/kysely/migration/execute-base.ts +0 -256
  426. package/src/adapters/kysely/migration/execute-factory.ts +0 -53
  427. package/src/adapters/kysely/migration/execute-mssql.ts +0 -250
  428. package/src/adapters/kysely/migration/execute-mysql.ts +0 -211
  429. package/src/adapters/kysely/migration/execute-postgres.test.ts +0 -2657
  430. package/src/adapters/kysely/migration/execute-postgres.ts +0 -234
  431. package/src/adapters/kysely/migration/execute-sqlite.ts +0 -247
  432. package/src/adapters/kysely/migration/execute.ts +0 -50
  433. package/src/adapters/kysely/migration/kysely-migrator.test.ts +0 -261
  434. package/src/bind-services.test.ts +0 -214
  435. package/src/bind-services.ts +0 -37
  436. package/src/db-fragment.test.ts +0 -800
  437. package/src/fragment.ts +0 -727
  438. package/src/query/result-transform.ts +0 -271
  439. package/src/query/unit-of-work-multi-schema.test.ts +0 -64
  440. package/src/query/unit-of-work.test.ts +0 -943
  441. package/src/schema/serialize.ts +0 -396
  442. package/src/shared/settings-schema.ts +0 -61
  443. package/src/uow-context-integration.test.ts +0 -102
  444. package/src/uow-context.test.ts +0 -182
  445. /package/dist/query/{query.js → simple-query-interface.js} +0 -0
@@ -0,0 +1,831 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { schema, idColumn, type FragnoId, referenceColumn } from "../../schema/create";
3
+ import {
4
+ type UOWCompiler,
5
+ type UOWDecoder,
6
+ createUnitOfWork,
7
+ type CompiledMutation,
8
+ type UOWExecutor,
9
+ } from "./unit-of-work";
10
+ import { GenericSQLUOWOperationCompiler } from "../../adapters/generic-sql/query/generic-sql-uow-operation-compiler";
11
+ import { BetterSQLite3DriverConfig } from "../../adapters/generic-sql/driver-config";
12
+ import { createUOWCompilerFromOperationCompiler } from "../../adapters/shared/uow-operation-compiler";
13
+ import type { CompiledQuery } from "../../sql-driver/sql-driver";
14
+
15
+ // Create compiler using actual implementation
16
+ function createCompiler(): UOWCompiler<CompiledQuery> {
17
+ const driverConfig = new BetterSQLite3DriverConfig();
18
+ const operationCompiler = new GenericSQLUOWOperationCompiler(driverConfig);
19
+ return createUOWCompilerFromOperationCompiler(operationCompiler);
20
+ }
21
+
22
+ // Mock executor that tracks execution and works with CompiledQuery
23
+ function createMockExecutor(): UOWExecutor<CompiledQuery, unknown> & {
24
+ getLog: () => string[];
25
+ clearLog: () => void;
26
+ } {
27
+ const executionLog: string[] = [];
28
+
29
+ return {
30
+ executeRetrievalPhase: async (queries: CompiledQuery[]) => {
31
+ executionLog.push(`RETRIEVAL: ${queries.length} queries`);
32
+ // Return mock results for each query
33
+ return queries.map(() => [{ id: "mock-id", name: "Mock User" }]);
34
+ },
35
+ executeMutationPhase: async (mutations: CompiledMutation<CompiledQuery>[]) => {
36
+ executionLog.push(`MUTATION: ${mutations.length} mutations`);
37
+ return {
38
+ success: true,
39
+ createdInternalIds: mutations.map(() => BigInt(Math.floor(Math.random() * 1000))),
40
+ };
41
+ },
42
+ getLog: () => executionLog,
43
+ clearLog: () => {
44
+ executionLog.length = 0;
45
+ },
46
+ };
47
+ }
48
+
49
+ function createMockDecoder(): UOWDecoder {
50
+ return {
51
+ decode(rawResults, operations) {
52
+ if (rawResults.length !== operations.length) {
53
+ throw new Error("rawResults and operations must have the same length");
54
+ }
55
+ return rawResults;
56
+ },
57
+ };
58
+ }
59
+
60
+ describe("UOW Coordinator - Parent-Child Execution", () => {
61
+ it("should allow child UOWs to add operations and parent to execute them", async () => {
62
+ const testSchema = schema((s) =>
63
+ s.addTable("users", (t) =>
64
+ t.addColumn("id", idColumn()).addColumn("name", "string").addColumn("email", "string"),
65
+ ),
66
+ );
67
+
68
+ const executor = createMockExecutor();
69
+ const parentUow = createUnitOfWork(createCompiler(), executor, createMockDecoder());
70
+
71
+ // Simulate service method 1: adds retrieval operation via child UOW
72
+ const serviceMethod1 = () => {
73
+ const childUow = parentUow.restrict();
74
+ childUow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary"));
75
+ };
76
+
77
+ // Simulate service method 2: adds mutation operation via child UOW
78
+ const serviceMethod2 = () => {
79
+ const childUow = parentUow.restrict();
80
+ childUow.forSchema(testSchema).create("users", { name: "Alice", email: "alice@example.com" });
81
+ };
82
+
83
+ // Call both service methods
84
+ serviceMethod1();
85
+ serviceMethod2();
86
+
87
+ // Parent should see both operations
88
+ expect(parentUow.getRetrievalOperations()).toHaveLength(1);
89
+ expect(parentUow.getMutationOperations()).toHaveLength(1);
90
+
91
+ // Execute retrieval phase
92
+ const results = await parentUow.executeRetrieve();
93
+ expect(results).toHaveLength(1);
94
+
95
+ // Execute mutation phase
96
+ const mutationResult = await parentUow.executeMutations();
97
+ expect(mutationResult.success).toBe(true);
98
+
99
+ // Verify execution happened
100
+ const log = executor.getLog();
101
+ expect(log).toEqual(["RETRIEVAL: 1 queries", "MUTATION: 1 mutations"]);
102
+ });
103
+
104
+ it("should handle nested service calls that await phase promises without deadlock", async () => {
105
+ const testSchema = schema((s) =>
106
+ s
107
+ .addTable("users", (t) =>
108
+ t.addColumn("id", idColumn()).addColumn("name", "string").addColumn("email", "string"),
109
+ )
110
+ .addTable("posts", (t) =>
111
+ t
112
+ .addColumn("id", idColumn())
113
+ .addColumn("userId", "string")
114
+ .addColumn("title", "string")
115
+ .addColumn("content", "string")
116
+ .createIndex("idx_user", ["userId"]),
117
+ ),
118
+ );
119
+
120
+ const executor = createMockExecutor();
121
+ const parentUow = createUnitOfWork(createCompiler(), executor, createMockDecoder());
122
+
123
+ // Service A: Get user by ID, awaits retrieval phase
124
+ const getUserById = async (userId: string) => {
125
+ const childUow = parentUow.restrict();
126
+ const typedUow = childUow
127
+ .forSchema(testSchema)
128
+ .find("users", (b) => b.whereIndex("primary", (eb) => eb("id", "=", userId)));
129
+
130
+ // Await retrieval phase - should not deadlock!
131
+ const [users] = await typedUow.retrievalPhase;
132
+ return users?.[0] ?? null;
133
+ };
134
+
135
+ // Service B: Get posts by user ID, awaits retrieval phase
136
+ const getPostsByUserId = async (userId: string) => {
137
+ const childUow = parentUow.restrict();
138
+ const typedUow = childUow
139
+ .forSchema(testSchema)
140
+ .find("posts", (b) => b.whereIndex("idx_user", (eb) => eb("userId", "=", userId)));
141
+
142
+ // Await retrieval phase - should not deadlock!
143
+ const [posts] = await typedUow.retrievalPhase;
144
+ return posts;
145
+ };
146
+
147
+ // Handler: Orchestrates multiple service calls that each await phase promises
148
+ const handler = async () => {
149
+ // Both services add retrieval operations and return promises that await retrievalPhase
150
+ const userPromise = getUserById("user-123");
151
+ const postsPromise = getPostsByUserId("user-123");
152
+
153
+ // Execute retrieval phase - this should resolve both service promises
154
+ await parentUow.executeRetrieve();
155
+
156
+ // Now we can await the service results
157
+ const user = await userPromise;
158
+ const posts = await postsPromise;
159
+
160
+ return { user, posts };
161
+ };
162
+
163
+ // Execute handler
164
+ const result = await handler();
165
+
166
+ // Verify results
167
+ expect(result.user).toEqual({ id: "mock-id", name: "Mock User" });
168
+ expect(result.posts).toEqual([{ id: "mock-id", name: "Mock User" }]);
169
+
170
+ // Verify both retrieval operations were registered
171
+ expect(parentUow.getRetrievalOperations()).toHaveLength(2);
172
+
173
+ // Verify execution happened
174
+ const log = executor.getLog();
175
+ expect(log).toEqual(["RETRIEVAL: 2 queries"]);
176
+ });
177
+
178
+ it("should handle retrieval-to-mutation flow with service composition", async () => {
179
+ const testSchema = schema((s) =>
180
+ s
181
+ .addTable("users", (t) =>
182
+ t
183
+ .addColumn("id", idColumn())
184
+ .addColumn("name", "string")
185
+ .addColumn("email", "string")
186
+ .addColumn("status", "string"),
187
+ )
188
+ .addTable("orders", (t) =>
189
+ t
190
+ .addColumn("id", idColumn())
191
+ .addColumn("userId", "string")
192
+ .addColumn("total", "integer")
193
+ .addColumn("status", "string")
194
+ .createIndex("idx_user", ["userId"]),
195
+ )
196
+ .addTable("payments", (t) =>
197
+ t
198
+ .addColumn("id", idColumn())
199
+ .addColumn("orderId", "string")
200
+ .addColumn("amount", "integer")
201
+ .createIndex("idx_order", ["orderId"]),
202
+ ),
203
+ );
204
+
205
+ const executor = createMockExecutor();
206
+ const parentUow = createUnitOfWork(createCompiler(), executor, createMockDecoder());
207
+
208
+ // Service A: Check if user exists
209
+ const validateUser = async (userId: string) => {
210
+ const childUow = parentUow.restrict();
211
+ const typedUow = childUow
212
+ .forSchema(testSchema)
213
+ .find("users", (b) => b.whereIndex("primary", (eb) => eb("id", "=", userId)));
214
+
215
+ const [users] = await typedUow.retrievalPhase;
216
+ const user = users?.[0];
217
+
218
+ if (!user) {
219
+ throw new Error("User not found");
220
+ }
221
+
222
+ return user;
223
+ };
224
+
225
+ // Service B: Get user's pending orders
226
+ const getPendingOrders = async (userId: string) => {
227
+ const childUow = parentUow.restrict();
228
+ const typedUow = childUow
229
+ .forSchema(testSchema)
230
+ .find("orders", (b) => b.whereIndex("idx_user", (eb) => eb("userId", "=", userId)));
231
+
232
+ const [orders] = await typedUow.retrievalPhase;
233
+ return orders;
234
+ };
235
+
236
+ // Service C: Create order and payment (uses validation results)
237
+ const createOrderWithPayment = (userId: string, amount: number) => {
238
+ const childUow = parentUow.restrict();
239
+ const typedUow = childUow.forSchema(testSchema);
240
+
241
+ // Create order
242
+ const orderId = typedUow.create("orders", {
243
+ userId,
244
+ total: amount,
245
+ status: "pending",
246
+ });
247
+
248
+ // Create payment for the order
249
+ typedUow.create("payments", {
250
+ orderId: orderId.externalId,
251
+ amount,
252
+ });
253
+
254
+ // Return the ID immediately - don't await mutation phase here
255
+ // The handler will execute the mutation phase
256
+ return orderId;
257
+ };
258
+
259
+ // Handler: Orchestrates validation, retrieval, and mutations
260
+ // Pattern: validate → retrieve data → use data to drive mutations
261
+ const handler = async () => {
262
+ // Phase 1: Retrieval - validate user and get existing orders
263
+ const userPromise = validateUser("user-123");
264
+ const ordersPromise = getPendingOrders("user-123");
265
+
266
+ // Execute retrieval phase - resolves all service promises
267
+ await parentUow.executeRetrieve();
268
+
269
+ const user = await userPromise;
270
+ const existingOrders = await ordersPromise;
271
+
272
+ // Business logic: Only allow order if user has < 5 pending orders
273
+ if (existingOrders.length >= 5) {
274
+ throw new Error("Too many pending orders");
275
+ }
276
+
277
+ // Phase 2: Mutation - create new order based on retrieval results
278
+ const orderId = createOrderWithPayment("user-123", 9999);
279
+
280
+ // Execute mutation phase
281
+ await parentUow.executeMutations();
282
+
283
+ return { user, orderId, existingOrderCount: existingOrders.length };
284
+ };
285
+
286
+ // Execute handler
287
+ const result = await handler();
288
+
289
+ // Verify results
290
+ expect(result.user).toEqual({ id: "mock-id", name: "Mock User" });
291
+ expect(result.orderId.externalId).toBeTruthy();
292
+ expect(result.existingOrderCount).toBe(1);
293
+
294
+ // Verify operations were registered
295
+ // 1 user validation + 1 orders query = 2 retrieval operations
296
+ expect(parentUow.getRetrievalOperations()).toHaveLength(2);
297
+ // 1 order create + 1 payment create = 2 mutation operations
298
+ expect(parentUow.getMutationOperations()).toHaveLength(2);
299
+
300
+ // Verify execution order
301
+ const log = executor.getLog();
302
+ expect(log).toEqual(["RETRIEVAL: 2 queries", "MUTATION: 2 mutations"]);
303
+ });
304
+
305
+ it("should handle deeply nested child UOWs (3+ levels)", async () => {
306
+ const testSchema = schema((s) =>
307
+ s
308
+ .addTable("users", (t) =>
309
+ t.addColumn("id", idColumn()).addColumn("name", "string").addColumn("email", "string"),
310
+ )
311
+ .addTable("posts", (t) =>
312
+ t
313
+ .addColumn("id", idColumn())
314
+ .addColumn("userId", "string")
315
+ .addColumn("title", "string")
316
+ .createIndex("idx_user", ["userId"]),
317
+ )
318
+ .addTable("comments", (t) =>
319
+ t
320
+ .addColumn("id", idColumn())
321
+ .addColumn("postId", "string")
322
+ .addColumn("content", "string")
323
+ .createIndex("idx_post", ["postId"]),
324
+ ),
325
+ );
326
+
327
+ const executor = createMockExecutor();
328
+ const parentUow = createUnitOfWork(createCompiler(), executor, createMockDecoder());
329
+
330
+ // Level 1: Handler (root)
331
+ const handler = async () => {
332
+ // Level 2: Service A - orchestrates user operations
333
+ const getUserWithPosts = async (userId: string) => {
334
+ const childUow1 = parentUow.restrict();
335
+
336
+ // Level 3: Service B - gets just the user
337
+ const getUser = async () => {
338
+ const childUow2 = childUow1.restrict();
339
+ const typedUow = childUow2
340
+ .forSchema(testSchema)
341
+ .find("users", (b) => b.whereIndex("primary", (eb) => eb("id", "=", userId)));
342
+
343
+ const [users] = await typedUow.retrievalPhase;
344
+ return users?.[0];
345
+ };
346
+
347
+ // Level 3: Service C - gets user's posts
348
+ const getUserPosts = async () => {
349
+ const childUow2 = childUow1.restrict();
350
+ const typedUow = childUow2
351
+ .forSchema(testSchema)
352
+ .find("posts", (b) => b.whereIndex("idx_user", (eb) => eb("userId", "=", userId)));
353
+
354
+ const [posts] = await typedUow.retrievalPhase;
355
+ return posts;
356
+ };
357
+
358
+ // Await both nested services
359
+ const userPromise = getUser();
360
+ const postsPromise = getUserPosts();
361
+
362
+ // Execute retrieval at this level - should resolve nested promises
363
+ await parentUow.executeRetrieve();
364
+
365
+ const user = await userPromise;
366
+ const posts = await postsPromise;
367
+
368
+ return { user, posts };
369
+ };
370
+
371
+ // Call service A and get results
372
+ const { user, posts } = await getUserWithPosts("user-123");
373
+
374
+ // Level 2: Service D - creates comments for the posts
375
+ const createComment = (postId: string, content: string) => {
376
+ const childUow1 = parentUow.restrict();
377
+
378
+ // Level 3: Service E - validates post exists (hypothetically)
379
+ // In real code this might query, but here we just create
380
+ const childUow2 = childUow1.restrict();
381
+ const typedUow2 = childUow2.forSchema(testSchema);
382
+
383
+ typedUow2.create("comments", { postId, content });
384
+ };
385
+
386
+ // Create comments for each post
387
+ if (posts && posts.length > 0) {
388
+ for (const post of posts) {
389
+ // Use string coercion since mock data returns string ids
390
+ createComment(String(post.id), "Great post!");
391
+ }
392
+ }
393
+
394
+ // Execute mutations
395
+ await parentUow.executeMutations();
396
+
397
+ return { user, postCount: posts?.length ?? 0 };
398
+ };
399
+
400
+ // Execute handler
401
+ const result = await handler();
402
+
403
+ // Verify results
404
+ expect(result.user).toBeDefined();
405
+ expect(result.postCount).toBe(1);
406
+
407
+ // Verify operations were registered at root level
408
+ // 1 user query + 1 posts query = 2 retrieval operations
409
+ expect(parentUow.getRetrievalOperations()).toHaveLength(2);
410
+ // 1 comment create = 1 mutation operation
411
+ expect(parentUow.getMutationOperations()).toHaveLength(1);
412
+
413
+ // Verify execution happened in correct order
414
+ const log = executor.getLog();
415
+ expect(log).toEqual(["RETRIEVAL: 2 queries", "MUTATION: 1 mutations"]);
416
+ });
417
+
418
+ it("should handle sibling child UOWs at same nesting level", async () => {
419
+ const testSchema = schema((s) =>
420
+ s
421
+ .addTable("users", (t) =>
422
+ t
423
+ .addColumn("id", idColumn())
424
+ .addColumn("name", "string")
425
+ .addColumn("email", "string")
426
+ .createIndex("idx_email", ["email"]),
427
+ )
428
+ .addTable("products", (t) =>
429
+ t.addColumn("id", idColumn()).addColumn("name", "string").addColumn("price", "integer"),
430
+ )
431
+ .addTable("orders", (t) =>
432
+ t
433
+ .addColumn("id", idColumn())
434
+ .addColumn("userId", "string")
435
+ .addColumn("productId", "string")
436
+ .addColumn("quantity", "integer"),
437
+ ),
438
+ );
439
+
440
+ const executor = createMockExecutor();
441
+ const parentUow = createUnitOfWork(createCompiler(), executor, createMockDecoder());
442
+
443
+ // Service method that creates TWO sibling child UOWs
444
+ const processUserOrder = async (email: string) => {
445
+ // Sibling 1: Look up user by email
446
+ const findUserByEmail = () => {
447
+ const childUow = parentUow.restrict();
448
+ const typedUow = childUow
449
+ .forSchema(testSchema)
450
+ .find("users", (b) => b.whereIndex("idx_email", (eb) => eb("email", "=", email)));
451
+
452
+ return typedUow.retrievalPhase.then(([users]) => users?.[0] ?? null);
453
+ };
454
+
455
+ // Sibling 2: Look up product by scanning (for simplicity)
456
+ const findProductByName = () => {
457
+ const childUow = parentUow.restrict();
458
+ const typedUow = childUow
459
+ .forSchema(testSchema)
460
+ .find("products", (b) => b.whereIndex("primary"));
461
+
462
+ return typedUow.retrievalPhase.then(([products]) => products?.[0] ?? null);
463
+ };
464
+
465
+ // Create both sibling UOWs at the same time
466
+ const userPromise = findUserByEmail();
467
+ const productPromise = findProductByName();
468
+
469
+ // Execute retrieval - should resolve both siblings
470
+ await parentUow.executeRetrieve();
471
+
472
+ const user = await userPromise;
473
+ const product = await productPromise;
474
+
475
+ if (!user || !product) {
476
+ throw new Error("User or product not found");
477
+ }
478
+
479
+ // Sibling 3: Create order using the results
480
+ const createOrder = () => {
481
+ const childUow = parentUow.restrict();
482
+ const typedUow = childUow.forSchema(testSchema);
483
+
484
+ return typedUow.create("orders", {
485
+ userId: String(user.id),
486
+ productId: String(product.id),
487
+ quantity: 1,
488
+ });
489
+ };
490
+
491
+ // Sibling 4: Update user (hypothetically, just another mutation)
492
+ const updateUserEmail = () => {
493
+ const childUow = parentUow.restrict();
494
+ const typedUow = childUow.forSchema(testSchema);
495
+
496
+ typedUow.update("users", user.id, (b) =>
497
+ b.set({ email: `updated-${email}`, name: user.name }),
498
+ );
499
+ };
500
+
501
+ // Execute mutations from both siblings
502
+ createOrder();
503
+ updateUserEmail();
504
+
505
+ await parentUow.executeMutations();
506
+
507
+ return { user, product };
508
+ };
509
+
510
+ // Execute the service
511
+ const result = await processUserOrder("test@example.com");
512
+
513
+ // Verify results
514
+ expect(result.user).toBeDefined();
515
+ expect(result.product).toBeDefined();
516
+
517
+ // Verify operations registered from all siblings
518
+ // Sibling 1 (user lookup) + Sibling 2 (product lookup) = 2 retrieval operations
519
+ expect(parentUow.getRetrievalOperations()).toHaveLength(2);
520
+ // Sibling 3 (order create) + Sibling 4 (user update) = 2 mutation operations
521
+ expect(parentUow.getMutationOperations()).toHaveLength(2);
522
+
523
+ // Verify execution order
524
+ const log = executor.getLog();
525
+ expect(log).toEqual(["RETRIEVAL: 2 queries", "MUTATION: 2 mutations"]);
526
+ });
527
+
528
+ it("should support transaction rollback pattern", async () => {
529
+ const testSchema = schema((s) =>
530
+ s
531
+ .addTable("accounts", (t) =>
532
+ t
533
+ .addColumn("id", idColumn())
534
+ .addColumn("userId", "string")
535
+ .addColumn("balance", "integer")
536
+ .createIndex("idx_user", ["userId"]),
537
+ )
538
+ .addTable("transactions", (t) =>
539
+ t
540
+ .addColumn("id", idColumn())
541
+ .addColumn("fromAccountId", referenceColumn())
542
+ .addColumn("toAccountId", referenceColumn())
543
+ .addColumn("amount", "integer"),
544
+ ),
545
+ );
546
+
547
+ // Create mock executor that returns account data with low balance
548
+ const executionLog: string[] = [];
549
+ const customExecutor: UOWExecutor<CompiledQuery, unknown> = {
550
+ executeRetrievalPhase: async (queries: CompiledQuery[]) => {
551
+ executionLog.push(`RETRIEVAL: ${queries.length} queries`);
552
+ // Return mock account data with balance field set to low value
553
+ return queries.map(() => [{ id: "mock-id", userId: "user-1", balance: 100 }]);
554
+ },
555
+ executeMutationPhase: async (mutations: CompiledMutation<CompiledQuery>[]) => {
556
+ executionLog.push(`MUTATION: ${mutations.length} mutations`);
557
+ return {
558
+ success: true,
559
+ createdInternalIds: mutations.map(() => BigInt(Math.floor(Math.random() * 1000))),
560
+ };
561
+ },
562
+ };
563
+
564
+ const parentUow = createUnitOfWork(createCompiler(), customExecutor, createMockDecoder());
565
+
566
+ // Service: Get account balance
567
+ const getAccountBalance = async (userId: string) => {
568
+ const childUow = parentUow.restrict();
569
+ const typedUow = childUow
570
+ .forSchema(testSchema)
571
+ .find("accounts", (b) => b.whereIndex("idx_user", (eb) => eb("userId", "=", userId)));
572
+
573
+ const [accounts] = await typedUow.retrievalPhase;
574
+ return accounts?.[0] ?? null;
575
+ };
576
+
577
+ // Service: Create transfer (mutation)
578
+ const createTransfer = (fromAccountId: FragnoId, toAccountId: FragnoId, amount: number) => {
579
+ const childUow = parentUow.restrict();
580
+ const typedUow = childUow.forSchema(testSchema);
581
+
582
+ // Check that both accounts haven't changed since retrieval
583
+ typedUow.check("accounts", fromAccountId);
584
+ typedUow.check("accounts", toAccountId);
585
+
586
+ return typedUow.create("transactions", {
587
+ fromAccountId,
588
+ toAccountId,
589
+ amount,
590
+ });
591
+ };
592
+
593
+ // Handler: Attempt transfer but abort if insufficient funds
594
+ const attemptTransfer = async (fromUserId: string, toUserId: string, amount: number) => {
595
+ // Phase 1: Retrieval - get both accounts
596
+ const fromAccountPromise = getAccountBalance(fromUserId);
597
+ const toAccountPromise = getAccountBalance(toUserId);
598
+
599
+ // Execute retrieval phase
600
+ await parentUow.executeRetrieve();
601
+
602
+ const fromAccount = await fromAccountPromise;
603
+ const toAccount = await toAccountPromise;
604
+
605
+ // Validation: Check if accounts exist
606
+ if (!fromAccount || !toAccount) {
607
+ throw new Error("One or both accounts not found");
608
+ }
609
+
610
+ // Validation: Check if sufficient balance (this should fail in our test)
611
+ if (fromAccount.balance < amount) {
612
+ throw new Error("Insufficient funds");
613
+ }
614
+
615
+ // Phase 2: Mutation - would create transfer, but we never get here
616
+ createTransfer(fromAccount.id, toAccount.id, amount);
617
+ await parentUow.executeMutations();
618
+
619
+ return { success: true };
620
+ };
621
+
622
+ // Execute handler - expect it to throw due to insufficient funds
623
+ await expect(attemptTransfer("user-1", "user-2", 10000)).rejects.toThrow("Insufficient funds");
624
+
625
+ // Verify retrieval phase was executed
626
+ expect(parentUow.getRetrievalOperations()).toHaveLength(2);
627
+
628
+ // Verify NO mutations were executed (rollback pattern)
629
+ expect(parentUow.getMutationOperations()).toHaveLength(0);
630
+
631
+ // Verify only retrieval phase was executed, no mutations
632
+ expect(executionLog).toEqual(["RETRIEVAL: 2 queries"]);
633
+ });
634
+
635
+ it("should handle errors thrown by service methods without unhandled rejections", async () => {
636
+ const testSchema = schema((s) =>
637
+ s
638
+ .addTable("users", (t) =>
639
+ t
640
+ .addColumn("id", idColumn())
641
+ .addColumn("name", "string")
642
+ .addColumn("email", "string")
643
+ .addColumn("status", "string"),
644
+ )
645
+ .addTable("posts", (t) =>
646
+ t
647
+ .addColumn("id", idColumn())
648
+ .addColumn("userId", "string")
649
+ .addColumn("title", "string")
650
+ .createIndex("idx_user", ["userId"]),
651
+ ),
652
+ );
653
+
654
+ const executor = createMockExecutor();
655
+ const parentUow = createUnitOfWork(createCompiler(), executor, createMockDecoder());
656
+
657
+ // Service A: Validates user and throws if not active
658
+ const validateActiveUser = async (userId: string) => {
659
+ const childUow = parentUow.restrict();
660
+ const typedUow = childUow
661
+ .forSchema(testSchema)
662
+ .find("users", (b) => b.whereIndex("primary", (eb) => eb("id", "=", userId)));
663
+
664
+ const [users] = await typedUow.retrievalPhase;
665
+ const user = users?.[0];
666
+
667
+ if (!user) {
668
+ throw new Error("User not found");
669
+ }
670
+
671
+ // Mock check: assume user status is "inactive"
672
+ if (user.name === "Mock User") {
673
+ throw new Error("User is not active");
674
+ }
675
+
676
+ return user;
677
+ };
678
+
679
+ // Service B: Gets user posts (won't be reached due to validation error)
680
+ const getUserPosts = async (userId: string) => {
681
+ const childUow = parentUow.restrict();
682
+ const typedUow = childUow
683
+ .forSchema(testSchema)
684
+ .find("posts", (b) => b.whereIndex("idx_user", (eb) => eb("userId", "=", userId)));
685
+
686
+ const [posts] = await typedUow.retrievalPhase;
687
+ return posts;
688
+ };
689
+
690
+ // Handler: Orchestrates service calls that may throw
691
+ const handler = async () => {
692
+ // Both services add retrieval operations
693
+ const userPromise = validateActiveUser("user-123");
694
+ const postsPromise = getUserPosts("user-123");
695
+
696
+ // Execute retrieval phase - this resolves the retrievalPhase promises
697
+ await parentUow.executeRetrieve();
698
+
699
+ // Now await the service results - validateActiveUser will throw
700
+ const user = await userPromise; // This will throw "User is not active"
701
+ const posts = await postsPromise; // Won't be reached
702
+
703
+ return { user, posts };
704
+ };
705
+
706
+ // Execute handler and expect it to throw
707
+ await expect(handler()).rejects.toThrow("User is not active");
708
+
709
+ // Verify retrieval phase was executed (both operations were registered)
710
+ expect(parentUow.getRetrievalOperations()).toHaveLength(2);
711
+
712
+ // Verify execution happened
713
+ const log = executor.getLog();
714
+ expect(log).toEqual(["RETRIEVAL: 2 queries"]);
715
+
716
+ // No mutations should have been added since we threw during retrieval validation
717
+ expect(parentUow.getMutationOperations()).toHaveLength(0);
718
+
719
+ // Give Node.js event loop time to detect any unhandled rejections
720
+ await new Promise((resolve) => setTimeout(resolve, 10));
721
+
722
+ // If we got here without Node.js throwing an unhandled rejection, the test passes
723
+ });
724
+
725
+ it("should inherit nonce from parent to children for idempotent operations", () => {
726
+ const executor = createMockExecutor();
727
+ const parentUow = createUnitOfWork(createCompiler(), executor, createMockDecoder());
728
+
729
+ // Parent UOW should have a nonce
730
+ const parentNonce = parentUow.nonce;
731
+ expect(parentNonce).toBeDefined();
732
+ expect(typeof parentNonce).toBe("string");
733
+ expect(parentNonce.length).toBeGreaterThan(0);
734
+
735
+ // Create first child
736
+ const child1 = parentUow.restrict();
737
+ expect(child1.nonce).toBe(parentNonce);
738
+
739
+ // Create second child (sibling to child1)
740
+ const child2 = parentUow.restrict();
741
+ expect(child2.nonce).toBe(parentNonce);
742
+
743
+ // Create nested child (child of child1)
744
+ const grandchild = child1.restrict();
745
+ expect(grandchild.nonce).toBe(parentNonce);
746
+
747
+ // All UOWs in the hierarchy should share the same nonce
748
+ expect(parentUow.nonce).toBe(child1.nonce);
749
+ expect(child1.nonce).toBe(child2.nonce);
750
+ expect(child2.nonce).toBe(grandchild.nonce);
751
+ });
752
+
753
+ it("should generate different nonces for separate UOW hierarchies", () => {
754
+ const executor = createMockExecutor();
755
+
756
+ // Create two separate parent UOWs
757
+ const parentUow1 = createUnitOfWork(createCompiler(), executor, createMockDecoder());
758
+ const parentUow2 = createUnitOfWork(createCompiler(), executor, createMockDecoder());
759
+
760
+ // They should have different nonces
761
+ expect(parentUow1.nonce).not.toBe(parentUow2.nonce);
762
+
763
+ // But children within each hierarchy should inherit their parent's nonce
764
+ const child1 = parentUow1.restrict();
765
+ const child2 = parentUow2.restrict();
766
+
767
+ expect(child1.nonce).toBe(parentUow1.nonce);
768
+ expect(child2.nonce).toBe(parentUow2.nonce);
769
+ expect(child1.nonce).not.toBe(child2.nonce);
770
+ });
771
+
772
+ it.skip("should not cause unhandled rejection when service method awaits retrievalPhase and executeRetrieve fails", async () => {
773
+ const testSchema = schema((s) =>
774
+ s.addTable("settings", (t) =>
775
+ t
776
+ .addColumn("id", idColumn())
777
+ .addColumn("key", "string")
778
+ .addColumn("value", "string")
779
+ .createIndex("unique_key", ["key"], { unique: true }),
780
+ ),
781
+ );
782
+
783
+ // Create executor that throws "table does not exist" error
784
+ const failingExecutor: UOWExecutor<CompiledQuery, unknown> = {
785
+ executeRetrievalPhase: async () => {
786
+ throw new Error('relation "settings" does not exist');
787
+ },
788
+ executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
789
+ };
790
+
791
+ const parentUow = createUnitOfWork(createCompiler(), failingExecutor, createMockDecoder());
792
+
793
+ // Service method that awaits retrievalPhase (simulating settingsService.get())
794
+ const getSettingValue = async (key: string) => {
795
+ const childUow = parentUow.restrict();
796
+ const typedUow = childUow
797
+ .forSchema(testSchema)
798
+ .find("settings", (b) => b.whereIndex("unique_key", (eb) => eb("key", "=", key)));
799
+
800
+ // This is the critical line - accessing retrievalPhase creates a new promise
801
+ // If not cached properly, this new promise won't have a catch handler attached
802
+ const [results] = await typedUow.retrievalPhase;
803
+ return results?.[0];
804
+ };
805
+
806
+ const deferred = Promise.withResolvers<string>();
807
+
808
+ // Handler that calls the service and handles the error
809
+ const handler = async () => {
810
+ try {
811
+ const settingPromise = getSettingValue("version");
812
+
813
+ await parentUow.executeRetrieve();
814
+
815
+ // Won't reach here
816
+ return await settingPromise;
817
+ } catch (error) {
818
+ // Error is caught here - this is expected behavior
819
+ expect(error).toBeInstanceOf(Error);
820
+ deferred.resolve((error as Error).message);
821
+ return null;
822
+ }
823
+ };
824
+
825
+ // Execute handler - should catch the error without unhandled rejection
826
+ const result = await handler();
827
+ expect(result).toBeNull();
828
+
829
+ expect(await deferred.promise).toContain('relation "settings" does not exist');
830
+ });
831
+ });