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,231 @@
1
+ import { v4 as uuidv4 } from "uuid";
2
+ import _, { chunk, defaults, groupBy } from "lodash";
3
+ import { Knex } from "knex";
4
+ import { SMDManager } from "../smd/smd-manager";
5
+
6
+ type TableData = {
7
+ references: Set<string>;
8
+ rows: any[];
9
+ uniqueColumns: string[];
10
+ uniquesMap: Map<string, string>;
11
+ };
12
+ export type UBRef = {
13
+ uuid: string;
14
+ of: string;
15
+ use?: string;
16
+ };
17
+ export function isRefField(field: any): field is UBRef {
18
+ return (
19
+ field !== undefined &&
20
+ field !== null &&
21
+ field.of !== undefined &&
22
+ field.uuid !== undefined
23
+ );
24
+ }
25
+
26
+ export class UpsertBuilder {
27
+ tables: Map<string, TableData>;
28
+ constructor() {
29
+ this.tables = new Map();
30
+ }
31
+
32
+ getTable(tableName: string): TableData {
33
+ const table = this.tables.get(tableName);
34
+ if (table === undefined) {
35
+ const tableSpec = (() => {
36
+ try {
37
+ return SMDManager.getTableSpec(tableName);
38
+ } catch {
39
+ return null;
40
+ }
41
+ })();
42
+
43
+ this.tables.set(tableName, {
44
+ references: new Set(),
45
+ rows: [],
46
+ uniqueColumns: tableSpec?.uniqueColumns ?? [],
47
+ uniquesMap: new Map<string, string>(),
48
+ });
49
+ }
50
+
51
+ return this.tables.get(tableName)!;
52
+ }
53
+
54
+ hasTable(tableName: string): boolean {
55
+ return this.tables.has(tableName);
56
+ }
57
+
58
+ register<T extends string>(
59
+ tableName: string,
60
+ row: {
61
+ [key in T]?: UBRef | string | number | boolean | bigint | null;
62
+ }
63
+ ): UBRef {
64
+ const table = this.getTable(tableName);
65
+
66
+ // uuid 생성 로직
67
+ let uuid: string | undefined;
68
+
69
+ // 해당 테이블의 unique 컬럼들의 값을 통해 키 생성
70
+ const uniqueKey = table.uniqueColumns
71
+ .map((unqCol) => row[unqCol as keyof typeof row])
72
+ .join("---delimiter--");
73
+ if (table.uniqueColumns.length > 0) {
74
+ // 기존 키가 있는 경우 uuid 그대로 사용
75
+ uuid = table.uniquesMap.get(uniqueKey);
76
+ }
77
+
78
+ // 없는 경우 uuid 생성하고, 생성한 uuid를 uniquesMap에 보관
79
+ if (!uuid) {
80
+ uuid = uuidv4();
81
+ table.uniquesMap.set(uniqueKey, uuid);
82
+ }
83
+
84
+ table.rows.push({
85
+ uuid,
86
+ ...row,
87
+ });
88
+
89
+ // 이 테이블에 사용된 RefField를 순회하여, 현재 테이블 정보에 어떤 필드를 참조하는지 추가
90
+ // 이 정보를 나중에 치환할 때 사용
91
+ row = Object.keys(row).reduce((r, rowKey) => {
92
+ const rowValue = row[rowKey as keyof typeof row];
93
+ if (isRefField(rowValue)) {
94
+ rowValue.use ??= "id";
95
+ table.references.add(rowValue.of + "." + rowValue.use);
96
+ }
97
+ r[rowKey] = rowValue;
98
+ return r;
99
+ }, {} as any);
100
+
101
+ return {
102
+ of: tableName,
103
+ uuid: (row as { uuid?: string }).uuid ?? uuid,
104
+ };
105
+ }
106
+
107
+ async upsert(wdb: Knex, tableName: string): Promise<number[]> {
108
+ if (this.hasTable(tableName) === false) {
109
+ return [];
110
+ }
111
+
112
+ const table = this.tables.get(tableName);
113
+ if (table === undefined) {
114
+ throw new Error(`존재하지 않는 테이블 ${tableName}에 upsert 요청`);
115
+ } else if (table.rows.length === 0) {
116
+ throw new Error(`${tableName}에 upsert 할 데이터가 없습니다.`);
117
+ }
118
+
119
+ if (
120
+ table.rows.some((row) =>
121
+ Object.entries(row).some(
122
+ ([, value]) => isRefField(value) && value.of !== tableName
123
+ )
124
+ )
125
+ ) {
126
+ throw new Error(`${tableName} 해결되지 않은 참조가 있습니다.`);
127
+ }
128
+
129
+ // 내부 참조 있는 경우 필터하여 분리
130
+ const groups = groupBy(table.rows, (row) =>
131
+ Object.entries(row).some(([, value]) => isRefField(value))
132
+ ? "selfRef"
133
+ : "normal"
134
+ );
135
+ const targetRows = groups.normal;
136
+
137
+ // Insert On Duplicate Update
138
+ const q = wdb.insert(targetRows).into(tableName);
139
+ await q.onDuplicateUpdate.apply(q, Object.keys(targetRows[0]));
140
+
141
+ // 전체 테이블 순회하여 현재 테이블 참조하는 모든 테이블 추출
142
+ const { references, refTables } = Array.from(this.tables).reduce(
143
+ (r, [, table]) => {
144
+ const reference = Array.from(table.references.values()).find((ref) =>
145
+ ref.includes(tableName + ".")
146
+ );
147
+ if (reference) {
148
+ r.references.push(reference);
149
+ r.refTables.push(table);
150
+ }
151
+
152
+ return r;
153
+ },
154
+ {
155
+ references: [] as string[],
156
+ refTables: [] as TableData[],
157
+ }
158
+ );
159
+
160
+ const extractFields = _.uniq(references).map(
161
+ (reference) => reference.split(".")[1]
162
+ );
163
+
164
+ // UUID 기준으로 id 추출
165
+ const uuids = table.rows.map((row) => row.uuid);
166
+ const upsertedRows = await wdb(tableName)
167
+ .select(_.uniq(["uuid", "id", ...extractFields]))
168
+ .whereIn("uuid", uuids);
169
+ const uuidMap = new Map<string, any>(
170
+ upsertedRows.map((row: any) => [row.uuid, row])
171
+ );
172
+
173
+ // 해당 테이블 참조를 실제 밸류로 변경
174
+ refTables.map((table) => {
175
+ table.rows = table.rows.map((row) => {
176
+ Object.keys(row).map((key) => {
177
+ const prop = row[key];
178
+ if (isRefField(prop) && prop.of === tableName) {
179
+ const parent = uuidMap.get(prop.uuid);
180
+ if (parent === undefined) {
181
+ console.error(prop);
182
+ throw new Error(
183
+ `존재하지 않는 uuid ${prop.uuid} -- in ${tableName}`
184
+ );
185
+ }
186
+ row[key] = parent[prop.use ?? "id"];
187
+ }
188
+ });
189
+ return row;
190
+ });
191
+ });
192
+
193
+ const ids = Array.from(uuidMap.values()).map((val) => val.id);
194
+
195
+ if (groups.selfRef) {
196
+ const selfRefIds = await this.upsert(wdb, tableName);
197
+ return [...ids, ...selfRefIds];
198
+ }
199
+
200
+ return ids;
201
+ }
202
+
203
+ async updateBatch(
204
+ wdb: Knex,
205
+ tableName: string,
206
+ options?: {
207
+ chunkSize?: number;
208
+ where?: string;
209
+ }
210
+ ): Promise<void> {
211
+ options = defaults(options, {
212
+ chunkSize: 500,
213
+ });
214
+
215
+ const table = this.tables.get(tableName);
216
+ if (table === undefined) {
217
+ throw new Error(`존재하지 않는 테이블 ${tableName}에 updateBatch 요청`);
218
+ } else if (table.rows.length === 0) {
219
+ throw new Error(`${tableName}에 updateBatch 할 데이터가 없습니다.`);
220
+ }
221
+
222
+ const chunks = chunk(table.rows, options.chunkSize);
223
+ for await (const chunk of chunks) {
224
+ await Promise.all(
225
+ chunk.map(async ({ id, ...row }) => {
226
+ return await wdb(tableName).where("id", id).update(row);
227
+ })
228
+ );
229
+ }
230
+ }
231
+ }
@@ -0,0 +1,29 @@
1
+ import { FastifyInstance } from "fastify";
2
+ import { ZodIssue } from "zod";
3
+ import { isSoException } from "./so-exceptions";
4
+
5
+ export function setupErrorHandler(server: FastifyInstance) {
6
+ server.setErrorHandler((error, _request, reply) => {
7
+ error.statusCode ??= 400;
8
+
9
+ if (isSoException(error) && error.payload && Array.isArray(error.payload)) {
10
+ const issues = error.payload as ZodIssue[];
11
+ const [issue] = issues;
12
+ const message = `${issue.message} (${issue.path.join("/")})`;
13
+ reply.status(error.statusCode <= 501 ? error.statusCode : 501).send({
14
+ name: error.name,
15
+ code: error.code,
16
+ message: message,
17
+ validationErrors: error.validation,
18
+ issues,
19
+ });
20
+ } else {
21
+ reply.status(error.statusCode <= 501 ? error.statusCode : 501).send({
22
+ name: error.name,
23
+ code: error.code,
24
+ message: error.message,
25
+ validationErrors: error.validation,
26
+ });
27
+ }
28
+ });
29
+ }
@@ -0,0 +1,91 @@
1
+ export abstract class SoException extends Error {
2
+ constructor(
3
+ public readonly statusCode: number,
4
+ public message: string,
5
+ public payload?: unknown
6
+ ) {
7
+ super(message);
8
+ }
9
+ }
10
+
11
+ export function isSoException(err: any): err is SoException {
12
+ return err.statusCode !== undefined;
13
+ }
14
+
15
+ /*
16
+ 잘못된 매개변수 등 요청사항에 문제가 있는 경우
17
+ */
18
+ export class BadRequestException extends SoException {
19
+ constructor(public message = "Bad Request", public payload?: unknown) {
20
+ super(400, message, payload);
21
+ }
22
+ }
23
+
24
+ /*
25
+ 로그인이 반드시 필요한 케이스에 로그아웃 상태인 경우 / 접근 권한이 없는 요청시
26
+ */
27
+ export class UnauthorizedException extends SoException {
28
+ constructor(public message = "Unauthorized", public payload?: unknown) {
29
+ super(401, message, payload);
30
+ }
31
+ }
32
+
33
+ /*
34
+ 존재하지 않는 레코드에 접근시
35
+ */
36
+ export class NotFoundException extends SoException {
37
+ constructor(public message = "Not Found", public payload?: unknown) {
38
+ super(404, message, payload);
39
+ }
40
+ }
41
+
42
+ /*
43
+ 현재 상태에서 처리가 불가능한 케이스
44
+ */
45
+ export class ServiceUnavailableException extends SoException {
46
+ constructor(
47
+ public message = "Service Unavailable",
48
+ public payload?: unknown
49
+ ) {
50
+ super(503, message, payload);
51
+ }
52
+ }
53
+
54
+ /*
55
+ 내부 처리 로직 (외부 API 콜 포함) 오류 발생시
56
+ */
57
+ export class InternalServerErrorException extends SoException {
58
+ constructor(
59
+ public message = "Internal Server Error",
60
+ public payload?: unknown
61
+ ) {
62
+ super(500, message, payload);
63
+ }
64
+ }
65
+
66
+ /*
67
+ 이미 처리함
68
+ */
69
+ export class AlreadyProcessedException extends SoException {
70
+ constructor(public message = "Already Processed", public payload?: unknown) {
71
+ super(641, message, payload);
72
+ }
73
+ }
74
+
75
+ /*
76
+ 중복 허용하지 않는 케이스에 중복 요청
77
+ */
78
+ export class DuplicateRowException extends SoException {
79
+ constructor(public message = "Duplicate Row", public payload?: unknown) {
80
+ super(642, message, payload);
81
+ }
82
+ }
83
+
84
+ /*
85
+ 뭔가를 하려고 했으나 대상이 없음
86
+ */
87
+ export class TargetNotFoundException extends SoException {
88
+ constructor(public message = "Target Not Found", public payload?: unknown) {
89
+ super(620, message, payload);
90
+ }
91
+ }
package/src/index.ts ADDED
@@ -0,0 +1,17 @@
1
+ export * from "./api/code-converters";
2
+ export * from "./api/context";
3
+ export * from "./api/decorators";
4
+ export * from "./api/init";
5
+ export * from "./database/base-model";
6
+ export * from "./database/db";
7
+ export * from "./database/upsert-builder";
8
+ export * from "./exceptions/error-handler";
9
+ export * from "./exceptions/so-exceptions";
10
+ export * from "./smd/smd-manager";
11
+ export * from "./smd/smd-utils";
12
+ export * from "./syncer/syncer";
13
+ export * from "./testing/fixture-manager";
14
+ export * from "./types/types";
15
+ export * from "./utils/controller";
16
+ export * from "./utils/model";
17
+ export * from "./utils/utils";
@@ -0,0 +1,119 @@
1
+ /*
2
+ fetch
3
+ */
4
+ import type { AxiosRequestConfig } from "axios";
5
+ import axios from "axios";
6
+ import { z, ZodIssue } from "zod";
7
+
8
+ export async function fetch(options: AxiosRequestConfig) {
9
+ try {
10
+ const res = await axios({
11
+ ...options,
12
+ });
13
+ return res.data;
14
+ } catch (e: unknown) {
15
+ if (axios.isAxiosError(e) && e.response && e.response.data) {
16
+ const d = e.response.data as {
17
+ message: string;
18
+ issues: ZodIssue[];
19
+ };
20
+ throw new SonamuError(e.response.status, d.message, d.issues);
21
+ }
22
+ throw e;
23
+ }
24
+ }
25
+
26
+ export class SonamuError extends Error {
27
+ isSonamuError: boolean;
28
+
29
+ constructor(
30
+ public code: number,
31
+ public message: string,
32
+ public issues: z.ZodIssue[]
33
+ ) {
34
+ super(message);
35
+ this.isSonamuError = true;
36
+ }
37
+ }
38
+ export function isSonamuError(e: any): e is SonamuError {
39
+ return e && e.isSonamuError === true;
40
+ }
41
+
42
+ export function defaultCatch(e: any) {
43
+ if (isSonamuError(e)) {
44
+ alert(e.message);
45
+ } else {
46
+ alert("에러 발생");
47
+ }
48
+ }
49
+
50
+ /*
51
+ Isomorphic Types
52
+ */
53
+ export type ListResult<T> = {
54
+ rows: T[];
55
+ total?: number;
56
+ };
57
+
58
+ export type EnumsLabel<T extends string, L extends "ko" | "en"> = {
59
+ [key in T]: { [lang in L]: string };
60
+ };
61
+ export type EnumsLabelKo<T extends string> = EnumsLabel<T, "ko">;
62
+
63
+ /*
64
+ SWR
65
+ */
66
+ export type SwrOptions = {
67
+ conditional?: () => boolean;
68
+ };
69
+ export type SWRError = {
70
+ name: string;
71
+ message: string;
72
+ statusCode: number;
73
+ };
74
+ export async function swrFetcher(
75
+ url: string,
76
+ params: string = ""
77
+ ): Promise<any> {
78
+ try {
79
+ const res = await axios.get(url + "?" + params);
80
+ return res.data;
81
+ } catch (e: any) {
82
+ const error: any = new Error(
83
+ e.response.data.message ?? e.response.message ?? "Unknown"
84
+ );
85
+ error.statusCode = e.response?.data.statusCode ?? e.response.status;
86
+ throw error;
87
+ }
88
+ }
89
+ export function handleConditional(
90
+ route: string | string[],
91
+ conditional?: () => boolean
92
+ ): string | string[] | null {
93
+ if (conditional) {
94
+ return conditional() ? route : null;
95
+ }
96
+ return route;
97
+ }
98
+
99
+ /*
100
+ Utils
101
+ */
102
+ export function zArrayable<T extends z.ZodTypeAny>(
103
+ shape: T
104
+ ): z.ZodUnion<[T, z.ZodArray<T, "many">]> {
105
+ return z.union([shape, shape.array()]);
106
+ }
107
+
108
+ /*
109
+ Custom Scalars
110
+ */
111
+ export const SQLDateTimeString = z
112
+ .string()
113
+ .regex(/([0-9]{4}-[0-9]{2}-[0-9]{2}( [0-9]{2}:[0-9]{2}:[0-9]{2})*)$/, {
114
+ message: "잘못된 SQLDate 타입",
115
+ })
116
+ .min(10)
117
+ .max(19)
118
+ .describe("SQLDateTimeString");
119
+ export type SQLDateTimeString = z.infer<typeof SQLDateTimeString>;