sonamu 0.4.14 → 0.5.1

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 (385) 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 +19 -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 +84 -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/bin/build-config.d.ts +9 -0
  31. package/dist/bin/build-config.d.ts.map +1 -0
  32. package/dist/bin/build-config.js +2 -0
  33. package/dist/bin/build-config.js.map +1 -0
  34. package/dist/bin/cli-wrapper.d.ts +2 -0
  35. package/dist/bin/cli-wrapper.d.ts.map +1 -0
  36. package/dist/bin/cli-wrapper.js +1 -38
  37. package/dist/bin/cli-wrapper.js.map +1 -1
  38. package/dist/bin/cli.d.ts +2 -2
  39. package/dist/bin/cli.d.ts.map +1 -0
  40. package/dist/bin/cli.js +1 -903
  41. package/dist/bin/cli.js.map +1 -1
  42. package/dist/database/_batch_update.d.ts +15 -0
  43. package/dist/database/_batch_update.d.ts.map +1 -0
  44. package/dist/database/_batch_update.js +2 -0
  45. package/dist/database/_batch_update.js.map +1 -0
  46. package/dist/database/base-model.d.ts +48 -0
  47. package/dist/database/base-model.d.ts.map +1 -0
  48. package/dist/database/base-model.js +2 -0
  49. package/dist/database/base-model.js.map +1 -0
  50. package/dist/database/code-generator.d.ts +13 -0
  51. package/dist/database/code-generator.d.ts.map +1 -0
  52. package/dist/database/code-generator.js +2 -0
  53. package/dist/database/code-generator.js.map +1 -0
  54. package/dist/database/db.d.ts +40 -0
  55. package/dist/database/db.d.ts.map +1 -0
  56. package/dist/database/db.js +2 -0
  57. package/dist/database/db.js.map +1 -0
  58. package/dist/database/knex-plugins/knex-on-duplicate-update.d.ts +2 -0
  59. package/dist/database/knex-plugins/knex-on-duplicate-update.d.ts.map +1 -0
  60. package/dist/database/knex-plugins/knex-on-duplicate-update.js +2 -0
  61. package/dist/database/knex-plugins/knex-on-duplicate-update.js.map +1 -0
  62. package/dist/database/puri-wrapper.d.ts +34 -0
  63. package/dist/database/puri-wrapper.d.ts.map +1 -0
  64. package/dist/database/puri-wrapper.js +2 -0
  65. package/dist/database/puri-wrapper.js.map +1 -0
  66. package/dist/database/puri.d.ts +83 -0
  67. package/dist/database/puri.d.ts.map +1 -0
  68. package/dist/database/puri.js +2 -0
  69. package/dist/database/puri.js.map +1 -0
  70. package/dist/database/puri.types.d.ts +60 -0
  71. package/dist/database/puri.types.d.ts.map +1 -0
  72. package/dist/database/puri.types.js +2 -0
  73. package/dist/database/puri.types.js.map +1 -0
  74. package/dist/database/transaction-context.d.ts +9 -0
  75. package/dist/database/transaction-context.d.ts.map +1 -0
  76. package/dist/database/transaction-context.js +2 -0
  77. package/dist/database/transaction-context.js.map +1 -0
  78. package/dist/database/upsert-builder.d.ts +34 -0
  79. package/dist/database/upsert-builder.d.ts.map +1 -0
  80. package/dist/database/upsert-builder.js +2 -0
  81. package/dist/database/upsert-builder.js.map +1 -0
  82. package/dist/entity/entity-manager.d.ts +32 -0
  83. package/dist/entity/entity-manager.d.ts.map +1 -0
  84. package/dist/entity/entity-manager.js +2 -0
  85. package/dist/entity/entity-manager.js.map +1 -0
  86. package/dist/entity/entity-utils.d.ts +61 -0
  87. package/dist/entity/entity-utils.d.ts.map +1 -0
  88. package/dist/entity/entity-utils.js +2 -0
  89. package/dist/entity/entity-utils.js.map +1 -0
  90. package/dist/entity/entity.d.ts +62 -0
  91. package/dist/entity/entity.d.ts.map +1 -0
  92. package/dist/entity/entity.js +2 -0
  93. package/dist/entity/entity.js.map +1 -0
  94. package/dist/entity/migrator.d.ts +135 -0
  95. package/dist/entity/migrator.d.ts.map +1 -0
  96. package/dist/entity/migrator.js +2 -0
  97. package/dist/entity/migrator.js.map +1 -0
  98. package/dist/exceptions/error-handler.d.ts +3 -0
  99. package/dist/exceptions/error-handler.d.ts.map +1 -0
  100. package/dist/exceptions/error-handler.js +2 -0
  101. package/dist/exceptions/error-handler.js.map +1 -0
  102. package/dist/exceptions/so-exceptions.d.ts +48 -0
  103. package/dist/exceptions/so-exceptions.d.ts.map +1 -0
  104. package/dist/exceptions/so-exceptions.js +2 -0
  105. package/dist/exceptions/so-exceptions.js.map +1 -0
  106. package/dist/file-storage/driver.d.ts +48 -0
  107. package/dist/file-storage/driver.d.ts.map +1 -0
  108. package/dist/file-storage/driver.js +2 -0
  109. package/dist/file-storage/driver.js.map +1 -0
  110. package/dist/file-storage/file-storage.d.ts +50 -0
  111. package/dist/file-storage/file-storage.d.ts.map +1 -0
  112. package/dist/file-storage/file-storage.js +2 -0
  113. package/dist/file-storage/file-storage.js.map +1 -0
  114. package/dist/index.d.ts +23 -813
  115. package/dist/index.d.ts.map +1 -0
  116. package/dist/index.js +1 -433
  117. package/dist/index.js.map +1 -1
  118. package/dist/migration/code-generation.d.ts +15 -0
  119. package/dist/migration/code-generation.d.ts.map +1 -0
  120. package/dist/migration/code-generation.js +2 -0
  121. package/dist/migration/code-generation.js.map +1 -0
  122. package/dist/migration/migration-set.d.ts +17 -0
  123. package/dist/migration/migration-set.d.ts.map +1 -0
  124. package/dist/migration/migration-set.js +2 -0
  125. package/dist/migration/migration-set.js.map +1 -0
  126. package/dist/migration/migrator.d.ts +130 -0
  127. package/dist/migration/migrator.d.ts.map +1 -0
  128. package/dist/migration/migrator.js +2 -0
  129. package/dist/migration/migrator.js.map +1 -0
  130. package/dist/migration/types.d.ts +52 -0
  131. package/dist/migration/types.d.ts.map +1 -0
  132. package/dist/migration/types.js +2 -0
  133. package/dist/migration/types.js.map +1 -0
  134. package/dist/stream/index.d.ts +2 -0
  135. package/dist/stream/index.d.ts.map +1 -0
  136. package/dist/stream/index.js +2 -0
  137. package/dist/stream/index.js.map +1 -0
  138. package/dist/stream/sse.d.ts +13 -0
  139. package/dist/stream/sse.d.ts.map +1 -0
  140. package/dist/stream/sse.js +2 -0
  141. package/dist/stream/sse.js.map +1 -0
  142. package/dist/syncer/index.d.ts +2 -0
  143. package/dist/syncer/index.d.ts.map +1 -0
  144. package/dist/syncer/index.js +2 -0
  145. package/dist/syncer/index.js.map +1 -0
  146. package/dist/syncer/syncer.d.ts +127 -0
  147. package/dist/syncer/syncer.d.ts.map +1 -0
  148. package/dist/syncer/syncer.js +2 -0
  149. package/dist/syncer/syncer.js.map +1 -0
  150. package/dist/templates/base-template.d.ts +13 -0
  151. package/dist/templates/base-template.d.ts.map +1 -0
  152. package/dist/templates/base-template.js +2 -0
  153. package/dist/templates/base-template.js.map +1 -0
  154. package/dist/templates/entity.template.d.ts +17 -0
  155. package/dist/templates/entity.template.d.ts.map +1 -0
  156. package/dist/templates/entity.template.js +2 -0
  157. package/dist/templates/entity.template.js.map +1 -0
  158. package/dist/templates/generated.template.d.ts +27 -0
  159. package/dist/templates/generated.template.d.ts.map +1 -0
  160. package/dist/templates/generated.template.js +2 -0
  161. package/dist/templates/generated.template.js.map +1 -0
  162. package/dist/templates/generated_http.template.d.ts +24 -0
  163. package/dist/templates/generated_http.template.d.ts.map +1 -0
  164. package/dist/templates/generated_http.template.js +2 -0
  165. package/dist/templates/generated_http.template.js.map +1 -0
  166. package/dist/templates/generated_sso.template.d.ts +20 -0
  167. package/dist/templates/generated_sso.template.d.ts.map +1 -0
  168. package/dist/templates/generated_sso.template.js +2 -0
  169. package/dist/templates/generated_sso.template.js.map +1 -0
  170. package/dist/templates/index.d.ts +2 -0
  171. package/dist/templates/index.d.ts.map +1 -0
  172. package/dist/templates/index.js +2 -0
  173. package/dist/templates/index.js.map +1 -0
  174. package/dist/templates/init_types.template.d.ts +17 -0
  175. package/dist/templates/init_types.template.d.ts.map +1 -0
  176. package/dist/templates/init_types.template.js +2 -0
  177. package/dist/templates/init_types.template.js.map +1 -0
  178. package/dist/templates/model.template.d.ts +17 -0
  179. package/dist/templates/model.template.d.ts.map +1 -0
  180. package/dist/templates/model.template.js +2 -0
  181. package/dist/templates/model.template.js.map +1 -0
  182. package/dist/templates/model_test.template.d.ts +17 -0
  183. package/dist/templates/model_test.template.d.ts.map +1 -0
  184. package/dist/templates/model_test.template.js +2 -0
  185. package/dist/templates/model_test.template.js.map +1 -0
  186. package/dist/templates/service.template.d.ts +29 -0
  187. package/dist/templates/service.template.d.ts.map +1 -0
  188. package/dist/templates/service.template.js +2 -0
  189. package/dist/templates/service.template.js.map +1 -0
  190. package/dist/templates/view_enums_buttonset.template.d.ts +17 -0
  191. package/dist/templates/view_enums_buttonset.template.d.ts.map +1 -0
  192. package/dist/templates/view_enums_buttonset.template.js +2 -0
  193. package/dist/templates/view_enums_buttonset.template.js.map +1 -0
  194. package/dist/templates/view_enums_dropdown.template.d.ts +18 -0
  195. package/dist/templates/view_enums_dropdown.template.d.ts.map +1 -0
  196. package/dist/templates/view_enums_dropdown.template.js +2 -0
  197. package/dist/templates/view_enums_dropdown.template.js.map +1 -0
  198. package/dist/templates/view_enums_select.template.d.ts +17 -0
  199. package/dist/templates/view_enums_select.template.d.ts.map +1 -0
  200. package/dist/templates/view_enums_select.template.js +2 -0
  201. package/dist/templates/view_enums_select.template.js.map +1 -0
  202. package/dist/templates/view_form.template.d.ts +26 -0
  203. package/dist/templates/view_form.template.d.ts.map +1 -0
  204. package/dist/templates/view_form.template.js +2 -0
  205. package/dist/templates/view_form.template.js.map +1 -0
  206. package/dist/templates/view_id_all_select.template.d.ts +17 -0
  207. package/dist/templates/view_id_all_select.template.d.ts.map +1 -0
  208. package/dist/templates/view_id_all_select.template.js +2 -0
  209. package/dist/templates/view_id_all_select.template.js.map +1 -0
  210. package/dist/templates/view_id_async_select.template.d.ts +17 -0
  211. package/dist/templates/view_id_async_select.template.d.ts.map +1 -0
  212. package/dist/templates/view_id_async_select.template.js +2 -0
  213. package/dist/templates/view_id_async_select.template.js.map +1 -0
  214. package/dist/templates/view_list.template.d.ts +38 -0
  215. package/dist/templates/view_list.template.d.ts.map +1 -0
  216. package/dist/templates/view_list.template.js +2 -0
  217. package/dist/templates/view_list.template.js.map +1 -0
  218. package/dist/templates/view_list_columns.template.d.ts +17 -0
  219. package/dist/templates/view_list_columns.template.d.ts.map +1 -0
  220. package/dist/templates/view_list_columns.template.js +2 -0
  221. package/dist/templates/view_list_columns.template.js.map +1 -0
  222. package/dist/templates/view_search_input.template.d.ts +17 -0
  223. package/dist/templates/view_search_input.template.d.ts.map +1 -0
  224. package/dist/templates/view_search_input.template.js +2 -0
  225. package/dist/templates/view_search_input.template.js.map +1 -0
  226. package/dist/testing/_relation-graph.d.ts +7 -0
  227. package/dist/testing/_relation-graph.d.ts.map +1 -0
  228. package/dist/testing/_relation-graph.js +2 -0
  229. package/dist/testing/_relation-graph.js.map +1 -0
  230. package/dist/testing/fixture-manager.d.ts +35 -0
  231. package/dist/testing/fixture-manager.d.ts.map +1 -0
  232. package/dist/testing/fixture-manager.js +2 -0
  233. package/dist/testing/fixture-manager.js.map +1 -0
  234. package/dist/types/types.d.ts +611 -0
  235. package/dist/types/types.d.ts.map +1 -0
  236. package/dist/types/types.js +2 -0
  237. package/dist/types/types.js.map +1 -0
  238. package/dist/typings/knex.d.js +2 -0
  239. package/dist/typings/knex.d.js.map +1 -0
  240. package/dist/utils/async-utils.d.ts +25 -0
  241. package/dist/utils/async-utils.d.ts.map +1 -0
  242. package/dist/utils/async-utils.js +2 -0
  243. package/dist/utils/async-utils.js.map +1 -0
  244. package/dist/utils/controller.d.ts +9 -0
  245. package/dist/utils/controller.d.ts.map +1 -0
  246. package/dist/utils/controller.js +2 -0
  247. package/dist/utils/controller.js.map +1 -0
  248. package/dist/utils/fs-utils.d.ts +9 -0
  249. package/dist/utils/fs-utils.d.ts.map +1 -0
  250. package/dist/utils/fs-utils.js +2 -0
  251. package/dist/utils/fs-utils.js.map +1 -0
  252. package/dist/utils/lodash-able.d.ts +2 -0
  253. package/dist/utils/lodash-able.d.ts.map +1 -0
  254. package/dist/utils/lodash-able.js +2 -0
  255. package/dist/utils/lodash-able.js.map +1 -0
  256. package/dist/utils/model.d.ts +17 -0
  257. package/dist/utils/model.d.ts.map +1 -0
  258. package/dist/utils/model.js +2 -0
  259. package/dist/utils/model.js.map +1 -0
  260. package/dist/utils/sql-parser.d.ts +4 -0
  261. package/dist/utils/sql-parser.d.ts.map +1 -0
  262. package/dist/utils/sql-parser.js +2 -0
  263. package/dist/utils/sql-parser.js.map +1 -0
  264. package/dist/utils/utils.d.ts +9 -0
  265. package/dist/utils/utils.d.ts.map +1 -0
  266. package/dist/utils/utils.js +2 -0
  267. package/dist/utils/utils.js.map +1 -0
  268. package/dist/utils/zod-error.d.ts +8 -0
  269. package/dist/utils/zod-error.d.ts.map +1 -0
  270. package/dist/utils/zod-error.js +2 -0
  271. package/dist/utils/zod-error.js.map +1 -0
  272. package/nodemon.json +6 -0
  273. package/package.json +32 -45
  274. package/src/api/base-frame.ts +3 -4
  275. package/src/api/caster.ts +22 -23
  276. package/src/api/code-converters.ts +170 -134
  277. package/src/api/context.ts +15 -3
  278. package/src/api/decorators.ts +144 -20
  279. package/src/api/index.ts +2 -0
  280. package/src/api/sonamu.ts +408 -165
  281. package/src/bin/build-config.ts +10 -0
  282. package/src/bin/cli-wrapper.ts +35 -32
  283. package/src/bin/cli.ts +141 -204
  284. package/src/database/_batch_update.ts +10 -15
  285. package/src/database/base-model.ts +326 -216
  286. package/src/database/db.ts +191 -21
  287. package/src/database/{drivers/knex/plugins → knex-plugins}/knex-on-duplicate-update.ts +1 -1
  288. package/src/database/puri-wrapper.ts +129 -0
  289. package/src/database/puri.ts +808 -0
  290. package/src/database/puri.types.ts +222 -0
  291. package/src/database/transaction-context.ts +18 -0
  292. package/src/database/upsert-builder.ts +32 -35
  293. package/src/entity/entity-manager.ts +7 -15
  294. package/src/entity/entity.ts +9 -31
  295. package/src/entity/migrator-/354/235/264/354/202/254/352/260/224/354/226/264/354/232/224.md +1 -0
  296. package/src/file-storage/driver.ts +131 -0
  297. package/src/file-storage/file-storage.ts +100 -0
  298. package/src/index.ts +15 -11
  299. package/src/migration/code-generation.ts +777 -0
  300. package/src/migration/migration-set.ts +453 -0
  301. package/src/migration/migrator.ts +823 -0
  302. package/src/migration/types.ts +53 -0
  303. package/src/shared/web.shared.ts.txt +33 -2
  304. package/src/stream/index.ts +1 -0
  305. package/src/stream/sse.ts +49 -0
  306. package/src/syncer/syncer.ts +294 -127
  307. package/src/templates/generated.template.ts +13 -1
  308. package/src/templates/generated_http.template.ts +15 -12
  309. package/src/templates/generated_sso.template.ts +50 -2
  310. package/src/templates/model.template.ts +138 -2
  311. package/src/templates/service.template.ts +0 -1
  312. package/src/templates/view_form.template.ts +11 -7
  313. package/src/templates/view_list.template.ts +12 -4
  314. package/src/testing/fixture-manager.ts +229 -174
  315. package/src/types/types.ts +108 -14
  316. package/src/utils/async-utils.ts +64 -0
  317. package/src/utils/fs-utils.ts +17 -0
  318. package/src/utils/model.ts +0 -2
  319. package/src/utils/utils.ts +14 -58
  320. package/src/utils/zod-error.ts +12 -176
  321. package/tsconfig.json +6 -0
  322. package/tsup.config.js +4 -2
  323. package/.pnp.cjs +0 -14363
  324. package/.pnp.loader.mjs +0 -2047
  325. package/.vscode/extensions.json +0 -6
  326. package/.vscode/settings.json +0 -9
  327. package/.yarnrc.yml +0 -5
  328. package/dist/base-model-CEB0H0aO.d.mts +0 -43
  329. package/dist/base-model-CrqDMYhI.d.ts +0 -43
  330. package/dist/bin/cli-wrapper.d.mts +0 -1
  331. package/dist/bin/cli-wrapper.mjs +0 -43
  332. package/dist/bin/cli-wrapper.mjs.map +0 -1
  333. package/dist/bin/cli.d.mts +0 -2
  334. package/dist/bin/cli.mjs +0 -907
  335. package/dist/bin/cli.mjs.map +0 -1
  336. package/dist/chunk-2WAC2GER.js +0 -7625
  337. package/dist/chunk-2WAC2GER.js.map +0 -1
  338. package/dist/chunk-C3IPIF6O.mjs +0 -1581
  339. package/dist/chunk-C3IPIF6O.mjs.map +0 -1
  340. package/dist/chunk-EXHKSVTE.js +0 -280
  341. package/dist/chunk-EXHKSVTE.js.map +0 -1
  342. package/dist/chunk-FCERKIIF.mjs +0 -7623
  343. package/dist/chunk-FCERKIIF.mjs.map +0 -1
  344. package/dist/chunk-HGIBJYOU.mjs +0 -231
  345. package/dist/chunk-HGIBJYOU.mjs.map +0 -1
  346. package/dist/chunk-JKSOJRQA.mjs +0 -280
  347. package/dist/chunk-JKSOJRQA.mjs.map +0 -1
  348. package/dist/chunk-OTKKFP3Y.js +0 -1581
  349. package/dist/chunk-OTKKFP3Y.js.map +0 -1
  350. package/dist/chunk-PTFDTOJU.mjs +0 -19
  351. package/dist/chunk-PTFDTOJU.mjs.map +0 -1
  352. package/dist/chunk-UZ2IY5VE.js +0 -231
  353. package/dist/chunk-UZ2IY5VE.js.map +0 -1
  354. package/dist/database/drivers/knex/base-model.d.mts +0 -16
  355. package/dist/database/drivers/knex/base-model.d.ts +0 -16
  356. package/dist/database/drivers/knex/base-model.js +0 -55
  357. package/dist/database/drivers/knex/base-model.js.map +0 -1
  358. package/dist/database/drivers/knex/base-model.mjs +0 -56
  359. package/dist/database/drivers/knex/base-model.mjs.map +0 -1
  360. package/dist/database/drivers/kysely/base-model.d.mts +0 -22
  361. package/dist/database/drivers/kysely/base-model.d.ts +0 -22
  362. package/dist/database/drivers/kysely/base-model.js +0 -64
  363. package/dist/database/drivers/kysely/base-model.js.map +0 -1
  364. package/dist/database/drivers/kysely/base-model.mjs +0 -65
  365. package/dist/database/drivers/kysely/base-model.mjs.map +0 -1
  366. package/dist/index.d.mts +0 -813
  367. package/dist/index.mjs +0 -435
  368. package/dist/index.mjs.map +0 -1
  369. package/dist/model-aFgomcdc.d.mts +0 -1112
  370. package/dist/model-aFgomcdc.d.ts +0 -1112
  371. package/src/database/base-model.abstract.ts +0 -97
  372. package/src/database/db.abstract.ts +0 -75
  373. package/src/database/drivers/knex/base-model.ts +0 -55
  374. package/src/database/drivers/knex/client.ts +0 -209
  375. package/src/database/drivers/knex/db.ts +0 -232
  376. package/src/database/drivers/knex/generator.ts +0 -659
  377. package/src/database/drivers/kysely/base-model.ts +0 -89
  378. package/src/database/drivers/kysely/client.ts +0 -309
  379. package/src/database/drivers/kysely/db.ts +0 -238
  380. package/src/database/drivers/kysely/generator.ts +0 -714
  381. package/src/database/types.ts +0 -118
  382. package/src/entity/migrator.ts +0 -1400
  383. package/src/smd/smd-manager.ts +0 -139
  384. package/src/smd/smd.ts +0 -571
  385. package/src/templates/kysely_types.template.ts +0 -205
