swagger-fsd-gen 1.0.2 → 1.0.4

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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,18 @@
1
+ # Changelog
2
+
3
+ ## 1.0.4
4
+
5
+ - Added stale generated module pruning (enabled by default) so removed/renamed Swagger tags no longer leave old `entities/{module}/api/*` files behind.
6
+ - Added `--prune-stale <true|false>` option to control stale-file pruning behavior.
7
+ - Fixed axios template method generation to use correct axios signatures:
8
+ - `post/put/patch(url, data, config)`
9
+ - `delete(url, config)` with optional `data` in config
10
+ - `get(url, config)` and other non-body methods.
11
+ - This resolves mixed-client leftovers (for example stale `ky` modules) when regenerating with `--http-client axios`.
12
+
13
+ ## 1.0.3
14
+
15
+ - Fixed invalid TypeScript identifiers in generated mutation keys when API paths contain `-` (for example `undo-upload`, `applied-campaigns`, `notification-settings`, `custom-filters`).
16
+ - Added stable HTTP client selection via `--http-client <ky|axios>` (default: `ky` for backward compatibility).
17
+ - Updated generated API templates to emit correct imports/types/calls for both `ky` and `axios`.
18
+ - Updated TanStack mutation template to emit client-specific variable types and options (`KyInstance/Options` or `AxiosInstance/AxiosRequestConfig`).
package/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # swagger-fsd-gen
2
2
 
3
- Swagger/OpenAPI 문서를 기반으로 **ky + TanStack Query + FSD(Feature-Sliced Design) 패턴**에 맞는 API 클라이언트를 자동으로 생성하는 도구입니다.
3
+ Swagger/OpenAPI 문서를 기반으로 **axios/ky + TanStack Query + FSD(Feature-Sliced Design) 패턴**에 맞는 API 클라이언트를 자동으로 생성하는 도구입니다.
4
4
 
5
5
  ## ✨ 주요 기능
6
6
 
7
- - 🚀 **ky HTTP 클라이언트** 기반 API 클래스 자동 생성
7
+ - 🚀 **axios/ky HTTP 클라이언트 선택** 기반 API 클래스 자동 생성
8
8
  - 🔄 **TanStack Query** 훅 자동 생성 (useQuery, useMutation)
9
9
  - 📁 **FSD(Feature-Sliced Design)** 패턴 자동 적용
10
10
  - 🔐 **HTTP Basic Authentication** 지원
@@ -33,6 +33,15 @@ npx generate-all --uri https://api.example.com/swagger.json --username your-user
33
33
  # yarn
34
34
  yarn fetch-swagger --url https://api.example.com/swagger.json --username your-username --password your-password
35
35
  yarn generate-all --uri https://api.example.com/swagger.json --username your-username --password your-password
36
+
37
+ # axios 클라이언트로 생성
38
+ yarn generate-all --uri https://api.example.com/swagger.json --http-client axios
39
+
40
+ # ky 클라이언트로 생성 (기본값)
41
+ yarn generate-all --uri https://api.example.com/swagger.json --http-client ky
42
+
43
+ # stale 파일 정리를 끄고 생성
44
+ yarn generate-all --uri https://api.example.com/swagger.json --prune-stale false
36
45
  ```
37
46
 
38
47
  ### 2. package.json에 스크립트 추가 (권장)
@@ -91,6 +100,8 @@ src/
91
100
  | --uri | Swagger 문서 URL/경로 | 필수 |
92
101
  | --username | Basic Auth 사용자명 | - |
93
102
  | --password | Basic Auth 비밀번호 | - |
103
+ | --http-client | HTTP 클라이언트 선택 | ky (`axios`, `ky`) |
104
+ | --prune-stale | stale 생성 파일 정리 | true (`true`, `false`) |
94
105
  | --dto-output-path | DTO 파일 경로 | src/shared/api/dto.ts |
95
106
  | --api-output-path | API 클래스 경로 | src/entities/{moduleName}/api/index.ts |
96
107
  | --query-output-path | Query 훅 경로 | src/entities/{moduleName}/api/queries.ts |
package/USAGE.md CHANGED
@@ -40,6 +40,15 @@ generate-all --uri https://api.example.com/swagger.json
40
40
  # 로컬 파일에서 생성
41
41
  generate-all --uri ./swagger/my-api.yml
42
42
 
43
+ # axios 클라이언트로 생성
44
+ generate-all --uri ./swagger/my-api.yml --http-client axios
45
+
46
+ # ky 클라이언트로 생성 (기본값)
47
+ generate-all --uri ./swagger/my-api.yml --http-client ky
48
+
49
+ # stale 생성 파일 정리 비활성화
50
+ generate-all --uri ./swagger/my-api.yml --prune-stale false
51
+
43
52
  # 인증이 필요한 경우
44
53
  generate-all --uri https://api.example.com/swagger.json --username admin --password secret
45
54
  ```
