@fragno-dev/db 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (355) hide show
  1. package/.turbo/turbo-build.log +202 -140
  2. package/CHANGELOG.md +35 -0
  3. package/README.md +30 -9
  4. package/dist/adapters/adapters.d.ts +23 -21
  5. package/dist/adapters/adapters.d.ts.map +1 -1
  6. package/dist/adapters/adapters.js.map +1 -1
  7. package/dist/adapters/generic-sql/driver-config.d.ts +16 -1
  8. package/dist/adapters/generic-sql/driver-config.d.ts.map +1 -1
  9. package/dist/adapters/generic-sql/driver-config.js +23 -1
  10. package/dist/adapters/generic-sql/driver-config.js.map +1 -1
  11. package/dist/adapters/generic-sql/generic-sql-adapter.d.ts +27 -9
  12. package/dist/adapters/generic-sql/generic-sql-adapter.d.ts.map +1 -1
  13. package/dist/adapters/generic-sql/generic-sql-adapter.js +55 -16
  14. package/dist/adapters/generic-sql/generic-sql-adapter.js.map +1 -1
  15. package/dist/adapters/generic-sql/generic-sql-uow-executor.js +129 -3
  16. package/dist/adapters/generic-sql/generic-sql-uow-executor.js.map +1 -1
  17. package/dist/adapters/generic-sql/migration/dialect/mysql.js +24 -5
  18. package/dist/adapters/generic-sql/migration/dialect/mysql.js.map +1 -1
  19. package/dist/adapters/generic-sql/migration/dialect/postgres.js +6 -5
  20. package/dist/adapters/generic-sql/migration/dialect/postgres.js.map +1 -1
  21. package/dist/adapters/generic-sql/migration/dialect/sqlite.js +21 -10
  22. package/dist/adapters/generic-sql/migration/dialect/sqlite.js.map +1 -1
  23. package/dist/adapters/generic-sql/migration/prepared-migrations.d.ts.map +1 -1
  24. package/dist/adapters/generic-sql/migration/prepared-migrations.js +8 -8
  25. package/dist/adapters/generic-sql/migration/prepared-migrations.js.map +1 -1
  26. package/dist/adapters/generic-sql/migration/sql-generator.js +74 -51
  27. package/dist/adapters/generic-sql/migration/sql-generator.js.map +1 -1
  28. package/dist/adapters/generic-sql/query/create-sql-query-compiler.js +6 -5
  29. package/dist/adapters/generic-sql/query/create-sql-query-compiler.js.map +1 -1
  30. package/dist/adapters/generic-sql/query/cursor-utils.js +42 -4
  31. package/dist/adapters/generic-sql/query/cursor-utils.js.map +1 -1
  32. package/dist/adapters/generic-sql/query/generic-sql-uow-operation-compiler.js +25 -17
  33. package/dist/adapters/generic-sql/query/generic-sql-uow-operation-compiler.js.map +1 -1
  34. package/dist/adapters/generic-sql/query/select-builder.js +5 -3
  35. package/dist/adapters/generic-sql/query/select-builder.js.map +1 -1
  36. package/dist/adapters/generic-sql/query/sql-query-compiler.js +15 -12
  37. package/dist/adapters/generic-sql/query/sql-query-compiler.js.map +1 -1
  38. package/dist/adapters/generic-sql/query/where-builder.js +39 -29
  39. package/dist/adapters/generic-sql/query/where-builder.js.map +1 -1
  40. package/dist/adapters/generic-sql/sqlite-storage.d.ts +13 -0
  41. package/dist/adapters/generic-sql/sqlite-storage.d.ts.map +1 -0
  42. package/dist/adapters/generic-sql/sqlite-storage.js +15 -0
  43. package/dist/adapters/generic-sql/sqlite-storage.js.map +1 -0
  44. package/dist/adapters/generic-sql/uow-decoder.js +7 -3
  45. package/dist/adapters/generic-sql/uow-decoder.js.map +1 -1
  46. package/dist/adapters/generic-sql/uow-encoder.js +28 -8
  47. package/dist/adapters/generic-sql/uow-encoder.js.map +1 -1
  48. package/dist/adapters/in-memory/condition-evaluator.js +131 -0
  49. package/dist/adapters/in-memory/condition-evaluator.js.map +1 -0
  50. package/dist/adapters/in-memory/errors.d.ts +13 -0
  51. package/dist/adapters/in-memory/errors.d.ts.map +1 -0
  52. package/dist/adapters/in-memory/errors.js +23 -0
  53. package/dist/adapters/in-memory/errors.js.map +1 -0
  54. package/dist/adapters/in-memory/in-memory-adapter.d.ts +27 -0
  55. package/dist/adapters/in-memory/in-memory-adapter.d.ts.map +1 -0
  56. package/dist/adapters/in-memory/in-memory-adapter.js +176 -0
  57. package/dist/adapters/in-memory/in-memory-adapter.js.map +1 -0
  58. package/dist/adapters/in-memory/in-memory-uow.js +648 -0
  59. package/dist/adapters/in-memory/in-memory-uow.js.map +1 -0
  60. package/dist/adapters/in-memory/index.d.ts +4 -0
  61. package/dist/adapters/in-memory/index.js +4 -0
  62. package/dist/adapters/in-memory/options.d.ts +28 -0
  63. package/dist/adapters/in-memory/options.d.ts.map +1 -0
  64. package/dist/adapters/in-memory/options.js +61 -0
  65. package/dist/adapters/in-memory/options.js.map +1 -0
  66. package/dist/adapters/in-memory/reference-resolution.js +26 -0
  67. package/dist/adapters/in-memory/reference-resolution.js.map +1 -0
  68. package/dist/adapters/in-memory/sorted-array-index.js +129 -0
  69. package/dist/adapters/in-memory/sorted-array-index.js.map +1 -0
  70. package/dist/adapters/in-memory/store.js +71 -0
  71. package/dist/adapters/in-memory/store.js.map +1 -0
  72. package/dist/adapters/in-memory/value-comparison.js +28 -0
  73. package/dist/adapters/in-memory/value-comparison.js.map +1 -0
  74. package/dist/adapters/shared/from-unit-of-work-compiler.js.map +1 -1
  75. package/dist/adapters/shared/uow-operation-compiler.js +11 -11
  76. package/dist/adapters/shared/uow-operation-compiler.js.map +1 -1
  77. package/dist/adapters/sql/index.d.ts +5 -0
  78. package/dist/adapters/sql/index.js +4 -0
  79. package/dist/db-fragment-definition-builder.d.ts +18 -7
  80. package/dist/db-fragment-definition-builder.d.ts.map +1 -1
  81. package/dist/db-fragment-definition-builder.js +116 -54
  82. package/dist/db-fragment-definition-builder.js.map +1 -1
  83. package/dist/dispatchers/cloudflare-do/index.d.ts +26 -0
  84. package/dist/dispatchers/cloudflare-do/index.d.ts.map +1 -0
  85. package/dist/dispatchers/cloudflare-do/index.js +63 -0
  86. package/dist/dispatchers/cloudflare-do/index.js.map +1 -0
  87. package/dist/dispatchers/node/index.d.ts +17 -0
  88. package/dist/dispatchers/node/index.d.ts.map +1 -0
  89. package/dist/dispatchers/node/index.js +59 -0
  90. package/dist/dispatchers/node/index.js.map +1 -0
  91. package/dist/fragments/internal-fragment.d.ts +79 -2
  92. package/dist/fragments/internal-fragment.d.ts.map +1 -1
  93. package/dist/fragments/internal-fragment.js +150 -32
  94. package/dist/fragments/internal-fragment.js.map +1 -1
  95. package/dist/fragments/internal-fragment.routes.js +29 -0
  96. package/dist/fragments/internal-fragment.routes.js.map +1 -0
  97. package/dist/fragments/internal-fragment.schema.d.ts +9 -0
  98. package/dist/fragments/internal-fragment.schema.d.ts.map +1 -0
  99. package/dist/fragments/internal-fragment.schema.js +22 -0
  100. package/dist/fragments/internal-fragment.schema.js.map +1 -0
  101. package/dist/hooks/durable-hooks-processor.d.ts +14 -0
  102. package/dist/hooks/durable-hooks-processor.d.ts.map +1 -0
  103. package/dist/hooks/durable-hooks-processor.js +32 -0
  104. package/dist/hooks/durable-hooks-processor.js.map +1 -0
  105. package/dist/hooks/hooks.d.ts +42 -1
  106. package/dist/hooks/hooks.d.ts.map +1 -1
  107. package/dist/hooks/hooks.js +72 -6
  108. package/dist/hooks/hooks.js.map +1 -1
  109. package/dist/migration-engine/auto-from-schema.js +14 -11
  110. package/dist/migration-engine/auto-from-schema.js.map +1 -1
  111. package/dist/migration-engine/generation-engine.d.ts +16 -10
  112. package/dist/migration-engine/generation-engine.d.ts.map +1 -1
  113. package/dist/migration-engine/generation-engine.js +72 -33
  114. package/dist/migration-engine/generation-engine.js.map +1 -1
  115. package/dist/migration-engine/shared.js.map +1 -1
  116. package/dist/mod.d.ts +15 -8
  117. package/dist/mod.d.ts.map +1 -1
  118. package/dist/mod.js +14 -8
  119. package/dist/mod.js.map +1 -1
  120. package/dist/naming/sql-naming.d.ts +19 -0
  121. package/dist/naming/sql-naming.d.ts.map +1 -0
  122. package/dist/naming/sql-naming.js +116 -0
  123. package/dist/naming/sql-naming.js.map +1 -0
  124. package/dist/node_modules/.pnpm/{rou3@0.7.10 → rou3@0.7.12}/node_modules/rou3/dist/index.js +8 -5
  125. package/dist/node_modules/.pnpm/rou3@0.7.12/node_modules/rou3/dist/index.js.map +1 -0
  126. package/dist/outbox/outbox-builder.js +156 -0
  127. package/dist/outbox/outbox-builder.js.map +1 -0
  128. package/dist/outbox/outbox.d.ts +52 -0
  129. package/dist/outbox/outbox.d.ts.map +1 -0
  130. package/dist/outbox/outbox.js +37 -0
  131. package/dist/outbox/outbox.js.map +1 -0
  132. package/dist/packages/fragno/dist/api/fragment-definition-builder.js +3 -2
  133. package/dist/packages/fragno/dist/api/fragment-definition-builder.js.map +1 -1
  134. package/dist/packages/fragno/dist/api/fragment-instantiator.js +164 -20
  135. package/dist/packages/fragno/dist/api/fragment-instantiator.js.map +1 -1
  136. package/dist/packages/fragno/dist/api/request-input-context.js +67 -0
  137. package/dist/packages/fragno/dist/api/request-input-context.js.map +1 -1
  138. package/dist/packages/fragno/dist/api/route.js +14 -1
  139. package/dist/packages/fragno/dist/api/route.js.map +1 -1
  140. package/dist/packages/fragno/dist/internal/trace-context.js +12 -0
  141. package/dist/packages/fragno/dist/internal/trace-context.js.map +1 -0
  142. package/dist/query/column-defaults.js +20 -4
  143. package/dist/query/column-defaults.js.map +1 -1
  144. package/dist/query/cursor.d.ts +3 -1
  145. package/dist/query/cursor.d.ts.map +1 -1
  146. package/dist/query/cursor.js +45 -14
  147. package/dist/query/cursor.js.map +1 -1
  148. package/dist/query/db-now.d.ts +8 -0
  149. package/dist/query/db-now.d.ts.map +1 -0
  150. package/dist/query/db-now.js +7 -0
  151. package/dist/query/db-now.js.map +1 -0
  152. package/dist/query/serialize/create-sql-serializer.js +3 -2
  153. package/dist/query/serialize/create-sql-serializer.js.map +1 -1
  154. package/dist/query/serialize/dialect/mysql-serializer.js +12 -6
  155. package/dist/query/serialize/dialect/mysql-serializer.js.map +1 -1
  156. package/dist/query/serialize/dialect/postgres-serializer.js +25 -7
  157. package/dist/query/serialize/dialect/postgres-serializer.js.map +1 -1
  158. package/dist/query/serialize/dialect/sqlite-serializer.js +55 -11
  159. package/dist/query/serialize/dialect/sqlite-serializer.js.map +1 -1
  160. package/dist/query/serialize/sql-serializer.js +2 -2
  161. package/dist/query/serialize/sql-serializer.js.map +1 -1
  162. package/dist/query/simple-query-interface.d.ts +6 -1
  163. package/dist/query/simple-query-interface.d.ts.map +1 -1
  164. package/dist/query/unit-of-work/execute-unit-of-work.d.ts.map +1 -1
  165. package/dist/query/unit-of-work/execute-unit-of-work.js +11 -6
  166. package/dist/query/unit-of-work/execute-unit-of-work.js.map +1 -1
  167. package/dist/query/unit-of-work/unit-of-work.d.ts +50 -14
  168. package/dist/query/unit-of-work/unit-of-work.d.ts.map +1 -1
  169. package/dist/query/unit-of-work/unit-of-work.js +86 -5
  170. package/dist/query/unit-of-work/unit-of-work.js.map +1 -1
  171. package/dist/query/value-decoding.js +9 -6
  172. package/dist/query/value-decoding.js.map +1 -1
  173. package/dist/query/value-encoding.js +29 -9
  174. package/dist/query/value-encoding.js.map +1 -1
  175. package/dist/schema/create.d.ts +38 -14
  176. package/dist/schema/create.d.ts.map +1 -1
  177. package/dist/schema/create.js +81 -42
  178. package/dist/schema/create.js.map +1 -1
  179. package/dist/schema/generate-id.js +2 -2
  180. package/dist/schema/generate-id.js.map +1 -1
  181. package/dist/schema/type-conversion/create-sql-type-mapper.js +3 -2
  182. package/dist/schema/type-conversion/create-sql-type-mapper.js.map +1 -1
  183. package/dist/schema/type-conversion/dialect/sqlite.js +9 -0
  184. package/dist/schema/type-conversion/dialect/sqlite.js.map +1 -1
  185. package/dist/schema/validator.d.ts +10 -0
  186. package/dist/schema/validator.d.ts.map +1 -0
  187. package/dist/schema/validator.js +123 -0
  188. package/dist/schema/validator.js.map +1 -0
  189. package/dist/schema-output/drizzle.d.ts +30 -0
  190. package/dist/schema-output/drizzle.d.ts.map +1 -0
  191. package/dist/{adapters/drizzle/generate.js → schema-output/drizzle.js} +82 -56
  192. package/dist/schema-output/drizzle.js.map +1 -0
  193. package/dist/schema-output/prisma.d.ts +17 -0
  194. package/dist/schema-output/prisma.d.ts.map +1 -0
  195. package/dist/schema-output/prisma.js +296 -0
  196. package/dist/schema-output/prisma.js.map +1 -0
  197. package/dist/util/default-database-adapter.js +61 -0
  198. package/dist/util/default-database-adapter.js.map +1 -0
  199. package/dist/with-database.d.ts +1 -1
  200. package/dist/with-database.d.ts.map +1 -1
  201. package/dist/with-database.js +12 -3
  202. package/dist/with-database.js.map +1 -1
  203. package/package.json +43 -28
  204. package/src/adapters/adapters.ts +30 -24
  205. package/src/adapters/drizzle/migrate-drizzle.test.ts +54 -33
  206. package/src/adapters/drizzle/migration-parity-drizzle-kit.test.ts +599 -0
  207. package/src/adapters/drizzle/test-utils.ts +12 -8
  208. package/src/adapters/generic-sql/driver-config.ts +38 -0
  209. package/src/adapters/generic-sql/generic-sql-adapter.test.ts +5 -5
  210. package/src/adapters/generic-sql/generic-sql-adapter.ts +110 -24
  211. package/src/adapters/generic-sql/generic-sql-uow-executor.test.ts +54 -0
  212. package/src/adapters/generic-sql/generic-sql-uow-executor.ts +231 -3
  213. package/src/adapters/generic-sql/migration/adapter-migration-parity.test.ts +118 -0
  214. package/src/adapters/generic-sql/migration/dialect/mysql.test.ts +26 -8
  215. package/src/adapters/generic-sql/migration/dialect/mysql.ts +46 -8
  216. package/src/adapters/generic-sql/migration/dialect/postgres.test.ts +25 -7
  217. package/src/adapters/generic-sql/migration/dialect/postgres.ts +8 -4
  218. package/src/adapters/generic-sql/migration/dialect/sqlite.test.ts +47 -8
  219. package/src/adapters/generic-sql/migration/dialect/sqlite.ts +27 -12
  220. package/src/adapters/generic-sql/migration/prepared-migrations.test.ts +128 -39
  221. package/src/adapters/generic-sql/migration/prepared-migrations.ts +15 -8
  222. package/src/adapters/generic-sql/migration/sql-generator.ts +142 -65
  223. package/src/adapters/generic-sql/query/create-sql-query-compiler.ts +9 -6
  224. package/src/adapters/generic-sql/query/cursor-utils.test.ts +271 -0
  225. package/src/adapters/generic-sql/query/cursor-utils.ts +41 -6
  226. package/src/adapters/generic-sql/query/generic-sql-uow-operation-compiler.test.ts +27 -27
  227. package/src/adapters/generic-sql/query/generic-sql-uow-operation-compiler.ts +38 -24
  228. package/src/adapters/generic-sql/query/select-builder.test.ts +15 -11
  229. package/src/adapters/generic-sql/query/select-builder.ts +6 -2
  230. package/src/adapters/generic-sql/query/sql-query-compiler.test.ts +52 -2
  231. package/src/adapters/generic-sql/query/sql-query-compiler.ts +50 -15
  232. package/src/adapters/generic-sql/query/where-builder.test.ts +91 -17
  233. package/src/adapters/generic-sql/query/where-builder.ts +90 -38
  234. package/src/adapters/{kysely/kysely-adapter-pglite.test.ts → generic-sql/sql-adapter-pglite-migrations.test.ts} +6 -6
  235. package/src/adapters/generic-sql/sql-adapter-pglite-pagination.test.ts +806 -0
  236. package/src/adapters/{drizzle/drizzle-adapter-pglite.test.ts → generic-sql/sql-adapter-pglite-queries.test.ts} +11 -11
  237. package/src/adapters/generic-sql/{test/generic-drizzle-adapter-sqlite3.test.ts → sql-adapter-sqlite3-driver.test.ts} +10 -10
  238. package/src/adapters/{drizzle/drizzle-adapter-sqlite3.test.ts → generic-sql/sql-adapter-sqlite3-uow.test.ts} +7 -7
  239. package/src/adapters/{kysely/kysely-adapter-sqlocal.test.ts → generic-sql/sql-adapter-sqlocal.test.ts} +6 -6
  240. package/src/adapters/generic-sql/sqlite-storage.ts +20 -0
  241. package/src/adapters/generic-sql/uow-decoder.test.ts +1 -1
  242. package/src/adapters/generic-sql/uow-decoder.ts +21 -3
  243. package/src/adapters/generic-sql/uow-encoder.test.ts +33 -2
  244. package/src/adapters/generic-sql/uow-encoder.ts +50 -11
  245. package/src/adapters/in-memory/condition-evaluator.test.ts +193 -0
  246. package/src/adapters/in-memory/condition-evaluator.ts +275 -0
  247. package/src/adapters/in-memory/errors.ts +20 -0
  248. package/src/adapters/in-memory/in-memory-adapter.ts +277 -0
  249. package/src/adapters/in-memory/in-memory-uow.mutations.test.ts +296 -0
  250. package/src/adapters/in-memory/in-memory-uow.retrieval.test.ts +100 -0
  251. package/src/adapters/in-memory/in-memory-uow.ts +1348 -0
  252. package/src/adapters/in-memory/index.ts +3 -0
  253. package/src/adapters/in-memory/options.test.ts +41 -0
  254. package/src/adapters/in-memory/options.ts +87 -0
  255. package/src/adapters/in-memory/reference-resolution.test.ts +50 -0
  256. package/src/adapters/in-memory/reference-resolution.ts +67 -0
  257. package/src/adapters/in-memory/sorted-array-index.test.ts +123 -0
  258. package/src/adapters/in-memory/sorted-array-index.ts +228 -0
  259. package/src/adapters/in-memory/store.test.ts +68 -0
  260. package/src/adapters/in-memory/store.ts +145 -0
  261. package/src/adapters/in-memory/value-comparison.ts +53 -0
  262. package/src/adapters/in-memory/value-normalization.test.ts +57 -0
  263. package/src/adapters/prisma/prisma-adapter-sqlite3.test.ts +1163 -0
  264. package/src/adapters/shared/from-unit-of-work-compiler.ts +3 -1
  265. package/src/adapters/shared/uow-operation-compiler.ts +26 -16
  266. package/src/adapters/sql/index.ts +12 -0
  267. package/src/db-fragment-definition-builder.test.ts +30 -12
  268. package/src/db-fragment-definition-builder.ts +142 -73
  269. package/src/db-fragment-instantiator.test.ts +105 -13
  270. package/src/db-fragment-integration.test.ts +9 -7
  271. package/src/dispatchers/cloudflare-do/index.test.ts +73 -0
  272. package/src/dispatchers/cloudflare-do/index.ts +104 -0
  273. package/src/dispatchers/node/index.test.ts +91 -0
  274. package/src/dispatchers/node/index.ts +87 -0
  275. package/src/fragments/internal-fragment.routes.ts +42 -0
  276. package/src/fragments/internal-fragment.schema.ts +51 -0
  277. package/src/fragments/internal-fragment.test.ts +458 -8
  278. package/src/fragments/internal-fragment.ts +322 -63
  279. package/src/hooks/durable-hooks-processor.test.ts +117 -0
  280. package/src/hooks/durable-hooks-processor.ts +67 -0
  281. package/src/hooks/hooks.test.ts +165 -5
  282. package/src/hooks/hooks.ts +197 -9
  283. package/src/migration-engine/auto-from-schema.test.ts +14 -14
  284. package/src/migration-engine/auto-from-schema.ts +5 -2
  285. package/src/migration-engine/create.test.ts +2 -2
  286. package/src/migration-engine/generation-engine.test.ts +229 -104
  287. package/src/migration-engine/generation-engine.ts +94 -64
  288. package/src/migration-engine/shared.ts +1 -0
  289. package/src/mod.ts +64 -26
  290. package/src/naming/sql-naming.ts +180 -0
  291. package/src/outbox/outbox-builder.ts +241 -0
  292. package/src/outbox/outbox.test.ts +253 -0
  293. package/src/outbox/outbox.ts +137 -0
  294. package/src/query/column-defaults.ts +41 -3
  295. package/src/query/condition-builder.test.ts +3 -3
  296. package/src/query/cursor.test.ts +116 -18
  297. package/src/query/cursor.ts +75 -26
  298. package/src/query/db-now.ts +6 -0
  299. package/src/query/query-type.test.ts +2 -2
  300. package/src/query/serialize/create-sql-serializer.ts +7 -2
  301. package/src/query/serialize/dialect/mysql-serializer.ts +12 -4
  302. package/src/query/serialize/dialect/postgres-serializer.ts +34 -4
  303. package/src/query/serialize/dialect/sqlite-serializer.test.ts +51 -1
  304. package/src/query/serialize/dialect/sqlite-serializer.ts +92 -9
  305. package/src/query/serialize/sql-serializer.ts +4 -4
  306. package/src/query/simple-query-interface.ts +5 -0
  307. package/src/query/unit-of-work/execute-unit-of-work.test.ts +25 -1
  308. package/src/query/unit-of-work/execute-unit-of-work.ts +25 -8
  309. package/src/query/unit-of-work/unit-of-work-coordinator.test.ts +12 -12
  310. package/src/query/unit-of-work/unit-of-work-types.test.ts +1 -1
  311. package/src/query/unit-of-work/unit-of-work.test.ts +168 -37
  312. package/src/query/unit-of-work/unit-of-work.ts +203 -18
  313. package/src/query/value-decoding.test.ts +13 -2
  314. package/src/query/value-decoding.ts +17 -4
  315. package/src/query/value-encoding.test.ts +85 -2
  316. package/src/query/value-encoding.ts +56 -6
  317. package/src/schema/create.test.ts +129 -42
  318. package/src/schema/create.ts +185 -47
  319. package/src/schema/generate-id.test.ts +2 -2
  320. package/src/schema/generate-id.ts +2 -2
  321. package/src/schema/serialize.test.ts +14 -2
  322. package/src/schema/type-conversion/create-sql-type-mapper.ts +7 -2
  323. package/src/schema/type-conversion/dialect/sqlite.ts +18 -0
  324. package/src/schema/type-conversion/type-mapping.test.ts +25 -1
  325. package/src/schema/validator.test.ts +197 -0
  326. package/src/schema/validator.ts +231 -0
  327. package/src/{adapters/drizzle/generate.test.ts → schema-output/drizzle.test.ts} +179 -129
  328. package/src/{adapters/drizzle/generate.ts → schema-output/drizzle.ts} +143 -93
  329. package/src/schema-output/prisma.test.ts +536 -0
  330. package/src/schema-output/prisma.ts +573 -0
  331. package/src/util/default-database-adapter.ts +106 -0
  332. package/src/with-database.ts +22 -3
  333. package/tsdown.config.ts +6 -4
  334. package/dist/adapters/drizzle/drizzle-adapter.d.ts +0 -20
  335. package/dist/adapters/drizzle/drizzle-adapter.d.ts.map +0 -1
  336. package/dist/adapters/drizzle/drizzle-adapter.js +0 -27
  337. package/dist/adapters/drizzle/drizzle-adapter.js.map +0 -1
  338. package/dist/adapters/drizzle/generate.d.ts +0 -30
  339. package/dist/adapters/drizzle/generate.d.ts.map +0 -1
  340. package/dist/adapters/drizzle/generate.js.map +0 -1
  341. package/dist/adapters/kysely/kysely-adapter.d.ts +0 -19
  342. package/dist/adapters/kysely/kysely-adapter.d.ts.map +0 -1
  343. package/dist/adapters/kysely/kysely-adapter.js +0 -17
  344. package/dist/adapters/kysely/kysely-adapter.js.map +0 -1
  345. package/dist/adapters/shared/table-name-mapper.d.ts +0 -12
  346. package/dist/adapters/shared/table-name-mapper.d.ts.map +0 -1
  347. package/dist/adapters/shared/table-name-mapper.js +0 -43
  348. package/dist/adapters/shared/table-name-mapper.js.map +0 -1
  349. package/dist/node_modules/.pnpm/rou3@0.7.10/node_modules/rou3/dist/index.js.map +0 -1
  350. package/dist/schema-generator/schema-generator.d.ts +0 -15
  351. package/dist/schema-generator/schema-generator.d.ts.map +0 -1
  352. package/src/adapters/drizzle/drizzle-adapter.ts +0 -39
  353. package/src/adapters/kysely/kysely-adapter.ts +0 -27
  354. package/src/adapters/shared/table-name-mapper.ts +0 -50
  355. package/src/schema-generator/schema-generator.ts +0 -12
