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
@@ -0,0 +1,39 @@
1
+ import { TemplateOptions } from "../types/types";
2
+ import { SMDManager, SMDNamesRecord } from "../smd/smd-manager";
3
+ import { Template } from "./base-template";
4
+
5
+ export class Template__model_test extends Template {
6
+ constructor() {
7
+ super("model_test");
8
+ }
9
+
10
+ getTargetAndPath(names: SMDNamesRecord) {
11
+ return {
12
+ target: "api/src/application",
13
+ path: `${names.fs}/${names.fs}.model.test.ts`,
14
+ };
15
+ }
16
+
17
+ render({ smdId }: TemplateOptions["model_test"]) {
18
+ const names = SMDManager.getNamesFromId(smdId);
19
+
20
+ return {
21
+ ...this.getTargetAndPath(names),
22
+ body: `
23
+ import { BadRequestException, FixtureManager } from "@sonamu/core";
24
+ import { ${smdId}ListParams, ${smdId}SaveParams } from "../${names.fs}/${names.fs}.types";
25
+ import { ${smdId}Model } from "../${names.fs}/${names.fs}.model";
26
+
27
+ describe.skip("${smdId}Model Model", () => {
28
+ new FixtureManager([
29
+ ]);
30
+
31
+ test("Query", async () => {
32
+ });
33
+ });
34
+
35
+ `.trim(),
36
+ importKeys: [],
37
+ };
38
+ }
39
+ }
@@ -0,0 +1,263 @@
1
+ import { camelize } from "inflection";
2
+ import { groupBy, sortBy, difference, uniq } from "lodash";
3
+ import { TemplateOptions } from "../types/types";
4
+ import { SMDManager, SMDNamesRecord } from "../smd/smd-manager";
5
+ import { ApiParamType, ApiParam } from "../types/types";
6
+ import {
7
+ apiParamTypeToTsType,
8
+ apiParamToTsCode,
9
+ unwrapPromiseOnce,
10
+ } from "../api/code-converters";
11
+ import { ExtendedApi } from "../api/decorators";
12
+ import { Template } from "./base-template";
13
+
14
+ export class Template__service extends Template {
15
+ constructor() {
16
+ super("service");
17
+ }
18
+
19
+ getTargetAndPath(names: SMDNamesRecord) {
20
+ return {
21
+ target: ":target/src/services",
22
+ path: `${names.fs}/${names.fs}.service.ts`,
23
+ };
24
+ }
25
+
26
+ render({ smdId }: TemplateOptions["service"], apis: ExtendedApi[]) {
27
+ const names = SMDManager.getNamesFromId(smdId);
28
+
29
+ // 서비스 TypeSource
30
+ const { lines, importKeys } = this.getTypeSource(apis);
31
+
32
+ return {
33
+ ...this.getTargetAndPath(names),
34
+ body: lines.join("\n"),
35
+ importKeys: importKeys.filter(
36
+ (key) => ["ListResult"].includes(key) === false
37
+ ),
38
+ customHeaders: [
39
+ `import { z } from 'zod';`,
40
+ `import qs from "qs";`,
41
+ `import useSWR, { SWRResponse } from "swr";`,
42
+ `import { fetch, ListResult, SWRError, SwrOptions, handleConditional } from '../sonamu.shared';`,
43
+ ],
44
+ };
45
+ }
46
+
47
+ getTypeSource(
48
+ apis: ExtendedApi[],
49
+ apiPrefix: string = "/api"
50
+ ): { lines: string[]; importKeys: string[] } {
51
+ const importKeys: string[] = [];
52
+
53
+ // 제네릭에서 선언한 타입, importKeys에서 제외 필요
54
+ let typeParamNames: string[] = [];
55
+
56
+ const groups = groupBy(apis, (api) => api.modelName);
57
+ const body = Object.keys(groups)
58
+ .map((modelName) => {
59
+ const methods = groups[modelName];
60
+ const methodCodes = methods
61
+ .map((api) => {
62
+ // 컨텍스트 제외된 파라미터 리스트
63
+ const paramsWithoutContext = api.parameters.filter(
64
+ (param) => !ApiParamType.isContext(param.type)
65
+ );
66
+
67
+ // 파라미터 타입 정의
68
+ const typeParamsDef = api.typeParameters
69
+ .map((typeParam) => {
70
+ return apiParamTypeToTsType(typeParam, importKeys);
71
+ })
72
+ .join(", ");
73
+ typeParamNames = typeParamNames.concat(
74
+ api.typeParameters.map((typeParam) => typeParam.id)
75
+ );
76
+
77
+ // 파라미터 정의
78
+ const paramsDef = apiParamToTsCode(
79
+ paramsWithoutContext,
80
+ importKeys
81
+ );
82
+
83
+ // 리턴 타입 정의
84
+ const returnTypeDef = apiParamTypeToTsType(
85
+ unwrapPromiseOnce(api.returnType),
86
+ importKeys
87
+ );
88
+
89
+ // 페이로드 데이터 정의
90
+ const payloadDef = `{ ${paramsWithoutContext
91
+ .map((param) => param.name)
92
+ .join(", ")} }`;
93
+
94
+ return sortBy(api.options.clients, (client) =>
95
+ client === "swr" ? 0 : 1
96
+ )
97
+ .map((client) => {
98
+ const apiBaseUrl = `${apiPrefix}${api.path}`;
99
+ switch (client) {
100
+ case "axios":
101
+ return this.renderAxios(
102
+ api,
103
+ apiBaseUrl,
104
+ typeParamsDef,
105
+ paramsDef,
106
+ returnTypeDef,
107
+ payloadDef
108
+ );
109
+ case "axios-multipart":
110
+ return this.renderAxiosMultipart(
111
+ api,
112
+ apiBaseUrl,
113
+ typeParamsDef,
114
+ paramsDef,
115
+ returnTypeDef,
116
+ paramsWithoutContext
117
+ );
118
+ case "swr":
119
+ return this.renderSwr(
120
+ api,
121
+ apiBaseUrl,
122
+ typeParamsDef,
123
+ paramsDef,
124
+ returnTypeDef,
125
+ payloadDef
126
+ );
127
+ case "window-fetch":
128
+ return this.renderWindowFetch(
129
+ api,
130
+ apiBaseUrl,
131
+ typeParamsDef,
132
+ paramsDef,
133
+ payloadDef
134
+ );
135
+ case "socketio":
136
+ default:
137
+ return `// Not supported ${camelize(client, true)} yet.`;
138
+ }
139
+ })
140
+ .join("\n");
141
+ })
142
+ .join("\n\n");
143
+
144
+ return `export namespace ${modelName.replace("Model", "Service")} {
145
+ ${methodCodes}
146
+ }`;
147
+ })
148
+ .join("\n\n");
149
+ return {
150
+ lines: [body],
151
+ importKeys: difference(uniq(importKeys), typeParamNames),
152
+ };
153
+ }
154
+
155
+ renderAxios(
156
+ api: ExtendedApi,
157
+ apiBaseUrl: string,
158
+ typeParamsDef: string,
159
+ paramsDef: string,
160
+ returnTypeDef: string,
161
+ payloadDef: string
162
+ ) {
163
+ const methodNameAxios = api.options.resourceName
164
+ ? "get" + camelize(api.options.resourceName)
165
+ : api.methodName;
166
+
167
+ if (api.options.httpMethod === "GET") {
168
+ return `
169
+ export async function ${methodNameAxios}${typeParamsDef}(${paramsDef}): Promise<${returnTypeDef}> {
170
+ return fetch({
171
+ method: "GET",
172
+ url: \`${apiBaseUrl}?\${qs.stringify(${payloadDef})}\`,
173
+ });
174
+ }
175
+ `.trim();
176
+ } else {
177
+ return `
178
+ export async function ${methodNameAxios}${typeParamsDef}(${paramsDef}): Promise<${returnTypeDef}> {
179
+ return fetch({
180
+ method: '${api.options.httpMethod}',
181
+ url: \`${apiBaseUrl}\`,
182
+ data: ${payloadDef},
183
+ });
184
+ }
185
+ `.trim();
186
+ }
187
+ }
188
+
189
+ renderAxiosMultipart(
190
+ api: ExtendedApi,
191
+ apiBaseUrl: string,
192
+ typeParamsDef: string,
193
+ paramsDef: string,
194
+ returnTypeDef: string,
195
+ paramsWithoutContext: ApiParam[]
196
+ ) {
197
+ const formDataDef = [
198
+ 'formData.append("file", file);',
199
+ ...paramsWithoutContext.map(
200
+ (param) => `formData.append('${param.name}', ${param.name});`
201
+ ),
202
+ ].join("\n");
203
+
204
+ const paramsDefComma = paramsDef !== "" ? ", " : "";
205
+ return `
206
+ export async function ${api.methodName}${typeParamsDef}(
207
+ ${paramsDef}${paramsDefComma}
208
+ file: File,
209
+ onUploadProgress?: (pe:ProgressEvent) => void
210
+ ): Promise<${returnTypeDef}> {
211
+ const formData = new FormData();
212
+ ${formDataDef}
213
+ return fetch({
214
+ method: 'POST',
215
+ url: \`${apiBaseUrl}\`,
216
+ headers: {
217
+ "Content-Type": "multipart/form-data",
218
+ },
219
+ onUploadProgress,
220
+ data: formData,
221
+ });
222
+ }
223
+ `.trim();
224
+ }
225
+
226
+ renderSwr(
227
+ api: ExtendedApi,
228
+ apiBaseUrl: string,
229
+ typeParamsDef: string,
230
+ paramsDef: string,
231
+ returnTypeDef: string,
232
+ payloadDef: string
233
+ ) {
234
+ const methodNameSwr = api.options.resourceName
235
+ ? "use" + camelize(api.options.resourceName)
236
+ : "use" + camelize(api.methodName);
237
+ return ` export function ${camelize(
238
+ methodNameSwr,
239
+ true
240
+ )}${typeParamsDef}(${[paramsDef, "options?: SwrOptions"]
241
+ .filter((p) => p !== "")
242
+ .join(",")}, ): SWRResponse<${returnTypeDef}, SWRError> {
243
+ return useSWR<${returnTypeDef}, SWRError>(handleConditional([
244
+ \`${apiBaseUrl}\`,
245
+ qs.stringify(${payloadDef}),
246
+ ], options?.conditional));
247
+ }`;
248
+ }
249
+
250
+ renderWindowFetch(
251
+ api: ExtendedApi,
252
+ apiBaseUrl: string,
253
+ typeParamsDef: string,
254
+ paramsDef: string,
255
+ payloadDef: string
256
+ ) {
257
+ return `
258
+ export async function ${api.methodName}${typeParamsDef}(${paramsDef}): Promise<Response> {
259
+ return window.fetch(\`${apiBaseUrl}?\${qs.stringify(${payloadDef})}\`);
260
+ }
261
+ `.trim();
262
+ }
263
+ }
@@ -0,0 +1,49 @@
1
+ import { TemplateOptions } from "../types/types";
2
+ import { SMDManager, SMDNamesRecord } from "../smd/smd-manager";
3
+ import { Template } from "./base-template";
4
+
5
+ export class Template__smd extends Template {
6
+ constructor() {
7
+ super("smd");
8
+ }
9
+
10
+ getTargetAndPath(names: SMDNamesRecord) {
11
+ return {
12
+ target: "api/src/application",
13
+ path: `${names.fs}/${names.fs}.smd.ts`,
14
+ };
15
+ }
16
+
17
+ render(options: TemplateOptions["smd"]) {
18
+ const { smdId, title, refCode } = options;
19
+ const names = SMDManager.getNamesFromId(smdId);
20
+
21
+ return {
22
+ ...this.getTargetAndPath(names),
23
+ body: `
24
+ import { p, MDInput } from "@sonamu/core";
25
+ import { ${smdId}FieldExpr } from "./${names.fs}.generated";
26
+
27
+ /*
28
+ ${smdId} SMD
29
+ */
30
+
31
+ export const ${names.camel}MdInput: SMDInput<${smdId}FieldExpr> = {
32
+ id: "${smdId}",
33
+ title: "${title ?? smdId}",
34
+ props: [
35
+ p.integer("id", { unsigned: true }),
36
+ p.timestamp("created_at", {
37
+ now: true,
38
+ }),
39
+ ],
40
+ subsets: {
41
+ A: [ 'id', 'created_at' ]
42
+ }
43
+ };
44
+ ${refCode ? `\n/* REFERENCE\n\n${refCode}\n*/` : ""}
45
+ `.trim(),
46
+ importKeys: [],
47
+ };
48
+ }
49
+ }
@@ -0,0 +1,34 @@
1
+ import { TemplateOptions } from "../types/types";
2
+ import { SMDManager, SMDNamesRecord } from "../smd/smd-manager";
3
+ import { Template } from "./base-template";
4
+
5
+ export class Template__view_enums_buttonset extends Template {
6
+ constructor() {
7
+ super("view_enums_buttonset");
8
+ }
9
+
10
+ getTargetAndPath(names: SMDNamesRecord, componentId: string) {
11
+ return {
12
+ target: "web/src/components",
13
+ path: `${names.fs}/${componentId}ButtonSet.tsx`,
14
+ };
15
+ }
16
+
17
+ render({ smdId, enumId }: TemplateOptions["view_enums_buttonset"]) {
18
+ const names = SMDManager.getNamesFromId(smdId);
19
+
20
+ return {
21
+ ...this.getTargetAndPath(names, enumId),
22
+ body: `
23
+ /*
24
+ view_enums_buttonset
25
+ ${JSON.stringify({
26
+ key: this.key,
27
+ options: smdId,
28
+ })}
29
+ */
30
+ `.trim(),
31
+ importKeys: [],
32
+ };
33
+ }
34
+ }
@@ -0,0 +1,67 @@
1
+ import { TemplateOptions } from "../types/types";
2
+ import { SMDManager, SMDNamesRecord } from "../smd/smd-manager";
3
+ import { Template } from "./base-template";
4
+
5
+ export class Template__view_enums_dropdown extends Template {
6
+ constructor() {
7
+ super("view_enums_dropdown");
8
+ }
9
+
10
+ getTargetAndPath(names: SMDNamesRecord, enumId: string) {
11
+ return {
12
+ target: "web/src/components",
13
+ path: `${names.fs}/${enumId}Dropdown.tsx`,
14
+ };
15
+ }
16
+
17
+ render({
18
+ smdId,
19
+ enumId,
20
+ idConstant,
21
+ }: TemplateOptions["view_enums_dropdown"]) {
22
+ const names = SMDManager.getNamesFromId(smdId);
23
+ const label = getLabel(idConstant);
24
+
25
+ return {
26
+ ...this.getTargetAndPath(names, enumId),
27
+ body: `
28
+ import React from 'react';
29
+ import {
30
+ Dropdown,
31
+ DropdownProps,
32
+ } from 'semantic-ui-react';
33
+
34
+ import { ${names.constant} } from 'src/services/${names.fs}/${names.fs}.enums';
35
+
36
+ export function ${enumId}Dropdown(props: DropdownProps) {
37
+ const options = Object.entries(${names.constant}.${idConstant}).map(([key, { ko }]) => {
38
+ return {
39
+ key,
40
+ value: key,
41
+ text: "${label}: " + ko,
42
+ };
43
+ });
44
+ return (
45
+ <Dropdown
46
+ className="label"
47
+ options={options}
48
+ {...props}
49
+ />
50
+ );
51
+ }
52
+ `.trim(),
53
+ importKeys: [],
54
+ };
55
+ }
56
+ }
57
+
58
+ export function getLabel(idConstant: string): string {
59
+ switch (idConstant) {
60
+ case "ORDER_BY":
61
+ return "정렬";
62
+ case "SEARCH_FIELD":
63
+ return "검색";
64
+ default:
65
+ return idConstant;
66
+ }
67
+ }
@@ -0,0 +1,60 @@
1
+ import { TemplateOptions } from "../types/types";
2
+ import { SMDManager, SMDNamesRecord } from "../smd/smd-manager";
3
+ import { Template } from "./base-template";
4
+ import { getLabel } from "./view_enums_dropdown.template";
5
+
6
+ export class Template__view_enums_select extends Template {
7
+ constructor() {
8
+ super("view_enums_select");
9
+ }
10
+
11
+ getTargetAndPath(names: SMDNamesRecord, enumId: string) {
12
+ return {
13
+ target: "web/src/components",
14
+ path: `${names.fs}/${enumId}Select.tsx`,
15
+ };
16
+ }
17
+
18
+ render({ smdId, enumId, idConstant }: TemplateOptions["view_enums_select"]) {
19
+ const names = SMDManager.getNamesFromId(smdId);
20
+ const label = getLabel(idConstant);
21
+
22
+ return {
23
+ ...this.getTargetAndPath(names, enumId),
24
+ body: `
25
+ import React from 'react';
26
+ import {
27
+ Dropdown,
28
+ DropdownProps,
29
+ } from 'semantic-ui-react';
30
+
31
+ import { ${names.constant} } from 'src/services/${names.fs}/${names.fs}.enums';
32
+
33
+ export type ${enumId}SelectProps = {
34
+ placeholder?: string;
35
+ textPrefix?: string;
36
+ } & DropdownProps;
37
+ export function ${enumId}Select({placeholder, textPrefix, ...props}: ${enumId}SelectProps) {
38
+ const typeOptions = Object.entries(${names.constant}.${idConstant}).map(([key, { ko }]) => {
39
+ return {
40
+ key,
41
+ value: key,
42
+ text: (textPrefix ?? '${label}: ') + ko,
43
+ };
44
+ });
45
+
46
+ return (
47
+ <Dropdown
48
+ placeholder={placeholder ?? "${label}"}
49
+ selection
50
+ options={typeOptions}
51
+ selectOnBlur={false}
52
+ {...props}
53
+ />
54
+ );
55
+ }
56
+ `.trim(),
57
+ importKeys: [],
58
+ };
59
+ }
60
+ }