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.
Files changed (85) 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-L4KELCY7.mjs → chunk-PP2PSSAG.mjs} +5241 -5571
  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-JOHF7PK4.js → chunk-XT6LHCX5.js} +5292 -5622
  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 +222 -928
  37. package/dist/index.d.ts +222 -928
  38. package/dist/index.js +13 -26
  39. package/dist/index.js.map +1 -1
  40. package/dist/index.mjs +18 -31
  41. package/dist/index.mjs.map +1 -1
  42. package/dist/model-CAH_4oQh.d.mts +1042 -0
  43. package/dist/model-CAH_4oQh.d.ts +1042 -0
  44. package/import-to-require.js +27 -0
  45. package/package.json +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-manager.ts +9 -5
  67. package/src/entity/entity-utils.ts +1 -1
  68. package/src/entity/entity.ts +9 -13
  69. package/src/entity/migrator.ts +98 -693
  70. package/src/index.ts +1 -1
  71. package/src/syncer/syncer.ts +103 -56
  72. package/src/templates/generated_http.template.ts +14 -0
  73. package/src/templates/kysely_types.template.ts +205 -0
  74. package/src/templates/model.template.ts +2 -139
  75. package/src/templates/service.template.ts +3 -1
  76. package/src/testing/_relation-graph.ts +111 -0
  77. package/src/testing/fixture-manager.ts +216 -332
  78. package/src/types/types.ts +56 -6
  79. package/src/utils/utils.ts +56 -4
  80. package/src/utils/zod-error.ts +189 -0
  81. package/tsconfig.json +2 -2
  82. package/tsup.config.js +11 -10
  83. package/dist/chunk-JOHF7PK4.js.map +0 -1
  84. package/dist/chunk-L4KELCY7.mjs.map +0 -1
  85. /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";
@@ -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
- return (
279
- await Promise.all(
280
- entityIds.map(async (entityId) =>
281
- this.generateTemplate(
282
- "generated_http",
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.debug("checksum saved", this.checksumsPath);
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.log({ name, type, paramDec });
688
+ console.debug({ name, type, paramDec });
662
689
  }
663
690
 
664
- return {
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((result, key) => {
1027
- const tpl = this.getTemplate(key);
1028
- if (key.startsWith("view_enums")) {
1029
- enumsKeys.map((componentId) => {
1030
- const { target, path: p } = tpl.getTargetAndPath(names, componentId);
1031
- result[`${key}__${componentId}`] = fs.existsSync(
1032
- path.join(Sonamu.appRootPath, target, p)
1033
- );
1034
- });
1035
- return result;
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
- const { target, path: p } = tpl.getTargetAndPath(names);
1039
- const { targets } = Sonamu.config.sync;
1040
- if (target.includes(":target")) {
1041
- targets.map((t) => {
1042
- result[`${key}__${t}`] = fs.existsSync(
1043
- path.join(Sonamu.appRootPath, target.replace(":target", t), p)
1044
- );
1045
- });
1046
- } else {
1047
- result[key] = fs.existsSync(path.join(Sonamu.appRootPath, target, p));
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
- return result;
1051
- }, {} as Record<`${TemplateKey}${string}`, boolean>);
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] = await this.propNodeToZodType(
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 this.actionGenerateSchemas();
1340
-
1341
- // generate types
1342
- if (form.parentId === undefined) {
1343
- await this.generateTemplate("init_types", {
1344
- entityId: form.entityId,
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
  // 파라미터 타입 정의