sonamu 0.3.1 → 0.4.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 (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-FLPD24HS.mjs +231 -0
  9. package/dist/chunk-FLPD24HS.mjs.map +1 -0
  10. package/dist/chunk-I2MMJRJN.mjs +1550 -0
  11. package/dist/chunk-I2MMJRJN.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-U636LQJJ.js +231 -0
  17. package/dist/chunk-U636LQJJ.js.map +1 -0
  18. package/dist/chunk-W7KDVJLQ.js +280 -0
  19. package/dist/chunk-W7KDVJLQ.js.map +1 -0
  20. package/dist/{chunk-YXILRRDT.js → chunk-XT6LHCX5.js} +5252 -5585
  21. package/dist/chunk-XT6LHCX5.js.map +1 -0
  22. package/dist/chunk-Z2P7XTXE.js +1550 -0
  23. package/dist/chunk-Z2P7XTXE.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 +220 -926
  37. package/dist/index.d.ts +220 -926
  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 +23 -2
  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 +78 -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 +98 -693
  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,13 +445,10 @@ 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
  );
@@ -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,7 +664,9 @@ export class Migrator {
700
664
  console.timeEnd(chalk.red("delete migration files"));
701
665
  }
702
666
 
703
- async compareMigrations(compareDB: Knex): Promise<GenMigrationCode[]> {
667
+ async compareMigrations(
668
+ compareDB: KnexClient | KyselyClient
669
+ ): Promise<GenMigrationCode[]> {
704
670
  // MD 순회하여 싱크
705
671
  const entityIds = EntityManager.getAllIds();
706
672
 
@@ -739,12 +705,12 @@ export class Migrator {
739
705
  if (dbSet === null) {
740
706
  // 기존 테이블 없음, 새로 테이블 생성
741
707
  return [
742
- await this.generateCreateCode_ColumnAndIndexes(
708
+ await DB.generator.generateCreateCode_ColumnAndIndexes(
743
709
  entitySet.table,
744
710
  entitySet.columns,
745
711
  entitySet.indexes
746
712
  ),
747
- ...(await this.generateCreateCode_Foreign(
713
+ ...(await DB.generator.generateCreateCode_Foreign(
748
714
  entitySet.table,
749
715
  entitySet.foreigns
750
716
  )),
@@ -812,7 +778,7 @@ export class Migrator {
812
778
  } else {
813
779
  // this.showMigrationSet("MD", entitySet);
814
780
  // this.showMigrationSet("DB", dbSet);
815
- return this.generateAlterCode_ColumnAndIndexes(
781
+ return DB.generator.generateAlterCode_ColumnAndIndexes(
816
782
  entitySet.table,
817
783
  entityColumns,
818
784
  entityIndexes,
@@ -842,7 +808,7 @@ export class Migrator {
842
808
 
843
809
  if (equal(entityForeigns, dbForeigns) === false) {
844
810
  // console.dir({ entityForeigns, dbForeigns }, { depth: null });
845
- return this.generateAlterCode_Foreigns(
811
+ return DB.generator.generateAlterCode_Foreigns(
846
812
  entitySet.table,
847
813
  entityForeigns,
848
814
  dbForeigns
@@ -883,7 +849,7 @@ export class Migrator {
883
849
  기존 테이블 정보 읽어서 MigrationSet 형식으로 리턴
884
850
  */
885
851
  async getMigrationSetFromDB(
886
- compareDB: Knex,
852
+ compareDB: KnexClient | KyselyClient,
887
853
  table: string
888
854
  ): Promise<MigrationSet | null> {
889
855
  let dbColumns: DBColumn[], dbIndexes: DBIndex[], dbForeigns: DBForeign[];
@@ -1009,7 +975,7 @@ export class Migrator {
1009
975
  };
1010
976
  case "datetime":
1011
977
  return {
1012
- type: "dateTime",
978
+ type: "datetime",
1013
979
  };
1014
980
  case "tinyint":
1015
981
  return {
@@ -1048,14 +1014,15 @@ export class Migrator {
1048
1014
  기존 테이블 읽어서 cols, indexes 반환
1049
1015
  */
1050
1016
  async readTable(
1051
- compareDB: Knex,
1017
+ compareDB: KnexClient | KyselyClient,
1052
1018
  tableName: string
1053
1019
  ): Promise<[DBColumn[], DBIndex[], DBForeign[]]> {
1054
1020
  // 테이블 정보
1055
1021
  try {
1056
- const [_cols] = (await compareDB.raw(
1022
+ const _cols = await compareDB.raw<DBColumn>(
1057
1023
  `SHOW FIELDS FROM ${tableName}`
1058
- )) as [DBColumn[]];
1024
+ );
1025
+
1059
1026
  const cols = _cols.map((col) => ({
1060
1027
  ...col,
1061
1028
  // Default 값은 숫자나 MySQL Expression이 아닌 경우 ""로 감싸줌
@@ -1068,8 +1035,12 @@ export class Migrator {
1068
1035
  }),
1069
1036
  }));
1070
1037
 
1071
- const [indexes] = await compareDB.raw(`SHOW INDEX FROM ${tableName}`);
1072
- const [[row]] = await compareDB.raw(`SHOW CREATE TABLE ${tableName}`);
1038
+ const indexes = await compareDB.raw<DBIndex>(
1039
+ `SHOW INDEX FROM ${tableName}`
1040
+ );
1041
+ const [row] = await compareDB.raw<{
1042
+ "Create Table": string;
1043
+ }>(`SHOW CREATE TABLE ${tableName}`);
1073
1044
  const ddl = row["Create Table"];
1074
1045
  const matched = ddl.match(/CONSTRAINT .+/g);
1075
1046
  const foreignKeys = (matched ?? []).map((line: string) => {
@@ -1091,7 +1062,8 @@ export class Migrator {
1091
1062
  const onDelete =
1092
1063
  (onClause ?? "")
1093
1064
  .replace(onUpdateFull ?? "", "")
1094
- .match(/ON DELETE ([A-Z ]+) /)?.[1] ?? "NO ACTION";
1065
+ .match(/ON DELETE ([A-Z ]+)/)?.[1]
1066
+ ?.trim() ?? "NO ACTION";
1095
1067
 
1096
1068
  return {
1097
1069
  keyName,
@@ -1151,6 +1123,7 @@ export class Migrator {
1151
1123
  }
1152
1124
  return {};
1153
1125
  })(),
1126
+ // FIXME: float(N, M) deprecated
1154
1127
  // Decimal, Float 타입의 경우 precision, scale 추가
1155
1128
  ...((isDecimalProp(prop) || isFloatProp(prop)) && {
1156
1129
  precision: prop.precision ?? 8,
@@ -1279,190 +1252,6 @@ export class Migrator {
1279
1252
  return migrationSet;
1280
1253
  }
1281
1254
 
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
1255
  /*
1467
1256
  마이그레이션 컬럼 배열 비교용 코드
1468
1257
  */
@@ -1533,390 +1322,6 @@ export class Migrator {
1533
1322
  }
1534
1323
  }
1535
1324
 
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
1325
  async destroy(): Promise<void> {
1921
1326
  await Promise.all(
1922
1327
  this.targets.apply.map((db) => {