sonamu 0.0.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 (60) hide show
  1. package/.pnp.cjs +15552 -0
  2. package/.pnp.loader.mjs +285 -0
  3. package/.vscode/extensions.json +6 -0
  4. package/.vscode/settings.json +9 -0
  5. package/.yarnrc.yml +5 -0
  6. package/dist/bin/cli.d.ts +2 -0
  7. package/dist/bin/cli.d.ts.map +1 -0
  8. package/dist/bin/cli.js +123 -0
  9. package/dist/bin/cli.js.map +1 -0
  10. package/dist/index.js +34 -0
  11. package/package.json +60 -0
  12. package/src/api/caster.ts +72 -0
  13. package/src/api/code-converters.ts +552 -0
  14. package/src/api/context.ts +20 -0
  15. package/src/api/decorators.ts +63 -0
  16. package/src/api/index.ts +5 -0
  17. package/src/api/init.ts +128 -0
  18. package/src/bin/cli.ts +115 -0
  19. package/src/database/base-model.ts +287 -0
  20. package/src/database/db.ts +95 -0
  21. package/src/database/knex-plugins/knex-on-duplicate-update.ts +41 -0
  22. package/src/database/upsert-builder.ts +231 -0
  23. package/src/exceptions/error-handler.ts +29 -0
  24. package/src/exceptions/so-exceptions.ts +91 -0
  25. package/src/index.ts +17 -0
  26. package/src/shared/web.shared.ts.txt +119 -0
  27. package/src/smd/migrator.ts +1462 -0
  28. package/src/smd/smd-manager.ts +141 -0
  29. package/src/smd/smd-utils.ts +266 -0
  30. package/src/smd/smd.ts +533 -0
  31. package/src/syncer/index.ts +1 -0
  32. package/src/syncer/syncer.ts +1283 -0
  33. package/src/templates/base-template.ts +19 -0
  34. package/src/templates/generated.template.ts +247 -0
  35. package/src/templates/generated_http.template.ts +114 -0
  36. package/src/templates/index.ts +1 -0
  37. package/src/templates/init_enums.template.ts +71 -0
  38. package/src/templates/init_generated.template.ts +44 -0
  39. package/src/templates/init_types.template.ts +38 -0
  40. package/src/templates/model.template.ts +168 -0
  41. package/src/templates/model_test.template.ts +39 -0
  42. package/src/templates/service.template.ts +263 -0
  43. package/src/templates/smd.template.ts +49 -0
  44. package/src/templates/view_enums_buttonset.template.ts +34 -0
  45. package/src/templates/view_enums_dropdown.template.ts +67 -0
  46. package/src/templates/view_enums_select.template.ts +60 -0
  47. package/src/templates/view_form.template.ts +397 -0
  48. package/src/templates/view_id_all_select.template.ts +34 -0
  49. package/src/templates/view_id_async_select.template.ts +113 -0
  50. package/src/templates/view_list.template.ts +652 -0
  51. package/src/templates/view_list_columns.template.ts +59 -0
  52. package/src/templates/view_search_input.template.ts +67 -0
  53. package/src/testing/fixture-manager.ts +271 -0
  54. package/src/types/types.ts +668 -0
  55. package/src/typings/knex.d.ts +24 -0
  56. package/src/utils/controller.ts +21 -0
  57. package/src/utils/lodash-able.ts +11 -0
  58. package/src/utils/model.ts +33 -0
  59. package/src/utils/utils.ts +28 -0
  60. package/tsconfig.json +47 -0
