sonamu 0.4.13 → 0.5.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 (371) hide show
  1. package/.swcrc +15 -0
  2. package/dist/api/base-frame.d.ts +8 -0
  3. package/dist/api/base-frame.d.ts.map +1 -0
  4. package/dist/api/base-frame.js +2 -0
  5. package/dist/api/base-frame.js.map +1 -0
  6. package/dist/api/caster.d.ts +5 -0
  7. package/dist/api/caster.d.ts.map +1 -0
  8. package/dist/api/caster.js +2 -0
  9. package/dist/api/caster.js.map +1 -0
  10. package/dist/api/code-converters.d.ts +23 -0
  11. package/dist/api/code-converters.d.ts.map +1 -0
  12. package/dist/api/code-converters.js +2 -0
  13. package/dist/api/code-converters.js.map +1 -0
  14. package/dist/api/context.d.ts +16 -0
  15. package/dist/api/context.d.ts.map +1 -0
  16. package/dist/api/context.js +2 -0
  17. package/dist/api/context.js.map +1 -0
  18. package/dist/api/decorators.d.ts +50 -0
  19. package/dist/api/decorators.d.ts.map +1 -0
  20. package/dist/api/decorators.js +2 -0
  21. package/dist/api/decorators.js.map +1 -0
  22. package/dist/api/index.d.ts +8 -0
  23. package/dist/api/index.d.ts.map +1 -0
  24. package/dist/api/index.js +2 -0
  25. package/dist/api/index.js.map +1 -0
  26. package/dist/api/sonamu.d.ts +83 -0
  27. package/dist/api/sonamu.d.ts.map +1 -0
  28. package/dist/api/sonamu.js +2 -0
  29. package/dist/api/sonamu.js.map +1 -0
  30. package/dist/api/sonamu.types.d.ts +30 -0
  31. package/dist/api/sonamu.types.d.ts.map +1 -0
  32. package/dist/api/sonamu.types.js +2 -0
  33. package/dist/api/sonamu.types.js.map +1 -0
  34. package/dist/bin/build-config.d.ts +5 -0
  35. package/dist/bin/build-config.d.ts.map +1 -0
  36. package/dist/bin/build-config.js +2 -0
  37. package/dist/bin/build-config.js.map +1 -0
  38. package/dist/bin/cli-wrapper.d.ts +2 -0
  39. package/dist/bin/cli-wrapper.d.ts.map +1 -0
  40. package/dist/bin/cli-wrapper.js +1 -38
  41. package/dist/bin/cli-wrapper.js.map +1 -1
  42. package/dist/bin/cli.d.ts +2 -2
  43. package/dist/bin/cli.d.ts.map +1 -0
  44. package/dist/bin/cli.js +1 -903
  45. package/dist/bin/cli.js.map +1 -1
  46. package/dist/bin/cli.mjs +2 -2
  47. package/dist/{chunk-DMJSNO2L.js → chunk-2WAC2GER.js} +44 -44
  48. package/dist/{chunk-DMJSNO2L.js.map → chunk-2WAC2GER.js.map} +1 -1
  49. package/dist/{chunk-NI37CY4T.mjs → chunk-C3IPIF6O.mjs} +2 -2
  50. package/dist/{chunk-DYFCACHD.js → chunk-EXHKSVTE.js} +7 -7
  51. package/dist/{chunk-QJFHDCBN.mjs → chunk-FCERKIIF.mjs} +2 -2
  52. package/dist/chunk-FCERKIIF.mjs.map +1 -0
  53. package/dist/{chunk-DDJ7T4MA.mjs → chunk-HGIBJYOU.mjs} +2 -2
  54. package/dist/{chunk-NIFOTHBW.mjs → chunk-JKSOJRQA.mjs} +2 -2
  55. package/dist/{chunk-CXAVBVKC.js → chunk-OTKKFP3Y.js} +100 -100
  56. package/dist/{chunk-J6S43O7G.js → chunk-UZ2IY5VE.js} +4 -4
  57. package/dist/database/_batch_update.d.ts +15 -0
  58. package/dist/database/_batch_update.d.ts.map +1 -0
  59. package/dist/database/_batch_update.js +2 -0
  60. package/dist/database/_batch_update.js.map +1 -0
  61. package/dist/database/base-model.d.ts +41 -0
  62. package/dist/database/base-model.d.ts.map +1 -0
  63. package/dist/database/base-model.js +2 -0
  64. package/dist/database/base-model.js.map +1 -0
  65. package/dist/database/code-generator.d.ts +13 -0
  66. package/dist/database/code-generator.d.ts.map +1 -0
  67. package/dist/database/code-generator.js +2 -0
  68. package/dist/database/code-generator.js.map +1 -0
  69. package/dist/database/db.d.ts +40 -0
  70. package/dist/database/db.d.ts.map +1 -0
  71. package/dist/database/db.js +2 -0
  72. package/dist/database/db.js.map +1 -0
  73. package/dist/database/drivers/knex/base-model.js +8 -8
  74. package/dist/database/drivers/knex/base-model.mjs +3 -3
  75. package/dist/database/drivers/kysely/base-model.js +9 -9
  76. package/dist/database/drivers/kysely/base-model.mjs +3 -3
  77. package/dist/database/knex-plugins/knex-on-duplicate-update.d.ts +2 -0
  78. package/dist/database/knex-plugins/knex-on-duplicate-update.d.ts.map +1 -0
  79. package/dist/database/knex-plugins/knex-on-duplicate-update.js +2 -0
  80. package/dist/database/knex-plugins/knex-on-duplicate-update.js.map +1 -0
  81. package/dist/database/puri-wrapper.d.ts +34 -0
  82. package/dist/database/puri-wrapper.d.ts.map +1 -0
  83. package/dist/database/puri-wrapper.js +2 -0
  84. package/dist/database/puri-wrapper.js.map +1 -0
  85. package/dist/database/puri.d.ts +83 -0
  86. package/dist/database/puri.d.ts.map +1 -0
  87. package/dist/database/puri.js +2 -0
  88. package/dist/database/puri.js.map +1 -0
  89. package/dist/database/puri.types.d.ts +60 -0
  90. package/dist/database/puri.types.d.ts.map +1 -0
  91. package/dist/database/puri.types.js +2 -0
  92. package/dist/database/puri.types.js.map +1 -0
  93. package/dist/database/transaction-context.d.ts +9 -0
  94. package/dist/database/transaction-context.d.ts.map +1 -0
  95. package/dist/database/transaction-context.js +2 -0
  96. package/dist/database/transaction-context.js.map +1 -0
  97. package/dist/database/types.d.ts +39 -0
  98. package/dist/database/types.d.ts.map +1 -0
  99. package/dist/database/types.js +2 -0
  100. package/dist/database/types.js.map +1 -0
  101. package/dist/database/upsert-builder.d.ts +34 -0
  102. package/dist/database/upsert-builder.d.ts.map +1 -0
  103. package/dist/database/upsert-builder.js +2 -0
  104. package/dist/database/upsert-builder.js.map +1 -0
  105. package/dist/entity/entity-manager.d.ts +32 -0
  106. package/dist/entity/entity-manager.d.ts.map +1 -0
  107. package/dist/entity/entity-manager.js +2 -0
  108. package/dist/entity/entity-manager.js.map +1 -0
  109. package/dist/entity/entity-utils.d.ts +61 -0
  110. package/dist/entity/entity-utils.d.ts.map +1 -0
  111. package/dist/entity/entity-utils.js +2 -0
  112. package/dist/entity/entity-utils.js.map +1 -0
  113. package/dist/entity/entity.d.ts +62 -0
  114. package/dist/entity/entity.d.ts.map +1 -0
  115. package/dist/entity/entity.js +2 -0
  116. package/dist/entity/entity.js.map +1 -0
  117. package/dist/entity/migrator.d.ts +135 -0
  118. package/dist/entity/migrator.d.ts.map +1 -0
  119. package/dist/entity/migrator.js +2 -0
  120. package/dist/entity/migrator.js.map +1 -0
  121. package/dist/exceptions/error-handler.d.ts +3 -0
  122. package/dist/exceptions/error-handler.d.ts.map +1 -0
  123. package/dist/exceptions/error-handler.js +2 -0
  124. package/dist/exceptions/error-handler.js.map +1 -0
  125. package/dist/exceptions/so-exceptions.d.ts +48 -0
  126. package/dist/exceptions/so-exceptions.d.ts.map +1 -0
  127. package/dist/exceptions/so-exceptions.js +2 -0
  128. package/dist/exceptions/so-exceptions.js.map +1 -0
  129. package/dist/file-storage/driver.d.ts +45 -0
  130. package/dist/file-storage/driver.d.ts.map +1 -0
  131. package/dist/file-storage/driver.js +2 -0
  132. package/dist/file-storage/driver.js.map +1 -0
  133. package/dist/file-storage/file-storage.d.ts +50 -0
  134. package/dist/file-storage/file-storage.d.ts.map +1 -0
  135. package/dist/file-storage/file-storage.js +2 -0
  136. package/dist/file-storage/file-storage.js.map +1 -0
  137. package/dist/index.d.ts +22 -813
  138. package/dist/index.d.ts.map +1 -0
  139. package/dist/index.js +1 -433
  140. package/dist/index.js.map +1 -1
  141. package/dist/index.mjs +3 -3
  142. package/dist/migration/code-generation.d.ts +15 -0
  143. package/dist/migration/code-generation.d.ts.map +1 -0
  144. package/dist/migration/code-generation.js +2 -0
  145. package/dist/migration/code-generation.js.map +1 -0
  146. package/dist/migration/migration-set.d.ts +17 -0
  147. package/dist/migration/migration-set.d.ts.map +1 -0
  148. package/dist/migration/migration-set.js +2 -0
  149. package/dist/migration/migration-set.js.map +1 -0
  150. package/dist/migration/migrator.d.ts +130 -0
  151. package/dist/migration/migrator.d.ts.map +1 -0
  152. package/dist/migration/migrator.js +2 -0
  153. package/dist/migration/migrator.js.map +1 -0
  154. package/dist/migration/types.d.ts +52 -0
  155. package/dist/migration/types.d.ts.map +1 -0
  156. package/dist/migration/types.js +2 -0
  157. package/dist/migration/types.js.map +1 -0
  158. package/dist/smd/smd-manager.d.ts +28 -0
  159. package/dist/smd/smd-manager.d.ts.map +1 -0
  160. package/dist/smd/smd-manager.js +2 -0
  161. package/dist/smd/smd-manager.js.map +1 -0
  162. package/dist/smd/smd.d.ts +40 -0
  163. package/dist/smd/smd.d.ts.map +1 -0
  164. package/dist/smd/smd.js +2 -0
  165. package/dist/smd/smd.js.map +1 -0
  166. package/dist/syncer/index.d.ts +2 -0
  167. package/dist/syncer/index.d.ts.map +1 -0
  168. package/dist/syncer/index.js +2 -0
  169. package/dist/syncer/index.js.map +1 -0
  170. package/dist/syncer/syncer.d.ts +127 -0
  171. package/dist/syncer/syncer.d.ts.map +1 -0
  172. package/dist/syncer/syncer.js +2 -0
  173. package/dist/syncer/syncer.js.map +1 -0
  174. package/dist/templates/base-template.d.ts +13 -0
  175. package/dist/templates/base-template.d.ts.map +1 -0
  176. package/dist/templates/base-template.js +2 -0
  177. package/dist/templates/base-template.js.map +1 -0
  178. package/dist/templates/entity.template.d.ts +17 -0
  179. package/dist/templates/entity.template.d.ts.map +1 -0
  180. package/dist/templates/entity.template.js +2 -0
  181. package/dist/templates/entity.template.js.map +1 -0
  182. package/dist/templates/generated.template.d.ts +27 -0
  183. package/dist/templates/generated.template.d.ts.map +1 -0
  184. package/dist/templates/generated.template.js +2 -0
  185. package/dist/templates/generated.template.js.map +1 -0
  186. package/dist/templates/generated_http.template.d.ts +24 -0
  187. package/dist/templates/generated_http.template.d.ts.map +1 -0
  188. package/dist/templates/generated_http.template.js +2 -0
  189. package/dist/templates/generated_http.template.js.map +1 -0
  190. package/dist/templates/generated_sso.template.d.ts +20 -0
  191. package/dist/templates/generated_sso.template.d.ts.map +1 -0
  192. package/dist/templates/generated_sso.template.js +2 -0
  193. package/dist/templates/generated_sso.template.js.map +1 -0
  194. package/dist/templates/index.d.ts +2 -0
  195. package/dist/templates/index.d.ts.map +1 -0
  196. package/dist/templates/index.js +2 -0
  197. package/dist/templates/index.js.map +1 -0
  198. package/dist/templates/init_types.template.d.ts +17 -0
  199. package/dist/templates/init_types.template.d.ts.map +1 -0
  200. package/dist/templates/init_types.template.js +2 -0
  201. package/dist/templates/init_types.template.js.map +1 -0
  202. package/dist/templates/model.template.d.ts +17 -0
  203. package/dist/templates/model.template.d.ts.map +1 -0
  204. package/dist/templates/model.template.js +2 -0
  205. package/dist/templates/model.template.js.map +1 -0
  206. package/dist/templates/model_test.template.d.ts +17 -0
  207. package/dist/templates/model_test.template.d.ts.map +1 -0
  208. package/dist/templates/model_test.template.js +2 -0
  209. package/dist/templates/model_test.template.js.map +1 -0
  210. package/dist/templates/service.template.d.ts +29 -0
  211. package/dist/templates/service.template.d.ts.map +1 -0
  212. package/dist/templates/service.template.js +2 -0
  213. package/dist/templates/service.template.js.map +1 -0
  214. package/dist/templates/view_enums_buttonset.template.d.ts +17 -0
  215. package/dist/templates/view_enums_buttonset.template.d.ts.map +1 -0
  216. package/dist/templates/view_enums_buttonset.template.js +2 -0
  217. package/dist/templates/view_enums_buttonset.template.js.map +1 -0
  218. package/dist/templates/view_enums_dropdown.template.d.ts +18 -0
  219. package/dist/templates/view_enums_dropdown.template.d.ts.map +1 -0
  220. package/dist/templates/view_enums_dropdown.template.js +2 -0
  221. package/dist/templates/view_enums_dropdown.template.js.map +1 -0
  222. package/dist/templates/view_enums_select.template.d.ts +17 -0
  223. package/dist/templates/view_enums_select.template.d.ts.map +1 -0
  224. package/dist/templates/view_enums_select.template.js +2 -0
  225. package/dist/templates/view_enums_select.template.js.map +1 -0
  226. package/dist/templates/view_form.template.d.ts +26 -0
  227. package/dist/templates/view_form.template.d.ts.map +1 -0
  228. package/dist/templates/view_form.template.js +2 -0
  229. package/dist/templates/view_form.template.js.map +1 -0
  230. package/dist/templates/view_id_all_select.template.d.ts +17 -0
  231. package/dist/templates/view_id_all_select.template.d.ts.map +1 -0
  232. package/dist/templates/view_id_all_select.template.js +2 -0
  233. package/dist/templates/view_id_all_select.template.js.map +1 -0
  234. package/dist/templates/view_id_async_select.template.d.ts +17 -0
  235. package/dist/templates/view_id_async_select.template.d.ts.map +1 -0
  236. package/dist/templates/view_id_async_select.template.js +2 -0
  237. package/dist/templates/view_id_async_select.template.js.map +1 -0
  238. package/dist/templates/view_list.template.d.ts +38 -0
  239. package/dist/templates/view_list.template.d.ts.map +1 -0
  240. package/dist/templates/view_list.template.js +2 -0
  241. package/dist/templates/view_list.template.js.map +1 -0
  242. package/dist/templates/view_list_columns.template.d.ts +17 -0
  243. package/dist/templates/view_list_columns.template.d.ts.map +1 -0
  244. package/dist/templates/view_list_columns.template.js +2 -0
  245. package/dist/templates/view_list_columns.template.js.map +1 -0
  246. package/dist/templates/view_search_input.template.d.ts +17 -0
  247. package/dist/templates/view_search_input.template.d.ts.map +1 -0
  248. package/dist/templates/view_search_input.template.js +2 -0
  249. package/dist/templates/view_search_input.template.js.map +1 -0
  250. package/dist/testing/_relation-graph.d.ts +7 -0
  251. package/dist/testing/_relation-graph.d.ts.map +1 -0
  252. package/dist/testing/_relation-graph.js +2 -0
  253. package/dist/testing/_relation-graph.js.map +1 -0
  254. package/dist/testing/fixture-manager.d.ts +35 -0
  255. package/dist/testing/fixture-manager.d.ts.map +1 -0
  256. package/dist/testing/fixture-manager.js +2 -0
  257. package/dist/testing/fixture-manager.js.map +1 -0
  258. package/dist/types/types.d.ts +609 -0
  259. package/dist/types/types.d.ts.map +1 -0
  260. package/dist/types/types.js +2 -0
  261. package/dist/types/types.js.map +1 -0
  262. package/dist/typings/knex.d.js +2 -0
  263. package/dist/typings/knex.d.js.map +1 -0
  264. package/dist/utils/async-utils.d.ts +25 -0
  265. package/dist/utils/async-utils.d.ts.map +1 -0
  266. package/dist/utils/async-utils.js +2 -0
  267. package/dist/utils/async-utils.js.map +1 -0
  268. package/dist/utils/controller.d.ts +9 -0
  269. package/dist/utils/controller.d.ts.map +1 -0
  270. package/dist/utils/controller.js +2 -0
  271. package/dist/utils/controller.js.map +1 -0
  272. package/dist/utils/fs-utils.d.ts +9 -0
  273. package/dist/utils/fs-utils.d.ts.map +1 -0
  274. package/dist/utils/fs-utils.js +2 -0
  275. package/dist/utils/fs-utils.js.map +1 -0
  276. package/dist/utils/lodash-able.d.ts +2 -0
  277. package/dist/utils/lodash-able.d.ts.map +1 -0
  278. package/dist/utils/lodash-able.js +2 -0
  279. package/dist/utils/lodash-able.js.map +1 -0
  280. package/dist/utils/model.d.ts +17 -0
  281. package/dist/utils/model.d.ts.map +1 -0
  282. package/dist/utils/model.js +2 -0
  283. package/dist/utils/model.js.map +1 -0
  284. package/dist/utils/sql-parser.d.ts +4 -0
  285. package/dist/utils/sql-parser.d.ts.map +1 -0
  286. package/dist/utils/sql-parser.js +2 -0
  287. package/dist/utils/sql-parser.js.map +1 -0
  288. package/dist/utils/utils.d.ts +9 -0
  289. package/dist/utils/utils.d.ts.map +1 -0
  290. package/dist/utils/utils.js +2 -0
  291. package/dist/utils/utils.js.map +1 -0
  292. package/dist/utils/zod-error.d.ts +8 -0
  293. package/dist/utils/zod-error.d.ts.map +1 -0
  294. package/dist/utils/zod-error.js +2 -0
  295. package/dist/utils/zod-error.js.map +1 -0
  296. package/nodemon.json +6 -0
  297. package/package.json +29 -44
  298. package/src/api/base-frame.ts +3 -4
  299. package/src/api/caster.ts +22 -23
  300. package/src/api/code-converters.ts +170 -134
  301. package/src/api/context.ts +13 -6
  302. package/src/api/decorators.ts +146 -20
  303. package/src/api/index.ts +2 -0
  304. package/src/api/sonamu.ts +374 -165
  305. package/src/bin/build-config.ts +5 -0
  306. package/src/bin/cli-wrapper.ts +29 -40
  307. package/src/bin/cli.ts +132 -190
  308. package/src/database/_batch_update.ts +10 -15
  309. package/src/database/base-model.ts +300 -216
  310. package/src/database/db.ts +191 -21
  311. package/src/database/{drivers/knex/plugins → knex-plugins}/knex-on-duplicate-update.ts +1 -1
  312. package/src/database/puri-wrapper.ts +129 -0
  313. package/src/database/puri.ts +808 -0
  314. package/src/database/puri.types.ts +222 -0
  315. package/src/database/transaction-context.ts +18 -0
  316. package/src/database/upsert-builder.ts +32 -35
  317. package/src/entity/entity-manager.ts +7 -15
  318. package/src/entity/entity.ts +9 -31
  319. package/src/entity/migrator-/354/235/264/354/202/254/352/260/224/354/226/264/354/232/224.md +1 -0
  320. package/src/file-storage/driver.ts +121 -0
  321. package/src/file-storage/file-storage.ts +100 -0
  322. package/src/index.ts +14 -11
  323. package/src/migration/code-generation.ts +777 -0
  324. package/src/migration/migration-set.ts +453 -0
  325. package/src/migration/migrator.ts +823 -0
  326. package/src/migration/types.ts +53 -0
  327. package/src/shared/web.shared.ts.txt +33 -2
  328. package/src/syncer/syncer.ts +294 -127
  329. package/src/templates/generated.template.ts +13 -1
  330. package/src/templates/generated_http.template.ts +15 -12
  331. package/src/templates/generated_sso.template.ts +50 -2
  332. package/src/templates/model.template.ts +138 -2
  333. package/src/templates/service.template.ts +0 -1
  334. package/src/templates/view_form.template.ts +11 -7
  335. package/src/templates/view_list.template.ts +12 -4
  336. package/src/testing/fixture-manager.ts +229 -174
  337. package/src/types/types.ts +102 -14
  338. package/src/utils/async-utils.ts +64 -0
  339. package/src/utils/fs-utils.ts +17 -0
  340. package/src/utils/model.ts +0 -2
  341. package/src/utils/utils.ts +14 -58
  342. package/src/utils/zod-error.ts +12 -176
  343. package/tsconfig.json +2 -0
  344. package/tsup.config.js +4 -2
  345. package/.pnp.cjs +0 -14363
  346. package/.pnp.loader.mjs +0 -2047
  347. package/.vscode/extensions.json +0 -6
  348. package/.vscode/settings.json +0 -9
  349. package/.yarnrc.yml +0 -5
  350. package/dist/chunk-QJFHDCBN.mjs.map +0 -1
  351. package/src/database/base-model.abstract.ts +0 -97
  352. package/src/database/db.abstract.ts +0 -75
  353. package/src/database/drivers/knex/base-model.ts +0 -55
  354. package/src/database/drivers/knex/client.ts +0 -209
  355. package/src/database/drivers/knex/db.ts +0 -232
  356. package/src/database/drivers/knex/generator.ts +0 -659
  357. package/src/database/drivers/kysely/base-model.ts +0 -89
  358. package/src/database/drivers/kysely/client.ts +0 -309
  359. package/src/database/drivers/kysely/db.ts +0 -238
  360. package/src/database/drivers/kysely/generator.ts +0 -714
  361. package/src/database/types.ts +0 -118
  362. package/src/entity/migrator.ts +0 -1400
  363. package/src/smd/smd-manager.ts +0 -139
  364. package/src/smd/smd.ts +0 -571
  365. package/src/templates/kysely_types.template.ts +0 -205
  366. /package/dist/{chunk-NI37CY4T.mjs.map → chunk-C3IPIF6O.mjs.map} +0 -0
  367. /package/dist/{chunk-DYFCACHD.js.map → chunk-EXHKSVTE.js.map} +0 -0
  368. /package/dist/{chunk-DDJ7T4MA.mjs.map → chunk-HGIBJYOU.mjs.map} +0 -0
  369. /package/dist/{chunk-NIFOTHBW.mjs.map → chunk-JKSOJRQA.mjs.map} +0 -0
  370. /package/dist/{chunk-CXAVBVKC.js.map → chunk-OTKKFP3Y.js.map} +0 -0
  371. /package/dist/{chunk-J6S43O7G.js.map → chunk-UZ2IY5VE.js.map} +0 -0