@@ -65,7 +74,11 @@ src/
65
74
  ### 1. 필요한 패키지 설치
66
75
 
67
76
  ```bash
77
+ # ky를 사용할 때
68
78
  npm install ky @tanstack/react-query
79
+
80
+ # axios를 사용할 때
81
+ npm install axios @tanstack/react-query
69
82
  ```
70
83
 
71
84
  ### 2. API 인스턴스 설정
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "swagger-fsd-gen",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "main": "index.js",
5
- "description": "Swagger API client generator that creates type-safe API clients using ky and TanStack Query with Feature Sliced Design pattern. Automatically generates API client code from Swagger/OpenAPI specifications.",
5
+ "description": "Swagger API client generator that creates type-safe API clients using axios or ky and TanStack Query with Feature Sliced Design pattern. Automatically generates API client code from Swagger/OpenAPI specifications.",
6
6
  "scripts": {
7
7
  "test": "echo \"Error: no test specified\" && exit 1"
8
8
  },
@@ -12,6 +12,7 @@
12
12
  "api-client",
13
13
  "typescript",
14
14
  "ky",
15
+ "axios",
15
16
  "tanstack-query",
16
17
  "code-generator",
17
18
  "fsd"
@@ -2,7 +2,7 @@
2
2
 
3
3
  /**
4
4
  * Swagger API 클라이언트 자동 생성 도구
5
- * - ky HTTP 클라이언트 기반 API 클래스 생성
5
+ * - axios/ky HTTP 클라이언트 기반 API 클래스 생성
6
6
  * - TanStack Query 훅 생성 (useQuery, useMutation)
7
7
  * - FSD(Feature-Sliced Design) 패턴 적용
8
8
  */
@@ -72,6 +72,7 @@ const parseArguments = () => {
72
72
  "uri",
73
73
  "username",
74
74
  "password",
75
+ "http-client",
75
76
  "dto-output-path",
76
77
  "api-output-path",
77
78
  "api-instance-output-path",
@@ -79,16 +80,22 @@ const parseArguments = () => {
79
80
  "mutation-output-path",
80
81
  "project-template",
81
82
  ],
83
+ boolean: ["prune-stale"],
82
84
  alias: {
83
85
  u: "uri",
84
86
  un: "username",
85
87
  pw: "password",
88
+ hc: "http-client",
86
89
  dp: "dto-output-path",
87
90
  ap: "api-output-path",
88
91
  aip: "api-instance-output-path",
89
92
  qp: "query-output-path",
90
93
  mp: "mutation-output-path",
91
94
  pt: "project-template",
95
+ ps: "prune-stale",
96
+ },
97
+ default: {
98
+ "prune-stale": true,
92
99
  },
93
100
  });
94
101
 
@@ -96,15 +103,34 @@ const parseArguments = () => {
96
103
  uri: argv.uri,
97
104
  username: argv.username,
98
105
  password: argv.password,
106
+ httpClient: argv["http-client"],
99
107
  dtoOutputPath: argv["dto-output-path"],
100
108
  apiOutputPath: argv["api-output-path"],
101
109
  apiInstanceOutputPath: argv["api-instance-output-path"],
102
110
  queryOutputPath: argv["query-output-path"],
103
111
  mutationOutputPath: argv["mutation-output-path"],
104
112
  projectTemplate: argv["project-template"],
113
+ pruneStale: argv["prune-stale"],
105
114
  };
106
115
  };
107
116
 
