@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
@@ -0,0 +1,51 @@
1
+ import { schema, idColumn, column } from "../schema/create";
2
+
3
+ // Constants for Fragno's internal settings table
4
+ export const SETTINGS_TABLE_NAME = "fragno_db_settings" as const;
5
+ // FIXME: In some places we simply use empty string "" as namespace, which is not correct.
6
+ export const SETTINGS_NAMESPACE = "fragno-db-settings" as const;
7
+
8
+ export const internalSchema = schema("fragno_internal", (s) => {
9
+ return s
10
+ .addTable(SETTINGS_TABLE_NAME, (t) => {
11
+ return t
12
+ .addColumn("id", idColumn())
13
+ .addColumn("key", column("string"))
14
+ .addColumn("value", column("string"))
15
+ .createIndex("unique_key", ["key"], { unique: true });
16
+ })
17
+ .addTable("fragno_hooks", (t) => {
18
+ return t
19
+ .addColumn("id", idColumn())
20
+ .addColumn("namespace", column("string"))
21
+ .addColumn("hookName", column("string"))
22
+ .addColumn("payload", column("json"))
23
+ .addColumn("status", column("string")) // "pending" | "processing" | "completed" | "failed"
24
+ .addColumn("attempts", column("integer").defaultTo(0))
25
+ .addColumn("maxAttempts", column("integer").defaultTo(5))
26
+ .addColumn("lastAttemptAt", column("timestamp").nullable())
27
+ .addColumn("nextRetryAt", column("timestamp").nullable())
28
+ .addColumn("error", column("string").nullable())
29
+ .addColumn(
30
+ "createdAt",
31
+ column("timestamp").defaultTo((b) => b.now()),
32
+ )
33
+ .addColumn("nonce", column("string"))
34
+ .createIndex("idx_namespace_status_retry", ["namespace", "status", "nextRetryAt"])
35
+ .createIndex("idx_nonce", ["nonce"]);
36
+ })
37
+ .addTable("fragno_db_outbox", (t) => {
38
+ return t
39
+ .addColumn("id", idColumn())
40
+ .addColumn("versionstamp", column("string"))
41
+ .addColumn("uowId", column("string"))
42
+ .addColumn("payload", column("json"))
43
+ .addColumn("refMap", column("json").nullable())
44
+ .addColumn(
45
+ "createdAt",
46
+ column("timestamp").defaultTo((b) => b.now()),
47
+ )
48
+ .createIndex("idx_outbox_versionstamp", ["versionstamp"], { unique: true })
49
+ .createIndex("idx_outbox_uow", ["uowId"]);
50
+ });
51
+ });
@@ -2,16 +2,22 @@ import SQLite from "better-sqlite3";
2
2
  import { SqliteDialect } from "kysely";
3
3
  import { beforeAll, describe, expect, it } from "vitest";
4
4
  import { instantiate } from "@fragno-dev/core";
5
- import { internalFragmentDef, internalSchema, SETTINGS_NAMESPACE } from "./internal-fragment";
5
+ import {
6
+ internalFragmentDef,
7
+ internalSchema,
8
+ SETTINGS_NAMESPACE,
9
+ getSchemaVersionFromDatabase,
10
+ } from "./internal-fragment";
6
11
  import type { FragnoPublicConfigWithDatabase } from "../db-fragment-definition-builder";
7
- import { DrizzleAdapter } from "../adapters/drizzle/drizzle-adapter";
12
+ import { SqlAdapter } from "../adapters/generic-sql/generic-sql-adapter";
8
13
  import { BetterSQLite3DriverConfig } from "../adapters/generic-sql/driver-config";
9
14
  import { ExponentialBackoffRetryPolicy, NoRetryPolicy } from "../query/unit-of-work/retry-policy";
15
+ import { ConcurrencyConflictError } from "../query/unit-of-work/execute-unit-of-work";
10
16
  import type { FragnoId } from "../schema/create";
11
17
 
