swagger-fsd-gen 1.0.0

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.
@@ -0,0 +1,341 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Swagger API 클라이언트 자동 생성 도구
5
+ * - ky HTTP 클라이언트 기반 API 클래스 생성
6
+ * - TanStack Query 훅 생성 (useQuery, useMutation)
7
+ * - FSD(Feature-Sliced Design) 패턴 적용
8
+ */
9
+
10
+ import path from "node:path";
11
+ import minimist from "minimist";
12
+ import { fileURLToPath } from "url";
13
+ import { generateApi } from "swagger-typescript-api";
14
+ import { fetchSwagger } from "../utils/fetch-swagger.js";
15
+ import { writeFileToPath } from "../utils/file.js";
16
+ import { AnyOfSchemaParser } from "../utils/parser.js";
17
+ import { isUrl } from "../utils/url.js";
18
+
19
+ const __filename = fileURLToPath(import.meta.url);
20
+ const __dirname = path.dirname(__filename);
21
+
22
+ /**
23
+ * 명령행 인수 파싱
24
+ * @returns {Object} 파싱된 인수들
25
+ */
26
+ const parseArguments = () => {
27
+ const argv = minimist(process.argv.slice(2), {
28
+ string: [
29
+ "uri",
30
+ "username",
31
+ "password",
32
+ "dto-output-path",
33
+ "api-output-path",
34
+ "api-instance-output-path",
35
+ "query-output-path",
36
+ "mutation-output-path",
37
+ "project-template",
38
+ ],
39
+ alias: {
40
+ u: "uri",
41
+ un: "username",
42
+ pw: "password",
43
+ dp: "dto-output-path",
44
+ ap: "api-output-path",
45
+ aip: "api-instance-output-path",
46
+ qp: "query-output-path",
47
+ mp: "mutation-output-path",
48
+ pt: "project-template",
49
+ },
50
+ });
51
+
52
+ return {
53
+ uri: argv.uri,
54
+ username: argv.username,
55
+ password: argv.password,
56
+ dtoOutputPath: argv["dto-output-path"],
57
+ apiOutputPath: argv["api-output-path"],
58
+ apiInstanceOutputPath: argv["api-instance-output-path"],
59
+ queryOutputPath: argv["query-output-path"],
60
+ mutationOutputPath: argv["mutation-output-path"],
61
+ projectTemplate: argv["project-template"],
62
+ };
63
+ };
64
+
65
+ /**
66
+ * 출력 경로 설정 (FSD 패턴 기본값)
67
+ * @param {Object} args - 명령행 인수
68
+ * @returns {Object} 설정된 출력 경로들
69
+ */
70
+ const setupOutputPaths = (args) => {
71
+ return {
72
+ // DTO 타입 정의 파일 (공통)
73
+ dto: {
74
+ relativePath: args.dtoOutputPath ?? "src/shared/api/dto.ts",
75
+ absolutePath: path.resolve(
76
+ process.cwd(),
77
+ args.dtoOutputPath ?? "src/shared/api/dto.ts"
78
+ ),
79
+ },
80
+ // API 클래스 파일 (모듈별)
81
+ api: {
82
+ relativePath:
83
+ args.apiOutputPath ?? "src/entities/{moduleName}/api/index.ts",
84
+ absolutePath: path.resolve(
85
+ process.cwd(),
86
+ args.apiOutputPath ?? "src/entities/{moduleName}/api/index.ts"
87
+ ),
88
+ },
89
+ // API 인스턴스 파일 (모듈별)
90
+ apiInstance: {
91
+ relativePath:
92
+ args.apiInstanceOutputPath ??
93
+ "src/entities/{moduleName}/api/instance.ts",
94
+ absolutePath: path.resolve(
95
+ process.cwd(),
96
+ args.apiInstanceOutputPath ??
97
+ "src/entities/{moduleName}/api/instance.ts"
98
+ ),
99
+ },
100
+ // TanStack Query 훅 파일 (모듈별)
101
+ query: {
102
+ relativePath:
103
+ args.queryOutputPath ?? "src/entities/{moduleName}/api/queries.ts",
104
+ absolutePath: path.resolve(
105
+ process.cwd(),
106
+ args.queryOutputPath ?? "src/entities/{moduleName}/api/queries.ts"
107
+ ),
108
+ },
109
+ // TanStack Mutation 훅 파일 (모듈별)
110
+ mutation: {
111
+ relativePath:
112
+ args.mutationOutputPath ?? "src/entities/{moduleName}/api/mutations.ts",
113
+ absolutePath: path.resolve(
114
+ process.cwd(),
115
+ args.mutationOutputPath ?? "src/entities/{moduleName}/api/mutations.ts"
116
+ ),
117
+ },
118
+ };
119
+ };
120
+
121
+ /**
122
+ * 사용법 출력
123
+ * @param {Object} outputPaths - 출력 경로 설정
124
+ */
125
+ const printUsage = (outputPaths) => {
126
+ console.error(
127
+ "❗️ Error: Please provide the swagger URL or swagger file name"
128
+ );
129
+ console.error(
130
+ "Usage: generate-all --uri <swagger-url|swagger-file-name> " +
131
+ "[--username <username>] [--password <password>] " +
132
+ "[--dto-output-path <dto-output-path>] " +
133
+ "[--api-output-path <api-output-path>] " +
134
+ "[--query-output-path <query-output-path>] " +
135
+ "[--mutation-output-path <mutation-output-path>] " +
136
+ "[--project-template <project-template>]"
137
+ );
138
+ console.error(
139
+ `\nCurrent output paths:\n` +
140
+ ` DTO Path: ${outputPaths.dto.relativePath}\n` +
141
+ ` API Path: ${outputPaths.api.relativePath}\n` +
142
+ ` Query Path: ${outputPaths.query.relativePath}\n` +
143
+ ` Mutation Path: ${outputPaths.mutation.relativePath}`
144
+ );
145
+ };
146
+
147
+ /**
148
+ * swagger-typescript-api를 사용하여 API 코드 생성
149
+ * @param {Object} params - 생성 파라미터
150
+ * @returns {Promise<Object>} 생성된 파일들
151
+ */
152
+ export const generateApiCode = async ({
153
+ uri,
154
+ username,
155
+ password,
156
+ templates,
157
+ ...params
158
+ }) => {
159
+ const isLocal = !isUrl(uri);
160
+
161
+ return generateApi({
162
+ // 로컬 파일 또는 원격 URL 처리
163
+ input: isLocal ? path.resolve(process.cwd(), uri) : undefined,
164
+ spec: !isLocal && (await fetchSwagger(uri, username, password)),
165
+ templates: templates,
166
+ generateClient: true,
167
+ generateUnionEnums: true,
168
+ cleanOutput: false,
169
+ silent: true,
170
+ // Prettier 설정
171
+ prettier: {
172
+ semi: true,
173
+ trailingComma: "es5",
174
+ singleQuote: true,
175
+ printWidth: 100,
176
+ tabWidth: 2,
177
+ arrowParens: "always",
178
+ jsxBracketSameLine: false,
179
+ jsxSingleQuote: false,
180
+ },
181
+ modular: true,
182
+ moduleNameFirstTag: true, // Swagger 태그를 모듈명으로 사용
183
+ moduleNameIndex: 1,
184
+ // typeSuffix: "Dto", // 타입에 Dto 접미사 추가
185
+ generateRouteTypes: true,
186
+ schemaParsers: {
187
+ complexAnyOf: AnyOfSchemaParser,
188
+ },
189
+ ...params,
190
+ });
191
+ };
192
+
193
+ /**
194
+ * API 클래스와 DTO 파일 생성
195
+ * @param {Object} args - 명령행 인수
196
+ * @param {Object} outputPaths - 출력 경로 설정
197
+ */
198
+ const generateApiFunctionCode = async (args, outputPaths) => {
199
+ const { projectTemplate, uri, username, password } = args;
200
+
201
+ // 템플릿 경로 결정 (커스텀 템플릿 또는 기본 템플릿)
202
+ const templatePath = projectTemplate
203
+ ? path.resolve(process.cwd(), projectTemplate)
204
+ : path.resolve(__dirname, "../templates");
205
+
206
+ console.log("🔄 Generating API classes and DTOs...");
207
+
208
+ const apiFunctionCode = await generateApiCode({
209
+ uri,
210
+ username,
211
+ password,
212
+ templates: templatePath,
213
+ });
214
+
215
+ // 생성된 파일들을 적절한 위치에 저장
216
+ for (const { fileName, fileContent } of apiFunctionCode.files) {
217
+ // http-client 파일은 사용하지 않음 (ky 사용)
218
+ if (fileName === "http-client") continue;
219
+
220
+ if (fileName === "data-contracts") {
221
+ // DTO 타입 정의 파일 저장
222
+ await writeFileToPath(outputPaths.dto.absolutePath, fileContent);
223
+ console.log(`✅ Generated DTO: ${outputPaths.dto.relativePath}`);
224
+ } else {
225
+ // 모듈명 추출 (예: UserRoute -> user)
226
+ const moduleName = fileName.replace("Route", "").toLowerCase();
227
+
228
+ if (fileName.match(/Route$/)) {
229
+ // API 인스턴스 파일 생성 (예: UserRoute -> user/api/instance.ts)
230
+ const output = outputPaths.apiInstance.absolutePath.replace(
231
+ "{moduleName}",
232
+ moduleName
233
+ );
234
+ await writeFileToPath(output, fileContent);
235
+ console.log(
236
+ `✅ Generated API instance: ${output.replace(process.cwd(), ".")}`
237
+ );
238
+ } else {
239
+ // API 클래스 파일 생성 (예: User -> user/api/index.ts)
240
+ const output = outputPaths.api.absolutePath.replace(
241
+ "{moduleName}",
242
+ moduleName
243
+ );
244
+ await writeFileToPath(output, fileContent);
245
+ console.log(
246
+ `✅ Generated API class: ${output.replace(process.cwd(), ".")}`
247
+ );
248
+ }
249
+ }
250
+ }
251
+ };
252
+
253
+ /**
254
+ * TanStack Query 훅 파일 생성
255
+ * @param {Object} args - 명령행 인수
256
+ * @param {Object} outputPaths - 출력 경로 설정
257
+ */
258
+ const generateTanstackQueryCode = async (args, outputPaths) => {
259
+ const { projectTemplate, uri, username, password } = args;
260
+
261
+ // TanStack Query 템플릿 경로 결정
262
+ const templatePath = projectTemplate
263
+ ? path.resolve(process.cwd(), projectTemplate, "tanstack-query")
264
+ : path.resolve(__dirname, "../templates/tanstack-query");
265
+
266
+ console.log("🔄 Generating TanStack Query hooks...");
267
+
268
+ const tanstackQueryCode = await generateApiCode({
269
+ uri,
270
+ username,
271
+ password,
272
+ templates: templatePath,
273
+ });
274
+
275
+ // 생성된 파일들을 적절한 위치에 저장
276
+ for (const { fileName, fileContent } of tanstackQueryCode.files) {
277
+ // 불필요한 파일들 제외
278
+ if (fileName === "http-client" || fileName === "data-contracts") continue;
279
+
280
+ const moduleName = fileName.replace("Route", "").toLowerCase();
281
+
282
+ if (fileName.match(/Route$/)) {
283
+ // Mutation 훅 파일 생성
284
+ const output = outputPaths.mutation.absolutePath.replace(
285
+ "{moduleName}",
286
+ moduleName
287
+ );
288
+ await writeFileToPath(output, fileContent);
289
+ console.log(
290
+ `✅ Generated mutations: ${output.replace(process.cwd(), ".")}`
291
+ );
292
+ } else {
293
+ // Query 훅 파일 생성
294
+ const output = outputPaths.query.absolutePath.replace(
295
+ "{moduleName}",
296
+ moduleName
297
+ );
298
+ await writeFileToPath(output, fileContent);
299
+ console.log(
300
+ `✅ Generated queries: ${output.replace(process.cwd(), ".")}`
301
+ );
302
+ }
303
+ }
304
+ };
305
+
306
+ /**
307
+ * 메인 실행 함수
308
+ */
309
+ const main = async () => {
310
+ console.log("🚀 Starting Swagger API client generation...\n");
311
+
312
+ const args = parseArguments();
313
+ const outputPaths = setupOutputPaths(args);
314
+
315
+ // URI 필수 체크
316
+ if (!args.uri) {
317
+ printUsage(outputPaths);
318
+ process.exit(1);
319
+ }
320
+
321
+ try {
322
+ // 1. API 클래스와 DTO 생성
323
+ await generateApiFunctionCode(args, outputPaths);
324
+
325
+ // 2. TanStack Query 훅 생성
326
+ await generateTanstackQueryCode(args, outputPaths);
327
+
328
+ console.log("\n🎉 API client generation completed successfully!");
329
+ console.log("\n📁 Generated files:");
330
+ console.log(` - DTOs: ${outputPaths.dto.relativePath}`);
331
+ console.log(` - API classes: ${outputPaths.api.relativePath}`);
332
+ console.log(` - Query hooks: ${outputPaths.query.relativePath}`);
333
+ console.log(` - Mutation hooks: ${outputPaths.mutation.relativePath}`);
334
+ } catch (error) {
335
+ console.error("\n❌ Error during generation:");
336
+ console.error(error.message);
337
+ process.exit(1);
338
+ }
339
+ };
340
+
341
+ main();
Binary file
@@ -0,0 +1,77 @@
1
+ <%
2
+ /**
3
+ * API 클래스 생성 템플릿
4
+ * - Swagger 태그별로 API 클래스 생성
5
+ * - ky HTTP 클라이언트 기반
6
+ * - 타입 안전성을 위한 TypeScript 지원
7
+ * - 쿼리 파라미터 처리를 위한 유틸리티 메서드 포함
8
+ */
9
+
10
+ const { utils, route, config, modelTypes } = it;
11
+ const { _, pascalCase, require } = utils;
12
+
13
+ // 모듈명을 PascalCase로 변환하여 클래스명 생성 (예: user -> UserApi)
14
+ const apiClassName = pascalCase(route.moduleName);
15
+
16
+ // 해당 모듈의 모든 API 엔드포인트
17
+ const routes = route.routes;
18
+
19
+ // 사용 가능한 DTO 타입들의 이름 목록
20
+ const dataContracts = _.map(modelTypes, "name");
21
+ %>
22
+
23
+ import { KyInstance, Options } from 'ky';
24
+
25
+ <%~ includeFile('./dto-import.ejs', { ...it }) %>
26
+
27
+ /**
28
+ * <%= route.moduleName %> 모듈 API 클래스
29
+ * - ky HTTP 클라이언트를 사용한 API 호출
30
+ * - 타입 안전성을 위한 TypeScript 지원
31
+ * - 쿼리 파라미터 자동 변환 기능
32
+ */
33
+ export class <%= apiClassName %>Api {
34
+ private readonly instance: KyInstance;
35
+
36
+ /**
37
+ * API 클래스 생성자
38
+ * @param instance - ky HTTP 클라이언트 인스턴스
39
+ */
40
+ constructor(instance: KyInstance) {
41
+ this.instance = instance;
42
+ }
43
+
44
+ /**
45
+ * 객체를 URLSearchParams로 변환하는 정적 유틸리티 메서드
46
+ * - 중첩된 객체나 배열을 쿼리 파라미터로 변환
47
+ * - undefined 값은 자동으로 제외
48
+ * - 배열 값은 여러 개의 동일한 키로 변환
49
+ *
50
+ * @param params - 쿼리 파라미터 객체
51
+ * @returns URLSearchParams 인스턴스
52
+ *
53
+ * @example
54
+ * createSearchParams({ name: 'john', tags: ['tag1', 'tag2'] })
55
+ * // 결과: name=john&tags=tag1&tags=tag2
56
+ */
57
+ static createSearchParams(
58
+ params?: Record<string, string | number | boolean | Array<string | number | boolean>>
59
+ ): URLSearchParams {
60
+ const urlSearchParams = new URLSearchParams();
61
+
62
+ if (params) {
63
+ Object.entries(params)
64
+ .filter(([, value]) => value !== undefined)
65
+ .forEach(([key, value]) => {
66
+ const values = Array.isArray(value) ? value : [value];
67
+ values.forEach((v) => urlSearchParams.append(key, v.toString()));
68
+ });
69
+ }
70
+
71
+ return urlSearchParams;
72
+ }
73
+
74
+ <% for (const route of routes) { %>
75
+ <%~ includeFile('./procedure-call.ejs', { ...it, route, apiClassName }) %>
76
+ <% } %>
77
+ }
@@ -0,0 +1,94 @@
1
+ <%
2
+ /**
3
+ * DTO(Data Transfer Object) 타입 정의 템플릿
4
+ * - Swagger 스키마에서 TypeScript 타입 정의 생성
5
+ * - enum, interface, type 등 다양한 타입 지원
6
+ * - 제네릭 타입 지원
7
+ * - Query 파라미터 및 Header 타입 자동 생성
8
+ */
9
+
10
+ const { modelTypes, utils, config, routes } = it;
11
+ const { formatDescription, require, _, Ts, pascalCase } = utils;
12
+
13
+ /**
14
+ * 제네릭 타입 문자열 생성
15
+ * @param {Object} contract - 타입 계약 객체
16
+ * @returns {string} 제네릭 타입 문자열 (예: <T extends string = any>)
17
+ */
18
+ const buildGenerics = (contract) => {
19
+ if (!contract.genericArgs || !contract.genericArgs.length) return '';
20
+
21
+ return '<' + contract.genericArgs.map(({ name, default: defaultType, extends: extendsType }) => {
22
+ return [
23
+ name,
24
+ extendsType && `extends ${extendsType}`,
25
+ defaultType && `= ${defaultType}`,
26
+ ].join('')
27
+ }).join(',') + '>'
28
+ }
29
+
30
+ /**
31
+ * 데이터 계약 템플릿 정의
32
+ * - enum: 열거형 타입 생성
33
+ * - interface: 인터페이스 타입 생성 (실제로는 type alias 사용)
34
+ * - type: 타입 별칭 생성
35
+ */
36
+ const dataContractTemplates = {
37
+ // 열거형 타입 템플릿
38
+ enum: (contract) => {
39
+ return `enum ${contract.name} {\r\n${contract.content} \r\n }`;
40
+ },
41
+ // 인터페이스 타입 템플릿 (객체 형태)
42
+ interface: (contract) => {
43
+ return `type ${contract.name}${buildGenerics(contract)} = {\r\n${contract.content}}`;
44
+ },
45
+ // 일반 타입 별칭 템플릿
46
+ type: (contract) => {
47
+ return `type ${contract.name}${buildGenerics(contract)} = ${contract.content}`;
48
+ },
49
+ }
50
+ %>
51
+
52
+ <% if (config.internalTemplateOptions.addUtilRequiredKeysType) { %>
53
+ /**
54
+ * 유틸리티 타입: 특정 키를 필수로 만드는 헬퍼 타입
55
+ * @template T - 원본 타입
56
+ * @template K - 필수로 만들 키들
57
+ */
58
+ type <%~ config.Ts.CodeGenKeyword.UtilRequiredKeys %><T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>
59
+ <% } %>
60
+
61
+ <% for (const contract of modelTypes) { %>
62
+ <%~ includeFile('@base/data-contract-jsdoc.ejs', { ...it, data: { ...contract, ...contract.typeData } }) %>
63
+ <%~ contract.internal ? '' : 'export'%> <%~ (dataContractTemplates[contract.typeIdentifier] || dataContractTemplates.type)(contract) %>
64
+
65
+
66
+ <% } %>
67
+
68
+ <%
69
+ /**
70
+ * API 엔드포인트별 Query 파라미터 및 Header 타입 생성
71
+ * - 각 API 엔드포인트의 쿼리 파라미터를 별도 타입으로 정의
72
+ * - 각 API 엔드포인트의 헤더를 별도 타입으로 정의
73
+ * - TanStack Query와 API 클래스에서 사용
74
+ */
75
+ %>
76
+ <% for (const route of routes.combined) { %>
77
+ <% for (const api of route.routes) { %>
78
+
79
+ <% const queryName = `${pascalCase(api.routeName.usage)}QueryParams`; %>
80
+ <% const queryContent = api.specificArgs.query?.type; %>
81
+ <% if (queryContent) { %>
82
+ /** <%= api.routeName.usage %> API의 쿼리 파라미터 타입 */
83
+ export type <%= queryName %> = <%= queryContent %>;
84
+ <% } %>
85
+
86
+ <% const headerName = `${pascalCase(api.routeName.usage)}Headers`; %>
87
+ <% const headerContent = api.specificArgs.headers?.type; %>
88
+ <% if (headerContent) { %>
89
+ /** <%= api.routeName.usage %> API의 헤더 타입 */
90
+ export type <%~ headerName %> = <%~ headerContent %>;
91
+ <% } %>
92
+
93
+ <% } %>
94
+ <% } %>
@@ -0,0 +1,27 @@
1
+ <%
2
+ /**
3
+ * DTO Import 템플릿
4
+ * - 생성된 DTO 타입들을 import하는 구문 생성
5
+ * - 상대 경로를 사용하여 dto.ts 파일에서 타입 가져오기
6
+ * - API 클래스와 TanStack Query 훅에서 사용
7
+ */
8
+
9
+ const { modelTypes, utils } = it;
10
+ const { _ } = utils;
11
+
12
+ // 사용 가능한 모든 DTO 타입명 추출
13
+ const dataContracts = _.map(modelTypes, "name");
14
+ %>
15
+
16
+ <% if (dataContracts?.length) { %>
17
+ /**
18
+ * 생성된 DTO 타입들을 import
19
+ * - 모든 API에서 사용되는 공통 타입 정의
20
+ * - 요청/응답 데이터의 타입 안전성 보장
21
+ */
22
+ import type {
23
+ <% for (const dataContract of dataContracts) { %>
24
+ <%= dataContract %>,
25
+ <% } %>
26
+ } from '../../../shared/api/dto';
27
+ <% } %>
@@ -0,0 +1,103 @@
1
+ <%
2
+ /**
3
+ * API 메서드 호출 템플릿
4
+ * - 각 Swagger 엔드포인트에 대한 TypeScript 메서드 생성
5
+ * - ky HTTP 클라이언트를 사용한 요청 처리
6
+ * - 경로 파라미터, 쿼리 파라미터, 요청 본문, 헤더 지원
7
+ * - 타입 안전성을 위한 TypeScript 타입 적용
8
+ */
9
+
10
+ const { utils, route, config, apiClassName } = it;
11
+ const { _, pascalCase } = utils;
12
+ const routeDocs = includeFile("./route-docs", { config, route, utils });
13
+
14
+ /**
15
+ * API 메서드명 생성
16
+ * HTTP 메서드 + 경로를 조합하여 camelCase 함수명 생성
17
+ * 예: GET /users/{id} -> getUserById
18
+ */
19
+ const functionName = route.request.method + pascalCase(`${route.request.path
20
+ .split('/')
21
+ .map((segment) =>
22
+ segment.includes('${') ? `By_${segment.replace(/[${}]/g, '')}` : segment
23
+ )
24
+ .join('_')}`);
25
+
26
+ // 응답 타입 추출 (성공 응답의 타입 또는 any)
27
+ const responseType = route.responseBodyInfo?.success?.type || 'any';
28
+
29
+ // 경로 파라미터 추출 (예: /users/{id}에서 id)
30
+ const pathParams = _.values(route.request.parameters);
31
+ const hasPathParams = pathParams.length > 0;
32
+
33
+ // 요청 본문 데이터 (POST, PUT, PATCH에서 사용)
34
+ const payload = route.request.payload;
35
+
36
+ // 쿼리 파라미터 (URL 뒤에 ?key=value 형태)
37
+ const query = route.request.query;
38
+
39
+ // 커스텀 헤더
40
+ const headers = route.request.headers;
41
+ const headerName = headers ? `${pascalCase(route.routeName.usage)}Headers` : null;
42
+
43
+ // 쿼리 파라미터 타입명 생성
44
+ const queryTypeName = query ? `${pascalCase(route.routeName.usage)}QueryParams` : null;
45
+
46
+ /**
47
+ * 메서드 파라미터 문자열 생성
48
+ * @param {Array} params - 경로 파라미터 배열
49
+ * @param {Object} query - 쿼리 파라미터 객체
50
+ * @param {Object} payload - 요청 본문 객체
51
+ * @param {string} headerName - 헤더 타입명
52
+ * @returns {string} 메서드 파라미터 문자열
53
+ */
54
+ const generateParams = (params, query, payload, headerName) => {
55
+ const paramList = [
56
+ // 경로 파라미터 (예: id: number)
57
+ ...(params ? params.map(param => `${param.name}${param.optional ? '?' : ''}: ${param.type}`) : []),
58
+ // 쿼리 파라미터 (예: params?: UserQueryParams)
59
+ ...(query ? [`params${query.optional ? '?' : ''}: ${queryTypeName || query.type}`] : []),
60
+ // 요청 본문 (예: data: CreateUserDto)
61
+ ...(payload ? [`data${payload.optional ? '?' : ''}: ${payload.type}`] : [])
62
+ ];
63
+
64
+ // ky Options에서 제외할 타입들 (중복 방지)
65
+ const omitTypes = [
66
+ ...(query ? ["'searchParams'"] : []),
67
+ ...(payload ? ["'json'"] : []),
68
+ ...(headerName ? ["'headers'"] : [])
69
+ ];
70
+
71
+ // ky Options 타입 생성 (중복되는 속성 제외)
72
+ const optionsType = omitTypes.length > 0
73
+ ? `Omit<Options, ${omitTypes.join(' | ')}>${headerName ? ` & { headers: ${headerName} }` : ''}`
74
+ : 'Options';
75
+
76
+ // ky 인스턴스와 옵션 파라미터 추가
77
+ paramList.push('kyInstance?: KyInstance', `options?: ${optionsType}`);
78
+ return paramList.join(', ');
79
+ };
80
+ %>
81
+
82
+ /**
83
+ <%~ routeDocs.lines %> */
84
+ <%= functionName %>(<%~ generateParams(pathParams, query, payload, headerName) %>) {
85
+ // ky 인스턴스 선택 (전달된 인스턴스 또는 기본 인스턴스)
86
+ const instance = kyInstance ?? this.instance;
87
+
88
+ <% if (query) { %>
89
+ // 쿼리 파라미터를 URLSearchParams로 변환
90
+ const urlSearchParams = <%= apiClassName %>Api.createSearchParams(params);
91
+ <% } %>
92
+
93
+ // HTTP 요청 실행 및 JSON 응답 반환
94
+ return instance.<%= route.request.method.toLowerCase() %><<%= responseType %>>(`<%= route.request.path.replace(/{/g, '{').replace(/}/g, '}').slice(1,) %>`,{
95
+ <% if (query) { %>
96
+ searchParams: urlSearchParams, // 쿼리 파라미터 설정
97
+ <% } %>
98
+ <% if (payload) { %>
99
+ json: data, // 요청 본문 JSON 설정
100
+ <% } %>
101
+ ...options, // 추가 ky 옵션 병합
102
+ }).json();
103
+ }
@@ -0,0 +1,29 @@
1
+ <%
2
+ const { config, route, utils } = it;
3
+ const { _, formatDescription, fmtToJSDocLine, pascalCase, require } = utils;
4
+ const { raw, request, routeName } = route;
5
+
6
+ const jsDocDescription = raw.description ?
7
+ ` * @description ${formatDescription(raw.description, true)}` :
8
+ fmtToJSDocLine('No description', { eol: false });
9
+ const jsDocLines = _.compact([
10
+ _.size(raw.tags) && ` * @tags ${raw.tags.join(", ")}`,
11
+ raw.summary && ` * @summary ${raw.summary}`,
12
+ ` * @request ${_.upperCase(request.method)}:${raw.route}`,
13
+ raw.deprecated && ` * @deprecated`,
14
+ routeName.duplicate && ` * @originalName ${routeName.original}`,
15
+ routeName.duplicate && ` * @duplicate`,
16
+ request.security && ` * @secure`,
17
+ ...(config.generateResponses && raw.responsesTypes.length
18
+ ? raw.responsesTypes.map(
19
+ ({ type, status, description, isSuccess }) =>
20
+ ` * @response \`${status}\` \`${_.replace(_.replace(type, /\/\*/g, "\\*"), /\*\//g, "*\\")}\` ${description}`,
21
+ )
22
+ : []),
23
+ ]).map(str => str.trimEnd()).join("\n");
24
+
25
+ return {
26
+ description: jsDocDescription,
27
+ lines: jsDocLines,
28
+ }
29
+ %>
@@ -0,0 +1,15 @@
1
+ <%
2
+ const { utils, route, config, modelTypes } = it;
3
+ const { _, pascalCase, require } = utils;
4
+ const apiClassName = pascalCase(route.moduleName);
5
+ const routes = route.routes;
6
+ const dataContracts = _.map(modelTypes, "name");
7
+ %>
8
+
9
+ import { kyInstance } from "@/shared/api"
10
+
11
+ import { <%= apiClassName %>Api } from './index'
12
+
13
+ const <%= route.moduleName %>Api = new <%= apiClassName %>Api(kyInstance);
14
+
15
+ export { <%= route.moduleName %>Api };