117
+ /**
118
+ * HTTP 클라이언트 모드 정규화
119
+ * @param {string | undefined} input
120
+ * @returns {"axios" | "ky"}
121
+ */
122
+ const resolveHttpClient = (input) => {
123
+ const normalized = (input ?? "ky").toString().toLowerCase();
124
+
125
+ if (normalized === "axios" || normalized === "ky") {
126
+ return normalized;
127
+ }
128
+
129
+ throw new Error(
130
+ `Invalid --http-client value "${input}". Supported values: axios | ky`
131
+ );
132
+ };
133
+
108
134
  /**
109
135
  * 출력 경로 설정 (FSD 패턴 기본값)
110
136
  * @param {Object} args - 명령행 인수
@@ -172,10 +198,12 @@ const printUsage = (outputPaths) => {
172
198
  console.error(
173
199
  "Usage: generate-all --uri <swagger-url|swagger-file-name> " +
174
200
  "[--username <username>] [--password <password>] " +
201
+ "[--http-client <axios|ky>] " +
175
202
  "[--dto-output-path <dto-output-path>] " +
176
203
  "[--api-output-path <api-output-path>] " +
177
204
  "[--query-output-path <query-output-path>] " +
178
205
  "[--mutation-output-path <mutation-output-path>] " +
206
+ "[--prune-stale <true|false>] " +
179
207
  "[--project-template <project-template>]"
180
208
  );
181
209
  console.error(
@@ -187,6 +215,79 @@ const printUsage = (outputPaths) => {
187
215
  );
188
216
  };
189
217
 
218
+ /**
219
+ * 모듈 파일명에서 모듈명을 추출
220
+ * @param {string} fileName
221
+ * @returns {string}
222
+ */
223
+ const extractModuleName = (fileName) => fileName.replace("Route", "").toLowerCase();
224
+
225
+ /**
226
+ * 템플릿 경로에서 현재 생성 대상이 아닌 모듈 파일을 정리
227
+ * @param {string} templatePath
228
+ * @param {Set<string>} generatedModules
229
+ */
230
+ const pruneStaleFilesByTemplate = async (templatePath, generatedModules) => {
231
+ if (!templatePath.includes("{moduleName}")) return;
232
+
233
+ const [prefix, suffix] = templatePath.split("{moduleName}");
234
+ const modulesRoot = prefix;
235
+
236
+ if (!fs.existsSync(modulesRoot)) return;
237
+
238
+ const moduleDirs = await fs.promises.readdir(modulesRoot, {
239
+ withFileTypes: true,
240
+ });
241
+
242
+ for (const dirent of moduleDirs) {
243
+ if (!dirent.isDirectory()) continue;
244
+
245
+ const moduleName = dirent.name;
246
+ if (generatedModules.has(moduleName)) continue;
247
+
248
+ const targetFilePath = path.join(modulesRoot, moduleName, suffix);
249
+ if (!fs.existsSync(targetFilePath)) continue;
250
+
251
+ await fs.promises.unlink(targetFilePath);
252
+ console.log(`🧹 Pruned stale generated file: ${targetFilePath}`);
253
+
254
+ const removableDirs = [
255
+ path.dirname(targetFilePath),
256
+ path.dirname(path.dirname(targetFilePath)),
257
+ ];
258
+
259
+ for (const candidateDir of removableDirs) {
260
+ if (!candidateDir.startsWith(modulesRoot)) continue;
261
+ if (!fs.existsSync(candidateDir)) continue;
262
+
263
+ const entries = await fs.promises.readdir(candidateDir);
264
+ if (entries.length === 0) {
265
+ await fs.promises.rmdir(candidateDir);
266
+ }
267
+ }
268
+ }
269
+ };
270
+
271
+ /**
272
+ * 이전 스웨거 기준 stale 생성 파일 정리
273
+ * @param {Object} outputPaths
274
+ * @param {Set<string>} generatedModules
275
+ */
276
+ const pruneStaleGeneratedFiles = async (outputPaths, generatedModules) => {
277
+ if (generatedModules.size === 0) return;
278
+
279
+ await pruneStaleFilesByTemplate(outputPaths.api.absolutePath, generatedModules);
280
+ await pruneStaleFilesByTemplate(
281
+ outputPaths.apiInstance.absolutePath,
282
+ generatedModules
283
+ );
284
+ await pruneStaleFilesByTemplate(outputPaths.query.absolutePath, generatedModules);
285
+ await pruneStaleFilesByTemplate(
286
+ outputPaths.mutation.absolutePath,
287
+ generatedModules
288
+ );
289
+ };
290
+
190
291
  /**
191
292
  * swagger-typescript-api를 사용하여 API 코드 생성
192
293
  * @param {Object} params - 생성 파라미터
@@ -196,10 +297,12 @@ export const generateApiCode = async ({
196
297
  uri,
197
298
  username,
198
299
  password,
300
+ httpClient,
199
301
  templates,
200
302
  ...params
201
303
  }) => {
202
304
  const isLocal = !isUrl(uri);
305
+ const httpClientType = httpClient === "axios" ? "axios" : "fetch";
203
306
 
204
307
  return generateApi({
205
308
  input: isLocal ? path.resolve(process.cwd(), uri) : undefined,
@@ -226,6 +329,7 @@ export const generateApiCode = async ({
226
329
  schemaParsers: {
227
330
  complexAnyOf: AnyOfSchemaParser,
228
331
  },
332
+ httpClientType,
229
333
  ...params,
230
334
  });
231
335
  };
@@ -248,7 +352,7 @@ const formatWithProjectPrettier = (filePath) => {
248
352
  * @param {Object} outputPaths - 출력 경로 설정
249
353
  */
