@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,899 @@
1
+ import SQLite from "better-sqlite3";
2
+ import { beforeAll, describe, expect, expectTypeOf, it, assert } from "vitest";
3
+ import { column, idColumn, referenceColumn, schema, type FragnoId } from "../../../schema/create";
4
+ import {
5
+ Cursor,
6
+ executeUnitOfWork,
7
+ ExponentialBackoffRetryPolicy,
8
+ type DatabaseAdapter,
9
+ } from "../../../mod";
10
+ import { SqliteDialect } from "kysely";
11
+ import { SqlDriverAdapter } from "../../../sql-driver/sql-driver-adapter";
12
+ import { BetterSQLite3DriverConfig } from "../driver-config";
13
+ import { GenericSQLAdapter } from "../generic-sql-adapter";
14
+ import { internalSchema } from "../../../fragments/internal-fragment";
15
+
16
+ describe("GenericSQLAdapter with DrizzleAdapter better-sqlite3", () => {
17
+ const testSchema = schema((s) => {
18
+ return s
19
+ .addTable("users", (t) => {
20
+ return t
21
+ .addColumn("id", idColumn())
22
+ .addColumn("name", column("string"))
23
+ .addColumn("age", column("integer").nullable())
24
+ .createIndex("name_idx", ["name"]);
25
+ })
26
+ .addTable("emails", (t) => {
27
+ return t
28
+ .addColumn("id", idColumn())
29
+ .addColumn("user_id", referenceColumn())
30
+ .addColumn("email", column("string"))
31
+ .addColumn("is_primary", column("bool").defaultTo(false))
32
+ .createIndex("unique_email", ["email"], { unique: true })
33
+ .createIndex("user_emails", ["user_id"]);
34
+ })
35
+ .addTable("posts", (t) => {
36
+ return t
37
+ .addColumn("id", idColumn())
38
+ .addColumn("user_id", referenceColumn())
39
+ .addColumn("title", column("string"))
40
+ .addColumn("content", column("string"))
41
+ .createIndex("posts_user_idx", ["user_id"]);
42
+ })
43
+ .addTable("comments", (t) => {
44
+ return t
45
+ .addColumn("id", idColumn())
46
+ .addColumn("post_id", referenceColumn())
47
+ .addColumn("user_id", referenceColumn())
48
+ .addColumn("text", column("string"))
49
+ .createIndex("comments_post_idx", ["post_id"])
50
+ .createIndex("comments_user_idx", ["user_id"]);
51
+ })
52
+ .addReference("user", {
53
+ type: "one",
54
+ from: { table: "emails", column: "user_id" },
55
+ to: { table: "users", column: "id" },
56
+ })
57
+ .addReference("author", {
58
+ type: "one",
59
+ from: { table: "posts", column: "user_id" },
60
+ to: { table: "users", column: "id" },
61
+ })
62
+ .addReference("post", {
63
+ type: "one",
64
+ from: { table: "comments", column: "post_id" },
65
+ to: { table: "posts", column: "id" },
66
+ })
67
+ .addReference("commenter", {
68
+ type: "one",
69
+ from: { table: "comments", column: "user_id" },
70
+ to: { table: "users", column: "id" },
71
+ });
72
+ });
73
+
74
+ // Second schema for multi-schema testing
75
+ const schema2 = schema((s) => {
76
+ return s
77
+ .addTable("products", (t) => {
78
+ return t
79
+ .addColumn("id", idColumn())
80
+ .addColumn("name", column("string"))
81
+ .addColumn("price", column("integer"))
82
+ .createIndex("name_idx", ["name"]);
83
+ })
84
+ .addTable("orders", (t) => {
85
+ return t
86
+ .addColumn("id", idColumn())
87
+ .addColumn("product_id", referenceColumn())
88
+ .addColumn("quantity", column("integer"))
89
+ .createIndex("product_orders_idx", ["product_id"]);
90
+ })
91
+ .addReference("product", {
92
+ type: "one",
93
+ from: { table: "orders", column: "product_id" },
94
+ to: { table: "products", column: "id" },
95
+ });
96
+ });
97
+
98
+ // oxlint-disable-next-line no-explicit-any
99
+ let adapter: DatabaseAdapter<any>;
100
+
101
+ beforeAll(async () => {
102
+ const dialect = new SqliteDialect({
103
+ database: new SQLite(":memory:"),
104
+ });
105
+ const sqlAdapter = new SqlDriverAdapter(dialect);
106
+ const driverConfig = new BetterSQLite3DriverConfig();
107
+ const genericAdapter = new GenericSQLAdapter({ dialect, driverConfig });
108
+
109
+ {
110
+ const migrations = genericAdapter.prepareMigrations(internalSchema, "");
111
+ await migrations.executeWithDriver(sqlAdapter, 0);
112
+ }
113
+
114
+ {
115
+ const migrations = genericAdapter.prepareMigrations(testSchema, "namespace");
116
+ await migrations.executeWithDriver(sqlAdapter, 0);
117
+ }
118
+
119
+ {
120
+ const migrations = genericAdapter.prepareMigrations(schema2, "namespace2");
121
+ await migrations.executeWithDriver(sqlAdapter, 0);
122
+ }
123
+
124
+ adapter = genericAdapter;
125
+ return async () => {
126
+ await sqlAdapter.destroy();
127
+ };
128
+ }, 12000);
129
+
130
+ it("should execute Unit of Work with version checking", async () => {
131
+ // Pass namespace to ensure mapper translates logical table names to physical (prefixed) names
132
+ const queryEngine = adapter.createQueryEngine(testSchema, "namespace");
133
+
134
+ // Create two users at once using UOW
135
+ const createUow = queryEngine.createUnitOfWork("create-users");
136
+ createUow.create("users", {
137
+ name: "Alice",
138
+ age: 25,
139
+ });
140
+ createUow.create("users", {
141
+ name: "Bob",
142
+ age: 30,
143
+ });
144
+
145
+ expectTypeOf<keyof typeof testSchema.tables>().toEqualTypeOf<
146
+ Parameters<typeof createUow.find>[0]
147
+ >();
148
+ expectTypeOf<keyof typeof testSchema.tables>().toEqualTypeOf<
149
+ "users" | "emails" | "posts" | "comments"
150
+ >();
151
+
152
+ const { success: createSuccess } = await createUow.executeMutations();
153
+ expect(createSuccess).toBe(true);
154
+
155
+ const createdIds = createUow.getCreatedIds();
156
+ expect(createdIds).toHaveLength(2);
157
+
158
+ // Verify both users were created by fetching them
159
+ const [allUsers] = await queryEngine
160
+ .createUnitOfWork("get-all-users")
161
+ .find("users")
162
+ .executeRetrieve();
163
+
164
+ expect(allUsers).toHaveLength(2);
165
+
166
+ // Verify Alice (first created user)
167
+ expect(allUsers[0]).toMatchObject({
168
+ name: "Alice",
169
+ age: 25,
170
+ id: expect.objectContaining({
171
+ externalId: createdIds[0].externalId,
172
+ version: 0,
173
+ }),
174
+ });
175
+
176
+ // Verify Bob (second created user)
177
+ expect(allUsers[1]).toMatchObject({
178
+ name: "Bob",
179
+ age: 30,
180
+ id: expect.objectContaining({
181
+ externalId: createdIds[1].externalId,
182
+ version: 0,
183
+ }),
184
+ });
185
+
186
+ // Use Alice (first user) for the rest of the test
187
+ const initialUserId = createdIds[0];
188
+
189
+ // Build a UOW to update Alice with optimistic locking
190
+ const uow = queryEngine
191
+ .createUnitOfWork("update-user-age")
192
+ // Retrieval phase: find Alice
193
+ .find("users", (b) => b.whereIndex("primary", (eb) => eb("id", "=", initialUserId)));
194
+
195
+ // Execute retrieval and transition to mutation phase
196
+ const [users] = await uow.executeRetrieve();
197
+
198
+ // Mutation phase: update with version check
199
+ uow.update("users", initialUserId, (b) => b.set({ age: 26 }).check());
200
+
201
+ // Execute mutations
202
+ const { success } = await uow.executeMutations();
203
+
204
+ // Should succeed
205
+ expect(success).toBe(true);
206
+ expect(users).toHaveLength(1);
207
+ expect(users[0].name).toBe("Alice");
208
+
209
+ // Verify Alice was updated
210
+ const [[updatedUser]] = await queryEngine
211
+ .createUnitOfWork("get-updated-user")
212
+ .find("users", (b) => b.whereIndex("primary", (eb) => eb("id", "=", initialUserId)))
213
+ .executeRetrieve();
214
+
215
+ expect(updatedUser).toMatchObject({
216
+ id: expect.objectContaining({
217
+ externalId: initialUserId.externalId,
218
+ version: 1, // Version incremented
219
+ }),
220
+ name: "Alice",
221
+ age: 26,
222
+ });
223
+
224
+ // Try to update Alice again with stale version (should fail)
225
+ const uow2 = queryEngine.createUnitOfWork("update-user-stale");
226
+
227
+ // Use the old version (0) which is now stale
228
+ uow2.update("users", initialUserId, (b) => b.set({ age: 27 }).check());
229
+
230
+ const { success: success2 } = await uow2.executeMutations();
231
+
232
+ // Should fail due to version conflict
233
+ expect(success2).toBe(false);
234
+
235
+ // Verify Alice was NOT updated
236
+ const [[unchangedUser]] = await queryEngine
237
+ .createUnitOfWork("verify-unchanged")
238
+ .find("users", (b) => b.whereIndex("primary", (eb) => eb("id", "=", initialUserId)))
239
+ .executeRetrieve();
240
+
241
+ expect(unchangedUser).toMatchObject({
242
+ id: expect.objectContaining({
243
+ version: 1, // Still version 1
244
+ }),
245
+ age: 26, // Still 26, not 27
246
+ });
247
+ });
248
+
249
+ it("should support count operations", async () => {
250
+ const queryEngine = adapter.createQueryEngine(testSchema, "namespace");
251
+
252
+ const createUow = queryEngine.createUnitOfWork("create-users");
253
+ createUow.create("users", { name: "User1", age: 20 });
254
+ createUow.create("users", { name: "User2", age: 30 });
255
+ createUow.create("users", { name: "User3", age: 40 });
256
+ await createUow.executeMutations();
257
+
258
+ // Count all users
259
+ const [totalCount] = await queryEngine
260
+ .createUnitOfWork("count-all")
261
+ .find("users", (b) => b.whereIndex("primary").selectCount())
262
+ .executeRetrieve();
263
+
264
+ // Tests are not isolated, so we can't use expect(totalCount).toBe(3)
265
+ expect(totalCount).toBeGreaterThanOrEqual(5); // At least Alice, Bob, and 3 new users
266
+ expect(typeof totalCount).toBe("number");
267
+ });
268
+
269
+ it("should support cursor-based pagination", async () => {
270
+ const queryEngine = adapter.createQueryEngine(testSchema, "namespace");
271
+
272
+ const createUow = queryEngine.createUnitOfWork("create-users");
273
+ createUow.create("users", { name: "Page User A", age: 20 });
274
+ createUow.create("users", { name: "Page User B", age: 30 });
275
+ createUow.create("users", { name: "Page User C", age: 40 });
276
+ createUow.create("users", { name: "Page User D", age: 50 });
277
+ createUow.create("users", { name: "Page User E", age: 60 });
278
+
279
+ await createUow.executeMutations();
280
+
281
+ // Fetch first page ordered by name
282
+ const [firstPage] = await queryEngine
283
+ .createUnitOfWork("first-page")
284
+ .find("users", (b) => b.whereIndex("name_idx").orderByIndex("name_idx", "asc").pageSize(2))
285
+ .executeRetrieve();
286
+
287
+ // Verify first page contains the first 2 users alphabetically
288
+ expect(firstPage).toHaveLength(2);
289
+ expect(firstPage.map((u) => u.name)).toEqual(["Alice", "Bob"]);
290
+
291
+ // Create cursor from last item of first page
292
+ const lastItem = firstPage[firstPage.length - 1]!;
293
+ const cursor = new Cursor({
294
+ indexName: "name_idx",
295
+ orderDirection: "asc",
296
+ pageSize: 2,
297
+ indexValues: { name: lastItem.name },
298
+ }).encode();
299
+
300
+ // Fetch next page using cursor
301
+ const [secondPage] = await queryEngine
302
+ .createUnitOfWork("second-page")
303
+ .find("users", (b) =>
304
+ b.whereIndex("name_idx").orderByIndex("name_idx", "asc").after(cursor).pageSize(2),
305
+ )
306
+ .executeRetrieve();
307
+
308
+ // Verify page 2 continues alphabetically
309
+ expect(secondPage).toHaveLength(2);
310
+ expect(secondPage.map((u) => u.name)).toEqual(["Page User A", "Page User B"]);
311
+
312
+ // Ensure no overlap between pages
313
+ const firstPageNames = new Set(firstPage.map((u) => u.name));
314
+ for (const user of secondPage) {
315
+ expect(firstPageNames.has(user.name)).toBe(false);
316
+ }
317
+ });
318
+
319
+ it("should support joins", async () => {
320
+ const queryEngine = adapter.createQueryEngine(testSchema, "namespace");
321
+
322
+ const createUow = queryEngine.createUnitOfWork("create-users");
323
+ createUow.create("users", { name: "Email User", age: 20 });
324
+
325
+ const { success } = await createUow.executeMutations();
326
+ expect(success).toBe(true);
327
+
328
+ // Fetch the created user to get the proper ID
329
+ const [usersResult] = await queryEngine
330
+ .createUnitOfWork("get-created-user")
331
+ .find("users", (b) => b.whereIndex("name_idx", (eb) => eb("name", "=", "Email User")))
332
+ .executeRetrieve();
333
+
334
+ expect(usersResult).toHaveLength(1);
335
+ const createdUser = usersResult[0];
336
+ expect(createdUser).toBeDefined();
337
+ expect(createdUser.name).toBe("Email User");
338
+
339
+ // Create an email for testing joins
340
+ const createEmailUow = queryEngine.createUnitOfWork("create-test-email");
341
+ createEmailUow.create("emails", {
342
+ user_id: createdUser.id,
343
+ email: "test@example.com",
344
+ is_primary: true,
345
+ });
346
+ await createEmailUow.executeMutations();
347
+
348
+ // Test join query
349
+ const uow = queryEngine
350
+ .createUnitOfWork("test-joins")
351
+ .find("emails", (b) =>
352
+ b
353
+ .whereIndex("user_emails", (eb) => eb("user_id", "=", createdUser.id))
354
+ .join((jb) => jb.user((builder) => builder.select(["name", "id", "age"]))),
355
+ );
356
+
357
+ const [[email]] = await uow.executeRetrieve();
358
+
359
+ expect(email).toMatchObject({
360
+ id: expect.objectContaining({
361
+ externalId: expect.stringMatching(/^[a-z0-9]{20,}$/),
362
+ internalId: expect.any(BigInt),
363
+ }),
364
+ user_id: expect.objectContaining({
365
+ internalId: expect.any(BigInt),
366
+ }),
367
+ email: "test@example.com",
368
+ is_primary: true,
369
+ user: {
370
+ id: expect.objectContaining({
371
+ externalId: expect.stringMatching(/^[a-z0-9]{20,}$/),
372
+ internalId: expect.any(BigInt),
373
+ }),
374
+ name: "Email User",
375
+ age: 20,
376
+ },
377
+ });
378
+ });
379
+
380
+ it("should support complex nested joins (comments -> post -> author)", async () => {
381
+ const queryEngine = adapter.createQueryEngine(testSchema, "namespace");
382
+
383
+ // Create a user (author)
384
+ const createAuthorUow = queryEngine.createUnitOfWork("create-author");
385
+ createAuthorUow.create("users", { name: "Blog Author", age: 30 });
386
+ await createAuthorUow.executeMutations();
387
+
388
+ // Fetch the created author to get the proper ID
389
+ const [[author]] = await queryEngine
390
+ .createUnitOfWork("get-author")
391
+ .find("users", (b) => b.whereIndex("name_idx", (eb) => eb("name", "=", "Blog Author")))
392
+ .executeRetrieve();
393
+
394
+ // Create a post by the author
395
+ const createPostUow = queryEngine.createUnitOfWork("create-post");
396
+ createPostUow.create("posts", {
397
+ user_id: author.id,
398
+ title: "My First Post",
399
+ content: "This is the content of my first post",
400
+ });
401
+ await createPostUow.executeMutations();
402
+
403
+ // Fetch the created post to get the proper ID
404
+ const [[post]] = await queryEngine
405
+ .createUnitOfWork("get-post")
406
+ .find("posts", (b) => b.whereIndex("posts_user_idx", (eb) => eb("user_id", "=", author.id)))
407
+ .executeRetrieve();
408
+
409
+ // Create a commenter
410
+ const createCommenterUow = queryEngine.createUnitOfWork("create-commenter");
411
+ createCommenterUow.create("users", { name: "Commenter User", age: 25 });
412
+ await createCommenterUow.executeMutations();
413
+
414
+ // Fetch the created commenter to get the proper ID
415
+ const [[commenter]] = await queryEngine
416
+ .createUnitOfWork("get-commenter")
417
+ .find("users", (b) => b.whereIndex("name_idx", (eb) => eb("name", "=", "Commenter User")))
418
+ .executeRetrieve();
419
+
420
+ // Create a comment on the post
421
+ const createCommentUow = queryEngine.createUnitOfWork("create-comment");
422
+ createCommentUow.create("comments", {
423
+ post_id: post.id,
424
+ user_id: commenter.id,
425
+ text: "Great post!",
426
+ });
427
+ await createCommentUow.executeMutations();
428
+
429
+ // Now perform a complex nested join: comments -> post -> author, and comments -> commenter
430
+ const uow = queryEngine.createUnitOfWork("test-complex-joins").find("comments", (b) =>
431
+ b.whereIndex("primary").join((jb) =>
432
+ jb
433
+ .post((postBuilder) =>
434
+ postBuilder
435
+ .select(["id", "title", "content"])
436
+ .orderByIndex("primary", "desc")
437
+ .pageSize(1)
438
+ .join((jb2) =>
439
+ // Nested join to the post's author
440
+ jb2.author((authorBuilder) =>
441
+ authorBuilder.select(["id", "name", "age"]).orderByIndex("name_idx", "asc"),
442
+ ),
443
+ ),
444
+ )
445
+ .commenter((commenterBuilder) => commenterBuilder.select(["id", "name"])),
446
+ ),
447
+ );
448
+
449
+ const [[comment]] = await uow.executeRetrieve();
450
+
451
+ // Verify the result structure with nested joins
452
+ expect(comment).toMatchObject({
453
+ id: expect.objectContaining({
454
+ externalId: expect.stringMatching(/^[a-z0-9]{20,}$/),
455
+ internalId: expect.any(BigInt),
456
+ }),
457
+ text: "Great post!",
458
+ // Post join (first level)
459
+ post: {
460
+ id: expect.objectContaining({
461
+ externalId: post.id.externalId,
462
+ }),
463
+ title: "My First Post",
464
+ content: "This is the content of my first post",
465
+ // Nested author join (second level)
466
+ author: {
467
+ id: expect.objectContaining({
468
+ externalId: author.id.externalId,
469
+ }),
470
+ name: "Blog Author",
471
+ age: 30,
472
+ },
473
+ },
474
+ // Commenter join (first level)
475
+ commenter: {
476
+ id: expect.objectContaining({
477
+ externalId: commenter.id.externalId,
478
+ }),
479
+ name: "Commenter User",
480
+ },
481
+ });
482
+ });
483
+
484
+ it("should return created IDs from UOW create operations", async () => {
485
+ const queryEngine = adapter.createQueryEngine(testSchema, "namespace");
486
+
487
+ // Test 1: Create operations return IDs with both external and internal IDs
488
+ const uow1 = queryEngine.createUnitOfWork("create-multiple-users");
489
+
490
+ uow1.create("users", { name: "Test User 1", age: 30 });
491
+ uow1.create("users", { name: "Test User 2", age: 35 });
492
+ uow1.create("users", { name: "Test User 3", age: 40 });
493
+
494
+ const { success: success1 } = await uow1.executeMutations();
495
+ expect(success1).toBe(true);
496
+
497
+ const createdIds1 = uow1.getCreatedIds();
498
+ expect(createdIds1).toMatchObject([
499
+ expect.objectContaining({
500
+ externalId: expect.stringMatching(/^[a-z0-9]{20,}$/),
501
+ internalId: expect.any(BigInt),
502
+ }),
503
+ expect.objectContaining({
504
+ externalId: expect.stringMatching(/^[a-z0-9]{20,}$/),
505
+ internalId: expect.any(BigInt),
506
+ }),
507
+ expect.objectContaining({
508
+ externalId: expect.stringMatching(/^[a-z0-9]{20,}$/),
509
+ internalId: expect.any(BigInt),
510
+ }),
511
+ ]);
512
+
513
+ // All external IDs should be unique
514
+ const externalIds = createdIds1.map((id) => id.externalId);
515
+ expect(new Set(externalIds).size).toBe(3);
516
+
517
+ // Verify we can use these IDs to query the created users
518
+ const user1 = await queryEngine.findFirst("users", (b) =>
519
+ b.whereIndex("primary", (eb) => eb("id", "=", createdIds1[0].externalId)),
520
+ );
521
+
522
+ const user2 = await queryEngine.findFirst("users", (b) =>
523
+ b.whereIndex("primary", (eb) => eb("id", "=", createdIds1[1].externalId)),
524
+ );
525
+
526
+ const user3 = await queryEngine.findFirst("users", (b) =>
527
+ b.whereIndex("primary", (eb) => eb("id", "=", createdIds1[2].externalId)),
528
+ );
529
+
530
+ expect(user1).toMatchObject({
531
+ id: expect.objectContaining({
532
+ externalId: createdIds1[0].externalId,
533
+ }),
534
+ name: "Test User 1",
535
+ age: 30,
536
+ });
537
+
538
+ expect(user2).toMatchObject({
539
+ id: expect.objectContaining({
540
+ externalId: createdIds1[1].externalId,
541
+ }),
542
+ name: "Test User 2",
543
+ age: 35,
544
+ });
545
+
546
+ expect(user3).toMatchObject({
547
+ id: expect.objectContaining({
548
+ externalId: createdIds1[2].externalId,
549
+ }),
550
+ name: "Test User 3",
551
+ age: 40,
552
+ });
553
+
554
+ // Test 2: Mixed operations (creates, updates, deletes) - only creates return IDs
555
+ const uow2 = queryEngine.createUnitOfWork("mixed-operations");
556
+
557
+ uow2.create("users", { name: "New User", age: 50 });
558
+ uow2.update("users", createdIds1[0], (b) => b.set({ age: 31 }));
559
+ uow2.create("users", { name: "Another New User", age: 55 });
560
+ uow2.delete("users", createdIds1[2]);
561
+
562
+ const { success: success2 } = await uow2.executeMutations();
563
+ expect(success2).toBe(true);
564
+
565
+ const createdIds2 = uow2.getCreatedIds();
566
+
567
+ // Only 2 creates, so only 2 IDs
568
+ expect(createdIds2).toHaveLength(2);
569
+ expect(createdIds2[0].externalId).toBeDefined();
570
+ expect(createdIds2[1].externalId).toBeDefined();
571
+
572
+ // Test 3: User-provided IDs are preserved
573
+ const customId = "my-custom-user-id-12345";
574
+ const uow3 = queryEngine.createUnitOfWork("create-with-custom-id");
575
+
576
+ uow3.create("users", { id: customId, name: "Custom ID User", age: 60 });
577
+
578
+ const { success: success3 } = await uow3.executeMutations();
579
+ expect(success3).toBe(true);
580
+
581
+ const createdIds3 = uow3.getCreatedIds();
582
+
583
+ expect(createdIds3).toHaveLength(1);
584
+ expect(createdIds3[0].externalId).toBe(customId);
585
+ expect(createdIds3[0].internalId).toBeDefined();
586
+
587
+ // Verify the user was created with the custom ID
588
+ const customIdUser = await queryEngine.findFirst("users", (b) =>
589
+ b.whereIndex("primary", (eb) => eb("id", "=", customId)),
590
+ );
591
+
592
+ expect(customIdUser).toMatchObject({
593
+ id: expect.objectContaining({
594
+ externalId: customId,
595
+ }),
596
+ name: "Custom ID User",
597
+ age: 60,
598
+ });
599
+ });
600
+
601
+ it("should handle timestamps and timezones correctly", async () => {
602
+ const queryEngine = adapter.createQueryEngine(testSchema, "namespace");
603
+
604
+ // Create a user
605
+ const createUserUow = queryEngine.createUnitOfWork("create-user-for-timestamp");
606
+ createUserUow.create("users", { name: "Timestamp User", age: 28 });
607
+ await createUserUow.executeMutations();
608
+
609
+ const [[user]] = await queryEngine
610
+ .createUnitOfWork("get-user-for-timestamp")
611
+ .find("users", (b) => b.whereIndex("name_idx", (eb) => eb("name", "=", "Timestamp User")))
612
+ .executeRetrieve();
613
+
614
+ // Create a post (note: SQLite schema doesn't have created_at column)
615
+ const createPostUow = queryEngine.createUnitOfWork("create-post-for-timestamp");
616
+ createPostUow.create("posts", {
617
+ user_id: user.id,
618
+ title: "Timestamp Test Post",
619
+ content: "Testing timestamp handling",
620
+ });
621
+ await createPostUow.executeMutations();
622
+
623
+ // Retrieve the post
624
+ const [[post]] = await queryEngine
625
+ .createUnitOfWork("get-post-for-timestamp")
626
+ .find("posts", (b) => b.whereIndex("posts_user_idx", (eb) => eb("user_id", "=", user.id)))
627
+ .executeRetrieve();
628
+
629
+ expect(post).toBeDefined();
630
+ expect(post.title).toBe("Timestamp Test Post");
631
+
632
+ // Test general Date handling (SQLite stores timestamps as integers)
633
+ const now = new Date();
634
+ expect(now).toBeInstanceOf(Date);
635
+ expect(typeof now.getTime).toBe("function");
636
+ expect(typeof now.toISOString).toBe("function");
637
+
638
+ // Verify date serialization/deserialization works
639
+ const isoString = now.toISOString();
640
+ expect(typeof isoString).toBe("string");
641
+ expect(new Date(isoString).getTime()).toBe(now.getTime());
642
+
643
+ // Test timezone preservation
644
+ const specificDate = new Date("2024-06-15T14:30:00Z");
645
+ expect(specificDate.toISOString()).toBe("2024-06-15T14:30:00.000Z");
646
+
647
+ // Verify SQLite numeric timestamp conversion
648
+ const timestamp = Date.now();
649
+ const dateFromTimestamp = new Date(timestamp);
650
+ expect(dateFromTimestamp.getTime()).toBe(timestamp);
651
+
652
+ // Verify that dates from different timezones are handled correctly
653
+ const localDate = new Date("2024-06-15T14:30:00");
654
+ expect(localDate).toBeInstanceOf(Date);
655
+ expect(typeof localDate.getTimezoneOffset()).toBe("number");
656
+ });
657
+
658
+ it("should support forSchema for multi-schema queries", async () => {
659
+ const queryEngine1 = adapter.createQueryEngine(testSchema, "namespace");
660
+ const queryEngine2 = adapter.createQueryEngine(schema2, "namespace2");
661
+
662
+ // Create test data in schema1 (users)
663
+ const createUsersUow = queryEngine1.createUnitOfWork("create-users-for-multi-schema");
664
+ createUsersUow.create("users", { name: "Multi Schema User 1", age: 25 });
665
+ createUsersUow.create("users", { name: "Multi Schema User 2", age: 30 });
666
+ const { success: success1 } = await createUsersUow.executeMutations();
667
+ expect(success1).toBe(true);
668
+
669
+ // Create test data in schema2 (products)
670
+ const createProductsUow = queryEngine2.createUnitOfWork("create-products-for-multi-schema");
671
+ createProductsUow.create("products", { name: "Product A", price: 100 });
672
+ createProductsUow.create("products", { name: "Product B", price: 200 });
673
+ const { success: success2 } = await createProductsUow.executeMutations();
674
+ expect(success2).toBe(true);
675
+
676
+ // Now use forSchema to query from both schemas
677
+ const uow = queryEngine1.createUnitOfWork("multi-schema-query");
678
+
679
+ const view1 = uow
680
+ .forSchema(testSchema)
681
+ .find("users", (b) =>
682
+ b
683
+ .whereIndex("name_idx", (eb) => eb("name", "starts with", "Multi Schema User"))
684
+ .select(["id", "name"]),
685
+ )
686
+ .find("users", (b) =>
687
+ b
688
+ .whereIndex("name_idx", (eb) => eb("name", "starts with", "Multi Schema User"))
689
+ .select(["name", "age"]),
690
+ );
691
+
692
+ const view2 = uow
693
+ .forSchema(schema2)
694
+ .find("products", (b) => b.whereIndex("primary").select(["name", "price"]));
695
+
696
+ // Execute the retrieval phase once
697
+ await uow.executeRetrieve();
698
+
699
+ // Get results from view1
700
+ const [users1, users2] = await view1.retrievalPhase;
701
+ const [user1] = users1;
702
+ expectTypeOf(user1).toMatchObjectType<{ id: FragnoId; name: string }>();
703
+
704
+ const [user2] = users2;
705
+ expectTypeOf(user2).toMatchObjectType<{ name: string; age: number | null }>();
706
+
707
+ // Get results from view2
708
+ const [products] = await view2.retrievalPhase;
709
+ const [product1] = products;
710
+ expectTypeOf(product1).toMatchObjectType<{ name: string; price: number }>();
711
+
712
+ // Verify users from schema1
713
+ expect(users1).toHaveLength(2);
714
+ expect(users1[0]).toMatchObject({
715
+ id: expect.any(Object),
716
+ name: "Multi Schema User 1",
717
+ });
718
+ expect(users1[1]).toMatchObject({
719
+ id: expect.any(Object),
720
+ name: "Multi Schema User 2",
721
+ });
722
+
723
+ expect(users2).toHaveLength(2);
724
+ expect(users2[0]).toMatchObject({
725
+ name: "Multi Schema User 1",
726
+ age: 25,
727
+ });
728
+ expect(users2[1]).toMatchObject({
729
+ name: "Multi Schema User 2",
730
+ age: 30,
731
+ });
732
+
733
+ // Verify products from schema2
734
+ expect(products).toHaveLength(2);
735
+ expect(products[0]).toMatchObject({
736
+ name: "Product A",
737
+ price: 100,
738
+ });
739
+ expect(products[1]).toMatchObject({
740
+ name: "Product B",
741
+ price: 200,
742
+ });
743
+ });
744
+
745
+ it("should verify hasNextPage in cursor pagination", async () => {
746
+ const queryEngine = adapter.createQueryEngine(testSchema, "namespace");
747
+
748
+ // Create exactly 15 users for precise pagination testing
749
+ const prefix = "HasNextPageTest";
750
+
751
+ for (let i = 1; i <= 15; i++) {
752
+ await queryEngine.create("users", {
753
+ name: `${prefix} ${i.toString().padStart(2, "0")}`,
754
+ age: 20 + i,
755
+ });
756
+ }
757
+
758
+ // Test 1: First page with more results available (pageSize=10, total=15)
759
+ const firstPage = await queryEngine.findWithCursor("users", (b) =>
760
+ b
761
+ .whereIndex("name_idx", (eb) => eb("name", "starts with", prefix))
762
+ .orderByIndex("name_idx", "asc")
763
+ .pageSize(10),
764
+ );
765
+
766
+ expect(firstPage.items).toHaveLength(10);
767
+ expect(firstPage.hasNextPage).toBe(true);
768
+ expect(firstPage.cursor).toBeInstanceOf(Cursor);
769
+
770
+ // Test 2: Second page (last page, partial results: 5 items remaining)
771
+ const secondPage = await queryEngine.findWithCursor("users", (b) =>
772
+ b
773
+ .whereIndex("name_idx", (eb) => eb("name", "starts with", prefix))
774
+ .after(firstPage.cursor!)
775
+ .orderByIndex("name_idx", "asc")
776
+ .pageSize(10),
777
+ );
778
+
779
+ expect(secondPage.items).toHaveLength(5);
780
+ expect(secondPage.hasNextPage).toBe(false);
781
+ expect(secondPage.cursor).toBeUndefined();
782
+
783
+ // Test 3: Empty results
784
+ const emptyPage = await queryEngine.findWithCursor("users", (b) =>
785
+ b
786
+ .whereIndex("name_idx", (eb) => eb("name", "starts with", "NonExistentPrefix"))
787
+ .orderByIndex("name_idx", "asc")
788
+ .pageSize(10),
789
+ );
790
+
791
+ expect(emptyPage.items).toHaveLength(0);
792
+ expect(emptyPage.hasNextPage).toBe(false);
793
+ expect(emptyPage.cursor).toBeUndefined();
794
+ });
795
+
796
+ it("should support executeUnitOfWork with retry logic", async () => {
797
+ const queryEngine = adapter.createQueryEngine(testSchema, "namespace");
798
+
799
+ // Create a test user
800
+ const createUow = queryEngine.createUnitOfWork("create-user-for-execute-uow");
801
+ createUow.create("users", { name: "Execute UOW User", age: 42 });
802
+ await createUow.executeMutations();
803
+
804
+ // Fetch the user to get their ID
805
+ const [[user]] = await queryEngine
806
+ .createUnitOfWork("get-user-for-execute-uow")
807
+ .find("users", (b) => b.whereIndex("name_idx", (eb) => eb("name", "=", "Execute UOW User")))
808
+ .executeRetrieve();
809
+
810
+ // Use executeUnitOfWork to increment age with optimistic locking
811
+ const result = await executeUnitOfWork(
812
+ {
813
+ retrieve: (uow) =>
814
+ uow.find("users", (b) => b.whereIndex("primary", (eb) => eb("id", "=", user.id))),
815
+ mutate: (uow, [users]) => {
816
+ const foundUser = users[0];
817
+ const newAge = foundUser.age! + 1;
818
+ uow.update("users", foundUser.id, (b) => b.set({ age: newAge }).check());
819
+ return { previousAge: foundUser.age, newAge };
820
+ },
821
+ onSuccess: ({ mutationResult }) => {
822
+ // Verify the age was incremented correctly
823
+ expect(mutationResult.newAge).toBe(mutationResult.previousAge! + 1);
824
+ },
825
+ },
826
+ {
827
+ createUnitOfWork: () => queryEngine.createUnitOfWork("execute-uow-update"),
828
+ retryPolicy: new ExponentialBackoffRetryPolicy({ maxRetries: 3, initialDelayMs: 1 }),
829
+ },
830
+ );
831
+
832
+ // Verify the operation succeeded
833
+ assert(result.success);
834
+ expect(result.mutationResult).toEqual({
835
+ previousAge: 42,
836
+ newAge: 43,
837
+ });
838
+
839
+ // Verify the user was actually updated in the database
840
+ const updatedUser = await queryEngine.findFirst("users", (b) =>
841
+ b.whereIndex("primary", (eb) => eb("id", "=", user.id)),
842
+ );
843
+
844
+ expect(updatedUser).toMatchObject({
845
+ id: expect.objectContaining({
846
+ externalId: user.id.externalId,
847
+ version: 1, // Version incremented due to check()
848
+ }),
849
+ name: "Execute UOW User",
850
+ age: 43,
851
+ });
852
+ });
853
+
854
+ it("should fail check() when version changes", async () => {
855
+ const queryEngine = adapter.createQueryEngine(testSchema, "namespace");
856
+
857
+ // Create a user
858
+ const createUserUow = queryEngine.createUnitOfWork("create-user-for-version-conflict");
859
+ createUserUow.create("users", {
860
+ name: "Version Conflict User SQLite",
861
+ age: 40,
862
+ });
863
+ await createUserUow.executeMutations();
864
+
865
+ // Get the user
866
+ const [[user]] = await queryEngine
867
+ .createUnitOfWork("get-user-for-version-conflict")
868
+ .find("users", (b) =>
869
+ b.whereIndex("name_idx", (eb) => eb("name", "=", "Version Conflict User SQLite")),
870
+ )
871
+ .executeRetrieve();
872
+
873
+ // Update the user to increment their version
874
+ const updateUow = queryEngine.createUnitOfWork("update-user-version");
875
+ updateUow.update("users", user.id, (b) => b.set({ age: 41 }));
876
+ await updateUow.executeMutations();
877
+
878
+ // Try to check with the old version (should fail)
879
+ const uow = queryEngine.createUnitOfWork("check-stale-version");
880
+ uow.check("users", user.id); // This has version 0, but the user now has version 1
881
+ uow.create("posts", {
882
+ user_id: user.id,
883
+ title: "Should Not Be Created SQLite",
884
+ content: "Content",
885
+ });
886
+
887
+ const { success } = await uow.executeMutations();
888
+ expect(success).toBe(false);
889
+
890
+ // Verify the post was NOT created
891
+ const [posts] = await queryEngine
892
+ .createUnitOfWork("get-posts-for-version-conflict")
893
+ .find("posts", (b) => b.whereIndex("posts_user_idx", (eb) => eb("user_id", "=", user.id)))
894
+ .executeRetrieve();
895
+
896
+ const conflictPosts = posts.filter((p) => p.title === "Should Not Be Created SQLite");
897
+ expect(conflictPosts).toHaveLength(0);
898
+ });
899
+ });