sonamu 0.3.1 → 0.4.2

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 (83) hide show
  1. package/.pnp.cjs +11 -0
  2. package/dist/base-model-BzMJ2E_I.d.mts +43 -0
  3. package/dist/base-model-CWRKUX49.d.ts +43 -0
  4. package/dist/bin/cli.js +118 -89
  5. package/dist/bin/cli.js.map +1 -1
  6. package/dist/bin/cli.mjs +74 -45
  7. package/dist/bin/cli.mjs.map +1 -1
  8. package/dist/chunk-6HSW7OS3.js +1567 -0
  9. package/dist/chunk-6HSW7OS3.js.map +1 -0
  10. package/dist/chunk-FLPD24HS.mjs +231 -0
  11. package/dist/chunk-FLPD24HS.mjs.map +1 -0
  12. package/dist/{chunk-MPXE4IHO.mjs → chunk-PP2PSSAG.mjs} +5284 -5617
  13. package/dist/chunk-PP2PSSAG.mjs.map +1 -0
  14. package/dist/chunk-QK5XXJUX.mjs +280 -0
  15. package/dist/chunk-QK5XXJUX.mjs.map +1 -0
  16. package/dist/chunk-S6FYTR3V.mjs +1567 -0
  17. package/dist/chunk-S6FYTR3V.mjs.map +1 -0
  18. package/dist/chunk-U636LQJJ.js +231 -0
  19. package/dist/chunk-U636LQJJ.js.map +1 -0
  20. package/dist/chunk-W7KDVJLQ.js +280 -0
  21. package/dist/chunk-W7KDVJLQ.js.map +1 -0
  22. package/dist/{chunk-YXILRRDT.js → chunk-XT6LHCX5.js} +5252 -5585
  23. package/dist/chunk-XT6LHCX5.js.map +1 -0
  24. package/dist/database/drivers/knex/base-model.d.mts +16 -0
  25. package/dist/database/drivers/knex/base-model.d.ts +16 -0
  26. package/dist/database/drivers/knex/base-model.js +55 -0
  27. package/dist/database/drivers/knex/base-model.js.map +1 -0
  28. package/dist/database/drivers/knex/base-model.mjs +56 -0
  29. package/dist/database/drivers/knex/base-model.mjs.map +1 -0
  30. package/dist/database/drivers/kysely/base-model.d.mts +22 -0
  31. package/dist/database/drivers/kysely/base-model.d.ts +22 -0
  32. package/dist/database/drivers/kysely/base-model.js +64 -0
  33. package/dist/database/drivers/kysely/base-model.js.map +1 -0
  34. package/dist/database/drivers/kysely/base-model.mjs +65 -0
  35. package/dist/database/drivers/kysely/base-model.mjs.map +1 -0
  36. package/dist/index.d.mts +222 -928
  37. package/dist/index.d.ts +222 -928
  38. package/dist/index.js +13 -26
  39. package/dist/index.js.map +1 -1
  40. package/dist/index.mjs +18 -31
  41. package/dist/index.mjs.map +1 -1
  42. package/dist/model-CAH_4oQh.d.mts +1042 -0
  43. package/dist/model-CAH_4oQh.d.ts +1042 -0
  44. package/import-to-require.js +27 -0
  45. package/package.json +24 -3
  46. package/src/api/caster.ts +6 -0
  47. package/src/api/code-converters.ts +3 -1
  48. package/src/api/sonamu.ts +41 -22
  49. package/src/bin/cli.ts +79 -46
  50. package/src/database/_batch_update.ts +16 -11
  51. package/src/database/base-model.abstract.ts +97 -0
  52. package/src/database/base-model.ts +214 -280
  53. package/src/database/code-generator.ts +72 -0
  54. package/src/database/db.abstract.ts +75 -0
  55. package/src/database/db.ts +21 -82
  56. package/src/database/drivers/knex/base-model.ts +55 -0
  57. package/src/database/drivers/knex/client.ts +209 -0
  58. package/src/database/drivers/knex/db.ts +227 -0
  59. package/src/database/drivers/knex/generator.ts +659 -0
  60. package/src/database/drivers/kysely/base-model.ts +89 -0
  61. package/src/database/drivers/kysely/client.ts +309 -0
  62. package/src/database/drivers/kysely/db.ts +238 -0
  63. package/src/database/drivers/kysely/generator.ts +714 -0
  64. package/src/database/types.ts +117 -0
  65. package/src/database/upsert-builder.ts +31 -18
  66. package/src/entity/entity-utils.ts +1 -1
  67. package/src/entity/migrator.ts +148 -711
  68. package/src/index.ts +1 -1
  69. package/src/syncer/syncer.ts +69 -27
  70. package/src/templates/generated_http.template.ts +14 -0
  71. package/src/templates/kysely_types.template.ts +205 -0
  72. package/src/templates/model.template.ts +2 -139
  73. package/src/templates/service.template.ts +3 -1
  74. package/src/testing/_relation-graph.ts +111 -0
  75. package/src/testing/fixture-manager.ts +216 -332
  76. package/src/types/types.ts +56 -6
  77. package/src/utils/utils.ts +56 -4
  78. package/src/utils/zod-error.ts +189 -0
  79. package/tsconfig.json +2 -2
  80. package/tsup.config.js +11 -10
  81. package/dist/chunk-MPXE4IHO.mjs.map +0 -1
  82. package/dist/chunk-YXILRRDT.js.map +0 -1
  83. /package/src/database/{knex-plugins → drivers/knex/plugins}/knex-on-duplicate-update.ts +0 -0
@@ -1,6 +1,4 @@
1
1
  import _ from "lodash";
2
- import knex, { Knex } from "knex";
3
- import prettier from "prettier";
4
2
  import chalk from "chalk";
5
3
  import { DateTime } from "luxon";
6
4
  import fs from "fs-extra";
@@ -38,6 +36,9 @@ import { EntityManager } from "./entity-manager";
38
36
  import { Entity } from "./entity";
39
37
  import { Sonamu } from "../api";
40
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";
41
42
 
42
43
  type MigratorMode = "dev" | "deploy";