250
354
  const generateApiFunctionCode = async (args, outputPaths) => {
251
- const { projectTemplate, uri, username, password } = args;
355
+ const { projectTemplate, uri, username, password, httpClient } = args;
252
356
 
253
357
  const templatePath = projectTemplate
254
358
  ? path.resolve(process.cwd(), projectTemplate)
@@ -260,10 +364,14 @@ const generateApiFunctionCode = async (args, outputPaths) => {
260
364
  uri,
261
365
  username,
262
366
  password,
367
+ httpClient,
263
368
  templates: templatePath,
264
- prettier: false, // prettier 비활성화
369
+ // swagger-typescript-api + prettier 조합에서 parser 추론 실패를 방지
370
+ prettier: { parser: "typescript" },
265
371
  });
266
372
 
373
+ const generatedModules = new Set();
374
+
267
375
  for (const { fileName, fileContent } of apiFunctionCode.files) {
268
376
  if (fileName === "http-client") continue;
269
377
 
@@ -274,7 +382,8 @@ const generateApiFunctionCode = async (args, outputPaths) => {
274
382
  formatWithProjectPrettier(outputPath);
275
383
  console.log(`✅ Generated DTO: ${outputPaths.dto.relativePath}`);
276
384
  } else {
277
- const moduleName = fileName.replace("Route", "").toLowerCase();
385
+ const moduleName = extractModuleName(fileName);
386
+ generatedModules.add(moduleName);
278
387
 
279
388
  if (fileName.match(/Route$/)) {
280
389
  outputPath = outputPaths.apiInstance.absolutePath.replace(
@@ -299,6 +408,8 @@ const generateApiFunctionCode = async (args, outputPaths) => {
299
408
  }
300
409
  }
301
410
  }
411
+
412
+ return generatedModules;
302
413
  };
303
414
 
304
415
  /**
@@ -307,7 +418,7 @@ const generateApiFunctionCode = async (args, outputPaths) => {
307
418
  * @param {Object} outputPaths - 출력 경로 설정
308
419
  */
309
420
  const generateTanstackQueryCode = async (args, outputPaths) => {
310
- const { projectTemplate, uri, username, password } = args;
421
+ const { projectTemplate, uri, username, password, httpClient } = args;
311
422
 
312
423
  const templatePath = projectTemplate
313
424
  ? path.resolve(process.cwd(), projectTemplate, "tanstack-query")
@@ -319,14 +430,19 @@ const generateTanstackQueryCode = async (args, outputPaths) => {
319
430
  uri,
320
431
  username,
321
432
  password,
433
+ httpClient,
322
434
  templates: templatePath,
323
- prettier: false, // prettier 비활성화
435
+ // swagger-typescript-api + prettier 조합에서 parser 추론 실패를 방지
436
+ prettier: { parser: "typescript" },
324
437
  });
325
438
 
439
+ const generatedModules = new Set();
440
+
326
441
  for (const { fileName, fileContent } of tanstackQueryCode.files) {
327
442
  if (fileName === "http-client" || fileName === "data-contracts") continue;
328
443
 
329
- const moduleName = fileName.replace("Route", "").toLowerCase();
444
+ const moduleName = extractModuleName(fileName);
445
+ generatedModules.add(moduleName);
330
446
  let outputPath;
331
447
 
332
448
  if (fileName.match(/Route$/)) {
@@ -351,6 +467,8 @@ const generateTanstackQueryCode = async (args, outputPaths) => {
351
467
  );
352
468
  }
353
469
  }
470
+
471
+ return generatedModules;
354
472
  };
