sonamu 0.2.54 → 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-L4KELCY7.mjs → chunk-PP2PSSAG.mjs} +5241 -5571
- 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-JOHF7PK4.js → chunk-XT6LHCX5.js} +5292 -5622
- 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 +222 -928
- package/dist/index.d.ts +222 -928
- 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-manager.ts +9 -5
- package/src/entity/entity-utils.ts +1 -1
- package/src/entity/entity.ts +9 -13
- package/src/entity/migrator.ts +98 -693
- package/src/index.ts +1 -1
- package/src/syncer/syncer.ts +103 -56
- 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-JOHF7PK4.js.map +0 -1
- package/dist/chunk-L4KELCY7.mjs.map +0 -1
- /package/src/database/{knex-plugins → drivers/knex/plugins}/knex-on-duplicate-update.ts +0 -0
package/src/index.ts
CHANGED
|
@@ -2,9 +2,9 @@ export * from "./api/code-converters";
|
|
|
2
2
|
export * from "./api/context";
|
|
3
3
|
export * from "./api/decorators";
|
|
4
4
|
export * from "./api/sonamu";
|
|
5
|
-
export * from "./database/base-model";
|
|
6
5
|
export * from "./database/db";
|
|
7
6
|
export * from "./database/upsert-builder";
|
|
7
|
+
export * from "./database/types";
|
|
8
8
|
export * from "./exceptions/error-handler";
|
|
9
9
|
export * from "./exceptions/so-exceptions";
|
|
10
10
|
export * from "./entity/entity";
|
package/src/syncer/syncer.ts
CHANGED
|
@@ -75,6 +75,9 @@ import { Template__generated_http } from "../templates/generated_http.template";
|
|
|
75
75
|
import { Sonamu } from "../api/sonamu";
|
|
76
76
|
import { execSync } from "child_process";
|
|
77
77
|
import { Template__generated_sso } from "../templates/generated_sso.template";
|
|
78
|
+
import { Template__kysely_interface } from "../templates/kysely_types.template";
|
|
79
|
+
import { DB } from "../database/db";
|
|
80
|
+
import { setTimeout as setTimeoutPromises } from "timers/promises";
|
|
78
81
|
|
|
79
82
|
type FileType = "model" | "types" | "functions" | "generated" | "entity";
|
|
80
83
|
type GlobPattern = {
|
|
@@ -111,6 +114,7 @@ export class Syncer {
|
|
|
111
114
|
}[] = [];
|
|
112
115
|
types: { [typeName: string]: z.ZodObject<any> } = {};
|
|
113
116
|
models: { [modelName: string]: unknown } = {};
|
|
117
|
+
isSyncing: boolean = false;
|
|
114
118
|
|
|
115
119
|
get checksumsPath(): string {
|
|
116
120
|
return path.join(Sonamu.apiRootPath, "/.so-checksum");
|
|
@@ -167,6 +171,23 @@ export class Syncer {
|
|
|
167
171
|
return;
|
|
168
172
|
}
|
|
169
173
|
|
|
174
|
+
const abc = new AbortController();
|
|
175
|
+
this.isSyncing = true;
|
|
176
|
+
const onSIGUSR2 = async () => {
|
|
177
|
+
if (this.isSyncing === false) {
|
|
178
|
+
process.exit(0);
|
|
179
|
+
}
|
|
180
|
+
console.log(chalk.magentaBright(`wait for syncing done....`));
|
|
181
|
+
|
|
182
|
+
// 싱크 완료 대기
|
|
183
|
+
try {
|
|
184
|
+
await setTimeoutPromises(20000, "waiting-sync", { signal: abc.signal });
|
|
185
|
+
} catch {}
|
|
186
|
+
console.log(chalk.magentaBright(`Syncing DONE!`));
|
|
187
|
+
process.exit(0);
|
|
188
|
+
};
|
|
189
|
+
process.on("SIGUSR2", onSIGUSR2);
|
|
190
|
+
|
|
170
191
|
// 변경된 파일 찾기
|
|
171
192
|
const diff = _.differenceWith(
|
|
172
193
|
currentChecksums,
|
|
@@ -193,6 +214,18 @@ export class Syncer {
|
|
|
193
214
|
console.log("// 액션: 스키마 생성");
|
|
194
215
|
await this.actionGenerateSchemas();
|
|
195
216
|
|
|
217
|
+
if (
|
|
218
|
+
DB.baseConfig?.client === "kysely" &&
|
|
219
|
+
DB.baseConfig.types?.enabled !== false
|
|
220
|
+
) {
|
|
221
|
+
console.log("// 액션: kysely 인터페이스 생성");
|
|
222
|
+
await this.generateTemplate(
|
|
223
|
+
"kysely_interface",
|
|
224
|
+
{},
|
|
225
|
+
{ overwrite: true }
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
196
229
|
// generated 싱크까지 동시에 처리 후 체크섬 갱신
|
|
197
230
|
diffGroups["generated"] = _.uniq([
|
|
198
231
|
...(diffGroups["generated"] ?? []),
|
|
@@ -232,6 +265,11 @@ export class Syncer {
|
|
|
232
265
|
|
|
233
266
|
// 저장
|
|
234
267
|
await this.saveChecksums(currentChecksums);
|
|
268
|
+
|
|
269
|
+
// 싱크 종료
|
|
270
|
+
this.isSyncing = false;
|
|
271
|
+
abc.abort();
|
|
272
|
+
process.off("SIGUSR2", onSIGUSR2);
|
|
235
273
|
}
|
|
236
274
|
|
|
237
275
|
getEntityIdFromPath(filePaths: string[]): string[] {
|
|
@@ -275,23 +313,12 @@ export class Syncer {
|
|
|
275
313
|
}
|
|
276
314
|
|
|
277
315
|
async actionGenerateHttps(entityIds: string[]): Promise<string[]> {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
entityId,
|
|
285
|
-
},
|
|
286
|
-
{
|
|
287
|
-
overwrite: true,
|
|
288
|
-
}
|
|
289
|
-
)
|
|
290
|
-
)
|
|
291
|
-
)
|
|
292
|
-
)
|
|
293
|
-
.flat()
|
|
294
|
-
.flat();
|
|
316
|
+
const [res] = await this.generateTemplate(
|
|
317
|
+
"generated_http",
|
|
318
|
+
{ entityId: entityIds[0] },
|
|
319
|
+
{ overwrite: true }
|
|
320
|
+
);
|
|
321
|
+
return res;
|
|
295
322
|
}
|
|
296
323
|
|
|
297
324
|
async copyFileWithReplaceCoreToShared(fromPath: string, toPath: string) {
|
|
@@ -397,7 +424,7 @@ export class Syncer {
|
|
|
397
424
|
await fs.writeJSON(this.checksumsPath, checksums, {
|
|
398
425
|
spaces: 2,
|
|
399
426
|
});
|
|
400
|
-
console.
|
|
427
|
+
console.log("checksum saved", this.checksumsPath);
|
|
401
428
|
}
|
|
402
429
|
|
|
403
430
|
async getChecksumOfFile(filePath: string): Promise<string> {
|
|
@@ -658,15 +685,26 @@ export class Syncer {
|
|
|
658
685
|
const type = this.resolveTypeNode(paramDec.type);
|
|
659
686
|
|
|
660
687
|
if (name === undefined) {
|
|
661
|
-
console.
|
|
688
|
+
console.debug({ name, type, paramDec });
|
|
662
689
|
}
|
|
663
690
|
|
|
664
|
-
|
|
691
|
+
const result: ApiParam = {
|
|
665
692
|
name: name.escapedText ? name.escapedText.toString() : `nonameAt${index}`,
|
|
666
693
|
type,
|
|
667
694
|
optional: paramDec.optional === true,
|
|
668
695
|
defaultDef: paramDec?.defaultDef,
|
|
669
696
|
};
|
|
697
|
+
|
|
698
|
+
// 구조분해할당의 경우 타입이름 사용
|
|
699
|
+
if (
|
|
700
|
+
ts.isObjectBindingPattern(name) &&
|
|
701
|
+
ts.isTypeReferenceNode(paramDec.type) &&
|
|
702
|
+
ts.isIdentifier(paramDec.type.typeName)
|
|
703
|
+
) {
|
|
704
|
+
result.name = inflection.camelize(paramDec.type.typeName.text, true);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
return result;
|
|
670
708
|
};
|
|
671
709
|
|
|
672
710
|
printNode(
|
|
@@ -787,6 +825,8 @@ export class Syncer {
|
|
|
787
825
|
return new Template__view_enums_dropdown();
|
|
788
826
|
} else if (key === "view_enums_buttonset") {
|
|
789
827
|
return new Template__view_enums_buttonset();
|
|
828
|
+
} else if (key === "kysely_interface") {
|
|
829
|
+
return new Template__kysely_interface();
|
|
790
830
|
} else {
|
|
791
831
|
throw new BadRequestException(`잘못된 템플릿 키 ${key}`);
|
|
792
832
|
}
|
|
@@ -1023,32 +1063,38 @@ export class Syncer {
|
|
|
1023
1063
|
(name) => name !== names.constant
|
|
1024
1064
|
);
|
|
1025
1065
|
|
|
1026
|
-
return keys.reduce(
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1066
|
+
return keys.reduce(
|
|
1067
|
+
(result, key) => {
|
|
1068
|
+
const tpl = this.getTemplate(key);
|
|
1069
|
+
if (key.startsWith("view_enums")) {
|
|
1070
|
+
enumsKeys.map((componentId) => {
|
|
1071
|
+
const { target, path: p } = tpl.getTargetAndPath(
|
|
1072
|
+
names,
|
|
1073
|
+
componentId
|
|
1074
|
+
);
|
|
1075
|
+
result[`${key}__${componentId}`] = fs.existsSync(
|
|
1076
|
+
path.join(Sonamu.appRootPath, target, p)
|
|
1077
|
+
);
|
|
1078
|
+
});
|
|
1079
|
+
return result;
|
|
1080
|
+
}
|
|
1037
1081
|
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1082
|
+
const { target, path: p } = tpl.getTargetAndPath(names);
|
|
1083
|
+
const { targets } = Sonamu.config.sync;
|
|
1084
|
+
if (target.includes(":target")) {
|
|
1085
|
+
targets.map((t) => {
|
|
1086
|
+
result[`${key}__${t}`] = fs.existsSync(
|
|
1087
|
+
path.join(Sonamu.appRootPath, target.replace(":target", t), p)
|
|
1088
|
+
);
|
|
1089
|
+
});
|
|
1090
|
+
} else {
|
|
1091
|
+
result[key] = fs.existsSync(path.join(Sonamu.appRootPath, target, p));
|
|
1092
|
+
}
|
|
1049
1093
|
|
|
1050
|
-
|
|
1051
|
-
|
|
1094
|
+
return result;
|
|
1095
|
+
},
|
|
1096
|
+
{} as Record<`${TemplateKey}${string}`, boolean>
|
|
1097
|
+
);
|
|
1052
1098
|
}
|
|
1053
1099
|
|
|
1054
1100
|
async getZodTypeById(zodTypeId: string): Promise<z.ZodTypeAny> {
|
|
@@ -1093,9 +1139,8 @@ export class Syncer {
|
|
|
1093
1139
|
const obj = await propNode.children.reduce(
|
|
1094
1140
|
async (promise, childPropNode) => {
|
|
1095
1141
|
const result = await promise;
|
|
1096
|
-
result[childPropNode.prop!.name] =
|
|
1097
|
-
childPropNode
|
|
1098
|
-
);
|
|
1142
|
+
result[childPropNode.prop!.name] =
|
|
1143
|
+
await this.propNodeToZodType(childPropNode);
|
|
1099
1144
|
return result;
|
|
1100
1145
|
},
|
|
1101
1146
|
{} as any
|
|
@@ -1335,15 +1380,17 @@ export class Syncer {
|
|
|
1335
1380
|
// reload entities
|
|
1336
1381
|
await EntityManager.reload();
|
|
1337
1382
|
|
|
1338
|
-
// generate schemas
|
|
1339
|
-
await
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1383
|
+
// generate schemas, types
|
|
1384
|
+
await Promise.all([
|
|
1385
|
+
this.actionGenerateSchemas(),
|
|
1386
|
+
...(form.entityId === undefined
|
|
1387
|
+
? [
|
|
1388
|
+
this.generateTemplate("init_types", {
|
|
1389
|
+
entityId: form.entityId,
|
|
1390
|
+
}),
|
|
1391
|
+
]
|
|
1392
|
+
: []),
|
|
1393
|
+
]);
|
|
1347
1394
|
}
|
|
1348
1395
|
|
|
1349
1396
|
async delEntity(entityId: string): Promise<{ delPaths: string[] }> {
|
|
@@ -112,6 +112,20 @@ export class Template__generated_http extends Template {
|
|
|
112
112
|
return zodType._def.items.map((item: any) =>
|
|
113
113
|
this.zodTypeToReqDefault(item, name)
|
|
114
114
|
);
|
|
115
|
+
} else if (zodType instanceof z.ZodDate) {
|
|
116
|
+
return "2000-01-01";
|
|
117
|
+
} else if (zodType instanceof z.ZodLiteral) {
|
|
118
|
+
return zodType.value;
|
|
119
|
+
} else if (zodType instanceof z.ZodEffects) {
|
|
120
|
+
return this.zodTypeToReqDefault(zodType._def.schema, name);
|
|
121
|
+
} else if (zodType instanceof z.ZodRecord || zodType instanceof z.ZodMap) {
|
|
122
|
+
const key = this.zodTypeToReqDefault(zodType._def.keyType, name) as any;
|
|
123
|
+
const value = this.zodTypeToReqDefault(zodType._def.valueType, name);
|
|
124
|
+
return { [key]: value };
|
|
125
|
+
} else if (zodType instanceof z.ZodSet) {
|
|
126
|
+
return [this.zodTypeToReqDefault(zodType._def.valueType, name)];
|
|
127
|
+
} else if (zodType instanceof z.ZodIntersection) {
|
|
128
|
+
return this.zodTypeToReqDefault(zodType._def.right, name);
|
|
115
129
|
} else {
|
|
116
130
|
// console.log(zodType);
|
|
117
131
|
return `unknown-${zodType._type}`;
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EntityProp,
|
|
3
|
+
isBelongsToOneRelationProp,
|
|
4
|
+
isBigIntegerProp,
|
|
5
|
+
isBooleanProp,
|
|
6
|
+
isDateProp,
|
|
7
|
+
isDateTimeProp,
|
|
8
|
+
isDecimalProp,
|
|
9
|
+
isDoubleProp,
|
|
10
|
+
isEnumProp,
|
|
11
|
+
isFloatProp,
|
|
12
|
+
isIntegerProp,
|
|
13
|
+
isJsonProp,
|
|
14
|
+
isRelationProp,
|
|
15
|
+
isStringProp,
|
|
16
|
+
isTextProp,
|
|
17
|
+
isTimeProp,
|
|
18
|
+
isTimestampProp,
|
|
19
|
+
isUuidProp,
|
|
20
|
+
isVirtualProp,
|
|
21
|
+
} from "../types/types";
|
|
22
|
+
import { EntityManager } from "../entity/entity-manager";
|
|
23
|
+
import { Template } from "./base-template";
|
|
24
|
+
import { SourceCode } from "./generated.template";
|
|
25
|
+
import _ from "lodash";
|
|
26
|
+
import { nonNullable } from "../utils/utils";
|
|
27
|
+
import { Sonamu } from "../api";
|
|
28
|
+
import { Entity } from "../entity/entity";
|
|
29
|
+
import inflection from "inflection";
|
|
30
|
+
import { DB } from "../database/db";
|
|
31
|
+
import { KyselyBaseConfig } from "../database/types";
|
|
32
|
+
|
|
33
|
+
export class Template__kysely_interface extends Template {
|
|
34
|
+
constructor() {
|
|
35
|
+
super("kysely_interface");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
getTargetAndPath() {
|
|
39
|
+
const { dir } = Sonamu.config.api;
|
|
40
|
+
const { types } = DB.baseConfig as KyselyBaseConfig;
|
|
41
|
+
const outDir = types?.outDir ?? "src/typings";
|
|
42
|
+
const fileName = types?.fileName ?? "database.types.ts";
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
target: `${dir}/${outDir}`,
|
|
46
|
+
path: fileName,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
render() {
|
|
51
|
+
const entityIds = EntityManager.getAllIds();
|
|
52
|
+
const entities = entityIds.map((id) => EntityManager.get(id));
|
|
53
|
+
const enums = _.merge({}, ...entities.map((e) => e.enums));
|
|
54
|
+
|
|
55
|
+
const manyToManyTables = _.uniq(
|
|
56
|
+
entities.flatMap((e) =>
|
|
57
|
+
e.props
|
|
58
|
+
.map((p) => {
|
|
59
|
+
if (isRelationProp(p) && p.relationType === "ManyToMany") {
|
|
60
|
+
return p.joinTable;
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
})
|
|
64
|
+
.filter(nonNullable)
|
|
65
|
+
)
|
|
66
|
+
).map((table) => {
|
|
67
|
+
const [fromTable, toTable] = table.split("__");
|
|
68
|
+
return {
|
|
69
|
+
table,
|
|
70
|
+
fromTable,
|
|
71
|
+
toTable,
|
|
72
|
+
interfaceName: `${inflection.classify(fromTable)}${inflection.classify(toTable)}Table`,
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const sourceCodes: Omit<SourceCode, "label">[] = entities.map((entity) => {
|
|
77
|
+
const columns = entity.props.map((prop) =>
|
|
78
|
+
this.resolveColumn(prop, enums)
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
lines: [
|
|
83
|
+
`interface ${entity.id}Table {
|
|
84
|
+
${columns.join("\n")}
|
|
85
|
+
}`,
|
|
86
|
+
"",
|
|
87
|
+
],
|
|
88
|
+
importKeys: [],
|
|
89
|
+
};
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
sourceCodes.push(
|
|
93
|
+
...manyToManyTables.map(({ fromTable, toTable, interfaceName }) => {
|
|
94
|
+
return {
|
|
95
|
+
lines: [
|
|
96
|
+
`interface ${interfaceName} {
|
|
97
|
+
id: number;
|
|
98
|
+
${inflection.singularize(fromTable)}_id: number;
|
|
99
|
+
${inflection.singularize(toTable)}_id: number;
|
|
100
|
+
}`,
|
|
101
|
+
"",
|
|
102
|
+
],
|
|
103
|
+
importKeys: [],
|
|
104
|
+
};
|
|
105
|
+
})
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const sourceCode = sourceCodes.reduce(
|
|
109
|
+
(result, ts) => {
|
|
110
|
+
if (ts === null) {
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
lines: [...result!.lines, ...ts.lines, ""],
|
|
115
|
+
importKeys: _.uniq([...result!.importKeys, ...ts.importKeys]),
|
|
116
|
+
};
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
lines: [],
|
|
120
|
+
importKeys: [],
|
|
121
|
+
} as Omit<SourceCode, "label">
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
...this.getTargetAndPath(),
|
|
126
|
+
body: sourceCode.lines.join("\n"),
|
|
127
|
+
importKeys: sourceCode.importKeys,
|
|
128
|
+
customHeaders: [
|
|
129
|
+
`import { Generated, ColumnType } from "kysely";`,
|
|
130
|
+
"",
|
|
131
|
+
`export interface KyselyDatabase {
|
|
132
|
+
${entities.map((entity) => `${entity.table}: ${entity.id}Table`).join(",\n")}
|
|
133
|
+
${manyToManyTables.map(({ table, interfaceName }) => `${table}: ${interfaceName}`).join(",\n")}
|
|
134
|
+
}`,
|
|
135
|
+
"",
|
|
136
|
+
`declare module "sonamu" {
|
|
137
|
+
export interface DatabaseExtend extends KyselyDatabase {}
|
|
138
|
+
}`,
|
|
139
|
+
],
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private resolveColumn(prop: EntityProp, enums: Entity["enums"]) {
|
|
144
|
+
if (isVirtualProp(prop)) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (prop.name === "id") {
|
|
149
|
+
return "id: Generated<number>";
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (isRelationProp(prop)) {
|
|
153
|
+
if (isBelongsToOneRelationProp(prop)) {
|
|
154
|
+
return `${prop.name}_id: ${prop.nullable ? "number | null" : "number"}`;
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
let type: string;
|
|
160
|
+
|
|
161
|
+
if (isIntegerProp(prop)) {
|
|
162
|
+
type = "number";
|
|
163
|
+
} else if (isBigIntegerProp(prop)) {
|
|
164
|
+
type = "string";
|
|
165
|
+
} else if (isStringProp(prop) || isTextProp(prop)) {
|
|
166
|
+
type = "string";
|
|
167
|
+
} else if (isEnumProp(prop)) {
|
|
168
|
+
const enumValues = enums[prop.id];
|
|
169
|
+
if (!enumValues) {
|
|
170
|
+
console.warn(`Enum values not found for ${prop.id}`);
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
type = Object.keys(enumValues.Values)
|
|
174
|
+
.map((e) => `"${e}"`)
|
|
175
|
+
.join(" | ");
|
|
176
|
+
} else if (isFloatProp(prop) || isDoubleProp(prop) || isDecimalProp(prop)) {
|
|
177
|
+
type = "number";
|
|
178
|
+
} else if (isBooleanProp(prop)) {
|
|
179
|
+
type = "boolean";
|
|
180
|
+
} else if (
|
|
181
|
+
isDateProp(prop) ||
|
|
182
|
+
isDateTimeProp(prop) ||
|
|
183
|
+
isTimeProp(prop) ||
|
|
184
|
+
isTimestampProp(prop)
|
|
185
|
+
) {
|
|
186
|
+
type = "string";
|
|
187
|
+
} else if (isJsonProp(prop)) {
|
|
188
|
+
type = "string";
|
|
189
|
+
} else if (isUuidProp(prop)) {
|
|
190
|
+
type = "string";
|
|
191
|
+
} else {
|
|
192
|
+
console.warn(`Unknown prop type: ${(prop as any).type}`);
|
|
193
|
+
type = "unknown";
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (prop.nullable) {
|
|
197
|
+
type = `${type} | null`;
|
|
198
|
+
}
|
|
199
|
+
if (prop.dbDefault) {
|
|
200
|
+
type = `ColumnType<${type}, ${type} | undefined, ${type}>`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return `${prop.name}: ${type};`;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
@@ -3,6 +3,7 @@ import { EntityManager, EntityNamesRecord } from "../entity/entity-manager";
|
|
|
3
3
|
import { Template } from "./base-template";
|
|
4
4
|
import { Template__view_list } from "./view_list.template";
|
|
5
5
|
import { Sonamu } from "../api";
|
|
6
|
+
import { DB } from "../database/db";
|
|
6
7
|
|
|
7
8
|
export class Template__model extends Template {
|
|
8
9
|
constructor() {
|
|
@@ -24,7 +25,6 @@ export class Template__model extends Template {
|
|
|
24
25
|
listParamsNode: RenderingNode
|
|
25
26
|
) {
|
|
26
27
|
const names = EntityManager.getNamesFromId(entityId);
|
|
27
|
-
const entity = EntityManager.get(entityId);
|
|
28
28
|
|
|
29
29
|
const vlTpl = new Template__view_list();
|
|
30
30
|
if (listParamsNode?.children === undefined) {
|
|
@@ -34,144 +34,7 @@ export class Template__model extends Template {
|
|
|
34
34
|
|
|
35
35
|
return {
|
|
36
36
|
...this.getTargetAndPath(names),
|
|
37
|
-
body:
|
|
38
|
-
import { BaseModelClass, ListResult, asArray, NotFoundException, BadRequestException, api } from 'sonamu';
|
|
39
|
-
import {
|
|
40
|
-
${entityId}SubsetKey,
|
|
41
|
-
${entityId}SubsetMapping,
|
|
42
|
-
} from "../sonamu.generated";
|
|
43
|
-
import {
|
|
44
|
-
${names.camel}SubsetQueries,
|
|
45
|
-
} from "../sonamu.generated.sso";
|
|
46
|
-
import { ${entityId}ListParams, ${entityId}SaveParams } from "./${names.fs}.types";
|
|
47
|
-
|
|
48
|
-
/*
|
|
49
|
-
${entityId} Model
|
|
50
|
-
*/
|
|
51
|
-
class ${entityId}ModelClass extends BaseModelClass {
|
|
52
|
-
modelName = "${entityId}";
|
|
53
|
-
|
|
54
|
-
@api({ httpMethod: "GET", clients: ["axios", "swr"], resourceName: "${entityId}" })
|
|
55
|
-
async findById<T extends ${entityId}SubsetKey>(
|
|
56
|
-
subset: T,
|
|
57
|
-
id: number
|
|
58
|
-
): Promise<${entityId}SubsetMapping[T]> {
|
|
59
|
-
const { rows } = await this.findMany(subset, {
|
|
60
|
-
id,
|
|
61
|
-
num: 1,
|
|
62
|
-
page: 1,
|
|
63
|
-
});
|
|
64
|
-
if (rows.length == 0) {
|
|
65
|
-
throw new NotFoundException(\`존재하지 않는 ${names.capital} ID \${id}\`);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return rows[0];
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
async findOne<T extends ${entityId}SubsetKey>(
|
|
72
|
-
subset: T,
|
|
73
|
-
listParams: ${entityId}ListParams
|
|
74
|
-
): Promise<${entityId}SubsetMapping[T] | null> {
|
|
75
|
-
const { rows } = await this.findMany(subset, {
|
|
76
|
-
...listParams,
|
|
77
|
-
num: 1,
|
|
78
|
-
page: 1,
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
return rows[0] ?? null;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
@api({ httpMethod: "GET", clients: ["axios", "swr"], resourceName: "${names.capitalPlural}" })
|
|
85
|
-
async findMany<T extends ${entityId}SubsetKey>(
|
|
86
|
-
subset: T,
|
|
87
|
-
params: ${entityId}ListParams = {}
|
|
88
|
-
): Promise<ListResult<${entityId}SubsetMapping[T]>> {
|
|
89
|
-
// params with defaults
|
|
90
|
-
params = {
|
|
91
|
-
num: 24,
|
|
92
|
-
page: 1,
|
|
93
|
-
search: "${def.search}",
|
|
94
|
-
orderBy: "${def.orderBy}",
|
|
95
|
-
...params,
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
// build queries
|
|
99
|
-
let { rows, total } = await this.runSubsetQuery({
|
|
100
|
-
subset,
|
|
101
|
-
params,
|
|
102
|
-
subsetQuery: ${names.camel}SubsetQueries[subset],
|
|
103
|
-
build: ({ qb }) => {
|
|
104
|
-
// id
|
|
105
|
-
if (params.id) {
|
|
106
|
-
qb.whereIn("${entity.table}.id", asArray(params.id));
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// search-keyword
|
|
110
|
-
if (params.search && params.keyword && params.keyword.length > 0) {
|
|
111
|
-
if (params.search === "id") {
|
|
112
|
-
qb.where("${entity.table}.id", params.keyword);
|
|
113
|
-
// } else if (params.search === "field") {
|
|
114
|
-
// qb.where("${entity.table}.field", "like", \`%\${params.keyword}%\`);
|
|
115
|
-
} else {
|
|
116
|
-
throw new BadRequestException(
|
|
117
|
-
\`구현되지 않은 검색 필드 \${params.search}\`
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// orderBy
|
|
123
|
-
if (params.orderBy) {
|
|
124
|
-
// default orderBy
|
|
125
|
-
const [orderByField, orderByDirec] = params.orderBy.split("-");
|
|
126
|
-
qb.orderBy("${entity.table}." + orderByField, orderByDirec);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return qb;
|
|
130
|
-
},
|
|
131
|
-
debug: false,
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
return {
|
|
135
|
-
rows,
|
|
136
|
-
total,
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
@api({ httpMethod: "POST" })
|
|
141
|
-
async save(
|
|
142
|
-
spa: ${entityId}SaveParams[]
|
|
143
|
-
): Promise<number[]> {
|
|
144
|
-
const wdb = this.getDB("w");
|
|
145
|
-
const ub = this.getUpsertBuilder();
|
|
146
|
-
|
|
147
|
-
// register
|
|
148
|
-
spa.map((sp) => {
|
|
149
|
-
ub.register("${entity.table}", sp);
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
// transaction
|
|
153
|
-
return wdb.transaction(async (trx) => {
|
|
154
|
-
const ids = await ub.upsert(trx, "${entity.table}");
|
|
155
|
-
|
|
156
|
-
return ids;
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
@api({ httpMethod: "POST", guards: [ "admin" ] })
|
|
161
|
-
async del(ids: number[]): Promise<number> {
|
|
162
|
-
const wdb = this.getDB("w");
|
|
163
|
-
|
|
164
|
-
// transaction
|
|
165
|
-
await wdb.transaction(async (trx) => {
|
|
166
|
-
return trx("${entity.table}").whereIn("${entity.table}.id", ids).delete();
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
return ids.length;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
export const ${entityId}Model = new ${entityId}ModelClass();
|
|
174
|
-
`.trim(),
|
|
37
|
+
body: DB.generator.generateModelTemplate(entityId, def),
|
|
175
38
|
importKeys: [],
|
|
176
39
|
};
|
|
177
40
|
}
|
|
@@ -72,7 +72,9 @@ export class Template__service extends Template {
|
|
|
72
72
|
const paramsWithoutContext = api.parameters.filter(
|
|
73
73
|
(param) =>
|
|
74
74
|
!ApiParamType.isContext(param.type) &&
|
|
75
|
-
!ApiParamType.isRefKnex(param.type)
|
|
75
|
+
!ApiParamType.isRefKnex(param.type) &&
|
|
76
|
+
!ApiParamType.isRefKysely(param.type) &&
|
|
77
|
+
!(param.optional === true && param.name.startsWith("_")) // _로 시작하는 파라미터는 제외
|
|
76
78
|
);
|
|
77
79
|
|
|
78
80
|
// 파라미터 타입 정의
|