43
44
  export type MigratorOptions = {
@@ -65,33 +66,29 @@ export class Migrator {
65
66
  readonly mode: MigratorMode;
66
67
 
67
68
  targets: {
68
- compare?: Knex;
69
- pending: Knex;
70
- shadow: Knex;
71
- apply: Knex[];
69
+ compare?: KnexClient | KyselyClient;
70
+ pending: KnexClient | KyselyClient;
71
+ shadow: KnexClient | KyselyClient;
72
+ apply: (KnexClient | KyselyClient)[];
72
73
  };
73
74
 
74
75
  constructor(options: MigratorOptions) {
75
76
  this.mode = options.mode;
76
- const { dbConfig } = Sonamu;
77
77
 
78
78
  if (this.mode === "dev") {
79
- const devDB = knex(dbConfig.development_master);
80
- const testDB = knex(dbConfig.test);
81
- const fixtureLocalDB = knex(dbConfig.fixture_local);
82
-
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
+ ]);
83
89
  const applyDBs = [devDB, testDB, fixtureLocalDB];
84
- if (
85
- (dbConfig.fixture_local.connection as Knex.MySql2ConnectionConfig)
86
- .host !==
87
- (dbConfig.fixture_remote.connection as Knex.MySql2ConnectionConfig)
88
- .host ||
89
- (dbConfig.fixture_local.connection as Knex.MySql2ConnectionConfig)
90
- .database !==
91
- (dbConfig.fixture_remote.connection as Knex.MySql2ConnectionConfig)
92
- .database
93
- ) {
94
- const fixtureRemoteDB = knex(dbConfig.fixture_remote);
90
+ if (uniqConfigs.length === 4) {
91
+ const fixtureRemoteDB = DB.getClient("fixture_remote");
95
92
  applyDBs.push(fixtureRemoteDB);
96
93
  }
97
94
 
@@ -102,8 +99,8 @@ export class Migrator {
102
99
  apply: applyDBs,
103
100
  };
104
101
  } else if (this.mode === "deploy") {
105
- const productionDB = knex(Sonamu.dbConfig.production_master);
106
- const testDB = knex(Sonamu.dbConfig.test);
102
+ const productionDB = DB.getClient("production_master");
103
+ const testDB = DB.getClient("test");
107
104
 
108
105
  this.targets = {
109
106
  pending: productionDB,
@@ -197,51 +194,48 @@ export class Migrator {
197
194
  );
198
195
  }
199
196
 
200
- const connKeys = Object.keys(Sonamu.dbConfig).filter(
197
+ const connKeys = Object.keys(DB.fullConfig).filter(
201
198
  (key) => key.endsWith("_slave") === false
202
- ) as (keyof typeof Sonamu.dbConfig)[];
199
+ ) as (keyof typeof DB.fullConfig)[];
203
200
 
204
201
  const statuses = await Promise.all(
205
202
  connKeys.map(async (connKey) => {
206
- const knexOptions = Sonamu.dbConfig[connKey];
207
- const tConn = knex(knexOptions);
203
+ const tConn = DB.getClient(connKey);
208
204
 
209
205
  const status = await (async () => {
210
206
  try {
211
- return await tConn.migrate.status();
207
+ return await tConn.status();
212
208
  } catch (err) {
209
+ console.error(err);
213
210
  return "error";
214
211
  }
215
212
  })();
216
213
  const pending = await (async () => {
217
214
  try {
218
- const [, fdList] = await tConn.migrate.list();
219
- return fdList.map((fd: { file: string }) =>
220
- fd.file.replace(".js", "")
221
- );
215
+ return await tConn.getMigrations();
222
216
  } catch (err) {
217
+ console.error(err);
223
218
  return [];
224
219
  }
225
220
  })();
226
221
  const currentVersion = await (async () => {
227
- try {
228
- return tConn.migrate.currentVersion();
229
- } catch (err) {
230
- return "error";
231
- }
222
+ // try {
223
+ // return tConn.migrate.currentVersion();
224
+ // } catch (err) {
225
+ return "error";
226
+ // }
232
227
  })();
233
228
 
234
- const connection =
235
- knexOptions.connection as Knex.MySql2ConnectionConfig;
229
+ const info = tConn.connectionInfo;
236
230
 
237
231
  await tConn.destroy();
238
232
 
239
233
  return {
240
234
  name: connKey.replace("_master", ""),
241
235
  connKey,
242
- connString: `${knexOptions.client}://${connection.user ?? ""}@${
243
- connection.host
244
- }:${connection.port ?? 3306}/${connection.database}` as ConnString,
236
+ connString: `mysql2://${info.user ?? ""}@${
237
+ info.host
238
+ }:${info.port}/${info.database}` as ConnString,
245
239
  currentVersion,
246
240
  status,
247
241
  pending,
@@ -255,7 +249,7 @@ export class Migrator {
255
249
  return [];
256
250
  }
257
251
 
258
- const compareDBconn = knex(Sonamu.dbConfig[status0conn.connKey]);
252
+ const compareDBconn = DB.getClient(status0conn.connKey);
259
253
  const genCodes = await this.compareMigrations(compareDBconn);
260
254
 
261
255
  await compareDBconn.destroy();
@@ -269,17 +263,17 @@ export class Migrator {
269
263
  preparedCodes,
270
264
  };
271
265
  /*
272
- TS/JS 코드 컴파일 상태 확인
273
- 1. 원본 파일 없는 JS파일이 존재하는 경우: 삭제
274
- 2. 컴파일 되지 않은 TS파일이 존재하는 경우: throw 쳐서 데브 서버 오픈 요청
275
-
276
- DB 마이그레이션 상태 확인
277
- 1. 전체 DB설정에 대해서 현재 마이그레이션 상태 확인
278
- - connKey: string
279
- - status: number
280
- - currentVersion: string
281
- - list: { file: string; directory: string }[]
282
-
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
+
283
277
  */
284
278
  }
285
279
 
@@ -294,34 +288,24 @@ export class Migrator {
294
288
  }[]
295
289
  > {
296
290
  // get uniq knex configs
297
- const configs = _.uniqBy(
298
- targets
299
- .map((target) => ({
300
- connKey: target,
301
- options: Sonamu.dbConfig[target as keyof typeof Sonamu.dbConfig],
302
- }))
303
- .filter((c) => c.options !== undefined),
304
- ({ options }) =>
305
- `${(options.connection as Knex.MySql2ConnectionConfig).host}:${
306
- (options.connection as Knex.MySql2ConnectionConfig).port ?? 3306
307
- }/${(options.connection as Knex.MySql2ConnectionConfig).database}`
308
- );
291
+ const configs = DB.getUniqueConfigs(targets as any);
309
292
 
310
293
  // get connections
311
294
  const conns = await Promise.all(
312
295
  configs.map(async (config) => ({
313
296
  connKey: config.connKey,
314
- knex: knex(config.options),
297
+ db: DB.getClient(config.connKey),
315
298
  }))
316
299
  );
317
300
 
318
301
  // action
302
+ // TODO: 마이그레이션 결과 리턴값 정리해야됨(kysely/knex)
319
303
  const result = await (async () => {
320
304
  switch (action) {
321
305
  case "latest":
322
306
  return Promise.all(
323
- conns.map(async ({ connKey, knex }) => {
324
- const [batchNo, applied] = await knex.migrate.latest();
307
+ conns.map(async ({ connKey, db }) => {
308
+ const [batchNo, applied] = await db.migrate();
325
309
  return {
326
310
  connKey,
327
311
  batchNo,
@@ -331,8 +315,8 @@ export class Migrator {
331
315
  );
332
316
  case "rollback":
333
317
  return Promise.all(
334
- conns.map(async ({ connKey, knex }) => {
335
- const [batchNo, applied] = await knex.migrate.rollback();
318
+ conns.map(async ({ connKey, db }) => {
319
+ const [batchNo, applied] = await db.rollback();
336
320
  return {
337
321
  connKey,
338
322
  batchNo,
@@ -345,8 +329,8 @@ export class Migrator {
345
329
 
346
330
  // destroy
347
331
  await Promise.all(
348
- conns.map(({ knex }) => {
349
- return knex.destroy();
332
+ conns.map(({ db }) => {
333
+ return db.destroy();
350
334
  })
351
335
  );
352
336
 
@@ -411,16 +395,10 @@ export class Migrator {
411
395
  }
412
396
 
413
397
  async clearPendingList(): Promise<void> {
414
- const [, pendingList] = (await this.targets.pending.migrate.list()) as [
415
- unknown,
416
- {
417
- file: string;
418
- directory: string;
419
- }[],
420
- ];
398
+ const pendingList = await this.targets.pending.getMigrations();
421
399
  const migrationsDir = `${Sonamu.apiRootPath}/src/migrations`;
422
400
  const delList = pendingList.map((df) => {
423
- return path.join(migrationsDir, df.file).replace(".js", ".ts");
401
+ return path.join(migrationsDir, `${df}.ts`);
424
402
  });
425
403
  for (let p of delList) {
426
404
  if (fs.existsSync(p)) {
@@ -444,7 +422,7 @@ export class Migrator {
444
422
 
445
423
  async run(): Promise<void> {
446
424
  // pending 마이그레이션 확인
447
- const [, pendingList] = await this.targets.pending.migrate.list();
425
+ const pendingList = await this.targets.pending.getMigrations();
448
426
  if (pendingList.length > 0) {
449
427
  console.log(
450
428
  chalk.red("pending 된 마이그레이션이 존재합니다."),
@@ -467,19 +445,16 @@ export class Migrator {
467
445
  console.timeEnd(chalk.blue("Migrator - runShadowTest"));
468
446
  await Promise.all(
469
447
  this.targets.apply.map(async (applyDb) => {
470
- const label = chalk.green(
471
- `APPLIED ${
472
- applyDb.client.connectionSettings.host
473
- } ${applyDb.client.database()}`
474
- );
448
+ const info = applyDb.connectionInfo;
449
+ const label = chalk.green(`APPLIED ${info.host} ${info.database}`);
475
450
  console.time(label);
476
- const [,] = await applyDb.migrate.latest();
451
+ await applyDb.migrate();
477
452
  console.timeEnd(label);
478
453
  })
479
454
  );
480
455
  }
481
456
 
482
- // MD-DB간 비교하여 코드 생성 리턴
457
+ // Entity-DB간 비교하여 코드 생성 리턴
483
458
  const codes = await this.compareMigrations(this.targets.compare!);
484
459
  if (codes.length === 0) {
485
460
  console.log(chalk.green("\n현재 모두 싱크된 상태입니다."));
@@ -523,8 +498,8 @@ export class Migrator {
523
498
  console.time(chalk.red("rollback:"));
524
499
  const rollbackAllResult = await Promise.all(
525
500
  this.targets.apply.map(async (db) => {
526
- await db.migrate.forceFreeMigrationsLock();
527
- return db.migrate.rollback(undefined, false);
501
+ // await db.migrate.forceFreeMigrationsLock();
502
+ return db.rollback();
528
503
  })
529
504
  );
530
505
  console.dir({ rollbackAllResult }, { depth: null });
@@ -606,8 +581,8 @@ export class Migrator {
606
581
  }[]
607
582
  > {
608
583
  // ShadowDB 생성 후 테스트 진행
609
- const tdb = knex(Sonamu.dbConfig.test);
610
- const tdbConn = Sonamu.dbConfig.test.connection as Knex.ConnectionConfig;
584
+ const tdb = DB.getClient("test");
585
+ const tdbConn = tdb.connectionInfo;
611
586
  const shadowDatabase = tdbConn.database + "__migration_shadow";
612
587
  const tmpSqlPath = `/tmp/${shadowDatabase}.sql`;
613
588
 
@@ -616,7 +591,7 @@ export class Migrator {
616
591
  chalk.magenta(`${tdbConn.database}의 데이터 ${tmpSqlPath}로 덤프`)
617
592
  );
618
593
  execSync(
619
- `mysqldump -h${tdbConn.host} -u${tdbConn.user} -p'${tdbConn.password}' ${tdbConn.database} --single-transaction --no-create-db --triggers > ${tmpSqlPath};`
594
+ `mysqldump -h${tdbConn.host} -P${tdbConn.port} -u${tdbConn.user} -p'${tdbConn.password}' ${tdbConn.database} --single-transaction --no-create-db --triggers > ${tmpSqlPath};`
620
595
  );
621
596
  execSync(
622
597
  `sed -i'' -e 's/\`${tdbConn.database}\`/\`${shadowDatabase}\`/g' ${tmpSqlPath};`
@@ -630,24 +605,13 @@ export class Migrator {
630
605
  // ShadowDB 테이블 + 데이터 생성
631
606
  console.log(chalk.magenta(`${shadowDatabase} 데이터베이스 생성`));
632
607
  execSync(
633
- `mysql -h${tdbConn.host} -u${tdbConn.user} -p'${tdbConn.password}' ${shadowDatabase} < ${tmpSqlPath};`
608
+ `mysql -h${tdbConn.host} -P${tdbConn.port} -u${tdbConn.user} -p'${tdbConn.password}' ${shadowDatabase} < ${tmpSqlPath};`
634
609
  );
635
610
 
636
- // tdb 연결 종료
637
- await tdb.destroy();
638
-
639
611
  // shadow db 테스트 진행
640
- const sdb = knex({
641
- ...Sonamu.dbConfig.test,
642
- connection: {
643
- ...tdbConn,
644
- database: shadowDatabase,
645
- password: tdbConn.password,
646
- },
647
- });
648
-
649
612
  try {
650
- const [batchNo, applied] = await sdb.migrate.latest();
613
+ await tdb.raw(`USE \`${shadowDatabase}\`;`);
614
+ const [batchNo, applied] = await tdb.migrate();
651
615
  console.log(chalk.green("Shadow DB 테스트에 성공했습니다!"), {
652
616
  batchNo,
653
617
  applied,
@@ -655,7 +619,7 @@ export class Migrator {
655
619
 
656
620
  // 생성한 Shadow DB 삭제
657
621
  console.log(chalk.magenta(`${shadowDatabase} 삭제`));
658
- await sdb.raw(`DROP DATABASE IF EXISTS \`${shadowDatabase}\`;`);
622
+ await tdb.raw(`DROP DATABASE IF EXISTS \`${shadowDatabase}\`;`);
659
623
 
660
624
  return [
661
625
  {
@@ -668,7 +632,7 @@ export class Migrator {
668
632
  console.error(e);
669
633
  throw new ServiceUnavailableException("Shadow DB 테스트 진행 중 에러");
670
634
  } finally {
671
- await sdb.destroy();
635
+ await tdb.destroy();
672
636
  }
673
637
  }
674
638
 
@@ -686,8 +650,8 @@ export class Migrator {
686
650
  console.time(chalk.red("rollback-all:"));
687
651
  const rollbackAllResult = await Promise.all(
688
652
  this.targets.apply.map(async (db) => {
689
- await db.migrate.forceFreeMigrationsLock();
690
- return db.migrate.rollback(undefined, true);
653
+ // await db.migrate.forceFreeMigrationsLock();
654
+ return db.rollbackAll();
691
655
  })
692
656
  );
693
657
  console.log({ rollbackAllResult });
@@ -700,11 +664,13 @@ export class Migrator {
700
664
  console.timeEnd(chalk.red("delete migration files"));
701
665
  }
702
666
 
703
- async compareMigrations(compareDB: Knex): Promise<GenMigrationCode[]> {
704
- // MD 순회하여 싱크
667
+ async compareMigrations(
668
+ compareDB: KnexClient | KyselyClient
669
+ ): Promise<GenMigrationCode[]> {
670
+ // Entity 순회하여 싱크
705
671
  const entityIds = EntityManager.getAllIds();
706
672
 
707
- // 조인테이블 포함하여 MD에서 MigrationSet 추출
673
+ // 조인테이블 포함하여 Entity에서 MigrationSet 추출
708
674
  const entitySetsWithJoinTable = entityIds
709
675
  .filter((entityId) => {
710
676
  const entity = EntityManager.get(entityId);
@@ -712,16 +678,28 @@ export class Migrator {
712
678
  })
713
679
  .map((entityId) => {
714
680
  const entity = EntityManager.get(entityId);
715
- return this.getMigrationSetFromMD(entity);
681
+ return this.getMigrationSetFromEntity(entity);
716
682
  });
717
683
 
718
684
  // 조인테이블만 추출
719
- const joinTables = _.uniqBy(
720
- entitySetsWithJoinTable.map((entitySet) => entitySet.joinTables).flat(),
721
- (joinTable) => {
722
- return joinTable.table;
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];
723
694
  }
724
- );
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
+ });
725
703
 
726
704
  // 조인테이블 포함하여 MigrationSet 배열
727
705
  const entitySets: MigrationSet[] = [
@@ -729,7 +707,7 @@ export class Migrator {
729
707
  ...joinTables,
730
708
  ];
731
709
 
732
- let codes: GenMigrationCode[] = (
710
+ const codes: GenMigrationCode[] = (
733
711
  await Promise.all(
734
712
  entitySets.map(async (entitySet) => {
735
713
  const dbSet = await this.getMigrationSetFromDB(
@@ -739,12 +717,12 @@ export class Migrator {
739
717
  if (dbSet === null) {
740
718
  // 기존 테이블 없음, 새로 테이블 생성
741
719
  return [
742
- await this.generateCreateCode_ColumnAndIndexes(
720
+ await DB.generator.generateCreateCode_ColumnAndIndexes(
743
721
  entitySet.table,
744
722
  entitySet.columns,
745
723
  entitySet.indexes
746
724
  ),
747
- ...(await this.generateCreateCode_Foreign(
725
+ ...(await DB.generator.generateCreateCode_Foreign(
748
726
  entitySet.table,
749
727
  entitySet.foreigns
750
728
  )),
@@ -810,9 +788,9 @@ export class Migrator {
810
788
  if (isEqualColumns && isEqualIndexes) {
811
789
  return null;
812
790
  } else {
813
- // this.showMigrationSet("MD", entitySet);
791
+ // this.showMigrationSet("Entity", entitySet);
814
792
  // this.showMigrationSet("DB", dbSet);
815
- return this.generateAlterCode_ColumnAndIndexes(
793
+ return DB.generator.generateAlterCode_ColumnAndIndexes(
816
794
  entitySet.table,
817
795
  entityColumns,
818
796
  entityIndexes,
@@ -841,8 +819,16 @@ export class Migrator {
841
819
  ).map((f) => replaceNoActionOnMySQL(f));
842
820
 
843
821
  if (equal(entityForeigns, dbForeigns) === false) {
844
- // console.dir({ entityForeigns, dbForeigns }, { depth: null });
845
- return this.generateAlterCode_Foreigns(
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(
846
832
  entitySet.table,
847
833
  entityForeigns,
848
834
  dbForeigns
@@ -883,7 +869,7 @@ export class Migrator {
883
869
  기존 테이블 정보 읽어서 MigrationSet 형식으로 리턴
884
870
  */
885
871
  async getMigrationSetFromDB(
886
- compareDB: Knex,
872
+ compareDB: KnexClient | KyselyClient,
887
873
  table: string
888
874
  ): Promise<MigrationSet | null> {
889
875
  let dbColumns: DBColumn[], dbIndexes: DBIndex[], dbForeigns: DBForeign[];
@@ -1009,7 +995,7 @@ export class Migrator {
1009
995
  };
1010
996
  case "datetime":
1011
997
  return {
1012
- type: "dateTime",
998
+ type: "datetime",
1013
999
  };
1014
1000
  case "tinyint":
1015
1001
  return {
@@ -1048,14 +1034,15 @@ export class Migrator {
1048
1034
  기존 테이블 읽어서 cols, indexes 반환
1049
1035
  */
1050
1036
  async readTable(
1051
- compareDB: Knex,
1037
+ compareDB: KnexClient | KyselyClient,
1052
1038
  tableName: string
1053
1039
  ): Promise<[DBColumn[], DBIndex[], DBForeign[]]> {
1054
1040
  // 테이블 정보
1055
1041
  try {
1056
- const [_cols] = (await compareDB.raw(
1042
+ const _cols = await compareDB.raw<DBColumn>(
1057
1043
  `SHOW FIELDS FROM ${tableName}`
1058
- )) as [DBColumn[]];
1044
+ );
1045
+
1059
1046
  const cols = _cols.map((col) => ({
1060
1047
  ...col,
1061
1048
  // Default 값은 숫자나 MySQL Expression이 아닌 경우 ""로 감싸줌
@@ -1068,8 +1055,12 @@ export class Migrator {
1068
1055
  }),
1069
1056
  }));
1070
1057
 
1071
- const [indexes] = await compareDB.raw(`SHOW INDEX FROM ${tableName}`);
1072
- const [[row]] = await compareDB.raw(`SHOW CREATE TABLE ${tableName}`);
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}`);
1073
1064
  const ddl = row["Create Table"];
1074
1065
  const matched = ddl.match(/CONSTRAINT .+/g);
1075
1066
  const foreignKeys = (matched ?? []).map((line: string) => {
@@ -1091,7 +1082,8 @@ export class Migrator {
1091
1082
  const onDelete =
1092
1083
  (onClause ?? "")
1093
1084
  .replace(onUpdateFull ?? "", "")
1094
- .match(/ON DELETE ([A-Z ]+) /)?.[1] ?? "NO ACTION";
1085
+ .match(/ON DELETE ([A-Z ]+)/)?.[1]
1086
+ ?.trim() ?? "NO ACTION";
1095
1087
 
1096
1088
  return {
1097
1089
  keyName,
@@ -1109,9 +1101,9 @@ export class Migrator {
1109
1101
  }
1110
1102
 
1111
1103
  /*
1112
- MD 내용 읽어서 MigrationSetAndJoinTable 추출
1104
+ Entity 내용 읽어서 MigrationSetAndJoinTable 추출
1113
1105
  */
1114
- getMigrationSetFromMD(entity: Entity): MigrationSetAndJoinTable {
1106
+ getMigrationSetFromEntity(entity: Entity): MigrationSetAndJoinTable {
1115
1107
  const migrationSet: MigrationSetAndJoinTable = entity.props.reduce(
1116
1108
  (r, prop) => {
1117
1109
  // virtual 필드 제외
@@ -1151,6 +1143,7 @@ export class Migrator {
1151
1143
  }
1152
1144
  return {};
1153
1145
  })(),
1146
+ // FIXME: float(N, M) deprecated
1154
1147
  // Decimal, Float 타입의 경우 precision, scale 추가
1155
1148
  ...((isDecimalProp(prop) || isFloatProp(prop)) && {
1156
1149
  precision: prop.precision ?? 8,
@@ -1220,9 +1213,21 @@ export class Migrator {
1220
1213
  },
1221
1214
  ],
1222
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
+ })();
1223
1228
  return {
1224
- columns: [field.split(".")[1]],
1225
- to: through.to.includes(field) ? join.to : join.from,
1229
+ columns: [col],
1230
+ to,
1226
1231
  onUpdate: through.onUpdate,
1227
1232
  onDelete: through.onDelete,
1228
1233
  };
@@ -1279,197 +1284,13 @@ export class Migrator {
1279
1284
  return migrationSet;
1280
1285
  }
1281
1286
 
1282
- /*
1283
- MigrationColumn[] 읽어서 컬럼 정의하는 구문 생성
1284
- */
1285
- genColumnDefinitions(columns: MigrationColumn[]): string[] {
1286
- return columns.map((column) => {
1287
- const chains: string[] = [];
1288
- if (column.name === "id") {
1289
- return `table.increments().primary();`;
1290
- }
1291
-
1292
- if (column.type === "float" || column.type === "decimal") {
1293
- chains.push(
1294
- `${column.type}('${column.name}', ${column.precision}, ${column.scale})`
1295
- );
1296
- } else {
1297
- // type, length
1298
- let columnType = column.type;
1299
- let extraType: string | undefined;
1300
- if (columnType.includes("text") && columnType !== "text") {
1301
- extraType = columnType;
1302
- columnType = "text";
1303
- }
1304
- chains.push(
1305
- `${column.type}('${column.name}'${
1306
- column.length ? `, ${column.length}` : ""
1307
- }${extraType ? `, '${extraType}'` : ""})`
1308
- );
1309
- }
1310
- if (column.unsigned) {
1311
- chains.push("unsigned()");
1312
- }
1313
-
1314
- // nullable
1315
- chains.push(column.nullable ? "nullable()" : "notNullable()");
1316
-
1317
- // defaultTo
1318
- if (column.defaultTo !== undefined) {
1319
- if (
1320
- typeof column.defaultTo === "string" &&
1321
- column.defaultTo.startsWith(`"`)
1322
- ) {
1323
- chains.push(`defaultTo(${column.defaultTo})`);
1324
- } else {
1325
- chains.push(`defaultTo(knex.raw('${column.defaultTo}'))`);
1326
- }
1327
- }
1328
-
1329
- return `table.${chains.join(".")};`;
1330
- });
1331
- }
1332
-
1333
- /*
1334
- MigrationIndex[] 읽어서 인덱스/유니크 정의하는 구문 생성
1335
- */
1336
- genIndexDefinitions(indexes: MigrationIndex[]): string[] {
1337
- if (indexes.length === 0) {
1338
- return [];
1339
- }
1340
- const lines = _.uniq(
1341
- indexes.reduce((r, index) => {
1342
- r.push(
1343
- `table.${index.type}([${index.columns
1344
- .map((col) => `'${col}'`)
1345
- .join(",")}])`
1346
- );
1347
- return r;
1348
- }, [] as string[])
1349
- );
1350
- return lines;
1351
- }
1352
-
1353
- /*
1354
- MigrationForeign[] 읽어서 외부키 constraint 정의하는 구문 생성
1355
- */
1356
- genForeignDefinitions(
1357
- table: string,
1358
- foreigns: MigrationForeign[]
1359
- ): { up: string[]; down: string[] } {
1360
- return foreigns.reduce(
1361
- (r, foreign) => {
1362
- const columnsStringQuote = foreign.columns
1363
- .map((col) => `'${col.replace(`${table}.`, "")}'`)
1364
- .join(",");
1365
- r.up.push(
1366
- `table.foreign('${foreign.columns.join(",")}')
1367
- .references('${foreign.to}')
1368
- .onUpdate('${foreign.onUpdate}')
1369
- .onDelete('${foreign.onDelete}')`
1370
- );
1371
- r.down.push(`table.dropForeign([${columnsStringQuote}])`);
1372
- return r;
1373
- },
1374
- {
1375
- up: [] as string[],
1376
- down: [] as string[],
1377
- }
1378
- );
1379
- }
1380
-
1381
- /*
1382
- 테이블 생성하는 케이스 - 컬럼/인덱스 생성
1383
- */
1384
- async generateCreateCode_ColumnAndIndexes(
1385
- table: string,
1386
- columns: MigrationColumn[],
1387
- indexes: MigrationIndex[]
1388
- ): Promise<GenMigrationCode> {
1389
- // 컬럼, 인덱스 처리
1390
- const lines: string[] = [
1391
- 'import { Knex } from "knex";',
1392
- "",
1393
- "export async function up(knex: Knex): Promise<void> {",
1394
- `return knex.schema.createTable("${table}", (table) => {`,
1395
- "// columns",
1396
- ...this.genColumnDefinitions(columns),
1397
- "",
1398
- "// indexes",
1399
- ...this.genIndexDefinitions(indexes),
1400
- "});",
1401
- "}",
1402
- "",
1403
- "export async function down(knex: Knex): Promise<void> {",
1404
- ` return knex.schema.dropTable("${table}");`,
1405
- "}",
1406
- ];
1407
- return {
1408
- table,
1409
- type: "normal",
1410
- title: `create__${table}`,
1411
- formatted: await prettier.format(lines.join("\n"), {
1412
- parser: "typescript",
1413
- }),
1414
- };
1415
- }
1416
- /*
1417
- 테이블 생성하는 케이스 - FK 생성
1418
- */
1419
- async generateCreateCode_Foreign(
1420
- table: string,
1421
- foreigns: MigrationForeign[]
1422
- ): Promise<GenMigrationCode[]> {
1423
- if (foreigns.length === 0) {
1424
- return [];
1425
- }
1426
-
1427
- const { up, down } = this.genForeignDefinitions(table, foreigns);
1428
- if (up.length === 0 && down.length === 0) {
1429
- console.log("fk 가 뭔가 다릅니다");
1430
- return [];
1431
- }
1432
-
1433
- const lines: string[] = [
1434
- 'import { Knex } from "knex";',
1435
- "",
1436
- "export async function up(knex: Knex): Promise<void> {",
1437
- `return knex.schema.alterTable("${table}", (table) => {`,
1438
- "// create fk",
1439
- ...up,
1440
- "});",
1441
- "}",
1442
- "",
1443
- "export async function down(knex: Knex): Promise<void> {",
1444
- `return knex.schema.alterTable("${table}", (table) => {`,
1445
- "// drop fk",
1446
- ...down,
1447
- "});",
1448
- "}",
1449
- ];
1450
-
1451
- const foreignKeysString = foreigns
1452
- .map((foreign) => foreign.columns.join("_"))
1453
- .join("_");
1454
- return [
1455
- {
1456
- table,
1457
- type: "foreign",
1458
- title: `foreign__${table}__${foreignKeysString}`,
1459
- formatted: await prettier.format(lines.join("\n"), {
1460
- parser: "typescript",
1461
- }),
1462
- },
1463
- ];
1464
- }
1465
-
1466
1287
  /*
1467
1288
  마이그레이션 컬럼 배열 비교용 코드
1468
1289
  */
1469
- showMigrationSet(which: string, migrationSet: MigrationSet): void {
1290
+ showMigrationSet(which: "Entity" | "DB", migrationSet: MigrationSet): void {
1470
1291
  const { columns, indexes, foreigns } = migrationSet;
1471
1292
  const styledChalk =
1472
- which === "MD" ? chalk.bgGreen.black : chalk.bgBlue.black;
1293
+ which === "Entity" ? chalk.bgGreen.black : chalk.bgBlue.black;
1473
1294
  console.log(
1474
1295
  styledChalk(
1475
1296
  `${which} ${migrationSet.table} Columns\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t`
@@ -1533,390 +1354,6 @@ export class Migrator {
1533
1354
  }
1534
1355
  }
1535
1356
 
1536
- async generateAlterCode_ColumnAndIndexes(
1537
- table: string,
1538
- entityColumns: MigrationColumn[],
1539
- entityIndexes: MigrationIndex[],
1540
- dbColumns: MigrationColumn[],
1541
- dbIndexes: MigrationIndex[]
1542
- ): Promise<GenMigrationCode[]> {
1543
- /*
1544
- 세부 비교 후 다른점 찾아서 코드 생성
1545
-
1546
- 1. 컬럼갯수 다름: MD에 있으나, DB에 없다면 추가
1547
- 2. 컬럼갯수 다름: MD에 없으나, DB에 있다면 삭제
1548
- 3. 그외 컬럼(컬럼 갯수가 동일하거나, 다른 경우 동일한 컬럼끼리) => alter
1549
- 4. 다른거 다 동일하고 index만 변경되는 경우
1550
-
1551
- ** 컬럼명을 변경하는 경우는 따로 핸들링하지 않음
1552
- => drop/add 형태의 마이그레이션 코드가 생성되는데, 수동으로 rename 코드로 수정하여 처리
1553
- */
1554
-
1555
- // 각 컬럼 이름 기준으로 add, drop, alter 여부 확인
1556
- const alterColumnsTo = this.getAlterColumnsTo(entityColumns, dbColumns);
1557
-
1558
- // 추출된 컬럼들을 기준으로 각각 라인 생성
1559
- const alterColumnLinesTo = this.getAlterColumnLinesTo(
1560
- alterColumnsTo,
1561
- entityColumns
1562
- );
1563
-
1564
- // 인덱스의 add, drop 여부 확인
1565
- const alterIndexesTo = this.getAlterIndexesTo(entityIndexes, dbIndexes);
1566
-
1567
- // 추출된 인덱스들을 기준으로 각각 라인 생성
1568
- const alterIndexLinesTo = this.getAlterIndexLinesTo(
1569
- alterIndexesTo,
1570
- alterColumnsTo
1571
- );
1572
-
1573
- const lines: string[] = [
1574
- 'import { Knex } from "knex";',
1575
- "",
1576
- "export async function up(knex: Knex): Promise<void> {",
1577
- `return knex.schema.alterTable("${table}", (table) => {`,
1578
- ...(alterColumnsTo.add.length > 0 ? alterColumnLinesTo.add.up : []),
1579
- ...(alterColumnsTo.drop.length > 0 ? alterColumnLinesTo.drop.up : []),
1580
- ...(alterColumnsTo.alter.length > 0 ? alterColumnLinesTo.alter.up : []),
1581
- ...(alterIndexesTo.add.length > 0 ? alterIndexLinesTo.add.up : []),
1582
- ...(alterIndexesTo.drop.length > 0 ? alterIndexLinesTo.drop.up : []),
1583
- "})",
1584
- "}",
1585
- "",
1586
- "export async function down(knex: Knex): Promise<void> {",
1587
- `return knex.schema.alterTable("${table}", (table) => {`,
1588
- ...(alterColumnsTo.add.length > 0 ? alterColumnLinesTo.add.down : []),
1589
- ...(alterColumnsTo.drop.length > 0 ? alterColumnLinesTo.drop.down : []),
1590
- ...(alterColumnsTo.alter.length > 0 ? alterColumnLinesTo.alter.down : []),
1591
- ...(alterIndexLinesTo.add.down.length > 0
1592
- ? alterIndexLinesTo.add.down
1593
- : []),
1594
- ...(alterIndexLinesTo.drop.down.length > 0
1595
- ? alterIndexLinesTo.drop.down
1596
- : []),
1597
- "})",
1598
- "}",
1599
- ];
1600
-
1601
- const formatted = await prettier.format(lines.join("\n"), {
1602
- parser: "typescript",
1603
- });
1604
-
1605
- const title = [
1606
- "alter",
1607
- table,
1608
- ...(["add", "drop", "alter"] as const)
1609
- .map((action) => {
1610
- const len = alterColumnsTo[action].length;
1611
- if (len > 0) {
1612
- return action + len;
1613
- }
1614
- return null;
1615
- })
1616
- .filter((part) => part !== null),
1617
- ].join("_");
1618
-
1619
- return [
1620
- {
1621
- table,
1622
- title,
1623
- formatted,
1624
- type: "normal",
1625
- },
1626
- ];
1627
- }
1628
-
1629
- getAlterColumnsTo(
1630
- entityColumns: MigrationColumn[],
1631
- dbColumns: MigrationColumn[]
1632
- ) {
1633
- const columnsTo = {
1634
- add: [] as MigrationColumn[],
1635
- drop: [] as MigrationColumn[],
1636
- alter: [] as MigrationColumn[],
1637
- };
1638
-
1639
- // 컬럼명 기준 비교
1640
- const extraColumns = {
1641
- db: _.differenceBy(dbColumns, entityColumns, (col) => col.name),
1642
- entity: _.differenceBy(entityColumns, dbColumns, (col) => col.name),
1643
- };
1644
- if (extraColumns.entity.length > 0) {
1645
- columnsTo.add = columnsTo.add.concat(extraColumns.entity);
1646
- }
1647
- if (extraColumns.db.length > 0) {
1648
- columnsTo.drop = columnsTo.drop.concat(extraColumns.db);
1649
- }
1650
-
1651
- // 동일 컬럼명의 세부 필드 비교
1652
- const sameDbColumns = _.intersectionBy(
1653
- dbColumns,
1654
- entityColumns,
1655
- (col) => col.name
1656
- );
1657
- const sameMdColumns = _.intersectionBy(
1658
- entityColumns,
1659
- dbColumns,
1660
- (col) => col.name
1661
- );
1662
- columnsTo.alter = _.differenceWith(sameDbColumns, sameMdColumns, (a, b) =>
1663
- equal(a, b)
1664
- );
1665
-
1666
- return columnsTo;
1667
- }
1668
-
1669
- getAlterColumnLinesTo(
1670
- columnsTo: ReturnType<Migrator["getAlterColumnsTo"]>,
1671
- entityColumns: MigrationColumn[]
1672
- ) {
1673
- let linesTo = {
1674
- add: {
1675
- up: [] as string[],
1676
- down: [] as string[],
1677
- },
1678
- drop: {
1679
- up: [] as string[],
1680
- down: [] as string[],
1681
- },
1682
- alter: {
1683
- up: [] as string[],
1684
- down: [] as string[],
1685
- },
1686
- };
1687
-
1688
- linesTo.add = {
1689
- up: ["// add", ...this.genColumnDefinitions(columnsTo.add)],
1690
- down: [
1691
- "// rollback - add",
1692
- `table.dropColumns(${columnsTo.add
1693
- .map((col) => `'${col.name}'`)
1694
- .join(", ")})`,
1695
- ],
1696
- };
1697
- linesTo.drop = {
1698
- up: [
1699
- "// drop",
1700
- `table.dropColumns(${columnsTo.drop
1701
- .map((col) => `'${col.name}'`)
1702
- .join(", ")})`,
1703
- ],
1704
- down: [
1705
- "// rollback - drop",
1706
- ...this.genColumnDefinitions(columnsTo.drop),
1707
- ],
1708
- };
1709
- linesTo.alter = columnsTo.alter.reduce(
1710
- (r, dbColumn) => {
1711
- const entityColumn = entityColumns.find(
1712
- (col) => col.name == dbColumn.name
1713
- );
1714
- if (entityColumn === undefined) {
1715
- return r;
1716
- }
1717
-
1718
- // 컬럼 변경사항
1719
- const columnDiffUp = _.difference(
1720
- this.genColumnDefinitions([entityColumn]),
1721
- this.genColumnDefinitions([dbColumn])
1722
- );
1723
- const columnDiffDown = _.difference(
1724
- this.genColumnDefinitions([dbColumn]),
1725
- this.genColumnDefinitions([entityColumn])
1726
- );
1727
- if (columnDiffUp.length > 0) {
1728
- r.up = [
1729
- ...r.up,
1730
- "// alter column",
1731
- ...columnDiffUp.map((l) => l.replace(";", "") + ".alter();"),
1732
- ];
1733
- r.down = [
1734
- ...r.down,
1735
- "// rollback - alter column",
1736
- ...columnDiffDown.map((l) => l.replace(";", "") + ".alter();"),
1737
- ];
1738
- }
1739
-
1740
- return r;
1741
- },
1742
- {
1743
- up: [] as string[],
1744
- down: [] as string[],
1745
- }
1746
- );
1747
-
1748
- return linesTo;
1749
- }
1750
-
1751
- getAlterIndexesTo(
1752
- entityIndexes: MigrationIndex[],
1753
- dbIndexes: MigrationIndex[]
1754
- ) {
1755
- // 인덱스 비교
1756
- let indexesTo = {
1757
- add: [] as MigrationIndex[],
1758
- drop: [] as MigrationIndex[],
1759
- };
1760
- const extraIndexes = {
1761
- db: _.differenceBy(dbIndexes, entityIndexes, (col) =>
1762
- [col.type, col.columns.join("-")].join("//")
1763
- ),
1764
- entity: _.differenceBy(entityIndexes, dbIndexes, (col) =>
1765
- [col.type, col.columns.join("-")].join("//")
1766
- ),
1767
- };
1768
- if (extraIndexes.entity.length > 0) {
1769
- indexesTo.add = indexesTo.add.concat(extraIndexes.entity);
1770
- }
1771
- if (extraIndexes.db.length > 0) {
1772
- indexesTo.drop = indexesTo.drop.concat(extraIndexes.db);
1773
- }
1774
-
1775
- return indexesTo;
1776
- }
1777
-
1778
- getAlterIndexLinesTo(
1779
- indexesTo: ReturnType<Migrator["getAlterIndexesTo"]>,
1780
- columnsTo: ReturnType<Migrator["getAlterColumnsTo"]>
1781
- ) {
1782
- let linesTo = {
1783
- add: {
1784
- up: [] as string[],
1785
- down: [] as string[],
1786
- },
1787
- drop: {
1788
- up: [] as string[],
1789
- down: [] as string[],
1790
- },
1791
- };
1792
-
1793
- // 인덱스가 추가되는 경우, 컬럼과 같이 추가된 케이스에는 drop에서 제외해야함!
1794
- linesTo.add = {
1795
- up: ["// add indexes", ...this.genIndexDefinitions(indexesTo.add)],
1796
- down: [
1797
- "// rollback - add indexes",
1798
- ...indexesTo.add
1799
- .filter(
1800
- (index) =>
1801
- index.columns.every((colName) =>
1802
- columnsTo.add.map((col) => col.name).includes(colName)
1803
- ) === false
1804
- )
1805
- .map(
1806
- (index) =>
1807
- `table.drop${inflection.capitalize(index.type)}([${index.columns
1808
- .map((columnName) => `'${columnName}'`)
1809
- .join(",")}])`
1810
- ),
1811
- ],
1812
- };
1813
- // 인덱스가 삭제되는 경우, 컬럼과 같이 삭제된 케이스에는 drop에서 제외해야함!
1814
- linesTo.drop = {
1815
- up: [
1816
- ...indexesTo.drop
1817
- .filter(
1818
- (index) =>
1819
- index.columns.every((colName) =>
1820
- columnsTo.drop.map((col) => col.name).includes(colName)
1821
- ) === false
1822
- )
1823
- .map(
1824
- (index) =>
1825
- `table.drop${inflection.capitalize(index.type)}([${index.columns
1826
- .map((columnName) => `'${columnName}'`)
1827
- .join(",")}])`
1828
- ),
1829
- ],
1830
- down: [
1831
- "// rollback - drop indexes",
1832
- ...this.genIndexDefinitions(indexesTo.drop),
1833
- ],
1834
- };
1835
-
1836
- return linesTo;
1837
- }
1838
-
1839
- async generateAlterCode_Foreigns(
1840
- table: string,
1841
- entityForeigns: MigrationForeign[],
1842
- dbForeigns: MigrationForeign[]
1843
- ): Promise<GenMigrationCode[]> {
1844
- // console.log({ entityForeigns, dbForeigns });
1845
-
1846
- const getKey = (mf: MigrationForeign): string => {
1847
- return [mf.columns.join("-"), mf.to].join("///");
1848
- };
1849
- const fkTo = entityForeigns.reduce(
1850
- (result, entityF) => {
1851
- const matchingDbF = dbForeigns.find(
1852
- (dbF) => getKey(entityF) === getKey(dbF)
1853
- );
1854
- if (!matchingDbF) {
1855
- result.add.push(entityF);
1856
- return result;
1857
- }
1858
-
1859
- if (equal(entityF, matchingDbF) === false) {
1860
- result.alterSrc.push(matchingDbF);
1861
- result.alterDst.push(entityF);
1862
- return result;
1863
- }
1864
- return result;
1865
- },
1866
- {
1867
- add: [] as MigrationForeign[],
1868
- alterSrc: [] as MigrationForeign[],
1869
- alterDst: [] as MigrationForeign[],
1870
- }
1871
- );
1872
-
1873
- const linesTo = {
1874
- add: this.genForeignDefinitions(table, fkTo.add),
1875
- alterSrc: this.genForeignDefinitions(table, fkTo.alterSrc),
1876
- alterDst: this.genForeignDefinitions(table, fkTo.alterDst),
1877
- };
1878
-
1879
- const lines: string[] = [
1880
- 'import { Knex } from "knex";',
1881
- "",
1882
- "export async function up(knex: Knex): Promise<void> {",
1883
- `return knex.schema.alterTable("${table}", (table) => {`,
1884
- ...linesTo.add.up,
1885
- ...linesTo.alterSrc.down,
1886
- ...linesTo.alterDst.up,
1887
- "})",
1888
- "}",
1889
- "",
1890
- "export async function down(knex: Knex): Promise<void> {",
1891
- `return knex.schema.alterTable("${table}", (table) => {`,
1892
- ...linesTo.add.down,
1893
- ...linesTo.alterDst.down,
1894
- ...linesTo.alterSrc.up,
1895
- "})",
1896
- "}",
1897
- ];
1898
-
1899
- const formatted = await prettier.format(lines.join("\n"), {
1900
- parser: "typescript",
1901
- });
1902
-
1903
- const title = [
1904
- "alter",
1905
- table,
1906
- "foreigns",
1907
- // TODO 바뀌는 부분
1908
- ].join("_");
1909
-
1910
- return [
1911
- {
1912
- table,
1913
- title,
1914
- formatted,
1915
- type: "normal",
1916
- },
1917
- ];
1918
- }
1919
-
1920
1357
  async destroy(): Promise<void> {
1921
1358
  await Promise.all(
1922
1359
  this.targets.apply.map((db) => {