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.
- package/.pnp.cjs +11 -0
- package/dist/base-model-BzMJ2E_I.d.mts +43 -0
- package/dist/base-model-CWRKUX49.d.ts +43 -0
- package/dist/bin/cli.js +118 -89
- package/dist/bin/cli.js.map +1 -1
- package/dist/bin/cli.mjs +74 -45
- package/dist/bin/cli.mjs.map +1 -1
- package/dist/chunk-FLPD24HS.mjs +231 -0
- package/dist/chunk-FLPD24HS.mjs.map +1 -0
- package/dist/chunk-I2MMJRJN.mjs +1550 -0
- package/dist/chunk-I2MMJRJN.mjs.map +1 -0
- package/dist/{chunk-MPXE4IHO.mjs → chunk-PP2PSSAG.mjs} +5284 -5617
- package/dist/chunk-PP2PSSAG.mjs.map +1 -0
- package/dist/chunk-QK5XXJUX.mjs +280 -0
- package/dist/chunk-QK5XXJUX.mjs.map +1 -0
- package/dist/chunk-U636LQJJ.js +231 -0
- package/dist/chunk-U636LQJJ.js.map +1 -0
- package/dist/chunk-W7KDVJLQ.js +280 -0
- package/dist/chunk-W7KDVJLQ.js.map +1 -0
- package/dist/{chunk-YXILRRDT.js → chunk-XT6LHCX5.js} +5252 -5585
- package/dist/chunk-XT6LHCX5.js.map +1 -0
- package/dist/chunk-Z2P7XTXE.js +1550 -0
- package/dist/chunk-Z2P7XTXE.js.map +1 -0
- package/dist/database/drivers/knex/base-model.d.mts +16 -0
- package/dist/database/drivers/knex/base-model.d.ts +16 -0
- package/dist/database/drivers/knex/base-model.js +55 -0
- package/dist/database/drivers/knex/base-model.js.map +1 -0
- package/dist/database/drivers/knex/base-model.mjs +56 -0
- package/dist/database/drivers/knex/base-model.mjs.map +1 -0
- package/dist/database/drivers/kysely/base-model.d.mts +22 -0
- package/dist/database/drivers/kysely/base-model.d.ts +22 -0
- package/dist/database/drivers/kysely/base-model.js +64 -0
- package/dist/database/drivers/kysely/base-model.js.map +1 -0
- package/dist/database/drivers/kysely/base-model.mjs +65 -0
- package/dist/database/drivers/kysely/base-model.mjs.map +1 -0
- package/dist/index.d.mts +220 -926
- package/dist/index.d.ts +220 -926
- package/dist/index.js +13 -26
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +18 -31
- package/dist/index.mjs.map +1 -1
- package/dist/model-CAH_4oQh.d.mts +1042 -0
- package/dist/model-CAH_4oQh.d.ts +1042 -0
- package/import-to-require.js +27 -0
- package/package.json +23 -2
- package/src/api/caster.ts +6 -0
- package/src/api/code-converters.ts +3 -1
- package/src/api/sonamu.ts +41 -22
- package/src/bin/cli.ts +78 -46
- package/src/database/_batch_update.ts +16 -11
- package/src/database/base-model.abstract.ts +97 -0
- package/src/database/base-model.ts +214 -280
- package/src/database/code-generator.ts +72 -0
- package/src/database/db.abstract.ts +75 -0
- package/src/database/db.ts +21 -82
- package/src/database/drivers/knex/base-model.ts +55 -0
- package/src/database/drivers/knex/client.ts +209 -0
- package/src/database/drivers/knex/db.ts +227 -0
- package/src/database/drivers/knex/generator.ts +659 -0
- package/src/database/drivers/kysely/base-model.ts +89 -0
- package/src/database/drivers/kysely/client.ts +309 -0
- package/src/database/drivers/kysely/db.ts +238 -0
- package/src/database/drivers/kysely/generator.ts +714 -0
- package/src/database/types.ts +117 -0
- package/src/database/upsert-builder.ts +31 -18
- package/src/entity/entity-utils.ts +1 -1
- package/src/entity/migrator.ts +98 -693
- package/src/index.ts +1 -1
- package/src/syncer/syncer.ts +69 -27
- package/src/templates/generated_http.template.ts +14 -0
- package/src/templates/kysely_types.template.ts +205 -0
- package/src/templates/model.template.ts +2 -139
- package/src/templates/service.template.ts +3 -1
- package/src/testing/_relation-graph.ts +111 -0
- package/src/testing/fixture-manager.ts +216 -332
- package/src/types/types.ts +56 -6
- package/src/utils/utils.ts +56 -4
- package/src/utils/zod-error.ts +189 -0
- package/tsconfig.json +2 -2
- package/tsup.config.js +11 -10
- package/dist/chunk-MPXE4IHO.mjs.map +0 -1
- package/dist/chunk-YXILRRDT.js.map +0 -1
- /package/src/database/{knex-plugins → drivers/knex/plugins}/knex-on-duplicate-update.ts +0 -0
|
@@ -0,0 +1,659 @@
|
|
|
1
|
+
import _ from "lodash";
|
|
2
|
+
import prettier from "prettier";
|
|
3
|
+
import inflection from "inflection";
|
|
4
|
+
import equal from "fast-deep-equal";
|
|
5
|
+
import {
|
|
6
|
+
GenMigrationCode,
|
|
7
|
+
MigrationColumn,
|
|
8
|
+
MigrationForeign,
|
|
9
|
+
MigrationIndex,
|
|
10
|
+
} from "../../../types/types";
|
|
11
|
+
import { CodeGenerator } from "../../code-generator";
|
|
12
|
+
import { EntityManager } from "../../../entity/entity-manager";
|
|
13
|
+
|
|
14
|
+
export class KnexGenerator extends CodeGenerator {
|
|
15
|
+
async generateCreateCode_ColumnAndIndexes(
|
|
16
|
+
table: string,
|
|
17
|
+
columns: MigrationColumn[],
|
|
18
|
+
indexes: MigrationIndex[]
|
|
19
|
+
): Promise<GenMigrationCode> {
|
|
20
|
+
// 컬럼, 인덱스 처리
|
|
21
|
+
const lines: string[] = [
|
|
22
|
+
'import { Knex } from "knex";',
|
|
23
|
+
"",
|
|
24
|
+
"export async function up(knex: Knex): Promise<void> {",
|
|
25
|
+
`return knex.schema.createTable("${table}", (table) => {`,
|
|
26
|
+
"// columns",
|
|
27
|
+
...this.genColumnDefinitions(columns),
|
|
28
|
+
"",
|
|
29
|
+
"// indexes",
|
|
30
|
+
...this.genIndexDefinitions(indexes),
|
|
31
|
+
"});",
|
|
32
|
+
"}",
|
|
33
|
+
"",
|
|
34
|
+
"export async function down(knex: Knex): Promise<void> {",
|
|
35
|
+
` return knex.schema.dropTable("${table}");`,
|
|
36
|
+
"}",
|
|
37
|
+
];
|
|
38
|
+
return {
|
|
39
|
+
table,
|
|
40
|
+
type: "normal",
|
|
41
|
+
title: `create__${table}`,
|
|
42
|
+
formatted: await prettier.format(lines.join("\n"), {
|
|
43
|
+
parser: "typescript",
|
|
44
|
+
}),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/*
|
|
49
|
+
테이블 생성하는 케이스 - FK 생성
|
|
50
|
+
*/
|
|
51
|
+
async generateCreateCode_Foreign(
|
|
52
|
+
table: string,
|
|
53
|
+
foreigns: MigrationForeign[]
|
|
54
|
+
): Promise<GenMigrationCode[]> {
|
|
55
|
+
if (foreigns.length === 0) {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const { up, down } = this.genForeignDefinitions(table, foreigns);
|
|
60
|
+
if (up.length === 0 && down.length === 0) {
|
|
61
|
+
console.log("fk 가 뭔가 다릅니다");
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const lines: string[] = [
|
|
66
|
+
'import { Knex } from "knex";',
|
|
67
|
+
"",
|
|
68
|
+
"export async function up(knex: Knex): Promise<void> {",
|
|
69
|
+
`return knex.schema.alterTable("${table}", (table) => {`,
|
|
70
|
+
"// create fk",
|
|
71
|
+
...up,
|
|
72
|
+
"});",
|
|
73
|
+
"}",
|
|
74
|
+
"",
|
|
75
|
+
"export async function down(knex: Knex): Promise<void> {",
|
|
76
|
+
`return knex.schema.alterTable("${table}", (table) => {`,
|
|
77
|
+
"// drop fk",
|
|
78
|
+
...down,
|
|
79
|
+
"});",
|
|
80
|
+
"}",
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
const foreignKeysString = foreigns
|
|
84
|
+
.map((foreign) => foreign.columns.join("_"))
|
|
85
|
+
.join("_");
|
|
86
|
+
return [
|
|
87
|
+
{
|
|
88
|
+
table,
|
|
89
|
+
type: "foreign",
|
|
90
|
+
title: `foreign__${table}__${foreignKeysString}`,
|
|
91
|
+
formatted: await prettier.format(lines.join("\n"), {
|
|
92
|
+
parser: "typescript",
|
|
93
|
+
}),
|
|
94
|
+
},
|
|
95
|
+
];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async generateAlterCode_ColumnAndIndexes(
|
|
99
|
+
table: string,
|
|
100
|
+
entityColumns: MigrationColumn[],
|
|
101
|
+
entityIndexes: MigrationIndex[],
|
|
102
|
+
dbColumns: MigrationColumn[],
|
|
103
|
+
dbIndexes: MigrationIndex[]
|
|
104
|
+
): Promise<GenMigrationCode[]> {
|
|
105
|
+
/*
|
|
106
|
+
세부 비교 후 다른점 찾아서 코드 생성
|
|
107
|
+
|
|
108
|
+
1. 컬럼갯수 다름: MD에 있으나, DB에 없다면 추가
|
|
109
|
+
2. 컬럼갯수 다름: MD에 없으나, DB에 있다면 삭제
|
|
110
|
+
3. 그외 컬럼(컬럼 갯수가 동일하거나, 다른 경우 동일한 컬럼끼리) => alter
|
|
111
|
+
4. 다른거 다 동일하고 index만 변경되는 경우
|
|
112
|
+
|
|
113
|
+
** 컬럼명을 변경하는 경우는 따로 핸들링하지 않음
|
|
114
|
+
=> drop/add 형태의 마이그레이션 코드가 생성되는데, 수동으로 rename 코드로 수정하여 처리
|
|
115
|
+
*/
|
|
116
|
+
|
|
117
|
+
// 각 컬럼 이름 기준으로 add, drop, alter 여부 확인
|
|
118
|
+
const alterColumnsTo = this.getAlterColumnsTo(entityColumns, dbColumns);
|
|
119
|
+
|
|
120
|
+
// 추출된 컬럼들을 기준으로 각각 라인 생성
|
|
121
|
+
const alterColumnLinesTo = this.getAlterColumnLinesTo(
|
|
122
|
+
alterColumnsTo,
|
|
123
|
+
entityColumns
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
// 인덱스의 add, drop 여부 확인
|
|
127
|
+
const alterIndexesTo = this.getAlterIndexesTo(entityIndexes, dbIndexes);
|
|
128
|
+
|
|
129
|
+
// 추출된 인덱스들을 기준으로 각각 라인 생성
|
|
130
|
+
const alterIndexLinesTo = this.getAlterIndexLinesTo(
|
|
131
|
+
alterIndexesTo,
|
|
132
|
+
alterColumnsTo
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
const lines: string[] = [
|
|
136
|
+
'import { Knex } from "knex";',
|
|
137
|
+
"",
|
|
138
|
+
"export async function up(knex: Knex): Promise<void> {",
|
|
139
|
+
`return knex.schema.alterTable("${table}", (table) => {`,
|
|
140
|
+
...(alterColumnsTo.add.length > 0 ? alterColumnLinesTo.add.up : []),
|
|
141
|
+
...(alterColumnsTo.drop.length > 0 ? alterColumnLinesTo.drop.up : []),
|
|
142
|
+
...(alterColumnsTo.alter.length > 0 ? alterColumnLinesTo.alter.up : []),
|
|
143
|
+
...(alterIndexesTo.add.length > 0 ? alterIndexLinesTo.add.up : []),
|
|
144
|
+
...(alterIndexesTo.drop.length > 0 ? alterIndexLinesTo.drop.up : []),
|
|
145
|
+
"})",
|
|
146
|
+
"}",
|
|
147
|
+
"",
|
|
148
|
+
"export async function down(knex: Knex): Promise<void> {",
|
|
149
|
+
`return knex.schema.alterTable("${table}", (table) => {`,
|
|
150
|
+
...(alterColumnsTo.add.length > 0 ? alterColumnLinesTo.add.down : []),
|
|
151
|
+
...(alterColumnsTo.drop.length > 0 ? alterColumnLinesTo.drop.down : []),
|
|
152
|
+
...(alterColumnsTo.alter.length > 0 ? alterColumnLinesTo.alter.down : []),
|
|
153
|
+
...(alterIndexLinesTo.add.down.length > 0
|
|
154
|
+
? alterIndexLinesTo.add.down
|
|
155
|
+
: []),
|
|
156
|
+
...(alterIndexLinesTo.drop.down.length > 0
|
|
157
|
+
? alterIndexLinesTo.drop.down
|
|
158
|
+
: []),
|
|
159
|
+
"})",
|
|
160
|
+
"}",
|
|
161
|
+
];
|
|
162
|
+
|
|
163
|
+
const formatted = await prettier.format(lines.join("\n"), {
|
|
164
|
+
parser: "typescript",
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const title = [
|
|
168
|
+
"alter",
|
|
169
|
+
table,
|
|
170
|
+
...(["add", "drop", "alter"] as const)
|
|
171
|
+
.map((action) => {
|
|
172
|
+
const len = alterColumnsTo[action].length;
|
|
173
|
+
if (len > 0) {
|
|
174
|
+
return action + len;
|
|
175
|
+
}
|
|
176
|
+
return null;
|
|
177
|
+
})
|
|
178
|
+
.filter((part) => part !== null),
|
|
179
|
+
].join("_");
|
|
180
|
+
|
|
181
|
+
return [
|
|
182
|
+
{
|
|
183
|
+
table,
|
|
184
|
+
title,
|
|
185
|
+
formatted,
|
|
186
|
+
type: "normal",
|
|
187
|
+
},
|
|
188
|
+
];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async generateAlterCode_Foreigns(
|
|
192
|
+
table: string,
|
|
193
|
+
entityForeigns: MigrationForeign[],
|
|
194
|
+
dbForeigns: MigrationForeign[]
|
|
195
|
+
): Promise<GenMigrationCode[]> {
|
|
196
|
+
const getKey = (mf: MigrationForeign): string => {
|
|
197
|
+
return [mf.columns.join("-"), mf.to].join("///");
|
|
198
|
+
};
|
|
199
|
+
const fkTo = entityForeigns.reduce(
|
|
200
|
+
(result, entityF) => {
|
|
201
|
+
const matchingDbF = dbForeigns.find(
|
|
202
|
+
(dbF) => getKey(entityF) === getKey(dbF)
|
|
203
|
+
);
|
|
204
|
+
if (!matchingDbF) {
|
|
205
|
+
result.add.push(entityF);
|
|
206
|
+
return result;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (equal(entityF, matchingDbF) === false) {
|
|
210
|
+
result.alterSrc.push(matchingDbF);
|
|
211
|
+
result.alterDst.push(entityF);
|
|
212
|
+
return result;
|
|
213
|
+
}
|
|
214
|
+
return result;
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
add: [] as MigrationForeign[],
|
|
218
|
+
alterSrc: [] as MigrationForeign[],
|
|
219
|
+
alterDst: [] as MigrationForeign[],
|
|
220
|
+
}
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
const linesTo = {
|
|
224
|
+
add: this.genForeignDefinitions(table, fkTo.add),
|
|
225
|
+
alterSrc: this.genForeignDefinitions(table, fkTo.alterSrc),
|
|
226
|
+
alterDst: this.genForeignDefinitions(table, fkTo.alterDst),
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const lines: string[] = [
|
|
230
|
+
'import { Knex } from "knex";',
|
|
231
|
+
"",
|
|
232
|
+
"export async function up(knex: Knex): Promise<void> {",
|
|
233
|
+
`return knex.schema.alterTable("${table}", (table) => {`,
|
|
234
|
+
...linesTo.add.up,
|
|
235
|
+
...linesTo.alterSrc.down,
|
|
236
|
+
...linesTo.alterDst.up,
|
|
237
|
+
"})",
|
|
238
|
+
"}",
|
|
239
|
+
"",
|
|
240
|
+
"export async function down(knex: Knex): Promise<void> {",
|
|
241
|
+
`return knex.schema.alterTable("${table}", (table) => {`,
|
|
242
|
+
...linesTo.add.down,
|
|
243
|
+
...linesTo.alterDst.down,
|
|
244
|
+
...linesTo.alterSrc.up,
|
|
245
|
+
"})",
|
|
246
|
+
"}",
|
|
247
|
+
];
|
|
248
|
+
|
|
249
|
+
const formatted = await prettier.format(lines.join("\n"), {
|
|
250
|
+
parser: "typescript",
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
const title = [
|
|
254
|
+
"alter",
|
|
255
|
+
table,
|
|
256
|
+
"foreigns",
|
|
257
|
+
// TODO 바뀌는 부분
|
|
258
|
+
].join("_");
|
|
259
|
+
|
|
260
|
+
return [
|
|
261
|
+
{
|
|
262
|
+
table,
|
|
263
|
+
title,
|
|
264
|
+
formatted,
|
|
265
|
+
type: "normal",
|
|
266
|
+
},
|
|
267
|
+
];
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
generateModelTemplate(
|
|
271
|
+
entityId: string,
|
|
272
|
+
def: { orderBy: string; search: string }
|
|
273
|
+
) {
|
|
274
|
+
const names = EntityManager.getNamesFromId(entityId);
|
|
275
|
+
const entity = EntityManager.get(entityId);
|
|
276
|
+
|
|
277
|
+
return `
|
|
278
|
+
import { ListResult, asArray, NotFoundException, BadRequestException, api } from 'sonamu';
|
|
279
|
+
import { BaseModelClass } from 'sonamu/knex';
|
|
280
|
+
import {
|
|
281
|
+
${entityId}SubsetKey,
|
|
282
|
+
${entityId}SubsetMapping,
|
|
283
|
+
} from "../sonamu.generated";
|
|
284
|
+
import {
|
|
285
|
+
${names.camel}SubsetQueries,
|
|
286
|
+
} from "../sonamu.generated.sso";
|
|
287
|
+
import { ${entityId}ListParams, ${entityId}SaveParams } from "./${names.fs}.types";
|
|
288
|
+
|
|
289
|
+
/*
|
|
290
|
+
${entityId} Model
|
|
291
|
+
*/
|
|
292
|
+
class ${entityId}ModelClass extends BaseModelClass {
|
|
293
|
+
modelName = "${entityId}";
|
|
294
|
+
|
|
295
|
+
@api({ httpMethod: "GET", clients: ["axios", "swr"], resourceName: "${entityId}" })
|
|
296
|
+
async findById<T extends ${entityId}SubsetKey>(
|
|
297
|
+
subset: T,
|
|
298
|
+
id: number
|
|
299
|
+
): Promise<${entityId}SubsetMapping[T]> {
|
|
300
|
+
const { rows } = await this.findMany(subset, {
|
|
301
|
+
id,
|
|
302
|
+
num: 1,
|
|
303
|
+
page: 1,
|
|
304
|
+
});
|
|
305
|
+
if (rows.length == 0) {
|
|
306
|
+
throw new NotFoundException(\`존재하지 않는 ${names.capital} ID \${id}\`);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return rows[0];
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async findOne<T extends ${entityId}SubsetKey>(
|
|
313
|
+
subset: T,
|
|
314
|
+
listParams: ${entityId}ListParams
|
|
315
|
+
): Promise<${entityId}SubsetMapping[T] | null> {
|
|
316
|
+
const { rows } = await this.findMany(subset, {
|
|
317
|
+
...listParams,
|
|
318
|
+
num: 1,
|
|
319
|
+
page: 1,
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
return rows[0] ?? null;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
@api({ httpMethod: "GET", clients: ["axios", "swr"], resourceName: "${names.capitalPlural}" })
|
|
326
|
+
async findMany<T extends ${entityId}SubsetKey>(
|
|
327
|
+
subset: T,
|
|
328
|
+
params: ${entityId}ListParams = {}
|
|
329
|
+
): Promise<ListResult<${entityId}SubsetMapping[T]>> {
|
|
330
|
+
// params with defaults
|
|
331
|
+
params = {
|
|
332
|
+
num: 24,
|
|
333
|
+
page: 1,
|
|
334
|
+
search: "${def.search}",
|
|
335
|
+
orderBy: "${def.orderBy}",
|
|
336
|
+
...params,
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
// build queries
|
|
340
|
+
let { rows, total } = await this.runSubsetQuery({
|
|
341
|
+
subset,
|
|
342
|
+
params,
|
|
343
|
+
subsetQuery: ${names.camel}SubsetQueries[subset],
|
|
344
|
+
build: ({ qb }) => {
|
|
345
|
+
// id
|
|
346
|
+
if (params.id) {
|
|
347
|
+
qb.whereIn("${entity.table}.id", asArray(params.id));
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// search-keyword
|
|
351
|
+
if (params.search && params.keyword && params.keyword.length > 0) {
|
|
352
|
+
if (params.search === "id") {
|
|
353
|
+
qb.where("${entity.table}.id", params.keyword);
|
|
354
|
+
}
|
|
355
|
+
// } else if (params.search === "field") {
|
|
356
|
+
// qb.where("${entity.table}.field", "like", \`%\${params.keyword}%\`);
|
|
357
|
+
// }
|
|
358
|
+
else {
|
|
359
|
+
throw new BadRequestException(
|
|
360
|
+
\`구현되지 않은 검색 필드 \${params.search}\`
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// orderBy
|
|
366
|
+
if (params.orderBy) {
|
|
367
|
+
// default orderBy
|
|
368
|
+
const [orderByField, orderByDirec] = params.orderBy.split("-");
|
|
369
|
+
qb.orderBy("${entity.table}." + orderByField, orderByDirec);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return qb;
|
|
373
|
+
},
|
|
374
|
+
debug: false,
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
return {
|
|
378
|
+
rows,
|
|
379
|
+
total,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
@api({ httpMethod: "POST" })
|
|
384
|
+
async save(
|
|
385
|
+
spa: ${entityId}SaveParams[]
|
|
386
|
+
): Promise<number[]> {
|
|
387
|
+
const wdb = this.getDB("w");
|
|
388
|
+
const ub = this.getUpsertBuilder();
|
|
389
|
+
|
|
390
|
+
// register
|
|
391
|
+
spa.map((sp) => {
|
|
392
|
+
ub.register("${entity.table}", sp);
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
// transaction
|
|
396
|
+
return wdb.transaction(async (trx) => {
|
|
397
|
+
const ids = await ub.upsert(trx, "${entity.table}");
|
|
398
|
+
|
|
399
|
+
return ids;
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
@api({ httpMethod: "POST", guards: [ "admin" ] })
|
|
404
|
+
async del(ids: number[]): Promise<number> {
|
|
405
|
+
const wdb = this.getDB("w");
|
|
406
|
+
|
|
407
|
+
// transaction
|
|
408
|
+
await wdb.transaction(async (trx) => {
|
|
409
|
+
return trx("${entity.table}").whereIn("${entity.table}.id", ids).delete();
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
return ids.length;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
export const ${entityId}Model = new ${entityId}ModelClass();
|
|
417
|
+
`.trim();
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/*
|
|
421
|
+
MigrationColumn[] 읽어서 컬럼 정의하는 구문 생성
|
|
422
|
+
*/
|
|
423
|
+
private genColumnDefinitions(columns: MigrationColumn[]): string[] {
|
|
424
|
+
return columns.map((column) => {
|
|
425
|
+
const chains: string[] = [];
|
|
426
|
+
if (column.name === "id") {
|
|
427
|
+
return `table.increments().primary();`;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// FIXME: float(M,D) deprecated -> decimal(M,D) 이용하도록 하고, float/double 처리 추가
|
|
431
|
+
if (column.type === "float" || column.type === "decimal") {
|
|
432
|
+
chains.push(
|
|
433
|
+
`${column.type}('${column.name}', ${column.precision}, ${column.scale})`
|
|
434
|
+
);
|
|
435
|
+
} else {
|
|
436
|
+
let columnType = column.type;
|
|
437
|
+
let extraType: string | undefined;
|
|
438
|
+
if (columnType.includes("text") && columnType !== "text") {
|
|
439
|
+
extraType = columnType;
|
|
440
|
+
columnType = "text";
|
|
441
|
+
}
|
|
442
|
+
chains.push(
|
|
443
|
+
`${column.type}('${column.name}'${
|
|
444
|
+
column.length ? `, ${column.length}` : ""
|
|
445
|
+
}${extraType ? `, '${extraType}'` : ""})`
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
if (column.unsigned) {
|
|
449
|
+
chains.push("unsigned()");
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
chains.push(column.nullable ? "nullable()" : "notNullable()");
|
|
453
|
+
|
|
454
|
+
if (column.defaultTo !== undefined) {
|
|
455
|
+
if (
|
|
456
|
+
typeof column.defaultTo === "string" &&
|
|
457
|
+
column.defaultTo.startsWith(`"`)
|
|
458
|
+
) {
|
|
459
|
+
chains.push(`defaultTo(${column.defaultTo})`);
|
|
460
|
+
} else {
|
|
461
|
+
chains.push(`defaultTo(knex.raw('${column.defaultTo}'))`);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
return `table.${chains.join(".")};`;
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/*
|
|
470
|
+
MigrationIndex[] 읽어서 인덱스/유니크 정의하는 구문 생성
|
|
471
|
+
*/
|
|
472
|
+
private genIndexDefinitions(indexes: MigrationIndex[]): string[] {
|
|
473
|
+
if (indexes.length === 0) {
|
|
474
|
+
return [];
|
|
475
|
+
}
|
|
476
|
+
const lines = _.uniq(
|
|
477
|
+
indexes.reduce((r, index) => {
|
|
478
|
+
r.push(
|
|
479
|
+
`table.${index.type}([${index.columns
|
|
480
|
+
.map((col) => `'${col}'`)
|
|
481
|
+
.join(",")}])`
|
|
482
|
+
);
|
|
483
|
+
return r;
|
|
484
|
+
}, [] as string[])
|
|
485
|
+
);
|
|
486
|
+
return lines;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
private getAlterColumnLinesTo(
|
|
490
|
+
columnsTo: ReturnType<KnexGenerator["getAlterColumnsTo"]>,
|
|
491
|
+
entityColumns: MigrationColumn[]
|
|
492
|
+
) {
|
|
493
|
+
let linesTo = {
|
|
494
|
+
add: {
|
|
495
|
+
up: [] as string[],
|
|
496
|
+
down: [] as string[],
|
|
497
|
+
},
|
|
498
|
+
drop: {
|
|
499
|
+
up: [] as string[],
|
|
500
|
+
down: [] as string[],
|
|
501
|
+
},
|
|
502
|
+
alter: {
|
|
503
|
+
up: [] as string[],
|
|
504
|
+
down: [] as string[],
|
|
505
|
+
},
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
linesTo.add = {
|
|
509
|
+
up: ["// add", ...this.genColumnDefinitions(columnsTo.add)],
|
|
510
|
+
down: [
|
|
511
|
+
"// rollback - add",
|
|
512
|
+
`table.dropColumns(${columnsTo.add
|
|
513
|
+
.map((col) => `'${col.name}'`)
|
|
514
|
+
.join(", ")})`,
|
|
515
|
+
],
|
|
516
|
+
};
|
|
517
|
+
linesTo.drop = {
|
|
518
|
+
up: [
|
|
519
|
+
"// drop",
|
|
520
|
+
`table.dropColumns(${columnsTo.drop
|
|
521
|
+
.map((col) => `'${col.name}'`)
|
|
522
|
+
.join(", ")})`,
|
|
523
|
+
],
|
|
524
|
+
down: [
|
|
525
|
+
"// rollback - drop",
|
|
526
|
+
...this.genColumnDefinitions(columnsTo.drop),
|
|
527
|
+
],
|
|
528
|
+
};
|
|
529
|
+
linesTo.alter = columnsTo.alter.reduce(
|
|
530
|
+
(r, dbColumn) => {
|
|
531
|
+
const entityColumn = entityColumns.find(
|
|
532
|
+
(col) => col.name == dbColumn.name
|
|
533
|
+
);
|
|
534
|
+
if (entityColumn === undefined) {
|
|
535
|
+
return r;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// 컬럼 변경사항
|
|
539
|
+
const columnDiffUp = _.difference(
|
|
540
|
+
this.genColumnDefinitions([entityColumn]),
|
|
541
|
+
this.genColumnDefinitions([dbColumn])
|
|
542
|
+
);
|
|
543
|
+
const columnDiffDown = _.difference(
|
|
544
|
+
this.genColumnDefinitions([dbColumn]),
|
|
545
|
+
this.genColumnDefinitions([entityColumn])
|
|
546
|
+
);
|
|
547
|
+
if (columnDiffUp.length > 0) {
|
|
548
|
+
r.up = [
|
|
549
|
+
...r.up,
|
|
550
|
+
"// alter column",
|
|
551
|
+
...columnDiffUp.map((l) => l.replace(";", "") + ".alter();"),
|
|
552
|
+
];
|
|
553
|
+
r.down = [
|
|
554
|
+
...r.down,
|
|
555
|
+
"// rollback - alter column",
|
|
556
|
+
...columnDiffDown.map((l) => l.replace(";", "") + ".alter();"),
|
|
557
|
+
];
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
return r;
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
up: [] as string[],
|
|
564
|
+
down: [] as string[],
|
|
565
|
+
}
|
|
566
|
+
);
|
|
567
|
+
|
|
568
|
+
return linesTo;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
private getAlterIndexLinesTo(
|
|
572
|
+
indexesTo: ReturnType<KnexGenerator["getAlterIndexesTo"]>,
|
|
573
|
+
columnsTo: ReturnType<KnexGenerator["getAlterColumnsTo"]>
|
|
574
|
+
) {
|
|
575
|
+
let linesTo = {
|
|
576
|
+
add: {
|
|
577
|
+
up: [] as string[],
|
|
578
|
+
down: [] as string[],
|
|
579
|
+
},
|
|
580
|
+
drop: {
|
|
581
|
+
up: [] as string[],
|
|
582
|
+
down: [] as string[],
|
|
583
|
+
},
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
// 인덱스가 추가되는 경우, 컬럼과 같이 추가된 케이스에는 drop에서 제외해야함!
|
|
587
|
+
linesTo.add = {
|
|
588
|
+
up: ["// add indexes", ...this.genIndexDefinitions(indexesTo.add)],
|
|
589
|
+
down: [
|
|
590
|
+
"// rollback - add indexes",
|
|
591
|
+
...indexesTo.add
|
|
592
|
+
.filter(
|
|
593
|
+
(index) =>
|
|
594
|
+
index.columns.every((colName) =>
|
|
595
|
+
columnsTo.add.map((col) => col.name).includes(colName)
|
|
596
|
+
) === false
|
|
597
|
+
)
|
|
598
|
+
.map(
|
|
599
|
+
(index) =>
|
|
600
|
+
`table.drop${inflection.capitalize(index.type)}([${index.columns
|
|
601
|
+
.map((columnName) => `'${columnName}'`)
|
|
602
|
+
.join(",")}])`
|
|
603
|
+
),
|
|
604
|
+
],
|
|
605
|
+
};
|
|
606
|
+
// 인덱스가 삭제되는 경우, 컬럼과 같이 삭제된 케이스에는 drop에서 제외해야함!
|
|
607
|
+
linesTo.drop = {
|
|
608
|
+
up: [
|
|
609
|
+
...indexesTo.drop
|
|
610
|
+
.filter(
|
|
611
|
+
(index) =>
|
|
612
|
+
index.columns.every((colName) =>
|
|
613
|
+
columnsTo.drop.map((col) => col.name).includes(colName)
|
|
614
|
+
) === false
|
|
615
|
+
)
|
|
616
|
+
.map(
|
|
617
|
+
(index) =>
|
|
618
|
+
`table.drop${inflection.capitalize(index.type)}([${index.columns
|
|
619
|
+
.map((columnName) => `'${columnName}'`)
|
|
620
|
+
.join(",")}])`
|
|
621
|
+
),
|
|
622
|
+
],
|
|
623
|
+
down: [
|
|
624
|
+
"// rollback - drop indexes",
|
|
625
|
+
...this.genIndexDefinitions(indexesTo.drop),
|
|
626
|
+
],
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
return linesTo;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/*
|
|
633
|
+
MigrationForeign[] 읽어서 외부키 constraint 정의하는 구문 생성
|
|
634
|
+
*/
|
|
635
|
+
private genForeignDefinitions(
|
|
636
|
+
table: string,
|
|
637
|
+
foreigns: MigrationForeign[]
|
|
638
|
+
): { up: string[]; down: string[] } {
|
|
639
|
+
return foreigns.reduce(
|
|
640
|
+
(r, foreign) => {
|
|
641
|
+
const columnsStringQuote = foreign.columns
|
|
642
|
+
.map((col) => `'${col.replace(`${table}.`, "")}'`)
|
|
643
|
+
.join(",");
|
|
644
|
+
r.up.push(
|
|
645
|
+
`table.foreign('${foreign.columns.join(",")}')
|
|
646
|
+
.references('${foreign.to}')
|
|
647
|
+
.onUpdate('${foreign.onUpdate}')
|
|
648
|
+
.onDelete('${foreign.onDelete}')`
|
|
649
|
+
);
|
|
650
|
+
r.down.push(`table.dropForeign([${columnsStringQuote}])`);
|
|
651
|
+
return r;
|
|
652
|
+
},
|
|
653
|
+
{
|
|
654
|
+
up: [] as string[],
|
|
655
|
+
down: [] as string[],
|
|
656
|
+
}
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
}
|