355
473
 
356
474
  /**
@@ -369,11 +487,25 @@ const main = async () => {
369
487
  }
370
488
 
371
489
  try {
490
+ args.httpClient = resolveHttpClient(args.httpClient);
491
+ console.log(`🌐 HTTP client mode: ${args.httpClient}`);
492
+
372
493
  // 1. API 클래스와 DTO 생성
373
- await generateApiFunctionCode(args, outputPaths);
494
+ const generatedApiModules = await generateApiFunctionCode(args, outputPaths);
374
495
 
375
496
  // 2. TanStack Query 훅 생성
376
- await generateTanstackQueryCode(args, outputPaths);
497
+ const generatedTanstackModules = await generateTanstackQueryCode(
498
+ args,
499
+ outputPaths
500
+ );
501
+
502
+ if (args.pruneStale) {
503
+ const generatedModules = new Set([
504
+ ...generatedApiModules,
505
+ ...generatedTanstackModules,
506
+ ]);
507
+ await pruneStaleGeneratedFiles(outputPaths, generatedModules);
508
+ }
377
509
 
378
510
  console.log("\n🎉 API client generation completed successfully!");
379
511
  console.log("\n📁 Generated files:");
package/templates/api.ejs CHANGED
@@ -2,13 +2,14 @@
2
2
  /**
3
3
  * API 클래스 생성 템플릿
4
4
  * - Swagger 태그별로 API 클래스 생성
5
- * - ky HTTP 클라이언트 기반
5
+ * - axios/ky HTTP 클라이언트 기반
6
6
  * - 타입 안전성을 위한 TypeScript 지원
7
7
  * - 쿼리 파라미터 처리를 위한 유틸리티 메서드 포함
8
8
  */
9
9
 
10
10
  const { utils, route, config, modelTypes } = it;
11
11
  const { _, pascalCase, require } = utils;
12
+ const isAxios = config.httpClientType === 'axios';
12
13
 
13
14
  // 모듈명을 PascalCase로 변환하여 클래스명 생성 (예: user -> UserApi)
14
15
  const apiClassName = pascalCase(route.moduleName);
@@ -20,27 +21,32 @@ const routes = route.routes;
20
21
  const dataContracts = _.map(modelTypes, "name");
21
22
  %>
22
23
 
23
- import { KyInstance, Options } from 'ky';
24
+ <% if (isAxios) { %>
25
+ import type { AxiosInstance, AxiosRequestConfig } from 'axios';
26
+ <% } else { %>
27
+ import type { KyInstance, Options } from 'ky';
28
+ <% } %>
24
29
 
25
30
  <%~ includeFile('./dto-import.ejs', { ...it }) %>
26
31
 
27
32
  /**
28
33
  * <%= route.moduleName %> 모듈 API 클래스
29
- * - ky HTTP 클라이언트를 사용한 API 호출
34
+ * - 선택된 HTTP 클라이언트를 사용한 API 호출
30
35
  * - 타입 안전성을 위한 TypeScript 지원
31
36
  * - 쿼리 파라미터 자동 변환 기능
32
37
  */