@@ -19,7 +19,8 @@ export class MySQLSerializer extends SQLSerializer {
19
19
  super(driverConfig);
20
20
  }
21
21
 
22
- protected serializeDate(value: Date): Date {
22
+ protected serializeDate(value: Date, _col: AnyColumn): Date {
23
+ void _col;
23
24
  // MySQL handles Date objects natively
24
25
  return value;
25
26
  }
@@ -34,7 +35,8 @@ export class MySQLSerializer extends SQLSerializer {
34
35
  return value;
35
36
  }
36
37
 
37
- protected deserializeDate(value: unknown): Date {
38
+ protected deserializeDate(value: unknown, _col: AnyColumn): Date {
39
+ void _col;
38
40
  if (value instanceof Date) {
39
41
  return value;
40
42
  }
@@ -96,12 +98,15 @@ export class MySQLSerializer extends SQLSerializer {
96
98
 
97
99
  protected deserializeInteger(value: unknown): number {
98
100
  if (typeof value === "number") {
101
+ if (Number.isNaN(value) || !Number.isFinite(value)) {
102
+ throw new Error(`Cannot deserialize integer from invalid number: ${value}`);
103
+ }
99
104
  return value;
100
105
  }
101
106
  // MySQL may return integers as strings
102
107
  if (typeof value === "string") {
103
108
  const num = Number(value);
104
- if (isNaN(num)) {
109
+ if (Number.isNaN(num) || !Number.isFinite(num)) {
105
110
  throw new Error(`Cannot deserialize integer from invalid string: ${value}`);
106
111
  }
107
112
  return num;
@@ -121,11 +126,14 @@ export class MySQLSerializer extends SQLSerializer {
121
126
  protected deserializeDecimal(value: unknown): number {
122
127
  // MySQL can return decimals as numbers or strings
123
128
  if (typeof value === "number") {
129
+ if (Number.isNaN(value) || !Number.isFinite(value)) {
130
+ throw new Error(`Cannot deserialize decimal from invalid number: ${value}`);
131
+ }
124
132
  return value;
125
133
  }
126
134
  if (typeof value === "string") {
127
135
  const num = parseFloat(value);
128
- if (isNaN(num)) {
136
+ if (Number.isNaN(num) || !Number.isFinite(num)) {
129
137
  throw new Error(`Cannot deserialize decimal from invalid string: ${value}`);
130
138
  }
131
139
  return num;
@@ -18,7 +18,8 @@ export class PostgreSQLSerializer extends SQLSerializer {
18
18
  super(driverConfig);
19
19
  }
20
20
 
21
- protected serializeDate(value: Date): Date {
21
+ protected serializeDate(value: Date, _col: AnyColumn): Date {
22
+ void _col;
22
23
  // PostgreSQL handles Date objects natively
23
24
  return value;
24
25
  }
@@ -33,13 +34,18 @@ export class PostgreSQLSerializer extends SQLSerializer {
33
34
  return value;
34
35
  }
35
36
 
36
- protected deserializeDate(value: unknown): Date {
37
+ protected deserializeDate(value: unknown, _col: AnyColumn): Date {
38
+ void _col;
37
39
  if (value instanceof Date) {
40
+ if (this.driverConfig.driverType === "pglite") {
41
+ return new Date(value.getTime() - value.getTimezoneOffset() * 60_000);
42
+ }
38
43
  return value;
39
44
  }
40
45
  // PostgreSQL returns timestamps/dates as strings
41
46
  if (typeof value === "string") {
42
- return new Date(value);
47
+ // Normalize timezone-less timestamps to UTC to avoid local offset drift.
48
+ return new Date(normalizeTimestampString(value));
43
49
  }
44
50
  throw new Error(`Cannot deserialize date from value: ${value}`);
45
51
  }
@@ -91,6 +97,9 @@ export class PostgreSQLSerializer extends SQLSerializer {
91
97
 
92
98
  protected deserializeInteger(value: unknown): number {
93
99
  if (typeof value === "number") {
100
+ if (Number.isNaN(value) || !Number.isFinite(value)) {
101
+ throw new Error(`Cannot deserialize integer from invalid number: ${value}`);
102
+ }
94
103
  return value;
95
104
  }
96
105
  // PostgreSQL may return bigint for large integers
@@ -108,11 +117,14 @@ export class PostgreSQLSerializer extends SQLSerializer {
108
117
  protected deserializeDecimal(value: unknown): number {
109
118
  // PostgreSQL can return decimals as numbers or strings depending on precision
110
119
  if (typeof value === "number") {
120
+ if (Number.isNaN(value) || !Number.isFinite(value)) {
121
+ throw new Error(`Cannot deserialize decimal from invalid number: ${value}`);
122
+ }
111
123
  return value;
112
124
  }
113
125
  if (typeof value === "string") {
114
126
  const num = parseFloat(value);
115
- if (isNaN(num)) {
127
+ if (Number.isNaN(num) || !Number.isFinite(num)) {
116
128
  throw new Error(`Cannot deserialize decimal from invalid string: ${value}`);
117
129
  }
118
130
  return num;
@@ -127,3 +139,21 @@ export class PostgreSQLSerializer extends SQLSerializer {
127
139
  throw new Error(`Cannot deserialize string from value: ${typeof value}`);
128
140
  }
129
141
  }
142
+
143
+ const normalizeTimestampString = (value: string) => {
144
+ if (!hasTimeComponent(value) || !isTimestampWithoutTimezone(value)) {
145
+ return value;
146
+ }
147
+ const withTimeSeparator = value.includes(" ") ? value.replace(" ", "T") : value;
148
+ return `${withTimeSeparator}Z`;
149
+ };
150
+
151
+ const hasTimeComponent = (value: string) => /\d{2}:\d{2}:\d{2}/.test(value);
152
+
153
+ const isTimestampWithoutTimezone = (value: string) => {
154
+ // Detect RFC3339-like timezone indicators: Z or +/-HH(:MM)?
155
+ if (value.endsWith("Z")) {
156
+ return false;
157
+ }
158
+ return !/[+-]\d{2}(:?\d{2})?$/.test(value);
159
+ };
@@ -2,10 +2,18 @@ import { describe, it, expect } from "vitest";
2
2
  import { SQLiteSerializer } from "./sqlite-serializer";
3
3
  import type { AnyColumn } from "../../../schema/create";
4
4
  import { BetterSQLite3DriverConfig } from "../../../adapters/generic-sql/driver-config";
5
+ import { sqliteStoragePrisma } from "../../../adapters/generic-sql/sqlite-storage";
5
6
 
6
7
  describe("SQLiteSerializer", () => {
7
8
  const mockDriverConfig = new BetterSQLite3DriverConfig();
8
9
  const serializer = new SQLiteSerializer(mockDriverConfig);
10
+ const prismaSerializer = new SQLiteSerializer(mockDriverConfig, sqliteStoragePrisma);
11
+ const timestampColumn: AnyColumn = {
12
+ name: "createdAt",
13
+ type: "timestamp",
14
+ role: "regular",
15
+ isNullable: false,
16
+ } as AnyColumn;
9
17
 
10
18
  describe("serializeBigInt", () => {
11
19
  describe("for internal-id and reference columns", () => {
@@ -110,6 +118,19 @@ describe("SQLiteSerializer", () => {
110
118
  expect((result as Buffer).length).toBe(8);
111
119
  });
112
120
 
121
+ it("should keep bigint for regular column in prisma storage", () => {
122
+ const col: AnyColumn = {
123
+ name: "largeNumber",
124
+ type: "bigint",
125
+ role: "regular",
126
+ isNullable: false,
127
+ } as AnyColumn;
128
+
129
+ const result = prismaSerializer["serializeBigInt"](BigInt(789), col);
130
+ expect(result).toBe(BigInt(789));
131
+ expect(typeof result).toBe("bigint");
132
+ });
133
+
113
134
  it("should handle large values outside safe integer range as Buffer", () => {
114
135
  const col: AnyColumn = {
115
136
  name: "largeNumber",
@@ -132,11 +153,18 @@ describe("SQLiteSerializer", () => {
132
153
  describe("other serialization methods", () => {
133
154
  it("should serialize Date to timestamp number", () => {
134
155
  const date = new Date("2024-01-01T00:00:00Z");
135
- const result = serializer["serializeDate"](date);
156
+ const result = serializer["serializeDate"](date, timestampColumn);
136
157
  expect(result).toBe(date.getTime());
137
158
  expect(typeof result).toBe("number");
138
159
  });
139
160
 
161
+ it("should serialize Date to ISO string for prisma storage", () => {
162
+ const date = new Date("2024-01-01T00:00:00Z");
163
+ const result = prismaSerializer["serializeDate"](date, timestampColumn);
164
+ expect(result).toBe(date.toISOString());
165
+ expect(typeof result).toBe("string");
166
+ });
167
+
140
168
  it("should serialize boolean to 0/1", () => {
141
169
  expect(serializer["serializeBoolean"](true)).toBe(1);
142
170
  expect(serializer["serializeBoolean"](false)).toBe(0);
@@ -181,6 +209,18 @@ describe("SQLiteSerializer", () => {
181
209
  });
182
210
  });
183
211
 
212
+ describe("deserializeDate", () => {
213
+ it("should parse CURRENT_TIMESTAMP format as UTC", () => {
214
+ const date = prismaSerializer["deserializeDate"]("2024-03-10 12:34:56", timestampColumn);
215
+ expect(date.toISOString()).toBe("2024-03-10T12:34:56.000Z");
216
+ });
217
+
218
+ it("should parse CURRENT_TIMESTAMP format with milliseconds as UTC", () => {
219
+ const date = prismaSerializer["deserializeDate"]("2024-03-10 12:34:56.789", timestampColumn);
220
+ expect(date.toISOString()).toBe("2024-03-10T12:34:56.789Z");
221
+ });
222
+ });
223
+
184
224
  describe("deserializeInteger", () => {
185
225
  it("should deserialize number directly", () => {
186
226
  expect(serializer["deserializeInteger"](42)).toBe(42);
@@ -248,4 +288,14 @@ describe("SQLiteSerializer", () => {
248
288
  );
249
289
  });
250
290
  });
291
+
292
+ describe("deserializeBigInt", () => {
293
+ it("should throw when prisma storage receives unsafe bigint number", () => {
294
+ const unsafeNumber = Number.MAX_SAFE_INTEGER + 10;
295
+ expect(() => prismaSerializer["deserializeBigInt"](unsafeNumber)).toThrow(RangeError);
296
+ expect(() => prismaSerializer["deserializeBigInt"](unsafeNumber)).toThrow(
297
+ /exceeds Number\.MAX_SAFE_INTEGER/,
298
+ );
299
+ });
300
+ });
251
301
  });
@@ -1,23 +1,59 @@
1
1
  import type { AnyColumn } from "../../../schema/create";
2
2
  import { SQLSerializer } from "../sql-serializer";
3
3
  import type { DriverConfig } from "../../../adapters/generic-sql/driver-config";
4
+ import type { SQLiteStorageMode } from "../../../adapters/generic-sql/sqlite-storage";
5
+ import { sqliteStorageDefault } from "../../../adapters/generic-sql/sqlite-storage";
6
+
7
+ const SQLITE_UTC_TIMESTAMP_REGEX =
8
+ /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,3}))?$/;
9
+
10
+ function parseSQLiteUtcTimestamp(value: string): Date | null {
11
+ const match = SQLITE_UTC_TIMESTAMP_REGEX.exec(value);
12
+ if (!match) {
13
+ return null;
14
+ }
15
+
16
+ const [, year, month, day, hour, minute, second, millis] = match;
17
+ const milliseconds = millis ? Number(millis.padEnd(3, "0")) : 0;
18
+ return new Date(
19
+ Date.UTC(
20
+ Number(year),
21
+ Number(month) - 1,
22
+ Number(day),
23
+ Number(hour),
24
+ Number(minute),
25
+ Number(second),
26
+ milliseconds,
27
+ ),
28
+ );
29
+ }
4
30
 
5
31
  /**
6
32
  * SQLite-specific serializer.
7
33
  *
8
34
  * SQLite has limited native type support and requires conversions:
9
35
  * - JSON → strings (no native JSON support)
10
- * - Dates → numbers (timestamps)
36
+ * - Dates → numbers or ISO strings (storage-mode dependent)
11
37
  * - Booleans → 0/1
12
- * - BigInts → Buffer (except for internal-id and reference columns → number)
38
+ * - BigInts → Buffer or integer (storage-mode dependent; internal-id/reference stay integer)
13
39
  * - Numbers/strings → Date for timestamps/dates
14
40
  */
15
41
  export class SQLiteSerializer extends SQLSerializer {
16
- constructor(driverConfig: DriverConfig) {
42
+ private readonly sqliteStorageMode: SQLiteStorageMode;
43
+
44
+ constructor(driverConfig: DriverConfig, sqliteStorageMode?: SQLiteStorageMode) {
17
45
  super(driverConfig);
46
+ this.sqliteStorageMode = sqliteStorageMode ?? sqliteStorageDefault;
18
47
  }
19
48
 
20
- protected serializeDate(value: Date): number {
49
+ protected serializeDate(value: Date, col: AnyColumn): number | string {
50
+ const storage =
51
+ col.type === "date"
52
+ ? this.sqliteStorageMode.dateStorage
53
+ : this.sqliteStorageMode.timestampStorage;
54
+ if (storage === "iso-text") {
55
+ return value.toISOString();
56
+ }
21
57
  return value.getTime();
22
58
  }
23
59
 
@@ -25,7 +61,7 @@ export class SQLiteSerializer extends SQLSerializer {
25
61
  return value ? 1 : 0;
26
62
  }
27
63
 
28
- protected serializeBigInt(value: bigint, col: AnyColumn): number | Buffer {
64
+ protected serializeBigInt(value: bigint, col: AnyColumn): bigint | number | Buffer {
29
65
  // SQLite special case: internal-id and reference columns use integer, not blob
30
66
  // These should be converted to numbers for SQLite
31
67
  if (col.role === "reference" || col.role === "internal-id") {
@@ -40,18 +76,52 @@ export class SQLiteSerializer extends SQLSerializer {
40
76
  }
41
77
  return Number(value);
42
78
  }
79
+ if (this.sqliteStorageMode.bigintStorage === "integer") {
80
+ return value;
81
+ }
43
82
  const buf = Buffer.alloc(8);
44
83
  buf.writeBigInt64BE(value);
45
84
  return buf;
46
85
  }
47
86
 
48
- protected deserializeDate(value: unknown): Date {
87
+ protected deserializeDate(value: unknown, col: AnyColumn): Date {
49
88
  if (value instanceof Date) {
50
89
  return value;
51
90
  }
52
- if (typeof value === "number" || typeof value === "string") {
91
+ if (typeof value === "string") {
92
+ if (/^\d+$/.test(value)) {
93
+ const numericDate = new Date(Number(value));
94
+ if (Number.isNaN(numericDate.getTime())) {
95
+ throw new Error(`Cannot deserialize date from value: ${value}`);
96
+ }
97
+ return numericDate;
98
+ }
99
+ const parsed = parseSQLiteUtcTimestamp(value);
100
+ const date = parsed ?? new Date(value);
101
+ if (Number.isNaN(date.getTime())) {
102
+ throw new Error(`Cannot deserialize date from value: ${value}`);
103
+ }
104
+ return date;
105
+ }
106
+ if (typeof value === "number") {
107
+ if (Number.isNaN(value)) {
108
+ throw new Error(`Cannot deserialize date from value: ${value}`);
109
+ }
53
110
  return new Date(value);
54
111
  }
112
+ if (typeof value === "bigint") {
113
+ const storage =
114
+ col.type === "date"
115
+ ? this.sqliteStorageMode.dateStorage
116
+ : this.sqliteStorageMode.timestampStorage;
117
+ if (storage === "epoch-ms") {
118
+ const date = new Date(Number(value));
119
+ if (Number.isNaN(date.getTime())) {
120
+ throw new Error(`Cannot deserialize date from value: ${value}`);
121
+ }
122
+ return date;
123
+ }
124
+ }
55
125
  throw new Error(`Cannot deserialize date from value: ${value}`);
56
126
  }
57
127
 
@@ -76,6 +146,13 @@ export class SQLiteSerializer extends SQLSerializer {
76
146
  return BigInt(value);
77
147
  }
78
148
  if (typeof value === "number") {
149
+ if (this.sqliteStorageMode.bigintStorage === "integer") {
150
+ if (Math.abs(value) > Number.MAX_SAFE_INTEGER) {
151
+ throw new RangeError(
152
+ `Cannot deserialize bigint value ${value}: exceeds Number.MAX_SAFE_INTEGER`,
153
+ );
154
+ }
155
+ }
79
156
  return BigInt(value);
80
157
  }
81
158
  throw new Error(`Cannot deserialize bigint from value: ${value}`);
@@ -110,12 +187,15 @@ export class SQLiteSerializer extends SQLSerializer {
110
187
 
111
188
  protected deserializeInteger(value: unknown): number {
112
189
  if (typeof value === "number") {
190
+ if (Number.isNaN(value) || !Number.isFinite(value)) {
191
+ throw new Error(`Cannot deserialize integer from invalid number: ${value}`);
192
+ }
113
193
  return value;
114
194
  }
115
195
  // SQLite may return integers as strings for large values
116
196
  if (typeof value === "string") {
117
197
  const num = Number(value);
118
- if (isNaN(num)) {
198
+ if (Number.isNaN(num) || !Number.isFinite(num)) {
119
199
  throw new Error(`Cannot deserialize integer from invalid string: ${value}`);
120
200
  }
121
201
  return num;
@@ -135,11 +215,14 @@ export class SQLiteSerializer extends SQLSerializer {
135
215
  protected deserializeDecimal(value: unknown): number {
136
216
  // SQLite stores decimals as REAL (floating point) or TEXT
137
217
  if (typeof value === "number") {
218
+ if (Number.isNaN(value) || !Number.isFinite(value)) {
219
+ throw new Error(`Cannot deserialize decimal from invalid number: ${value}`);
220
+ }
138
221
  return value;
139
222
  }
140
223
  if (typeof value === "string") {
141
224
  const num = parseFloat(value);
142
- if (isNaN(num)) {
225
+ if (Number.isNaN(num) || !Number.isFinite(num)) {
143
226
  throw new Error(`Cannot deserialize decimal from invalid string: ${value}`);
144
227
  }
145
228
  return num;
@@ -40,7 +40,7 @@ export abstract class SQLSerializer {
40
40
 
41
41
  // Handle date/timestamp deserialization
42
42
  if (col.type === "timestamp" || col.type === "date") {
43
- return this.deserializeDate(value);
43
+ return this.deserializeDate(value, col);
44
44
  }
45
45
 
46
46
  // Handle boolean deserialization
@@ -103,7 +103,7 @@ export abstract class SQLSerializer {
103
103
  if (!skipDriverConversions) {
104
104
  // Handle date/timestamp serialization
105
105
  if (value instanceof Date) {
106
- return this.serializeDate(value);
106
+ return this.serializeDate(value, col);
107
107
  }
108
108
 
109
109
  // Handle boolean serialization
@@ -126,13 +126,13 @@ export abstract class SQLSerializer {
126
126
  }
127
127
 
128
128
  // Abstract methods for dialect-specific serialization
129
- protected abstract serializeDate(value: Date): Date | number;
129
+ protected abstract serializeDate(value: Date, col: AnyColumn): Date | number | string;
130
130
  protected abstract serializeBoolean(value: boolean): boolean | number;
131
131
  protected abstract serializeBigInt(value: bigint, col: AnyColumn): bigint | number | Buffer;
132
132
  protected abstract serializeJson(value: unknown): unknown;
133
133
 
134
134
  // Abstract methods for dialect-specific deserialization
135
- protected abstract deserializeDate(value: unknown): Date;
135
+ protected abstract deserializeDate(value: unknown, col: AnyColumn): Date;
136
136
  protected abstract deserializeBoolean(value: unknown): boolean;
137
137
  protected abstract deserializeBigInt(value: unknown): bigint;
138
138
  protected abstract deserializeJson(value: unknown): unknown;
@@ -133,6 +133,11 @@ export type FindManyOptions<
133
133
  : {});
134
134
 
135
135
  export interface SimpleQueryInterface<TSchema extends AnySchema, TUOWConfig = void> {
136
+ /**
137
+ * Fetch the current database timestamp.
138
+ * Optional for adapters that cannot provide DB time.
139
+ */
140
+ now?: () => Promise<Date>;
136
141
  /**
137
142
  * Find multiple records using a builder pattern
138
143
  */
@@ -20,7 +20,7 @@ import {
20
20
  } from "./retry-policy";
21
21
  import type { AwaitedPromisesInObject, TxResult } from "./execute-unit-of-work";
22
22
 
23
- const testSchema = schema((s) =>
23
+ const testSchema = schema("test", (s) =>
24
24
  s.addTable("users", (t) =>
25
25
  t
26
26
  .addColumn("id", idColumn())
@@ -414,6 +414,26 @@ describe("Unified Tx API", () => {
414
414
  expect(result.userId).toBeInstanceOf(FragnoId);
415
415
  });
416
416
 
417
+ it("should throw when retry policy is provided without retrieve operations", async () => {
418
+ const compiler = createMockCompiler();
419
+ const executor: UOWExecutor<unknown, unknown> = {
420
+ executeRetrievalPhase: async () => [],
421
+ executeMutationPhase: async () => ({ success: false }),
422
+ };
423
+ const decoder = createMockDecoder();
424
+
425
+ await expect(
426
+ createHandlerTxBuilder({
427
+ createUnitOfWork: () => createUnitOfWork(compiler, executor, decoder),
428
+ retryPolicy: new NoRetryPolicy(),
429
+ })
430
+ .mutate(() => ({ done: true }))
431
+ .execute(),
432
+ ).rejects.toThrow(
433
+ "Retry policy is only supported when the transaction includes retrieve operations.",
434
+ );
435
+ });
436
+
417
437
  it("should execute a transaction with serviceCalls as retrieve source", async () => {
418
438
  const compiler = createMockCompiler();
419
439
  const mockUser = {
@@ -772,6 +792,7 @@ describe("Unified Tx API", () => {
772
792
  createUnitOfWork: () => createUnitOfWork(compiler, executor, decoder),
773
793
  retryPolicy: new ExponentialBackoffRetryPolicy({ maxRetries: 5, initialDelayMs: 1 }),
774
794
  })
795
+ .retrieve(({ forSchema }) => forSchema(testSchema).find("users"))
775
796
  .mutate(({ forSchema }) => {
776
797
  forSchema(testSchema).create("users", {
777
798
  email: "test@example.com",
@@ -801,6 +822,7 @@ describe("Unified Tx API", () => {
801
822
  createUnitOfWork: () => createUnitOfWork(compiler, executor, decoder),
802
823
  retryPolicy: new NoRetryPolicy(),
803
824
  })
825
+ .retrieve(({ forSchema }) => forSchema(testSchema).find("users"))
804
826
  .mutate(() => ({ done: true }))
805
827
  .execute(),
806
828
  ).rejects.toThrow(ConcurrencyConflictError);
@@ -850,6 +872,7 @@ describe("Unified Tx API", () => {
850
872
  },
851
873
  retryPolicy: new LinearBackoffRetryPolicy({ maxRetries: 3, delayMs: 1 }),
852
874
  })
875
+ .retrieve(({ forSchema }) => forSchema(testSchema).find("users"))
853
876
  .mutate(({ forSchema }) => {
854
877
  forSchema(testSchema).create("users", {
855
878
  email: "test@example.com",
@@ -882,6 +905,7 @@ describe("Unified Tx API", () => {
882
905
  retryPolicy: new LinearBackoffRetryPolicy({ maxRetries: 5, delayMs: 100 }),
883
906
  signal: controller.signal,
884
907
  })
908
+ .retrieve(({ forSchema }) => forSchema(testSchema).find("users"))
885
909
  .mutate(() => ({ done: true }))
886
910
  .execute(),
887
911
  ).rejects.toThrow("Transaction execution aborted");
@@ -1,7 +1,7 @@
1
1
  import type { AnySchema } from "../../schema/create";
2
2
  import type { TypedUnitOfWork, IUnitOfWork } from "./unit-of-work";
3
3
  import type { HooksMap } from "../../hooks/hooks";
4
- import { ExponentialBackoffRetryPolicy, type RetryPolicy } from "./retry-policy";
4
+ import { ExponentialBackoffRetryPolicy, NoRetryPolicy, type RetryPolicy } from "./retry-policy";
5
5
 
6
6
  /**
7
7
  * Symbol to identify TxResult objects
@@ -724,13 +724,6 @@ async function executeTx(
724
724
  type TServiceCalls = readonly (TxResult<any, any> | undefined)[];
725
725
  type TMutateResult = unknown;
726
726
  type THooks = HooksMap;
727
- const retryPolicy =
728
- options.retryPolicy ??
729
- new ExponentialBackoffRetryPolicy({
730
- maxRetries: 5,
731
- initialDelayMs: 10,
732
- maxDelayMs: 100,
733
- });
734
727
  const signal = options.signal;
735
728
  let attempt = 0;
736
729
 
@@ -740,6 +733,8 @@ async function executeTx(
740
733
  throw new Error("Transaction execution aborted");
741
734
  }
742
735
 
736
+ let retryPolicy: RetryPolicy | undefined;
737
+
743
738
  try {
744
739
  // Create a fresh UOW for this attempt
745
740
  const baseUow = options.createUnitOfWork();
@@ -764,6 +759,24 @@ async function executeTx(
764
759
 
765
760
  const allServiceCallTxResults = serviceCalls ? collectAllTxResults([...serviceCalls]) : [];
766
761
 
762
+ const hasRetrieveOps = baseUow.getRetrievalOperations().length > 0;
763
+ if (!hasRetrieveOps) {
764
+ if (options.retryPolicy) {
765
+ throw new Error(
766
+ "Retry policy is only supported when the transaction includes retrieve operations.",
767
+ );
768
+ }
769
+ retryPolicy = new NoRetryPolicy();
770
+ } else {
771
+ retryPolicy =
772
+ options.retryPolicy ??
773
+ new ExponentialBackoffRetryPolicy({
774
+ maxRetries: 5,
775
+ initialDelayMs: 10,
776
+ maxDelayMs: 100,
777
+ });
778
+ }
779
+
767
780
  await baseUow.executeRetrieve();
768
781
 
769
782
  // Get retrieve results from TypedUnitOfWork's retrievalPhase or default to empty array
@@ -885,6 +898,10 @@ async function executeTx(
885
898
  throw error;
886
899
  }
887
900
 
901
+ if (!retryPolicy) {
902
+ throw error;
903
+ }
904
+
888
905
  if (!retryPolicy.shouldRetry(attempt, error, signal)) {
889
906
  if (signal?.aborted) {
890
907
  throw new Error("Transaction execution aborted");
@@ -59,7 +59,7 @@ function createMockDecoder(): UOWDecoder {
59
59
 
60
60
  describe("UOW Coordinator - Parent-Child Execution", () => {
61
61
  it("should allow child UOWs to add operations and parent to execute them", async () => {
62
- const testSchema = schema((s) =>
62
+ const testSchema = schema("test", (s) =>
63
63
  s.addTable("users", (t) =>
64
64
  t.addColumn("id", idColumn()).addColumn("name", "string").addColumn("email", "string"),
65
65
  ),
@@ -102,7 +102,7 @@ describe("UOW Coordinator - Parent-Child Execution", () => {
102
102
  });
103
103
 
104
104
  it("should handle nested service calls that await phase promises without deadlock", async () => {
105
- const testSchema = schema((s) =>
105
+ const testSchema = schema("test", (s) =>
106
106
  s
107
107
  .addTable("users", (t) =>
108
108
  t.addColumn("id", idColumn()).addColumn("name", "string").addColumn("email", "string"),
@@ -176,7 +176,7 @@ describe("UOW Coordinator - Parent-Child Execution", () => {
176
176
  });
177
177
 
178
178
  it("should handle retrieval-to-mutation flow with service composition", async () => {
179
- const testSchema = schema((s) =>
179
+ const testSchema = schema("test", (s) =>
180
180
  s
181
181
  .addTable("users", (t) =>
182
182
  t
@@ -303,7 +303,7 @@ describe("UOW Coordinator - Parent-Child Execution", () => {
303
303
  });
304
304
 
305
305
  it("should handle deeply nested child UOWs (3+ levels)", async () => {
306
- const testSchema = schema((s) =>
306
+ const testSchema = schema("test", (s) =>
307
307
  s
308
308
  .addTable("users", (t) =>
309
309
  t.addColumn("id", idColumn()).addColumn("name", "string").addColumn("email", "string"),
@@ -416,7 +416,7 @@ describe("UOW Coordinator - Parent-Child Execution", () => {
416
416
  });
417
417
 
418
418
  it("should handle sibling child UOWs at same nesting level", async () => {
419
- const testSchema = schema((s) =>
419
+ const testSchema = schema("test", (s) =>
420
420
  s
421
421
  .addTable("users", (t) =>
422
422
  t
@@ -526,7 +526,7 @@ describe("UOW Coordinator - Parent-Child Execution", () => {
526
526
  });
527
527
 
528
528
  it("should support transaction rollback pattern", async () => {
529
- const testSchema = schema((s) =>
529
+ const testSchema = schema("test", (s) =>
530
530
  s
531
531
  .addTable("accounts", (t) =>
532
532
  t
@@ -633,7 +633,7 @@ describe("UOW Coordinator - Parent-Child Execution", () => {
633
633
  });
634
634
 
635
635
  it("should handle errors thrown by service methods without unhandled rejections", async () => {
636
- const testSchema = schema((s) =>
636
+ const testSchema = schema("test", (s) =>
637
637
  s
638
638
  .addTable("users", (t) =>
639
639
  t
@@ -770,7 +770,7 @@ describe("UOW Coordinator - Parent-Child Execution", () => {
770
770
  });
771
771
 
772
772
  it("should not cause unhandled rejection when service method awaits retrievalPhase and executeRetrieve fails", async () => {
773
- const testSchema = schema((s) =>
773
+ const testSchema = schema("test", (s) =>
774
774
  s.addTable("settings", (t) =>
775
775
  t
776
776
  .addColumn("id", idColumn())
@@ -830,7 +830,7 @@ describe("UOW Coordinator - Parent-Child Execution", () => {
830
830
  });
831
831
 
832
832
  it("should allow child UOW to call getCreatedIds() after parent executes mutations", async () => {
833
- const testSchema = schema((s) =>
833
+ const testSchema = schema("test", (s) =>
834
834
  s.addTable("products", (t) =>
835
835
  t.addColumn("id", idColumn()).addColumn("name", "string").addColumn("price", "integer"),
836
836
  ),
@@ -862,7 +862,7 @@ describe("UOW Coordinator - Parent-Child Execution", () => {
862
862
  });
863
863
 
864
864
  it("should preserve internal IDs in child UOW when using two-phase pattern with mutationPhase await", async () => {
865
- const testSchema = schema((s) =>
865
+ const testSchema = schema("test", (s) =>
866
866
  s.addTable("orders", (t) =>
867
867
  t
868
868
  .addColumn("id", idColumn())
@@ -916,7 +916,7 @@ describe("UOW Coordinator - Parent-Child Execution", () => {
916
916
  });
917
917
 
918
918
  it("should fail when handler executes mutations before service finishes scheduling them (anti-pattern)", async () => {
919
- const testSchema = schema((s) =>
919
+ const testSchema = schema("test", (s) =>
920
920
  s.addTable("totp_secret", (t) =>
921
921
  t
922
922
  .addColumn("id", idColumn())
@@ -997,7 +997,7 @@ describe("UOW Coordinator - Parent-Child Execution", () => {
997
997
  });
998
998
 
999
999
  it("should succeed when handler awaits service promise between phases (correct pattern)", async () => {
1000
- const testSchema = schema((s) =>
1000
+ const testSchema = schema("test", (s) =>
1001
1001
  s.addTable("totp_secret", (t) =>
1002
1002
  t
1003
1003
  .addColumn("id", idColumn())