@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,573 @@
1
+ import { parseVarchar } from "../util/parse";
2
+ import type { AnyColumn, AnySchema, AnyTable, Relation } from "../schema/create";
3
+ import {
4
+ defaultNamingStrategyForDatabase,
5
+ type SupportedDatabase,
6
+ } from "../adapters/generic-sql/driver-config";
7
+ import type { SQLiteStorageMode } from "../adapters/generic-sql/sqlite-storage";
8
+ import { sqliteStorageDefault, sqliteStoragePrisma } from "../adapters/generic-sql/sqlite-storage";
9
+ import {
10
+ sanitizeNamespace,
11
+ createNamingResolver,
12
+ type NamingResolver,
13
+ type SqlNamingStrategy,
14
+ } from "../naming/sql-naming";
15
+
16
+ export interface GeneratePrismaSchemaOptions {
17
+ sqliteStorageMode?: SQLiteStorageMode;
18
+ namingStrategy?: SqlNamingStrategy;
19
+ }
20
+
21
+ const VALID_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
22
+
23
+ function isValidIdentifier(name: string): boolean {
24
+ return VALID_IDENTIFIER.test(name);
25
+ }
26
+
27
+ function sanitizeIdentifier(name: string): string {
28
+ const sanitized = name.replace(/[^A-Za-z0-9_]/g, "_");
29
+ if (sanitized.length === 0) {
30
+ return "_";
31
+ }
32
+ if (/^[0-9]/.test(sanitized)) {
33
+ return `_${sanitized}`;
34
+ }
35
+ return sanitized;
36
+ }
37
+
38
+ function ensureUniqueName(base: string, used: Set<string>): string {
39
+ if (!used.has(base)) {
40
+ return base;
41
+ }
42
+ let index = 1;
43
+ let candidate = `${base}_${index}`;
44
+ while (used.has(candidate)) {
45
+ index += 1;
46
+ candidate = `${base}_${index}`;
47
+ }
48
+ return candidate;
49
+ }
50
+
51
+ function toPascalCase(value: string): string {
52
+ return value
53
+ .split(/[_-]+/)
54
+ .filter(Boolean)
55
+ .map((part) => part[0]?.toUpperCase() + part.slice(1))
56
+ .join("");
57
+ }
58
+
59
+ function isVarcharType(value: string): value is `varchar(${number})` {
60
+ return value.startsWith("varchar(");
61
+ }
62
+
63
+ function getModelName(table: AnyTable, namespace: string | null): string {
64
+ const base = toPascalCase(table.name);
65
+ if (!namespace) {
66
+ return base;
67
+ }
68
+ return `${base}_${sanitizeNamespace(namespace)}`;
69
+ }
70
+
71
+ function getPhysicalTableName(table: AnyTable, resolver?: NamingResolver): string {
72
+ return resolver ? resolver.getTableName(table.name) : table.name;
73
+ }
74
+
75
+ function getRelationName(
76
+ namespace: string | null,
77
+ from: string,
78
+ referenceName: string,
79
+ to: string,
80
+ ): string {
81
+ if (!namespace) {
82
+ return `${from}_${referenceName}_${to}`;
83
+ }
84
+ return `${namespace}_${from}_${referenceName}_${to}`;
85
+ }
86
+
87
+ function getForeignKeyMapName(
88
+ table: AnyTable,
89
+ relation: Relation,
90
+ _namespace: string | null,
91
+ resolver?: NamingResolver,
92
+ ): string {
93
+ if (resolver) {
94
+ return resolver.getForeignKeyName({
95
+ logicalTable: table.name,
96
+ logicalReferencedTable: relation.table.name,
97
+ referenceName: relation.name,
98
+ });
99
+ }
100
+ return `${table.name}_${relation.table.name}_${relation.name}_fk`;
101
+ }
102
+
103
+ function getIndexMapName(
104
+ indexName: string,
105
+ tableName: string,
106
+ namespace: string | null,
107
+ resolver?: NamingResolver,
108
+ unique?: boolean,
109
+ ): string {
110
+ if (!resolver) {
111
+ return namespace ? `${indexName}_${namespace}` : indexName;
112
+ }
113
+ return unique
114
+ ? resolver.getUniqueIndexName(indexName, tableName)
115
+ : resolver.getIndexName(indexName, tableName);
116
+ }
117
+
118
+ function getPrismaScalarType(
119
+ column: AnyColumn,
120
+ provider: SupportedDatabase,
121
+ sqliteStorageMode: SQLiteStorageMode,
122
+ ): { type: string; nativeType?: string } {
123
+ const internalIdType = provider === "sqlite" ? "Int" : "BigInt";
124
+
125
+ if (column.role === "internal-id") {
126
+ return { type: internalIdType };
127
+ }
128
+
129
+ if (column.role === "reference") {
130
+ return { type: internalIdType };
131
+ }
132
+
133
+ if (isVarcharType(column.type)) {
134
+ const length = parseVarchar(column.type);
135
+ if (provider === "postgresql" || provider === "mysql") {
136
+ return { type: "String", nativeType: `@db.VarChar(${length})` };
137
+ }
138
+ return { type: "String" };
139
+ }
140
+
141
+ switch (column.type) {
142
+ case "string":
143
+ return { type: "String" };
144
+ case "integer":
145
+ return { type: "Int" };
146
+ case "bigint":
147
+ if (provider === "sqlite" && sqliteStorageMode.bigintStorage === "blob") {
148
+ return { type: "Bytes" };
149
+ }
150
+ return { type: "BigInt" };
151
+ case "bool":
152
+ return { type: "Boolean" };
153
+ case "decimal":
154
+ if (provider === "sqlite") {
155
+ return { type: "Float" };
156
+ }
157
+ return { type: "Decimal" };
158
+ case "binary":
159
+ return { type: "Bytes" };
160
+ case "json":
161
+ if (provider === "postgresql") {
162
+ return { type: "Json", nativeType: "@db.Json" };
163
+ }
164
+ return { type: "Json" };
165
+ case "timestamp":
166
+ if (provider === "sqlite" && sqliteStorageMode.timestampStorage === "epoch-ms") {
167
+ return { type: "Int" };
168
+ }
169
+ return { type: "DateTime" };
170
+ case "date":
171
+ if (provider === "sqlite" && sqliteStorageMode.dateStorage === "epoch-ms") {
172
+ return { type: "Int" };
173
+ }
174
+ if (provider === "postgresql" || provider === "mysql") {
175
+ return { type: "DateTime", nativeType: "@db.Date" };
176
+ }
177
+ return { type: "DateTime" };
178
+ default: {
179
+ const exhaustiveCheck: never = column.type;
180
+ throw new Error(`Unsupported column type: ${exhaustiveCheck}`);
181
+ }
182
+ }
183
+ }
184
+
185
+ function formatDefaultValue(value: unknown): string {
186
+ if (value instanceof Date) {
187
+ return JSON.stringify(value.toISOString());
188
+ }
189
+ if (typeof value === "bigint") {
190
+ return value.toString();
191
+ }
192
+ return JSON.stringify(value);
193
+ }
194
+
195
+ function getColumnDefault(
196
+ column: AnyColumn,
197
+ provider: SupportedDatabase,
198
+ sqliteStorageMode: SQLiteStorageMode,
199
+ ): string | undefined {
200
+ if (!column.default) {
201
+ return undefined;
202
+ }
203
+
204
+ if ("value" in column.default) {
205
+ return `@default(${formatDefaultValue(column.default.value)})`;
206
+ }
207
+
208
+ if ("dbSpecial" in column.default && column.default.dbSpecial === "now") {
209
+ const scalar = getPrismaScalarType(column, provider, sqliteStorageMode);
210
+ if (scalar.type === "DateTime") {
211
+ return "@default(now())";
212
+ }
213
+ if (provider === "sqlite") {
214
+ const storage =
215
+ column.type === "date" ? sqliteStorageMode.dateStorage : sqliteStorageMode.timestampStorage;
216
+ if (storage === "epoch-ms") {
217
+ return `@default(dbgenerated("CURRENT_TIMESTAMP"))`;
218
+ }
219
+ }
220
+ }
221
+
222
+ return undefined;
223
+ }
224
+
225
+ function getColumnFieldName(
226
+ columnName: string,
227
+ usedNames: Set<string>,
228
+ ): { fieldName: string; needsMap: boolean } {
229
+ const isValid = isValidIdentifier(columnName);
230
+ const baseName = isValid ? columnName : sanitizeIdentifier(columnName);
231
+ const fieldName = ensureUniqueName(baseName, usedNames);
232
+ return { fieldName, needsMap: fieldName !== columnName };
233
+ }
234
+
235
+ function getRelationFieldName(baseName: string, usedNames: Set<string>): string {
236
+ const validBase = isValidIdentifier(baseName) ? baseName : sanitizeIdentifier(baseName);
237
+ return ensureUniqueName(validBase, usedNames);
238
+ }
239
+
240
+ function isRelationOptional(relation: Relation, columnFieldNames: Map<string, AnyColumn>): boolean {
241
+ for (const [localColumn] of relation.on) {
242
+ const column = columnFieldNames.get(localColumn);
243
+ if (column?.isNullable) {
244
+ return true;
245
+ }
246
+ }
247
+ return false;
248
+ }
249
+
250
+ function getColumnFieldMappings(table: AnyTable): {
251
+ fieldNameByColumn: Map<string, string>;
252
+ columnByName: Map<string, AnyColumn>;
253
+ } {
254
+ const fieldNameByColumn = new Map<string, string>();
255
+ const columnByName = new Map<string, AnyColumn>();
256
+ const usedNames = new Set<string>();
257
+
258
+ for (const column of Object.values(table.columns)) {
259
+ const { fieldName } = getColumnFieldName(column.name, usedNames);
260
+ usedNames.add(fieldName);
261
+ fieldNameByColumn.set(column.name, fieldName);
262
+ columnByName.set(column.name, column);
263
+ }
264
+
265
+ return { fieldNameByColumn, columnByName };
266
+ }
267
+
268
+ function areInverseRelations(one: Relation, many: Relation): boolean {
269
+ if (one.type !== "one" || many.type !== "many") {
270
+ return false;
271
+ }
272
+ if (one.referencer !== many.table || one.table !== many.referencer) {
273
+ return false;
274
+ }
275
+ return one.on.every(([left, right]) =>
276
+ many.on.some(([manyLeft, manyRight]) => manyLeft === right && manyRight === left),
277
+ );
278
+ }
279
+
280
+ function findMatchingManyRelation(one: Relation): Relation | undefined {
281
+ for (const relation of Object.values(one.table.relations)) {
282
+ if (relation.type !== "many") {
283
+ continue;
284
+ }
285
+ if (areInverseRelations(one, relation)) {
286
+ return relation;
287
+ }
288
+ }
289
+ return undefined;
290
+ }
291
+
292
+ function findMatchingOneRelation(many: Relation): Relation | undefined {
293
+ for (const relation of Object.values(many.table.relations)) {
294
+ if (relation.type !== "one") {
295
+ continue;
296
+ }
297
+ if (areInverseRelations(relation, many)) {
298
+ return relation;
299
+ }
300
+ }
301
+ return undefined;
302
+ }
303
+
304
+ function generateColumnFields(
305
+ table: AnyTable,
306
+ provider: SupportedDatabase,
307
+ sqliteStorageMode: SQLiteStorageMode,
308
+ fieldNameByColumn: Map<string, string>,
309
+ resolver?: NamingResolver,
310
+ ): string[] {
311
+ const lines: string[] = [];
312
+
313
+ for (const column of Object.values(table.columns)) {
314
+ const fieldName = fieldNameByColumn.get(column.name)!;
315
+ const scalar = getPrismaScalarType(column, provider, sqliteStorageMode);
316
+ const isOptional = column.isNullable;
317
+
318
+ const attributes: string[] = [];
319
+
320
+ if (column.role === "internal-id") {
321
+ attributes.push("@id", "@default(autoincrement())");
322
+ }
323
+
324
+ if (column.role === "external-id") {
325
+ attributes.push("@unique", "@default(cuid())");
326
+ }
327
+
328
+ const defaultValue = getColumnDefault(column, provider, sqliteStorageMode);
329
+ if (defaultValue) {
330
+ attributes.push(defaultValue);
331
+ }
332
+
333
+ if (scalar.nativeType) {
334
+ attributes.push(scalar.nativeType);
335
+ }
336
+
337
+ const physicalName = resolver ? resolver.getColumnName(table.name, column.name) : column.name;
338
+ if (fieldName !== physicalName) {
339
+ attributes.push(`@map("${physicalName}")`);
340
+ }
341
+
342
+ const suffix = isOptional ? "?" : "";
343
+ const attrSuffix = attributes.length > 0 ? ` ${attributes.join(" ")}` : "";
344
+ lines.push(` ${fieldName} ${scalar.type}${suffix}${attrSuffix}`);
345
+ }
346
+
347
+ return lines;
348
+ }
349
+
350
+ function generateRelationFields(
351
+ table: AnyTable,
352
+ namespace: string | null,
353
+ fieldNameByColumn: Map<string, string>,
354
+ columnByName: Map<string, AnyColumn>,
355
+ fieldNameByTableColumn: Map<AnyTable, Map<string, string>>,
356
+ resolver?: NamingResolver,
357
+ ): string[] {
358
+ const lines: string[] = [];
359
+ const usedNames = new Set<string>(fieldNameByColumn.values());
360
+ const relations = Object.values(table.relations)
361
+ .slice()
362
+ .sort((a, b) => a.name.localeCompare(b.name));
363
+
364
+ for (const relation of relations) {
365
+ if (relation.type !== "one") {
366
+ continue;
367
+ }
368
+
369
+ const fieldName = getRelationFieldName(relation.name, usedNames);
370
+ usedNames.add(fieldName);
371
+
372
+ const relationName = getRelationName(namespace, table.name, relation.name, relation.table.name);
373
+
374
+ const relatedModel = getModelName(relation.table, namespace);
375
+ const localFields = relation.on.map(([left]) => fieldNameByColumn.get(left) ?? left);
376
+ const referenceFields = relation.on.map(([, right]) => {
377
+ const actualRight = right === "id" ? "_internalId" : right;
378
+ const refFieldNames = fieldNameByTableColumn.get(relation.table);
379
+ return refFieldNames?.get(actualRight) ?? actualRight;
380
+ });
381
+
382
+ const fkMapName = getForeignKeyMapName(table, relation, namespace, resolver);
383
+
384
+ const relationParts = [
385
+ `"${relationName}"`,
386
+ `fields: [${localFields.join(", ")}]`,
387
+ `references: [${referenceFields.join(", ")}]`,
388
+ `map: "${fkMapName}"`,
389
+ ];
390
+
391
+ const optional = isRelationOptional(relation, columnByName);
392
+ const suffix = optional ? "?" : "";
393
+
394
+ lines.push(` ${fieldName} ${relatedModel}${suffix} @relation(${relationParts.join(", ")})`);
395
+ }
396
+
397
+ for (const relation of relations) {
398
+ if (relation.type !== "many") {
399
+ continue;
400
+ }
401
+
402
+ const matchingOne = findMatchingOneRelation(relation);
403
+ const relationName = matchingOne
404
+ ? getRelationName(
405
+ namespace,
406
+ matchingOne.referencer.name,
407
+ matchingOne.name,
408
+ matchingOne.table.name,
409
+ )
410
+ : getRelationName(namespace, relation.referencer.name, relation.name, relation.table.name);
411
+
412
+ const fieldName = getRelationFieldName(relation.name, usedNames);
413
+ usedNames.add(fieldName);
414
+
415
+ const relatedModel = getModelName(relation.table, namespace);
416
+ lines.push(` ${fieldName} ${relatedModel}[] @relation("${relationName}")`);
417
+ }
418
+
419
+ const inverseCandidates: Relation[] = [];
420
+ for (const sourceTable of fieldNameByTableColumn.keys()) {
421
+ for (const rel of Object.values(sourceTable.relations)) {
422
+ if (rel.type === "one" && rel.table === table) {
423
+ inverseCandidates.push(rel);
424
+ }
425
+ }
426
+ }
427
+
428
+ for (const rel of inverseCandidates.sort((a, b) => a.name.localeCompare(b.name))) {
429
+ const matchingMany = findMatchingManyRelation(rel);
430
+ if (matchingMany) {
431
+ continue;
432
+ }
433
+
434
+ const baseName = rel.referencer.name;
435
+ let fieldName = baseName;
436
+ if (usedNames.has(fieldName)) {
437
+ fieldName = `${baseName}_${rel.name}`;
438
+ }
439
+ fieldName = getRelationFieldName(fieldName, usedNames);
440
+ usedNames.add(fieldName);
441
+
442
+ const relationName = getRelationName(namespace, rel.referencer.name, rel.name, rel.table.name);
443
+
444
+ const relatedModel = getModelName(rel.referencer, namespace);
445
+ lines.push(` ${fieldName} ${relatedModel}[] @relation("${relationName}")`);
446
+ }
447
+
448
+ return lines;
449
+ }
450
+
451
+ function generateModel(
452
+ table: AnyTable,
453
+ namespace: string | null,
454
+ provider: SupportedDatabase,
455
+ sqliteStorageMode: SQLiteStorageMode,
456
+ fieldNameByTableColumn: Map<AnyTable, Map<string, string>>,
457
+ columnByTableName: Map<AnyTable, Map<string, AnyColumn>>,
458
+ resolver?: NamingResolver,
459
+ ): string {
460
+ const modelName = getModelName(table, namespace);
461
+ const physicalName = getPhysicalTableName(table, resolver);
462
+
463
+ const fieldNameByColumn = fieldNameByTableColumn.get(table)!;
464
+ const columnByName = columnByTableName.get(table)!;
465
+
466
+ const fieldLines = [
467
+ ...generateColumnFields(table, provider, sqliteStorageMode, fieldNameByColumn, resolver),
468
+ ...generateRelationFields(
469
+ table,
470
+ namespace,
471
+ fieldNameByColumn,
472
+ columnByName,
473
+ fieldNameByTableColumn,
474
+ resolver,
475
+ ),
476
+ ];
477
+
478
+ const indexLines: string[] = [];
479
+ const sortedIndexes = Object.values(table.indexes)
480
+ .slice()
481
+ .sort((a, b) => a.name.localeCompare(b.name));
482
+
483
+ for (const index of sortedIndexes) {
484
+ const fields = index.columnNames
485
+ .map((name) => fieldNameByColumn.get(name as string) ?? name)
486
+ .join(", ");
487
+ const mapName = getIndexMapName(index.name, table.name, namespace, resolver, index.unique);
488
+ const directive = index.unique ? "@@unique" : "@@index";
489
+ indexLines.push(` ${directive}([${fields}], map: "${mapName}")`);
490
+ }
491
+
492
+ indexLines.push(` @@map("${physicalName}")`);
493
+
494
+ const lines = [`model ${modelName} {`, ...fieldLines, ...indexLines, `}`];
495
+ return lines.join("\n");
496
+ }
497
+
498
+ export function generatePrismaSchema(
499
+ fragments: { namespace: string | null; schema: AnySchema }[],
500
+ provider: SupportedDatabase,
501
+ options?: GeneratePrismaSchemaOptions,
502
+ ): string {
503
+ const sqliteStorageMode =
504
+ options?.sqliteStorageMode ??
505
+ (provider === "sqlite" ? sqliteStoragePrisma : sqliteStorageDefault);
506
+ const namingStrategy = options?.namingStrategy ?? defaultNamingStrategyForDatabase(provider);
507
+ const namespaces = Array.from(new Set(fragments.map((fragment) => fragment.namespace)));
508
+ const sortedNamespaces = namespaces.sort((a, b) => {
509
+ const aKey = a ?? "";
510
+ const bKey = b ?? "";
511
+ if (aKey === "") {
512
+ return -1;
513
+ }
514
+ if (bKey === "") {
515
+ return 1;
516
+ }
517
+ return aKey.localeCompare(bKey);
518
+ });
519
+
520
+ const sortedFragments = sortedNamespaces
521
+ .map((namespace) => fragments.find((fragment) => fragment.namespace === namespace))
522
+ .filter((fragment): fragment is { namespace: string | null; schema: AnySchema } => !!fragment);
523
+
524
+ const fieldNameByTableColumn = new Map<AnyTable, Map<string, string>>();
525
+ const columnByTableName = new Map<AnyTable, Map<string, AnyColumn>>();
526
+
527
+ for (const fragment of sortedFragments) {
528
+ for (const table of Object.values(fragment.schema.tables)) {
529
+ const mapping = getColumnFieldMappings(table);
530
+ fieldNameByTableColumn.set(table, mapping.fieldNameByColumn);
531
+ columnByTableName.set(table, mapping.columnByName);
532
+ }
533
+ }
534
+
535
+ const headerNamespaces = sortedNamespaces.filter(Boolean);
536
+ const headerSuffix = headerNamespaces.length > 0 ? headerNamespaces.join(", ") : "(internal)";
537
+ const lines: string[] = [
538
+ "// Generated by Fragno Prisma adapter.",
539
+ `// Provider: ${provider}`,
540
+ `// Namespaces: ${headerSuffix}`,
541
+ "",
542
+ ];
543
+
544
+ const models: string[] = [];
545
+
546
+ for (const fragment of sortedFragments) {
547
+ const resolver = createNamingResolver(fragment.schema, fragment.namespace, namingStrategy);
548
+ const tables = Object.values(fragment.schema.tables)
549
+ .slice()
550
+ .sort((a, b) => {
551
+ const aName = getPhysicalTableName(a, resolver);
552
+ const bName = getPhysicalTableName(b, resolver);
553
+ return aName.localeCompare(bName);
554
+ });
555
+
556
+ for (const table of tables) {
557
+ models.push(
558
+ generateModel(
559
+ table,
560
+ fragment.namespace,
561
+ provider,
562
+ sqliteStorageMode,
563
+ fieldNameByTableColumn,
564
+ columnByTableName,
565
+ resolver,
566
+ ),
567
+ );
568
+ }
569
+ }
570
+
571
+ lines.push(models.join("\n\n"));
572
+ return lines.join("\n");
573
+ }
@@ -0,0 +1,106 @@
1
+ import fs from "node:fs";
2
+ import { createRequire } from "node:module";
3
+ import path from "node:path";
4
+ import { SqliteDialect } from "kysely";
5
+ import { SqlAdapter } from "../adapters/generic-sql/generic-sql-adapter";
6
+ import { BetterSQLite3DriverConfig } from "../adapters/generic-sql/driver-config";
7
+ import type { DatabaseAdapter } from "../adapters/adapters";
8
+ import type { AnySchema } from "../schema/create";
9
+ type DatabaseAdapterConfig = {
10
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
+ databaseAdapter?: DatabaseAdapter<any>;
12
+ databaseNamespace?: string | null;
13
+ };
14
+
15
+ type BetterSqlite3Constructor = typeof import("better-sqlite3");
16
+
17
+ const createNodeRequire = (): NodeRequire | null => {
18
+ try {
19
+ const metaUrl = typeof import.meta !== "undefined" ? import.meta.url : undefined;
20
+ if (!metaUrl || typeof metaUrl !== "string") {
21
+ return null;
22
+ }
23
+ return createRequire(metaUrl);
24
+ } catch {
25
+ return null;
26
+ }
27
+ };
28
+
29
+ const loadBetterSqlite3 = (): BetterSqlite3Constructor | null => {
30
+ const requireFn = createNodeRequire();
31
+ if (!requireFn) {
32
+ return null;
33
+ }
34
+ try {
35
+ const module = requireFn("better-sqlite3");
36
+ return (module.default ?? module) as BetterSqlite3Constructor;
37
+ } catch {
38
+ return null;
39
+ }
40
+ };
41
+
42
+ const betterSqlite3Constructor = loadBetterSqlite3();
43
+
44
+ const defaultDataDir = (): string => {
45
+ const configured = process.env["FRAGNO_DATA_DIR"];
46
+ if (configured && configured.trim().length > 0) {
47
+ return configured;
48
+ }
49
+ return path.join(process.env["HOME"] ?? process.cwd(), ".fragno");
50
+ };
51
+
52
+ const sanitizeFileSegment = (name: string): string => {
53
+ const sanitized = name.replace(/[^a-z0-9-]/gi, "_");
54
+ return sanitized.length > 0 ? sanitized : "fragno";
55
+ };
56
+
57
+ const resolveSqliteDatabasePath = <TSchema extends AnySchema>(
58
+ options: DatabaseAdapterConfig,
59
+ schema: TSchema,
60
+ ): string => {
61
+ const baseName =
62
+ typeof options.databaseNamespace === "string" && options.databaseNamespace.length > 0
63
+ ? options.databaseNamespace
64
+ : schema.name;
65
+ const fileName = `${sanitizeFileSegment(baseName)}.sqlite`;
66
+ return path.join(defaultDataDir(), fileName);
67
+ };
68
+
69
+ const createDefaultSqliteAdapter = <TSchema extends AnySchema>(
70
+ options: DatabaseAdapterConfig,
71
+ schema: TSchema,
72
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
73
+ ): DatabaseAdapter<any> | null => {
74
+ if (!betterSqlite3Constructor) {
75
+ return null;
76
+ }
77
+
78
+ const dbPath = resolveSqliteDatabasePath(options, schema);
79
+ fs.mkdirSync(path.dirname(dbPath), { recursive: true });
80
+
81
+ const dialect = new SqliteDialect({
82
+ database: new betterSqlite3Constructor(dbPath),
83
+ });
84
+ const driverConfig = new BetterSQLite3DriverConfig();
85
+ return new SqlAdapter({ dialect, driverConfig });
86
+ };
87
+
88
+ export const resolveDatabaseAdapter = <TSchema extends AnySchema>(
89
+ options: DatabaseAdapterConfig,
90
+ schema: TSchema,
91
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
92
+ ): DatabaseAdapter<any> => {
93
+ if (options.databaseAdapter) {
94
+ return options.databaseAdapter;
95
+ }
96
+
97
+ const defaultAdapter = createDefaultSqliteAdapter(options, schema);
98
+ if (!defaultAdapter) {
99
+ throw new Error(
100
+ "Database fragment requires options.databaseAdapter, or install better-sqlite3 to use the default SQLite adapter.",
101
+ );
102
+ }
103
+
104
+ options.databaseAdapter = defaultAdapter;
105
+ return defaultAdapter;
106
+ };