package/src/smd/smd.ts ADDED
@@ -0,0 +1,533 @@
1
+ import _, { uniq } from "lodash";
2
+ import { SMDManager } from "./smd-manager";
3
+ import { dasherize, pluralize, underscore } from "inflection";
4
+ import {
5
+ SMDProp,
6
+ RelationProp,
7
+ SMDInput,
8
+ isRelationProp,
9
+ SubsetQuery,
10
+ isVirtualProp,
11
+ isBelongsToOneRelationProp,
12
+ isOneToOneRelationProp,
13
+ isHasManyRelationProp,
14
+ isManyToManyRelationProp,
15
+ SMDPropNode,
16
+ isEnumProp,
17
+ StringProp,
18
+ } from "../types/types";
19
+ import inflection from "inflection";
20
+ import path from "path";
21
+ import { existsSync } from "fs";
22
+ import { z } from "zod";
23
+ import { EnumsLabelKo } from "../types/types";
24
+ import { Syncer } from "../syncer";
25
+
26
+ export class SMD {
27
+ id: string;
28
+ parentId?: string;
29
+ table: string;
30
+ title: string;
31
+ names: {
32
+ fs: string;
33
+ module: string;
34
+ };
35
+ props: SMDProp[];
36
+ propsDict: {
37
+ [key: string]: SMDProp;
38
+ };
39
+ relations: {
40
+ [key: string]: RelationProp;
41
+ };
42
+ subsets: {
43
+ [key: string]: string[];
44
+ };
45
+ types: {
46
+ [name: string]: z.ZodTypeAny;
47
+ } = {};
48
+ enums: {
49
+ [name: string]: z.ZodEnum<any>;
50
+ } = {};
51
+ enumLabels: {
52
+ [name: string]: EnumsLabelKo<string>;
53
+ } = {};
54
+
55
+ constructor({ id, parentId, table, title, props, subsets }: SMDInput<any>) {
56
+ // id
57
+ this.id = id;
58
+ this.parentId = parentId;
59
+ this.title = title ?? this.id;
60
+ this.table = table ?? underscore(pluralize(id));
61
+
62
+ // props
63
+ if (props) {
64
+ this.props = props.map((prop) => {
65
+ if (isEnumProp(prop)) {
66
+ if (prop.id.includes("$Model")) {
67
+ prop.id = prop.id.replace("$Model", id);
68
+ }
69
+ }
70
+ return prop;
71
+ });
72
+ this.propsDict = props.reduce((result, prop) => {
73
+ return {
74
+ ...result,
75
+ [prop.name]: prop,
76
+ };
77
+ }, {});
78
+
79
+ // relations
80
+ this.relations = props
81
+ .filter((prop) => isRelationProp(prop))
82
+ .reduce((result, prop) => {
83
+ return {
84
+ ...result,
85
+ [prop.name]: prop,
86
+ };
87
+ }, {});
88
+ } else {
89
+ this.props = [];
90
+ this.propsDict = {};
91
+ this.relations = {};
92
+ }
93
+
94
+ // subsets
95
+ this.subsets = subsets ?? {};
96
+
97
+ // names
98
+ this.names = {
99
+ fs:
100
+ parentId === undefined
101
+ ? dasherize(underscore(id)).toLowerCase()
102
+ : dasherize(parentId).toLowerCase(),
103
+ module: id,
104
+ };
105
+
106
+ this.registerModulePaths();
107
+ this.registerTableSpecs();
108
+ }
109
+
110
+ /*
111
+ subset SELECT/JOIN/LOADER 결과 리턴
112
+ */
113
+ getSubsetQuery(subsetKey: string): SubsetQuery {
114
+ const subset = this.subsets[subsetKey];
115
+
116
+ const result: SubsetQuery = this.resolveSubsetQuery("", subset);
117
+ return result;
118
+ }
119
+
120
+ /*
121
+ */
122
+ resolveSubsetQuery(prefix: string, fields: string[]): SubsetQuery {
123
+ // 서브셋을 1뎁스만 분리하여 그룹핑
124
+ const subsetGroup = _.groupBy(fields, (field) => {
125
+ if (field.includes(".")) {
126
+ const [rel] = field.split(".");
127
+ return rel;
128
+ } else {
129
+ return "";
130
+ }
131
+ });
132
+
133
+ const result = Object.keys(subsetGroup).reduce(
134
+ (r, groupKey) => {
135
+ const fields = subsetGroup[groupKey];
136
+ // 현재 테이블 필드셋은 select, virtual에 추가하고 리턴
137
+ if (groupKey === "") {
138
+ const realFields = fields.filter(
139
+ (field) => !isVirtualProp(this.propsDict[field])
140
+ );
141
+ const virtualFields = fields.filter((field) =>
142
+ isVirtualProp(this.propsDict[field])
143
+ );
144
+
145
+ if (prefix === "") {
146
+ // 현재 테이블인 경우
147
+ r.select = r.select.concat(
148
+ realFields.map((field) => `${this.table}.${field}`)
149
+ );
150
+ r.virtual = r.virtual.concat(virtualFields);
151
+ } else {
152
+ // 넘어온 테이블인 경우
153
+ r.select = r.select.concat(
154
+ realFields.map(
155
+ (field) =>
156
+ `${prefix.replace(".", "__")}.${field} as ${prefix.replace(
157
+ ".",
158
+ "__"
159
+ )}__${field}`
160
+ )
161
+ );
162
+ }
163
+
164
+ return r;
165
+ }
166
+
167
+ const relation = this.relations[groupKey];
168
+ if (relation === undefined) {
169
+ throw new Error(`존재하지 않는 relation 참조 ${groupKey}`);
170
+ }
171
+ const relSMD = SMDManager.get(relation.with);
172
+
173
+ if (
174
+ isOneToOneRelationProp(relation) ||
175
+ isBelongsToOneRelationProp(relation)
176
+ ) {
177
+ // -One Relation: JOIN 으로 처리
178
+ const relFields = fields.map((field) =>
179
+ field.split(".").slice(1).join(".")
180
+ );
181
+
182
+ // -One Relation에서 id 필드만 참조하는 경우 릴레이션 넘기지 않고 리턴
183
+ if (relFields.length === 1 && relFields[0] === "id") {
184
+ if (prefix === "") {
185
+ r.select = r.select.concat(`${this.table}.${groupKey}_id`);
186
+ } else {
187
+ r.select = r.select.concat(
188
+ `${prefix}.${groupKey}_id as ${prefix}__${groupKey}_id`
189
+ );
190
+ }
191
+ return r;
192
+ }
193
+
194
+ const relSubsetQuery = relSMD.resolveSubsetQuery(
195
+ `${prefix !== "" ? prefix + "." : ""}${groupKey}`,
196
+ relFields
197
+ );
198
+ r.select = r.select.concat(relSubsetQuery.select);
199
+ r.virtual = r.virtual.concat(relSubsetQuery.virtual);
200
+
201
+ const joinAs = prefix === "" ? groupKey : prefix + "__" + groupKey;
202
+ const fromTable = prefix === "" ? this.table : prefix;
203
+
204
+ let joinClause;
205
+ if (relation.customJoinClause) {
206
+ joinClause = {
207
+ custom: relation.customJoinClause,
208
+ };
209
+ } else {
210
+ let from, to;
211
+ if (isOneToOneRelationProp(relation)) {
212
+ if (relation.hasJoinColumn) {
213
+ from = `${fromTable}.${relation.name}_id`;
214
+ to = `${joinAs}.id`;
215
+ } else {
216
+ from = `${fromTable}.id`;
217
+ to = `${joinAs}.${relation.name}_id`;
218
+ }
219
+ } else {
220
+ from = `${fromTable}.${relation.name}_id`;
221
+ to = `${joinAs}.id`;
222
+ }
223
+ joinClause = {
224
+ from,
225
+ to,
226
+ };
227
+ }
228
+
229
+ r.joins.push({
230
+ as: joinAs,
231
+ join: "outer",
232
+ table: relSMD.table,
233
+ ...joinClause,
234
+ });
235
+
236
+ // BelongsToOne 밑에 HasMany가 붙은 경우
237
+ if (relSubsetQuery.loaders.length > 0) {
238
+ const convertedLoaders = relSubsetQuery.loaders.map((loader) => {
239
+ const newAs = [groupKey, loader.as].join("__");
240
+ return {
241
+ as: newAs,
242
+ table: loader.table,
243
+ manyJoin: loader.manyJoin,
244
+ oneJoins: loader.oneJoins,
245
+ select: loader.select,
246
+ };
247
+ });
248
+
249
+ r.loaders = [...r.loaders, ...convertedLoaders];
250
+ }
251
+
252
+ r.joins = r.joins.concat(relSubsetQuery.joins);
253
+ } else if (
254
+ isHasManyRelationProp(relation) ||
255
+ isManyToManyRelationProp(relation)
256
+ ) {
257
+ // -Many Relation: Loader 로 처리
258
+ const relFields = fields.map((field) =>
259
+ field.split(".").slice(1).join(".")
260
+ );
261
+ const relSubsetQuery = relSMD.resolveSubsetQuery("", relFields);
262
+
263
+ let manyJoin: SubsetQuery["loaders"][number]["manyJoin"];
264
+ if (isHasManyRelationProp(relation)) {
265
+ manyJoin = {
266
+ fromTable: this.table,
267
+ fromCol: "id",
268
+ idField: prefix === "" ? `id` : `${prefix}__id`,
269
+ toTable: relSMD.table,
270
+ toCol: relation.joinColumn,
271
+ };
272
+ } else if (isManyToManyRelationProp(relation)) {
273
+ const [table1, table2] = relation.joinTable.split("__");
274
+
275
+ manyJoin = {
276
+ fromTable: this.table,
277
+ fromCol: "id",
278
+ idField: prefix === "" ? `id` : `${prefix}__id`,
279
+ through: {
280
+ table: relation.joinTable,
281
+ fromCol: `${inflection.singularize(table1)}_id`,
282
+ toCol: `${inflection.singularize(table2)}_id`,
283
+ },
284
+ toTable: relSMD.table,
285
+ toCol: "id",
286
+ };
287
+ } else {
288
+ throw new Error();
289
+ }
290
+
291
+ r.loaders.push({
292
+ as: groupKey,
293
+ table: relSMD.table,
294
+ manyJoin,
295
+ oneJoins: relSubsetQuery.joins,
296
+ select: relSubsetQuery.select,
297
+ loaders: relSubsetQuery.loaders,
298
+ });
299
+ }
300
+
301
+ return r;
302
+ },
303
+ {
304
+ select: [],
305
+ virtual: [],
306
+ joins: [],
307
+ loaders: [],
308
+ } as SubsetQuery
309
+ );
310
+ return result;
311
+ }
312
+
313
+ /*
314
+ FieldExpr[] 을 SMDPropNode[] 로 변환
315
+ */
316
+ fieldExprsToPropNodes(fieldExprs: string[], smd: SMD = this): SMDPropNode[] {
317
+ const groups = fieldExprs.reduce(
318
+ (result, fieldExpr) => {
319
+ let key, value, elseExpr;
320
+ if (fieldExpr.includes(".")) {
321
+ [key, ...elseExpr] = fieldExpr.split(".");
322
+ value = elseExpr.join(".");
323
+ } else {
324
+ key = "";
325
+ value = fieldExpr;
326
+ }
327
+ result[key] = (result[key] ?? []).concat(value);
328
+
329
+ return result;
330
+ },
331
+ {} as {
332
+ [k: string]: string[];
333
+ }
334
+ );
335
+
336
+ return Object.keys(groups)
337
+ .map((key) => {
338
+ const group = groups[key];
339
+
340
+ // 일반 prop 처리
341
+ if (key === "") {
342
+ return group.map((propName) => {
343
+ // uuid 개별 처리
344
+ if (propName === "uuid") {
345
+ return {
346
+ nodeType: "plain" as const,
347
+ prop: {
348
+ type: "string",
349
+ name: "uuid",
350
+ length: 128,
351
+ } as StringProp,
352
+ children: [],
353
+ };
354
+ }
355
+
356
+ const prop = smd.propsDict[propName];
357
+ if (prop === undefined) {
358
+ throw new Error(`${this.id} -- 잘못된 FieldExpr ${propName}`);
359
+ }
360
+ return {
361
+ nodeType: "plain" as const,
362
+ prop,
363
+ children: [],
364
+ };
365
+ });
366
+ }
367
+
368
+ // relation prop 처리
369
+ const prop = smd.propsDict[key];
370
+ if (!isRelationProp(prop)) {
371
+ throw new Error(`잘못된 FieldExpr ${key}.${group[0]}`);
372
+ }
373
+ const relSMD = SMDManager.get(prop.with);
374
+
375
+ // relation -One 에 id 필드 하나인 경우
376
+ if (isBelongsToOneRelationProp(prop) || isOneToOneRelationProp(prop)) {
377
+ if (group.length == 1 && (group[0] === "id" || group[0] == "id?")) {
378
+ // id 하나만 있는지 체크해서, 하나만 있으면 상위 prop으로 id를 리턴
379
+ const idProp = relSMD.propsDict.id;
380
+ return {
381
+ nodeType: "plain" as const,
382
+ prop: {
383
+ ...idProp,
384
+ name: key + "_id",
385
+ nullable: prop.nullable,
386
+ },
387
+ children: [],
388
+ };
389
+ }
390
+ }
391
+
392
+ // -One 그외의 경우 object로 리턴
393
+ // -Many의 경우 array로 리턴
394
+ // Recursive 로 뎁스 처리
395
+ const children = this.fieldExprsToPropNodes(group, relSMD);
396
+ const nodeType =
397
+ isBelongsToOneRelationProp(prop) || isOneToOneRelationProp(prop)
398
+ ? ("object" as const)
399
+ : ("array" as const);
400
+
401
+ return {
402
+ prop,
403
+ children,
404
+ nodeType,
405
+ };
406
+ })
407
+ .flat();
408
+ }
409
+
410
+ getFieldExprs(
411
+ prefix = "",
412
+ maxDepth: number = 3,
413
+ froms: string[] = []
414
+ ): string[] {
415
+ return this.props
416
+ .map((prop) => {
417
+ const propName = [prefix, prop.name].filter((v) => v !== "").join(".");
418
+ if (isRelationProp(prop) && maxDepth - 1 >= 0) {
419
+ // 역방향 relation인 경우 제외
420
+ if (froms.includes(prop.with)) {
421
+ return null;
422
+ }
423
+ // 정방향 relation인 경우 recursive 콜
424
+ const relMd = SMDManager.get(prop.with);
425
+ return relMd.getFieldExprs(propName, maxDepth - 1, [
426
+ ...froms,
427
+ this.id,
428
+ ]);
429
+ }
430
+ return propName;
431
+ })
432
+ .flat()
433
+ .filter((f) => f !== null) as string[];
434
+ }
435
+
436
+ registerModulePaths() {
437
+ const basePath = `${this.names.fs}`;
438
+ const appRootPath = Syncer.getInstance().config.appRootPath;
439
+
440
+ // base-scheme
441
+ SMDManager.setModulePath(
442
+ `${this.id}BaseSchema`,
443
+ `${basePath}/${this.names.fs}.generated`
444
+ );
445
+
446
+ // subset
447
+ if (Object.keys(this.subsets).length > 0) {
448
+ SMDManager.setModulePath(
449
+ `${this.id}SubsetKey`,
450
+ `${basePath}/${this.names.fs}.generated`
451
+ );
452
+ SMDManager.setModulePath(
453
+ `${this.id}SubsetMapping`,
454
+ `${basePath}/${this.names.fs}.generated`
455
+ );
456
+ Object.keys(this.subsets).map((subsetKey) => {
457
+ SMDManager.setModulePath(
458
+ `${this.id}Subset${subsetKey.toUpperCase()}`,
459
+ `${basePath}/${this.names.fs}.generated`
460
+ );
461
+ });
462
+ }
463
+
464
+ // types
465
+ const typesModulePath = `${basePath}/${this.names.fs}.types`;
466
+ const typesFileDistPath = path.resolve(
467
+ appRootPath,
468
+ `api/dist/application/${typesModulePath}.js`
469
+ );
470
+
471
+ if (existsSync(typesFileDistPath)) {
472
+ const importPath = path.relative(__dirname, typesFileDistPath);
473
+ import(importPath).then((t) => {
474
+ this.types = Object.keys(t).reduce((result, key) => {
475
+ SMDManager.setModulePath(key, typesModulePath);
476
+ return {
477
+ ...result,
478
+ [key]: t[key],
479
+ };
480
+ }, {});
481
+ });
482
+ }
483
+
484
+ // enums
485
+ const enumsModulePath = `${basePath}/${this.names.fs}.enums`;
486
+ const enumsFileDistPath = path.resolve(
487
+ appRootPath,
488
+ `api/dist/application/${enumsModulePath}.js`
489
+ );
490
+ if (existsSync(enumsFileDistPath)) {
491
+ const importPath = path.relative(__dirname, enumsFileDistPath);
492
+ import(importPath).then((t) => {
493
+ this.enums = Object.keys(t).reduce((result, key) => {
494
+ SMDManager.setModulePath(key, enumsModulePath);
495
+
496
+ // Enum Labels 별도 처리
497
+ if (key === underscore(this.id).toUpperCase()) {
498
+ this.enumLabels = t[key];
499
+ }
500
+ return {
501
+ ...result,
502
+ [key]: t[key],
503
+ };
504
+ }, {});
505
+ });
506
+ }
507
+ }
508
+
509
+ registerTableSpecs(): void {
510
+ const uniqueColumns = uniq(
511
+ this.props
512
+ .map((prop) => {
513
+ const propColumn =
514
+ isBelongsToOneRelationProp(prop) || isOneToOneRelationProp(prop)
515
+ ? `${prop.name}_id`
516
+ : prop.name;
517
+ if (prop.unique === true) {
518
+ return propColumn;
519
+ } else if (prop.unique && Array.isArray(prop.unique)) {
520
+ return propColumn;
521
+ } else {
522
+ return null;
523
+ }
524
+ })
525
+ .filter((prop) => prop !== null)
526
+ ) as string[];
527
+
528
+ SMDManager.setTableSpec({
529
+ name: this.table,
530
+ uniqueColumns,
531
+ });
532
+ }
533
+ }
@@ -0,0 +1 @@
1
+ export * from "./syncer";