@fragno-dev/db 0.2.1 → 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 (362) hide show
  1. package/.turbo/turbo-build.log +206 -140
  2. package/CHANGELOG.md +67 -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 +38 -28
  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 +45 -96
  80. package/dist/db-fragment-definition-builder.d.ts.map +1 -1
  81. package/dist/db-fragment-definition-builder.js +121 -99
  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 +172 -9
  92. package/dist/fragments/internal-fragment.d.ts.map +1 -1
  93. package/dist/fragments/internal-fragment.js +193 -74
  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 +47 -4
  106. package/dist/hooks/hooks.d.ts.map +1 -1
  107. package/dist/hooks/hooks.js +106 -39
  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 +17 -10
  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 +351 -100
  165. package/dist/query/unit-of-work/execute-unit-of-work.d.ts.map +1 -1
  166. package/dist/query/unit-of-work/execute-unit-of-work.js +440 -267
  167. package/dist/query/unit-of-work/execute-unit-of-work.js.map +1 -1
  168. package/dist/query/unit-of-work/unit-of-work.d.ts +67 -22
  169. package/dist/query/unit-of-work/unit-of-work.d.ts.map +1 -1
  170. package/dist/query/unit-of-work/unit-of-work.js +110 -13
  171. package/dist/query/unit-of-work/unit-of-work.js.map +1 -1
  172. package/dist/query/value-decoding.js +8 -5
  173. package/dist/query/value-decoding.js.map +1 -1
  174. package/dist/query/value-encoding.js +29 -9
  175. package/dist/query/value-encoding.js.map +1 -1
  176. package/dist/schema/create.d.ts +40 -14
  177. package/dist/schema/create.d.ts.map +1 -1
  178. package/dist/schema/create.js +82 -42
  179. package/dist/schema/create.js.map +1 -1
  180. package/dist/schema/generate-id.d.ts +20 -0
  181. package/dist/schema/generate-id.d.ts.map +1 -0
  182. package/dist/schema/generate-id.js +28 -0
  183. package/dist/schema/generate-id.js.map +1 -0
  184. package/dist/schema/type-conversion/create-sql-type-mapper.js +3 -2
  185. package/dist/schema/type-conversion/create-sql-type-mapper.js.map +1 -1
  186. package/dist/schema/type-conversion/dialect/sqlite.js +9 -0
  187. package/dist/schema/type-conversion/dialect/sqlite.js.map +1 -1
  188. package/dist/schema/validator.d.ts +10 -0
  189. package/dist/schema/validator.d.ts.map +1 -0
  190. package/dist/schema/validator.js +123 -0
  191. package/dist/schema/validator.js.map +1 -0
  192. package/dist/schema-output/drizzle.d.ts +30 -0
  193. package/dist/schema-output/drizzle.d.ts.map +1 -0
  194. package/dist/{adapters/drizzle/generate.js → schema-output/drizzle.js} +82 -56
  195. package/dist/schema-output/drizzle.js.map +1 -0
  196. package/dist/schema-output/prisma.d.ts +17 -0
  197. package/dist/schema-output/prisma.d.ts.map +1 -0
  198. package/dist/schema-output/prisma.js +296 -0
  199. package/dist/schema-output/prisma.js.map +1 -0
  200. package/dist/util/default-database-adapter.js +61 -0
  201. package/dist/util/default-database-adapter.js.map +1 -0
  202. package/dist/with-database.d.ts +1 -1
  203. package/dist/with-database.d.ts.map +1 -1
  204. package/dist/with-database.js +12 -3
  205. package/dist/with-database.js.map +1 -1
  206. package/package.json +43 -28
  207. package/src/adapters/adapters.ts +30 -24
  208. package/src/adapters/drizzle/migrate-drizzle.test.ts +54 -33
  209. package/src/adapters/drizzle/migration-parity-drizzle-kit.test.ts +599 -0
  210. package/src/adapters/drizzle/test-utils.ts +12 -8
  211. package/src/adapters/generic-sql/driver-config.ts +38 -0
  212. package/src/adapters/generic-sql/generic-sql-adapter.test.ts +5 -5
  213. package/src/adapters/generic-sql/generic-sql-adapter.ts +110 -24
  214. package/src/adapters/generic-sql/generic-sql-uow-executor.test.ts +54 -0
  215. package/src/adapters/generic-sql/generic-sql-uow-executor.ts +231 -3
  216. package/src/adapters/generic-sql/migration/adapter-migration-parity.test.ts +118 -0
  217. package/src/adapters/generic-sql/migration/dialect/mysql.test.ts +26 -8
  218. package/src/adapters/generic-sql/migration/dialect/mysql.ts +46 -8
  219. package/src/adapters/generic-sql/migration/dialect/postgres.test.ts +25 -7
  220. package/src/adapters/generic-sql/migration/dialect/postgres.ts +8 -4
  221. package/src/adapters/generic-sql/migration/dialect/sqlite.test.ts +47 -8
  222. package/src/adapters/generic-sql/migration/dialect/sqlite.ts +27 -12
  223. package/src/adapters/generic-sql/migration/prepared-migrations.test.ts +128 -39
  224. package/src/adapters/generic-sql/migration/prepared-migrations.ts +15 -8
  225. package/src/adapters/generic-sql/migration/sql-generator.ts +142 -65
  226. package/src/adapters/generic-sql/query/create-sql-query-compiler.ts +9 -6
  227. package/src/adapters/generic-sql/query/cursor-utils.test.ts +271 -0
  228. package/src/adapters/generic-sql/query/cursor-utils.ts +41 -6
  229. package/src/adapters/generic-sql/query/generic-sql-uow-operation-compiler.test.ts +27 -27
  230. package/src/adapters/generic-sql/query/generic-sql-uow-operation-compiler.ts +38 -24
  231. package/src/adapters/generic-sql/query/select-builder.test.ts +15 -11
  232. package/src/adapters/generic-sql/query/select-builder.ts +6 -2
  233. package/src/adapters/generic-sql/query/sql-query-compiler.test.ts +52 -2
  234. package/src/adapters/generic-sql/query/sql-query-compiler.ts +50 -15
  235. package/src/adapters/generic-sql/query/where-builder.test.ts +91 -17
  236. package/src/adapters/generic-sql/query/where-builder.ts +90 -38
  237. package/src/adapters/{kysely/kysely-adapter-pglite.test.ts → generic-sql/sql-adapter-pglite-migrations.test.ts} +6 -6
  238. package/src/adapters/generic-sql/sql-adapter-pglite-pagination.test.ts +806 -0
  239. package/src/adapters/{drizzle/drizzle-adapter-pglite.test.ts → generic-sql/sql-adapter-pglite-queries.test.ts} +11 -11
  240. package/src/adapters/generic-sql/{test/generic-drizzle-adapter-sqlite3.test.ts → sql-adapter-sqlite3-driver.test.ts} +49 -35
  241. package/src/adapters/{drizzle/drizzle-adapter-sqlite3.test.ts → generic-sql/sql-adapter-sqlite3-uow.test.ts} +48 -32
  242. package/src/adapters/{kysely/kysely-adapter-sqlocal.test.ts → generic-sql/sql-adapter-sqlocal.test.ts} +6 -6
  243. package/src/adapters/generic-sql/sqlite-storage.ts +20 -0
  244. package/src/adapters/generic-sql/uow-decoder.test.ts +1 -1
  245. package/src/adapters/generic-sql/uow-decoder.ts +21 -3
  246. package/src/adapters/generic-sql/uow-encoder.test.ts +33 -2
  247. package/src/adapters/generic-sql/uow-encoder.ts +50 -11
  248. package/src/adapters/in-memory/condition-evaluator.test.ts +193 -0
  249. package/src/adapters/in-memory/condition-evaluator.ts +275 -0
  250. package/src/adapters/in-memory/errors.ts +20 -0
  251. package/src/adapters/in-memory/in-memory-adapter.ts +277 -0
  252. package/src/adapters/in-memory/in-memory-uow.mutations.test.ts +296 -0
  253. package/src/adapters/in-memory/in-memory-uow.retrieval.test.ts +100 -0
  254. package/src/adapters/in-memory/in-memory-uow.ts +1348 -0
  255. package/src/adapters/in-memory/index.ts +3 -0
  256. package/src/adapters/in-memory/options.test.ts +41 -0
  257. package/src/adapters/in-memory/options.ts +87 -0
  258. package/src/adapters/in-memory/reference-resolution.test.ts +50 -0
  259. package/src/adapters/in-memory/reference-resolution.ts +67 -0
  260. package/src/adapters/in-memory/sorted-array-index.test.ts +123 -0
  261. package/src/adapters/in-memory/sorted-array-index.ts +228 -0
  262. package/src/adapters/in-memory/store.test.ts +68 -0
  263. package/src/adapters/in-memory/store.ts +145 -0
  264. package/src/adapters/in-memory/value-comparison.ts +53 -0
  265. package/src/adapters/in-memory/value-normalization.test.ts +57 -0
  266. package/src/adapters/prisma/prisma-adapter-sqlite3.test.ts +1163 -0
  267. package/src/adapters/shared/from-unit-of-work-compiler.ts +3 -1
  268. package/src/adapters/shared/uow-operation-compiler.ts +26 -16
  269. package/src/adapters/sql/index.ts +12 -0
  270. package/src/db-fragment-definition-builder.test.ts +88 -54
  271. package/src/db-fragment-definition-builder.ts +201 -322
  272. package/src/db-fragment-instantiator.test.ts +169 -101
  273. package/src/db-fragment-integration.test.ts +301 -149
  274. package/src/dispatchers/cloudflare-do/index.test.ts +73 -0
  275. package/src/dispatchers/cloudflare-do/index.ts +104 -0
  276. package/src/dispatchers/node/index.test.ts +91 -0
  277. package/src/dispatchers/node/index.ts +87 -0
  278. package/src/fragments/internal-fragment.routes.ts +42 -0
  279. package/src/fragments/internal-fragment.schema.ts +51 -0
  280. package/src/fragments/internal-fragment.test.ts +730 -274
  281. package/src/fragments/internal-fragment.ts +447 -154
  282. package/src/hooks/durable-hooks-processor.test.ts +117 -0
  283. package/src/hooks/durable-hooks-processor.ts +67 -0
  284. package/src/hooks/hooks.test.ts +411 -259
  285. package/src/hooks/hooks.ts +265 -66
  286. package/src/migration-engine/auto-from-schema.test.ts +14 -14
  287. package/src/migration-engine/auto-from-schema.ts +5 -2
  288. package/src/migration-engine/create.test.ts +2 -2
  289. package/src/migration-engine/generation-engine.test.ts +229 -104
  290. package/src/migration-engine/generation-engine.ts +94 -64
  291. package/src/migration-engine/shared.ts +1 -0
  292. package/src/mod.ts +78 -30
  293. package/src/naming/sql-naming.ts +180 -0
  294. package/src/outbox/outbox-builder.ts +241 -0
  295. package/src/outbox/outbox.test.ts +253 -0
  296. package/src/outbox/outbox.ts +137 -0
  297. package/src/query/column-defaults.ts +41 -3
  298. package/src/query/condition-builder.test.ts +3 -3
  299. package/src/query/cursor.test.ts +116 -18
  300. package/src/query/cursor.ts +75 -26
  301. package/src/query/db-now.ts +6 -0
  302. package/src/query/query-type.test.ts +2 -2
  303. package/src/query/serialize/create-sql-serializer.ts +7 -2
  304. package/src/query/serialize/dialect/mysql-serializer.ts +12 -4
  305. package/src/query/serialize/dialect/postgres-serializer.ts +34 -4
  306. package/src/query/serialize/dialect/sqlite-serializer.test.ts +51 -1
  307. package/src/query/serialize/dialect/sqlite-serializer.ts +92 -9
  308. package/src/query/serialize/sql-serializer.ts +4 -4
  309. package/src/query/simple-query-interface.ts +5 -0
  310. package/src/query/unit-of-work/execute-unit-of-work.test.ts +1512 -1458
  311. package/src/query/unit-of-work/execute-unit-of-work.ts +1708 -596
  312. package/src/query/unit-of-work/tx-builder.test.ts +1041 -0
  313. package/src/query/unit-of-work/unit-of-work-coordinator.test.ts +32 -32
  314. package/src/query/unit-of-work/unit-of-work-types.test.ts +1 -1
  315. package/src/query/unit-of-work/unit-of-work.test.ts +231 -36
  316. package/src/query/unit-of-work/unit-of-work.ts +229 -31
  317. package/src/query/value-decoding.test.ts +13 -2
  318. package/src/query/value-decoding.ts +17 -4
  319. package/src/query/value-encoding.test.ts +85 -2
  320. package/src/query/value-encoding.ts +56 -6
  321. package/src/schema/create.test.ts +129 -42
  322. package/src/schema/create.ts +187 -47
  323. package/src/schema/generate-id.test.ts +57 -0
  324. package/src/schema/generate-id.ts +38 -0
  325. package/src/schema/serialize.test.ts +14 -2
  326. package/src/schema/type-conversion/create-sql-type-mapper.ts +7 -2
  327. package/src/schema/type-conversion/dialect/sqlite.ts +18 -0
  328. package/src/schema/type-conversion/type-mapping.test.ts +25 -1
  329. package/src/schema/validator.test.ts +197 -0
  330. package/src/schema/validator.ts +231 -0
  331. package/src/{adapters/drizzle/generate.test.ts → schema-output/drizzle.test.ts} +179 -129
  332. package/src/{adapters/drizzle/generate.ts → schema-output/drizzle.ts} +143 -93
  333. package/src/schema-output/prisma.test.ts +536 -0
  334. package/src/schema-output/prisma.ts +573 -0
  335. package/src/util/default-database-adapter.ts +106 -0
  336. package/src/with-database.ts +22 -3
  337. package/tsdown.config.ts +6 -4
  338. package/dist/adapters/drizzle/drizzle-adapter.d.ts +0 -20
  339. package/dist/adapters/drizzle/drizzle-adapter.d.ts.map +0 -1
  340. package/dist/adapters/drizzle/drizzle-adapter.js +0 -27
  341. package/dist/adapters/drizzle/drizzle-adapter.js.map +0 -1
  342. package/dist/adapters/drizzle/generate.d.ts +0 -30
  343. package/dist/adapters/drizzle/generate.d.ts.map +0 -1
  344. package/dist/adapters/drizzle/generate.js.map +0 -1
  345. package/dist/adapters/kysely/kysely-adapter.d.ts +0 -19
  346. package/dist/adapters/kysely/kysely-adapter.d.ts.map +0 -1
  347. package/dist/adapters/kysely/kysely-adapter.js +0 -17
  348. package/dist/adapters/kysely/kysely-adapter.js.map +0 -1
  349. package/dist/adapters/shared/table-name-mapper.d.ts +0 -12
  350. package/dist/adapters/shared/table-name-mapper.d.ts.map +0 -1
  351. package/dist/adapters/shared/table-name-mapper.js +0 -43
  352. package/dist/adapters/shared/table-name-mapper.js.map +0 -1
  353. package/dist/node_modules/.pnpm/rou3@0.7.10/node_modules/rou3/dist/index.js.map +0 -1
  354. package/dist/schema-generator/schema-generator.d.ts +0 -15
  355. package/dist/schema-generator/schema-generator.d.ts.map +0 -1
  356. package/src/adapters/drizzle/drizzle-adapter.ts +0 -39
  357. package/src/adapters/kysely/kysely-adapter.ts +0 -27
  358. package/src/adapters/shared/table-name-mapper.ts +0 -50
  359. package/src/schema-generator/schema-generator.ts +0 -12
  360. package/src/shared/config.ts +0 -10
  361. package/src/shared/connection-pool.ts +0 -24
  362. package/src/shared/prisma.ts +0 -45
