@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,1716 @@
1
+ import { describe, it, expect, assert, expectTypeOf } from "vitest";
2
+ import { column, schema, idColumn, FragnoId } from "../../schema/create";
3
+ import {
4
+ type UOWCompiler,
5
+ type UOWDecoder,
6
+ createUnitOfWork,
7
+ type InferIdColumnName,
8
+ type IndexColumns,
9
+ } from "./unit-of-work";
10
+ import { createIndexedBuilder } from "../condition-builder";
11
+ import type { SimpleQueryInterface } from "../simple-query-interface";
12
+
13
+ // Mock compiler and executor for testing
14
+ function createMockCompiler(): UOWCompiler<unknown> {
15
+ return {
16
+ compileRetrievalOperation: () => null,
17
+ compileMutationOperation: () => null,
18
+ };
19
+ }
20
+
21
+ function createMockExecutor() {
22
+ return {
23
+ executeRetrievalPhase: async () => [],
24
+ executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
25
+ };
26
+ }
27
+
28
+ function createMockDecoder(): UOWDecoder {
29
+ return {
30
+ decode(rawResults, operations) {
31
+ if (rawResults.length !== operations.length) {
32
+ throw new Error("rawResults and operations must have the same length");
33
+ }
34
+ return rawResults;
35
+ },
36
+ };
37
+ }
38
+
39
+ describe("FindBuilder", () => {
40
+ it("should support primary index", () => {
41
+ const testSchema = schema((s) =>
42
+ s.addTable("users", (t) =>
43
+ t
44
+ .addColumn("id", idColumn())
45
+ .addColumn("email", "string")
46
+ .addColumn("name", "string")
47
+ .addColumn("age", "integer")
48
+ .createIndex("idx_email", ["email"], { unique: true })
49
+ .createIndex("idx_name_age", ["name", "age"]),
50
+ ),
51
+ );
52
+
53
+ const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
54
+ uow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary"));
55
+
56
+ const ops = uow.getRetrievalOperations();
57
+ expect(ops).toHaveLength(1);
58
+ expect(ops[0].indexName).toBe("_primary");
59
+ });
60
+
61
+ it("should support custom indexes", () => {
62
+ const testSchema = schema((s) =>
63
+ s.addTable("users", (t) =>
64
+ t
65
+ .addColumn("id", idColumn())
66
+ .addColumn("email", "string")
67
+ .addColumn("name", "string")
68
+ .createIndex("idx_email", ["email"], { unique: true }),
69
+ ),
70
+ );
71
+
72
+ const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
73
+ uow
74
+ .forSchema(testSchema)
75
+ .find("users", (b) =>
76
+ b.whereIndex("idx_email", (eb) => eb("email", "=", "test@example.com")),
77
+ );
78
+
79
+ const ops = uow.getRetrievalOperations();
80
+ expect(ops).toHaveLength(1);
81
+ expect(ops[0].indexName).toBe("idx_email");
82
+ });
83
+
84
+ it("should support cursor-based pagination", () => {
85
+ const testSchema = schema((s) =>
86
+ s.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string")),
87
+ );
88
+
89
+ const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
90
+
91
+ const cursor = "eyJpbmRleFZhbHVlcyI6eyJpZCI6InVzZXIxMjMifSwiZGlyZWN0aW9uIjoiZm9yd2FyZCJ9";
92
+ uow
93
+ .forSchema(testSchema)
94
+ .find("users", (b) => b.whereIndex("primary").after(cursor).pageSize(10));
95
+
96
+ const ops = uow.getRetrievalOperations();
97
+ expect(ops).toHaveLength(1);
98
+ const op = ops[0];
99
+ assert(op.type === "find");
100
+ expect(op.options.after).toBe(cursor);
101
+ expect(op.options.pageSize).toBe(10);
102
+ });
103
+
104
+ it("should support backward cursor pagination", () => {
105
+ const testSchema = schema((s) =>
106
+ s.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string")),
107
+ );
108
+
109
+ const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
110
+
111
+ const cursor = "eyJpbmRleFZhbHVlcyI6eyJpZCI6InVzZXI0NTYifSwiZGlyZWN0aW9uIjoiYmFja3dhcmQifQ==";
112
+ uow
113
+ .forSchema(testSchema)
114
+ .find("users", (b) => b.whereIndex("primary").before(cursor).pageSize(5));
115
+
116
+ const ops = uow.getRetrievalOperations();
117
+ expect(ops).toHaveLength(1);
118
+ const op = ops[0];
119
+ assert(op.type === "find");
120
+ expect(op.options.before).toBe(cursor);
121
+ expect(op.options.pageSize).toBe(5);
122
+ });
123
+
124
+ it("should throw RangeError for pageSize <= 0", () => {
125
+ const testSchema = schema((s) =>
126
+ s.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string")),
127
+ );
128
+
129
+ const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
130
+
131
+ expect(() => {
132
+ uow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary").pageSize(0));
133
+ }).toThrow(RangeError);
134
+
135
+ expect(() => {
136
+ uow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary").pageSize(-1));
137
+ }).toThrow(RangeError);
138
+
139
+ expect(() => {
140
+ uow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary").pageSize(-10));
141
+ }).toThrow(RangeError);
142
+ });
143
+
144
+ it("should throw RangeError for non-integer pageSize", () => {
145
+ const testSchema = schema((s) =>
146
+ s.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string")),
147
+ );
148
+
149
+ const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
150
+
151
+ expect(() => {
152
+ uow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary").pageSize(1.5));
153
+ }).toThrow(RangeError);
154
+
155
+ expect(() => {
156
+ uow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary").pageSize(3.14));
157
+ }).toThrow(RangeError);
158
+
159
+ expect(() => {
160
+ uow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary").pageSize(NaN));
161
+ }).toThrow(RangeError);
162
+
163
+ expect(() => {
164
+ uow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary").pageSize(Infinity));
165
+ }).toThrow(RangeError);
166
+ });
167
+
168
+ it("should throw if index doesn't exist", () => {
169
+ const testSchema = schema((s) =>
170
+ s.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string")),
171
+ );
172
+
173
+ const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
174
+ expect(() => {
175
+ uow.forSchema(testSchema).find("users", (b) => b.whereIndex("nonexistent" as "primary"));
176
+ }).toThrow('Index "nonexistent" not found on table "users"');
177
+ });
178
+
179
+ it("should throw if finalized without index", () => {
180
+ const testSchema = schema((s) =>
181
+ s.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string")),
182
+ );
183
+
184
+ const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
185
+ expect(() => {
186
+ uow.forSchema(testSchema).find("users", (b) => b);
187
+ }).toThrow(
188
+ 'Must specify an index using .whereIndex() before finalizing find operation on table "users"',
189
+ );
190
+ });
191
+
192
+ it("should support count operations", () => {
193
+ const testSchema = schema((s) =>
194
+ s.addTable("users", (t) =>
195
+ t.addColumn("id", idColumn()).addColumn("name", "string").addColumn("age", "integer"),
196
+ ),
197
+ );
198
+
199
+ const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
200
+ uow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary").selectCount());
201
+
202
+ const ops = uow.getRetrievalOperations();
203
+ expect(ops).toHaveLength(1);
204
+ expect(ops[0]?.type).toBe("count");
205
+ });
206
+
207
+ it("should throw when using both select and selectCount", () => {
208
+ const testSchema = schema((s) =>
209
+ s.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string")),
210
+ );
211
+
212
+ const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
213
+
214
+ // select() then selectCount()
215
+ expect(() => {
216
+ uow
217
+ .forSchema(testSchema)
218
+ .find("users", (b) => b.whereIndex("primary").select(["name"]).selectCount());
219
+ }).toThrow(/cannot call selectCount/i);
220
+
221
+ // selectCount() then select()
222
+ const uow2 = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
223
+ expect(() => {
224
+ uow2
225
+ .forSchema(testSchema)
226
+ .find("users", (b) => b.whereIndex("primary").selectCount().select(["name"]));
227
+ }).toThrow(/cannot call select/i);
228
+ });
229
+
230
+ it("should support orderByIndex", () => {
231
+ const testSchema = schema((s) =>
232
+ s.addTable("users", (t) =>
233
+ t
234
+ .addColumn("id", idColumn())
235
+ .addColumn("name", "string")
236
+ .addColumn("createdAt", "integer")
237
+ .createIndex("idx_created", ["createdAt"]),
238
+ ),
239
+ );
240
+
241
+ const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
242
+ uow
243
+ .forSchema(testSchema)
244
+ .find("users", (b) => b.whereIndex("primary").orderByIndex("idx_created", "desc"));
245
+
246
+ const ops = uow.getRetrievalOperations();
247
+ expect(ops).toHaveLength(1);
248
+ const op = ops[0];
249
+ if (op?.type === "find") {
250
+ expect(op.options.orderByIndex).toEqual({
251
+ indexName: "idx_created",
252
+ direction: "desc",
253
+ });
254
+ } else {
255
+ throw new Error("Expected find operation");
256
+ }
257
+ });
258
+
259
+ it("should support join operations", () => {
260
+ const testSchema = schema((s) =>
261
+ s
262
+ .addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string"))
263
+ .addTable("posts", (t) =>
264
+ t
265
+ .addColumn("id", idColumn())
266
+ .addColumn("userId", column("string"))
267
+ .addColumn("title", "string")
268
+ .createIndex("idx_user", ["userId"]),
269
+ )
270
+ .addReference("user", {
271
+ type: "one",
272
+ from: { table: "posts", column: "userId" },
273
+ to: { table: "users", column: "id" },
274
+ }),
275
+ );
276
+
277
+ const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
278
+
279
+ uow
280
+ .forSchema(testSchema)
281
+ .find("posts", (b) =>
282
+ b.whereIndex("primary").join((jb) => jb["user"]((builder) => builder.select(["name"]))),
283
+ );
284
+
285
+ const ops = uow.getRetrievalOperations();
286
+ expect(ops).toHaveLength(1);
287
+ const op = ops[0];
288
+ assert(op.type === "find");
289
+ expect(op.options.joins).toBeDefined();
290
+ expect(op.options.joins).toHaveLength(1);
291
+ });
292
+
293
+ it("should support join operations without builder function", () => {
294
+ const testSchema = schema((s) =>
295
+ s
296
+ .addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string"))
297
+ .addTable("posts", (t) =>
298
+ t
299
+ .addColumn("id", idColumn())
300
+ .addColumn("userId", column("string"))
301
+ .addColumn("title", "string")
302
+ .createIndex("idx_user", ["userId"]),
303
+ )
304
+ .addReference("user", {
305
+ type: "one",
306
+ from: { table: "posts", column: "userId" },
307
+ to: { table: "users", column: "id" },
308
+ }),
309
+ );
310
+
311
+ const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
312
+
313
+ // Join without builder function should use default options
314
+ uow.forSchema(testSchema).find("posts", (b) => b.whereIndex("primary").join((jb) => jb.user()));
315
+
316
+ const ops = uow.getRetrievalOperations();
317
+ expect(ops).toHaveLength(1);
318
+ const op = ops[0];
319
+ assert(op.type === "find");
320
+ expect(op.options.joins).toBeDefined();
321
+ expect(op.options.joins).toHaveLength(1);
322
+ const joinOptions = op.options.joins![0]!.options;
323
+ assert(joinOptions !== false);
324
+ expect(joinOptions.select).toBe(true); // Should default to selecting all columns
325
+ });
326
+
327
+ it("should support join with whereIndex", () => {
328
+ const testSchema = schema((s) =>
329
+ s
330
+ .addTable("users", (t) =>
331
+ t
332
+ .addColumn("id", idColumn())
333
+ .addColumn("name", "string")
334
+ .createIndex("idx_name", ["name"]),
335
+ )
336
+ .addTable("posts", (t) =>
337
+ t
338
+ .addColumn("id", idColumn())
339
+ .addColumn("userId", column("string"))
340
+ .addColumn("title", "string")
341
+ .createIndex("idx_user", ["userId"]),
342
+ )
343
+ .addReference("user", {
344
+ type: "one",
345
+ from: { table: "posts", column: "userId" },
346
+ to: { table: "users", column: "id" },
347
+ }),
348
+ );
349
+
350
+ const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
351
+
352
+ uow
353
+ .forSchema(testSchema)
354
+ .find("posts", (b) =>
355
+ b
356
+ .whereIndex("primary")
357
+ .join((jb) =>
358
+ jb["user"]((builder) =>
359
+ builder.whereIndex("idx_name", (eb) => eb("name", "=", "Alice")).select(["name"]),
360
+ ),
361
+ ),
362
+ );
363
+
364
+ const ops = uow.getRetrievalOperations();
365
+ expect(ops).toHaveLength(1);
366
+ const op = ops[0];
367
+ assert(op.type === "find");
368
+ expect(op.options.joins).toBeDefined();
369
+ expect(op.options.joins).toHaveLength(1);
370
+ const joinOptions = op.options.joins![0]!.options;
371
+ assert(joinOptions !== false);
372
+ expect(joinOptions.where).toBeDefined();
373
+ });
374
+
375
+ it("should support join with orderByIndex", () => {
376
+ const testSchema = schema((s) =>
377
+ s
378
+ .addTable("users", (t) =>
379
+ t
380
+ .addColumn("id", idColumn())
381
+ .addColumn("name", "string")
382
+ .addColumn("createdAt", "integer")
383
+ .createIndex("idx_created", ["createdAt"]),
384
+ )
385
+ .addTable("posts", (t) =>
386
+ t
387
+ .addColumn("id", idColumn())
388
+ .addColumn("userId", column("string"))
389
+ .addColumn("title", "string")
390
+ .createIndex("idx_user", ["userId"]),
391
+ )
392
+ .addReference("user", {
393
+ type: "one",
394
+ from: { table: "posts", column: "userId" },
395
+ to: { table: "users", column: "id" },
396
+ }),
397
+ );
398
+
399
+ const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
400
+
401
+ uow
402
+ .forSchema(testSchema)
403
+ .find("posts", (b) =>
404
+ b
405
+ .whereIndex("primary")
406
+ .join((jb) => jb["user"]((builder) => builder.orderByIndex("idx_created", "desc"))),
407
+ );
408
+
409
+ const ops = uow.getRetrievalOperations();
410
+ expect(ops).toHaveLength(1);
411
+ const op = ops[0];
412
+ assert(op.type === "find");
413
+ expect(op.options.joins).toBeDefined();
414
+ const joinOptions = op.options.joins![0]!.options;
415
+ assert(joinOptions !== false);
416
+ expect(joinOptions.orderBy).toBeDefined();
417
+ expect(joinOptions.orderBy).toHaveLength(1);
418
+ });
419
+
420
+ it("should support join with pageSize", () => {
421
+ const testSchema = schema((s) =>
422
+ s
423
+ .addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string"))
424
+ .addTable("posts", (t) =>
425
+ t
426
+ .addColumn("id", idColumn())
427
+ .addColumn("userId", column("string"))
428
+ .addColumn("title", "string")
429
+ .createIndex("idx_user", ["userId"]),
430
+ )
431
+ .addReference("user", {
432
+ type: "one",
433
+ from: { table: "posts", column: "userId" },
434
+ to: { table: "users", column: "id" },
435
+ }),
436
+ );
437
+
438
+ const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
439
+
440
+ uow
441
+ .forSchema(testSchema)
442
+ .find("posts", (b) =>
443
+ b.whereIndex("primary").join((jb) => jb["user"]((builder) => builder.pageSize(5))),
444
+ );
445
+
446
+ const ops = uow.getRetrievalOperations();
447
+ expect(ops).toHaveLength(1);
448
+ const op = ops[0];
449
+ assert(op.type === "find");
450
+ const joinOptions = op.options.joins![0]!.options;
451
+ assert(joinOptions !== false);
452
+ expect(joinOptions.limit).toBe(5);
453
+ });
454
+
455
+ it("should throw RangeError for invalid pageSize in join", () => {
456
+ const testSchema = schema((s) =>
457
+ s
458
+ .addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string"))
459
+ .addTable("posts", (t) =>
460
+ t
461
+ .addColumn("id", idColumn())
462
+ .addColumn("userId", column("string"))
463
+ .addColumn("title", "string")
464
+ .createIndex("idx_user", ["userId"]),
465
+ )
466
+ .addReference("user", {
467
+ type: "one",
468
+ from: { table: "posts", column: "userId" },
469
+ to: { table: "users", column: "id" },
470
+ }),
471
+ );
472
+
473
+ const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
474
+
475
+ expect(() => {
476
+ uow
477
+ .forSchema(testSchema)
478
+ .find("posts", (b) =>
479
+ b.whereIndex("primary").join((jb) => jb["user"]((builder) => builder.pageSize(0))),
480
+ );
481
+ }).toThrow(RangeError);
482
+
483
+ expect(() => {
484
+ uow
485
+ .forSchema(testSchema)
486
+ .find("posts", (b) =>
487
+ b.whereIndex("primary").join((jb) => jb["user"]((builder) => builder.pageSize(-5))),
488
+ );
489
+ }).toThrow(RangeError);
490
+
491
+ expect(() => {
492
+ uow
493
+ .forSchema(testSchema)
494
+ .find("posts", (b) =>
495
+ b.whereIndex("primary").join((jb) => jb["user"]((builder) => builder.pageSize(2.5))),
496
+ );
497
+ }).toThrow(RangeError);
498
+ });
499
+
500
+ it("should support nested joins", () => {
501
+ const testSchema = schema((s) =>
502
+ s
503
+ .addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string"))
504
+ .addTable("posts", (t) =>
505
+ t
506
+ .addColumn("id", idColumn())
507
+ .addColumn("userId", column("string"))
508
+ .addColumn("authorId", column("string"))
509
+ .addColumn("title", "string")
510
+ .createIndex("idx_user", ["userId"])
511
+ .createIndex("idx_author", ["authorId"]),
512
+ )
513
+ .addTable("comments", (t) =>
514
+ t
515
+ .addColumn("id", idColumn())
516
+ .addColumn("postId", column("string"))
517
+ .addColumn("text", "string")
518
+ .createIndex("idx_post", ["postId"]),
519
+ )
520
+ .addReference("user", {
521
+ type: "one",
522
+ from: { table: "posts", column: "userId" },
523
+ to: { table: "users", column: "id" },
524
+ })
525
+ .addReference("post", {
526
+ type: "one",
527
+ from: { table: "comments", column: "postId" },
528
+ to: { table: "posts", column: "id" },
529
+ }),
530
+ );
531
+
532
+ const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
533
+
534
+ uow
535
+ .forSchema(testSchema)
536
+ .find("comments", (b) =>
537
+ b
538
+ .whereIndex("primary")
539
+ .join((jb) =>
540
+ jb["post"]((postBuilder) =>
541
+ postBuilder
542
+ .select(["title"])
543
+ .join((jb2) => jb2["user"]((userBuilder) => userBuilder.select(["name"]))),
544
+ ),
545
+ ),
546
+ );
547
+
548
+ const ops = uow.getRetrievalOperations();
549
+ expect(ops).toHaveLength(1);
550
+ const op = ops[0];
551
+ assert(op.type === "find");
552
+ expect(op.options.joins).toBeDefined();
553
+ expect(op.options.joins).toHaveLength(1);
554
+
555
+ const postJoin = op.options.joins![0]!;
556
+ assert(postJoin.options !== false);
557
+ expect(postJoin.options.join).toBeDefined();
558
+ expect(postJoin.options.join).toHaveLength(1);
559
+
560
+ const userJoin = postJoin.options.join![0]!;
561
+ assert(userJoin.options !== false);
562
+ expect(userJoin.relation.name).toBe("user");
563
+ });
564
+ });
565
+
566
+ describe("IndexedConditionBuilder", () => {
567
+ const testSchema = schema((s) =>
568
+ s.addTable("users", (t) =>
569
+ t
570
+ .addColumn("id", idColumn())
571
+ .addColumn("email", column("string"))
572
+ .addColumn("name", column("string"))
573
+ .addColumn("age", column("integer").nullable())
574
+ .addColumn("bio", column("string").nullable()) // Not indexed
575
+ .createIndex("_primary", ["id"], { unique: true })
576
+ .createIndex("idx_email", ["email"], { unique: true })
577
+ .createIndex("idx_name_age", ["name", "age"]),
578
+ ),
579
+ );
580
+
581
+ const usersTable = testSchema.tables.users;
582
+
583
+ it("should enforce indexed columns at runtime", () => {
584
+ // Collect all indexed column names from all indexes
585
+ const indexedColumns = new Set<string>();
586
+ for (const index of Object.values(usersTable.indexes)) {
587
+ for (const col of index.columns) {
588
+ indexedColumns.add(col.ormName);
589
+ }
590
+ }
591
+
592
+ const builder = createIndexedBuilder(usersTable.columns, indexedColumns);
593
+
594
+ // Should work with indexed columns
595
+ expect(() => builder("id", "=", "123")).not.toThrow();
596
+ expect(() => builder("email", "=", "test@example.com")).not.toThrow();
597
+ expect(() => builder("name", "=", "Alice")).not.toThrow();
598
+ expect(() => builder("age", ">", 18)).not.toThrow();
599
+
600
+ // Should throw when using non-indexed column
601
+ expect(() => builder("bio" as "email", "=", "Some bio")).toThrow('Column "bio" is not indexed');
602
+ });
603
+
604
+ it("should work with complex conditions", () => {
605
+ const indexedColumns = new Set(["id", "email", "name", "age"]);
606
+ const builder = createIndexedBuilder(usersTable.columns, indexedColumns);
607
+
608
+ // Complex AND condition with indexed columns
609
+ const condition = builder.and(
610
+ builder("name", "=", "Alice"),
611
+ builder("age", ">", 18),
612
+ builder("email", "contains", "example"),
613
+ );
614
+
615
+ expect(condition).toEqual({
616
+ type: "and",
617
+ items: [
618
+ {
619
+ type: "compare",
620
+ a: usersTable.columns.name,
621
+ operator: "=",
622
+ b: "Alice",
623
+ },
624
+ {
625
+ type: "compare",
626
+ a: usersTable.columns.age,
627
+ operator: ">",
628
+ b: 18,
629
+ },
630
+ {
631
+ type: "compare",
632
+ a: usersTable.columns.email,
633
+ operator: "contains",
634
+ b: "example",
635
+ },
636
+ ],
637
+ });
638
+ });
639
+
640
+ it("should provide helpful error message listing available columns", () => {
641
+ const indexedColumns = new Set(["id", "email"]);
642
+ const builder = createIndexedBuilder(usersTable.columns, indexedColumns);
643
+
644
+ expect(() => builder("name" as "email", "=", "Alice")).toThrow(
645
+ "Only indexed columns can be used in Unit of Work queries. Available indexed columns: id, email",
646
+ );
647
+ });
648
+
649
+ it("should work with all builder helper methods", () => {
650
+ const indexedColumns = new Set(["id", "email", "age"]);
651
+ const builder = createIndexedBuilder(usersTable.columns, indexedColumns);
652
+
653
+ // isNull
654
+ expect(() => builder.isNull("age")).not.toThrow();
655
+ expect(() => builder.isNull("bio" as "age")).toThrow('Column "bio" is not indexed');
656
+
657
+ // isNotNull
658
+ expect(() => builder.isNotNull("email")).not.toThrow();
659
+ expect(() => builder.isNotNull("bio" as "email")).toThrow('Column "bio" is not indexed');
660
+
661
+ // not
662
+ const notCondition = builder.not(builder("id", "=", "123"));
663
+ expect(notCondition).toEqual({
664
+ type: "not",
665
+ item: {
666
+ type: "compare",
667
+ a: usersTable.columns.id,
668
+ operator: "=",
669
+ b: "123",
670
+ },
671
+ });
672
+
673
+ // or
674
+ const orCondition = builder.or(builder("email", "contains", "gmail"), builder("age", ">", 30));
675
+ expect(orCondition).toEqual({
676
+ type: "or",
677
+ items: [
678
+ {
679
+ type: "compare",
680
+ a: usersTable.columns.email,
681
+ operator: "contains",
682
+ b: "gmail",
683
+ },
684
+ {
685
+ type: "compare",
686
+ a: usersTable.columns.age,
687
+ operator: ">",
688
+ b: 30,
689
+ },
690
+ ],
691
+ });
692
+ });
693
+
694
+ it("should enforce index restrictions in nested conditions", () => {
695
+ const indexedColumns = new Set(["id", "email"]);
696
+ const builder = createIndexedBuilder(usersTable.columns, indexedColumns);
697
+
698
+ // This should throw because "name" is not indexed, even though it's nested
699
+ expect(() => {
700
+ builder.and(
701
+ builder("email", "=", "test@example.com"),
702
+ builder("name" as "email", "=", "Alice"),
703
+ );
704
+ }).toThrow('Column "name" is not indexed');
705
+
706
+ // This should throw because "bio" is not indexed, even in OR
707
+ expect(() => {
708
+ builder.or(builder("id", "=", "123"), builder("bio" as "id", "=", "Some bio"));
709
+ }).toThrow('Column "bio" is not indexed');
710
+ });
711
+
712
+ describe("type safety", () => {
713
+ it("should restrict to only indexed columns at type level", () => {
714
+ // This schema has "bio" column that is NOT indexed
715
+ const typeTestSchema = schema((s) =>
716
+ s.addTable("users", (t) =>
717
+ t
718
+ .addColumn("id", idColumn())
719
+ .addColumn("email", column("string"))
720
+ .addColumn("name", column("string"))
721
+ .addColumn("age", column("integer").nullable())
722
+ .addColumn("bio", column("string").nullable()) // Not indexed!
723
+ .createIndex("idx_email", ["email"], { unique: true })
724
+ .createIndex("idx_name_age", ["name", "age"]),
725
+ ),
726
+ );
727
+
728
+ type _IdColumnName = InferIdColumnName<typeof typeTestSchema.tables.users>;
729
+ expectTypeOf<_IdColumnName>().toEqualTypeOf<"id">();
730
+ type _IndexColumnNames = IndexColumns<
731
+ typeof typeTestSchema.tables.users.indexes.idx_name_age
732
+ >;
733
+ expectTypeOf<_IndexColumnNames>().toEqualTypeOf<"name" | "age">();
734
+
735
+ const baseUow = createUnitOfWork(
736
+ createMockCompiler(),
737
+ createMockExecutor(),
738
+ createMockDecoder(),
739
+ );
740
+ const uow = baseUow.forSchema(typeTestSchema);
741
+ expectTypeOf<keyof typeof typeTestSchema.tables>().toEqualTypeOf<"users">();
742
+ type _Query = SimpleQueryInterface<typeof typeTestSchema>;
743
+ expectTypeOf<Parameters<_Query["create"]>[0]>().toEqualTypeOf<"users">();
744
+
745
+ expectTypeOf<Parameters<typeof uow.find>[0]>().toEqualTypeOf<"users">();
746
+
747
+ uow.find("users", (b) =>
748
+ b.whereIndex("primary", (eb) => {
749
+ type _EbFirstParameter = Parameters<typeof eb>[0];
750
+ expectTypeOf<_EbFirstParameter>().toEqualTypeOf<"id">();
751
+ return eb("id", "=", "123");
752
+ }),
753
+ );
754
+
755
+ uow.find("users", (b) =>
756
+ b.whereIndex("idx_email", (eb) => {
757
+ expectTypeOf(eb).parameter(0).toEqualTypeOf<"email">();
758
+ return eb("email", "=", "123");
759
+ }),
760
+ );
761
+
762
+ uow.find("users", (b) =>
763
+ b.whereIndex("idx_name_age", (eb) => {
764
+ expectTypeOf(eb).parameter(0).toEqualTypeOf<"name" | "age">();
765
+ return eb("name", "=", "123");
766
+ }),
767
+ );
768
+ });
769
+ });
770
+ });
771
+
772
+ describe("UpdateBuilder with string ID", () => {
773
+ const testSchema = schema((s) =>
774
+ s.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string")),
775
+ );
776
+
777
+ it("should allow update with string ID", async () => {
778
+ const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
779
+
780
+ // Should work with string ID
781
+ uow.forSchema(testSchema).update("users", "user-123", (b) => b.set({ name: "New Name" }));
782
+
783
+ const ops = uow.getMutationOperations();
784
+ expect(ops).toHaveLength(1);
785
+ expect(ops).toMatchObject([
786
+ {
787
+ type: "update",
788
+ id: "user-123",
789
+ checkVersion: false,
790
+ },
791
+ ]);
792
+ });
793
+
794
+ it("should throw when using check() with string ID", async () => {
795
+ const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
796
+
797
+ // Should throw because check() is not allowed with string ID
798
+ expect(() => {
799
+ uow
800
+ .forSchema(testSchema)
801
+ .update("users", "user-123", (b) => b.set({ name: "New Name" }).check());
802
+ }).toThrow(
803
+ 'Cannot use check() with a string ID on table "users". Version checking requires a FragnoId with version information.',
804
+ );
805
+ });
806
+ });
807
+
808
+ describe("DeleteBuilder with string ID", () => {
809
+ const testSchema = schema((s) =>
810
+ s.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string")),
811
+ );
812
+
813
+ it("should allow delete with string ID", async () => {
814
+ const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
815
+
816
+ // Should work with string ID
817
+ uow.forSchema(testSchema).delete("users", "user-123");
818
+
819
+ const ops = uow.getMutationOperations();
820
+ expect(ops).toMatchObject([
821
+ {
822
+ type: "delete",
823
+ id: "user-123",
824
+ checkVersion: false,
825
+ },
826
+ ]);
827
+ });
828
+
829
+ it("should throw when using check() with string ID", async () => {
830
+ const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
831
+
832
+ // Should throw because check() is not allowed with string ID
833
+ expect(() => {
834
+ uow.forSchema(testSchema).delete("users", "user-123", (b) => b.check());
835
+ }).toThrow(
836
+ 'Cannot use check() with a string ID on table "users". Version checking requires a FragnoId with version information.',
837
+ );
838
+ });
839
+ });
840
+
841
+ describe("getCreatedIds", () => {
842
+ const testSchema = schema((s) =>
843
+ s.addTable("users", (t) =>
844
+ t.addColumn("id", idColumn()).addColumn("email", "string").addColumn("name", "string"),
845
+ ),
846
+ );
847
+
848
+ it("should return created IDs after executeMutations with internal IDs", async () => {
849
+ const executor = {
850
+ executeRetrievalPhase: async () => [],
851
+ executeMutationPhase: async () => ({
852
+ success: true,
853
+ createdInternalIds: [1n, 2n],
854
+ }),
855
+ };
856
+
857
+ const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
858
+
859
+ uow.forSchema(testSchema).create("users", { email: "user1@example.com", name: "User 1" });
860
+ uow.forSchema(testSchema).create("users", { email: "user2@example.com", name: "User 2" });
861
+
862
+ await uow.executeMutations();
863
+ const createdIds = uow.getCreatedIds();
864
+
865
+ expect(createdIds).toHaveLength(2);
866
+ expect(createdIds[0].externalId).toBeDefined();
867
+ expect(createdIds[0].internalId).toBe(1n);
868
+ expect(createdIds[0].version).toBe(0);
869
+ expect(createdIds[1].externalId).toBeDefined();
870
+ expect(createdIds[1].internalId).toBe(2n);
871
+ expect(createdIds[1].version).toBe(0);
872
+ });
873
+
874
+ it("should return created IDs without internal IDs when not supported", async () => {
875
+ const executor = {
876
+ executeRetrievalPhase: async () => [],
877
+ executeMutationPhase: async () => ({
878
+ success: true,
879
+ createdInternalIds: [null, null],
880
+ }),
881
+ };
882
+
883
+ const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
884
+
885
+ uow.forSchema(testSchema).create("users", { email: "user1@example.com", name: "User 1" });
886
+ uow.forSchema(testSchema).create("users", { email: "user2@example.com", name: "User 2" });
887
+
888
+ await uow.executeMutations();
889
+ const createdIds = uow.getCreatedIds();
890
+
891
+ expect(createdIds).toHaveLength(2);
892
+ expect(createdIds[0].externalId).toBeDefined();
893
+ expect(createdIds[0].internalId).toBeUndefined();
894
+ expect(createdIds[1].externalId).toBeDefined();
895
+ expect(createdIds[1].internalId).toBeUndefined();
896
+ });
897
+
898
+ it("should preserve user-provided external IDs", async () => {
899
+ const executor = {
900
+ executeRetrievalPhase: async () => [],
901
+ executeMutationPhase: async () => ({
902
+ success: true,
903
+ createdInternalIds: [1n],
904
+ }),
905
+ };
906
+
907
+ const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
908
+
909
+ uow
910
+ .forSchema(testSchema)
911
+ .create("users", { id: "my-custom-id", email: "user@example.com", name: "User" });
912
+
913
+ await uow.executeMutations();
914
+ const createdIds = uow.getCreatedIds();
915
+
916
+ expect(createdIds).toHaveLength(1);
917
+ expect(createdIds[0].externalId).toBe("my-custom-id");
918
+ expect(createdIds[0].internalId).toBe(1n);
919
+ });
920
+
921
+ it("should only return IDs for create operations, not updates or deletes", async () => {
922
+ const executor = {
923
+ executeRetrievalPhase: async () => [],
924
+ executeMutationPhase: async () => ({
925
+ success: true,
926
+ createdInternalIds: [1n],
927
+ }),
928
+ };
929
+
930
+ const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
931
+
932
+ uow.forSchema(testSchema).create("users", { email: "user@example.com", name: "User" });
933
+ uow.forSchema(testSchema).update("users", "existing-id", (b) => b.set({ name: "Updated" }));
934
+ uow.forSchema(testSchema).delete("users", "other-id");
935
+
936
+ await uow.executeMutations();
937
+ const createdIds = uow.getCreatedIds();
938
+
939
+ // Only one create operation, so only one ID returned
940
+ expect(createdIds).toHaveLength(1);
941
+ expect(createdIds[0].internalId).toBe(1n);
942
+ });
943
+
944
+ it("should throw when called before executeMutations", () => {
945
+ const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
946
+
947
+ uow.forSchema(testSchema).create("users", { email: "user@example.com", name: "User" });
948
+
949
+ expect(() => uow.getCreatedIds()).toThrow(
950
+ "getCreatedIds() can only be called after executeMutations()",
951
+ );
952
+ });
953
+ });
954
+
955
+ describe("Phase promises with multiple views", () => {
956
+ it("should return only operations added to the current view when using retrievalPhase promise", async () => {
957
+ // Create two separate schemas
958
+ const schema1 = schema((s) =>
959
+ s.addTable("users", (t) =>
960
+ t.addColumn("id", idColumn()).addColumn("name", "string").addColumn("email", "string"),
961
+ ),
962
+ );
963
+
964
+ const schema2 = schema((s) =>
965
+ s.addTable("posts", (t) =>
966
+ t.addColumn("id", idColumn()).addColumn("title", "string").addColumn("content", "string"),
967
+ ),
968
+ );
969
+
970
+ // Create a schema namespace map
971
+ const schemaNamespaceMap = new WeakMap<typeof schema1 | typeof schema2, string>();
972
+ schemaNamespaceMap.set(schema1, "namespace1");
973
+ schemaNamespaceMap.set(schema2, "namespace2");
974
+
975
+ // Mock executor that returns distinct results
976
+ const executor = {
977
+ executeRetrievalPhase: async () => {
978
+ return [
979
+ [{ id: "user1", name: "Alice", email: "alice@example.com" }],
980
+ [{ id: "user2", name: "Bob", email: "bob@example.com" }],
981
+ [{ id: "post1", title: "Post 1", content: "Content 1" }],
982
+ ];
983
+ },
984
+ executeMutationPhase: async () => ({
985
+ success: true,
986
+ createdInternalIds: [],
987
+ }),
988
+ };
989
+
990
+ // Create parent UOW
991
+ const parentUow = createUnitOfWork(
992
+ createMockCompiler(),
993
+ executor,
994
+ createMockDecoder(),
995
+ schemaNamespaceMap,
996
+ "test-uow",
997
+ );
998
+
999
+ // Add a find operation via a schema1 view (but don't keep a reference to this view)
1000
+ parentUow.forSchema(schema1).find("users", (b) => b.whereIndex("primary"));
1001
+
1002
+ // Create a view for schema1 and add another find operation
1003
+ const view1 = parentUow.forSchema(schema1).find("users", (b) => b.whereIndex("primary"));
1004
+
1005
+ // Create a view for schema2 and add a find operation
1006
+ const view2 = parentUow.forSchema(schema2).find("posts", (b) => b.whereIndex("primary"));
1007
+
1008
+ // Execute retrieval phase on parent
1009
+ const parentResults = await parentUow.executeRetrieve();
1010
+
1011
+ // Parent should have all 3 results
1012
+ expect(parentResults).toHaveLength(3);
1013
+
1014
+ // View1's retrievalPhase promise should only contain results from operations added through view1
1015
+ // (which is index 1 in the parent's operations)
1016
+ const view1Results = await view1.retrievalPhase;
1017
+ expect(view1Results).toHaveLength(1);
1018
+ expect(view1Results[0]).toEqual([{ id: "user2", name: "Bob", email: "bob@example.com" }]);
1019
+
1020
+ // View2's retrievalPhase promise should only contain results from operations added through view2
1021
+ // (which is index 2 in the parent's operations)
1022
+ const view2Results = await view2.retrievalPhase;
1023
+ expect(view2Results).toHaveLength(1);
1024
+ expect(view2Results[0]).toEqual([{ id: "post1", title: "Post 1", content: "Content 1" }]);
1025
+ });
1026
+
1027
+ it("should isolate operations when getUnitOfWork is called multiple times with same schema", async () => {
1028
+ const testSchema = schema((s) =>
1029
+ s.addTable("users", (t) =>
1030
+ t.addColumn("id", idColumn()).addColumn("name", "string").addColumn("email", "string"),
1031
+ ),
1032
+ );
1033
+
1034
+ const executor = {
1035
+ executeRetrievalPhase: async () => {
1036
+ return [
1037
+ [{ id: "user1", name: "Alice", email: "alice@example.com" }],
1038
+ [{ id: "user2", name: "Bob", email: "bob@example.com" }],
1039
+ ];
1040
+ },
1041
+ executeMutationPhase: async () => ({
1042
+ success: true,
1043
+ createdInternalIds: [],
1044
+ }),
1045
+ };
1046
+
1047
+ const parentUow = createUnitOfWork(
1048
+ createMockCompiler(),
1049
+ executor,
1050
+ createMockDecoder(),
1051
+ undefined,
1052
+ "test-uow",
1053
+ );
1054
+
1055
+ // Simulate what happens in db-fragment-definition-builder when getUnitOfWork(schema) is called twice
1056
+ const view1 = parentUow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary"));
1057
+
1058
+ const view2 = parentUow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary"));
1059
+
1060
+ // Execute retrieval
1061
+ await parentUow.executeRetrieve();
1062
+
1063
+ // Each view should only see its own operation's results
1064
+ const view1Results = await view1.retrievalPhase;
1065
+ expect(view1Results).toHaveLength(1);
1066
+ expect(view1Results[0]).toEqual([{ id: "user1", name: "Alice", email: "alice@example.com" }]);
1067
+
1068
+ const view2Results = await view2.retrievalPhase;
1069
+ expect(view2Results).toHaveLength(1);
1070
+ expect(view2Results[0]).toEqual([{ id: "user2", name: "Bob", email: "bob@example.com" }]);
1071
+ });
1072
+
1073
+ it("should show that getCreatedIds returns ALL created IDs regardless of which view created them", async () => {
1074
+ const schema1 = schema((s) =>
1075
+ s.addTable("users", (t) =>
1076
+ t.addColumn("id", idColumn()).addColumn("name", "string").addColumn("email", "string"),
1077
+ ),
1078
+ );
1079
+
1080
+ const schema2 = schema((s) =>
1081
+ s.addTable("posts", (t) =>
1082
+ t.addColumn("id", idColumn()).addColumn("title", "string").addColumn("content", "string"),
1083
+ ),
1084
+ );
1085
+
1086
+ const schemaNamespaceMap = new WeakMap<typeof schema1 | typeof schema2, string>();
1087
+ schemaNamespaceMap.set(schema1, "namespace1");
1088
+ schemaNamespaceMap.set(schema2, "namespace2");
1089
+
1090
+ const executor = {
1091
+ executeRetrievalPhase: async () => [],
1092
+ executeMutationPhase: async () => ({
1093
+ success: true,
1094
+ createdInternalIds: [1n, 2n],
1095
+ }),
1096
+ };
1097
+
1098
+ const parentUow = createUnitOfWork(
1099
+ createMockCompiler(),
1100
+ executor,
1101
+ createMockDecoder(),
1102
+ schemaNamespaceMap,
1103
+ "test-uow",
1104
+ );
1105
+
1106
+ // View1 creates one user
1107
+ const view1 = parentUow.forSchema(schema1);
1108
+ view1.create("users", { name: "Alice", email: "alice@example.com" });
1109
+
1110
+ // View2 creates one post
1111
+ const view2 = parentUow.forSchema(schema2);
1112
+ view2.create("posts", { title: "Post 1", content: "Content 1" });
1113
+
1114
+ // Execute mutations
1115
+ await parentUow.executeMutations();
1116
+
1117
+ // Both views see ALL created IDs (not filtered by view)
1118
+ const view1Ids = view1.getCreatedIds();
1119
+ const view2Ids = view2.getCreatedIds();
1120
+
1121
+ expect(view1Ids).toHaveLength(2); // Sees both IDs, not just the one it created
1122
+ expect(view2Ids).toHaveLength(2); // Sees both IDs, not just the one it created
1123
+
1124
+ // They're the same array
1125
+ expect(view1Ids).toEqual(view2Ids);
1126
+ });
1127
+
1128
+ it("should generate unique IDs when multiple views create items", async () => {
1129
+ const schema1 = schema((s) =>
1130
+ s.addTable("users", (t) =>
1131
+ t.addColumn("id", idColumn()).addColumn("name", "string").addColumn("email", "string"),
1132
+ ),
1133
+ );
1134
+
1135
+ const schema2 = schema((s) =>
1136
+ s.addTable("posts", (t) =>
1137
+ t.addColumn("id", idColumn()).addColumn("title", "string").addColumn("content", "string"),
1138
+ ),
1139
+ );
1140
+
1141
+ const schemaNamespaceMap = new WeakMap<typeof schema1 | typeof schema2, string>();
1142
+ schemaNamespaceMap.set(schema1, "namespace1");
1143
+ schemaNamespaceMap.set(schema2, "namespace2");
1144
+
1145
+ const executor = {
1146
+ executeRetrievalPhase: async () => [],
1147
+ executeMutationPhase: async () => ({
1148
+ success: true,
1149
+ createdInternalIds: [1n, 2n],
1150
+ }),
1151
+ };
1152
+
1153
+ const parentUow = createUnitOfWork(
1154
+ createMockCompiler(),
1155
+ executor,
1156
+ createMockDecoder(),
1157
+ schemaNamespaceMap,
1158
+ "test-uow",
1159
+ );
1160
+
1161
+ // View1 creates a user
1162
+ const view1 = parentUow.forSchema(schema1);
1163
+ const userId = view1.create("users", { name: "Alice", email: "alice@example.com" });
1164
+
1165
+ // View2 creates a post
1166
+ const view2 = parentUow.forSchema(schema2);
1167
+ const postId = view2.create("posts", { title: "Post 1", content: "Content 1" });
1168
+
1169
+ // IDs should be unique before execution
1170
+ expect(userId.externalId).not.toBe(postId.externalId);
1171
+ expect(userId.externalId).toBeTruthy();
1172
+ expect(postId.externalId).toBeTruthy();
1173
+ expect(userId.internalId).toBeUndefined();
1174
+ expect(postId.internalId).toBeUndefined();
1175
+
1176
+ // Execute mutations
1177
+ await parentUow.executeMutations();
1178
+
1179
+ // Get the created IDs after execution
1180
+ const createdIds = parentUow.getCreatedIds();
1181
+ expect(createdIds).toHaveLength(2);
1182
+
1183
+ // Both should have external IDs set (from create operation)
1184
+ expect(createdIds[0].externalId).toBe(userId.externalId);
1185
+ expect(createdIds[1].externalId).toBe(postId.externalId);
1186
+
1187
+ // Both should now have internal IDs (from database)
1188
+ expect(createdIds[0].internalId).toBe(1n);
1189
+ expect(createdIds[1].internalId).toBe(2n);
1190
+
1191
+ // IDs should still be unique
1192
+ expect(createdIds[0].externalId).not.toBe(createdIds[1].externalId);
1193
+ });
1194
+ });
1195
+
1196
+ describe("Error Handling", () => {
1197
+ const testSchema = schema((s) =>
1198
+ s.addTable("users", (t) =>
1199
+ t.addColumn("id", idColumn()).addColumn("name", "string").addColumn("email", "string"),
1200
+ ),
1201
+ );
1202
+
1203
+ it("should throw error from executeRetrieve() when retrieval fails", async () => {
1204
+ const executor = {
1205
+ executeRetrievalPhase: async () => {
1206
+ throw new Error("Database connection failed");
1207
+ },
1208
+ executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
1209
+ };
1210
+
1211
+ const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
1212
+ uow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary"));
1213
+
1214
+ await expect(uow.executeRetrieve()).rejects.toThrow("Database connection failed");
1215
+ });
1216
+
1217
+ it("should throw error from executeMutations() when mutation fails", async () => {
1218
+ const executor = {
1219
+ executeRetrievalPhase: async () => [],
1220
+ executeMutationPhase: async () => {
1221
+ throw new Error("Write conflict");
1222
+ },
1223
+ };
1224
+
1225
+ const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
1226
+ uow.forSchema(testSchema).create("users", { name: "Alice", email: "alice@example.com" });
1227
+
1228
+ await expect(uow.executeMutations()).rejects.toThrow("Write conflict");
1229
+ });
1230
+
1231
+ it("should reject retrievalPhase promise when executeRetrieve() fails", async () => {
1232
+ const executor = {
1233
+ executeRetrievalPhase: async () => {
1234
+ throw new Error("Query timeout");
1235
+ },
1236
+ executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
1237
+ };
1238
+
1239
+ const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
1240
+ uow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary"));
1241
+
1242
+ // Start executing (this will fail)
1243
+ const executePromise = uow.executeRetrieve().catch((e) => {
1244
+ return (e as Error).message;
1245
+ });
1246
+
1247
+ uow.retrievalPhase
1248
+ .then(() => {
1249
+ throw new Error("Should not be called");
1250
+ })
1251
+ .catch(() => {
1252
+ throw new Error("Should not be called");
1253
+ });
1254
+
1255
+ // uow.retrievalPhase should not be settled yet
1256
+
1257
+ // Wait for execute to complete
1258
+ expect(await executePromise).toBe("Query timeout");
1259
+ });
1260
+
1261
+ it("should reject mutationPhase promise when executeMutations() fails", async () => {
1262
+ const executor = {
1263
+ executeRetrievalPhase: async () => [],
1264
+ executeMutationPhase: async () => {
1265
+ throw new Error("Constraint violation");
1266
+ },
1267
+ };
1268
+
1269
+ const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
1270
+ uow.forSchema(testSchema).create("users", { name: "Alice", email: "alice@example.com" });
1271
+
1272
+ // Start executing (this will fail)
1273
+ const executePromise = uow.executeMutations().catch((e) => {
1274
+ return (e as Error).message;
1275
+ });
1276
+
1277
+ uow.mutationPhase
1278
+ .then(() => {
1279
+ throw new Error("Should not be called");
1280
+ })
1281
+ .catch(() => {
1282
+ throw new Error("Should not be called");
1283
+ });
1284
+ // Wait for execute to complete
1285
+ expect(await executePromise).toBe("Constraint violation");
1286
+ });
1287
+
1288
+ it("should not cause unhandled promise rejection when executeRetrieve() fails and coordination promise is not awaited", async () => {
1289
+ const executor = {
1290
+ executeRetrievalPhase: async () => {
1291
+ throw new Error("Table does not exist");
1292
+ },
1293
+ executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
1294
+ };
1295
+
1296
+ const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
1297
+ uow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary"));
1298
+
1299
+ // Access the retrievalPhase promise but don't await it
1300
+ // This simulates the internal coordination promise that might not be awaited
1301
+ const _retrievalPhase = uow.retrievalPhase;
1302
+
1303
+ const errorResolver = Promise.withResolvers<void>();
1304
+
1305
+ // Execute and catch the error from executeRetrieve()
1306
+ try {
1307
+ await uow.executeRetrieve();
1308
+ } catch (error) {
1309
+ // Error is caught, this is expected
1310
+ expect(error).toBeInstanceOf(Error);
1311
+ expect((error as Error).message).toBe("Table does not exist");
1312
+ errorResolver.resolve();
1313
+ }
1314
+
1315
+ await errorResolver.promise;
1316
+ });
1317
+
1318
+ it("should not cause unhandled promise rejection when executeMutations() fails and coordination promise is not awaited", async () => {
1319
+ const executor = {
1320
+ executeRetrievalPhase: async () => [],
1321
+ executeMutationPhase: async () => {
1322
+ throw new Error("Deadlock detected");
1323
+ },
1324
+ };
1325
+
1326
+ const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
1327
+ uow.forSchema(testSchema).create("users", { name: "Alice", email: "alice@example.com" });
1328
+
1329
+ // Access the mutationPhase promise but don't await it
1330
+ // This simulates the internal coordination promise that might not be awaited
1331
+ const _mutationPhase = uow.mutationPhase;
1332
+
1333
+ const errorResolver = Promise.withResolvers<void>();
1334
+
1335
+ // Execute and catch the error from executeMutations()
1336
+ try {
1337
+ await uow.executeMutations();
1338
+ } catch (error) {
1339
+ // Error is caught, this is expected
1340
+ expect(error).toBeInstanceOf(Error);
1341
+ expect((error as Error).message).toBe("Deadlock detected");
1342
+ errorResolver.resolve();
1343
+ }
1344
+
1345
+ await errorResolver.promise;
1346
+ });
1347
+
1348
+ it("should handle error in executeRetrieve() when coordination promise is never accessed", async () => {
1349
+ const executor = {
1350
+ executeRetrievalPhase: async () => {
1351
+ throw new Error("Connection lost");
1352
+ },
1353
+ executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
1354
+ };
1355
+
1356
+ const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
1357
+ uow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary"));
1358
+
1359
+ // Don't access retrievalPhase at all - this is the most common case
1360
+ // The internal coordination promise should not cause unhandled rejection
1361
+ const errorResolver = Promise.withResolvers<void>();
1362
+
1363
+ // Execute and catch the error
1364
+ try {
1365
+ await uow.executeRetrieve();
1366
+ } catch (error) {
1367
+ expect(error).toBeInstanceOf(Error);
1368
+ expect((error as Error).message).toBe("Connection lost");
1369
+ errorResolver.resolve();
1370
+ }
1371
+
1372
+ await errorResolver.promise;
1373
+ });
1374
+
1375
+ it("should handle error in executeMutations() when coordination promise is never accessed", async () => {
1376
+ const executor = {
1377
+ executeRetrievalPhase: async () => [],
1378
+ executeMutationPhase: async () => {
1379
+ throw new Error("Transaction aborted");
1380
+ },
1381
+ };
1382
+
1383
+ const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
1384
+ uow.forSchema(testSchema).create("users", { name: "Alice", email: "alice@example.com" });
1385
+
1386
+ // Don't access mutationPhase at all - this is the most common case
1387
+ // The internal coordination promise should not cause unhandled rejection
1388
+ const errorResolver = Promise.withResolvers<void>();
1389
+ // Execute and catch the error
1390
+ try {
1391
+ await uow.executeMutations();
1392
+ } catch (error) {
1393
+ expect(error).toBeInstanceOf(Error);
1394
+ expect((error as Error).message).toBe("Transaction aborted");
1395
+ errorResolver.resolve();
1396
+ }
1397
+
1398
+ await errorResolver.promise;
1399
+ });
1400
+
1401
+ it("should handle reset() after retrieval error", async () => {
1402
+ const executor = {
1403
+ executeRetrievalPhase: async () => {
1404
+ throw new Error("First attempt failed");
1405
+ },
1406
+ executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
1407
+ };
1408
+
1409
+ const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
1410
+ uow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary"));
1411
+
1412
+ // First attempt fails
1413
+ const errorResolver = Promise.withResolvers<void>();
1414
+ try {
1415
+ await uow.executeRetrieve();
1416
+ } catch (error) {
1417
+ expect((error as Error).message).toBe("First attempt failed");
1418
+ errorResolver.resolve();
1419
+ }
1420
+
1421
+ // Reset the UOW
1422
+ uow.reset();
1423
+
1424
+ // The UOW should be in a clean state
1425
+ expect(uow.state).toBe("building-retrieval");
1426
+ expect(uow.getRetrievalOperations()).toHaveLength(0);
1427
+
1428
+ await errorResolver.promise;
1429
+ });
1430
+
1431
+ it("should handle reset() after mutation error", async () => {
1432
+ const executor = {
1433
+ executeRetrievalPhase: async () => [],
1434
+ executeMutationPhase: async () => {
1435
+ throw new Error("First mutation failed");
1436
+ },
1437
+ };
1438
+
1439
+ const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
1440
+ uow.forSchema(testSchema).create("users", { name: "Alice", email: "alice@example.com" });
1441
+
1442
+ // First attempt fails
1443
+ const errorResolver = Promise.withResolvers<void>();
1444
+ try {
1445
+ await uow.executeMutations();
1446
+ } catch (error) {
1447
+ expect((error as Error).message).toBe("First mutation failed");
1448
+ errorResolver.resolve();
1449
+ }
1450
+
1451
+ // Reset the UOW
1452
+ uow.reset();
1453
+
1454
+ // The UOW should be in a clean state
1455
+ expect(uow.state).toBe("building-retrieval");
1456
+ expect(uow.getMutationOperations()).toHaveLength(0);
1457
+
1458
+ await errorResolver.promise;
1459
+ });
1460
+
1461
+ it("should support standalone check() operation", () => {
1462
+ const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
1463
+ const typedUow = uow.forSchema(testSchema);
1464
+
1465
+ // Create a FragnoId for testing
1466
+ const userId = FragnoId.fromExternal("user-123", 5);
1467
+
1468
+ typedUow.check("users", userId);
1469
+
1470
+ const mutationOps = uow.getMutationOperations();
1471
+ expect(mutationOps).toHaveLength(1);
1472
+
1473
+ const checkOp = mutationOps[0];
1474
+ assert(checkOp);
1475
+ assert(checkOp.type === "check");
1476
+ expect(checkOp.table).toBe("users");
1477
+ expect(checkOp.id).toBe(userId);
1478
+ });
1479
+ });
1480
+
1481
+ describe("findFirst convenience method", () => {
1482
+ const testSchema = schema((s) =>
1483
+ s
1484
+ .addTable("users", (t) =>
1485
+ t
1486
+ .addColumn("id", idColumn())
1487
+ .addColumn("name", "string")
1488
+ .addColumn("email", "string")
1489
+ .createIndex("idx_email", ["email"])
1490
+ .createIndex("idx_name", ["name"]),
1491
+ )
1492
+ .addTable("posts", (t) =>
1493
+ t
1494
+ .addColumn("id", idColumn())
1495
+ .addColumn("userId", "string")
1496
+ .addColumn("title", "string")
1497
+ .createIndex("idx_user", ["userId"]),
1498
+ ),
1499
+ );
1500
+
1501
+ it("should return a single result instead of an array", async () => {
1502
+ const executor = {
1503
+ executeRetrievalPhase: async () => {
1504
+ return [[{ id: "mock-id", name: "Mock User", email: "mock@example.com" }]];
1505
+ },
1506
+ executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
1507
+ };
1508
+
1509
+ const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
1510
+
1511
+ // Use findFirst instead of find
1512
+ const typedUow = uow.forSchema(testSchema).findFirst("users", (b) => b.whereIndex("primary"));
1513
+
1514
+ // Execute retrieval
1515
+ await uow.executeRetrieve();
1516
+ const results = await typedUow.retrievalPhase;
1517
+
1518
+ // Result should be a single object, not an array
1519
+ const [user] = results;
1520
+ expect(user).toEqual({ id: "mock-id", name: "Mock User", email: "mock@example.com" });
1521
+ expect(Array.isArray(user)).toBe(false);
1522
+ });
1523
+
1524
+ it("should return null when no results are found", async () => {
1525
+ // Create executor that returns empty results
1526
+ const emptyExecutor = {
1527
+ executeRetrievalPhase: async () => {
1528
+ return [[]]; // Empty array for no results
1529
+ },
1530
+ executeMutationPhase: async () => {
1531
+ return { success: true, createdInternalIds: [] };
1532
+ },
1533
+ };
1534
+
1535
+ const uow = createUnitOfWork(createMockCompiler(), emptyExecutor, createMockDecoder());
1536
+
1537
+ const typedUow = uow.forSchema(testSchema).findFirst("users", (b) => b.whereIndex("primary"));
1538
+
1539
+ await uow.executeRetrieve();
1540
+ const results = await typedUow.retrievalPhase;
1541
+ const [user] = results;
1542
+
1543
+ // Should be null when no results
1544
+ expect(user).toBeNull();
1545
+ });
1546
+
1547
+ it("should automatically set pageSize to 1", () => {
1548
+ const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
1549
+
1550
+ uow.forSchema(testSchema).findFirst("users", (b) => b.whereIndex("primary"));
1551
+
1552
+ // Check that pageSize was set to 1 in the operation
1553
+ const ops = uow.getRetrievalOperations();
1554
+ expect(ops).toHaveLength(1);
1555
+ expect(ops[0]?.type).toBe("find");
1556
+ if (ops[0]?.type === "find") {
1557
+ expect(ops[0].options.pageSize).toBe(1);
1558
+ }
1559
+ });
1560
+
1561
+ it("should work with custom select clause", async () => {
1562
+ const executor = {
1563
+ executeRetrievalPhase: async () => {
1564
+ return [[{ id: "mock-id", name: "Mock User" }]];
1565
+ },
1566
+ executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
1567
+ };
1568
+
1569
+ const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
1570
+
1571
+ const typedUow = uow
1572
+ .forSchema(testSchema)
1573
+ .findFirst("users", (b) => b.whereIndex("primary").select(["id", "name"] as const));
1574
+
1575
+ await uow.executeRetrieve();
1576
+ const results = await typedUow.retrievalPhase;
1577
+ const [user] = results;
1578
+
1579
+ expect(user).toBeDefined();
1580
+ expect(user).not.toBeNull();
1581
+ expect(user).toEqual({ id: "mock-id", name: "Mock User" });
1582
+ });
1583
+
1584
+ it("should work with where conditions", async () => {
1585
+ const executor = {
1586
+ executeRetrievalPhase: async () => {
1587
+ return [[{ id: "mock-id", name: "Mock User", email: "test@example.com" }]];
1588
+ },
1589
+ executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
1590
+ };
1591
+
1592
+ const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
1593
+
1594
+ const typedUow = uow
1595
+ .forSchema(testSchema)
1596
+ .findFirst("users", (b) =>
1597
+ b.whereIndex("idx_email", (eb) => eb("email", "=", "test@example.com")),
1598
+ );
1599
+
1600
+ await uow.executeRetrieve();
1601
+ const results = await typedUow.retrievalPhase;
1602
+ const [user] = results;
1603
+
1604
+ expect(user).toEqual({ id: "mock-id", name: "Mock User", email: "test@example.com" });
1605
+ });
1606
+
1607
+ it("should handle multiple findFirst operations in the same UOW", async () => {
1608
+ const executor = {
1609
+ executeRetrievalPhase: async () => {
1610
+ return [
1611
+ [{ id: "user-1", name: "User 1", email: "user1@example.com" }],
1612
+ [{ id: "post-1", userId: "user-1", title: "Post 1" }],
1613
+ ];
1614
+ },
1615
+ executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
1616
+ };
1617
+
1618
+ const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
1619
+
1620
+ const typedUow = uow
1621
+ .forSchema(testSchema)
1622
+ .findFirst("users", (b) => b.whereIndex("primary"))
1623
+ .findFirst("posts", (b) => b.whereIndex("primary"));
1624
+
1625
+ await uow.executeRetrieve();
1626
+ const results = await typedUow.retrievalPhase;
1627
+ const [user, post] = results;
1628
+
1629
+ // Both should be single objects, not arrays
1630
+ expect(user).toEqual({ id: "user-1", name: "User 1", email: "user1@example.com" });
1631
+ expect(post).toEqual({ id: "post-1", userId: "user-1", title: "Post 1" });
1632
+ expect(Array.isArray(user)).toBe(false);
1633
+ expect(Array.isArray(post)).toBe(false);
1634
+ });
1635
+
1636
+ it("should handle mix of find and findFirst operations", async () => {
1637
+ const executor = {
1638
+ executeRetrievalPhase: async () => {
1639
+ return [
1640
+ [{ id: "user-1", name: "User 1", email: "user1@example.com" }],
1641
+ [
1642
+ { id: "post-1", userId: "user-1", title: "Post 1" },
1643
+ { id: "post-2", userId: "user-1", title: "Post 2" },
1644
+ ],
1645
+ ];
1646
+ },
1647
+ executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
1648
+ };
1649
+
1650
+ const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
1651
+
1652
+ const typedUow = uow
1653
+ .forSchema(testSchema)
1654
+ .findFirst("users", (b) => b.whereIndex("primary")) // Single result
1655
+ .find("posts", (b) => b.whereIndex("primary")); // Array of results
1656
+
1657
+ await uow.executeRetrieve();
1658
+ const results = await typedUow.retrievalPhase;
1659
+ const [user, posts] = results;
1660
+
1661
+ // User should be a single object
1662
+ expect(user).toEqual({ id: "user-1", name: "User 1", email: "user1@example.com" });
1663
+ expect(Array.isArray(user)).toBe(false);
1664
+
1665
+ // Posts should be an array
1666
+ expect(Array.isArray(posts)).toBe(true);
1667
+ expect(posts).toHaveLength(2);
1668
+ });
1669
+
1670
+ it("should work with orderByIndex", async () => {
1671
+ const executor = {
1672
+ executeRetrievalPhase: async () => {
1673
+ return [[{ id: "user-1", name: "Alice", email: "alice@example.com" }]];
1674
+ },
1675
+ executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
1676
+ };
1677
+
1678
+ const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
1679
+
1680
+ const typedUow = uow
1681
+ .forSchema(testSchema)
1682
+ .findFirst("users", (b) => b.whereIndex("idx_name").orderByIndex("idx_name", "asc"));
1683
+
1684
+ await uow.executeRetrieve();
1685
+ const results = await typedUow.retrievalPhase;
1686
+ const [user] = results;
1687
+
1688
+ expect(user).toEqual({ id: "user-1", name: "Alice", email: "alice@example.com" });
1689
+ expect(Array.isArray(user)).toBe(false);
1690
+ });
1691
+
1692
+ it("should work without explicit builder function", async () => {
1693
+ const executor = {
1694
+ executeRetrievalPhase: async () => {
1695
+ return [[{ id: "user-1", name: "User 1", email: "user1@example.com" }]];
1696
+ },
1697
+ executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
1698
+ };
1699
+
1700
+ const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
1701
+
1702
+ // findFirst without builder function should use primary index by default
1703
+ const typedUow = uow.forSchema(testSchema).findFirst("users");
1704
+
1705
+ await uow.executeRetrieve();
1706
+ const results = await typedUow.retrievalPhase;
1707
+ const [user] = results;
1708
+
1709
+ expect(user).toEqual({ id: "user-1", name: "User 1", email: "user1@example.com" });
1710
+ expect(Array.isArray(user)).toBe(false);
1711
+
1712
+ // Verify the operation used the primary index
1713
+ const ops = uow.getRetrievalOperations();
1714
+ expect(ops[0]?.indexName).toBe("_primary");
1715
+ });
1716
+ });