@uql/core 1.0.12 → 3.0.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 (466) hide show
  1. package/CHANGELOG.md +243 -0
  2. package/LICENSE.md +2 -2
  3. package/dist/CHANGELOG.md +186 -0
  4. package/dist/README.md +413 -0
  5. package/dist/browser/http/bus.d.ts +3 -0
  6. package/dist/browser/http/bus.js +14 -0
  7. package/dist/browser/http/http.d.ts +6 -0
  8. package/dist/browser/http/http.js +45 -0
  9. package/dist/browser/http/index.d.ts +2 -0
  10. package/dist/browser/http/index.js +3 -0
  11. package/dist/browser/index.d.ts +4 -0
  12. package/dist/browser/index.js +5 -0
  13. package/dist/browser/options.d.ts +4 -0
  14. package/dist/browser/options.js +14 -0
  15. package/dist/browser/querier/genericClientRepository.d.ts +17 -0
  16. package/dist/browser/querier/genericClientRepository.js +39 -0
  17. package/dist/browser/querier/httpQuerier.d.ts +20 -0
  18. package/dist/browser/querier/httpQuerier.js +71 -0
  19. package/dist/browser/querier/index.d.ts +3 -0
  20. package/dist/browser/querier/index.js +4 -0
  21. package/dist/browser/querier/querier.util.d.ts +2 -0
  22. package/dist/browser/querier/querier.util.js +17 -0
  23. package/dist/browser/type/clientQuerier.d.ts +16 -0
  24. package/dist/browser/type/clientQuerier.js +2 -0
  25. package/dist/browser/type/clientQuerierPool.d.ts +4 -0
  26. package/dist/browser/type/clientQuerierPool.js +2 -0
  27. package/dist/browser/type/clientRepository.d.ts +13 -0
  28. package/dist/browser/type/clientRepository.js +2 -0
  29. package/dist/browser/type/index.d.ts +4 -0
  30. package/dist/browser/type/index.js +5 -0
  31. package/dist/browser/type/request.d.ts +28 -0
  32. package/dist/browser/type/request.js +2 -0
  33. package/dist/browser/uql-browser.min.js +2150 -0
  34. package/dist/browser/uql-browser.min.js.map +1 -0
  35. package/dist/dialect/abstractDialect.d.ts +16 -0
  36. package/dist/dialect/abstractDialect.js +28 -0
  37. package/dist/dialect/abstractSqlDialect.d.ts +47 -0
  38. package/dist/dialect/abstractSqlDialect.js +650 -0
  39. package/dist/dialect/index.d.ts +3 -0
  40. package/dist/dialect/index.js +4 -0
  41. package/dist/dialect/queryContext.d.ts +48 -0
  42. package/dist/dialect/queryContext.js +65 -0
  43. package/{entity → dist/entity}/decorator/definition.d.ts +3 -3
  44. package/dist/entity/decorator/definition.js +214 -0
  45. package/{entity → dist/entity}/decorator/entity.d.ts +1 -1
  46. package/dist/entity/decorator/entity.js +7 -0
  47. package/{entity → dist/entity}/decorator/field.d.ts +1 -1
  48. package/dist/entity/decorator/field.js +8 -0
  49. package/{entity → dist/entity}/decorator/id.d.ts +1 -1
  50. package/dist/entity/decorator/id.js +8 -0
  51. package/dist/entity/decorator/index.d.ts +5 -0
  52. package/dist/entity/decorator/index.js +6 -0
  53. package/{entity → dist/entity}/decorator/relation.d.ts +1 -1
  54. package/dist/entity/decorator/relation.js +20 -0
  55. package/dist/entity/index.d.ts +1 -0
  56. package/dist/entity/index.js +2 -0
  57. package/dist/express/index.d.ts +2 -0
  58. package/dist/express/index.js +3 -0
  59. package/dist/express/querierMiddleware.d.ts +26 -0
  60. package/dist/express/querierMiddleware.js +190 -0
  61. package/dist/express/query.util.d.ts +2 -0
  62. package/dist/express/query.util.js +19 -0
  63. package/dist/index.d.ts +9 -0
  64. package/dist/index.js +10 -0
  65. package/dist/maria/index.d.ts +3 -0
  66. package/dist/maria/index.js +4 -0
  67. package/dist/maria/mariaDialect.d.ts +8 -0
  68. package/dist/maria/mariaDialect.js +38 -0
  69. package/dist/maria/mariaQuerierPool.test.d.ts +5 -0
  70. package/dist/maria/mariaQuerierPool.test.js +19 -0
  71. package/dist/maria/mariadbQuerier.d.ts +17 -0
  72. package/dist/maria/mariadbQuerier.js +39 -0
  73. package/dist/maria/mariadbQuerier.test.d.ts +4 -0
  74. package/dist/maria/mariadbQuerier.test.js +19 -0
  75. package/dist/maria/mariadbQuerierPool.d.ts +10 -0
  76. package/dist/maria/mariadbQuerierPool.js +17 -0
  77. package/dist/migrate/cli.d.ts +2 -0
  78. package/dist/migrate/cli.js +254 -0
  79. package/dist/migrate/generator/index.d.ts +4 -0
  80. package/dist/migrate/generator/index.js +5 -0
  81. package/dist/migrate/generator/mongoSchemaGenerator.d.ts +12 -0
  82. package/dist/migrate/generator/mongoSchemaGenerator.js +100 -0
  83. package/dist/migrate/generator/mysqlSchemaGenerator.d.ts +14 -0
  84. package/dist/migrate/generator/mysqlSchemaGenerator.js +81 -0
  85. package/dist/migrate/generator/postgresSchemaGenerator.d.ts +18 -0
  86. package/dist/migrate/generator/postgresSchemaGenerator.js +111 -0
  87. package/dist/migrate/generator/sqliteSchemaGenerator.d.ts +14 -0
  88. package/dist/migrate/generator/sqliteSchemaGenerator.js +68 -0
  89. package/dist/migrate/index.d.ts +12 -0
  90. package/dist/migrate/index.js +17 -0
  91. package/dist/migrate/introspection/index.d.ts +4 -0
  92. package/dist/migrate/introspection/index.js +5 -0
  93. package/dist/migrate/introspection/mongoIntrospector.d.ts +8 -0
  94. package/dist/migrate/introspection/mongoIntrospector.js +46 -0
  95. package/dist/migrate/introspection/mysqlIntrospector.d.ts +25 -0
  96. package/dist/migrate/introspection/mysqlIntrospector.js +220 -0
  97. package/dist/migrate/introspection/postgresIntrospector.d.ts +21 -0
  98. package/dist/migrate/introspection/postgresIntrospector.js +269 -0
  99. package/dist/migrate/introspection/sqliteIntrospector.d.ts +23 -0
  100. package/dist/migrate/introspection/sqliteIntrospector.js +212 -0
  101. package/dist/migrate/migrator-mongo.test.d.ts +1 -0
  102. package/dist/migrate/migrator-mongo.test.js +54 -0
  103. package/dist/migrate/migrator.d.ts +133 -0
  104. package/dist/migrate/migrator.js +600 -0
  105. package/dist/migrate/migrator.test.d.ts +1 -0
  106. package/dist/migrate/migrator.test.js +106 -0
  107. package/dist/migrate/schemaGenerator.d.ts +78 -0
  108. package/dist/migrate/schemaGenerator.js +363 -0
  109. package/dist/migrate/storage/databaseStorage.d.ts +24 -0
  110. package/dist/migrate/storage/databaseStorage.js +77 -0
  111. package/dist/migrate/storage/index.d.ts +2 -0
  112. package/dist/migrate/storage/index.js +3 -0
  113. package/dist/migrate/storage/jsonStorage.d.ts +15 -0
  114. package/dist/migrate/storage/jsonStorage.js +51 -0
  115. package/dist/migrate/type.d.ts +1 -0
  116. package/dist/migrate/type.js +2 -0
  117. package/dist/mongo/index.d.ts +3 -0
  118. package/dist/mongo/index.js +4 -0
  119. package/dist/mongo/mongoDialect.d.ts +34 -0
  120. package/dist/mongo/mongoDialect.js +163 -0
  121. package/dist/mongo/mongodbQuerier.d.ts +28 -0
  122. package/dist/mongo/mongodbQuerier.js +204 -0
  123. package/dist/mongo/mongodbQuerier.test.d.ts +1 -0
  124. package/dist/mongo/mongodbQuerier.test.js +36 -0
  125. package/dist/mongo/mongodbQuerierPool.d.ts +10 -0
  126. package/dist/mongo/mongodbQuerierPool.js +20 -0
  127. package/dist/mongo/mongodbQuerierPool.test.d.ts +1 -0
  128. package/dist/mongo/mongodbQuerierPool.test.js +21 -0
  129. package/dist/mysql/index.d.ts +3 -0
  130. package/dist/mysql/index.js +4 -0
  131. package/dist/mysql/mysql2Querier.d.ts +17 -0
  132. package/dist/mysql/mysql2Querier.js +43 -0
  133. package/dist/mysql/mysql2Querier.test.d.ts +4 -0
  134. package/dist/mysql/mysql2Querier.test.js +16 -0
  135. package/dist/mysql/mysql2QuerierPool.d.ts +10 -0
  136. package/dist/mysql/mysql2QuerierPool.js +17 -0
  137. package/dist/mysql/mysql2QuerierPool.test.d.ts +5 -0
  138. package/dist/mysql/mysql2QuerierPool.test.js +16 -0
  139. package/dist/mysql/mysqlDialect.d.ts +5 -0
  140. package/dist/mysql/mysqlDialect.js +15 -0
  141. package/dist/namingStrategy/defaultNamingStrategy.d.ts +9 -0
  142. package/dist/namingStrategy/defaultNamingStrategy.js +15 -0
  143. package/dist/namingStrategy/index.d.ts +2 -0
  144. package/dist/namingStrategy/index.js +3 -0
  145. package/dist/namingStrategy/snakeCaseNamingStrategy.d.ts +8 -0
  146. package/dist/namingStrategy/snakeCaseNamingStrategy.js +14 -0
  147. package/{options.d.ts → dist/options.d.ts} +1 -1
  148. package/dist/options.js +14 -0
  149. package/dist/package.json +131 -0
  150. package/dist/postgres/index.d.ts +3 -0
  151. package/dist/postgres/index.js +4 -0
  152. package/dist/postgres/pgQuerier.d.ts +17 -0
  153. package/dist/postgres/pgQuerier.js +39 -0
  154. package/dist/postgres/pgQuerier.test.d.ts +4 -0
  155. package/dist/postgres/pgQuerier.test.js +20 -0
  156. package/dist/postgres/pgQuerierPool.d.ts +10 -0
  157. package/dist/postgres/pgQuerierPool.js +17 -0
  158. package/dist/postgres/pgQuerierPool.test.d.ts +5 -0
  159. package/dist/postgres/pgQuerierPool.test.js +23 -0
  160. package/dist/postgres/postgresDialect.d.ts +13 -0
  161. package/dist/postgres/postgresDialect.js +110 -0
  162. package/dist/querier/abstractQuerier-test.d.ts +45 -0
  163. package/dist/querier/abstractQuerier-test.js +461 -0
  164. package/dist/querier/abstractQuerier.d.ts +50 -0
  165. package/dist/querier/abstractQuerier.js +278 -0
  166. package/dist/querier/abstractQuerierPool-test.d.ts +9 -0
  167. package/dist/querier/abstractQuerierPool-test.js +18 -0
  168. package/dist/querier/abstractQuerierPool.d.ts +14 -0
  169. package/dist/querier/abstractQuerierPool.js +9 -0
  170. package/dist/querier/abstractSqlQuerier-test.d.ts +9 -0
  171. package/dist/querier/abstractSqlQuerier-test.js +16 -0
  172. package/dist/querier/abstractSqlQuerier.d.ts +28 -0
  173. package/dist/querier/abstractSqlQuerier.js +133 -0
  174. package/dist/querier/decorator/index.d.ts +3 -0
  175. package/dist/querier/decorator/index.js +4 -0
  176. package/dist/querier/decorator/injectQuerier.d.ts +3 -0
  177. package/dist/querier/decorator/injectQuerier.js +33 -0
  178. package/dist/querier/decorator/serialized.d.ts +6 -0
  179. package/dist/querier/decorator/serialized.js +14 -0
  180. package/{querier → dist/querier}/decorator/transactional.d.ts +1 -1
  181. package/dist/querier/decorator/transactional.js +48 -0
  182. package/dist/querier/index.d.ts +4 -0
  183. package/dist/querier/index.js +5 -0
  184. package/dist/repository/genericRepository.d.ts +20 -0
  185. package/dist/repository/genericRepository.js +51 -0
  186. package/dist/repository/index.d.ts +1 -0
  187. package/dist/repository/index.js +2 -0
  188. package/dist/sqlite/index.d.ts +3 -0
  189. package/dist/sqlite/index.js +4 -0
  190. package/dist/sqlite/sqliteDialect.d.ts +10 -0
  191. package/dist/sqlite/sqliteDialect.js +48 -0
  192. package/dist/sqlite/sqliteQuerier.d.ts +15 -0
  193. package/dist/sqlite/sqliteQuerier.js +33 -0
  194. package/dist/sqlite/sqliteQuerier.test.d.ts +5 -0
  195. package/dist/sqlite/sqliteQuerier.test.js +19 -0
  196. package/dist/sqlite/sqliteQuerierPool.d.ts +14 -0
  197. package/dist/sqlite/sqliteQuerierPool.js +34 -0
  198. package/dist/sqlite/sqliteQuerierPool.test.d.ts +5 -0
  199. package/dist/sqlite/sqliteQuerierPool.test.js +10 -0
  200. package/dist/test/entityMock.d.ts +164 -0
  201. package/dist/test/entityMock.js +554 -0
  202. package/dist/test/index.d.ts +3 -0
  203. package/dist/test/index.js +4 -0
  204. package/dist/test/it.util.d.ts +4 -0
  205. package/dist/test/it.util.js +55 -0
  206. package/dist/test/spec.util.d.ts +14 -0
  207. package/dist/test/spec.util.js +50 -0
  208. package/{type → dist/type}/entity.d.ts +97 -4
  209. package/dist/type/entity.js +5 -0
  210. package/dist/type/index.d.ts +9 -0
  211. package/dist/type/index.js +10 -0
  212. package/dist/type/migration.d.ts +213 -0
  213. package/dist/type/migration.js +2 -0
  214. package/dist/type/namingStrategy.d.ts +17 -0
  215. package/dist/type/namingStrategy.js +2 -0
  216. package/dist/type/querier.d.ts +96 -0
  217. package/dist/type/querier.js +11 -0
  218. package/{type → dist/type}/querierPool.d.ts +7 -3
  219. package/dist/type/querierPool.js +2 -0
  220. package/{type → dist/type}/query.d.ts +170 -108
  221. package/dist/type/query.js +9 -0
  222. package/{type → dist/type}/repository.d.ts +42 -35
  223. package/dist/type/repository.js +2 -0
  224. package/{type → dist/type}/universalQuerier.d.ts +50 -28
  225. package/dist/type/universalQuerier.js +2 -0
  226. package/dist/type/utility.d.ts +13 -0
  227. package/dist/type/utility.js +2 -0
  228. package/dist/util/dialect.util.d.ts +12 -0
  229. package/dist/util/dialect.util.js +93 -0
  230. package/dist/util/index.d.ts +5 -0
  231. package/dist/util/index.js +6 -0
  232. package/dist/util/object.util.d.ts +7 -0
  233. package/dist/util/object.util.js +19 -0
  234. package/dist/util/raw.d.ts +8 -0
  235. package/dist/util/raw.js +11 -0
  236. package/dist/util/sql.util.d.ts +13 -0
  237. package/dist/util/sql.util.js +78 -0
  238. package/{util → dist/util}/string.util.d.ts +1 -0
  239. package/dist/util/string.util.js +34 -0
  240. package/package.json +84 -16
  241. package/src/@types/index.d.ts +1 -0
  242. package/src/@types/jest.d.ts +6 -0
  243. package/src/browser/http/bus.spec.ts +22 -0
  244. package/src/browser/http/bus.ts +17 -0
  245. package/src/browser/http/http.spec.ts +70 -0
  246. package/src/browser/http/http.ts +55 -0
  247. package/src/browser/http/index.ts +2 -0
  248. package/src/browser/index.ts +4 -0
  249. package/src/browser/options.spec.ts +37 -0
  250. package/src/browser/options.ts +18 -0
  251. package/src/browser/querier/genericClientRepository.spec.ts +105 -0
  252. package/src/browser/querier/genericClientRepository.ts +49 -0
  253. package/src/browser/querier/httpQuerier.ts +82 -0
  254. package/src/browser/querier/index.ts +3 -0
  255. package/src/browser/querier/querier.util.spec.ts +35 -0
  256. package/src/browser/querier/querier.util.ts +18 -0
  257. package/src/browser/type/clientQuerier.ts +45 -0
  258. package/src/browser/type/clientQuerierPool.ts +5 -0
  259. package/src/browser/type/clientRepository.ts +22 -0
  260. package/src/browser/type/index.ts +4 -0
  261. package/src/browser/type/request.ts +25 -0
  262. package/src/dialect/abstractDialect.ts +28 -0
  263. package/src/dialect/abstractSqlDialect-spec.ts +1309 -0
  264. package/src/dialect/abstractSqlDialect.ts +805 -0
  265. package/src/dialect/index.ts +3 -0
  266. package/src/dialect/namingStrategy.spec.ts +52 -0
  267. package/src/dialect/queryContext.ts +69 -0
  268. package/src/entity/decorator/definition.spec.ts +736 -0
  269. package/src/entity/decorator/definition.ts +265 -0
  270. package/src/entity/decorator/entity.ts +8 -0
  271. package/src/entity/decorator/field.ts +9 -0
  272. package/src/entity/decorator/id.ts +9 -0
  273. package/src/entity/decorator/index.ts +5 -0
  274. package/src/entity/decorator/relation.spec.ts +41 -0
  275. package/src/entity/decorator/relation.ts +34 -0
  276. package/src/entity/index.ts +1 -0
  277. package/src/express/@types/express.d.ts +8 -0
  278. package/src/express/@types/index.d.ts +1 -0
  279. package/src/express/index.ts +2 -0
  280. package/src/express/querierMiddleware.ts +217 -0
  281. package/src/express/query.util.spec.ts +40 -0
  282. package/src/express/query.util.ts +21 -0
  283. package/src/index.ts +9 -0
  284. package/src/maria/index.ts +3 -0
  285. package/src/maria/mariaDialect.spec.ts +207 -0
  286. package/src/maria/mariaDialect.ts +42 -0
  287. package/src/maria/mariaQuerierPool.test.ts +23 -0
  288. package/src/maria/mariadbQuerier.test.ts +23 -0
  289. package/src/maria/mariadbQuerier.ts +45 -0
  290. package/src/maria/mariadbQuerierPool.ts +21 -0
  291. package/src/migrate/cli.ts +301 -0
  292. package/src/migrate/generator/index.ts +4 -0
  293. package/src/migrate/generator/mongoSchemaGenerator.spec.ts +112 -0
  294. package/src/migrate/generator/mongoSchemaGenerator.ts +115 -0
  295. package/src/migrate/generator/mysqlSchemaGenerator.spec.ts +34 -0
  296. package/src/migrate/generator/mysqlSchemaGenerator.ts +92 -0
  297. package/src/migrate/generator/postgresSchemaGenerator.spec.ts +44 -0
  298. package/src/migrate/generator/postgresSchemaGenerator.ts +127 -0
  299. package/src/migrate/generator/sqliteSchemaGenerator.spec.ts +33 -0
  300. package/src/migrate/generator/sqliteSchemaGenerator.ts +81 -0
  301. package/src/migrate/index.ts +41 -0
  302. package/src/migrate/introspection/index.ts +4 -0
  303. package/src/migrate/introspection/mongoIntrospector.spec.ts +75 -0
  304. package/src/migrate/introspection/mongoIntrospector.ts +47 -0
  305. package/src/migrate/introspection/mysqlIntrospector.spec.ts +113 -0
  306. package/src/migrate/introspection/mysqlIntrospector.ts +278 -0
  307. package/src/migrate/introspection/postgresIntrospector.spec.ts +112 -0
  308. package/src/migrate/introspection/postgresIntrospector.ts +329 -0
  309. package/src/migrate/introspection/sqliteIntrospector.spec.ts +112 -0
  310. package/src/migrate/introspection/sqliteIntrospector.ts +296 -0
  311. package/src/migrate/migrator-mongo.test.ts +54 -0
  312. package/src/migrate/migrator.spec.ts +255 -0
  313. package/src/migrate/migrator.test.ts +94 -0
  314. package/src/migrate/migrator.ts +719 -0
  315. package/src/migrate/namingStrategy.spec.ts +22 -0
  316. package/src/migrate/schemaGenerator-advanced.spec.ts +138 -0
  317. package/src/migrate/schemaGenerator.spec.ts +190 -0
  318. package/src/migrate/schemaGenerator.ts +478 -0
  319. package/src/migrate/storage/databaseStorage.spec.ts +69 -0
  320. package/src/migrate/storage/databaseStorage.ts +100 -0
  321. package/src/migrate/storage/index.ts +2 -0
  322. package/src/migrate/storage/jsonStorage.ts +58 -0
  323. package/src/migrate/type.ts +1 -0
  324. package/src/mongo/index.ts +3 -0
  325. package/src/mongo/mongoDialect.spec.ts +251 -0
  326. package/src/mongo/mongoDialect.ts +238 -0
  327. package/src/mongo/mongodbQuerier.test.ts +45 -0
  328. package/src/mongo/mongodbQuerier.ts +256 -0
  329. package/src/mongo/mongodbQuerierPool.test.ts +25 -0
  330. package/src/mongo/mongodbQuerierPool.ts +24 -0
  331. package/src/mysql/index.ts +3 -0
  332. package/src/mysql/mysql2Querier.test.ts +20 -0
  333. package/src/mysql/mysql2Querier.ts +49 -0
  334. package/src/mysql/mysql2QuerierPool.test.ts +20 -0
  335. package/src/mysql/mysql2QuerierPool.ts +21 -0
  336. package/src/mysql/mysqlDialect.spec.ts +20 -0
  337. package/src/mysql/mysqlDialect.ts +16 -0
  338. package/src/namingStrategy/defaultNamingStrategy.ts +18 -0
  339. package/src/namingStrategy/index.spec.ts +36 -0
  340. package/src/namingStrategy/index.ts +2 -0
  341. package/src/namingStrategy/snakeCaseNamingStrategy.ts +15 -0
  342. package/src/options.spec.ts +41 -0
  343. package/src/options.ts +18 -0
  344. package/src/postgres/index.ts +3 -0
  345. package/src/postgres/manual-types.d.ts +4 -0
  346. package/src/postgres/pgQuerier.test.ts +25 -0
  347. package/src/postgres/pgQuerier.ts +45 -0
  348. package/src/postgres/pgQuerierPool.test.ts +28 -0
  349. package/src/postgres/pgQuerierPool.ts +21 -0
  350. package/src/postgres/postgresDialect.spec.ts +428 -0
  351. package/src/postgres/postgresDialect.ts +144 -0
  352. package/src/querier/abstractQuerier-test.ts +584 -0
  353. package/src/querier/abstractQuerier.ts +353 -0
  354. package/src/querier/abstractQuerierPool-test.ts +20 -0
  355. package/src/querier/abstractQuerierPool.ts +18 -0
  356. package/src/querier/abstractSqlQuerier-spec.ts +979 -0
  357. package/src/querier/abstractSqlQuerier-test.ts +21 -0
  358. package/src/querier/abstractSqlQuerier.ts +138 -0
  359. package/src/querier/decorator/index.ts +3 -0
  360. package/src/querier/decorator/injectQuerier.spec.ts +74 -0
  361. package/src/querier/decorator/injectQuerier.ts +45 -0
  362. package/src/querier/decorator/serialized.spec.ts +98 -0
  363. package/src/querier/decorator/serialized.ts +13 -0
  364. package/src/querier/decorator/transactional.spec.ts +240 -0
  365. package/src/querier/decorator/transactional.ts +56 -0
  366. package/src/querier/index.ts +4 -0
  367. package/src/repository/genericRepository.spec.ts +111 -0
  368. package/src/repository/genericRepository.ts +74 -0
  369. package/src/repository/index.ts +1 -0
  370. package/src/sqlite/index.ts +3 -0
  371. package/src/sqlite/manual-types.d.ts +4 -0
  372. package/src/sqlite/sqliteDialect.spec.ts +155 -0
  373. package/src/sqlite/sqliteDialect.ts +76 -0
  374. package/src/sqlite/sqliteQuerier.spec.ts +36 -0
  375. package/src/sqlite/sqliteQuerier.test.ts +21 -0
  376. package/src/sqlite/sqliteQuerier.ts +37 -0
  377. package/src/sqlite/sqliteQuerierPool.test.ts +12 -0
  378. package/src/sqlite/sqliteQuerierPool.ts +38 -0
  379. package/src/test/entityMock.ts +375 -0
  380. package/src/test/index.ts +3 -0
  381. package/src/test/it.util.ts +69 -0
  382. package/src/test/spec.util.ts +57 -0
  383. package/src/type/entity.ts +218 -0
  384. package/src/type/index.ts +9 -0
  385. package/src/type/migration.ts +241 -0
  386. package/src/type/namingStrategy.ts +17 -0
  387. package/src/type/querier.ts +143 -0
  388. package/src/type/querierPool.ts +26 -0
  389. package/src/type/query.ts +506 -0
  390. package/src/type/repository.ts +142 -0
  391. package/src/type/universalQuerier.ts +133 -0
  392. package/src/type/utility.ts +21 -0
  393. package/src/util/dialect.util-extra.spec.ts +96 -0
  394. package/src/util/dialect.util.spec.ts +23 -0
  395. package/src/util/dialect.util.ts +134 -0
  396. package/src/util/index.ts +5 -0
  397. package/src/util/object.util.spec.ts +29 -0
  398. package/src/util/object.util.ts +27 -0
  399. package/src/util/raw.ts +11 -0
  400. package/src/util/sql.util-extra.spec.ts +17 -0
  401. package/src/util/sql.util.spec.ts +208 -0
  402. package/src/util/sql.util.ts +104 -0
  403. package/src/util/string.util.spec.ts +46 -0
  404. package/src/util/string.util.ts +35 -0
  405. package/tsconfig.build.json +5 -0
  406. package/tsconfig.json +8 -0
  407. package/README.md +0 -177
  408. package/dialect/abstractSqlDialect.d.ts +0 -30
  409. package/dialect/abstractSqlDialect.js +0 -365
  410. package/dialect/index.d.ts +0 -4
  411. package/dialect/index.js +0 -8
  412. package/dialect/mysqlDialect.d.ts +0 -6
  413. package/dialect/mysqlDialect.js +0 -21
  414. package/dialect/postgresDialect.d.ts +0 -8
  415. package/dialect/postgresDialect.js +0 -44
  416. package/dialect/sqliteDialect.d.ts +0 -4
  417. package/dialect/sqliteDialect.js +0 -11
  418. package/entity/decorator/definition.js +0 -223
  419. package/entity/decorator/entity.js +0 -11
  420. package/entity/decorator/field.js +0 -12
  421. package/entity/decorator/id.js +0 -12
  422. package/entity/decorator/index.d.ts +0 -5
  423. package/entity/decorator/index.js +0 -12
  424. package/entity/decorator/relation.js +0 -27
  425. package/entity/index.d.ts +0 -1
  426. package/entity/index.js +0 -5
  427. package/index.d.ts +0 -1
  428. package/index.js +0 -5
  429. package/options.js +0 -20
  430. package/querier/abstractQuerier.d.ts +0 -30
  431. package/querier/abstractQuerier.js +0 -230
  432. package/querier/abstractSqlQuerier.d.ts +0 -27
  433. package/querier/abstractSqlQuerier.js +0 -88
  434. package/querier/decorator/index.d.ts +0 -2
  435. package/querier/decorator/index.js +0 -6
  436. package/querier/decorator/injectQuerier.d.ts +0 -3
  437. package/querier/decorator/injectQuerier.js +0 -39
  438. package/querier/decorator/transactional.js +0 -52
  439. package/querier/index.d.ts +0 -3
  440. package/querier/index.js +0 -7
  441. package/repository/genericRepository.d.ts +0 -19
  442. package/repository/genericRepository.js +0 -52
  443. package/repository/index.d.ts +0 -1
  444. package/repository/index.js +0 -5
  445. package/type/entity.js +0 -5
  446. package/type/index.d.ts +0 -7
  447. package/type/index.js +0 -11
  448. package/type/querier.d.ts +0 -53
  449. package/type/querier.js +0 -3
  450. package/type/querierPool.js +0 -3
  451. package/type/query.js +0 -13
  452. package/type/repository.js +0 -3
  453. package/type/universalQuerier.js +0 -3
  454. package/type/utility.d.ts +0 -12
  455. package/type/utility.js +0 -3
  456. package/util/dialect.util.d.ts +0 -17
  457. package/util/dialect.util.js +0 -114
  458. package/util/index.d.ts +0 -5
  459. package/util/index.js +0 -9
  460. package/util/object.util.d.ts +0 -3
  461. package/util/object.util.js +0 -22
  462. package/util/raw.d.ts +0 -2
  463. package/util/raw.js +0 -9
  464. package/util/sql.util.d.ts +0 -2
  465. package/util/sql.util.js +0 -55
  466. package/util/string.util.js +0 -20