33
38
  export class <%= apiClassName %>Api {
34
- private readonly instance: KyInstance;
39
+ private readonly instance: <%= isAxios ? 'AxiosInstance' : 'KyInstance' %>;
35
40
 
36
41
  /**
37
42
  * API 클래스 생성자
38
- * @param instance - ky HTTP 클라이언트 인스턴스
43
+ * @param instance - HTTP 클라이언트 인스턴스
39
44
  */
40
- constructor(instance: KyInstance) {
45
+ constructor(instance: <%= isAxios ? 'AxiosInstance' : 'KyInstance' %>) {
41
46
  this.instance = instance;
42
47
  }
43
48
 
49
+ <% if (!isAxios) { %>
44
50
  /**
45
51
  * 객체를 URLSearchParams로 변환하는 정적 유틸리티 메서드
46
52
  * - 중첩된 객체나 배열을 쿼리 파라미터로 변환
@@ -70,6 +76,7 @@ static createSearchParams(
70
76
 
71
77
  return urlSearchParams;
72
78
  }
79
+ <% } %>
73
80
 
74
81
  <% for (const route of routes) { %>
75
82
  <%~ includeFile('./procedure-call.ejs', { ...it, route, apiClassName }) %>
@@ -10,6 +10,7 @@
10
10
  const { utils, route, config, apiClassName } = it;
11
11
  const { _, pascalCase } = utils;
12
12
  const routeDocs = includeFile("./route-docs", { config, route, utils });
13
+ const isAxios = config.httpClientType === 'axios';
13
14
 
14
15
  /**
15
16
  * API 메서드명 생성
@@ -42,6 +43,7 @@ const headerName = headers ? `${pascalCase(route.routeName.usage)}Headers` : nul
42
43
 
43
44
  // 쿼리 파라미터 타입명 생성
44
45
  const queryTypeName = query ? `${pascalCase(route.routeName.usage)}QueryParams` : null;
46
+ const instanceParamName = isAxios ? 'axiosInstance' : 'kyInstance';
45
47
 
46
48
  /**
47
49
  * 메서드 파라미터 문자열 생성
@@ -61,20 +63,21 @@ const generateParams = (params, query, payload, headerName) => {
61
63
  ...(payload ? [`data${payload.optional ? '?' : ''}: ${payload.type}`] : [])
62
64
  ];
63
65
 
64
- // ky Options에서 제외할 타입들 (중복 방지)
66
+ // HTTP client options에서 제외할 타입들 (중복 방지)
65
67
  const omitTypes = [
66
- ...(query ? ["'searchParams'"] : []),
67
- ...(payload ? ["'json'"] : []),
68
+ ...(query ? [isAxios ? "'params'" : "'searchParams'"] : []),
69
+ ...(payload ? [isAxios ? "'data'" : "'json'"] : []),
68
70
  ...(headerName ? ["'headers'"] : [])
69
71
  ];
70
72
 
71
- // ky Options 타입 생성 (중복되는 속성 제외)
73
+ // HTTP client options 타입 생성 (중복되는 속성 제외)
74
+ const optionsBaseType = isAxios ? 'AxiosRequestConfig' : 'Options';
72
75
  const optionsType = omitTypes.length > 0
73
- ? `Omit<Options, ${omitTypes.join(' | ')}>${headerName ? ` & { headers: ${headerName} }` : ''}`
74
- : 'Options';
76
+ ? `Omit<${optionsBaseType}, ${omitTypes.join(' | ')}>${headerName ? ` & { headers: ${headerName} }` : ''}`
77
+ : optionsBaseType;
75
78
 
76
- // ky 인스턴스와 옵션 파라미터 추가
77
- paramList.push('kyInstance?: KyInstance', `options?: ${optionsType}`);
79
+ // HTTP 클라이언트 인스턴스와 옵션 파라미터 추가
80
+ paramList.push(`${instanceParamName}?: ${isAxios ? 'AxiosInstance' : 'KyInstance'}`, `options?: ${optionsType}`);
78
81
  return paramList.join(', ');
79
82
  };
80
83
  %>
@@ -82,14 +85,49 @@ const generateParams = (params, query, payload, headerName) => {
82
85
  /**
83
86
  <%~ routeDocs.lines %> */
84
87
  <%= functionName %>(<%~ generateParams(pathParams, query, payload, headerName) %>) {
85
- // ky 인스턴스 선택 (전달된 인스턴스 또는 기본 인스턴스)
86
- const instance = kyInstance ?? this.instance;
88
+ // HTTP 클라이언트 인스턴스 선택 (전달된 인스턴스 또는 기본 인스턴스)
89
+ const instance = <%= instanceParamName %> ?? this.instance;
87
90
 
88
- <% if (query) { %>
91
+ <% if (query && !isAxios) { %>
89
92
  // 쿼리 파라미터를 URLSearchParams로 변환
90
93
  const urlSearchParams = <%= apiClassName %>Api.createSearchParams(params);
91
94
  <% } %>
92
95
 
96
+ <% if (isAxios) { %>
97
+ <% if (['post', 'put', 'patch'].includes(route.request.method.toLowerCase())) { %>
98
+ return instance.<%= route.request.method.toLowerCase() %><<%= responseType %>>(
99
+ `<%= route.request.path.replace(/{/g, '{').replace(/}/g, '}').slice(1,) %>`,
100
+ <% if (payload) { %>
101
+ data,
102
+ <% } else { %>
103
+ undefined,
104
+ <% } %>
105
+ {
106
+ <% if (query) { %>
107
+ params, // 쿼리 파라미터 설정
108
+ <% } %>
109
+ ...options, // 추가 axios 옵션 병합
110
+ }
111
+ ).then((response) => response.data);
112
+ <% } else if (route.request.method.toLowerCase() === 'delete') { %>
113
+ return instance.delete<<%= responseType %>>(`<%= route.request.path.replace(/{/g, '{').replace(/}/g, '}').slice(1,) %>`, {
114
+ <% if (query) { %>
115
+ params, // 쿼리 파라미터 설정
116
+ <% } %>
117
+ <% if (payload) { %>
118
+ data, // 요청 본문 설정
119
+ <% } %>
120
+ ...options, // 추가 axios 옵션 병합
121
+ }).then((response) => response.data);
122
+ <% } else { %>
123
+ return instance.<%= route.request.method.toLowerCase() %><<%= responseType %>>(`<%= route.request.path.replace(/{/g, '{').replace(/}/g, '}').slice(1,) %>`, {
124
+ <% if (query) { %>
125
+ params, // 쿼리 파라미터 설정
126
+ <% } %>
127
+ ...options, // 추가 axios 옵션 병합
128
+ }).then((response) => response.data);
129
+ <% } %>
130
+ <% } else { %>
93
131
  // HTTP 요청 실행 및 JSON 응답 반환
94
132
  return instance.<%= route.request.method.toLowerCase() %><<%= responseType %>>(`<%= route.request.path.replace(/{/g, '{').replace(/}/g, '}').slice(1,) %>`,{
95
133
  <% if (query) { %>
@@ -100,4 +138,5 @@ return instance.<%= route.request.method.toLowerCase() %><<%= responseType %>>(`
100
138
  <% } %>
101
139
  ...options, // 추가 ky 옵션 병합
102
140
  }).json();
141
+ <% } %>
103
142
  }
@@ -4,12 +4,13 @@ const { _, pascalCase, require } = utils;
4
4
  const apiClassName = pascalCase(route.moduleName);
5
5
  const routes = route.routes;
6
6
  const dataContracts = _.map(modelTypes, "name");
7
+ const isAxios = config.httpClientType === 'axios';
7
8
  %>
8
9
 
9
- import { kyInstance } from "@/shared/api"
10
+ import { <%= isAxios ? 'axiosInstance' : 'kyInstance' %> } from "@/shared/api"
10
11
 
11
12
  import { <%= apiClassName %>Api } from './index'
12
13
 
13
- const <%= route.moduleName %>Api = new <%= apiClassName %>Api(kyInstance);
14
+ const <%= route.moduleName %>Api = new <%= apiClassName %>Api(<%= isAxios ? 'axiosInstance' : 'kyInstance' %>);
14
15
 
15
16
  export { <%= route.moduleName %>Api };
@@ -6,6 +6,10 @@
6
6
  const { utils, route, modelTypes, config } = it;
7
7
  const { pascalCase, internalCase: camelCase } = utils;
8
8
  const { moduleName, routes } = route;
9
+ const isAxios = config.httpClientType === 'axios';
10
+ const clientInstanceParam = isAxios ? 'axiosInstance' : 'kyInstance';
11
+ const clientInstanceType = isAxios ? 'AxiosInstance' : 'KyInstance';
12
+ const clientOptionsType = isAxios ? 'AxiosRequestConfig' : 'Options';
9
13
 
10
14
  const getFunctionName = ({ request: { method, path } }) =>
11
15
  `${method}${pascalCase(path
@@ -18,6 +22,12 @@ const getFunctionName = ({ request: { method, path } }) =>
18
22
  .join('_'))}`;
19
23
 
20
24
  const removeBracket = (type) => type.replace(/[()]/g, '')
25
+ const sanitizeIdentifierSegment = (segment) =>
26
+ segment
27
+ .replace(/[${}]/g, '')
28
+ .replace(/[^a-zA-Z0-9]/g, '_')
29
+ .replace(/_+/g, '_')
30
+ .replace(/^_+|_+$/g, '');
21
31
 
22
32
  const getRequestParams = ({ parameters = [], query, payload, routeName }) => {
23
33
  const queryParamsDto = query ? `${pascalCase(routeName.usage)}QueryParams` : '';
@@ -40,10 +50,9 @@ const buildMutationKeyName = ({ path, method }) =>
40
50
  .split('/')
41
51
  .filter(segment => segment )
42
52
  .map(segment =>
43
- segment.match(/\${/)
44
- ? segment.replace(/[${}]/g, '').toUpperCase()
45
- : segment.toUpperCase()
53
+ sanitizeIdentifierSegment(segment).toUpperCase()
46
54
  )
55
+ .filter(Boolean)
47
56
  .join('_')
48
57
 
49
58
  const buildPathMutationKey = ({ path }) =>
@@ -84,7 +93,7 @@ if (mutationConfigs.length > 0) {
84
93
  .filter(config => config.hasVariables)
85
94
  .map(config => {
86
95
  const headerName = config.route.specificArgs.headers ? `${pascalCase(config.route.routeName.usage)}Headers` : null;
87
- return `type T${pascalCase(config.functionName)}Variables = { ${config.requestParams}, kyInstance?: KyInstance; options?: ${headerName ? `Omit<Options, 'headers'> & { headers: ${headerName} }` : 'Options'}; };`
96
+ return `type T${pascalCase(config.functionName)}Variables = { ${config.requestParams}, ${clientInstanceParam}?: ${clientInstanceType}; options?: ${headerName ? `Omit<${clientOptionsType}, 'headers'> & { headers: ${headerName} }` : clientOptionsType}; };`
88
97
  })
89
98
  .join('\n');
90
99
 
@@ -98,7 +107,11 @@ UseMutationOptions,
98
107
  useQueryClient,
99
108
  } from '@tanstack/react-query';
100
109
  import { useMutation } from '@tanstack/react-query';
110
+ <% if (isAxios) { %>
111
+ import type{ AxiosInstance, AxiosRequestConfig } from 'axios';
112
+ <% } else { %>
101
113
  import type{ KyInstance, Options } from 'ky';
114
+ <% } %>
102
115
 
103
116
  <%~ includeFile('../dto-import.ejs', { ...it }) %>
104
117
 
@@ -114,8 +127,8 @@ const mutations = {
114
127
  <% for (const config of mutationConfigs) { %>
115
128
  <%= config.functionName %>: () => ({
116
129
  mutationFn: <% if (config.hasVariables) { %>(variables: T<%= pascalCase(config.functionName) %>Variables) => {
117
- const { <%= config.requestParamsWithoutTypes %>, kyInstance, options } = variables;
118
- return <%= moduleName %>Api.<%= config.functionName %>(<%= config.requestParamsWithoutTypes %>, kyInstance, options);
130
+ const { <%= config.requestParamsWithoutTypes %>, <%= clientInstanceParam %>, options } = variables;
131
+ return <%= moduleName %>Api.<%= config.functionName %>(<%= config.requestParamsWithoutTypes %>, <%= clientInstanceParam %>, options);
119
132
  }<% } else { %>() => <%= moduleName %>Api.<%= config.functionName %>()<% } %>,
120
133
  mutationKey: <% ~moduleName.toUpperCase() %>_MUTATION_KEY.<% ~config.mutationKeyName %>,
121
134
  }),