@@ -1,1400 +0,0 @@
1
- import _ from "lodash";
2
- import chalk from "chalk";
3
- import { DateTime } from "luxon";
4
- import fs from "fs-extra";
5
- import equal from "fast-deep-equal";
6
- import inflection from "inflection";
7
- import prompts from "prompts";
8
- import { execSync } from "child_process";
9
- import path from "path";
10
-
11
- import {
12
- GenMigrationCode,
13
- isBelongsToOneRelationProp,
14
- isHasManyRelationProp,
15
- isManyToManyRelationProp,
16
- isOneToOneRelationProp,
17
- isRelationProp,
18
- isVirtualProp,
19
- isStringProp,
20
- KnexColumnType,
21
- MigrationColumn,
22
- MigrationForeign,
23
- MigrationIndex,
24
- MigrationJoinTable,
25
- MigrationSet,
26
- MigrationSetAndJoinTable,
27
- isDecimalProp,
28
- isFloatProp,
29
- isTextProp,
30
- isEnumProp,
31
- isIntegerProp,
32
- isKnexError,
33
- RelationOn,
34
- } from "../types/types";
35
- import { EntityManager } from "./entity-manager";
36
- import { Entity } from "./entity";
37
- import { Sonamu } from "../api";
38
- import { ServiceUnavailableException } from "../exceptions/so-exceptions";
39
- import { DB } from "../database/db";
40
- import { KnexClient } from "../database/drivers/knex/client";
41
- import { KyselyClient } from "../database/drivers/kysely/client";
42
-
43
- type MigratorMode = "dev" | "deploy";
44
- export type MigratorOptions = {
45
- readonly mode: MigratorMode;
46
- };
47
- type MigrationCode = {
48
- name: string;
49
- path: string;
50
- };
51
- type ConnString = `${"mysql2"}://${string}@${string}:${number}/${string}`; // mysql2://account@host:port/database
52
- export type MigrationStatus = {
53
- codes: MigrationCode[];
54
- conns: {
55
- name: string;
56
- connKey: string;
57
- connString: ConnString;
58
- currentVersion: string;
59
- status: string | number;
60
- pending: string[];
61
- }[];
62
- preparedCodes: GenMigrationCode[];
63
- };
64
-
65
- export class Migrator {
66
- readonly mode: MigratorMode;
67
-
68
- targets: {
69
- compare?: KnexClient | KyselyClient;
70
- pending: KnexClient | KyselyClient;
71
- shadow: KnexClient | KyselyClient;
72
- apply: (KnexClient | KyselyClient)[];
73
- };
74
-
75
- constructor(options: MigratorOptions) {
76
- this.mode = options.mode;
77
-
78
- if (this.mode === "dev") {
79
- const devDB = DB.getClient("development_master");
80
- const testDB = DB.getClient("test");
81
- const fixtureLocalDB = DB.getClient("fixture_local");
82
-
83
- const uniqConfigs = DB.getUniqueConfigs([
84
- "development_master",
85
- "test",
86
- "fixture_local",
87
- "fixture_remote",
88
- ]);
89
- const applyDBs = [devDB, testDB, fixtureLocalDB];
90
- if (uniqConfigs.length === 4) {
91
- const fixtureRemoteDB = DB.getClient("fixture_remote");
92
- applyDBs.push(fixtureRemoteDB);
93
- }
94
-
95
- this.targets = {
96
- compare: devDB,
97
- pending: devDB,
98
- shadow: testDB,
99
- apply: applyDBs,
100
- };
101
- } else if (this.mode === "deploy") {
102
- const productionDB = DB.getClient("production_master");
103
- const testDB = DB.getClient("test");
104
-
105
- this.targets = {
106
- pending: productionDB,
107
- shadow: testDB,
108
- apply: [productionDB],
109
- };
110
- } else {
111
- throw new Error(`잘못된 모드 ${this.mode} 입력`);
112
- }
113
- }
114
-
115
- async getMigrationCodes(): Promise<{
116
- normal: MigrationCode[];
117
- onlyTs: MigrationCode[];
118
- onlyJs: MigrationCode[];
119
- }> {
120
- const srcMigrationsDir = `${Sonamu.apiRootPath}/src/migrations`;
121
- const distMigrationsDir = `${Sonamu.apiRootPath}/dist/migrations`;
122
-
123
- if (fs.existsSync(srcMigrationsDir) === false) {
124
- fs.mkdirSync(srcMigrationsDir, {
125
- recursive: true,
126
- });
127
- }
128
- if (fs.existsSync(distMigrationsDir) === false) {
129
- fs.mkdirSync(distMigrationsDir, {
130
- recursive: true,
131
- });
132
- }
133
- const srcMigrations = fs
134
- .readdirSync(srcMigrationsDir)
135
- .filter((f) => f.endsWith(".ts"))
136
- .map((f) => f.split(".")[0]);
137
- const distMigrations = fs
138
- .readdirSync(distMigrationsDir)
139
- .filter((f) => f.endsWith(".js"))
140
- .map((f) => f.split(".")[0]);
141
-
142
- const normal = _.intersection(srcMigrations, distMigrations)
143
- .map((filename) => {
144
- return {
145
- name: filename,
146
- path: path.join(srcMigrationsDir, filename) + ".ts",
147
- };
148
- })
149
- .sort((a, b) => (a > b ? 1 : -1));
150
-
151
- const onlyTs = _.difference(srcMigrations, distMigrations).map(
152
- (filename) => {
153
- return {
154
- name: filename,
155
- path: path.join(srcMigrationsDir, filename) + ".ts",
156
- };
157
- }
158
- );
159
-
160
- const onlyJs = _.difference(distMigrations, srcMigrations).map(
161
- (filename) => {
162
- return {
163
- name: filename,
164
- path: path.join(distMigrationsDir, filename) + ".js",
165
- };
166
- }
167
- );
168
-
169
- return {
170
- normal,
171
- onlyTs,
172
- onlyJs,
173
- };
174
- }
175
-
176
- async getStatus(): Promise<MigrationStatus> {
177
- const { normal, onlyTs, onlyJs } = await this.getMigrationCodes();
178
- if (onlyTs.length > 0) {
179
- console.debug({ onlyTs });
180
- throw new ServiceUnavailableException(
181
- `There are un-compiled TS migration files.\nPlease compile them first.\n\n${onlyTs
182
- .map((f) => f.name)
183
- .join("\n")}`
184
- );
185
- }
186
- if (onlyJs.length > 0) {
187
- console.debug({ onlyJs });
188
- await Promise.all(
189
- onlyJs.map(async (f) => {
190
- execSync(
191
- `rm -f ${f.path.replace("/src/", "/dist/").replace(".ts", ".js")}`
192
- );
193
- })
194
- );
195
- }
196
-
197
- const connKeys = Object.keys(DB.fullConfig).filter(
198
- (key) => key.endsWith("_slave") === false
199
- ) as (keyof typeof DB.fullConfig)[];
200
-
201
- const statuses = await Promise.all(
202
- connKeys.map(async (connKey) => {
203
- const tConn = DB.getClient(connKey);
204
-
205
- const status = await (async () => {
206
- try {
207
- return await tConn.status();
208
- } catch (err) {
209
- console.error(err);
210
- return "error";
211
- }
212
- })();
213
- const pending = await (async () => {
214
- try {
215
- return await tConn.getMigrations();
216
- } catch (err) {
217
- console.error(err);
218
- return [];
219
- }
220
- })();
221
- const currentVersion = await (async () => {
222
- // try {
223
- // return tConn.migrate.currentVersion();
224
- // } catch (err) {
225
- return "error";
226
- // }
227
- })();
228
-
229
- const info = tConn.connectionInfo;
230
-
231
- await tConn.destroy();
232
-
233
- return {
234
- name: connKey.replace("_master", ""),
235
- connKey,
236
- connString: `mysql2://${info.user ?? ""}@${
237
- info.host
238
- }:${info.port}/${info.database}` as ConnString,
239
- currentVersion,
240
- status,
241
- pending,
242
- };
243
- })
244
- );
245
-
246
- const preparedCodes: GenMigrationCode[] = await (async () => {
247
- const status0conn = statuses.find((status) => status.status === 0);
248
- if (status0conn === undefined) {
249
- return [];
250
- }
251
-
252
- const compareDBconn = DB.getClient(status0conn.connKey);
253
- const genCodes = await this.compareMigrations(compareDBconn);
254
-
255
- await compareDBconn.destroy();
256
-
257
- return genCodes;
258
- })();
259
-
260
- return {
261
- conns: statuses,
262
- codes: normal,
263
- preparedCodes,
264
- };
265
- /*
266
- TS/JS 코드 컴파일 상태 확인
267
- 1. 원본 파일 없는 JS파일이 존재하는 경우: 삭제
268
- 2. 컴파일 되지 않은 TS파일이 존재하는 경우: throw 쳐서 데브 서버 오픈 요청
269
-
270
- DB 마이그레이션 상태 확인
271
- 1. 전체 DB설정에 대해서 현재 마이그레이션 상태 확인
272
- - connKey: string
273
- - status: number
274
- - currentVersion: string
275
- - list: { file: string; directory: string }[]
276
-
277
- */
278
- }
279
-
280
- async runAction(
281
- action: "latest" | "rollback",
282
- targets: string[]
283
- ): Promise<
284
- {
285
- connKey: string;
286
- batchNo: number;
287
- applied: string[];
288
- }[]
289
- > {
290
- // get uniq knex configs
291
- const configs = DB.getUniqueConfigs(targets as any);
292
-
293
- // get connections
294
- const conns = await Promise.all(
295
- configs.map(async (config) => ({
296
- connKey: config.connKey,
297
- db: DB.getClient(config.connKey),
298
- }))
299
- );
300
-
301
- // action
302
- // TODO: 마이그레이션 결과 리턴값 정리해야됨(kysely/knex)
303
- const result = await (async () => {
304
- switch (action) {
305
- case "latest":
306
- return Promise.all(
307
- conns.map(async ({ connKey, db }) => {
308
- const [batchNo, applied] = await db.migrate();
309
- return {
310
- connKey,
311
- batchNo,
312
- applied,
313
- };
314
- })
315
- );
316
- case "rollback":
317
- return Promise.all(
318
- conns.map(async ({ connKey, db }) => {
319
- const [batchNo, applied] = await db.rollback();
320
- return {
321
- connKey,
322
- batchNo,
323
- applied,
324
- };
325
- })
326
- );
327
- }
328
- })();
329
-
330
- // destroy
331
- await Promise.all(
332
- conns.map(({ db }) => {
333
- return db.destroy();
334
- })
335
- );
336
-
337
- return result;
338
- }
339
-
340
- async delCodes(codeNames: string[]): Promise<number> {
341
- const { conns } = await this.getStatus();
342
- if (
343
- conns.some((conn) => {
344
- return codeNames.some(
345
- (codeName) => conn.pending.includes(codeName) === false
346
- );
347
- })
348
- ) {
349
- throw new Error(
350
- "You cannot delete a migration file if there is already applied."
351
- );
352
- }
353
-
354
- const delFiles = codeNames
355
- .map((codeName) => [
356
- `${Sonamu.apiRootPath}/src/migrations/${codeName}.ts`,
357
- `${Sonamu.apiRootPath}/dist/migrations/${codeName}.js`,
358
- ])
359
- .flat();
360
-
361
- const res = await Promise.all(
362
- delFiles.map((delFile) => {
363
- if (fs.existsSync(delFile)) {
364
- console.log(chalk.red(`DELETE: ${delFile}`));
365
- fs.unlinkSync(delFile);
366
- return delFiles.includes(".ts") ? 1 : 0;
367
- }
368
- return 0;
369
- })
370
- );
371
- return _.sum(res);
372
- }
373
-
374
- async generatePreparedCodes(): Promise<number> {
375
- const { preparedCodes } = await this.getStatus();
376
- if (preparedCodes.length === 0) {
377
- console.log(chalk.green("\n현재 모두 싱크된 상태입니다."));
378
- return 0;
379
- }
380
-
381
- // 실제 코드 생성
382
- const migrationsDir = `${Sonamu.apiRootPath}/src/migrations`;
383
- preparedCodes
384
- .filter((pcode) => pcode.formatted)
385
- .map((pcode, index) => {
386
- const dateTag = DateTime.local()
387
- .plus({ seconds: index })
388
- .toFormat("yyyyMMddHHmmss");
389
- const filePath = `${migrationsDir}/${dateTag}_${pcode.title}.ts`;
390
- fs.writeFileSync(filePath, pcode.formatted!);
391
- console.log(chalk.green(`MIGRTAION CREATED ${filePath}`));
392
- });
393
-
394
- return preparedCodes.length;
395
- }
396
-
397
- async clearPendingList(): Promise<void> {
398
- const pendingList = await this.targets.pending.getMigrations();
399
- const migrationsDir = `${Sonamu.apiRootPath}/src/migrations`;
400
- const delList = pendingList.map((df) => {
401
- return path.join(migrationsDir, `${df}.ts`);
402
- });
403
- for (let p of delList) {
404
- if (fs.existsSync(p)) {
405
- fs.unlinkSync(p);
406
- }
407
- }
408
- await this.cleanUpDist(true);
409
- }
410
-
411
- async check(): Promise<void> {
412
- const codes = await this.compareMigrations(this.targets.compare!);
413
- if (codes.length === 0) {
414
- console.log(chalk.green("\n현재 모두 싱크된 상태입니다."));
415
- return;
416
- }
417
-
418
- // 현재 생성된 코드 표기
419
- console.table(codes, ["type", "title"]);
420
- console.log(codes[0]);
421
- }
422
-
423
- async run(): Promise<void> {
424
- // pending 마이그레이션 확인
425
- const pendingList = await this.targets.pending.getMigrations();
426
- if (pendingList.length > 0) {
427
- console.log(
428
- chalk.red("pending 된 마이그레이션이 존재합니다."),
429
- pendingList.map((pending: any) => pending.file)
430
- );
431
-
432
- // pending이 있는 경우 Shadow DB 테스트 진행 여부 컨펌
433
- const answer = await prompts({
434
- type: "confirm",
435
- name: "value",
436
- message: "Shadow DB 테스트를 진행하시겠습니까?",
437
- initial: true,
438
- });
439
- if (answer.value === false) {
440
- return;
441
- }
442
-
443
- console.time(chalk.blue("Migrator - runShadowTest"));
444
- await this.runShadowTest();
445
- console.timeEnd(chalk.blue("Migrator - runShadowTest"));
446
- await Promise.all(
447
- this.targets.apply.map(async (applyDb) => {
448
- const info = applyDb.connectionInfo;
449
- const label = chalk.green(`APPLIED ${info.host} ${info.database}`);
450
- console.time(label);
451
- await applyDb.migrate();
452
- console.timeEnd(label);
453
- })
454
- );
455
- }
456
-
457
- // Entity-DB간 비교하여 코드 생성 리턴
458
- const codes = await this.compareMigrations(this.targets.compare!);
459
- if (codes.length === 0) {
460
- console.log(chalk.green("\n현재 모두 싱크된 상태입니다."));
461
- return;
462
- }
463
-
464
- // 현재 생성된 코드 표기
465
- console.table(codes, ["type", "title"]);
466
-
467
- /* DEBUG: 디버깅용 코드
468
- codes.map((code) => console.log(code.formatted));
469
- process.exit();
470
- */
471
-
472
- // 실제 파일 생성 프롬프트
473
- const answer = await prompts({
474
- type: "confirm",
475
- name: "value",
476
- message: "마이그레이션 코드를 생성하시겠습니까?",
477
- initial: false,
478
- });
479
- if (answer.value === false) {
480
- return;
481
- }
482
-
483
- // 실제 코드 생성
484
- const migrationsDir = `${Sonamu.apiRootPath}/src/migrations`;
485
- codes
486
- .filter((code) => code.formatted)
487
- .map((code, index) => {
488
- const dateTag = DateTime.local()
489
- .plus({ seconds: index })
490
- .toFormat("yyyyMMddHHmmss");
491
- const filePath = `${migrationsDir}/${dateTag}_${code.title}.ts`;
492
- fs.writeFileSync(filePath, code.formatted!);
493
- console.log(chalk.green(`MIGRTAION CREATED ${filePath}`));
494
- });
495
- }
496
-
497
- async rollback() {
498
- console.time(chalk.red("rollback:"));
499
- const rollbackAllResult = await Promise.all(
500
- this.targets.apply.map(async (db) => {
501
- // await db.migrate.forceFreeMigrationsLock();
502
- return db.rollback();
503
- })
504
- );
505
- console.dir({ rollbackAllResult }, { depth: null });
506
- console.timeEnd(chalk.red("rollback:"));
507
- }
508
-
509
- async cleanUpDist(force: boolean = false): Promise<void> {
510
- const files = (["src", "dist"] as const).reduce(
511
- (r, which) => {
512
- const migrationPath = path.join(
513
- Sonamu.apiRootPath,
514
- which,
515
- "migrations"
516
- );
517
- if (fs.existsSync(migrationPath) === false) {
518
- fs.mkdirSync(migrationPath, {
519
- recursive: true,
520
- });
521
- }
522
- const files = fs
523
- .readdirSync(migrationPath)
524
- .filter((filename) => filename.startsWith(".") === false);
525
- r[which] = files;
526
- return r;
527
- },
528
- {
529
- src: [] as string[],
530
- dist: [] as string[],
531
- }
532
- );
533
-
534
- const diffOnSrc = _.differenceBy(
535
- files.src,
536
- files.dist,
537
- (filename) => filename.split(".")[0]
538
- );
539
- if (diffOnSrc.length > 0) {
540
- throw new Error(
541
- "컴파일 되지 않은 파일이 있습니다.\n" + diffOnSrc.join("\n")
542
- );
543
- }
544
-
545
- const diffOnDist = _.differenceBy(
546
- files.dist,
547
- files.src,
548
- (filename) => filename.split(".")[0]
549
- );
550
- if (diffOnDist.length > 0) {
551
- console.log(chalk.red("원본 ts파일을 찾을 수 없는 js파일이 있습니다."));
552
- console.log(diffOnDist);
553
-
554
- if (!force) {
555
- const answer = await prompts({
556
- type: "confirm",
557
- name: "value",
558
- message: "삭제를 진행하시겠습니까?",
559
- initial: true,
560
- });
561
- if (answer.value === false) {
562
- return;
563
- }
564
- }
565
-
566
- const filesToRm = diffOnDist.map((filename) => {
567
- return path.join(Sonamu.apiRootPath, "dist", "migrations", filename);
568
- });
569
- filesToRm.map((filePath) => {
570
- fs.unlinkSync(filePath);
571
- });
572
- console.log(chalk.green(`${filesToRm.length}건 삭제되었습니다!`));
573
- }
574
- }
575
-
576
- async runShadowTest(): Promise<
577
- {
578
- connKey: string;
579
- batchNo: number;
580
- applied: string[];
581
- }[]
582
- > {
583
- // ShadowDB 생성 후 테스트 진행
584
- const tdb = DB.getClient("test");
585
- const tdbConn = tdb.connectionInfo;
586
- const shadowDatabase = tdbConn.database + "__migration_shadow";
587
- const tmpSqlPath = `/tmp/${shadowDatabase}.sql`;
588
-
589
- // 테스트DB 덤프 후 Database명 치환
590
- console.log(
591
- chalk.magenta(`${tdbConn.database}의 데이터 ${tmpSqlPath}로 덤프`)
592
- );
593
- execSync(
594
- `mysqldump -h${tdbConn.host} -P${tdbConn.port} -u${tdbConn.user} -p'${tdbConn.password}' ${tdbConn.database} --single-transaction --no-create-db --triggers > ${tmpSqlPath};`
595
- );
596
- execSync(
597
- `sed -i'' -e 's/\`${tdbConn.database}\`/\`${shadowDatabase}\`/g' ${tmpSqlPath};`
598
- );
599
-
600
- // 기존 ShadowDB 리셋
601
- console.log(chalk.magenta(`${shadowDatabase} 리셋`));
602
- await tdb.raw(`DROP DATABASE IF EXISTS \`${shadowDatabase}\`;`);
603
- await tdb.raw(`CREATE DATABASE \`${shadowDatabase}\`;`);
604
-
605
- // ShadowDB 테이블 + 데이터 생성
606
- console.log(chalk.magenta(`${shadowDatabase} 데이터베이스 생성`));
607
- execSync(
608
- `mysql -h${tdbConn.host} -P${tdbConn.port} -u${tdbConn.user} -p'${tdbConn.password}' ${shadowDatabase} < ${tmpSqlPath};`
609
- );
610
-
611
- // shadow db 테스트 진행
612
- try {
613
- await tdb.raw(`USE \`${shadowDatabase}\`;`);
614
- const [batchNo, applied] = await tdb.migrate();
615
- console.log(chalk.green("Shadow DB 테스트에 성공했습니다!"), {
616
- batchNo,
617
- applied,
618
- });
619
-
620
- // 생성한 Shadow DB 삭제
621
- console.log(chalk.magenta(`${shadowDatabase} 삭제`));
622
- await tdb.raw(`DROP DATABASE IF EXISTS \`${shadowDatabase}\`;`);
623
-
624
- return [
625
- {
626
- connKey: "shadow",
627
- batchNo,
628
- applied,
629
- },
630
- ];
631
- } catch (e) {
632
- console.error(e);
633
- throw new ServiceUnavailableException("Shadow DB 테스트 진행 중 에러");
634
- } finally {
635
- await tdb.destroy();
636
- }
637
- }
638
-
639
- async resetAll() {
640
- const answer = await prompts({
641
- type: "confirm",
642
- name: "value",
643
- message: "모든 DB를 롤백하고 전체 마이그레이션 파일을 삭제하시겠습니까?",
644
- initial: false,
645
- });
646
- if (answer.value === false) {
647
- return;
648
- }
649
-
650
- console.time(chalk.red("rollback-all:"));
651
- const rollbackAllResult = await Promise.all(
652
- this.targets.apply.map(async (db) => {
653
- // await db.migrate.forceFreeMigrationsLock();
654
- return db.rollbackAll();
655
- })
656
- );
657
- console.log({ rollbackAllResult });
658
- console.timeEnd(chalk.red("rollback-all:"));
659
-
660
- const migrationsDir = `${Sonamu.apiRootPath}/src/migrations`;
661
- console.time(chalk.red("delete migration files"));
662
- execSync(`rm -f ${migrationsDir}/*`);
663
- execSync(`rm -f ${migrationsDir.replace("/src/", "/dist/")}/*`);
664
- console.timeEnd(chalk.red("delete migration files"));
665
- }
666
-
667
- async compareMigrations(
668
- compareDB: KnexClient | KyselyClient
669
- ): Promise<GenMigrationCode[]> {
670
- // Entity 순회하여 싱크
671
- const entityIds = EntityManager.getAllIds();
672
-
673
- // 조인테이블 포함하여 Entity에서 MigrationSet 추출
674
- const entitySetsWithJoinTable = entityIds
675
- .filter((entityId) => {
676
- const entity = EntityManager.get(entityId);
677
- return entity.props.length > 0;
678
- })
679
- .map((entityId) => {
680
- const entity = EntityManager.get(entityId);
681
- return this.getMigrationSetFromEntity(entity);
682
- });
683
-
684
- // 조인테이블만 추출
685
- const joinTablesWithDup = entitySetsWithJoinTable
686
- .map((entitySet) => entitySet.joinTables)
687
- .flat();
688
- // 중복 제거 (중복인 경우 indexes를 병합)
689
- const joinTables = Object.values(
690
- _.groupBy(joinTablesWithDup, (jt) => jt.table)
691
- ).map((tables) => {
692
- if (tables.length === 1) {
693
- return tables[0];
694
- }
695
- return {
696
- ...tables[0],
697
- indexes: _.uniqBy(
698
- tables.flatMap((t) => t.indexes),
699
- (index) => [index.type, ...index.columns.sort()].join("-")
700
- ),
701
- };
702
- });
703
-
704
- // 조인테이블 포함하여 MigrationSet 배열
705
- const entitySets: MigrationSet[] = [
706
- ...entitySetsWithJoinTable,
707
- ...joinTables,
708
- ];
709
-
710
- const codes: GenMigrationCode[] = (
711
- await Promise.all(
712
- entitySets.map(async (entitySet) => {
713
- const dbSet = await this.getMigrationSetFromDB(
714
- compareDB,
715
- entitySet.table
716
- );
717
- if (dbSet === null) {
718
- // 기존 테이블 없음, 새로 테이블 생성
719
- return [
720
- await DB.generator.generateCreateCode_ColumnAndIndexes(
721
- entitySet.table,
722
- entitySet.columns,
723
- entitySet.indexes
724
- ),
725
- ...(await DB.generator.generateCreateCode_Foreign(
726
- entitySet.table,
727
- entitySet.foreigns
728
- )),
729
- ];
730
- }
731
-
732
- // 기존 테이블 존재하는 케이스
733
- const alterCodes: (GenMigrationCode | GenMigrationCode[] | null)[] =
734
- await Promise.all(
735
- (["columnsAndIndexes", "foreigns"] as const).map((key) => {
736
- // 배열 원소의 순서가 달라서 불일치가 발생하는걸 방지하기 위해 각 항목별로 정렬 처리 후 비교
737
- if (key === "columnsAndIndexes") {
738
- const replaceColumnDefaultTo = (col: MigrationColumn) => {
739
- // float인 경우 기본값을 0으로 지정하는 경우 "0.00"으로 변환되는 케이스 대응
740
- if (
741
- col.type === "float" &&
742
- col.defaultTo &&
743
- String(col.defaultTo).includes('"') === false
744
- ) {
745
- col.defaultTo = `"${Number(col.defaultTo).toFixed(
746
- col.scale ?? 2
747
- )}"`;
748
- }
749
- // string인 경우 기본값이 빈 스트링인 경우 대응
750
- if (col.type === "string" && col.defaultTo === "") {
751
- col.defaultTo = '""';
752
- }
753
- return col;
754
- };
755
- const entityColumns = _.sortBy(
756
- entitySet.columns,
757
- (a) => a.name
758
- ).map(replaceColumnDefaultTo);
759
- const dbColumns = _.sortBy(dbSet.columns, (a) => a.name).map(
760
- replaceColumnDefaultTo
761
- );
762
-
763
- /* 디버깅용 코드, 특정 컬럼에서 불일치 발생할 때 확인
764
- const entityColumn = entitySet.columns.find(
765
- (col) => col.name === "price_krw"
766
- );
767
- const dbColumn = dbSet.columns.find(
768
- (col) => col.name === "price_krw"
769
- );
770
- console.debug({ entityColumn, dbColumn });
771
- */
772
-
773
- const entityIndexes = _.sortBy(entitySet.indexes, (a) =>
774
- [
775
- a.type,
776
- ...a.columns.sort((c1, c2) => (c1 > c2 ? 1 : -1)),
777
- ].join("-")
778
- );
779
- const dbIndexes = _.sortBy(dbSet.indexes, (a) =>
780
- [
781
- a.type,
782
- ...a.columns.sort((c1, c2) => (c1 > c2 ? 1 : -1)),
783
- ].join("-")
784
- );
785
-
786
- const isEqualColumns = equal(entityColumns, dbColumns);
787
- const isEqualIndexes = equal(entityIndexes, dbIndexes);
788
- if (isEqualColumns && isEqualIndexes) {
789
- return null;
790
- } else {
791
- // this.showMigrationSet("Entity", entitySet);
792
- // this.showMigrationSet("DB", dbSet);
793
- return DB.generator.generateAlterCode_ColumnAndIndexes(
794
- entitySet.table,
795
- entityColumns,
796
- entityIndexes,
797
- dbColumns,
798
- dbIndexes
799
- );
800
- }
801
- } else {
802
- const replaceNoActionOnMySQL = (f: MigrationForeign) => {
803
- // MySQL에서 RESTRICT와 NO ACTION은 동일함
804
- const { onDelete, onUpdate } = f;
805
- return {
806
- ...f,
807
- onUpdate:
808
- onUpdate === "RESTRICT" ? "NO ACTION" : onUpdate,
809
- onDelete:
810
- onDelete === "RESTRICT" ? "NO ACTION" : onDelete,
811
- };
812
- };
813
-
814
- const entityForeigns = _.sortBy(entitySet.foreigns, (a) =>
815
- [a.to, ...a.columns].join("-")
816
- ).map((f) => replaceNoActionOnMySQL(f));
817
- const dbForeigns = _.sortBy(dbSet.foreigns, (a) =>
818
- [a.to, ...a.columns].join("-")
819
- ).map((f) => replaceNoActionOnMySQL(f));
820
-
821
- if (equal(entityForeigns, dbForeigns) === false) {
822
- // console.dir(
823
- // {
824
- // debugOn: "foreign",
825
- // table: entitySet.table,
826
- // entityForeigns,
827
- // dbForeigns,
828
- // },
829
- // { depth: null }
830
- // );
831
- return DB.generator.generateAlterCode_Foreigns(
832
- entitySet.table,
833
- entityForeigns,
834
- dbForeigns
835
- );
836
- }
837
- }
838
- return null;
839
- })
840
- );
841
- if (alterCodes.every((alterCode) => alterCode === null)) {
842
- return null;
843
- } else {
844
- return alterCodes.filter((alterCode) => alterCode !== null).flat();
845
- }
846
- })
847
- )
848
- )
849
- .flat()
850
- .filter((code) => code !== null) as GenMigrationCode[];
851
-
852
- /*
853
- normal 타입이 앞으로, foreign 이 뒤로
854
- */
855
- codes.sort((codeA, codeB) => {
856
- if (codeA.type === "foreign" && codeB.type == "normal") {
857
- return 1;
858
- } else if (codeA.type === "normal" && codeB.type === "foreign") {
859
- return -1;
860
- } else {
861
- return 0;
862
- }
863
- });
864
-
865
- return codes;
866
- }
867
-
868
- /*
869
- 기존 테이블 정보 읽어서 MigrationSet 형식으로 리턴
870
- */
871
- async getMigrationSetFromDB(
872
- compareDB: KnexClient | KyselyClient,
873
- table: string
874
- ): Promise<MigrationSet | null> {
875
- let dbColumns: DBColumn[], dbIndexes: DBIndex[], dbForeigns: DBForeign[];
876
- try {
877
- [dbColumns, dbIndexes, dbForeigns] = await this.readTable(
878
- compareDB,
879
- table
880
- );
881
- } catch (e: unknown) {
882
- if (isKnexError(e) && e.code === "ER_NO_SUCH_TABLE") {
883
- return null;
884
- }
885
- console.error(e);
886
- return null;
887
- }
888
-
889
- const columns: MigrationColumn[] = dbColumns.map((dbColumn) => {
890
- const dbColType = this.resolveDBColType(dbColumn.Type, dbColumn.Field);
891
- return {
892
- name: dbColumn.Field,
893
- nullable: dbColumn.Null !== "NO",
894
- ...dbColType,
895
- ...(() => {
896
- if (dbColumn.Default !== null) {
897
- return {
898
- defaultTo: dbColumn.Default,
899
- };
900
- }
901
- return {};
902
- })(),
903
- };
904
- });
905
-
906
- const dbIndexesGroup = _.groupBy(
907
- dbIndexes.filter(
908
- (dbIndex) =>
909
- dbIndex.Key_name !== "PRIMARY" &&
910
- !dbForeigns.find(
911
- (dbForeign) => dbForeign.keyName === dbIndex.Key_name
912
- )
913
- ),
914
- (dbIndex) => dbIndex.Key_name
915
- );
916
-
917
- // indexes 처리
918
- const indexes: MigrationIndex[] = Object.keys(dbIndexesGroup).map(
919
- (keyName) => {
920
- const currentIndexes = dbIndexesGroup[keyName];
921
- return {
922
- type: currentIndexes[0].Non_unique === 1 ? "index" : "unique",
923
- columns: currentIndexes.map(
924
- (currentIndex) => currentIndex.Column_name
925
- ),
926
- };
927
- }
928
- );
929
- // console.log(table);
930
- // console.table(dbIndexes);
931
- // console.table(dbForeigns);
932
-
933
- // foreigns 처리
934
- const foreigns: MigrationForeign[] = dbForeigns.map((dbForeign) => {
935
- return {
936
- columns: [dbForeign.from],
937
- to: `${dbForeign.referencesTable}.${dbForeign.referencesField}`,
938
- onUpdate: dbForeign.onUpdate as RelationOn,
939
- onDelete: dbForeign.onDelete as RelationOn,
940
- };
941
- });
942
-
943
- return {
944
- table,
945
- columns,
946
- indexes,
947
- foreigns,
948
- };
949
- }
950
-
951
- resolveDBColType(
952
- colType: string,
953
- colField: string
954
- ): Pick<
955
- MigrationColumn,
956
- "type" | "unsigned" | "length" | "precision" | "scale"
957
- > {
958
- let [rawType, unsigned] = colType.split(" ");
959
- const matched = rawType.match(/\(([0-9]+)\)/);
960
- let length;
961
- if (matched !== null && matched[1]) {
962
- rawType = rawType.replace(/\(([0-9]+)\)/, "");
963
- length = parseInt(matched[1]);
964
- }
965
-
966
- if (rawType === "char" && colField === "uuid") {
967
- return {
968
- type: "uuid",
969
- };
970
- }
971
-
972
- switch (rawType) {
973
- case "int":
974
- return {
975
- type: "integer",
976
- unsigned: unsigned === "unsigned",
977
- };
978
- case "varchar":
979
- // case "char":
980
- return {
981
- type: "string",
982
- ...(length !== undefined && {
983
- length,
984
- }),
985
- };
986
- case "text":
987
- case "mediumtext":
988
- case "longtext":
989
- case "timestamp":
990
- case "json":
991
- case "date":
992
- case "time":
993
- return {
994
- type: rawType,
995
- };
996
- case "datetime":
997
- return {
998
- type: "datetime",
999
- };
1000
- case "tinyint":
1001
- return {
1002
- type: "boolean",
1003
- };
1004
- default:
1005
- // decimal 처리
1006
- if (rawType.startsWith("decimal")) {
1007
- const [, precision, scale] =
1008
- rawType.match(/decimal\(([0-9]+),([0-9]+)\)/) ?? [];
1009
- return {
1010
- type: "decimal",
1011
- precision: parseInt(precision),
1012
- scale: parseInt(scale),
1013
- ...(unsigned === "unsigned" && {
1014
- unsigned: true,
1015
- }),
1016
- };
1017
- } else if (rawType.startsWith("float")) {
1018
- const [, precision, scale] =
1019
- rawType.match(/float\(([0-9]+),([0-9]+)\)/) ?? [];
1020
- return {
1021
- type: "float",
1022
- precision: parseInt(precision),
1023
- scale: parseInt(scale),
1024
- ...(unsigned === "unsigned" && {
1025
- unsigned: true,
1026
- }),
1027
- };
1028
- }
1029
- throw new Error(`resolve 불가능한 DB컬럼 타입 ${colType} ${rawType}`);
1030
- }
1031
- }
1032
-
1033
- /*
1034
- 기존 테이블 읽어서 cols, indexes 반환
1035
- */
1036
- async readTable(
1037
- compareDB: KnexClient | KyselyClient,
1038
- tableName: string
1039
- ): Promise<[DBColumn[], DBIndex[], DBForeign[]]> {
1040
- // 테이블 정보
1041
- try {
1042
- const _cols = await compareDB.raw<DBColumn>(
1043
- `SHOW FIELDS FROM ${tableName}`
1044
- );
1045
-
1046
- const cols = _cols.map((col) => ({
1047
- ...col,
1048
- // Default 값은 숫자나 MySQL Expression이 아닌 경우 ""로 감싸줌
1049
- ...(col.Default !== null && {
1050
- Default:
1051
- col.Default.replace(/[0-9]+/g, "").length > 0 &&
1052
- col.Extra !== "DEFAULT_GENERATED"
1053
- ? `"${col.Default}"`
1054
- : col.Default,
1055
- }),
1056
- }));
1057
-
1058
- const indexes = await compareDB.raw<DBIndex>(
1059
- `SHOW INDEX FROM ${tableName}`
1060
- );
1061
- const [row] = await compareDB.raw<{
1062
- "Create Table": string;
1063
- }>(`SHOW CREATE TABLE ${tableName}`);
1064
- const ddl = row["Create Table"];
1065
- const matched = ddl.match(/CONSTRAINT .+/g);
1066
- const foreignKeys = (matched ?? []).map((line: string) => {
1067
- // 해당 라인을 정규식으로 파싱
1068
- const matched = line.match(
1069
- /CONSTRAINT `(.+)` FOREIGN KEY \(`(.+)`\) REFERENCES `(.+)` \(`(.+)`\)( ON [A-Z ]+)*/
1070
- );
1071
- if (!matched) {
1072
- throw new Error(`인식할 수 없는 FOREIGN KEY CONSTRAINT ${line}`);
1073
- }
1074
- const [, keyName, from, referencesTable, referencesField, onClause] =
1075
- matched;
1076
- // console.debug({ tableName, line, onClause });
1077
-
1078
- const [onUpdateFull, _onUpdate] =
1079
- (onClause ?? "").match(/ON UPDATE ([A-Z ]+)$/) ?? [];
1080
- const onUpdate = _onUpdate ?? "NO ACTION";
1081
-
1082
- const onDelete =
1083
- (onClause ?? "")
1084
- .replace(onUpdateFull ?? "", "")
1085
- .match(/ON DELETE ([A-Z ]+)/)?.[1]
1086
- ?.trim() ?? "NO ACTION";
1087
-
1088
- return {
1089
- keyName,
1090
- from,
1091
- referencesTable,
1092
- referencesField,
1093
- onDelete,
1094
- onUpdate,
1095
- };
1096
- });
1097
- return [cols, indexes, foreignKeys];
1098
- } catch (e) {
1099
- throw e;
1100
- }
1101
- }
1102
-
1103
- /*
1104
- Entity 내용 읽어서 MigrationSetAndJoinTable 추출
1105
- */
1106
- getMigrationSetFromEntity(entity: Entity): MigrationSetAndJoinTable {
1107
- const migrationSet: MigrationSetAndJoinTable = entity.props.reduce(
1108
- (r, prop) => {
1109
- // virtual 필드 제외
1110
- if (isVirtualProp(prop)) {
1111
- return r;
1112
- }
1113
- // HasMany 케이스는 아무 처리도 하지 않음
1114
- if (isHasManyRelationProp(prop)) {
1115
- return r;
1116
- }
1117
-
1118
- // 일반 컬럼
1119
- if (!isRelationProp(prop)) {
1120
- // type resolve
1121
- let type: KnexColumnType;
1122
- if (isTextProp(prop)) {
1123
- type = prop.textType;
1124
- } else if (isEnumProp(prop)) {
1125
- type = "string";
1126
- } else {
1127
- type = prop.type as KnexColumnType;
1128
- }
1129
-
1130
- const column = {
1131
- name: prop.name,
1132
- type,
1133
- ...(isIntegerProp(prop) && { unsigned: prop.unsigned === true }),
1134
- ...((isStringProp(prop) || isEnumProp(prop)) && {
1135
- length: prop.length,
1136
- }),
1137
- nullable: prop.nullable === true,
1138
- ...(() => {
1139
- if (prop.dbDefault !== undefined) {
1140
- return {
1141
- defaultTo: prop.dbDefault,
1142
- };
1143
- }
1144
- return {};
1145
- })(),
1146
- // FIXME: float(N, M) deprecated
1147
- // Decimal, Float 타입의 경우 precision, scale 추가
1148
- ...((isDecimalProp(prop) || isFloatProp(prop)) && {
1149
- precision: prop.precision ?? 8,
1150
- scale: prop.scale ?? 2,
1151
- }),
1152
- };
1153
-
1154
- r.columns.push(column);
1155
- }
1156
-
1157
- if (isManyToManyRelationProp(prop)) {
1158
- // ManyToMany 케이스
1159
- const relMd = EntityManager.get(prop.with);
1160
- const [table1, table2] = prop.joinTable.split("__");
1161
- const join = {
1162
- from: `${entity.table}.id`,
1163
- through: {
1164
- from: `${prop.joinTable}.${inflection.singularize(table1)}_id`,
1165
- to: `${prop.joinTable}.${inflection.singularize(table2)}_id`,
1166
- onUpdate: prop.onUpdate,
1167
- onDelete: prop.onDelete,
1168
- },
1169
- to: `${relMd.table}.id`,
1170
- };
1171
- const through = join.through;
1172
- const fields = [through.from, through.to];
1173
- r.joinTables.push({
1174
- table: through.from.split(".")[0],
1175
- indexes: [
1176
- {
1177
- type: "unique",
1178
- columns: ["uuid"],
1179
- },
1180
- // 조인 테이블에 걸린 인덱스 찾아와서 연결
1181
- ...entity.indexes
1182
- .filter((index) =>
1183
- index.columns.find((col) =>
1184
- col.includes(prop.joinTable + ".")
1185
- )
1186
- )
1187
- .map((index) => ({
1188
- ...index,
1189
- columns: index.columns.map((col) =>
1190
- col.replace(prop.joinTable + ".", "")
1191
- ),
1192
- })),
1193
- ],
1194
- columns: [
1195
- {
1196
- name: "id",
1197
- type: "integer",
1198
- nullable: false,
1199
- unsigned: true,
1200
- },
1201
- ...fields.map((field) => {
1202
- return {
1203
- name: field.split(".")[1],
1204
- type: "integer",
1205
- nullable: false,
1206
- unsigned: true,
1207
- } as MigrationColumn;
1208
- }),
1209
- {
1210
- name: "uuid",
1211
- nullable: true,
1212
- type: "uuid",
1213
- },
1214
- ],
1215
- foreigns: fields.map((field) => {
1216
- // 현재 필드가 어떤 테이블에 속하는지 판단
1217
- const col = field.split(".")[1];
1218
- const to = (() => {
1219
- if (
1220
- inflection.singularize(join.to.split(".")[0]) + "_id" ===
1221
- col
1222
- ) {
1223
- return join.to;
1224
- } else {
1225
- return join.from;
1226
- }
1227
- })();
1228
- return {
1229
- columns: [col],
1230
- to,
1231
- onUpdate: through.onUpdate,
1232
- onDelete: through.onDelete,
1233
- };
1234
- }),
1235
- });
1236
- return r;
1237
- } else if (
1238
- isBelongsToOneRelationProp(prop) ||
1239
- (isOneToOneRelationProp(prop) && prop.hasJoinColumn)
1240
- ) {
1241
- // -OneRelation 케이스
1242
- const idColumnName = prop.name + "_id";
1243
- r.columns.push({
1244
- name: idColumnName,
1245
- type: "integer",
1246
- unsigned: true,
1247
- nullable: prop.nullable ?? false,
1248
- });
1249
- if ((prop.useConstraint ?? true) === true) {
1250
- r.foreigns.push({
1251
- columns: [idColumnName],
1252
- to: `${inflection.underscore(inflection.pluralize(prop.with)).toLowerCase()}.id`,
1253
- onUpdate: prop.onUpdate ?? "RESTRICT",
1254
- onDelete: prop.onDelete ?? "RESTRICT",
1255
- });
1256
- }
1257
- }
1258
-
1259
- return r;
1260
- },
1261
- {
1262
- table: entity.table,
1263
- columns: [] as MigrationColumn[],
1264
- indexes: [] as MigrationIndex[],
1265
- foreigns: [] as MigrationForeign[],
1266
- joinTables: [] as MigrationJoinTable[],
1267
- }
1268
- );
1269
-
1270
- // indexes
1271
- migrationSet.indexes = entity.indexes.filter((index) =>
1272
- index.columns.find((col) => col.includes(".") === false)
1273
- );
1274
-
1275
- // uuid
1276
- migrationSet.columns = migrationSet.columns.concat({
1277
- name: "uuid",
1278
- nullable: true,
1279
- type: "uuid",
1280
- } as MigrationColumn);
1281
- migrationSet.indexes = migrationSet.indexes.concat({
1282
- type: "unique",
1283
- columns: ["uuid"],
1284
- } as MigrationIndex);
1285
-
1286
- return migrationSet;
1287
- }
1288
-
1289
- /*
1290
- 마이그레이션 컬럼 배열 비교용 코드
1291
- */
1292
- showMigrationSet(which: "Entity" | "DB", migrationSet: MigrationSet): void {
1293
- const { columns, indexes, foreigns } = migrationSet;
1294
- const styledChalk =
1295
- which === "Entity" ? chalk.bgGreen.black : chalk.bgBlue.black;
1296
- console.log(
1297
- styledChalk(
1298
- `${which} ${migrationSet.table} Columns\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t`
1299
- )
1300
- );
1301
- console.table(
1302
- columns.map((column) => {
1303
- return {
1304
- ..._.pick(column, [
1305
- "name",
1306
- "type",
1307
- "nullable",
1308
- "unsigned",
1309
- "length",
1310
- "defaultTo",
1311
- "precision",
1312
- "scale",
1313
- ]),
1314
- };
1315
- }),
1316
- [
1317
- "name",
1318
- "type",
1319
- "nullable",
1320
- "unsigned",
1321
- "length",
1322
- "defaultTo",
1323
- "precision",
1324
- "scale",
1325
- ]
1326
- );
1327
-
1328
- if (indexes.length > 0) {
1329
- console.log(
1330
- styledChalk(
1331
- `${which} ${migrationSet.table} Indexes\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t`
1332
- )
1333
- );
1334
- console.table(
1335
- indexes.map((index) => {
1336
- return {
1337
- ..._.pick(index, ["type", "columns", "name"]),
1338
- };
1339
- })
1340
- );
1341
- }
1342
-
1343
- if (foreigns.length > 0) {
1344
- console.log(
1345
- chalk.bgMagenta.black(
1346
- `${which} ${migrationSet.table} Foreigns\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t`
1347
- )
1348
- );
1349
- console.table(
1350
- foreigns.map((foreign) => {
1351
- return {
1352
- ..._.pick(foreign, ["columns", "to", "onUpdate", "onDelete"]),
1353
- };
1354
- })
1355
- );
1356
- }
1357
- }
1358
-
1359
- async destroy(): Promise<void> {
1360
- await Promise.all(
1361
- this.targets.apply.map((db) => {
1362
- return db.destroy();
1363
- })
1364
- );
1365
- }
1366
- }
1367
-
1368
- type DBColumn = {
1369
- Field: string;
1370
- Type: string;
1371
- Null: string;
1372
- Key: string;
1373
- Default: string | null;
1374
- Extra: string;
1375
- };
1376
- type DBIndex = {
1377
- Table: string;
1378
- Non_unique: number;
1379
- Key_name: string;
1380
- Seq_in_index: number;
1381
- Column_name: string;
1382
- Collation: string | null;
1383
- Cardinality: number | null;
1384
- Sub_part: number | null;
1385
- Packed: string | null;
1386
- Null: string;
1387
- Index_type: string;
1388
- Comment: string;
1389
- Index_comment: string;
1390
- Visible: string;
1391
- Expression: string | null;
1392
- };
1393
- type DBForeign = {
1394
- keyName: string;
1395
- from: string;
1396
- referencesTable: string;
1397
- referencesField: string;
1398
- onDelete: string;
1399
- onUpdate: string;
1400
- };