@@ -0,0 +1,2150 @@
1
+ import { isSqlQuerier } from '../type/index.js';
2
+ export { isSqlQuerier } from '../type/index.js';
3
+ import { AbstractDialect } from '../dialect/index.js';
4
+ import { getMeta, getEntities } from '../entity/index.js';
5
+ import { escapeSqlId, getKeys } from '../util/index.js';
6
+ import { readdir, readFile, mkdir, writeFile } from 'node:fs/promises';
7
+ import { join, basename, extname, dirname } from 'node:path';
8
+ import { pathToFileURL } from 'node:url';
9
+
10
+ /**
11
+ * Abstract base class for SQL schema generation
12
+ */ class AbstractSchemaGenerator extends AbstractDialect {
13
+ constructor(namingStrategy, escapeIdChar = '`'){
14
+ super(namingStrategy), this.escapeIdChar = escapeIdChar;
15
+ }
16
+ /**
17
+ * Escape an identifier (table name, column name, etc.)
18
+ */ escapeId(identifier) {
19
+ return escapeSqlId(identifier, this.escapeIdChar);
20
+ }
21
+ generateCreateTable(entity, options = {}) {
22
+ const meta = getMeta(entity);
23
+ const tableName = this.resolveTableName(entity, meta);
24
+ const columns = this.generateColumnDefinitions(meta);
25
+ const constraints = this.generateTableConstraints(meta);
26
+ const ifNotExists = options.ifNotExists ? 'IF NOT EXISTS ' : '';
27
+ let sql = `CREATE TABLE ${ifNotExists}${this.escapeId(tableName)} (\n`;
28
+ sql += columns.map((col)=>` ${col}`).join(',\n');
29
+ if (constraints.length > 0) {
30
+ sql += ',\n';
31
+ sql += constraints.map((c)=>` ${c}`).join(',\n');
32
+ }
33
+ sql += '\n)';
34
+ sql += this.getTableOptions(meta);
35
+ sql += ';';
36
+ return sql;
37
+ }
38
+ generateDropTable(entity) {
39
+ const meta = getMeta(entity);
40
+ const tableName = this.resolveTableName(entity, meta);
41
+ return `DROP TABLE IF EXISTS ${this.escapeId(tableName)};`;
42
+ }
43
+ generateAlterTable(diff) {
44
+ const statements = [];
45
+ const tableName = this.escapeId(diff.tableName);
46
+ // Add new columns
47
+ if (diff.columnsToAdd?.length) {
48
+ for (const column of diff.columnsToAdd){
49
+ const colDef = this.generateColumnDefinitionFromSchema(column);
50
+ statements.push(`ALTER TABLE ${tableName} ADD COLUMN ${colDef};`);
51
+ }
52
+ }
53
+ // Alter existing columns
54
+ if (diff.columnsToAlter?.length) {
55
+ for (const { to } of diff.columnsToAlter){
56
+ const colDef = this.generateColumnDefinitionFromSchema(to);
57
+ const colStatements = this.generateAlterColumnStatements(diff.tableName, to, colDef);
58
+ statements.push(...colStatements);
59
+ }
60
+ }
61
+ // Drop columns
62
+ if (diff.columnsToDrop?.length) {
63
+ for (const columnName of diff.columnsToDrop){
64
+ statements.push(`ALTER TABLE ${tableName} DROP COLUMN ${this.escapeId(columnName)};`);
65
+ }
66
+ }
67
+ // Add indexes
68
+ if (diff.indexesToAdd?.length) {
69
+ for (const index of diff.indexesToAdd){
70
+ statements.push(this.generateCreateIndex(diff.tableName, index));
71
+ }
72
+ }
73
+ // Drop indexes
74
+ if (diff.indexesToDrop?.length) {
75
+ for (const indexName of diff.indexesToDrop){
76
+ statements.push(this.generateDropIndex(diff.tableName, indexName));
77
+ }
78
+ }
79
+ return statements;
80
+ }
81
+ generateAlterTableDown(diff) {
82
+ const statements = [];
83
+ const tableName = this.escapeId(diff.tableName);
84
+ // Rollback additions by dropping columns
85
+ if (diff.columnsToAdd?.length) {
86
+ for (const column of diff.columnsToAdd){
87
+ statements.push(`ALTER TABLE ${tableName} DROP COLUMN ${this.escapeId(column.name)};`);
88
+ }
89
+ }
90
+ return statements;
91
+ }
92
+ generateCreateIndex(tableName, index) {
93
+ const unique = index.unique ? 'UNIQUE ' : '';
94
+ const columns = index.columns.map((c)=>this.escapeId(c)).join(', ');
95
+ return `CREATE ${unique}INDEX ${this.escapeId(index.name)} ON ${this.escapeId(tableName)} (${columns});`;
96
+ }
97
+ generateDropIndex(tableName, indexName) {
98
+ return `DROP INDEX IF EXISTS ${this.escapeId(indexName)};`;
99
+ }
100
+ /**
101
+ * Generate column definitions from entity metadata
102
+ */ generateColumnDefinitions(meta) {
103
+ const columns = [];
104
+ const fieldKeys = getKeys(meta.fields);
105
+ for (const key of fieldKeys){
106
+ const field = meta.fields[key];
107
+ if (field?.virtual) continue; // Skip virtual fields
108
+ const colDef = this.generateColumnDefinition(key, field, meta);
109
+ columns.push(colDef);
110
+ }
111
+ return columns;
112
+ }
113
+ /**
114
+ * Generate a single column definition
115
+ */ generateColumnDefinition(fieldKey, field, meta) {
116
+ const columnName = this.escapeId(this.resolveColumnName(fieldKey, field));
117
+ const isId = field.isId === true;
118
+ const isPrimaryKey = isId && meta.id === fieldKey;
119
+ // Determine SQL type
120
+ let sqlType;
121
+ if (isPrimaryKey && field.autoIncrement !== false && !field.onInsert) {
122
+ // Auto-increment primary key
123
+ sqlType = this.serialPrimaryKeyType;
124
+ } else {
125
+ sqlType = this.getSqlType(field, field.type);
126
+ }
127
+ let definition = `${columnName} ${sqlType}`;
128
+ // PRIMARY KEY constraint (for non-serial types)
129
+ if (isPrimaryKey && !sqlType.includes('PRIMARY KEY')) {
130
+ definition += ' PRIMARY KEY';
131
+ }
132
+ // NULL/NOT NULL
133
+ if (!isPrimaryKey) {
134
+ const nullable = field.nullable ?? true;
135
+ if (!nullable) {
136
+ definition += ' NOT NULL';
137
+ }
138
+ }
139
+ // UNIQUE constraint
140
+ if (field.unique && !isPrimaryKey) {
141
+ definition += ' UNIQUE';
142
+ }
143
+ // DEFAULT value
144
+ if (field.defaultValue !== undefined) {
145
+ definition += ` DEFAULT ${this.formatDefaultValue(field.defaultValue)}`;
146
+ }
147
+ // COMMENT (if supported)
148
+ if (field.comment) {
149
+ definition += this.generateColumnComment(this.resolveTableName(meta.entity, meta), this.resolveColumnName(fieldKey, field), field.comment);
150
+ }
151
+ return definition;
152
+ }
153
+ /**
154
+ * Generate column definition from a ColumnSchema object
155
+ */ generateColumnDefinitionFromSchema(column) {
156
+ const columnName = this.escapeId(column.name);
157
+ let type = column.type;
158
+ if (column.length && !type.includes('(')) {
159
+ type = `${type}(${column.length})`;
160
+ } else if (column.precision !== undefined && !type.includes('(')) {
161
+ if (column.scale !== undefined) {
162
+ type = `${type}(${column.precision}, ${column.scale})`;
163
+ } else {
164
+ type = `${type}(${column.precision})`;
165
+ }
166
+ }
167
+ let definition = `${columnName} ${type}`;
168
+ if (column.isPrimaryKey) {
169
+ definition += ' PRIMARY KEY';
170
+ }
171
+ if (!column.nullable && !column.isPrimaryKey) {
172
+ definition += ' NOT NULL';
173
+ }
174
+ if (column.isUnique && !column.isPrimaryKey) {
175
+ definition += ' UNIQUE';
176
+ }
177
+ if (column.defaultValue !== undefined) {
178
+ definition += ` DEFAULT ${this.formatDefaultValue(column.defaultValue)}`;
179
+ }
180
+ return definition;
181
+ }
182
+ /**
183
+ * Generate table constraints (indexes, foreign keys, etc.)
184
+ */ generateTableConstraints(meta) {
185
+ const constraints = [];
186
+ const fieldKeys = getKeys(meta.fields);
187
+ const tableName = this.resolveTableName(meta.entity, meta);
188
+ // Generate indexes from field options
189
+ for (const key of fieldKeys){
190
+ const field = meta.fields[key];
191
+ if (field?.index) {
192
+ const columnName = this.resolveColumnName(key, field);
193
+ const indexName = typeof field.index === 'string' ? field.index : `idx_${tableName}_${columnName}`;
194
+ constraints.push(`INDEX ${this.escapeId(indexName)} (${this.escapeId(columnName)})`);
195
+ }
196
+ }
197
+ // Generate foreign key constraints from references
198
+ for (const key of fieldKeys){
199
+ const field = meta.fields[key];
200
+ if (field?.reference) {
201
+ const refEntity = field.reference();
202
+ const refMeta = getMeta(refEntity);
203
+ const refIdField = refMeta.fields[refMeta.id];
204
+ const columnName = this.resolveColumnName(key, field);
205
+ const refTableName = this.resolveTableName(refEntity, refMeta);
206
+ const refColumnName = this.resolveColumnName(refMeta.id, refIdField);
207
+ const fkName = `fk_${tableName}_${columnName}`;
208
+ constraints.push(`CONSTRAINT ${this.escapeId(fkName)} FOREIGN KEY (${this.escapeId(columnName)}) ` + `REFERENCES ${this.escapeId(refTableName)} (${this.escapeId(refColumnName)})`);
209
+ }
210
+ }
211
+ return constraints;
212
+ }
213
+ getSqlType(field, fieldType) {
214
+ // Use explicit column type if specified
215
+ if (field.columnType) {
216
+ return this.mapColumnType(field.columnType, field);
217
+ }
218
+ // Handle special types
219
+ if (field.type === 'json' || field.type === 'jsonb') {
220
+ return this.mapColumnType(field.type, field);
221
+ }
222
+ if (field.type === 'vector') {
223
+ return this.mapColumnType('vector', field);
224
+ }
225
+ // Infer from TypeScript type
226
+ const type = fieldType ?? field.type;
227
+ if (type === Number || type === 'number') {
228
+ return field.precision ? this.mapColumnType('decimal', field) : 'BIGINT';
229
+ }
230
+ if (type === String || type === 'string') {
231
+ const length = field.length ?? 255;
232
+ return `VARCHAR(${length})`;
233
+ }
234
+ if (type === Boolean || type === 'boolean') {
235
+ return this.getBooleanType();
236
+ }
237
+ if (type === Date || type === 'date') {
238
+ return 'TIMESTAMP';
239
+ }
240
+ if (type === BigInt || type === 'bigint') {
241
+ return 'BIGINT';
242
+ }
243
+ // Default to VARCHAR
244
+ return `VARCHAR(${field.length ?? 255})`;
245
+ }
246
+ /**
247
+ * Get table options (e.g., ENGINE for MySQL)
248
+ */ getTableOptions(meta) {
249
+ return '';
250
+ }
251
+ /**
252
+ * Format a default value for SQL
253
+ */ formatDefaultValue(value) {
254
+ if (value === null) {
255
+ return 'NULL';
256
+ }
257
+ if (typeof value === 'string') {
258
+ return `'${value.replace(/'/g, "''")}'`;
259
+ }
260
+ if (typeof value === 'boolean') {
261
+ return value ? 'TRUE' : 'FALSE';
262
+ }
263
+ if (typeof value === 'number' || typeof value === 'bigint') {
264
+ return String(value);
265
+ }
266
+ if (value instanceof Date) {
267
+ return `'${value.toISOString()}'`;
268
+ }
269
+ return String(value);
270
+ }
271
+ /**
272
+ * Compare two schemas and return the differences
273
+ */ diffSchema(entity, currentSchema) {
274
+ const meta = getMeta(entity);
275
+ if (!currentSchema) {
276
+ // Table doesn't exist, need to create
277
+ return {
278
+ tableName: this.resolveTableName(entity, meta),
279
+ type: 'create'
280
+ };
281
+ }
282
+ const columnsToAdd = [];
283
+ const columnsToAlter = [];
284
+ const columnsToDrop = [];
285
+ const currentColumns = new Map(currentSchema.columns.map((c)=>[
286
+ c.name,
287
+ c
288
+ ]));
289
+ const fieldKeys = getKeys(meta.fields);
290
+ // Check for new or altered columns
291
+ for (const key of fieldKeys){
292
+ const field = meta.fields[key];
293
+ if (field?.virtual) continue;
294
+ const columnName = this.resolveColumnName(key, field);
295
+ const currentColumn = currentColumns.get(columnName);
296
+ if (!currentColumn) {
297
+ // Column needs to be added
298
+ columnsToAdd.push(this.fieldToColumnSchema(key, field, meta));
299
+ } else {
300
+ // Check if column needs alteration
301
+ const desiredColumn = this.fieldToColumnSchema(key, field, meta);
302
+ if (this.columnsNeedAlteration(currentColumn, desiredColumn)) {
303
+ columnsToAlter.push({
304
+ from: currentColumn,
305
+ to: desiredColumn
306
+ });
307
+ }
308
+ }
309
+ currentColumns.delete(columnName);
310
+ }
311
+ // Remaining columns in currentColumns should be dropped
312
+ for (const [name] of currentColumns){
313
+ columnsToDrop.push(name);
314
+ }
315
+ if (columnsToAdd.length === 0 && columnsToAlter.length === 0 && columnsToDrop.length === 0) {
316
+ return undefined; // No changes needed
317
+ }
318
+ return {
319
+ tableName: this.resolveTableName(entity, meta),
320
+ type: 'alter',
321
+ columnsToAdd: columnsToAdd.length > 0 ? columnsToAdd : undefined,
322
+ columnsToAlter: columnsToAlter.length > 0 ? columnsToAlter : undefined,
323
+ columnsToDrop: columnsToDrop.length > 0 ? columnsToDrop : undefined
324
+ };
325
+ }
326
+ /**
327
+ * Convert field options to ColumnSchema
328
+ */ fieldToColumnSchema(fieldKey, field, meta) {
329
+ const isId = field.isId === true;
330
+ const isPrimaryKey = isId && meta.id === fieldKey;
331
+ return {
332
+ name: this.resolveColumnName(fieldKey, field),
333
+ type: this.getSqlType(field, field.type),
334
+ nullable: field.nullable ?? !isPrimaryKey,
335
+ defaultValue: field.defaultValue,
336
+ isPrimaryKey,
337
+ isAutoIncrement: isPrimaryKey && field.autoIncrement !== false && !field.onInsert,
338
+ isUnique: field.unique ?? false,
339
+ length: field.length,
340
+ precision: field.precision,
341
+ scale: field.scale,
342
+ comment: field.comment
343
+ };
344
+ }
345
+ /**
346
+ * Check if two columns differ enough to require alteration
347
+ */ columnsNeedAlteration(current, desired) {
348
+ // Compare relevant properties
349
+ return current.type.toLowerCase() !== desired.type.toLowerCase() || current.nullable !== desired.nullable || current.isUnique !== desired.isUnique || JSON.stringify(current.defaultValue) !== JSON.stringify(desired.defaultValue);
350
+ }
351
+ }
352
+
353
+ /**
354
+ * MySQL/MariaDB-specific schema generator
355
+ */ class MysqlSchemaGenerator extends AbstractSchemaGenerator {
356
+ mapColumnType(columnType, field) {
357
+ switch(columnType){
358
+ case 'int':
359
+ return 'INT';
360
+ case 'smallint':
361
+ return 'SMALLINT';
362
+ case 'bigint':
363
+ return 'BIGINT';
364
+ case 'float':
365
+ return 'FLOAT';
366
+ case 'double':
367
+ case 'real':
368
+ return 'DOUBLE';
369
+ case 'decimal':
370
+ case 'numeric':
371
+ if (field.precision !== undefined) {
372
+ if (field.scale !== undefined) {
373
+ return `DECIMAL(${field.precision}, ${field.scale})`;
374
+ }
375
+ return `DECIMAL(${field.precision})`;
376
+ }
377
+ return 'DECIMAL(10, 2)';
378
+ case 'boolean':
379
+ return 'TINYINT(1)';
380
+ case 'char':
381
+ return `CHAR(${field.length ?? 1})`;
382
+ case 'varchar':
383
+ return `VARCHAR(${field.length ?? 255})`;
384
+ case 'text':
385
+ return 'TEXT';
386
+ case 'uuid':
387
+ return 'CHAR(36)';
388
+ case 'date':
389
+ return 'DATE';
390
+ case 'time':
391
+ return 'TIME';
392
+ case 'timestamp':
393
+ case 'timestamptz':
394
+ return 'TIMESTAMP';
395
+ case 'json':
396
+ case 'jsonb':
397
+ return 'JSON';
398
+ case 'blob':
399
+ case 'bytea':
400
+ return 'BLOB';
401
+ case 'vector':
402
+ // MySQL doesn't have native vector support, use JSON
403
+ return 'JSON';
404
+ case 'serial':
405
+ return 'INT AUTO_INCREMENT';
406
+ case 'bigserial':
407
+ return 'BIGINT AUTO_INCREMENT';
408
+ default:
409
+ return 'TEXT';
410
+ }
411
+ }
412
+ getBooleanType() {
413
+ return 'TINYINT(1)';
414
+ }
415
+ generateAlterColumnStatements(tableName, column, newDefinition) {
416
+ return [
417
+ `ALTER TABLE ${this.escapeId(tableName)} MODIFY COLUMN ${newDefinition};`
418
+ ];
419
+ }
420
+ generateColumnComment(tableName, columnName, comment) {
421
+ const escapedComment = comment.replace(/'/g, "''");
422
+ return ` COMMENT '${escapedComment}'`;
423
+ }
424
+ getTableOptions() {
425
+ return ' ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci';
426
+ }
427
+ generateDropIndex(tableName, indexName) {
428
+ // MySQL requires table name in DROP INDEX
429
+ return `DROP INDEX ${this.escapeId(indexName)} ON ${this.escapeId(tableName)};`;
430
+ }
431
+ constructor(...args){
432
+ super(...args), this.serialPrimaryKeyType = 'INT AUTO_INCREMENT PRIMARY KEY';
433
+ }
434
+ }
435
+
436
+ /**
437
+ * PostgreSQL-specific schema generator
438
+ */ class PostgresSchemaGenerator extends AbstractSchemaGenerator {
439
+ constructor(namingStrategy){
440
+ super(namingStrategy, '"'), this.serialPrimaryKeyType = 'SERIAL PRIMARY KEY';
441
+ }
442
+ mapColumnType(columnType, field) {
443
+ switch(columnType){
444
+ case 'int':
445
+ return 'INTEGER';
446
+ case 'smallint':
447
+ return 'SMALLINT';
448
+ case 'bigint':
449
+ return 'BIGINT';
450
+ case 'float':
451
+ case 'double':
452
+ case 'real':
453
+ return 'DOUBLE PRECISION';
454
+ case 'decimal':
455
+ case 'numeric':
456
+ if (field.precision !== undefined) {
457
+ if (field.scale !== undefined) {
458
+ return `NUMERIC(${field.precision}, ${field.scale})`;
459
+ }
460
+ return `NUMERIC(${field.precision})`;
461
+ }
462
+ return 'NUMERIC';
463
+ case 'boolean':
464
+ return 'BOOLEAN';
465
+ case 'char':
466
+ return `CHAR(${field.length ?? 1})`;
467
+ case 'varchar':
468
+ return `VARCHAR(${field.length ?? 255})`;
469
+ case 'text':
470
+ return 'TEXT';
471
+ case 'uuid':
472
+ return 'UUID';
473
+ case 'date':
474
+ return 'DATE';
475
+ case 'time':
476
+ return 'TIME';
477
+ case 'timestamp':
478
+ return 'TIMESTAMP';
479
+ case 'timestamptz':
480
+ return 'TIMESTAMPTZ';
481
+ case 'json':
482
+ return 'JSON';
483
+ case 'jsonb':
484
+ return 'JSONB';
485
+ case 'blob':
486
+ case 'bytea':
487
+ return 'BYTEA';
488
+ case 'vector':
489
+ if (field.length) {
490
+ return `VECTOR(${field.length})`;
491
+ }
492
+ return 'VECTOR';
493
+ case 'serial':
494
+ return 'SERIAL';
495
+ case 'bigserial':
496
+ return 'BIGSERIAL';
497
+ default:
498
+ return 'TEXT';
499
+ }
500
+ }
501
+ getBooleanType() {
502
+ return 'BOOLEAN';
503
+ }
504
+ generateAlterColumnStatements(tableName, column, newDefinition) {
505
+ const statements = [];
506
+ const escapedTableName = this.escapeId(tableName);
507
+ const escapedColumnName = this.escapeId(column.name);
508
+ // PostgreSQL uses separate ALTER COLUMN clauses for different changes
509
+ // 1. Change type
510
+ statements.push(`ALTER TABLE ${escapedTableName} ALTER COLUMN ${escapedColumnName} TYPE ${column.type};`);
511
+ // 2. Change nullability
512
+ if (column.nullable) {
513
+ statements.push(`ALTER TABLE ${escapedTableName} ALTER COLUMN ${escapedColumnName} DROP NOT NULL;`);
514
+ } else {
515
+ statements.push(`ALTER TABLE ${escapedTableName} ALTER COLUMN ${escapedColumnName} SET NOT NULL;`);
516
+ }
517
+ // 3. Change default value
518
+ if (column.defaultValue !== undefined) {
519
+ statements.push(`ALTER TABLE ${escapedTableName} ALTER COLUMN ${escapedColumnName} SET DEFAULT ${this.formatDefaultValue(column.defaultValue)};`);
520
+ } else {
521
+ statements.push(`ALTER TABLE ${escapedTableName} ALTER COLUMN ${escapedColumnName} DROP DEFAULT;`);
522
+ }
523
+ return statements;
524
+ }
525
+ generateColumnComment(tableName, columnName, comment) {
526
+ // PostgreSQL handles comments separately via COMMENT ON COLUMN
527
+ return '';
528
+ }
529
+ /**
530
+ * Generate COMMENT ON COLUMN statement for PostgreSQL
531
+ */ generateColumnCommentStatement(tableName, columnName, comment) {
532
+ const escapedComment = comment.replace(/'/g, "''");
533
+ return `COMMENT ON COLUMN ${this.escapeId(tableName)}.${this.escapeId(columnName)} IS '${escapedComment}';`;
534
+ }
535
+ generateDropIndex(tableName, indexName) {
536
+ // PostgreSQL doesn't require table name in DROP INDEX
537
+ return `DROP INDEX IF EXISTS ${this.escapeId(indexName)};`;
538
+ }
539
+ }
540
+
541
+ /**
542
+ * SQLite-specific schema generator
543
+ */ class SqliteSchemaGenerator extends AbstractSchemaGenerator {
544
+ mapColumnType(columnType, field) {
545
+ // SQLite has dynamic typing, but we use type affinity
546
+ switch(columnType){
547
+ case 'int':
548
+ case 'smallint':
549
+ case 'bigint':
550
+ case 'serial':
551
+ case 'bigserial':
552
+ return 'INTEGER';
553
+ case 'float':
554
+ case 'double':
555
+ case 'real':
556
+ case 'decimal':
557
+ case 'numeric':
558
+ return 'REAL';
559
+ case 'boolean':
560
+ return 'INTEGER'; // SQLite uses 0/1 for booleans
561
+ case 'char':
562
+ case 'varchar':
563
+ case 'text':
564
+ case 'uuid':
565
+ return 'TEXT';
566
+ case 'date':
567
+ case 'time':
568
+ case 'timestamp':
569
+ case 'timestamptz':
570
+ return 'TEXT'; // SQLite stores dates as TEXT or INTEGER
571
+ case 'json':
572
+ case 'jsonb':
573
+ return 'TEXT'; // SQLite stores JSON as TEXT
574
+ case 'blob':
575
+ case 'bytea':
576
+ return 'BLOB';
577
+ case 'vector':
578
+ return 'TEXT'; // Store as JSON array
579
+ default:
580
+ return 'TEXT';
581
+ }
582
+ }
583
+ getBooleanType() {
584
+ return 'INTEGER';
585
+ }
586
+ generateAlterColumnStatements(tableName, column, newDefinition) {
587
+ // SQLite has very limited ALTER TABLE support
588
+ // Column type changes require recreating the table
589
+ throw new Error(`SQLite does not support altering column '${column.name}' in table '${tableName}'. ` + 'You need to recreate the table to change column types.');
590
+ }
591
+ generateColumnComment(tableName, columnName, comment) {
592
+ return '';
593
+ }
594
+ generateDropIndex(tableName, indexName) {
595
+ return `DROP INDEX IF EXISTS ${this.escapeId(indexName)};`;
596
+ }
597
+ formatDefaultValue(value) {
598
+ if (typeof value === 'boolean') {
599
+ return value ? '1' : '0';
600
+ }
601
+ return super.formatDefaultValue(value);
602
+ }
603
+ constructor(...args){
604
+ super(...args), this.serialPrimaryKeyType = 'INTEGER PRIMARY KEY AUTOINCREMENT';
605
+ }
606
+ }
607
+
608
+ /**
609
+ * MySQL/MariaDB schema introspector.
610
+ * Works with both MySQL and MariaDB as they share the same information_schema structure.
611
+ */ class MysqlSchemaIntrospector {
612
+ constructor(querierPool){
613
+ this.querierPool = querierPool;
614
+ }
615
+ async getTableSchema(tableName) {
616
+ const querier = await this.getQuerier();
617
+ try {
618
+ const exists = await this.tableExistsInternal(querier, tableName);
619
+ if (!exists) {
620
+ return undefined;
621
+ }
622
+ const [columns, indexes, foreignKeys, primaryKey] = await Promise.all([
623
+ this.getColumns(querier, tableName),
624
+ this.getIndexes(querier, tableName),
625
+ this.getForeignKeys(querier, tableName),
626
+ this.getPrimaryKey(querier, tableName)
627
+ ]);
628
+ return {
629
+ name: tableName,
630
+ columns,
631
+ primaryKey,
632
+ indexes,
633
+ foreignKeys
634
+ };
635
+ } finally{
636
+ await querier.release();
637
+ }
638
+ }
639
+ async getTableNames() {
640
+ const querier = await this.getQuerier();
641
+ try {
642
+ const sql = `
643
+ SELECT TABLE_NAME as table_name
644
+ FROM information_schema.TABLES
645
+ WHERE TABLE_SCHEMA = DATABASE()
646
+ AND TABLE_TYPE = 'BASE TABLE'
647
+ ORDER BY TABLE_NAME
648
+ `;
649
+ const results = await querier.all(sql);
650
+ return results.map((r)=>r.table_name);
651
+ } finally{
652
+ await querier.release();
653
+ }
654
+ }
655
+ async tableExists(tableName) {
656
+ const querier = await this.getQuerier();
657
+ try {
658
+ return this.tableExistsInternal(querier, tableName);
659
+ } finally{
660
+ await querier.release();
661
+ }
662
+ }
663
+ async tableExistsInternal(querier, tableName) {
664
+ const sql = `
665
+ SELECT COUNT(*) as count
666
+ FROM information_schema.TABLES
667
+ WHERE TABLE_SCHEMA = DATABASE()
668
+ AND TABLE_NAME = ?
669
+ `;
670
+ const results = await querier.all(sql, [
671
+ tableName
672
+ ]);
673
+ return (results[0]?.count ?? 0) > 0;
674
+ }
675
+ async getQuerier() {
676
+ const querier = await this.querierPool.getQuerier();
677
+ if (!isSqlQuerier(querier)) {
678
+ await querier.release();
679
+ throw new Error('MysqlSchemaIntrospector requires a SQL-based querier');
680
+ }
681
+ return querier;
682
+ }
683
+ async getColumns(querier, tableName) {
684
+ const sql = `
685
+ SELECT
686
+ COLUMN_NAME as column_name,
687
+ DATA_TYPE as data_type,
688
+ COLUMN_TYPE as column_type,
689
+ IS_NULLABLE as is_nullable,
690
+ COLUMN_DEFAULT as column_default,
691
+ CHARACTER_MAXIMUM_LENGTH as character_maximum_length,
692
+ NUMERIC_PRECISION as numeric_precision,
693
+ NUMERIC_SCALE as numeric_scale,
694
+ COLUMN_KEY as column_key,
695
+ EXTRA as extra,
696
+ COLUMN_COMMENT as column_comment
697
+ FROM information_schema.COLUMNS
698
+ WHERE TABLE_SCHEMA = DATABASE()
699
+ AND TABLE_NAME = ?
700
+ ORDER BY ORDINAL_POSITION
701
+ `;
702
+ const results = await querier.all(sql, [
703
+ tableName
704
+ ]);
705
+ return results.map((row)=>({
706
+ name: row.column_name,
707
+ type: row.data_type.toUpperCase(),
708
+ nullable: row.is_nullable === 'YES',
709
+ defaultValue: this.parseDefaultValue(row.column_default),
710
+ isPrimaryKey: row.column_key === 'PRI',
711
+ isAutoIncrement: row.extra.toLowerCase().includes('auto_increment'),
712
+ isUnique: row.column_key === 'UNI',
713
+ length: row.character_maximum_length ?? undefined,
714
+ precision: row.numeric_precision ?? undefined,
715
+ scale: row.numeric_scale ?? undefined,
716
+ comment: row.column_comment || undefined
717
+ }));
718
+ }
719
+ async getIndexes(querier, tableName) {
720
+ const sql = `
721
+ SELECT
722
+ INDEX_NAME as index_name,
723
+ GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX) as columns,
724
+ NOT NON_UNIQUE as is_unique
725
+ FROM information_schema.STATISTICS
726
+ WHERE TABLE_SCHEMA = DATABASE()
727
+ AND TABLE_NAME = ?
728
+ AND INDEX_NAME != 'PRIMARY'
729
+ GROUP BY INDEX_NAME, NON_UNIQUE
730
+ ORDER BY INDEX_NAME
731
+ `;
732
+ const results = await querier.all(sql, [
733
+ tableName
734
+ ]);
735
+ return results.map((row)=>({
736
+ name: row.index_name,
737
+ columns: row.columns.split(','),
738
+ unique: Boolean(row.is_unique)
739
+ }));
740
+ }
741
+ async getForeignKeys(querier, tableName) {
742
+ const sql = `
743
+ SELECT
744
+ kcu.CONSTRAINT_NAME as constraint_name,
745
+ GROUP_CONCAT(kcu.COLUMN_NAME ORDER BY kcu.ORDINAL_POSITION) as columns,
746
+ kcu.REFERENCED_TABLE_NAME as referenced_table,
747
+ GROUP_CONCAT(kcu.REFERENCED_COLUMN_NAME ORDER BY kcu.ORDINAL_POSITION) as referenced_columns,
748
+ rc.DELETE_RULE as delete_rule,
749
+ rc.UPDATE_RULE as update_rule
750
+ FROM information_schema.KEY_COLUMN_USAGE kcu
751
+ JOIN information_schema.REFERENTIAL_CONSTRAINTS rc
752
+ ON kcu.CONSTRAINT_NAME = rc.CONSTRAINT_NAME
753
+ AND kcu.TABLE_SCHEMA = rc.CONSTRAINT_SCHEMA
754
+ WHERE kcu.TABLE_SCHEMA = DATABASE()
755
+ AND kcu.TABLE_NAME = ?
756
+ AND kcu.REFERENCED_TABLE_NAME IS NOT NULL
757
+ GROUP BY kcu.CONSTRAINT_NAME, kcu.REFERENCED_TABLE_NAME, rc.DELETE_RULE, rc.UPDATE_RULE
758
+ ORDER BY kcu.CONSTRAINT_NAME
759
+ `;
760
+ const results = await querier.all(sql, [
761
+ tableName
762
+ ]);
763
+ return results.map((row)=>({
764
+ name: row.constraint_name,
765
+ columns: row.columns.split(','),
766
+ referencedTable: row.referenced_table,
767
+ referencedColumns: row.referenced_columns.split(','),
768
+ onDelete: this.normalizeReferentialAction(row.delete_rule),
769
+ onUpdate: this.normalizeReferentialAction(row.update_rule)
770
+ }));
771
+ }
772
+ async getPrimaryKey(querier, tableName) {
773
+ const sql = `
774
+ SELECT COLUMN_NAME as column_name
775
+ FROM information_schema.KEY_COLUMN_USAGE
776
+ WHERE TABLE_SCHEMA = DATABASE()
777
+ AND TABLE_NAME = ?
778
+ AND CONSTRAINT_NAME = 'PRIMARY'
779
+ ORDER BY ORDINAL_POSITION
780
+ `;
781
+ const results = await querier.all(sql, [
782
+ tableName
783
+ ]);
784
+ if (results.length === 0) {
785
+ return undefined;
786
+ }
787
+ return results.map((r)=>r.column_name);
788
+ }
789
+ parseDefaultValue(defaultValue) {
790
+ if (defaultValue === null) {
791
+ return undefined;
792
+ }
793
+ // Check for common patterns
794
+ if (defaultValue === 'NULL') {
795
+ return null;
796
+ }
797
+ if (defaultValue === 'CURRENT_TIMESTAMP') {
798
+ return defaultValue;
799
+ }
800
+ if (/^'.*'$/.test(defaultValue)) {
801
+ return defaultValue.slice(1, -1);
802
+ }
803
+ if (/^-?\d+$/.test(defaultValue)) {
804
+ return Number.parseInt(defaultValue, 10);
805
+ }
806
+ if (/^-?\d+\.\d+$/.test(defaultValue)) {
807
+ return Number.parseFloat(defaultValue);
808
+ }
809
+ return defaultValue;
810
+ }
811
+ normalizeReferentialAction(action) {
812
+ switch(action.toUpperCase()){
813
+ case 'CASCADE':
814
+ return 'CASCADE';
815
+ case 'SET NULL':
816
+ return 'SET NULL';
817
+ case 'RESTRICT':
818
+ return 'RESTRICT';
819
+ case 'NO ACTION':
820
+ return 'NO ACTION';
821
+ default:
822
+ return undefined;
823
+ }
824
+ }
825
+ }
826
+ /**
827
+ * Alias for MysqlSchemaIntrospector.
828
+ * MariaDB uses the same information_schema structure as MySQL.
829
+ */ const MariadbSchemaIntrospector = MysqlSchemaIntrospector;
830
+
831
+ /**
832
+ * PostgreSQL schema introspector
833
+ */ class PostgresSchemaIntrospector {
834
+ constructor(querierPool){
835
+ this.querierPool = querierPool;
836
+ }
837
+ async getTableSchema(tableName) {
838
+ const querier = await this.getQuerier();
839
+ try {
840
+ const exists = await this.tableExistsInternal(querier, tableName);
841
+ if (!exists) {
842
+ return undefined;
843
+ }
844
+ const [columns, indexes, foreignKeys, primaryKey] = await Promise.all([
845
+ this.getColumns(querier, tableName),
846
+ this.getIndexes(querier, tableName),
847
+ this.getForeignKeys(querier, tableName),
848
+ this.getPrimaryKey(querier, tableName)
849
+ ]);
850
+ return {
851
+ name: tableName,
852
+ columns,
853
+ primaryKey,
854
+ indexes,
855
+ foreignKeys
856
+ };
857
+ } finally{
858
+ await querier.release();
859
+ }
860
+ }
861
+ async getTableNames() {
862
+ const querier = await this.getQuerier();
863
+ try {
864
+ const sql = `
865
+ SELECT table_name
866
+ FROM information_schema.tables
867
+ WHERE table_schema = 'public'
868
+ AND table_type = 'BASE TABLE'
869
+ ORDER BY table_name
870
+ `;
871
+ const results = await querier.all(sql);
872
+ return results.map((r)=>r.table_name);
873
+ } finally{
874
+ await querier.release();
875
+ }
876
+ }
877
+ async tableExists(tableName) {
878
+ const querier = await this.getQuerier();
879
+ try {
880
+ return this.tableExistsInternal(querier, tableName);
881
+ } finally{
882
+ await querier.release();
883
+ }
884
+ }
885
+ async tableExistsInternal(querier, tableName) {
886
+ const sql = `
887
+ SELECT EXISTS (
888
+ SELECT FROM information_schema.tables
889
+ WHERE table_schema = 'public'
890
+ AND table_name = $1
891
+ ) AS exists
892
+ `;
893
+ const results = await querier.all(sql, [
894
+ tableName
895
+ ]);
896
+ return results[0]?.exists ?? false;
897
+ }
898
+ async getQuerier() {
899
+ const querier = await this.querierPool.getQuerier();
900
+ if (!isSqlQuerier(querier)) {
901
+ await querier.release();
902
+ throw new Error('PostgresSchemaIntrospector requires a SQL-based querier');
903
+ }
904
+ return querier;
905
+ }
906
+ async getColumns(querier, tableName) {
907
+ const sql = /*sql*/ `
908
+ SELECT
909
+ c.column_name,
910
+ c.data_type,
911
+ c.udt_name,
912
+ c.is_nullable,
913
+ c.column_default,
914
+ c.character_maximum_length,
915
+ c.numeric_precision,
916
+ c.numeric_scale,
917
+ COALESCE(
918
+ (SELECT TRUE FROM information_schema.table_constraints tc
919
+ JOIN information_schema.key_column_usage kcu
920
+ ON tc.constraint_name = kcu.constraint_name
921
+ WHERE tc.table_name = c.table_name
922
+ AND tc.constraint_type = 'PRIMARY KEY'
923
+ AND kcu.column_name = c.column_name
924
+ LIMIT 1),
925
+ FALSE
926
+ ) AS is_primary_key,
927
+ COALESCE(
928
+ (SELECT TRUE FROM information_schema.table_constraints tc
929
+ JOIN information_schema.key_column_usage kcu
930
+ ON tc.constraint_name = kcu.constraint_name
931
+ WHERE tc.table_name = c.table_name
932
+ AND tc.constraint_type = 'UNIQUE'
933
+ AND kcu.column_name = c.column_name
934
+ LIMIT 1),
935
+ FALSE
936
+ ) AS is_unique,
937
+ pg_catalog.col_description(
938
+ (SELECT oid FROM pg_catalog.pg_class WHERE relname = c.table_name),
939
+ c.ordinal_position
940
+ ) AS column_comment
941
+ FROM information_schema.columns c
942
+ WHERE c.table_schema = 'public'
943
+ AND c.table_name = $1
944
+ ORDER BY c.ordinal_position
945
+ `;
946
+ const results = await querier.all(sql, [
947
+ tableName
948
+ ]);
949
+ return results.map((row)=>({
950
+ name: row.column_name,
951
+ type: this.normalizeType(row.data_type, row.udt_name),
952
+ nullable: row.is_nullable === 'YES',
953
+ defaultValue: this.parseDefaultValue(row.column_default),
954
+ isPrimaryKey: row.is_primary_key,
955
+ isAutoIncrement: this.isAutoIncrement(row.column_default),
956
+ isUnique: row.is_unique,
957
+ length: row.character_maximum_length ?? undefined,
958
+ precision: row.numeric_precision ?? undefined,
959
+ scale: row.numeric_scale ?? undefined,
960
+ comment: row.column_comment ?? undefined
961
+ }));
962
+ }
963
+ async getIndexes(querier, tableName) {
964
+ const sql = /*sql*/ `
965
+ SELECT
966
+ i.relname AS index_name,
967
+ array_agg(a.attname ORDER BY k.n) AS columns,
968
+ ix.indisunique AS is_unique
969
+ FROM pg_class t
970
+ JOIN pg_index ix ON t.oid = ix.indrelid
971
+ JOIN pg_class i ON i.oid = ix.indexrelid
972
+ JOIN pg_namespace n ON n.oid = t.relnamespace
973
+ CROSS JOIN LATERAL unnest(ix.indkey) WITH ORDINALITY AS k(attnum, n)
974
+ JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = k.attnum
975
+ WHERE t.relname = $1
976
+ AND n.nspname = 'public'
977
+ AND NOT ix.indisprimary
978
+ GROUP BY i.relname, ix.indisunique
979
+ ORDER BY i.relname
980
+ `;
981
+ const results = await querier.all(sql, [
982
+ tableName
983
+ ]);
984
+ return results.map((row)=>({
985
+ name: row.index_name,
986
+ columns: row.columns,
987
+ unique: row.is_unique
988
+ }));
989
+ }
990
+ async getForeignKeys(querier, tableName) {
991
+ const sql = /*sql*/ `
992
+ SELECT
993
+ tc.constraint_name,
994
+ array_agg(kcu.column_name ORDER BY kcu.ordinal_position) AS columns,
995
+ ccu.table_name AS referenced_table,
996
+ array_agg(ccu.column_name ORDER BY kcu.ordinal_position) AS referenced_columns,
997
+ rc.delete_rule,
998
+ rc.update_rule
999
+ FROM information_schema.table_constraints tc
1000
+ JOIN information_schema.key_column_usage kcu
1001
+ ON tc.constraint_name = kcu.constraint_name
1002
+ AND tc.table_schema = kcu.table_schema
1003
+ JOIN information_schema.constraint_column_usage ccu
1004
+ ON ccu.constraint_name = tc.constraint_name
1005
+ AND ccu.table_schema = tc.table_schema
1006
+ JOIN information_schema.referential_constraints rc
1007
+ ON rc.constraint_name = tc.constraint_name
1008
+ AND rc.constraint_schema = tc.table_schema
1009
+ WHERE tc.constraint_type = 'FOREIGN KEY'
1010
+ AND tc.table_name = $1
1011
+ AND tc.table_schema = 'public'
1012
+ GROUP BY tc.constraint_name, ccu.table_name, rc.delete_rule, rc.update_rule
1013
+ ORDER BY tc.constraint_name
1014
+ `;
1015
+ const results = await querier.all(sql, [
1016
+ tableName
1017
+ ]);
1018
+ return results.map((row)=>({
1019
+ name: row.constraint_name,
1020
+ columns: row.columns,
1021
+ referencedTable: row.referenced_table,
1022
+ referencedColumns: row.referenced_columns,
1023
+ onDelete: this.normalizeReferentialAction(row.delete_rule),
1024
+ onUpdate: this.normalizeReferentialAction(row.update_rule)
1025
+ }));
1026
+ }
1027
+ async getPrimaryKey(querier, tableName) {
1028
+ const sql = /*sql*/ `
1029
+ SELECT kcu.column_name
1030
+ FROM information_schema.table_constraints tc
1031
+ JOIN information_schema.key_column_usage kcu
1032
+ ON tc.constraint_name = kcu.constraint_name
1033
+ AND tc.table_schema = kcu.table_schema
1034
+ WHERE tc.constraint_type = 'PRIMARY KEY'
1035
+ AND tc.table_name = $1
1036
+ AND tc.table_schema = 'public'
1037
+ ORDER BY kcu.ordinal_position
1038
+ `;
1039
+ const results = await querier.all(sql, [
1040
+ tableName
1041
+ ]);
1042
+ if (results.length === 0) {
1043
+ return undefined;
1044
+ }
1045
+ return results.map((r)=>r.column_name);
1046
+ }
1047
+ normalizeType(dataType, udtName) {
1048
+ // Handle user-defined types and arrays
1049
+ if (dataType === 'USER-DEFINED') {
1050
+ return udtName.toUpperCase();
1051
+ }
1052
+ if (dataType === 'ARRAY') {
1053
+ return `${udtName.replace(/^_/, '')}[]`;
1054
+ }
1055
+ return dataType.toUpperCase();
1056
+ }
1057
+ parseDefaultValue(defaultValue) {
1058
+ if (!defaultValue) {
1059
+ return undefined;
1060
+ }
1061
+ // Remove type casting
1062
+ const cleaned = defaultValue.replace(/::[a-z_]+(\[\])?/gi, '').trim();
1063
+ // Check for common patterns
1064
+ if (cleaned.startsWith("'") && cleaned.endsWith("'")) {
1065
+ return cleaned.slice(1, -1);
1066
+ }
1067
+ if (cleaned === 'true' || cleaned === 'false') {
1068
+ return cleaned === 'true';
1069
+ }
1070
+ if (cleaned === 'NULL') {
1071
+ return null;
1072
+ }
1073
+ if (/^-?\d+$/.test(cleaned)) {
1074
+ return Number.parseInt(cleaned, 10);
1075
+ }
1076
+ if (/^-?\d+\.\d+$/.test(cleaned)) {
1077
+ return Number.parseFloat(cleaned);
1078
+ }
1079
+ // Return as-is for functions like CURRENT_TIMESTAMP, nextval(), etc.
1080
+ return defaultValue;
1081
+ }
1082
+ isAutoIncrement(defaultValue) {
1083
+ if (!defaultValue) {
1084
+ return false;
1085
+ }
1086
+ return defaultValue.includes('nextval(');
1087
+ }
1088
+ normalizeReferentialAction(action) {
1089
+ switch(action.toUpperCase()){
1090
+ case 'CASCADE':
1091
+ return 'CASCADE';
1092
+ case 'SET NULL':
1093
+ return 'SET NULL';
1094
+ case 'RESTRICT':
1095
+ return 'RESTRICT';
1096
+ case 'NO ACTION':
1097
+ return 'NO ACTION';
1098
+ default:
1099
+ return undefined;
1100
+ }
1101
+ }
1102
+ }
1103
+
1104
+ /**
1105
+ * SQLite schema introspector
1106
+ */ class SqliteSchemaIntrospector {
1107
+ constructor(querierPool){
1108
+ this.querierPool = querierPool;
1109
+ }
1110
+ async getTableSchema(tableName) {
1111
+ const querier = await this.getQuerier();
1112
+ try {
1113
+ const exists = await this.tableExistsInternal(querier, tableName);
1114
+ if (!exists) {
1115
+ return undefined;
1116
+ }
1117
+ const [columns, indexes, foreignKeys, primaryKey] = await Promise.all([
1118
+ this.getColumns(querier, tableName),
1119
+ this.getIndexes(querier, tableName),
1120
+ this.getForeignKeys(querier, tableName),
1121
+ this.getPrimaryKey(querier, tableName)
1122
+ ]);
1123
+ return {
1124
+ name: tableName,
1125
+ columns,
1126
+ primaryKey,
1127
+ indexes,
1128
+ foreignKeys
1129
+ };
1130
+ } finally{
1131
+ await querier.release();
1132
+ }
1133
+ }
1134
+ async getTableNames() {
1135
+ const querier = await this.getQuerier();
1136
+ try {
1137
+ const sql = /*sql*/ `
1138
+ SELECT name
1139
+ FROM sqlite_master
1140
+ WHERE type = 'table'
1141
+ AND name NOT LIKE 'sqlite_%'
1142
+ ORDER BY name
1143
+ `;
1144
+ const results = await querier.all(sql);
1145
+ return results.map((r)=>r.name);
1146
+ } finally{
1147
+ await querier.release();
1148
+ }
1149
+ }
1150
+ async tableExists(tableName) {
1151
+ const querier = await this.getQuerier();
1152
+ try {
1153
+ return this.tableExistsInternal(querier, tableName);
1154
+ } finally{
1155
+ await querier.release();
1156
+ }
1157
+ }
1158
+ async tableExistsInternal(querier, tableName) {
1159
+ const sql = /*sql*/ `
1160
+ SELECT COUNT(*) as count
1161
+ FROM sqlite_master
1162
+ WHERE type = 'table'
1163
+ AND name = ?
1164
+ `;
1165
+ const results = await querier.all(sql, [
1166
+ tableName
1167
+ ]);
1168
+ return (results[0]?.count ?? 0) > 0;
1169
+ }
1170
+ async getQuerier() {
1171
+ const querier = await this.querierPool.getQuerier();
1172
+ if (!isSqlQuerier(querier)) {
1173
+ await querier.release();
1174
+ throw new Error('SqliteSchemaIntrospector requires a SQL-based querier');
1175
+ }
1176
+ return querier;
1177
+ }
1178
+ async getColumns(querier, tableName) {
1179
+ // SQLite uses PRAGMA for table info
1180
+ const sql = `PRAGMA table_info(${this.escapeId(tableName)})`;
1181
+ const results = await querier.all(sql);
1182
+ // Get unique columns from indexes
1183
+ const uniqueColumns = await this.getUniqueColumns(querier, tableName);
1184
+ return results.map((row)=>({
1185
+ name: row.name,
1186
+ type: this.normalizeType(row.type),
1187
+ nullable: row.notnull === 0,
1188
+ defaultValue: this.parseDefaultValue(row.dflt_value),
1189
+ isPrimaryKey: row.pk > 0,
1190
+ isAutoIncrement: row.pk > 0 && row.type.toUpperCase() === 'INTEGER',
1191
+ isUnique: uniqueColumns.has(row.name),
1192
+ length: this.extractLength(row.type),
1193
+ precision: undefined,
1194
+ scale: undefined,
1195
+ comment: undefined
1196
+ }));
1197
+ }
1198
+ async getUniqueColumns(querier, tableName) {
1199
+ const sql = `PRAGMA index_list(${this.escapeId(tableName)})`;
1200
+ const indexes = await querier.all(sql);
1201
+ const uniqueColumns = new Set();
1202
+ for (const index of indexes){
1203
+ if (index.unique && index.origin === 'u') {
1204
+ const indexInfo = await querier.all(`PRAGMA index_info(${this.escapeId(index.name)})`);
1205
+ // Only single-column unique constraints
1206
+ if (indexInfo.length === 1) {
1207
+ uniqueColumns.add(indexInfo[0].name);
1208
+ }
1209
+ }
1210
+ }
1211
+ return uniqueColumns;
1212
+ }
1213
+ async getIndexes(querier, tableName) {
1214
+ const sql = `PRAGMA index_list(${this.escapeId(tableName)})`;
1215
+ const indexes = await querier.all(sql);
1216
+ const result = [];
1217
+ for (const index of indexes){
1218
+ // Skip auto-generated indexes (primary key, unique constraints)
1219
+ if (index.origin !== 'c') {
1220
+ continue;
1221
+ }
1222
+ const columns = await querier.all(`PRAGMA index_info(${this.escapeId(index.name)})`);
1223
+ result.push({
1224
+ name: index.name,
1225
+ columns: columns.map((c)=>c.name),
1226
+ unique: Boolean(index.unique)
1227
+ });
1228
+ }
1229
+ return result;
1230
+ }
1231
+ async getForeignKeys(querier, tableName) {
1232
+ const sql = `PRAGMA foreign_key_list(${this.escapeId(tableName)})`;
1233
+ const results = await querier.all(sql);
1234
+ // Group by id to handle composite foreign keys
1235
+ const grouped = new Map();
1236
+ for (const row of results){
1237
+ const existing = grouped.get(row.id) ?? [];
1238
+ existing.push(row);
1239
+ grouped.set(row.id, existing);
1240
+ }
1241
+ return Array.from(grouped.entries()).map(([id, rows])=>{
1242
+ const first = rows[0];
1243
+ return {
1244
+ name: `fk_${tableName}_${id}`,
1245
+ columns: rows.map((r)=>r.from),
1246
+ referencedTable: first.table,
1247
+ referencedColumns: rows.map((r)=>r.to),
1248
+ onDelete: this.normalizeReferentialAction(first.on_delete),
1249
+ onUpdate: this.normalizeReferentialAction(first.on_update)
1250
+ };
1251
+ });
1252
+ }
1253
+ async getPrimaryKey(querier, tableName) {
1254
+ const sql = `PRAGMA table_info(${this.escapeId(tableName)})`;
1255
+ const results = await querier.all(sql);
1256
+ const pkColumns = results.filter((r)=>r.pk > 0).sort((a, b)=>a.pk - b.pk);
1257
+ if (pkColumns.length === 0) {
1258
+ return undefined;
1259
+ }
1260
+ return pkColumns.map((r)=>r.name);
1261
+ }
1262
+ escapeId(identifier) {
1263
+ return `\`${identifier.replace(/`/g, '``')}\``;
1264
+ }
1265
+ normalizeType(type) {
1266
+ // Extract base type without length/precision
1267
+ const match = type.match(/^([A-Za-z]+)/);
1268
+ return match ? match[1].toUpperCase() : type.toUpperCase();
1269
+ }
1270
+ extractLength(type) {
1271
+ const match = type.match(/\((\d+)\)/);
1272
+ return match ? Number.parseInt(match[1], 10) : undefined;
1273
+ }
1274
+ parseDefaultValue(defaultValue) {
1275
+ if (defaultValue === null) {
1276
+ return undefined;
1277
+ }
1278
+ // Check for common patterns
1279
+ if (defaultValue === 'NULL') {
1280
+ return null;
1281
+ }
1282
+ if (defaultValue === 'CURRENT_TIMESTAMP' || defaultValue === 'CURRENT_DATE' || defaultValue === 'CURRENT_TIME') {
1283
+ return defaultValue;
1284
+ }
1285
+ if (/^'.*'$/.test(defaultValue)) {
1286
+ return defaultValue.slice(1, -1);
1287
+ }
1288
+ if (/^-?\d+$/.test(defaultValue)) {
1289
+ return Number.parseInt(defaultValue, 10);
1290
+ }
1291
+ if (/^-?\d+\.\d+$/.test(defaultValue)) {
1292
+ return Number.parseFloat(defaultValue);
1293
+ }
1294
+ return defaultValue;
1295
+ }
1296
+ normalizeReferentialAction(action) {
1297
+ switch(action.toUpperCase()){
1298
+ case 'CASCADE':
1299
+ return 'CASCADE';
1300
+ case 'SET NULL':
1301
+ return 'SET NULL';
1302
+ case 'RESTRICT':
1303
+ return 'RESTRICT';
1304
+ case 'NO ACTION':
1305
+ return 'NO ACTION';
1306
+ default:
1307
+ return undefined;
1308
+ }
1309
+ }
1310
+ }
1311
+
1312
+ class MongoSchemaGenerator extends AbstractDialect {
1313
+ generateCreateTable(entity) {
1314
+ const meta = getMeta(entity);
1315
+ const collectionName = this.resolveTableName(entity, meta);
1316
+ const indexes = [];
1317
+ for(const key in meta.fields){
1318
+ const field = meta.fields[key];
1319
+ if (field.index) {
1320
+ const columnName = this.resolveColumnName(key, field);
1321
+ const indexName = typeof field.index === 'string' ? field.index : `idx_${collectionName}_${columnName}`;
1322
+ indexes.push({
1323
+ name: indexName,
1324
+ columns: [
1325
+ columnName
1326
+ ],
1327
+ unique: !!field.unique
1328
+ });
1329
+ }
1330
+ }
1331
+ return JSON.stringify({
1332
+ action: 'createCollection',
1333
+ name: collectionName,
1334
+ indexes
1335
+ });
1336
+ }
1337
+ generateDropTable(entity) {
1338
+ const meta = getMeta(entity);
1339
+ const collectionName = this.resolveTableName(entity, meta);
1340
+ return JSON.stringify({
1341
+ action: 'dropCollection',
1342
+ name: collectionName
1343
+ });
1344
+ }
1345
+ generateAlterTable(diff) {
1346
+ const statements = [];
1347
+ if (diff.indexesToAdd?.length) {
1348
+ for (const index of diff.indexesToAdd){
1349
+ statements.push(this.generateCreateIndex(diff.tableName, index));
1350
+ }
1351
+ }
1352
+ return statements;
1353
+ }
1354
+ generateAlterTableDown(diff) {
1355
+ const statements = [];
1356
+ if (diff.indexesToAdd?.length) {
1357
+ for (const index of diff.indexesToAdd){
1358
+ statements.push(this.generateDropIndex(diff.tableName, index.name));
1359
+ }
1360
+ }
1361
+ return statements;
1362
+ }
1363
+ generateCreateIndex(tableName, index) {
1364
+ const key = {};
1365
+ for (const col of index.columns){
1366
+ key[col] = 1;
1367
+ }
1368
+ return JSON.stringify({
1369
+ action: 'createIndex',
1370
+ collection: tableName,
1371
+ name: index.name,
1372
+ key,
1373
+ options: {
1374
+ unique: index.unique,
1375
+ name: index.name
1376
+ }
1377
+ });
1378
+ }
1379
+ generateDropIndex(tableName, indexName) {
1380
+ return JSON.stringify({
1381
+ action: 'dropIndex',
1382
+ collection: tableName,
1383
+ name: indexName
1384
+ });
1385
+ }
1386
+ getSqlType() {
1387
+ return '';
1388
+ }
1389
+ diffSchema(entity, currentSchema) {
1390
+ const meta = getMeta(entity);
1391
+ const collectionName = this.resolveTableName(entity, meta);
1392
+ if (!currentSchema) {
1393
+ return {
1394
+ tableName: collectionName,
1395
+ type: 'create'
1396
+ };
1397
+ }
1398
+ const indexesToAdd = [];
1399
+ const existingIndexes = new Set(currentSchema.indexes?.map((i)=>i.name) ?? []);
1400
+ for(const key in meta.fields){
1401
+ const field = meta.fields[key];
1402
+ if (field.index) {
1403
+ const columnName = this.resolveColumnName(key, field);
1404
+ const indexName = typeof field.index === 'string' ? field.index : `idx_${collectionName}_${columnName}`;
1405
+ if (!existingIndexes.has(indexName)) {
1406
+ indexesToAdd.push({
1407
+ name: indexName,
1408
+ columns: [
1409
+ columnName
1410
+ ],
1411
+ unique: !!field.unique
1412
+ });
1413
+ }
1414
+ }
1415
+ }
1416
+ if (indexesToAdd.length === 0) {
1417
+ return undefined;
1418
+ }
1419
+ return {
1420
+ tableName: collectionName,
1421
+ type: 'alter',
1422
+ indexesToAdd
1423
+ };
1424
+ }
1425
+ }
1426
+
1427
+ class MongoSchemaIntrospector {
1428
+ constructor(querierPool){
1429
+ this.querierPool = querierPool;
1430
+ }
1431
+ async getTableSchema(tableName) {
1432
+ const querier = await this.querierPool.getQuerier();
1433
+ try {
1434
+ const { db } = querier;
1435
+ const collections = await db.listCollections({
1436
+ name: tableName
1437
+ }).toArray();
1438
+ if (collections.length === 0) {
1439
+ return undefined;
1440
+ }
1441
+ // MongoDB doesn't have a fixed schema, but we can look at the indexes
1442
+ const indexes = await db.collection(tableName).indexes();
1443
+ return {
1444
+ name: tableName,
1445
+ columns: [],
1446
+ indexes: indexes.map((idx)=>({
1447
+ name: idx.name,
1448
+ columns: Object.keys(idx.key),
1449
+ unique: !!idx.unique
1450
+ }))
1451
+ };
1452
+ } finally{
1453
+ await querier.release();
1454
+ }
1455
+ }
1456
+ async getTableNames() {
1457
+ const querier = await this.querierPool.getQuerier();
1458
+ try {
1459
+ const { db } = querier;
1460
+ const collections = await db.listCollections().toArray();
1461
+ return collections.map((c)=>c.name);
1462
+ } finally{
1463
+ await querier.release();
1464
+ }
1465
+ }
1466
+ async tableExists(tableName) {
1467
+ const names = await this.getTableNames();
1468
+ return names.includes(tableName);
1469
+ }
1470
+ }
1471
+
1472
+ /**
1473
+ * Stores migration state in a database table.
1474
+ * Uses the querier's dialect for escaping and placeholders.
1475
+ */ class DatabaseMigrationStorage {
1476
+ constructor(querierPool, options = {}){
1477
+ this.querierPool = querierPool;
1478
+ this.storageInitialized = false;
1479
+ this.tableName = options.tableName ?? 'uql_migrations';
1480
+ }
1481
+ async ensureStorage() {
1482
+ if (this.storageInitialized) {
1483
+ return;
1484
+ }
1485
+ const querier = await this.querierPool.getQuerier();
1486
+ if (!isSqlQuerier(querier)) {
1487
+ await querier.release();
1488
+ throw new Error('DatabaseMigrationStorage requires a SQL-based querier');
1489
+ }
1490
+ try {
1491
+ await this.createTableIfNotExists(querier);
1492
+ this.storageInitialized = true;
1493
+ } finally{
1494
+ await querier.release();
1495
+ }
1496
+ }
1497
+ async createTableIfNotExists(querier) {
1498
+ const { escapeId } = querier.dialect;
1499
+ const sql = `
1500
+ CREATE TABLE IF NOT EXISTS ${escapeId(this.tableName)} (
1501
+ ${escapeId('name')} VARCHAR(255) PRIMARY KEY,
1502
+ ${escapeId('executed_at')} TIMESTAMP DEFAULT CURRENT_TIMESTAMP
1503
+ )
1504
+ `;
1505
+ await querier.run(sql);
1506
+ }
1507
+ async executed() {
1508
+ await this.ensureStorage();
1509
+ const querier = await this.querierPool.getQuerier();
1510
+ if (!isSqlQuerier(querier)) {
1511
+ await querier.release();
1512
+ throw new Error('DatabaseMigrationStorage requires a SQL-based querier');
1513
+ }
1514
+ try {
1515
+ const { escapeId } = querier.dialect;
1516
+ const sql = `SELECT ${escapeId('name')} FROM ${escapeId(this.tableName)} ORDER BY ${escapeId('name')} ASC`;
1517
+ const results = await querier.all(sql);
1518
+ return results.map((r)=>r.name);
1519
+ } finally{
1520
+ await querier.release();
1521
+ }
1522
+ }
1523
+ /**
1524
+ * Log a migration as executed - uses provided querier (within transaction)
1525
+ */ async logWithQuerier(querier, migrationName) {
1526
+ await this.ensureStorage();
1527
+ const { escapeId, placeholder } = querier.dialect;
1528
+ const sql = `INSERT INTO ${escapeId(this.tableName)} (${escapeId('name')}) VALUES (${placeholder(1)})`;
1529
+ await querier.run(sql, [
1530
+ migrationName
1531
+ ]);
1532
+ }
1533
+ /**
1534
+ * Unlog a migration - uses provided querier (within transaction)
1535
+ */ async unlogWithQuerier(querier, migrationName) {
1536
+ await this.ensureStorage();
1537
+ const { escapeId, placeholder } = querier.dialect;
1538
+ const sql = `DELETE FROM ${escapeId(this.tableName)} WHERE ${escapeId('name')} = ${placeholder(1)}`;
1539
+ await querier.run(sql, [
1540
+ migrationName
1541
+ ]);
1542
+ }
1543
+ }
1544
+
1545
+ /**
1546
+ * Main class for managing database migrations
1547
+ */ class Migrator {
1548
+ constructor(querierPool, options = {}){
1549
+ this.querierPool = querierPool;
1550
+ this.dialect = options.dialect ?? querierPool.dialect ?? 'postgres';
1551
+ this.storage = options.storage ?? new DatabaseMigrationStorage(querierPool, {
1552
+ tableName: options.tableName
1553
+ });
1554
+ this.migrationsPath = options.migrationsPath ?? './migrations';
1555
+ this.logger = options.logger ?? (()=>{});
1556
+ this.entities = options.entities ?? [];
1557
+ this.schemaIntrospector = this.createIntrospector();
1558
+ this.schemaGenerator = options.schemaGenerator ?? this.createGenerator(options.namingStrategy);
1559
+ }
1560
+ /**
1561
+ * Set the schema generator for DDL operations
1562
+ */ setSchemaGenerator(generator) {
1563
+ this.schemaGenerator = generator;
1564
+ }
1565
+ createIntrospector() {
1566
+ switch(this.dialect){
1567
+ case 'postgres':
1568
+ return new PostgresSchemaIntrospector(this.querierPool);
1569
+ case 'mysql':
1570
+ return new MysqlSchemaIntrospector(this.querierPool);
1571
+ case 'mariadb':
1572
+ return new MariadbSchemaIntrospector(this.querierPool);
1573
+ case 'sqlite':
1574
+ return new SqliteSchemaIntrospector(this.querierPool);
1575
+ case 'mongodb':
1576
+ return new MongoSchemaIntrospector(this.querierPool);
1577
+ default:
1578
+ return undefined;
1579
+ }
1580
+ }
1581
+ createGenerator(namingStrategy) {
1582
+ switch(this.dialect){
1583
+ case 'postgres':
1584
+ return new PostgresSchemaGenerator(namingStrategy);
1585
+ case 'mysql':
1586
+ return new MysqlSchemaGenerator(namingStrategy);
1587
+ case 'mariadb':
1588
+ return new MysqlSchemaGenerator(namingStrategy);
1589
+ case 'sqlite':
1590
+ return new SqliteSchemaGenerator(namingStrategy);
1591
+ case 'mongodb':
1592
+ return new MongoSchemaGenerator(namingStrategy);
1593
+ default:
1594
+ return undefined;
1595
+ }
1596
+ }
1597
+ /**
1598
+ * Get the SQL dialect
1599
+ */ getDialect() {
1600
+ return this.dialect;
1601
+ }
1602
+ /**
1603
+ * Get all discovered migrations from the migrations directory
1604
+ */ async getMigrations() {
1605
+ const files = await this.getMigrationFiles();
1606
+ const migrations = [];
1607
+ for (const file of files){
1608
+ const migration = await this.loadMigration(file);
1609
+ if (migration) {
1610
+ migrations.push(migration);
1611
+ }
1612
+ }
1613
+ // Sort by name (which typically includes timestamp)
1614
+ return migrations.sort((a, b)=>a.name.localeCompare(b.name));
1615
+ }
1616
+ /**
1617
+ * Get list of pending migrations (not yet executed)
1618
+ */ async pending() {
1619
+ const [migrations, executed] = await Promise.all([
1620
+ this.getMigrations(),
1621
+ this.storage.executed()
1622
+ ]);
1623
+ const executedSet = new Set(executed);
1624
+ return migrations.filter((m)=>!executedSet.has(m.name));
1625
+ }
1626
+ /**
1627
+ * Get list of executed migrations
1628
+ */ async executed() {
1629
+ return this.storage.executed();
1630
+ }
1631
+ /**
1632
+ * Run all pending migrations
1633
+ */ async up(options = {}) {
1634
+ const pendingMigrations = await this.pending();
1635
+ const results = [];
1636
+ let migrationsToRun = pendingMigrations;
1637
+ if (options.to) {
1638
+ const toIndex = migrationsToRun.findIndex((m)=>m.name === options.to);
1639
+ if (toIndex === -1) {
1640
+ throw new Error(`Migration '${options.to}' not found`);
1641
+ }
1642
+ migrationsToRun = migrationsToRun.slice(0, toIndex + 1);
1643
+ }
1644
+ if (options.step !== undefined) {
1645
+ migrationsToRun = migrationsToRun.slice(0, options.step);
1646
+ }
1647
+ for (const migration of migrationsToRun){
1648
+ const result = await this.runMigration(migration, 'up');
1649
+ results.push(result);
1650
+ if (!result.success) {
1651
+ break; // Stop on first failure
1652
+ }
1653
+ }
1654
+ return results;
1655
+ }
1656
+ /**
1657
+ * Rollback migrations
1658
+ */ async down(options = {}) {
1659
+ const [migrations, executed] = await Promise.all([
1660
+ this.getMigrations(),
1661
+ this.storage.executed()
1662
+ ]);
1663
+ const executedSet = new Set(executed);
1664
+ const executedMigrations = migrations.filter((m)=>executedSet.has(m.name)).reverse(); // Rollback in reverse order
1665
+ const results = [];
1666
+ let migrationsToRun = executedMigrations;
1667
+ if (options.to) {
1668
+ const toIndex = migrationsToRun.findIndex((m)=>m.name === options.to);
1669
+ if (toIndex === -1) {
1670
+ throw new Error(`Migration '${options.to}' not found`);
1671
+ }
1672
+ migrationsToRun = migrationsToRun.slice(0, toIndex + 1);
1673
+ }
1674
+ if (options.step !== undefined) {
1675
+ migrationsToRun = migrationsToRun.slice(0, options.step);
1676
+ }
1677
+ for (const migration of migrationsToRun){
1678
+ const result = await this.runMigration(migration, 'down');
1679
+ results.push(result);
1680
+ if (!result.success) {
1681
+ break; // Stop on first failure
1682
+ }
1683
+ }
1684
+ return results;
1685
+ }
1686
+ /**
1687
+ * Run a single migration within a transaction
1688
+ */ async runMigration(migration, direction) {
1689
+ const startTime = Date.now();
1690
+ const querier = await this.querierPool.getQuerier();
1691
+ if (!isSqlQuerier(querier)) {
1692
+ await querier.release();
1693
+ throw new Error('Migrator requires a SQL-based querier');
1694
+ }
1695
+ try {
1696
+ this.logger(`${direction === 'up' ? 'Running' : 'Reverting'} migration: ${migration.name}`);
1697
+ await querier.beginTransaction();
1698
+ if (direction === 'up') {
1699
+ await migration.up(querier);
1700
+ // Log within the same transaction
1701
+ await this.storage.logWithQuerier(querier, migration.name);
1702
+ } else {
1703
+ await migration.down(querier);
1704
+ // Unlog within the same transaction
1705
+ await this.storage.unlogWithQuerier(querier, migration.name);
1706
+ }
1707
+ await querier.commitTransaction();
1708
+ const duration = Date.now() - startTime;
1709
+ this.logger(`Migration ${migration.name} ${direction === 'up' ? 'applied' : 'reverted'} in ${duration}ms`);
1710
+ return {
1711
+ name: migration.name,
1712
+ direction,
1713
+ duration,
1714
+ success: true
1715
+ };
1716
+ } catch (error) {
1717
+ await querier.rollbackTransaction();
1718
+ const duration = Date.now() - startTime;
1719
+ this.logger(`Migration ${migration.name} failed: ${error.message}`);
1720
+ return {
1721
+ name: migration.name,
1722
+ direction,
1723
+ duration,
1724
+ success: false,
1725
+ error: error
1726
+ };
1727
+ } finally{
1728
+ await querier.release();
1729
+ }
1730
+ }
1731
+ /**
1732
+ * Generate a new migration file
1733
+ */ async generate(name) {
1734
+ const timestamp = this.getTimestamp();
1735
+ const fileName = `${timestamp}_${this.slugify(name)}.ts`;
1736
+ const filePath = join(this.migrationsPath, fileName);
1737
+ const content = this.generateMigrationContent(name);
1738
+ const { writeFile, mkdir } = await import('node:fs/promises');
1739
+ await mkdir(this.migrationsPath, {
1740
+ recursive: true
1741
+ });
1742
+ await writeFile(filePath, content, 'utf-8');
1743
+ this.logger(`Created migration: ${filePath}`);
1744
+ return filePath;
1745
+ }
1746
+ /**
1747
+ * Generate a migration based on entity schema differences
1748
+ */ async generateFromEntities(name) {
1749
+ if (!this.schemaGenerator) {
1750
+ throw new Error('Schema generator not set. Call setSchemaGenerator() first.');
1751
+ }
1752
+ const diffs = await this.getDiffs();
1753
+ const upStatements = [];
1754
+ const downStatements = [];
1755
+ for (const diff of diffs){
1756
+ if (diff.type === 'create') {
1757
+ const entity = this.findEntityForTable(diff.tableName);
1758
+ if (entity) {
1759
+ upStatements.push(this.schemaGenerator.generateCreateTable(entity));
1760
+ downStatements.push(this.schemaGenerator.generateDropTable(entity));
1761
+ }
1762
+ } else if (diff.type === 'alter') {
1763
+ const alterStatements = this.schemaGenerator.generateAlterTable(diff);
1764
+ upStatements.push(...alterStatements);
1765
+ const alterDownStatements = this.schemaGenerator.generateAlterTableDown(diff);
1766
+ downStatements.push(...alterDownStatements);
1767
+ }
1768
+ }
1769
+ if (upStatements.length === 0) {
1770
+ this.logger('No schema changes detected.');
1771
+ return '';
1772
+ }
1773
+ const timestamp = this.getTimestamp();
1774
+ const fileName = `${timestamp}_${this.slugify(name)}.ts`;
1775
+ const filePath = join(this.migrationsPath, fileName);
1776
+ const content = this.generateMigrationContentWithStatements(name, upStatements, downStatements.reverse());
1777
+ const { writeFile, mkdir } = await import('node:fs/promises');
1778
+ await mkdir(this.migrationsPath, {
1779
+ recursive: true
1780
+ });
1781
+ await writeFile(filePath, content, 'utf-8');
1782
+ this.logger(`Created migration from entities: ${filePath}`);
1783
+ return filePath;
1784
+ }
1785
+ /**
1786
+ * Get all schema differences between entities and database
1787
+ */ async getDiffs() {
1788
+ if (!this.schemaGenerator || !this.schemaIntrospector) {
1789
+ throw new Error('Schema generator and introspector must be set');
1790
+ }
1791
+ const entities = this.entities.length > 0 ? this.entities : getEntities();
1792
+ const diffs = [];
1793
+ for (const entity of entities){
1794
+ const meta = getMeta(entity);
1795
+ const tableName = this.schemaGenerator.resolveTableName(entity, meta);
1796
+ const currentSchema = await this.schemaIntrospector.getTableSchema(tableName);
1797
+ const diff = this.schemaGenerator.diffSchema(entity, currentSchema);
1798
+ if (diff) {
1799
+ diffs.push(diff);
1800
+ }
1801
+ }
1802
+ return diffs;
1803
+ }
1804
+ findEntityForTable(tableName) {
1805
+ const entities = this.entities.length > 0 ? this.entities : getEntities();
1806
+ for (const entity of entities){
1807
+ const meta = getMeta(entity);
1808
+ const name = this.schemaGenerator.resolveTableName(entity, meta);
1809
+ if (name === tableName) {
1810
+ return entity;
1811
+ }
1812
+ }
1813
+ return undefined;
1814
+ }
1815
+ /**
1816
+ * Sync schema directly (for development only - not for production!)
1817
+ */ async sync(options = {}) {
1818
+ if (options.force) {
1819
+ return this.syncForce();
1820
+ }
1821
+ return this.autoSync({
1822
+ safe: true
1823
+ });
1824
+ }
1825
+ /**
1826
+ * Drops and recreates all tables (Development only!)
1827
+ */ async syncForce() {
1828
+ if (!this.schemaGenerator) {
1829
+ throw new Error('Schema generator not set. Call setSchemaGenerator() first.');
1830
+ }
1831
+ const entities = this.entities.length > 0 ? this.entities : getEntities();
1832
+ const querier = await this.querierPool.getQuerier();
1833
+ if (!isSqlQuerier(querier)) {
1834
+ await querier.release();
1835
+ throw new Error('Migrator requires a SQL-based querier');
1836
+ }
1837
+ try {
1838
+ await querier.beginTransaction();
1839
+ // Drop all tables first (in reverse order for foreign keys)
1840
+ for (const entity of [
1841
+ ...entities
1842
+ ].reverse()){
1843
+ const dropSql = this.schemaGenerator.generateDropTable(entity);
1844
+ this.logger(`Executing: ${dropSql}`);
1845
+ await querier.run(dropSql);
1846
+ }
1847
+ // Create all tables
1848
+ for (const entity of entities){
1849
+ const createSql = this.schemaGenerator.generateCreateTable(entity);
1850
+ this.logger(`Executing: ${createSql}`);
1851
+ await querier.run(createSql);
1852
+ }
1853
+ await querier.commitTransaction();
1854
+ this.logger('Schema sync (force) completed');
1855
+ } catch (error) {
1856
+ await querier.rollbackTransaction();
1857
+ throw error;
1858
+ } finally{
1859
+ await querier.release();
1860
+ }
1861
+ }
1862
+ /**
1863
+ * Safely synchronizes the schema by only adding missing tables and columns.
1864
+ */ async autoSync(options = {}) {
1865
+ if (!this.schemaGenerator || !this.schemaIntrospector) {
1866
+ throw new Error('Schema generator and introspector must be set');
1867
+ }
1868
+ const diffs = await this.getDiffs();
1869
+ const statements = [];
1870
+ for (const diff of diffs){
1871
+ if (diff.type === 'create') {
1872
+ const entity = this.findEntityForTable(diff.tableName);
1873
+ if (entity) {
1874
+ statements.push(this.schemaGenerator.generateCreateTable(entity));
1875
+ }
1876
+ } else if (diff.type === 'alter') {
1877
+ const filteredDiff = this.filterDiff(diff, options);
1878
+ const alterStatements = this.schemaGenerator.generateAlterTable(filteredDiff);
1879
+ statements.push(...alterStatements);
1880
+ }
1881
+ }
1882
+ if (statements.length === 0) {
1883
+ if (options.logging) this.logger('Schema is already in sync.');
1884
+ return;
1885
+ }
1886
+ await this.executeSyncStatements(statements, options);
1887
+ }
1888
+ filterDiff(diff, options) {
1889
+ const filteredDiff = {
1890
+ ...diff
1891
+ };
1892
+ if (options.safe !== false) {
1893
+ // In safe mode, we only allow additions
1894
+ delete filteredDiff.columnsToDrop;
1895
+ delete filteredDiff.indexesToDrop;
1896
+ delete filteredDiff.foreignKeysToDrop;
1897
+ }
1898
+ if (!options.drop) {
1899
+ delete filteredDiff.columnsToDrop;
1900
+ }
1901
+ return filteredDiff;
1902
+ }
1903
+ async executeSyncStatements(statements, options) {
1904
+ const querier = await this.querierPool.getQuerier();
1905
+ try {
1906
+ if (this.dialect === 'mongodb') {
1907
+ await this.executeMongoSyncStatements(statements, options, querier);
1908
+ } else {
1909
+ await this.executeSqlSyncStatements(statements, options, querier);
1910
+ }
1911
+ if (options.logging) this.logger('Schema synchronization completed');
1912
+ } catch (error) {
1913
+ if (this.dialect !== 'mongodb' && isSqlQuerier(querier)) {
1914
+ await querier.rollbackTransaction();
1915
+ }
1916
+ throw error;
1917
+ } finally{
1918
+ await querier.release();
1919
+ }
1920
+ }
1921
+ async executeMongoSyncStatements(statements, options, querier) {
1922
+ const db = querier.db;
1923
+ for (const stmt of statements){
1924
+ const cmd = JSON.parse(stmt);
1925
+ if (options.logging) this.logger(`Executing MongoDB: ${stmt}`);
1926
+ const collectionName = cmd.name || cmd.collection;
1927
+ if (!collectionName) {
1928
+ throw new Error(`MongoDB command missing collection name: ${stmt}`);
1929
+ }
1930
+ const collection = db.collection(collectionName);
1931
+ if (cmd.action === 'createCollection') {
1932
+ await db.createCollection(cmd.name);
1933
+ if (cmd.indexes?.length) {
1934
+ for (const idx of cmd.indexes){
1935
+ const key = Object.fromEntries(idx.columns.map((c)=>[
1936
+ c,
1937
+ 1
1938
+ ]));
1939
+ await collection.createIndex(key, {
1940
+ unique: idx.unique,
1941
+ name: idx.name
1942
+ });
1943
+ }
1944
+ }
1945
+ } else if (cmd.action === 'dropCollection') {
1946
+ await collection.drop();
1947
+ } else if (cmd.action === 'createIndex') {
1948
+ await collection.createIndex(cmd.key, cmd.options);
1949
+ } else if (cmd.action === 'dropIndex') {
1950
+ await collection.dropIndex(cmd.name);
1951
+ }
1952
+ }
1953
+ }
1954
+ async executeSqlSyncStatements(statements, options, querier) {
1955
+ if (!isSqlQuerier(querier)) {
1956
+ throw new Error('Migrator requires a SQL-based querier for this dialect');
1957
+ }
1958
+ await querier.beginTransaction();
1959
+ for (const sql of statements){
1960
+ if (options.logging) this.logger(`Executing: ${sql}`);
1961
+ await querier.run(sql);
1962
+ }
1963
+ await querier.commitTransaction();
1964
+ }
1965
+ /**
1966
+ * Get migration status
1967
+ */ async status() {
1968
+ const [pending, executed] = await Promise.all([
1969
+ this.pending().then((m)=>m.map((x)=>x.name)),
1970
+ this.executed()
1971
+ ]);
1972
+ return {
1973
+ pending,
1974
+ executed
1975
+ };
1976
+ }
1977
+ /**
1978
+ * Get migration files from the migrations directory
1979
+ */ async getMigrationFiles() {
1980
+ try {
1981
+ const files = await readdir(this.migrationsPath);
1982
+ return files.filter((f)=>/\.(ts|js|mjs)$/.test(f)).filter((f)=>!f.endsWith('.d.ts')).sort();
1983
+ } catch (error) {
1984
+ if (error.code === 'ENOENT') {
1985
+ return [];
1986
+ }
1987
+ throw error;
1988
+ }
1989
+ }
1990
+ /**
1991
+ * Load a migration from a file
1992
+ */ async loadMigration(fileName) {
1993
+ const filePath = join(this.migrationsPath, fileName);
1994
+ const fileUrl = pathToFileURL(filePath).href;
1995
+ try {
1996
+ const module = await import(fileUrl);
1997
+ const migration = module.default ?? module;
1998
+ if (this.isMigration(migration)) {
1999
+ return {
2000
+ name: this.getMigrationName(fileName),
2001
+ up: migration.up.bind(migration),
2002
+ down: migration.down.bind(migration)
2003
+ };
2004
+ }
2005
+ this.logger(`Warning: ${fileName} is not a valid migration`);
2006
+ return undefined;
2007
+ } catch (error) {
2008
+ this.logger(`Error loading migration ${fileName}: ${error.message}`);
2009
+ return undefined;
2010
+ }
2011
+ }
2012
+ /**
2013
+ * Check if an object is a valid migration
2014
+ */ isMigration(obj) {
2015
+ return typeof obj === 'object' && obj !== undefined && obj !== null && typeof obj.up === 'function' && typeof obj.down === 'function';
2016
+ }
2017
+ /**
2018
+ * Extract migration name from filename
2019
+ */ getMigrationName(fileName) {
2020
+ return basename(fileName, extname(fileName));
2021
+ }
2022
+ /**
2023
+ * Generate timestamp string for migration names
2024
+ */ getTimestamp() {
2025
+ const now = new Date();
2026
+ return [
2027
+ now.getFullYear(),
2028
+ String(now.getMonth() + 1).padStart(2, '0'),
2029
+ String(now.getDate()).padStart(2, '0'),
2030
+ String(now.getHours()).padStart(2, '0'),
2031
+ String(now.getMinutes()).padStart(2, '0'),
2032
+ String(now.getSeconds()).padStart(2, '0')
2033
+ ].join('');
2034
+ }
2035
+ /**
2036
+ * Convert a string to a slug for filenames
2037
+ */ slugify(text) {
2038
+ return text.toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '');
2039
+ }
2040
+ /**
2041
+ * Generate migration file content
2042
+ */ generateMigrationContent(name) {
2043
+ return /*ts*/ `import type { SqlQuerier } from '@uql/migrate';
2044
+
2045
+ /**
2046
+ * Migration: ${name}
2047
+ * Created: ${new Date().toISOString()}
2048
+ */
2049
+ export default {
2050
+ async up(querier: SqlQuerier): Promise<void> {
2051
+ // Add your migration logic here
2052
+ // Example:
2053
+ // await querier.run(\`
2054
+ // CREATE TABLE "users" (
2055
+ // "id" SERIAL PRIMARY KEY,
2056
+ // "name" VARCHAR(255) NOT NULL,
2057
+ // "email" VARCHAR(255) UNIQUE NOT NULL,
2058
+ // "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
2059
+ // )
2060
+ // \`);
2061
+ },
2062
+
2063
+ async down(querier: SqlQuerier): Promise<void> {
2064
+ // Add your rollback logic here
2065
+ // Example:
2066
+ // await querier.run(\`DROP TABLE IF EXISTS "users"\`);
2067
+ },
2068
+ };
2069
+ `;
2070
+ }
2071
+ /**
2072
+ * Generate migration file content with SQL statements
2073
+ */ generateMigrationContentWithStatements(name, upStatements, downStatements) {
2074
+ const upSql = upStatements.map((s)=>/*ts*/ ` await querier.run(\`${s}\`);`).join('\n');
2075
+ const downSql = downStatements.map((s)=>/*ts*/ ` await querier.run(\`${s}\`);`).join('\n');
2076
+ return /*ts*/ `import type { SqlQuerier } from '@uql/migrate';
2077
+
2078
+ /**
2079
+ * Migration: ${name}
2080
+ * Created: ${new Date().toISOString()}
2081
+ * Generated from entity definitions
2082
+ */
2083
+ export default {
2084
+ async up(querier: SqlQuerier): Promise<void> {
2085
+ ${upSql}
2086
+ },
2087
+
2088
+ async down(querier: SqlQuerier): Promise<void> {
2089
+ ${downSql}
2090
+ },
2091
+ };
2092
+ `;
2093
+ }
2094
+ }
2095
+ /**
2096
+ * Helper function to define a migration with proper typing
2097
+ */ function defineMigration(migration) {
2098
+ return migration;
2099
+ }
2100
+
2101
+ /**
2102
+ * Stores migration state in a JSON file (useful for development/testing)
2103
+ */ class JsonMigrationStorage {
2104
+ constructor(filePath = './migrations/.uql-migrations.json'){
2105
+ this.cache = null;
2106
+ this.filePath = filePath;
2107
+ }
2108
+ async ensureStorage() {
2109
+ try {
2110
+ await this.load();
2111
+ } catch {
2112
+ // File doesn't exist, create it
2113
+ await this.save([]);
2114
+ }
2115
+ }
2116
+ async executed() {
2117
+ await this.ensureStorage();
2118
+ return this.cache ?? [];
2119
+ }
2120
+ async logWithQuerier(_querier, migrationName) {
2121
+ const executed = await this.executed();
2122
+ if (!executed.includes(migrationName)) {
2123
+ executed.push(migrationName);
2124
+ executed.sort();
2125
+ await this.save(executed);
2126
+ }
2127
+ }
2128
+ async unlogWithQuerier(_querier, migrationName) {
2129
+ const executed = await this.executed();
2130
+ const index = executed.indexOf(migrationName);
2131
+ if (index !== -1) {
2132
+ executed.splice(index, 1);
2133
+ await this.save(executed);
2134
+ }
2135
+ }
2136
+ async load() {
2137
+ const content = await readFile(this.filePath, 'utf-8');
2138
+ this.cache = JSON.parse(content);
2139
+ }
2140
+ async save(migrations) {
2141
+ await mkdir(dirname(this.filePath), {
2142
+ recursive: true
2143
+ });
2144
+ await writeFile(this.filePath, JSON.stringify(migrations, null, 2), 'utf-8');
2145
+ this.cache = migrations;
2146
+ }
2147
+ }
2148
+
2149
+ export { AbstractSchemaGenerator, DatabaseMigrationStorage, JsonMigrationStorage, MysqlSchemaGenerator as MariadbSchemaGenerator, MariadbSchemaIntrospector, Migrator, MysqlSchemaGenerator, MysqlSchemaIntrospector, PostgresSchemaGenerator, PostgresSchemaIntrospector, SqliteSchemaGenerator, SqliteSchemaIntrospector, defineMigration };
2150
+ //# sourceMappingURL=uql-browser.min.js.map