sonamu 0.0.20 → 0.0.22

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 (37) hide show
  1. package/dist/bin/cli.js +9 -0
  2. package/dist/bin/cli.js.map +1 -1
  3. package/dist/database/upsert-builder.d.ts +2 -0
  4. package/dist/database/upsert-builder.d.ts.map +1 -1
  5. package/dist/database/upsert-builder.js +25 -2
  6. package/dist/database/upsert-builder.js.map +1 -1
  7. package/dist/smd/migrator.d.ts +2 -0
  8. package/dist/smd/migrator.d.ts.map +1 -1
  9. package/dist/smd/migrator.js +92 -75
  10. package/dist/smd/migrator.js.map +1 -1
  11. package/dist/smd/smd-utils.d.ts +7 -1
  12. package/dist/smd/smd-utils.d.ts.map +1 -1
  13. package/dist/smd/smd-utils.js +18 -1
  14. package/dist/smd/smd-utils.js.map +1 -1
  15. package/dist/smd/smd.d.ts +3 -2
  16. package/dist/smd/smd.d.ts.map +1 -1
  17. package/dist/smd/smd.js +7 -17
  18. package/dist/smd/smd.js.map +1 -1
  19. package/dist/templates/model_test.template.js +1 -1
  20. package/dist/templates/smd.template.d.ts.map +1 -1
  21. package/dist/templates/smd.template.js +5 -1
  22. package/dist/templates/smd.template.js.map +1 -1
  23. package/dist/templates/view_search_input.template.js +3 -3
  24. package/dist/types/types.d.ts +14 -3
  25. package/dist/types/types.d.ts.map +1 -1
  26. package/dist/types/types.js +5 -1
  27. package/dist/types/types.js.map +1 -1
  28. package/package.json +1 -1
  29. package/src/bin/cli.ts +9 -0
  30. package/src/database/upsert-builder.ts +24 -2
  31. package/src/smd/migrator.ts +118 -92
  32. package/src/smd/smd-utils.ts +21 -0
  33. package/src/smd/smd.ts +19 -17
  34. package/src/templates/model_test.template.ts +1 -1
  35. package/src/templates/smd.template.ts +5 -1
  36. package/src/templates/view_search_input.template.ts +3 -3
  37. package/src/types/types.ts +18 -3
@@ -124,6 +124,18 @@ export class Migrator {
124
124
  await this.cleanUpDist(true);
125
125
  }
126
126
 