@@ -0,0 +1,823 @@
1
+ import _ from "lodash";
2
+ import knex, { Knex } from "knex";
3
+ import chalk from "chalk";
4
+ import { DateTime } from "luxon";
5
+ import { mkdir, readdir, unlink, writeFile } from "fs/promises";
6
+ import { exists } from "../utils/fs-utils";
7
+ import prompts from "prompts";
8
+ import { execSync } from "child_process";
9
+ import path from "path";
10
+ import { GenMigrationCode, MigrationSet } from "../types/types";
11
+ import { EntityManager } from "../entity/entity-manager";
12
+ import { Sonamu } from "../api";
13
+ import { ServiceUnavailableException } from "../exceptions/so-exceptions";
14
+ import { SonamuDBConfig } from "../database/db";
15
+ import { generateCreateCode, generateAlterCode } from "./code-generation";
16
+ import { MigrationStatus, MigrationCode, ConnString } from "./types";
17
+ import { getMigrationSetFromDB } from "./migration-set";
18
+ import { getMigrationSetFromEntity } from "./migration-set";
19
+
20
+ type MigratorMode = "dev" | "deploy";
21
+ export type MigratorOptions = {
22
+ readonly mode: MigratorMode;
23
+ };
24
+
25
+ export class Migrator {
26
+ targets: {
27
+ compare?: Knex;
28
+ pending: Knex;
29
+ shadow: Knex;
30
+ apply: Knex[];
31
+ };
32
+
33
+ constructor(private readonly options: MigratorOptions) {
34
+ const { dbConfig } = Sonamu;
35
+
36
+ if (this.options.mode === "dev") {
37
+ const devDB = knex(dbConfig.development_master);
38
+ const testDB = knex(dbConfig.test);
39
+ const fixtureLocalDB = knex(dbConfig.fixture_local);
40
+
41
+ const applyDBs = [devDB, testDB, fixtureLocalDB];
42
+ if (
43
+ (dbConfig.fixture_local.connection as Knex.MySql2ConnectionConfig)
44
+ .host !==
45
+ (dbConfig.fixture_remote.connection as Knex.MySql2ConnectionConfig)
46
+ .host ||
47
+ (dbConfig.fixture_local.connection as Knex.MySql2ConnectionConfig)
48
+ .database !==
49
+ (dbConfig.fixture_remote.connection as Knex.MySql2ConnectionConfig)
50
+ .database
51
+ ) {
52
+ const fixtureRemoteDB = knex(dbConfig.fixture_remote);
53
+ applyDBs.push(fixtureRemoteDB);
54
+ }
55
+
56
+ this.targets = {
57
+ compare: devDB,
58
+ pending: devDB,
59
+ shadow: testDB,
60
+ apply: applyDBs,
61
+ };
62
+ } else if (this.options.mode === "deploy") {
63
+ const productionDB = knex(dbConfig.production_master);
64
+ const testDB = knex(dbConfig.test);
65
+
66
+ this.targets = {
67
+ pending: productionDB,
68
+ shadow: testDB,
69
+ apply: [productionDB],
70
+ };
71
+ } else {
72
+ throw new Error(`잘못된 모드 ${this.options.mode} 입력`);
73
+ }
74
+ }
75
+
76
+ private async getMigrationCodes(): Promise<{
77
+ normal: MigrationCode[];
78
+ onlyTs: MigrationCode[];
79
+ onlyJs: MigrationCode[];
80
+ }> {
81
+ const srcMigrationsDir = `${Sonamu.apiRootPath}/src/migrations`;
82
+ const distMigrationsDir = `${Sonamu.apiRootPath}/dist/migrations`;
83
+
84
+ if (!(await exists(srcMigrationsDir))) {
85
+ await mkdir(srcMigrationsDir, {
86
+ recursive: true,
87
+ });
88
+ }
89
+ if (!(await exists(distMigrationsDir))) {
90
+ await mkdir(distMigrationsDir, {
91
+ recursive: true,
92
+ });
93
+ }
94
+ const srcMigrations = (await readdir(srcMigrationsDir))
95
+ .filter((f) => f.endsWith(".ts"))
96
+ .map((f) => f.split(".")[0]);
97
+ const distMigrations = (await readdir(distMigrationsDir))
98
+ .filter((f) => f.endsWith(".js"))
99
+ .map((f) => f.split(".")[0]);
100
+
101
+ const normal = _.intersection(srcMigrations, distMigrations)
102
+ .map((filename) => {
103
+ return {
104
+ name: filename,
105
+ path: path.join(srcMigrationsDir, filename) + ".ts",
106
+ };
107
+ })
108
+ .sort((a, b) => (a > b ? 1 : -1));
109
+
110
+ const onlyTs = _.difference(srcMigrations, distMigrations).map(
111
+ (filename) => {
112
+ return {
113
+ name: filename,
114
+ path: path.join(srcMigrationsDir, filename) + ".ts",
115
+ };
116
+ }
117
+ );
118
+
119
+ const onlyJs = _.difference(distMigrations, srcMigrations).map(
120
+ (filename) => {
121
+ return {
122
+ name: filename,
123
+ path: path.join(distMigrationsDir, filename) + ".js",
124
+ };
125
+ }
126
+ );
127
+
128
+ return {
129
+ normal,
130
+ onlyTs,
131
+ onlyJs,
132
+ };
133
+ }
134
+
135
+ /**
136
+ * 타겟별 마이그레이션 상태와 코드 생성/준비 상태를 구해옵니다.
137
+ * 실제로 DB에 접근도 하고 마이그레이션 코드 파일도 확인하고,
138
+ * 필요하다면 적용할 수 있는 코드를 생성까지 해옵니다.
139
+ *
140
+ * CLI와 Sonamu UI에서 사용됩니다.
141
+ *
142
+ * @returns
143
+ */
144
+ async getStatus(): Promise<MigrationStatus> {
145
+ const { normal, onlyTs, onlyJs } = await this.getMigrationCodes();
146
+ if (onlyTs.length > 0) {
147
+ console.debug({ onlyTs });
148
+ throw new ServiceUnavailableException(
149
+ `There are un-compiled TS migration files.\nPlease compile them first. You might want to run a development server with HMR.\n\n${onlyTs
150
+ .map((f) => f.name)
151
+ .join("\n")}`
152
+ );
153
+ }
154
+ if (onlyJs.length > 0) {
155
+ console.debug({ onlyJs });
156
+ await Promise.all(
157
+ onlyJs.map(async (f) => {
158
+ execSync(
159
+ `rm -f ${f.path.replace("/src/", "/dist/").replace(".ts", ".js")}`
160
+ );
161
+ })
162
+ );
163
+ }
164
+
165
+ const connKeys = Object.keys(Sonamu.dbConfig).filter(
166
+ (key) => key.endsWith("_slave") === false
167
+ ) as (keyof typeof Sonamu.dbConfig)[];
168
+
169
+ const statuses = await Promise.all(
170
+ connKeys.map(async (connKey) => {
171
+ const knexOptions = Sonamu.dbConfig[connKey];
172
+ const tConn = knex(knexOptions);
173
+
174
+ const status = await (async () => {
175
+ try {
176
+ return await tConn.migrate.status();
177
+ } catch (err) {
178
+ console.warn(
179
+ chalk.yellow(
180
+ `${connKey}의 마이그레이션 상태를 가져오는 데에 실패하였습니다. 데이터베이스가 올바르게 구성되지 않은 것 같습니다. 확인하시고 다시 시도해주세요.\n시도한 연결 설정:\n${JSON.stringify(knexOptions.connection, null, 2)}\n발생한 에러:\n${err}\n`
181
+ )
182
+ );
183
+ return "error" /*클라이언트에서 에러 체크에 사용하는 리터럴입니다.*/;
184
+ }
185
+ })();
186
+ const pending = await (async () => {
187
+ try {
188
+ const [, fdList] = await tConn.migrate.list();
189
+ return fdList.map((fd: { file: string }) =>
190
+ fd.file.replace(".js", "")
191
+ );
192
+ } catch (err) {
193
+ return [];
194
+ }
195
+ })();
196
+ const currentVersion = await (async () => {
197
+ try {
198
+ return await tConn.migrate.currentVersion();
199
+ } catch (err) {
200
+ return "error";
201
+ }
202
+ })();
203
+
204
+ const connection =
205
+ knexOptions.connection as Knex.MySql2ConnectionConfig;
206
+
207
+ await tConn.destroy();
208
+
209
+ return {
210
+ name: connKey.replace("_master", ""),
211
+ connKey,
212
+ connString: `mysql2://${connection.user ?? ""}@${connection.host}:${
213
+ connection.port
214
+ }/${connection.database}` as ConnString,
215
+ currentVersion,
216
+ status,
217
+ pending,
218
+ };
219
+ })
220
+ );
221
+
222
+ const preparedCodes: GenMigrationCode[] = await (async () => {
223
+ const status0conn = statuses.find((status) => status.status === 0);
224
+ if (status0conn === undefined) {
225
+ return [];
226
+ }
227
+
228
+ const compareDBconn = knex(Sonamu.dbConfig[status0conn.connKey]);
229
+ const genCodes = await this.compareMigrations(compareDBconn);
230
+
231
+ await compareDBconn.destroy();
232
+
233
+ return genCodes;
234
+ })();
235
+
236
+ return {
237
+ conns: statuses,
238
+ codes: normal,
239
+ preparedCodes,
240
+ };
241
+ /*
242
+ TS/JS 코드 컴파일 상태 확인
243
+ 1. 원본 파일 없는 JS파일이 존재하는 경우: 삭제
244
+ 2. 컴파일 되지 않은 TS파일이 존재하는 경우: throw 쳐서 데브 서버 오픈 요청
245
+
246
+ DB 마이그레이션 상태 확인
247
+ 1. 전체 DB설정에 대해서 현재 마이그레이션 상태 확인
248
+ - connKey: string
249
+ - status: number
250
+ - currentVersion: string
251
+ - list: { file: string; directory: string }[]
252
+
253
+ */
254
+ }
255
+
256
+ /**
257
+ * 마이그레이션을 적용하거나 롤백합니다.
258
+ * Sonamu UI에서 마이그레이션 작업을 수행할 때 사용됩니다.
259
+ *
260
+ * CLI와 Sonamu UI에서 사용됩니다.
261
+ *
262
+ * @param action 작업 유형 (apply/rollback)
263
+ * @param targets 작업 대상 DB 설정 키 (keyof SonamuDBConfig)
264
+ * @returns 작업 결과
265
+ */
266
+ async runAction(
267
+ action: "apply" | "rollback",
268
+ targets: (keyof SonamuDBConfig)[]
269
+ ): Promise<
270
+ {
271
+ connKey: string;
272
+ batchNo: number;
273
+ applied: string[];
274
+ }[]
275
+ > {
276
+ // get uniq knex configs
277
+ const configs = _.uniqBy(
278
+ targets
279
+ .map((target) => ({
280
+ connKey: target,
281
+ options: Sonamu.dbConfig[target as keyof typeof Sonamu.dbConfig],
282
+ }))
283
+ .filter((c) => c.options !== undefined),
284
+ ({ options }) =>
285
+ `${(options.connection as Knex.MySql2ConnectionConfig).host}:${
286
+ (options.connection as Knex.MySql2ConnectionConfig).port ?? 3306
287
+ }/${(options.connection as Knex.MySql2ConnectionConfig).database}`
288
+ );
289
+
290
+ // get connections
291
+ const conns = await Promise.all(
292
+ configs.map(async (config) => ({
293
+ connKey: config.connKey,
294
+ knex: knex(config.options),
295
+ }))
296
+ );
297
+
298
+ // action
299
+ const result = await (async () => {
300
+ switch (action) {
301
+ case "apply":
302
+ return Promise.all(
303
+ conns.map(async ({ connKey, knex }) => {
304
+ const [batchNo, applied] = await knex.migrate.latest();
305
+ return {
306
+ connKey,
307
+ batchNo,
308
+ applied,
309
+ };
310
+ })
311
+ );
312
+ case "rollback":
313
+ return Promise.all(
314
+ conns.map(async ({ connKey, knex }) => {
315
+ const [batchNo, applied] = await knex.migrate.rollback();
316
+ return {
317
+ connKey,
318
+ batchNo,
319
+ applied,
320
+ };
321
+ })
322
+ );
323
+ }
324
+ })();
325
+
326
+ // destroy
327
+ await Promise.all(
328
+ conns.map(({ knex }) => {
329
+ return knex.destroy();
330
+ })
331
+ );
332
+
333
+ return result;
334
+ }
335
+
336
+ /**
337
+ * 마이그레이션 코드 파일을 삭제합니다.
338
+ *
339
+ * Sonamu UI에서 사용됩니다.
340
+ *
341
+ * @param codeNames 삭제할 마이그레이션 코드 파일 이름 배열
342
+ * @returns 삭제된 마이그레이션 코드 파일 개수
343
+ */
344
+ async delCodes(codeNames: string[]): Promise<number> {
345
+ const { conns } = await this.getStatus();
346
+ if (
347
+ conns.some((conn) => {
348
+ return codeNames.some(
349
+ (codeName) => conn.pending.includes(codeName) === false
350
+ );
351
+ })
352
+ ) {
353
+ throw new Error(
354
+ "You cannot delete a migration file if there is already applied."
355
+ );
356
+ }
357
+
358
+ const delFiles = codeNames
359
+ .map((codeName) => [
360
+ `${Sonamu.apiRootPath}/src/migrations/${codeName}.ts`,
361
+ `${Sonamu.apiRootPath}/dist/migrations/${codeName}.js`,
362
+ ])
363
+ .flat();
364
+
365
+ const res = await Promise.all(
366
+ delFiles.map(async (delFile) => {
367
+ if (await exists(delFile)) {
368
+ console.log(chalk.red(`DELETE: ${delFile}`));
369
+ await unlink(delFile);
370
+ return delFiles.includes(".ts") ? 1 : 0;
371
+ }
372
+ return 0;
373
+ })
374
+ );
375
+ return _.sum(res);
376
+ }
377
+
378
+ /**
379
+ * 마이그레이션 코드 파일을 생성합니다.
380
+ *
381
+ * Sonamu UI에서 사용됩니다.
382
+ *
383
+ * @returns 생성된 마이그레이션 코드 파일 개수
384
+ */
385
+ async generatePreparedCodes(): Promise<number> {
386
+ const { preparedCodes } = await this.getStatus();
387
+ if (preparedCodes.length === 0) {
388
+ console.log(chalk.green("\n현재 모두 싱크된 상태입니다."));
389
+ return 0;
390
+ }
391
+
392
+ // 실제 코드 생성
393
+ const migrationsDir = `${Sonamu.apiRootPath}/src/migrations`;
394
+
395
+ for (const [index, pcode] of preparedCodes.entries()) {
396
+ if (pcode.formatted) {
397
+ const dateTag = DateTime.local()
398
+ .plus({ seconds: index })
399
+ .toFormat("yyyyMMddHHmmss");
400
+ const filePath = `${migrationsDir}/${dateTag}_${pcode.title}.ts`;
401
+ await writeFile(filePath, pcode.formatted!);
402
+ console.log(chalk.green(`MIGRTAION CREATED ${filePath}`));
403
+ }
404
+ }
405
+
406
+ return preparedCodes.length;
407
+ }
408
+
409
+ /**
410
+ * pending 마이그레이션 목록을 삭제합니다.
411
+ *
412
+ * CLI에서 사용됩니다.
413
+ */
414
+ async clearPendingList(): Promise<void> {
415
+ const [, pendingList] = (await this.targets.pending.migrate.list()) as [
416
+ unknown,
417
+ {
418
+ file: string;
419
+ directory: string;
420
+ }[],
421
+ ];
422
+ const migrationsDir = `${Sonamu.apiRootPath}/src/migrations`;
423
+ const delList = pendingList.map((df) => {
424
+ return path.join(migrationsDir, df.file).replace(".js", ".ts");
425
+ });
426
+ for (let p of delList) {
427
+ if (await exists(p)) {
428
+ await unlink(p);
429
+ }
430
+ }
431
+ await this.cleanUpDist(true);
432
+ }
433
+
434
+ /**
435
+ * 마이그레이션 코드 파일을 확인합니다.
436
+ *
437
+ * CLI에서 사용됩니다.
438
+ */
439
+ async check(): Promise<void> {
440
+ const codes = await this.compareMigrations(this.targets.compare!);
441
+ if (codes.length === 0) {
442
+ console.log(chalk.green("\n현재 모두 싱크된 상태입니다."));
443
+ return;
444
+ }
445
+
446
+ // 현재 생성된 코드 표기
447
+ console.table(codes, ["type", "title"]);
448
+ console.log(codes[0]);
449
+ }
450
+
451
+ /**
452
+ * 마이그레이션을 수행합니다.
453
+ *
454
+ * runAction이 인자로 들어온 타겟들에 대해 주어진 동작(apply/rollback)을 수행한다면,
455
+ * 이 함수는 생성자로 들어온 connection(knex)들에 대해 마이그레이션을 수행합니다.
456
+ *
457
+ * CLI에서 사용됩니다.
458
+ */
459
+ async run(): Promise<void> {
460
+ // pending 마이그레이션 확인
461
+ const [, pendingList] = await this.targets.pending.migrate.list();
462
+ if (pendingList.length > 0) {
463
+ console.log(
464
+ chalk.red("pending 된 마이그레이션이 존재합니다."),
465
+ pendingList.map((pending: any) => pending.file)
466
+ );
467
+
468
+ // pending이 있는 경우 Shadow DB 테스트 진행 여부 컨펌
469
+ const answer = await prompts({
470
+ type: "confirm",
471
+ name: "value",
472
+ message: "Shadow DB 테스트를 진행하시겠습니까?",
473
+ initial: true,
474
+ });
475
+ if (answer.value === false) {
476
+ return;
477
+ }
478
+
479
+ console.time(chalk.blue("Migrator - runShadowTest"));
480
+ await this.runShadowTest();
481
+ console.timeEnd(chalk.blue("Migrator - runShadowTest"));
482
+ await Promise.all(
483
+ this.targets.apply.map(async (applyDb) => {
484
+ const label = chalk.green(
485
+ `APPLIED ${
486
+ applyDb.client.connectionSettings.host
487
+ } ${applyDb.client.database()}`
488
+ );
489
+ console.time(label);
490
+ const [,] = await applyDb.migrate.latest();
491
+ console.timeEnd(label);
492
+ })
493
+ );
494
+ }
495
+
496
+ // Entity-DB간 비교하여 코드 생성 리턴
497
+ const codes = await this.compareMigrations(this.targets.compare!);
498
+ if (codes.length === 0) {
499
+ console.log(chalk.green("\n현재 모두 싱크된 상태입니다."));
500
+ return;
501
+ }
502
+
503
+ // 현재 생성된 코드 표기
504
+ console.table(codes, ["type", "title"]);
505
+
506
+ /* DEBUG: 디버깅용 코드
507
+ codes.map((code) => console.log(code.formatted));
508
+ process.exit();
509
+ */
510
+
511
+ // 실제 파일 생성 프롬프트
512
+ const answer = await prompts({
513
+ type: "confirm",
514
+ name: "value",
515
+ message: "마이그레이션 코드를 생성하시겠습니까?",
516
+ initial: false,
517
+ });
518
+ if (answer.value === false) {
519
+ return;
520
+ }
521
+
522
+ // 실제 코드 생성
523
+ const migrationsDir = `${Sonamu.apiRootPath}/src/migrations`;
524
+
525
+ for (const [index, code] of codes.entries()) {
526
+ if (code.formatted) {
527
+ const dateTag = DateTime.local()
528
+ .plus({ seconds: index })
529
+ .toFormat("yyyyMMddHHmmss");
530
+ const filePath = `${migrationsDir}/${dateTag}_${code.title}.ts`;
531
+ await writeFile(filePath, code.formatted!);
532
+ console.log(chalk.green(`MIGRTAION CREATED ${filePath}`));
533
+ }
534
+ }
535
+ }
536
+
537
+ /**
538
+ * 타겟으로 지정된 DB를 롤백합니다.
539
+ *
540
+ * runAction이 인자로 들어온 타겟들에 대해 주어진 동작(apply/rollback)을 수행한다면,
541
+ * 이 함수는 생성자로 들어온 connection(knex)들에 대해 롤백을 수행합니다.
542
+ *
543
+ * CLI에서 사용됩니다.
544
+ */
545
+ async rollback() {
546
+ console.time(chalk.red("rollback:"));
547
+ const rollbackAllResult = await Promise.all(
548
+ this.targets.apply.map(async (db) => {
549
+ await db.migrate.forceFreeMigrationsLock();
550
+ return db.migrate.rollback(undefined, false);
551
+ })
552
+ );
553
+ console.dir({ rollbackAllResult }, { depth: null });
554
+ console.timeEnd(chalk.red("rollback:"));
555
+ }
556
+
557
+ /**
558
+ * 빌드된 마이그레이션 파일을 삭제합니다.
559
+ *
560
+ * CLI에서 사용됩니다.
561
+ *
562
+ * @param force 강제 삭제 여부
563
+ * @returns
564
+ */
565
+ async cleanUpDist(force: boolean = false): Promise<void> {
566
+ async function getFilesUnder(dir: string): Promise<string[]> {
567
+ const migrationPath = path.join(Sonamu.apiRootPath, dir, "migrations");
568
+ if (!(await exists(migrationPath))) {
569
+ await mkdir(migrationPath, {
570
+ recursive: true,
571
+ });
572
+ }
573
+ return (await readdir(migrationPath)).filter(
574
+ (filename) => filename.startsWith(".") === false
575
+ );
576
+ }
577
+
578
+ const files = {
579
+ src: await getFilesUnder("src"),
580
+ dist: await getFilesUnder("dist"),
581
+ };
582
+
583
+ const diffOnSrc = _.differenceBy(
584
+ files.src,
585
+ files.dist,
586
+ (filename) => filename.split(".")[0]
587
+ );
588
+ if (diffOnSrc.length > 0) {
589
+ throw new Error(
590
+ "컴파일 되지 않은 파일이 있습니다.\n" + diffOnSrc.join("\n")
591
+ );
592
+ }
593
+
594
+ const diffOnDist = _.differenceBy(
595
+ files.dist,
596
+ files.src,
597
+ (filename) => filename.split(".")[0]
598
+ );
599
+ if (diffOnDist.length > 0) {
600
+ console.log(chalk.red("원본 ts파일을 찾을 수 없는 js파일이 있습니다."));
601
+ console.log(diffOnDist);
602
+
603
+ if (!force) {
604
+ const answer = await prompts({
605
+ type: "confirm",
606
+ name: "value",
607
+ message: "삭제를 진행하시겠습니까?",
608
+ initial: true,
609
+ });
610
+ if (answer.value === false) {
611
+ return;
612
+ }
613
+ }
614
+
615
+ const filesToRm = diffOnDist.map((filename) => {
616
+ return path.join(Sonamu.apiRootPath, "dist", "migrations", filename);
617
+ });
618
+ for (const filePath of filesToRm) {
619
+ await unlink(filePath);
620
+ }
621
+ console.log(chalk.green(`${filesToRm.length}건 삭제되었습니다!`));
622
+ }
623
+ }
624
+
625
+ /**
626
+ * Shadow DB 테스트를 진행합니다.
627
+ *
628
+ * Sonamu UI에서 사용됩니다.
629
+ *
630
+ * @returns Shadow DB 테스트 결과
631
+ */
632
+ async runShadowTest(): Promise<
633
+ {
634
+ connKey: string;
635
+ batchNo: number;
636
+ applied: string[];
637
+ }[]
638
+ > {
639
+ // ShadowDB 생성 후 테스트 진행
640
+ const tdb = knex(Sonamu.dbConfig.test);
641
+ const tdbConn = Sonamu.dbConfig.test
642
+ .connection as Knex.MySql2ConnectionConfig;
643
+ const shadowDatabase = tdbConn.database + "__migration_shadow";
644
+ const tmpSqlPath = `/tmp/${shadowDatabase}.sql`;
645
+
646
+ // 테스트DB 덤프 후 Database명 치환
647
+ console.log(
648
+ chalk.magenta(`${tdbConn.database}의 데이터 ${tmpSqlPath}로 덤프`)
649
+ );
650
+ execSync(
651
+ `mysqldump -h${tdbConn.host} -P${tdbConn.port ?? 3306} -u${tdbConn.user} -p'${tdbConn.password}' ${tdbConn.database} --single-transaction --no-create-db --triggers > ${tmpSqlPath};`
652
+ );
653
+ execSync(
654
+ `sed -i'' -e 's/\`${tdbConn.database}\`/\`${shadowDatabase}\`/g' ${tmpSqlPath};`
655
+ );
656
+
657
+ // 기존 ShadowDB 리셋
658
+ console.log(chalk.magenta(`${shadowDatabase} 리셋`));
659
+ await tdb.raw(`DROP DATABASE IF EXISTS \`${shadowDatabase}\`;`);
660
+ await tdb.raw(`CREATE DATABASE \`${shadowDatabase}\`;`);
661
+
662
+ // ShadowDB 테이블 + 데이터 생성
663
+ console.log(chalk.magenta(`${shadowDatabase} 데이터베이스 생성`));
664
+ execSync(
665
+ `mysql -h${tdbConn.host} -P${tdbConn.port ?? 3306} -u${tdbConn.user} -p'${tdbConn.password}' ${shadowDatabase} < ${tmpSqlPath};`
666
+ );
667
+
668
+ // shadow db 테스트 진행
669
+ const sdb = knex({
670
+ ...Sonamu.dbConfig.test,
671
+ connection: {
672
+ ...tdbConn,
673
+ database: shadowDatabase,
674
+ password: tdbConn.password,
675
+ },
676
+ });
677
+
678
+ // shadow db 테스트 진행
679
+ try {
680
+ const [batchNo, applied] = await sdb.migrate.latest();
681
+ console.log(chalk.green("Shadow DB 테스트에 성공했습니다!"), {
682
+ batchNo,
683
+ applied,
684
+ });
685
+
686
+ // 생성한 Shadow DB 삭제
687
+ console.log(chalk.magenta(`${shadowDatabase} 삭제`));
688
+ await tdb.raw(`DROP DATABASE IF EXISTS \`${shadowDatabase}\`;`);
689
+
690
+ return [
691
+ {
692
+ connKey: "shadow",
693
+ batchNo,
694
+ applied,
695
+ },
696
+ ];
697
+ } catch (e) {
698
+ console.error(e);
699
+ throw new ServiceUnavailableException("Shadow DB 테스트 진행 중 에러");
700
+ } finally {
701
+ await tdb.destroy();
702
+ }
703
+ }
704
+
705
+ /**
706
+ * 모든 DB를 롤백하고 전체 마이그레이션 파일을 삭제합니다.
707
+ *
708
+ * CLI에서 사용됩니다.
709
+ *
710
+ * @returns
711
+ */
712
+ async resetAll() {
713
+ const answer = await prompts({
714
+ type: "confirm",
715
+ name: "value",
716
+ message: "모든 DB를 롤백하고 전체 마이그레이션 파일을 삭제하시겠습니까?",
717
+ initial: false,
718
+ });
719
+ if (answer.value === false) {
720
+ return;
721
+ }
722
+
723
+ console.time(chalk.red("rollback-all:"));
724
+ const rollbackAllResult = await Promise.all(
725
+ this.targets.apply.map(async (db) => {
726
+ await db.migrate.forceFreeMigrationsLock();
727
+ return db.migrate.rollback(undefined, true);
728
+ })
729
+ );
730
+ console.log({ rollbackAllResult });
731
+ console.timeEnd(chalk.red("rollback-all:"));
732
+
733
+ const migrationsDir = `${Sonamu.apiRootPath}/src/migrations`;
734
+ console.time(chalk.red("delete migration files"));
735
+ execSync(`rm -f ${migrationsDir}/*`);
736
+ execSync(`rm -f ${migrationsDir.replace("/src/", "/dist/")}/*`);
737
+ console.timeEnd(chalk.red("delete migration files"));
738
+ }
739
+
740
+ private async compareMigrations(
741
+ compareDB: Knex
742
+ ): Promise<GenMigrationCode[]> {
743
+ // Entity 순회하여 싱크
744
+ const entityIds = EntityManager.getAllIds();
745
+
746
+ // 조인테이블 포함하여 Entity에서 MigrationSet 추출
747
+ const entitySetsWithJoinTable = entityIds
748
+ .filter((entityId) => EntityManager.get(entityId).props.length > 0)
749
+ .map((entityId) =>
750
+ getMigrationSetFromEntity(EntityManager.get(entityId))
751
+ );
752
+
753
+ // 조인테이블만 추출
754
+ const joinTablesWithDup = entitySetsWithJoinTable
755
+ .map((entitySet) => entitySet.joinTables)
756
+ .flat();
757
+ // 중복 제거 (중복인 경우 indexes를 병합)
758
+ const joinTables = Object.values(
759
+ _.groupBy(joinTablesWithDup, (jt) => jt.table)
760
+ ).map((tables) => {
761
+ if (tables.length === 1) {
762
+ return tables[0];
763
+ }
764
+ return {
765
+ ...tables[0],
766
+ indexes: _.uniqBy(
767
+ tables.flatMap((t) => t.indexes),
768
+ (index) => [index.type, ...index.columns.sort()].join("-")
769
+ ),
770
+ };
771
+ });
772
+
773
+ // 조인테이블 포함하여 MigrationSet 배열
774
+ const entitySets: MigrationSet[] = [
775
+ ...entitySetsWithJoinTable,
776
+ ...joinTables,
777
+ ];
778
+
779
+ const codes: GenMigrationCode[] = (
780
+ await Promise.all(
781
+ entitySets.map(async (entitySet) => {
782
+ const dbSet = await getMigrationSetFromDB(compareDB, entitySet.table);
783
+
784
+ if (dbSet === null) {
785
+ // 기존 테이블 없음, 새로 테이블 생성
786
+ return await generateCreateCode(entitySet);
787
+ } else {
788
+ // 기존 테이블 존재하는 케이스
789
+ return await generateAlterCode(entitySet, dbSet);
790
+ }
791
+ })
792
+ )
793
+ ).flat();
794
+
795
+ // normal 타입이 앞으로, foreign이 뒤로
796
+ codes.sort((codeA, codeB) => {
797
+ if (codeA.type === "foreign" && codeB.type == "normal") {
798
+ return 1;
799
+ } else if (codeA.type === "normal" && codeB.type === "foreign") {
800
+ return -1;
801
+ } else {
802
+ return 0;
803
+ }
804
+ });
805
+
806
+ return codes;
807
+ }
808
+
809
+ /**
810
+ * 마이그레이션 대상 커넥션을 종료합니다.
811
+ *
812
+ * CLI에서 사용됩니다.
813
+ *
814
+ * @returns {Promise<void>} 종료 결과
815
+ */
816
+ async destroy(): Promise<void> {
817
+ await Promise.all(
818
+ this.targets.apply.map((db) => {
819
+ return db.destroy();
820
+ })
821
+ );
822
+ }
823
+ }