@@ -0,0 +1,1348 @@
1
+ import type { AnySchema, AnyTable } from "../../schema/create";
2
+ import { FragnoId, FragnoReference } from "../../schema/create";
3
+ import { SQLocalDriverConfig } from "../generic-sql/driver-config";
4
+ import type {
5
+ CompiledMutation,
6
+ MutationOperation,
7
+ MutationResult,
8
+ RetrievalOperation,
9
+ UOWCompiler,
10
+ UOWDecoder,
11
+ UOWExecutor,
12
+ } from "../../query/unit-of-work/unit-of-work";
13
+ import { buildCondition } from "../../query/condition-builder";
14
+ import {
15
+ encodeValues,
16
+ encodeValuesWithDbDefaults,
17
+ ReferenceSubquery,
18
+ } from "../../query/value-encoding";
19
+ import { isDbNow } from "../../query/db-now";
20
+ import { createSQLSerializer } from "../../query/serialize/create-sql-serializer";
21
+ import type { CompiledJoin } from "../../query/orm/orm";
22
+ import {
23
+ createCursorFromRecord,
24
+ decodeCursor,
25
+ type Cursor,
26
+ type CursorResult,
27
+ } from "../../query/cursor";
28
+ import type {
29
+ InMemoryNamespaceStore,
30
+ InMemoryRow,
31
+ InMemoryStore,
32
+ InMemoryTableStore,
33
+ } from "./store";
34
+ import { buildIndexKey, ensureNamespaceStore, normalizeIndexValue } from "./store";
35
+ import { evaluateCondition } from "./condition-evaluator";
36
+ import { resolveReferenceSubqueries } from "./reference-resolution";
37
+ import type { ResolvedInMemoryAdapterOptions } from "./options";
38
+ import { compareNormalizedValues } from "./value-comparison";
39
+ import type { NamingResolver } from "../../naming/sql-naming";
40
+
41
+ type InMemoryCompiledQuery = RetrievalOperation<AnySchema> | MutationOperation<AnySchema>;
42
+ type InMemoryRawResult = InMemoryRow[] | { count: number }[];
43
+ type CursorInput = string | Cursor | undefined;
44
+ type ResolverFactory = (schema: AnySchema, namespace: string | null) => NamingResolver;
45
+
46
+ const getResolver = (
47
+ schema: AnySchema,
48
+ namespace: string | null | undefined,
49
+ resolverFactory?: ResolverFactory,
50
+ ) => (resolverFactory ? resolverFactory(schema, namespace ?? null) : undefined);
51
+
52
+ const getPhysicalTableName = (table: AnyTable, resolver?: NamingResolver) =>
53
+ resolver ? resolver.getTableName(table.name) : table.name;
54
+
55
+ const getPhysicalColumnName = (table: AnyTable, columnName: string, resolver?: NamingResolver) =>
56
+ resolver ? resolver.getColumnName(table.name, columnName) : columnName;
57
+
58
+ const cursorSerializer = createSQLSerializer(new SQLocalDriverConfig());
59
+
60
+ class VersionConflictError extends Error {
61
+ constructor(message: string) {
62
+ super(message);
63
+ this.name = "VersionConflictError";
64
+ }
65
+ }
66
+
67
+ const getNamespaceStore = (
68
+ store: InMemoryStore,
69
+ schema: AnySchema,
70
+ namespace: string | null | undefined,
71
+ resolver?: NamingResolver,
72
+ ): InMemoryNamespaceStore => {
73
+ const namespaceKey = namespace ?? schema.name;
74
+ return ensureNamespaceStore(store, namespaceKey, schema, resolver);
75
+ };
76
+
77
+ const getTableStore = (
78
+ namespaceStore: InMemoryNamespaceStore,
79
+ table: AnyTable,
80
+ resolver?: NamingResolver,
81
+ ): InMemoryTableStore => {
82
+ const physicalTableName = getPhysicalTableName(table, resolver);
83
+ const tableStore = namespaceStore.tables.get(physicalTableName);
84
+ if (!tableStore) {
85
+ throw new Error(`Missing in-memory table store for "${physicalTableName}".`);
86
+ }
87
+ return tableStore;
88
+ };
89
+
90
+ const buildSelection = (
91
+ table: AnyTable,
92
+ select: undefined | true | readonly string[],
93
+ _resolver?: NamingResolver,
94
+ ): Set<string> => {
95
+ const selection = new Set<string>();
96
+
97
+ if (!select || select === true) {
98
+ for (const columnName of Object.keys(table.columns)) {
99
+ selection.add(columnName);
100
+ }
101
+ } else {
102
+ for (const columnName of select) {
103
+ selection.add(columnName);
104
+ }
105
+ }
106
+
107
+ selection.add("_internalId");
108
+ selection.add("_version");
109
+
110
+ return selection;
111
+ };
112
+
113
+ const selectRow = (
114
+ row: InMemoryRow,
115
+ table: AnyTable,
116
+ select: undefined | true | readonly string[],
117
+ resolver?: NamingResolver,
118
+ ): InMemoryRow => {
119
+ const selection = buildSelection(table, select, resolver);
120
+ const selected: InMemoryRow = {};
121
+ for (const columnName of selection) {
122
+ const physicalColumnName = getPhysicalColumnName(table, columnName, resolver);
123
+ if (Object.prototype.hasOwnProperty.call(row, physicalColumnName)) {
124
+ selected[columnName] = row[physicalColumnName];
125
+ }
126
+ }
127
+ return selected;
128
+ };
129
+
130
+ const isNullish = (value: unknown): value is null | undefined =>
131
+ value === null || value === undefined;
132
+
133
+ const prefixSelection = (
134
+ row: InMemoryRow,
135
+ table: AnyTable,
136
+ select: undefined | true | readonly string[],
137
+ prefix: string,
138
+ resolver?: NamingResolver,
139
+ ): InMemoryRow => {
140
+ const selected = selectRow(row, table, select, resolver);
141
+ const prefixed: InMemoryRow = {};
142
+
143
+ for (const key in selected) {
144
+ prefixed[`${prefix}:${key}`] = selected[key];
145
+ }
146
+
147
+ return prefixed;
148
+ };
149
+
150
+ const orderRows = (
151
+ rows: InMemoryRow[],
152
+ orderBy: [AnyTable["columns"][string], "asc" | "desc"][] | undefined,
153
+ resolver?: NamingResolver,
154
+ ): InMemoryRow[] => {
155
+ if (!orderBy || orderBy.length === 0) {
156
+ return rows;
157
+ }
158
+
159
+ return rows.slice().sort((left, right) => {
160
+ for (const [column, direction] of orderBy) {
161
+ const columnName = resolver
162
+ ? resolver.getColumnName(column.tableName, column.name)
163
+ : column.name;
164
+ const leftValue = normalizeIndexValue(left[columnName], column);
165
+ const rightValue = normalizeIndexValue(right[columnName], column);
166
+ const comparison = compareNormalizedValues(leftValue, rightValue);
167
+ if (comparison !== 0) {
168
+ return direction === "asc" ? comparison : -comparison;
169
+ }
170
+ }
171
+ return 0;
172
+ });
173
+ };
174
+
175
+ const assertOrderByIndexOnly = (
176
+ table: AnyTable,
177
+ orderBy: [AnyTable["columns"][string], "asc" | "desc"][] | undefined,
178
+ resolver?: NamingResolver,
179
+ ): void => {
180
+ if (!orderBy || orderBy.length === 0) {
181
+ return;
182
+ }
183
+
184
+ const direction = orderBy[0][1];
185
+ if (!orderBy.every(([, dir]) => dir === direction)) {
186
+ throw new Error(
187
+ `In-memory adapter only supports orderByIndex; mixed orderBy directions found on table "${table.name}".`,
188
+ );
189
+ }
190
+
191
+ const orderColumnNames = orderBy.map(([column]) =>
192
+ resolver ? resolver.getColumnName(table.name, column.name) : column.name,
193
+ );
194
+ const idColumnName = resolver
195
+ ? resolver.getColumnName(table.name, table.getIdColumn().name)
196
+ : table.getIdColumn().name;
197
+ if (orderColumnNames.length === 1 && orderColumnNames[0] === idColumnName) {
198
+ return;
199
+ }
200
+
201
+ for (const index of Object.values(table.indexes)) {
202
+ const indexColumnNames = (index.columnNames as readonly string[]).map((columnName) =>
203
+ resolver ? resolver.getColumnName(table.name, columnName) : columnName,
204
+ );
205
+ if (indexColumnNames.length !== orderColumnNames.length) {
206
+ continue;
207
+ }
208
+ let matches = true;
209
+ for (let i = 0; i < indexColumnNames.length; i += 1) {
210
+ if (indexColumnNames[i] !== orderColumnNames[i]) {
211
+ matches = false;
212
+ break;
213
+ }
214
+ }
215
+ if (matches) {
216
+ return;
217
+ }
218
+ }
219
+
220
+ throw new Error(
221
+ `In-memory adapter only supports orderByIndex; received orderBy on table "${table.name}".`,
222
+ );
223
+ };
224
+
225
+ const findJoinMatches = (
226
+ parentRow: InMemoryRow,
227
+ parentTable: AnyTable,
228
+ join: CompiledJoin,
229
+ namespaceStore: InMemoryNamespaceStore,
230
+ resolver?: NamingResolver,
231
+ now: () => Date = () => new Date(),
232
+ ): InMemoryRow[] => {
233
+ const { relation, options } = join;
234
+ if (options === false) {
235
+ return [];
236
+ }
237
+
238
+ const targetTable = relation.table;
239
+ const targetStore = getTableStore(namespaceStore, targetTable, resolver);
240
+ const matches: InMemoryRow[] = [];
241
+
242
+ assertOrderByIndexOnly(targetTable, options.orderBy, resolver);
243
+
244
+ for (const row of targetStore.rows.values()) {
245
+ let matchesJoin = true;
246
+
247
+ for (const [left, right] of relation.on) {
248
+ const leftColumn = parentTable.columns[left];
249
+ if (!leftColumn) {
250
+ throw new Error(`Column "${left}" not found on table "${parentTable.name}".`);
251
+ }
252
+
253
+ const rightColumn = targetTable.columns[right];
254
+ if (!rightColumn) {
255
+ throw new Error(`Column "${right}" not found on table "${targetTable.name}".`);
256
+ }
257
+
258
+ const actualRight = rightColumn.role === "external-id" ? "_internalId" : right;
259
+ const actualRightColumn = targetTable.columns[actualRight];
260
+ if (!actualRightColumn) {
261
+ throw new Error(`Column "${actualRight}" not found on table "${targetTable.name}".`);
262
+ }
263
+
264
+ const leftValue = parentRow[getPhysicalColumnName(parentTable, left, resolver)];
265
+ const rightValue = row[getPhysicalColumnName(targetTable, actualRight, resolver)];
266
+ if (isNullish(leftValue) || isNullish(rightValue)) {
267
+ matchesJoin = false;
268
+ break;
269
+ }
270
+
271
+ const leftNormalized = normalizeIndexValue(leftValue, leftColumn);
272
+ const rightNormalized = normalizeIndexValue(rightValue, actualRightColumn);
273
+ if (compareNormalizedValues(leftNormalized, rightNormalized) !== 0) {
274
+ matchesJoin = false;
275
+ break;
276
+ }
277
+ }
278
+
279
+ if (!matchesJoin) {
280
+ continue;
281
+ }
282
+
283
+ if (
284
+ options.where &&
285
+ !evaluateCondition(options.where, targetTable, row, namespaceStore, resolver, now)
286
+ ) {
287
+ continue;
288
+ }
289
+
290
+ matches.push(row);
291
+ }
292
+
293
+ const ordered = orderRows(matches, options.orderBy, resolver);
294
+ if (options.limit !== undefined) {
295
+ return ordered.slice(0, Math.max(0, options.limit));
296
+ }
297
+
298
+ return ordered;
299
+ };
300
+
301
+ const applyJoins = (
302
+ baseOutput: InMemoryRow,
303
+ parentRow: InMemoryRow,
304
+ parentTable: AnyTable,
305
+ joins: CompiledJoin[] | undefined,
306
+ namespaceStore: InMemoryNamespaceStore,
307
+ resolver?: NamingResolver,
308
+ now: () => Date = () => new Date(),
309
+ parentPath = "",
310
+ ): InMemoryRow[] => {
311
+ if (!joins || joins.length === 0) {
312
+ return [baseOutput];
313
+ }
314
+
315
+ let outputs: InMemoryRow[] = [baseOutput];
316
+
317
+ for (const join of joins) {
318
+ if (join.options === false) {
319
+ continue;
320
+ }
321
+
322
+ const relationPath = parentPath ? `${parentPath}:${join.relation.name}` : join.relation.name;
323
+ const nextOutputs: InMemoryRow[] = [];
324
+
325
+ for (const currentOutput of outputs) {
326
+ const matches = findJoinMatches(parentRow, parentTable, join, namespaceStore, resolver, now);
327
+
328
+ if (matches.length === 0) {
329
+ nextOutputs.push(currentOutput);
330
+ continue;
331
+ }
332
+
333
+ for (const matchRow of matches) {
334
+ const prefixed = prefixSelection(
335
+ matchRow,
336
+ join.relation.table,
337
+ join.options.select,
338
+ relationPath,
339
+ resolver,
340
+ );
341
+ const merged = { ...currentOutput, ...prefixed };
342
+
343
+ if (join.options.join && join.options.join.length > 0) {
344
+ nextOutputs.push(
345
+ ...applyJoins(
346
+ merged,
347
+ matchRow,
348
+ join.relation.table,
349
+ join.options.join,
350
+ namespaceStore,
351
+ resolver,
352
+ now,
353
+ relationPath,
354
+ ),
355
+ );
356
+ } else {
357
+ nextOutputs.push(merged);
358
+ }
359
+ }
360
+ }
361
+
362
+ outputs = nextOutputs;
363
+ }
364
+
365
+ return outputs;
366
+ };
367
+
368
+ const getExternalId = (id: FragnoId | string): string =>
369
+ typeof id === "string" ? id : id.externalId;
370
+
371
+ const getVersionToCheck = (id: FragnoId | string, checkVersion: boolean): number | undefined => {
372
+ if (!checkVersion) {
373
+ return undefined;
374
+ }
375
+
376
+ if (typeof id === "string") {
377
+ throw new Error(
378
+ "Cannot use checkVersion with a string ID. Version checking requires a FragnoId.",
379
+ );
380
+ }
381
+
382
+ return id.version;
383
+ };
384
+
385
+ const resolveReferenceSubqueriesOrThrow = (
386
+ namespaceStore: InMemoryNamespaceStore,
387
+ table: AnyTable,
388
+ encodedValues: Record<string, unknown>,
389
+ resolver?: NamingResolver,
390
+ ): Record<string, unknown> => {
391
+ const resolved = resolveReferenceSubqueries(namespaceStore, encodedValues, resolver);
392
+ for (const [key, value] of Object.entries(encodedValues)) {
393
+ if (!(value instanceof ReferenceSubquery)) {
394
+ continue;
395
+ }
396
+
397
+ if (resolved[key] === null || resolved[key] === undefined) {
398
+ throw new Error(
399
+ `Foreign key constraint violation on table "${table.name}" for column "${key}".`,
400
+ );
401
+ }
402
+ }
403
+ return resolved;
404
+ };
405
+
406
+ const getReferencedColumn = (
407
+ table: AnyTable,
408
+ columnName: string,
409
+ resolver?: NamingResolver,
410
+ ): { name: string; column: AnyTable["columns"][string] } => {
411
+ const column = table.columns[columnName];
412
+ if (!column) {
413
+ throw new Error(`Column "${columnName}" not found on table "${table.name}".`);
414
+ }
415
+
416
+ const actualName = column.role === "external-id" ? "_internalId" : columnName;
417
+ const actualColumn = table.columns[actualName];
418
+ if (!actualColumn) {
419
+ throw new Error(`Column "${actualName}" not found on table "${table.name}".`);
420
+ }
421
+
422
+ return {
423
+ name: getPhysicalColumnName(table, actualName, resolver),
424
+ column: actualColumn,
425
+ };
426
+ };
427
+
428
+ const enforceOutgoingForeignKeys = (
429
+ namespaceStore: InMemoryNamespaceStore,
430
+ table: AnyTable,
431
+ row: InMemoryRow,
432
+ columnsToCheck?: Set<string>,
433
+ resolver?: NamingResolver,
434
+ ): void => {
435
+ for (const relation of Object.values(table.relations)) {
436
+ if (relation.type !== "one") {
437
+ continue;
438
+ }
439
+
440
+ const localColumnNames = relation.on.map(([local]) => local);
441
+ if (columnsToCheck && !localColumnNames.some((name) => columnsToCheck.has(name))) {
442
+ continue;
443
+ }
444
+
445
+ const localValues = localColumnNames.map(
446
+ (name) => row[getPhysicalColumnName(table, name, resolver)],
447
+ );
448
+ if (localValues.some((value) => value === null || value === undefined)) {
449
+ continue;
450
+ }
451
+
452
+ const referencedTable = relation.table;
453
+ const referencedStore = getTableStore(namespaceStore, referencedTable, resolver);
454
+ let foundMatch = false;
455
+
456
+ for (const targetRow of referencedStore.rows.values()) {
457
+ let matches = true;
458
+ for (const [localName, foreignName] of relation.on) {
459
+ if (!table.columns[localName]) {
460
+ throw new Error(`Column "${localName}" not found on table "${table.name}".`);
461
+ }
462
+
463
+ const { name: actualForeignName, column: foreignColumn } = getReferencedColumn(
464
+ referencedTable,
465
+ foreignName,
466
+ resolver,
467
+ );
468
+ const localValue = row[getPhysicalColumnName(table, localName, resolver)];
469
+ const targetValue = targetRow[actualForeignName];
470
+
471
+ const normalizedLocal = normalizeIndexValue(localValue, foreignColumn);
472
+ const normalizedTarget = normalizeIndexValue(targetValue, foreignColumn);
473
+
474
+ if (compareNormalizedValues(normalizedLocal, normalizedTarget) !== 0) {
475
+ matches = false;
476
+ break;
477
+ }
478
+ }
479
+
480
+ if (matches) {
481
+ foundMatch = true;
482
+ break;
483
+ }
484
+ }
485
+
486
+ if (!foundMatch) {
487
+ throw new Error(
488
+ `Foreign key constraint violation on table "${table.name}" for relation "${relation.name}".`,
489
+ );
490
+ }
491
+ }
492
+ };
493
+
494
+ const enforceNoIncomingForeignKeys = (
495
+ namespaceStore: InMemoryNamespaceStore,
496
+ schema: AnySchema,
497
+ table: AnyTable,
498
+ row: InMemoryRow,
499
+ resolver?: NamingResolver,
500
+ ): void => {
501
+ for (const sourceTable of Object.values(schema.tables)) {
502
+ for (const relation of Object.values(sourceTable.relations)) {
503
+ if (relation.type !== "one") {
504
+ continue;
505
+ }
506
+
507
+ if (relation.table.name !== table.name) {
508
+ continue;
509
+ }
510
+
511
+ const sourceStore = getTableStore(namespaceStore, sourceTable, resolver);
512
+ const targetColumnInfo = relation.on.map(([, foreignName]) =>
513
+ getReferencedColumn(table, foreignName, resolver),
514
+ );
515
+ const targetValues = targetColumnInfo.map(({ name }) => row[name]);
516
+ if (targetValues.some((value) => value === null || value === undefined)) {
517
+ continue;
518
+ }
519
+
520
+ for (const sourceRow of sourceStore.rows.values()) {
521
+ let matches = true;
522
+ for (let i = 0; i < relation.on.length; i += 1) {
523
+ const [localName] = relation.on[i]!;
524
+ const { column } = targetColumnInfo[i]!;
525
+ const localValue = sourceRow[getPhysicalColumnName(sourceTable, localName, resolver)];
526
+ if (localValue === null || localValue === undefined) {
527
+ matches = false;
528
+ break;
529
+ }
530
+
531
+ const normalizedLocal = normalizeIndexValue(localValue, column);
532
+ const normalizedTarget = normalizeIndexValue(targetValues[i], column);
533
+ if (compareNormalizedValues(normalizedLocal, normalizedTarget) !== 0) {
534
+ matches = false;
535
+ break;
536
+ }
537
+ }
538
+
539
+ if (matches) {
540
+ throw new Error(
541
+ `Foreign key constraint violation on table "${table.name}" for relation "${relation.name}".`,
542
+ );
543
+ }
544
+ }
545
+ }
546
+ }
547
+ };
548
+
549
+ const findRowByExternalId = (
550
+ tableStore: InMemoryTableStore,
551
+ table: AnyTable,
552
+ externalId: string,
553
+ resolver?: NamingResolver,
554
+ ): { internalId: bigint; row: InMemoryRow } | undefined => {
555
+ const idColumn = table.getIdColumn();
556
+ const idColumnName = getPhysicalColumnName(table, idColumn.name, resolver);
557
+ for (const [internalId, row] of tableStore.rows) {
558
+ if (row[idColumnName] === externalId) {
559
+ return { internalId, row };
560
+ }
561
+ }
562
+ return undefined;
563
+ };
564
+
565
+ const resolveCursorValue = (value: unknown, column: AnyTable["columns"][string]): unknown => {
566
+ if (value && typeof value === "object") {
567
+ const maybeExternalId = (value as { externalId?: unknown }).externalId;
568
+ const maybeInternalId = (value as { internalId?: unknown }).internalId;
569
+
570
+ if (typeof maybeExternalId === "string") {
571
+ if (column.role === "external-id") {
572
+ return maybeExternalId;
573
+ }
574
+ if ((column.role === "internal-id" || column.role === "reference") && maybeInternalId) {
575
+ return typeof maybeInternalId === "string" ? BigInt(maybeInternalId) : maybeInternalId;
576
+ }
577
+ }
578
+ }
579
+
580
+ return value;
581
+ };
582
+
583
+ const buildCursorKey = (
584
+ cursor: CursorInput,
585
+ table: AnyTable,
586
+ columnNames: readonly string[],
587
+ resolver?: NamingResolver,
588
+ ): readonly unknown[] | undefined => {
589
+ if (!cursor) {
590
+ return undefined;
591
+ }
592
+
593
+ const cursorObj = typeof cursor === "string" ? decodeCursor(cursor) : cursor;
594
+
595
+ const columnMap = resolver ? resolver.getColumnNameMap(table) : undefined;
596
+
597
+ return columnNames.map((columnName) => {
598
+ const logicalName = columnMap?.[columnName] ?? columnName;
599
+ const column = table.columns[logicalName];
600
+ if (!column) {
601
+ throw new Error(`Column "${logicalName}" not found on table "${table.name}".`);
602
+ }
603
+
604
+ const rawValue = resolveCursorValue(cursorObj.indexValues[column.name], column);
605
+ if (rawValue === undefined) {
606
+ return undefined;
607
+ }
608
+
609
+ const deserialized = cursorSerializer.deserialize(rawValue, column);
610
+ return normalizeIndexValue(deserialized, column);
611
+ });
612
+ };
613
+
614
+ const findRows = (
615
+ op: Extract<RetrievalOperation<AnySchema>, { type: "find" }>,
616
+ namespaceStore: InMemoryNamespaceStore,
617
+ tableStore: InMemoryTableStore,
618
+ resolver?: NamingResolver,
619
+ now: () => Date = () => new Date(),
620
+ ): InMemoryRow[] => {
621
+ const table = op.table;
622
+ const orderByIndex = op.options.orderByIndex;
623
+ const orderIndexName = orderByIndex?.indexName ?? op.indexName;
624
+ const orderIndex = tableStore.indexes.get(orderIndexName);
625
+ if (!orderIndex) {
626
+ throw new Error(`Missing in-memory index "${orderIndexName}" on table "${table.name}".`);
627
+ }
628
+ const direction = orderByIndex?.direction ?? "asc";
629
+ const afterKey = buildCursorKey(
630
+ op.options.after,
631
+ table,
632
+ orderIndex.definition.columnNames,
633
+ resolver,
634
+ );
635
+ const beforeKey = buildCursorKey(
636
+ op.options.before,
637
+ table,
638
+ orderIndex.definition.columnNames,
639
+ resolver,
640
+ );
641
+ const limit =
642
+ op.withCursor && op.options.pageSize !== undefined
643
+ ? op.options.pageSize + 1
644
+ : op.options.pageSize;
645
+
646
+ const scanOptions = {
647
+ direction,
648
+ limit,
649
+ start: undefined as readonly unknown[] | undefined,
650
+ startInclusive: true,
651
+ end: undefined as readonly unknown[] | undefined,
652
+ endInclusive: true,
653
+ };
654
+
655
+ if (afterKey) {
656
+ if (direction === "asc") {
657
+ scanOptions.start = afterKey;
658
+ scanOptions.startInclusive = false;
659
+ } else {
660
+ scanOptions.end = afterKey;
661
+ scanOptions.endInclusive = false;
662
+ }
663
+ }
664
+
665
+ if (beforeKey) {
666
+ if (direction === "asc") {
667
+ scanOptions.end = beforeKey;
668
+ scanOptions.endInclusive = false;
669
+ } else {
670
+ scanOptions.start = beforeKey;
671
+ scanOptions.startInclusive = false;
672
+ }
673
+ }
674
+
675
+ const entries = orderIndex.index.scan(scanOptions);
676
+
677
+ const whereResult = op.options.where
678
+ ? buildCondition(table.columns, op.options.where)
679
+ : undefined;
680
+
681
+ if (whereResult === false) {
682
+ return [];
683
+ }
684
+
685
+ const condition = whereResult === true ? undefined : whereResult;
686
+ const results: InMemoryRow[] = [];
687
+
688
+ for (const entry of entries) {
689
+ const row = tableStore.rows.get(entry.value);
690
+ if (!row) {
691
+ continue;
692
+ }
693
+ if (condition && !evaluateCondition(condition, table, row, namespaceStore, resolver, now)) {
694
+ continue;
695
+ }
696
+
697
+ const baseOutput = selectRow(
698
+ row,
699
+ table,
700
+ op.options.select as readonly string[] | true | undefined,
701
+ resolver,
702
+ );
703
+
704
+ if (op.options.joins && op.options.joins.length > 0) {
705
+ const joined = applyJoins(
706
+ baseOutput,
707
+ row,
708
+ table,
709
+ op.options.joins,
710
+ namespaceStore,
711
+ resolver,
712
+ now,
713
+ );
714
+ for (const joinedRow of joined) {
715
+ results.push(joinedRow);
716
+ if (limit !== undefined && results.length >= limit) {
717
+ break;
718
+ }
719
+ }
720
+ } else {
721
+ results.push(baseOutput);
722
+ }
723
+
724
+ if (limit !== undefined && results.length >= limit) {
725
+ break;
726
+ }
727
+ }
728
+
729
+ return results;
730
+ };
731
+
732
+ const countRows = (
733
+ op: Extract<RetrievalOperation<AnySchema>, { type: "count" }>,
734
+ namespaceStore: InMemoryNamespaceStore,
735
+ tableStore: InMemoryTableStore,
736
+ resolver?: NamingResolver,
737
+ now: () => Date = () => new Date(),
738
+ ): number => {
739
+ const table = op.table;
740
+ const whereResult = op.options.where
741
+ ? buildCondition(table.columns, op.options.where)
742
+ : undefined;
743
+
744
+ if (whereResult === false) {
745
+ return 0;
746
+ }
747
+
748
+ const condition = whereResult === true ? undefined : whereResult;
749
+ let count = 0;
750
+
751
+ for (const row of tableStore.rows.values()) {
752
+ if (condition && !evaluateCondition(condition, table, row, namespaceStore, resolver, now)) {
753
+ continue;
754
+ }
755
+ count += 1;
756
+ }
757
+
758
+ return count;
759
+ };
760
+
761
+ const resolveDbNowValue = (value: unknown, options: ResolvedInMemoryAdapterOptions): unknown =>
762
+ isDbNow(value) ? options.clock.now() : value;
763
+
764
+ const createRow = (
765
+ op: Extract<MutationOperation<AnySchema>, { type: "create" }>,
766
+ namespaceStore: InMemoryNamespaceStore,
767
+ tableStore: InMemoryTableStore,
768
+ options: ResolvedInMemoryAdapterOptions,
769
+ resolver?: NamingResolver,
770
+ ): bigint => {
771
+ const table = op.schema.tables[op.table];
772
+ if (!table) {
773
+ throw new Error(`Invalid table name ${op.table}.`);
774
+ }
775
+
776
+ const encoded = encodeValuesWithDbDefaults(
777
+ op.values,
778
+ table,
779
+ {
780
+ now: options.clock.now,
781
+ createId: options.idGenerator,
782
+ },
783
+ resolver,
784
+ );
785
+ const resolvedValues = options.enforceConstraints
786
+ ? resolveReferenceSubqueriesOrThrow(namespaceStore, table, encoded, resolver)
787
+ : resolveReferenceSubqueries(namespaceStore, encoded, resolver);
788
+
789
+ const row: InMemoryRow = {};
790
+ for (const columnName of Object.keys(table.columns)) {
791
+ const column = table.columns[columnName];
792
+ if (!column || column.role === "internal-id") {
793
+ continue;
794
+ }
795
+
796
+ const physicalColumnName = getPhysicalColumnName(table, column.name, resolver);
797
+ const value = resolvedValues[physicalColumnName];
798
+ if (value === undefined) {
799
+ if (column.isNullable) {
800
+ row[physicalColumnName] = null;
801
+ continue;
802
+ }
803
+
804
+ if (column.role === "version") {
805
+ row[physicalColumnName] = 0;
806
+ continue;
807
+ }
808
+
809
+ throw new Error(`Missing required value for column "${column.name}".`);
810
+ }
811
+
812
+ row[physicalColumnName] = resolveDbNowValue(value, options);
813
+ }
814
+
815
+ const internalId = options.internalIdGeneratorProvided
816
+ ? options.internalIdGenerator()
817
+ : tableStore.nextInternalId;
818
+ if (!options.internalIdGeneratorProvided) {
819
+ tableStore.nextInternalId += 1n;
820
+ }
821
+
822
+ const internalIdColumnName = getPhysicalColumnName(table, "_internalId", resolver);
823
+ const versionColumnName = getPhysicalColumnName(table, "_version", resolver);
824
+ row[internalIdColumnName] = internalId;
825
+ row[versionColumnName] = row[versionColumnName] ?? 0;
826
+
827
+ if (options.enforceConstraints) {
828
+ enforceOutgoingForeignKeys(namespaceStore, table, row, undefined, resolver);
829
+ }
830
+
831
+ tableStore.rows.set(internalId, row);
832
+
833
+ for (const indexStore of tableStore.indexes.values()) {
834
+ const key = buildIndexKey(table, indexStore.definition, row, resolver);
835
+ indexStore.index.insert(key, internalId, { enforceUnique: options.enforceConstraints });
836
+ }
837
+
838
+ return internalId;
839
+ };
840
+
841
+ const updateRow = (
842
+ op: Extract<MutationOperation<AnySchema>, { type: "update" }>,
843
+ namespaceStore: InMemoryNamespaceStore,
844
+ tableStore: InMemoryTableStore,
845
+ options: ResolvedInMemoryAdapterOptions,
846
+ resolver?: NamingResolver,
847
+ ): (() => void) | null => {
848
+ const table = op.schema.tables[op.table];
849
+ if (!table) {
850
+ throw new Error(`Invalid table name ${op.table}.`);
851
+ }
852
+
853
+ const externalId = getExternalId(op.id);
854
+ const versionToCheck = getVersionToCheck(op.id, op.checkVersion);
855
+ const existing = findRowByExternalId(tableStore, table, externalId, resolver);
856
+ if (!existing) {
857
+ if (versionToCheck !== undefined) {
858
+ throw new VersionConflictError(`Version conflict: row "${externalId}" not found.`);
859
+ }
860
+ return null;
861
+ }
862
+
863
+ const versionColumnName = getPhysicalColumnName(table, "_version", resolver);
864
+ const currentVersion = Number(existing.row[versionColumnName] ?? 0);
865
+ if (versionToCheck !== undefined && currentVersion !== versionToCheck) {
866
+ throw new VersionConflictError(`Version conflict: row "${externalId}" has changed.`);
867
+ }
868
+
869
+ const encoded = encodeValues(op.set as Record<string, unknown>, table, false, {}, resolver);
870
+ const resolvedValues = options.enforceConstraints
871
+ ? resolveReferenceSubqueriesOrThrow(namespaceStore, table, encoded, resolver)
872
+ : resolveReferenceSubqueries(namespaceStore, encoded, resolver);
873
+
874
+ const resolvedUpdateValues: InMemoryRow = {};
875
+ for (const [columnName, value] of Object.entries(resolvedValues)) {
876
+ resolvedUpdateValues[columnName] = resolveDbNowValue(value, options);
877
+ }
878
+
879
+ const updatedRow: InMemoryRow = { ...existing.row, ...resolvedUpdateValues };
880
+ updatedRow[versionColumnName] = currentVersion + 1;
881
+
882
+ if (options.enforceConstraints) {
883
+ enforceOutgoingForeignKeys(
884
+ namespaceStore,
885
+ table,
886
+ updatedRow,
887
+ new Set(Object.keys(resolvedValues)),
888
+ resolver,
889
+ );
890
+ }
891
+
892
+ const indexUpdates = Array.from(tableStore.indexes.values()).map((indexStore) => ({
893
+ indexStore,
894
+ oldKey: buildIndexKey(table, indexStore.definition, existing.row, resolver),
895
+ newKey: buildIndexKey(table, indexStore.definition, updatedRow, resolver),
896
+ }));
897
+
898
+ const applied: typeof indexUpdates = [];
899
+ try {
900
+ for (const update of indexUpdates) {
901
+ update.indexStore.index.update(update.oldKey, update.newKey, existing.internalId, {
902
+ enforceUnique: options.enforceConstraints,
903
+ });
904
+ applied.push(update);
905
+ }
906
+ } catch (error) {
907
+ for (const update of applied.reverse()) {
908
+ update.indexStore.index.update(update.newKey, update.oldKey, existing.internalId, {
909
+ enforceUnique: options.enforceConstraints,
910
+ });
911
+ }
912
+ throw error;
913
+ }
914
+
915
+ tableStore.rows.set(existing.internalId, updatedRow);
916
+
917
+ return () => {
918
+ for (const update of indexUpdates.slice().reverse()) {
919
+ update.indexStore.index.update(update.newKey, update.oldKey, existing.internalId, {
920
+ enforceUnique: options.enforceConstraints,
921
+ });
922
+ }
923
+ tableStore.rows.set(existing.internalId, existing.row);
924
+ };
925
+ };
926
+
927
+ const deleteRow = (
928
+ op: Extract<MutationOperation<AnySchema>, { type: "delete" }>,
929
+ namespaceStore: InMemoryNamespaceStore,
930
+ tableStore: InMemoryTableStore,
931
+ table: AnyTable,
932
+ options: ResolvedInMemoryAdapterOptions,
933
+ resolver?: NamingResolver,
934
+ ): (() => void) | null => {
935
+ const externalId = getExternalId(op.id);
936
+ const versionToCheck = getVersionToCheck(op.id, op.checkVersion);
937
+ const existing = findRowByExternalId(tableStore, table, externalId, resolver);
938
+ if (!existing) {
939
+ if (versionToCheck !== undefined) {
940
+ throw new VersionConflictError(`Version conflict: row "${externalId}" not found.`);
941
+ }
942
+ return null;
943
+ }
944
+
945
+ const versionColumnName = getPhysicalColumnName(table, "_version", resolver);
946
+ const currentVersion = Number(existing.row[versionColumnName] ?? 0);
947
+ if (versionToCheck !== undefined && currentVersion !== versionToCheck) {
948
+ throw new VersionConflictError(`Version conflict: row "${externalId}" has changed.`);
949
+ }
950
+
951
+ if (options.enforceConstraints) {
952
+ enforceNoIncomingForeignKeys(namespaceStore, op.schema, table, existing.row, resolver);
953
+ }
954
+
955
+ const indexEntries = Array.from(tableStore.indexes.values()).map((indexStore) => ({
956
+ indexStore,
957
+ key: buildIndexKey(table, indexStore.definition, existing.row, resolver),
958
+ }));
959
+
960
+ const removedEntries: typeof indexEntries = [];
961
+ for (const entry of indexEntries) {
962
+ const removed = entry.indexStore.index.remove(entry.key, existing.internalId);
963
+ if (!removed) {
964
+ for (const removedEntry of removedEntries) {
965
+ removedEntry.indexStore.index.insert(removedEntry.key, existing.internalId, {
966
+ enforceUnique: options.enforceConstraints,
967
+ });
968
+ }
969
+ throw new Error("Failed to remove index entry during delete.");
970
+ }
971
+ removedEntries.push(entry);
972
+ }
973
+
974
+ tableStore.rows.delete(existing.internalId);
975
+
976
+ return () => {
977
+ tableStore.rows.set(existing.internalId, existing.row);
978
+ for (const entry of indexEntries) {
979
+ entry.indexStore.index.insert(entry.key, existing.internalId, {
980
+ enforceUnique: options.enforceConstraints,
981
+ });
982
+ }
983
+ };
984
+ };
985
+
986
+ const checkRow = (
987
+ op: Extract<MutationOperation<AnySchema>, { type: "check" }>,
988
+ tableStore: InMemoryTableStore,
989
+ table: AnyTable,
990
+ resolver?: NamingResolver,
991
+ ): void => {
992
+ const existing = findRowByExternalId(tableStore, table, op.id.externalId, resolver);
993
+ if (!existing) {
994
+ throw new VersionConflictError(`Version conflict: row "${op.id.externalId}" not found.`);
995
+ }
996
+
997
+ const versionColumnName = getPhysicalColumnName(table, "_version", resolver);
998
+ const currentVersion = Number(existing.row[versionColumnName] ?? 0);
999
+ if (currentVersion !== op.id.version) {
1000
+ throw new VersionConflictError(`Version conflict: row "${op.id.externalId}" has changed.`);
1001
+ }
1002
+ };
1003
+
1004
+ export const createInMemoryUowCompiler = (): UOWCompiler<InMemoryCompiledQuery> => ({
1005
+ compileRetrievalOperation(op: RetrievalOperation<AnySchema>): InMemoryCompiledQuery | null {
1006
+ return op;
1007
+ },
1008
+ compileMutationOperation(
1009
+ op: MutationOperation<AnySchema>,
1010
+ ): CompiledMutation<InMemoryCompiledQuery> | null {
1011
+ return {
1012
+ query: op,
1013
+ operation: op,
1014
+ op: op.type,
1015
+ expectedAffectedRows: null,
1016
+ expectedReturnedRows: null,
1017
+ };
1018
+ },
1019
+ });
1020
+
1021
+ export const createInMemoryUowExecutor = (
1022
+ store: InMemoryStore,
1023
+ options: ResolvedInMemoryAdapterOptions,
1024
+ resolverFactory?: ResolverFactory,
1025
+ ): UOWExecutor<InMemoryCompiledQuery, InMemoryRawResult> => ({
1026
+ async executeRetrievalPhase(
1027
+ retrievalBatch: InMemoryCompiledQuery[],
1028
+ ): Promise<InMemoryRawResult[]> {
1029
+ const results: InMemoryRawResult[] = [];
1030
+
1031
+ for (const compiled of retrievalBatch) {
1032
+ if (compiled.type === "count" || compiled.type === "find") {
1033
+ const resolver = getResolver(compiled.schema, compiled.namespace, resolverFactory);
1034
+ const namespaceStore = getNamespaceStore(
1035
+ store,
1036
+ compiled.schema,
1037
+ compiled.namespace,
1038
+ resolver,
1039
+ );
1040
+ const tableStore = getTableStore(namespaceStore, compiled.table, resolver);
1041
+
1042
+ if (compiled.type === "find") {
1043
+ results.push(findRows(compiled, namespaceStore, tableStore, resolver, options.clock.now));
1044
+ } else {
1045
+ results.push([
1046
+ { count: countRows(compiled, namespaceStore, tableStore, resolver, options.clock.now) },
1047
+ ]);
1048
+ }
1049
+ continue;
1050
+ }
1051
+
1052
+ throw new Error(`Unsupported in-memory retrieval operation: ${compiled.type}`);
1053
+ }
1054
+
1055
+ return results;
1056
+ },
1057
+
1058
+ async executeMutationPhase(
1059
+ mutationBatch: CompiledMutation<InMemoryCompiledQuery>[],
1060
+ ): Promise<MutationResult> {
1061
+ const createdInternalIds: (bigint | null)[] = [];
1062
+ const rollbackActions: Array<() => void> = [];
1063
+
1064
+ try {
1065
+ for (const compiled of mutationBatch) {
1066
+ const operation = compiled.query;
1067
+
1068
+ if (operation.type === "create") {
1069
+ const resolver = getResolver(operation.schema, operation.namespace, resolverFactory);
1070
+ const namespaceStore = getNamespaceStore(
1071
+ store,
1072
+ operation.schema,
1073
+ operation.namespace,
1074
+ resolver,
1075
+ );
1076
+ const table = operation.schema.tables[operation.table];
1077
+ if (!table) {
1078
+ throw new Error(`Invalid table name ${operation.table}.`);
1079
+ }
1080
+ const tableStore = getTableStore(namespaceStore, table, resolver);
1081
+ const previousInternalId = tableStore.nextInternalId;
1082
+ const internalId = createRow(operation, namespaceStore, tableStore, options, resolver);
1083
+ createdInternalIds.push(internalId);
1084
+ rollbackActions.push(() => {
1085
+ const row = tableStore.rows.get(internalId);
1086
+ if (row) {
1087
+ for (const indexStore of tableStore.indexes.values()) {
1088
+ const key = buildIndexKey(table, indexStore.definition, row, resolver);
1089
+ indexStore.index.remove(key, internalId);
1090
+ }
1091
+ tableStore.rows.delete(internalId);
1092
+ }
1093
+ tableStore.nextInternalId = previousInternalId;
1094
+ });
1095
+ continue;
1096
+ }
1097
+
1098
+ if (operation.type === "update") {
1099
+ const resolver = getResolver(operation.schema, operation.namespace, resolverFactory);
1100
+ const namespaceStore = getNamespaceStore(
1101
+ store,
1102
+ operation.schema,
1103
+ operation.namespace,
1104
+ resolver,
1105
+ );
1106
+ const table = operation.schema.tables[operation.table];
1107
+ if (!table) {
1108
+ throw new Error(`Invalid table name ${operation.table}.`);
1109
+ }
1110
+ const tableStore = getTableStore(namespaceStore, table, resolver);
1111
+ const rollback = updateRow(operation, namespaceStore, tableStore, options, resolver);
1112
+ if (rollback) {
1113
+ rollbackActions.push(rollback);
1114
+ }
1115
+ continue;
1116
+ }
1117
+
1118
+ if (operation.type === "delete") {
1119
+ const resolver = getResolver(operation.schema, operation.namespace, resolverFactory);
1120
+ const namespaceStore = getNamespaceStore(
1121
+ store,
1122
+ operation.schema,
1123
+ operation.namespace,
1124
+ resolver,
1125
+ );
1126
+ const table = operation.schema.tables[operation.table];
1127
+ if (!table) {
1128
+ throw new Error(`Invalid table name ${operation.table}.`);
1129
+ }
1130
+ const tableStore = getTableStore(namespaceStore, table, resolver);
1131
+ const rollback = deleteRow(
1132
+ operation,
1133
+ namespaceStore,
1134
+ tableStore,
1135
+ table,
1136
+ options,
1137
+ resolver,
1138
+ );
1139
+ if (rollback) {
1140
+ rollbackActions.push(rollback);
1141
+ }
1142
+ continue;
1143
+ }
1144
+
1145
+ if (operation.type === "check") {
1146
+ const resolver = getResolver(operation.schema, operation.namespace, resolverFactory);
1147
+ const namespaceStore = getNamespaceStore(
1148
+ store,
1149
+ operation.schema,
1150
+ operation.namespace,
1151
+ resolver,
1152
+ );
1153
+ const table = operation.schema.tables[operation.table];
1154
+ if (!table) {
1155
+ throw new Error(`Invalid table name ${operation.table}.`);
1156
+ }
1157
+ const tableStore = getTableStore(namespaceStore, table, resolver);
1158
+ checkRow(operation, tableStore, table, resolver);
1159
+ continue;
1160
+ }
1161
+
1162
+ throw new Error(`Unsupported in-memory mutation "${operation.type}".`);
1163
+ }
1164
+ } catch (error) {
1165
+ for (const rollback of rollbackActions.reverse()) {
1166
+ rollback();
1167
+ }
1168
+ if (error instanceof VersionConflictError) {
1169
+ return { success: false };
1170
+ }
1171
+ throw error;
1172
+ }
1173
+
1174
+ return { success: true, createdInternalIds };
1175
+ },
1176
+ });
1177
+
1178
+ export class InMemoryUowDecoder implements UOWDecoder<InMemoryRawResult> {
1179
+ readonly #resolverFactory?: ResolverFactory;
1180
+
1181
+ constructor(resolverFactory?: ResolverFactory) {
1182
+ this.#resolverFactory = resolverFactory;
1183
+ }
1184
+
1185
+ decode(rawResults: InMemoryRawResult[], operations: RetrievalOperation<AnySchema>[]): unknown[] {
1186
+ if (rawResults.length !== operations.length) {
1187
+ throw new Error("rawResults and ops must have the same length");
1188
+ }
1189
+
1190
+ return rawResults.map((result, index) => {
1191
+ const op = operations[index];
1192
+ if (!op) {
1193
+ throw new Error("op must be defined");
1194
+ }
1195
+
1196
+ if (op.type === "count") {
1197
+ return this.decodeCount(result);
1198
+ }
1199
+
1200
+ const resolver = getResolver(op.schema, op.namespace, this.#resolverFactory);
1201
+ const rows = result as InMemoryRow[];
1202
+ const decodedRows = rows.map((row) => this.decodeRow(row, op.table, resolver));
1203
+
1204
+ if (op.withCursor) {
1205
+ return this.decodeCursorResult(decodedRows, op.table, op);
1206
+ }
1207
+
1208
+ return decodedRows;
1209
+ });
1210
+ }
1211
+
1212
+ private decodeCount(result: InMemoryRawResult): number {
1213
+ if (typeof result === "number") {
1214
+ return result;
1215
+ }
1216
+
1217
+ const rows = result as { count: number }[];
1218
+ const first = rows[0];
1219
+ if (!first) {
1220
+ return 0;
1221
+ }
1222
+ const count = Number(first.count);
1223
+ if (Number.isNaN(count)) {
1224
+ throw new Error(`Unexpected result for count, received: ${first.count}`);
1225
+ }
1226
+ return count;
1227
+ }
1228
+
1229
+ private decodeRow(
1230
+ row: InMemoryRow,
1231
+ table: AnyTable,
1232
+ resolver?: NamingResolver,
1233
+ ): Record<string, unknown> {
1234
+ const output: Record<string, unknown> = {};
1235
+ const columnValues: Record<string, unknown> = {};
1236
+ const relationData: Record<string, Record<string, unknown>> = {};
1237
+ const columnMap = resolver ? resolver.getColumnNameMap(table) : undefined;
1238
+
1239
+ for (const key in row) {
1240
+ const colonIndex = key.indexOf(":");
1241
+ if (colonIndex === -1) {
1242
+ const logicalName = columnMap?.[key] ?? key;
1243
+ if (table.columns[logicalName]) {
1244
+ columnValues[logicalName] = row[key];
1245
+ }
1246
+ continue;
1247
+ }
1248
+
1249
+ const relationName = key.slice(0, colonIndex);
1250
+ const remainder = key.slice(colonIndex + 1);
1251
+ const relation = table.relations[relationName];
1252
+ if (!relation) {
1253
+ continue;
1254
+ }
1255
+
1256
+ relationData[relationName] ??= {};
1257
+ relationData[relationName][remainder] = row[key];
1258
+ }
1259
+
1260
+ for (const relationName in relationData) {
1261
+ const relation = table.relations[relationName];
1262
+ if (!relation) {
1263
+ continue;
1264
+ }
1265
+ output[relationName] = this.decodeRow(relationData[relationName], relation.table, resolver);
1266
+ }
1267
+
1268
+ for (const key in columnValues) {
1269
+ const column = table.columns[key];
1270
+ if (!column) {
1271
+ continue;
1272
+ }
1273
+ if (column.isHidden) {
1274
+ continue;
1275
+ }
1276
+
1277
+ if (column.role === "external-id" && columnValues["_internalId"] !== undefined) {
1278
+ output[key] = new FragnoId({
1279
+ externalId: columnValues[key] as string,
1280
+ internalId: columnValues["_internalId"] as bigint,
1281
+ version: columnValues["_version"] as number,
1282
+ });
1283
+ continue;
1284
+ }
1285
+
1286
+ if (column.role === "reference") {
1287
+ const value = columnValues[key];
1288
+ output[key] =
1289
+ value === null || value === undefined
1290
+ ? null
1291
+ : FragnoReference.fromInternal(value as bigint);
1292
+ continue;
1293
+ }
1294
+
1295
+ output[key] = columnValues[key];
1296
+ }
1297
+
1298
+ return output;
1299
+ }
1300
+
1301
+ private decodeCursorResult(
1302
+ decodedRows: Record<string, unknown>[],
1303
+ table: AnyTable,
1304
+ operation: Extract<RetrievalOperation<AnySchema>, { type: "find" }>,
1305
+ ): CursorResult<unknown> {
1306
+ let cursor: Cursor | undefined;
1307
+ let hasNextPage = false;
1308
+ let items = decodedRows;
1309
+
1310
+ if (
1311
+ operation.options.pageSize &&
1312
+ operation.options.pageSize > 0 &&
1313
+ decodedRows.length > operation.options.pageSize
1314
+ ) {
1315
+ hasNextPage = true;
1316
+ items = decodedRows.slice(0, operation.options.pageSize);
1317
+
1318
+ if (operation.options.orderByIndex) {
1319
+ const lastItem = items[items.length - 1];
1320
+ const indexName = operation.options.orderByIndex.indexName;
1321
+
1322
+ let indexColumns;
1323
+ if (indexName === "_primary") {
1324
+ indexColumns = [table.getIdColumn()];
1325
+ } else {
1326
+ const index = table.indexes[indexName];
1327
+ if (index) {
1328
+ indexColumns = index.columns;
1329
+ }
1330
+ }
1331
+
1332
+ if (indexColumns && lastItem) {
1333
+ cursor = createCursorFromRecord(lastItem, indexColumns, {
1334
+ indexName: operation.options.orderByIndex.indexName,
1335
+ orderDirection: operation.options.orderByIndex.direction,
1336
+ pageSize: operation.options.pageSize,
1337
+ });
1338
+ }
1339
+ }
1340
+ }
1341
+
1342
+ return {
1343
+ items,
1344
+ cursor,
1345
+ hasNextPage,
1346
+ };
1347
+ }
1348
+ }