12
18
  describe("Internal Fragment", () => {
13
19
  let sqliteDatabase: SQLite.Database;
14
- let adapter: DrizzleAdapter;
20
+ let adapter: SqlAdapter;
15
21
  let fragment: ReturnType<typeof instantiateFragment>;
16
22
 
17
23
  function instantiateFragment(options: FragnoPublicConfigWithDatabase) {
@@ -25,19 +31,20 @@ describe("Internal Fragment", () => {
25
31
  database: sqliteDatabase,
26
32
  });
27
33
 
28
- adapter = new DrizzleAdapter({
34
+ adapter = new SqlAdapter({
29
35
  dialect,
30
36
  driverConfig: new BetterSQLite3DriverConfig(),
31
37
  });
32
38
 
33
39
  {
34
- const migrations = adapter.prepareMigrations(internalSchema, "");
40
+ const migrations = adapter.prepareMigrations(internalSchema, null);
35
41
  await migrations.executeWithDriver(adapter.driver, 0);
36
42
  }
37
43
 
38
44
  // Instantiate fragment with shared database adapter
39
45
  const options: FragnoPublicConfigWithDatabase = {
40
46
  databaseAdapter: adapter,
47
+ databaseNamespace: null,
41
48
  };
42
49
 
43
50
  fragment = instantiateFragment(options);
@@ -151,7 +158,7 @@ describe("Internal Fragment", () => {
151
158
 
152
159
  describe("Hook Service", () => {
153
160
  let sqliteDatabase: SQLite.Database;
154
- let adapter: DrizzleAdapter;
161
+ let adapter: SqlAdapter;
155
162
  let fragment: ReturnType<typeof instantiateFragment>;
156
163
 
157
164
  function instantiateFragment(options: FragnoPublicConfigWithDatabase) {
@@ -165,18 +172,19 @@ describe("Hook Service", () => {
165
172
  database: sqliteDatabase,
166
173
  });
167
174
 
168
- adapter = new DrizzleAdapter({
175
+ adapter = new SqlAdapter({
169
176
  dialect,
170
177
  driverConfig: new BetterSQLite3DriverConfig(),
171
178
  });
172
179
 
173
180
  {
174
- const migrations = adapter.prepareMigrations(internalSchema, "");
181
+ const migrations = adapter.prepareMigrations(internalSchema, null);
175
182
  await migrations.executeWithDriver(adapter.driver, 0);
176
183
  }
177
184
 
178
185
  const options: FragnoPublicConfigWithDatabase = {
179
186
  databaseAdapter: adapter,
187
+ databaseNamespace: null,
180
188
  };
181
189
 
182
190
  fragment = instantiateFragment(options);
@@ -282,6 +290,51 @@ describe("Hook Service", () => {
282
290
  expect(result?.lastAttemptAt).toBeInstanceOf(Date);
283
291
  });
284
292
 
293
+ it("should reject marking completed with a stale id", async () => {
294
+ const namespace = "complete-stale";
295
+ const nonce = "test-nonce-complete-stale";
296
+ let staleId!: FragnoId;
297
+
298
+ await fragment.inContext(async function () {
299
+ const createdId = await this.handlerTx()
300
+ .mutate(({ forSchema }) => {
301
+ const uow = forSchema(internalSchema);
302
+ return uow.create("fragno_hooks", {
303
+ namespace,
304
+ hookName: "onCompleteStale",
305
+ payload: { test: "data" },
306
+ status: "pending",
307
+ attempts: 0,
308
+ maxAttempts: 5,
309
+ lastAttemptAt: null,
310
+ nextRetryAt: null,
311
+ error: null,
312
+ nonce,
313
+ });
314
+ })
315
+ .execute();
316
+ staleId = createdId;
317
+ });
318
+
319
+ await fragment.inContext(async function () {
320
+ await this.handlerTx()
321
+ .withServiceCalls(
322
+ () => [fragment.services.hookService.claimPendingHookEvents(namespace)] as const,
323
+ )
324
+ .execute();
325
+ });
326
+
327
+ await expect(
328
+ fragment.inContext(async function () {
329
+ await this.handlerTx()
330
+ .withServiceCalls(
331
+ () => [fragment.services.hookService.markHookCompleted(staleId)] as const,
332
+ )
333
+ .execute();
334
+ }),
335
+ ).rejects.toThrow(ConcurrencyConflictError);
336
+ });
337
+
285
338
  it("should mark a hook event as processing", async () => {
286
339
  const nonce = "test-nonce-3";
287
340
  let eventId: FragnoId;
@@ -477,6 +530,49 @@ describe("Hook Service", () => {
477
530
  expect(staleEvent?.attempts).toBe(1);
478
531
  });
479
532
 
533
+ it("should detect conflicts when requeueing after another update in the same transaction", async () => {
534
+ const namespace = "requeue-conflict";
535
+ const nonce = "test-nonce-requeue-conflict";
536
+ let eventId!: FragnoId;
537
+
538
+ const staleBefore = new Date(Date.now() - 60_000);
539
+ const lastAttemptAt = new Date(Date.now() - 120_000);
540
+
541
+ await fragment.inContext(async function () {
542
+ eventId = await this.handlerTx()
543
+ .mutate(({ forSchema }) => {
544
+ const uow = forSchema(internalSchema);
545
+ return uow.create("fragno_hooks", {
546
+ namespace,
547
+ hookName: "onRequeueConflict",
548
+ payload: { test: "requeue" },
549
+ status: "processing",
550
+ attempts: 0,
551
+ maxAttempts: 5,
552
+ lastAttemptAt,
553
+ nextRetryAt: null,
554
+ error: null,
555
+ nonce,
556
+ });
557
+ })
558
+ .execute();
559
+ });
560
+
561
+ await expect(
562
+ fragment.inContext(async function () {
563
+ await this.handlerTx({ retryPolicy: new NoRetryPolicy() })
564
+ .withServiceCalls(
565
+ () =>
566
+ [
567
+ fragment.services.hookService.markHookProcessing(eventId),
568
+ fragment.services.hookService.requeueStuckProcessingHooks(namespace, staleBefore),
569
+ ] as const,
570
+ )
571
+ .execute();
572
+ }),
573
+ ).rejects.toThrow(ConcurrencyConflictError);
574
+ });
575
+
480
576
  it("should not retrieve events from different namespace", async () => {
481
577
  const nonce = "test-nonce-7";
482
578
 
@@ -552,4 +648,358 @@ describe("Hook Service", () => {
552
648
  const futureEvent = events.find((e) => e.id.externalId === eventId.externalId);
553
649
  expect(futureEvent).toBeUndefined();
554
650
  });
651
+
652
+ it("should claim only ready pending events and mark them processing", async () => {
653
+ const namespace = "claim-ready";
654
+ const pastTime = new Date(Date.now() - 10000);
655
+ const futureTime = new Date(Date.now() + 60000);
656
+
657
+ let nullRetryId!: FragnoId;
658
+ let pastRetryId!: FragnoId;
659
+ let futureRetryId!: FragnoId;
660
+
661
+ await fragment.inContext(async function () {
662
+ await this.handlerTx()
663
+ .mutate(({ forSchema }) => {
664
+ const uow = forSchema(internalSchema);
665
+ nullRetryId = uow.create("fragno_hooks", {
666
+ namespace,
667
+ hookName: "onNullRetry",
668
+ payload: { test: "null" },
669
+ status: "pending",
670
+ attempts: 0,
671
+ maxAttempts: 5,
672
+ lastAttemptAt: null,
673
+ nextRetryAt: null,
674
+ error: null,
675
+ nonce: "test-nonce-claim-null",
676
+ });
677
+ pastRetryId = uow.create("fragno_hooks", {
678
+ namespace,
679
+ hookName: "onPastRetry",
680
+ payload: { test: "past" },
681
+ status: "pending",
682
+ attempts: 1,
683
+ maxAttempts: 5,
684
+ lastAttemptAt: pastTime,
685
+ nextRetryAt: pastTime,
686
+ error: "Previous error",
687
+ nonce: "test-nonce-claim-past",
688
+ });
689
+ futureRetryId = uow.create("fragno_hooks", {
690
+ namespace,
691
+ hookName: "onFutureRetry",
692
+ payload: { test: "future" },
693
+ status: "pending",
694
+ attempts: 1,
695
+ maxAttempts: 5,
696
+ lastAttemptAt: new Date(),
697
+ nextRetryAt: futureTime,
698
+ error: "Previous error",
699
+ nonce: "test-nonce-claim-future",
700
+ });
701
+ })
702
+ .execute();
703
+ });
704
+
705
+ const claimed = await fragment.inContext(async function () {
706
+ return await this.handlerTx()
707
+ .withServiceCalls(
708
+ () => [fragment.services.hookService.claimPendingHookEvents(namespace)] as const,
709
+ )
710
+ .transform(({ serviceResult: [result] }) => result)
711
+ .execute();
712
+ });
713
+
714
+ expect(claimed).toHaveLength(2);
715
+ const claimedIds = new Set(claimed.map((event) => event.id.externalId));
716
+ expect(claimedIds.has(nullRetryId.externalId)).toBe(true);
717
+ expect(claimedIds.has(pastRetryId.externalId)).toBe(true);
718
+ expect(claimedIds.has(futureRetryId.externalId)).toBe(false);
719
+
720
+ const [nullEvent, pastEvent, futureEvent] = await fragment.inContext(async function () {
721
+ return await this.handlerTx()
722
+ .withServiceCalls(
723
+ () =>
724
+ [
725
+ fragment.services.hookService.getHookById(nullRetryId),
726
+ fragment.services.hookService.getHookById(pastRetryId),
727
+ fragment.services.hookService.getHookById(futureRetryId),
728
+ ] as const,
729
+ )
730
+ .transform(({ serviceResult: [nullResult, pastResult, futureResult] }) => [
731
+ nullResult,
732
+ pastResult,
733
+ futureResult,
734
+ ])
735
+ .execute();
736
+ });
737
+
738
+ expect(nullEvent?.status).toBe("processing");
739
+ expect(nullEvent?.lastAttemptAt).toBeInstanceOf(Date);
740
+ expect(pastEvent?.status).toBe("processing");
741
+ expect(pastEvent?.lastAttemptAt).toBeInstanceOf(Date);
742
+ expect(futureEvent?.status).toBe("pending");
743
+ });
744
+
745
+ it("should return claimed ids with incremented versions", async () => {
746
+ const namespace = "claim-version";
747
+ const nonce = "test-nonce-claim-version";
748
+ let createdId!: FragnoId;
749
+
750
+ await fragment.inContext(async function () {
751
+ createdId = await this.handlerTx()
752
+ .mutate(({ forSchema }) => {
753
+ const uow = forSchema(internalSchema);
754
+ return uow.create("fragno_hooks", {
755
+ namespace,
756
+ hookName: "onClaimVersion",
757
+ payload: { test: "version" },
758
+ status: "pending",
759
+ attempts: 0,
760
+ maxAttempts: 5,
761
+ lastAttemptAt: null,
762
+ nextRetryAt: null,
763
+ error: null,
764
+ nonce,
765
+ });
766
+ })
767
+ .execute();
768
+ });
769
+
770
+ const claimed = await fragment.inContext(async function () {
771
+ return await this.handlerTx()
772
+ .withServiceCalls(
773
+ () => [fragment.services.hookService.claimPendingHookEvents(namespace)] as const,
774
+ )
775
+ .transform(({ serviceResult: [result] }) => result)
776
+ .execute();
777
+ });
778
+
779
+ expect(claimed).toHaveLength(1);
780
+ expect(claimed[0]?.id.externalId).toBe(createdId.externalId);
781
+ expect(claimed[0]?.id.version).toBe(createdId.version + 1);
782
+ });
783
+
784
+ it("should return now when pending hooks have no nextRetryAt", async () => {
785
+ const namespace = "wake-now";
786
+
787
+ await fragment.inContext(async function () {
788
+ await this.handlerTx()
789
+ .mutate(({ forSchema }) => {
790
+ const uow = forSchema(internalSchema);
791
+ uow.create("fragno_hooks", {
792
+ namespace,
793
+ hookName: "onImmediate",
794
+ payload: { test: "now" },
795
+ status: "pending",
796
+ attempts: 0,
797
+ maxAttempts: 5,
798
+ lastAttemptAt: null,
799
+ nextRetryAt: null,
800
+ error: null,
801
+ nonce: "test-nonce-now",
802
+ });
803
+ })
804
+ .execute();
805
+ });
806
+
807
+ const wakeAt = await fragment.inContext(async function () {
808
+ return await this.handlerTx()
809
+ .withServiceCalls(
810
+ () => [fragment.services.hookService.getNextHookWakeAt(namespace)] as const,
811
+ )
812
+ .transform(({ serviceResult: [result] }) => result)
813
+ .execute();
814
+ });
815
+
816
+ expect(wakeAt).toBeInstanceOf(Date);
817
+ expect(Math.abs((wakeAt as Date).getTime() - Date.now())).toBeLessThan(5000);
818
+ });
819
+
820
+ it("should return earliest scheduled hook time", async () => {
821
+ const namespace = "wake-future";
822
+ const soon = new Date(Date.now() + 10000);
823
+ const later = new Date(Date.now() + 60000);
824
+
825
+ await fragment.inContext(async function () {
826
+ await this.handlerTx()
827
+ .mutate(({ forSchema }) => {
828
+ const uow = forSchema(internalSchema);
829
+ uow.create("fragno_hooks", {
830
+ namespace,
831
+ hookName: "onSoon",
832
+ payload: { test: "soon" },
833
+ status: "pending",
834
+ attempts: 0,
835
+ maxAttempts: 5,
836
+ lastAttemptAt: null,
837
+ nextRetryAt: soon,
838
+ error: null,
839
+ nonce: "test-nonce-soon",
840
+ });
841
+ uow.create("fragno_hooks", {
842
+ namespace,
843
+ hookName: "onLater",
844
+ payload: { test: "later" },
845
+ status: "pending",
846
+ attempts: 0,
847
+ maxAttempts: 5,
848
+ lastAttemptAt: null,
849
+ nextRetryAt: later,
850
+ error: null,
851
+ nonce: "test-nonce-later",
852
+ });
853
+ })
854
+ .execute();
855
+ });
856
+
857
+ const wakeAt = await fragment.inContext(async function () {
858
+ return await this.handlerTx()
859
+ .withServiceCalls(
860
+ () => [fragment.services.hookService.getNextHookWakeAt(namespace)] as const,
861
+ )
862
+ .transform(({ serviceResult: [result] }) => result)
863
+ .execute();
864
+ });
865
+
866
+ expect(wakeAt).toEqual(soon);
867
+ });
868
+
869
+ it("should return null when no pending hooks exist", async () => {
870
+ const namespace = "wake-none";
871
+ const wakeAt = await fragment.inContext(async function () {
872
+ return await this.handlerTx()
873
+ .withServiceCalls(
874
+ () => [fragment.services.hookService.getNextHookWakeAt(namespace)] as const,
875
+ )
876
+ .transform(({ serviceResult: [result] }) => result)
877
+ .execute();
878
+ });
879
+
880
+ expect(wakeAt).toBeNull();
881
+ });
882
+ });
883
+
884
+ describe("getSchemaVersionFromDatabase", () => {
885
+ function createTestSetup() {
886
+ const sqliteDatabase = new SQLite(":memory:");
887
+ const dialect = new SqliteDialect({ database: sqliteDatabase });
888
+ const adapter = new SqlAdapter({
889
+ dialect,
890
+ driverConfig: new BetterSQLite3DriverConfig(),
891
+ });
892
+
893
+ function instantiateFragment(options: FragnoPublicConfigWithDatabase) {
894
+ return instantiate(internalFragmentDef).withConfig({}).withOptions(options).build();
895
+ }
896
+
897
+ return { sqliteDatabase, adapter, instantiateFragment };
898
+ }
899
+
900
+ async function setupAndMigrate() {
901
+ const { sqliteDatabase, adapter, instantiateFragment } = createTestSetup();
902
+ // Create tables without writing a version record, so tests control version state
903
+ const migrations = adapter.prepareMigrations(internalSchema, "");
904
+ await migrations.executeWithDriver(adapter.driver, 0, undefined, {
905
+ updateVersionInMigration: false,
906
+ });
907
+ const fragment = instantiateFragment({
908
+ databaseAdapter: adapter,
909
+ databaseNamespace: null,
910
+ });
911
+ return { sqliteDatabase, adapter, fragment };
912
+ }
913
+
914
+ it("should return 0 when no version exists", async () => {
915
+ const { fragment } = await setupAndMigrate();
916
+
917
+ const version = await getSchemaVersionFromDatabase(fragment, "nonexistent");
918
+ expect(version).toBe(0);
919
+ });
920
+
921
+ it("should find version stored under empty-string namespace", async () => {
922
+ const { fragment } = await setupAndMigrate();
923
+
924
+ // Write version under empty-string namespace (key = ".schema_version")
925
+ await fragment.inContext(async function () {
926
+ await this.handlerTx()
927
+ .withServiceCalls(() => [fragment.services.settingsService.set("", "schema_version", "5")])
928
+ .execute();
929
+ });
930
+
931
+ const version = await getSchemaVersionFromDatabase(fragment, "");
932
+ expect(version).toBe(5);
933
+ });
934
+
935
+ it("should find version via back-compat when stored under internalSchema.name but read with empty string", async () => {
936
+ const { fragment } = await setupAndMigrate();
937
+
938
+ // Write version under "fragno_internal" namespace (legacy key from buggy code)
939
+ await fragment.inContext(async function () {
940
+ await this.handlerTx()
941
+ .withServiceCalls(() => [
942
+ fragment.services.settingsService.set(internalSchema.name, "schema_version", "3"),
943
+ ])
944
+ .execute();
945
+ });
946
+
947
+ // Reading with "" should find it via back-compat fallback
948
+ const version = await getSchemaVersionFromDatabase(fragment, "");
949
+ expect(version).toBe(3);
950
+ });
951
+
952
+ it("should find version via back-compat when stored under empty string but read with internalSchema.name", async () => {
953
+ const { fragment } = await setupAndMigrate();
954
+
955
+ // Write version under empty-string namespace
956
+ await fragment.inContext(async function () {
957
+ await this.handlerTx()
958
+ .withServiceCalls(() => [fragment.services.settingsService.set("", "schema_version", "7")])
959
+ .execute();
960
+ });
961
+
962
+ // Reading with internalSchema.name should find it via back-compat fallback
963
+ const version = await getSchemaVersionFromDatabase(fragment, internalSchema.name);
964
+ expect(version).toBe(7);
965
+ });
966
+
967
+ it("should prefer primary namespace over back-compat fallback", async () => {
968
+ const { fragment } = await setupAndMigrate();
969
+
970
+ // Write version under BOTH namespaces with different values
971
+ await fragment.inContext(async function () {
972
+ await this.handlerTx()
973
+ .withServiceCalls(() => [
974
+ fragment.services.settingsService.set("", "schema_version", "10"),
975
+ fragment.services.settingsService.set(internalSchema.name, "schema_version", "20"),
976
+ ])
977
+ .execute();
978
+ });
979
+
980
+ // Reading with "" should find 10 (primary), not 20 (back-compat)
981
+ const versionEmpty = await getSchemaVersionFromDatabase(fragment, "");
982
+ expect(versionEmpty).toBe(10);
983
+
984
+ // Reading with internalSchema.name should find 20 (primary), not 10 (back-compat)
985
+ const versionNamed = await getSchemaVersionFromDatabase(fragment, internalSchema.name);
986
+ expect(versionNamed).toBe(20);
987
+ });
988
+
989
+ it("should not use back-compat for non-internal namespaces", async () => {
990
+ const { fragment } = await setupAndMigrate();
991
+
992
+ // Write version under "some-fragment"
993
+ await fragment.inContext(async function () {
994
+ await this.handlerTx()
995
+ .withServiceCalls(() => [
996
+ fragment.services.settingsService.set("some-fragment", "schema_version", "4"),
997
+ ])
998
+ .execute();
999
+ });
1000
+
1001
+ // Reading with a different non-internal namespace should NOT find it
1002
+ const version = await getSchemaVersionFromDatabase(fragment, "other-fragment");
1003
+ expect(version).toBe(0);
1004
+ });
555
1005
  });