127
+ async check(): Promise<void> {
128
+ const codes = await this.compareMigrations();
129
+ if (codes.length === 0) {
130
+ console.log(chalk.green("\n현재 모두 싱크된 상태입니다."));
131
+ return;
132
+ }
133
+
134
+ // 현재 생성된 코드 표기
135
+ console.table(codes, ["type", "title"]);
136
+ console.log(codes[0]);
137
+ }
138
+
127
139
  async run(): Promise<void> {
128
140
  // pending 마이그레이션 확인
129
141
  const [, pendingList] = await this.targets.pending.migrate.list();
@@ -420,14 +432,8 @@ export class Migrator {
420
432
  ).map((key) => {
421
433
  // 배열 원소의 순서가 달라서 불일치가 발생하는걸 방지하기 위해 각 항목별로 정렬 처리 후 비교
422
434
  if (key === "columnsAndIndexes") {
423
- const smdColumns = sortBy(
424
- smdSet.columns,
425
- (a: any) => (a as MigrationColumn).name
426
- );
427
- const dbColumns = sortBy(
428
- dbSet.columns,
429
- (a: any) => (a as MigrationColumn).name
430
- );
435
+ const smdColumns = sortBy(smdSet.columns, (a: any) => a.name);
436
+ const dbColumns = sortBy(dbSet.columns, (a: any) => a.name);
431
437
 
432
438
  /* 디버깅용 코드, 특정 컬럼에서 불일치 발생할 때 확인
433
439
  const smdCreatedAt = smdSet.columns.find(
@@ -439,11 +445,11 @@ export class Migrator {
439
445
  console.debug({ smdCreatedAt, dbCreatedAt });
440
446
  */
441
447
 
442
- const smdIndexes = sortBy(smdSet.indexes, (a: any) =>
443
- (a as MigrationIndex).columns.join("-")
448
+ const smdIndexes = sortBy(smdSet.indexes, (a) =>
449
+ [a.type, ...a.columns].join("-")
444
450
  );
445
- const dbIndexes = sortBy(dbSet.indexes, (a: any) =>
446
- (a as MigrationIndex).columns.join("-")
451
+ const dbIndexes = sortBy(dbSet.indexes, (a) =>
452
+ [a.type, ...a.columns].join("-")
447
453
  );
448
454
 
449
455
  const isEqualColumns = equal(smdColumns, dbColumns);
@@ -462,17 +468,20 @@ export class Migrator {
462
468
  );
463
469
  }
464
470
  } else {
465
- const smdForeigns = sortBy(smdSet.foreigns, (a: any) =>
466
- (a as MigrationForeign).columns.join("-")
471
+ const smdForeigns = sortBy(smdSet.foreigns, (a) =>
472
+ [a.to, ...a.columns].join("-")
467
473
  );
468
- const dbForeigns = sortBy(dbSet.foreigns, (a: any) =>
469
- (a as MigrationForeign).columns.join("-")
474
+ const dbForeigns = sortBy(dbSet.foreigns, (a) =>
475
+ [a.to, ...a.columns].join("-")
470
476
  );
471
477
 
472
478
  if (equal(smdForeigns, dbForeigns) === false) {
473
- // TODO FK alter
474
- console.log(chalk.red(`FK 다름! ${smdSet.table}`));
475
479
  // console.dir({ smdForeigns, dbForeigns }, { depth: null });
480
+ return this.generateAlterCode_Foreigns(
481
+ smdSet.table,
482
+ smdForeigns,
483
+ dbForeigns
484
+ );
476
485
  }
477
486
  }
478
487
  return null;
@@ -550,9 +559,6 @@ export class Migrator {
550
559
  columns: currentIndexes.map(
551
560
  (currentIndex) => currentIndex.Column_name
552
561
  ),
553
- ...propIf(currentIndexes.length > 1, {
554
- name: keyName,
555
- }),
556
562
  };
557
563
  }
558
564
  );
@@ -742,66 +748,6 @@ export class Migrator {
742
748
  r.columns.push(column);
743
749
  }
744
750
 
745
- // 일반 컬럼 + ToOne 케이스 컬럼
746
- if (
747
- !isRelationProp(prop) ||
748
- isBelongsToOneRelationProp(prop) ||
749
- (isOneToOneRelationProp(prop) && prop.hasJoinColumn)
750
- ) {
751
- const propName = !isRelationProp(prop)
752
- ? prop.name
753
- : `${prop.name}_id`;
754
-
755
- // index 처리
756
- if (prop.index !== undefined) {
757
- if (prop.index !== true) {
758
- prop.index.map((indexName) => {
759
- const namedOne = r.indexes.find(
760
- (_index) => _index.name === indexName
761
- );
762
- if (namedOne) {
763
- namedOne.columns.push(propName);
764
- } else {
765
- r.indexes.push({
766
- type: "index",
767
- columns: [propName],
768
- name: indexName,
769
- });
770
- }
771
- });
772
- } else {
773
- r.indexes.push({
774
- type: "index",
775
- columns: [propName],
776
- });
777
- }
778
- }
779
- // unique 처리
780
- if (prop.unique !== undefined) {
781
- if (prop.unique !== true) {
782
- prop.unique.map((indexName) => {
783
- const namedOne = r.indexes.find(
784
- (_index) => _index.name === indexName
785
- );
786
- if (namedOne) {
787
- namedOne.columns.push(propName);
788
- } else {
789
- r.indexes.push({
790
- type: "unique",
791
- columns: [propName],
792
- name: indexName,
793
- });
794
- }
795
- });
796
- } else {
797
- r.indexes.push({
798
- type: "unique",
799
- columns: [propName],
800
- });
801
- }
802
- }
803
- }
804
-
805
751
  if (isManyToManyRelationProp(prop)) {
806
752
  // ManyToMany 케이스
807
753
  const relMd = SMDManager.get(prop.with);
@@ -888,6 +834,9 @@ export class Migrator {
888
834
  }
889
835
  );
890
836
 
837
+ // indexes
838
+ migrationSet.indexes = smd.indexes;
839
+
891
840
  // uuid
892
841
  migrationSet.columns = migrationSet.columns.concat({
893
842
  name: "uuid",
@@ -949,15 +898,11 @@ export class Migrator {
949
898
  }
950
899
  const lines = uniq(
951
900
  indexes.reduce((r, index) => {
952
- if (index.name === undefined) {
953
- r.push(`table.${index.type}(['${index.columns[0]}'])`);
954
- } else {
955
- r.push(
956
- `table.${index.type}([${index.columns
957
- .map((col) => `'${col}'`)
958
- .join(",")}], '${index.name}')`
959
- );
960
- }
901
+ r.push(
902
+ `table.${index.type}([${index.columns
903
+ .map((col) => `'${col}'`)
904
+ .join(",")}])`
905
+ );
961
906
  return r;
962
907
  }, [] as string[])
963
908
  );
@@ -1362,10 +1307,10 @@ export class Migrator {
1362
1307
  };
1363
1308
  const extraIndexes = {
1364
1309
  db: differenceBy(dbIndexes, smdIndexes, (col) =>
1365
- [col.type, col.name ?? col.columns.join("-")].join("//")
1310
+ [col.type, col.columns.join("-")].join("//")
1366
1311
  ),
1367
1312
  smd: differenceBy(smdIndexes, dbIndexes, (col) =>
1368
- [col.type, col.name ?? col.columns.join("-")].join("//")
1313
+ [col.type, col.columns.join("-")].join("//")
1369
1314
  ),
1370
1315
  };
1371
1316
  if (extraIndexes.smd.length > 0) {
@@ -1439,6 +1384,87 @@ export class Migrator {
1439
1384
  return linesTo;
1440
1385
  }
1441
1386
 
1387
+ generateAlterCode_Foreigns(
1388
+ table: string,
1389
+ smdForeigns: MigrationForeign[],
1390
+ dbForeigns: MigrationForeign[]
1391
+ ): GenMigrationCode[] {
1392
+ // console.log({ smdForeigns, dbForeigns });
1393
+
1394
+ const getKey = (mf: MigrationForeign): string => {
1395
+ return [mf.columns.join("-"), mf.to].join("///");
1396
+ };
1397
+ const fkTo = smdForeigns.reduce(
1398
+ (result, smdF) => {
1399
+ const matchingDbF = dbForeigns.find(
1400
+ (dbF) => getKey(smdF) === getKey(dbF)
1401
+ );
1402
+ if (!matchingDbF) {
1403
+ result.add.push(smdF);
1404
+ return result;
1405
+ }
1406
+
1407
+ if (equal(smdF, matchingDbF) === false) {
1408
+ result.alterSrc.push(matchingDbF);
1409
+ result.alterDst.push(smdF);
1410
+ return result;
1411
+ }
1412
+ return result;
1413
+ },
1414
+ {
1415
+ add: [] as MigrationForeign[],
1416
+ alterSrc: [] as MigrationForeign[],
1417
+ alterDst: [] as MigrationForeign[],
1418
+ }
1419
+ );
1420
+
1421
+ const linesTo = {
1422
+ add: this.genForeignDefinitions(table, fkTo.add),
1423
+ alterSrc: this.genForeignDefinitions(table, fkTo.alterSrc),
1424
+ alterDst: this.genForeignDefinitions(table, fkTo.alterDst),
1425
+ };
1426
+
1427
+ const lines: string[] = [
1428
+ 'import { Knex } from "knex";',
1429
+ "",
1430
+ "export async function up(knex: Knex): Promise<void> {",
1431
+ `return knex.schema.alterTable("${table}", (table) => {`,
1432
+ ...linesTo.add.up,
1433
+ ...linesTo.alterSrc.down,
1434
+ ...linesTo.alterDst.up,
1435
+ "})",
1436
+ "}",
1437
+ "",
1438
+ "export async function down(knex: Knex): Promise<void> {",
1439
+ `return knex.schema.alterTable("${table}", (table) => {`,
1440
+ ...linesTo.add.down,
1441
+ ...linesTo.alterDst.down,
1442
+ ...linesTo.alterSrc.up,
1443
+ "})",
1444
+ "}",
1445
+ ];
1446
+
1447
+ const formatted = prettier.format(lines.join("\n"), {
1448
+ parser: "typescript",
1449
+ });
1450
+
1451
+ const title = [
1452
+ "alter",
1453
+ table,
1454
+ "foreigns",
1455
+ // TODO 바뀌는 부분
1456
+ ].join("_");
1457
+
1458
+ return [
1459
+ {
1460
+ table,
1461
+ title,
1462
+ formatted,
1463
+ type: "normal",
1464
+ },
1465
+ ];
1466
+ }
1467
+
1442
1468
  async destroy(): Promise<void> {
1443
1469
  await Promise.all(
1444
1470
  this.targets.apply.map((db) => {
@@ -15,6 +15,7 @@ import {
15
15
  JsonProp,
16
16
  ManyToManyRelationProp,
17
17
  OneToOneRelationProp,
18
+ SMDIndex,
18
19
  StringProp,
19
20
  TextProp,
20
21
  TimeProp,
@@ -22,6 +23,7 @@ import {
22
23
  UuidProp,
23
24
  VirtualProp,
24
25
  } from "../types/types";
26
+ import { asArray } from "../utils/model";
25
27
 
26
28
  export const p = {
27
29
  integer,
@@ -264,3 +266,22 @@ function relationManyToMany(
264
266
  ...option,
265
267
  };
266
268
  }
269
+
270
+ export const i = {
271
+ index,
272
+ unique,
273
+ };
274
+
275
+ function index(columns: string | string[]): SMDIndex {
276
+ return {
277
+ type: "index",
278
+ columns: asArray(columns),
279
+ };
280
+ }
281
+
282
+ function unique(columns: string | string[]): SMDIndex {
283
+ return {
284
+ type: "unique",
285
+ columns: asArray(columns),
286
+ };
287
+ }
package/src/smd/smd.ts CHANGED
@@ -15,6 +15,7 @@ import {
15
15
  SMDPropNode,
16
16
  isEnumProp,
17
17
  StringProp,
18
+ SMDIndex,
18
19
  } from "../types/types";
19
20
  import inflection from "inflection";
20
21
  import path from "path";
@@ -39,6 +40,7 @@ export class SMD {
39
40
  relations: {
40
41
  [key: string]: RelationProp;
41
42
  };
43
+ indexes: SMDIndex[];
42
44
  subsets: {
43
45
  [key: string]: string[];
44
46
  };
@@ -52,7 +54,15 @@ export class SMD {
52
54
  [name: string]: EnumsLabelKo<string>;
53
55
  } = {};
54
56
 
55
- constructor({ id, parentId, table, title, props, subsets }: SMDInput<any>) {
57
+ constructor({
58
+ id,
59
+ parentId,
60
+ table,
61
+ title,
62
+ props,
63
+ indexes,
64
+ subsets,
65
+ }: SMDInput<any>) {
56
66
  // id
57
67
  this.id = id;
58
68
  this.parentId = parentId;
@@ -91,6 +101,9 @@ export class SMD {
91
101
  this.relations = {};
92
102
  }
93
103
 
104
+ // indexes
105
+ this.indexes = indexes ?? [];
106
+
94
107
  // subsets
95
108
  this.subsets = subsets ?? {};
96
109
 
@@ -514,22 +527,11 @@ export class SMD {
514
527
 
515
528
  registerTableSpecs(): void {
516
529
  const uniqueColumns = uniq(
517
- this.props
518
- .map((prop) => {
519
- const propColumn =
520
- isBelongsToOneRelationProp(prop) || isOneToOneRelationProp(prop)
521
- ? `${prop.name}_id`
522
- : prop.name;
523
- if (prop.unique === true) {
524
- return propColumn;
525
- } else if (prop.unique && Array.isArray(prop.unique)) {
526
- return propColumn;
527
- } else {
528
- return null;
529
- }
530
- })
531
- .filter((prop) => prop !== null)
532
- ) as string[];
530
+ this.indexes
531
+ .filter((idx) => idx.type === "unique")
532
+ .map((idx) => idx.columns)
533
+ .flat()
534
+ );
533
535
 
534
536
  SMDManager.setTableSpec({
535
537
  name: this.table,
@@ -21,7 +21,7 @@ export class Template__model_test extends Template {
21
21
  ...this.getTargetAndPath(names),
22
22
  body: `
23
23
  import { describe, test, expect } from "vitest";
24
- import { bootstrap } from '../../testing/helpers';
24
+ import { bootstrap } from '../../testing/bootstrap';
25
25
 
26
26
  bootstrap([]);
27
27
  describe.skip("${smdId}ModelTest", () => {
@@ -21,7 +21,7 @@ export class Template__smd extends Template {
21
21
  return {
22
22
  ...this.getTargetAndPath(names),
23
23
  body: `
24
- import { p, SMDInput } from "sonamu";
24
+ import { p, i, SMDInput } from "sonamu";
25
25
  import { ${smdId}FieldExpr } from "./${names.fs}.generated";
26
26
 
27
27
  /*
@@ -37,6 +37,10 @@ export const ${names.camel}SmdInput: SMDInput<${smdId}FieldExpr> = {
37
37
  now: true,
38
38
  }),
39
39
  ],
40
+ indexes: [
41
+ i.index('created_at'),
42
+ //
43
+ ],
40
44
  subsets: {
41
45
  A: [ 'id', 'created_at' ]
42
46
  }
@@ -34,8 +34,8 @@ export function ${names.capital}SearchInput({
34
34
  }) {
35
35
  const [keyword, setKeyword] = useState<string>(inputValue ?? '');
36
36
 
37
- const handleKeyDown = (e: { code: string }) => {
38
- if (inputOnChange && e.code === 'Enter') {
37
+ const handleKeyDown = (e: { key: string }) => {
38
+ if (inputOnChange && e.key === 'Enter') {
39
39
  inputOnChange(e as any, {
40
40
  value: keyword,
41
41
  });
@@ -51,7 +51,7 @@ export function ${names.capital}SearchInput({
51
51
  labelPosition="left"
52
52
  action={{
53
53
  icon: 'search',
54
- onClick: () => handleKeyDown({ code: 'Enter' }),
54
+ onClick: () => handleKeyDown({ key: 'Enter' }),
55
55
  }}
56
56
  {...inputProps}
57
57
  value={keyword}
@@ -122,8 +122,6 @@ type _RelationProp = {
122
122
  name: string;
123
123
  with: string;
124
124
  nullable?: boolean;
125
- index?: true | string[];
126
- unique?: true | string[];
127
125
  toFilter?: true;
128
126
  };
129
127
  export type OneToOneRelationProp = _RelationProp & {
@@ -180,12 +178,19 @@ export type SMDProp =
180
178
  | VirtualProp
181
179
  | RelationProp;
182
180
 
181
+ export type SMDIndex = {
182
+ type: "index" | "unique";
183
+ columns: string[];
184
+ name?: string;
185
+ };
186
+
183
187
  export type SMDInput<T extends string> = {
184
188
  id: string;
185
189
  parentId?: string;
186
190
  table?: string;
187
191
  title?: string;
188
192
  props?: SMDProp[];
193
+ indexes?: SMDIndex[];
189
194
  subsets?: {
190
195
  [subset: string]: T[];
191
196
  };
@@ -323,6 +328,17 @@ export type SubsetQuery = {
323
328
  };
324
329
 
325
330
  /* Knex Migration */
331
+ export type KnexError = {
332
+ code: string;
333
+ errno: number;
334
+ sql: string;
335
+ sqlMessage: string;
336
+ sqlState: string;
337
+ };
338
+ export function isKnexError(e: any): e is KnexError {
339
+ return e.code && e.sqlMessage && e.sqlState;
340
+ }
341
+
326
342
  export type KnexColumnType =
327
343
  | "string"
328
344
  | "text"
@@ -352,7 +368,6 @@ export type MigrationColumn = {
352
368
  export type MigrationIndex = {
353
369
  columns: string[];
354
370
  type: "unique" | "index";
355
- name?: string;
356
371
  };
357
372
  export type MigrationForeign = {
358
373
  columns: string[];