swagger-fsd-gen 1.0.4 → 1.0.5
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 +9 -0
- package/README.md +2 -13
- package/USAGE.md +0 -13
- package/package.json +2 -3
- package/scripts/generate-all.js +9 -141
- package/templates/api.ejs +6 -13
- package/templates/procedure-call.ejs +11 -50
- package/templates/route-types.ejs +2 -3
- package/templates/tanstack-query/route-types.ejs +3 -11
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.5
|
|
4
|
+
|
|
5
|
+
- Rebased behavior to the 1.0.2 line.
|
|
6
|
+
- Fixed only one issue in TanStack mutation key generation:
|
|
7
|
+
- when API path segments contain `-`, generated mutation key identifiers now use `_`.
|
|
8
|
+
- example: `undo-upload` -> `UNDO_UPLOAD`
|
|
9
|
+
- No stale-file prune behavior.
|
|
10
|
+
- No generated folder/template structure changes.
|
|
11
|
+
|
|
3
12
|
## 1.0.4
|
|
4
13
|
|
|
5
14
|
- Added stale generated module pruning (enabled by default) so removed/renamed Swagger tags no longer leave old `entities/{module}/api/*` files behind.
|
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# swagger-fsd-gen
|
|
2
2
|
|
|
3
|
-
Swagger/OpenAPI 문서를 기반으로 **
|
|
3
|
+
Swagger/OpenAPI 문서를 기반으로 **ky + TanStack Query + FSD(Feature-Sliced Design) 패턴**에 맞는 API 클라이언트를 자동으로 생성하는 도구입니다.
|
|
4
4
|
|
|
5
5
|
## ✨ 주요 기능
|
|
6
6
|
|
|
7
|
-
- 🚀 **
|
|
7
|
+
- 🚀 **ky HTTP 클라이언트** 기반 API 클래스 자동 생성
|
|
8
8
|
- 🔄 **TanStack Query** 훅 자동 생성 (useQuery, useMutation)
|
|
9
9
|
- 📁 **FSD(Feature-Sliced Design)** 패턴 자동 적용
|
|
10
10
|
- 🔐 **HTTP Basic Authentication** 지원
|
|
@@ -33,15 +33,6 @@ 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
|
|
45
36
|
```
|
|
46
37
|
|
|
47
38
|
### 2. package.json에 스크립트 추가 (권장)
|
|
@@ -100,8 +91,6 @@ src/
|
|
|
100
91
|
| --uri | Swagger 문서 URL/경로 | 필수 |
|
|
101
92
|
| --username | Basic Auth 사용자명 | - |
|
|
102
93
|
| --password | Basic Auth 비밀번호 | - |
|
|
103
|
-
| --http-client | HTTP 클라이언트 선택 | ky (`axios`, `ky`) |
|
|
104
|
-
| --prune-stale | stale 생성 파일 정리 | true (`true`, `false`) |
|
|
105
94
|
| --dto-output-path | DTO 파일 경로 | src/shared/api/dto.ts |
|
|
106
95
|
| --api-output-path | API 클래스 경로 | src/entities/{moduleName}/api/index.ts |
|
|
107
96
|
| --query-output-path | Query 훅 경로 | src/entities/{moduleName}/api/queries.ts |
|
package/USAGE.md
CHANGED
|
@@ -40,15 +40,6 @@ 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
|
-
|
|
52
43
|
# 인증이 필요한 경우
|
|
53
44
|
generate-all --uri https://api.example.com/swagger.json --username admin --password secret
|
|
54
45
|
```
|
|
@@ -74,11 +65,7 @@ src/
|
|
|
74
65
|
### 1. 필요한 패키지 설치
|
|
75
66
|
|
|
76
67
|
```bash
|
|
77
|
-
# ky를 사용할 때
|
|
78
68
|
npm install ky @tanstack/react-query
|
|
79
|
-
|
|
80
|
-
# axios를 사용할 때
|
|
81
|
-
npm install axios @tanstack/react-query
|
|
82
69
|
```
|
|
83
70
|
|
|
84
71
|
### 2. API 인스턴스 설정
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "swagger-fsd-gen",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"main": "index.js",
|
|
5
|
-
"description": "Swagger API client generator that creates type-safe API clients using
|
|
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.",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
8
|
},
|
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
"api-client",
|
|
13
13
|
"typescript",
|
|
14
14
|
"ky",
|
|
15
|
-
"axios",
|
|
16
15
|
"tanstack-query",
|
|
17
16
|
"code-generator",
|
|
18
17
|
"fsd"
|
package/scripts/generate-all.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Swagger API 클라이언트 자동 생성 도구
|
|
5
|
-
* -
|
|
5
|
+
* - ky HTTP 클라이언트 기반 API 클래스 생성
|
|
6
6
|
* - TanStack Query 훅 생성 (useQuery, useMutation)
|
|
7
7
|
* - FSD(Feature-Sliced Design) 패턴 적용
|
|
8
8
|
*/
|
|
@@ -72,7 +72,6 @@ const parseArguments = () => {
|
|
|
72
72
|
"uri",
|
|
73
73
|
"username",
|
|
74
74
|
"password",
|
|
75
|
-
"http-client",
|
|
76
75
|
"dto-output-path",
|
|
77
76
|
"api-output-path",
|
|
78
77
|
"api-instance-output-path",
|
|
@@ -80,22 +79,16 @@ const parseArguments = () => {
|
|
|
80
79
|
"mutation-output-path",
|
|
81
80
|
"project-template",
|
|
82
81
|
],
|
|
83
|
-
boolean: ["prune-stale"],
|
|
84
82
|
alias: {
|
|
85
83
|
u: "uri",
|
|
86
84
|
un: "username",
|
|
87
85
|
pw: "password",
|
|
88
|
-
hc: "http-client",
|
|
89
86
|
dp: "dto-output-path",
|
|
90
87
|
ap: "api-output-path",
|
|
91
88
|
aip: "api-instance-output-path",
|
|
92
89
|
qp: "query-output-path",
|
|
93
90
|
mp: "mutation-output-path",
|
|
94
91
|
pt: "project-template",
|
|
95
|
-
ps: "prune-stale",
|
|
96
|
-
},
|
|
97
|
-
default: {
|
|
98
|
-
"prune-stale": true,
|
|
99
92
|
},
|
|
100
93
|
});
|
|
101
94
|
|
|
@@ -103,34 +96,15 @@ const parseArguments = () => {
|
|
|
103
96
|
uri: argv.uri,
|
|
104
97
|
username: argv.username,
|
|
105
98
|
password: argv.password,
|
|
106
|
-
httpClient: argv["http-client"],
|
|
107
99
|
dtoOutputPath: argv["dto-output-path"],
|
|
108
100
|
apiOutputPath: argv["api-output-path"],
|
|
109
101
|
apiInstanceOutputPath: argv["api-instance-output-path"],
|
|
110
102
|
queryOutputPath: argv["query-output-path"],
|
|
111
103
|
mutationOutputPath: argv["mutation-output-path"],
|
|
112
104
|
projectTemplate: argv["project-template"],
|
|
113
|
-
pruneStale: argv["prune-stale"],
|
|
114
105
|
};
|
|
115
106
|
};
|
|
116
107
|
|
|
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
|
-
|
|
134
108
|
/**
|
|
135
109
|
* 출력 경로 설정 (FSD 패턴 기본값)
|
|
136
110
|
* @param {Object} args - 명령행 인수
|
|
@@ -198,12 +172,10 @@ const printUsage = (outputPaths) => {
|
|
|
198
172
|
console.error(
|
|
199
173
|
"Usage: generate-all --uri <swagger-url|swagger-file-name> " +
|
|
200
174
|
"[--username <username>] [--password <password>] " +
|
|
201
|
-
"[--http-client <axios|ky>] " +
|
|
202
175
|
"[--dto-output-path <dto-output-path>] " +
|
|
203
176
|
"[--api-output-path <api-output-path>] " +
|
|
204
177
|
"[--query-output-path <query-output-path>] " +
|
|
205
178
|
"[--mutation-output-path <mutation-output-path>] " +
|
|
206
|
-
"[--prune-stale <true|false>] " +
|
|
207
179
|
"[--project-template <project-template>]"
|
|
208
180
|
);
|
|
209
181
|
console.error(
|
|
@@ -215,79 +187,6 @@ const printUsage = (outputPaths) => {
|
|
|
215
187
|
);
|
|
216
188
|
};
|
|
217
189
|
|
|
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
|
-
|
|
291
190
|
/**
|
|
292
191
|
* swagger-typescript-api를 사용하여 API 코드 생성
|
|
293
192
|
* @param {Object} params - 생성 파라미터
|
|
@@ -297,12 +196,10 @@ export const generateApiCode = async ({
|
|
|
297
196
|
uri,
|
|
298
197
|
username,
|
|
299
198
|
password,
|
|
300
|
-
httpClient,
|
|
301
199
|
templates,
|
|
302
200
|
...params
|
|
303
201
|
}) => {
|
|
304
202
|
const isLocal = !isUrl(uri);
|
|
305
|
-
const httpClientType = httpClient === "axios" ? "axios" : "fetch";
|
|
306
203
|
|
|
307
204
|
return generateApi({
|
|
308
205
|
input: isLocal ? path.resolve(process.cwd(), uri) : undefined,
|
|
@@ -329,7 +226,6 @@ export const generateApiCode = async ({
|
|
|
329
226
|
schemaParsers: {
|
|
330
227
|
complexAnyOf: AnyOfSchemaParser,
|
|
331
228
|
},
|
|
332
|
-
httpClientType,
|
|
333
229
|
...params,
|
|
334
230
|
});
|
|
335
231
|
};
|
|
@@ -352,7 +248,7 @@ const formatWithProjectPrettier = (filePath) => {
|
|
|
352
248
|
* @param {Object} outputPaths - 출력 경로 설정
|
|
353
249
|
*/
|
|
354
250
|
const generateApiFunctionCode = async (args, outputPaths) => {
|
|
355
|
-
const { projectTemplate, uri, username, password
|
|
251
|
+
const { projectTemplate, uri, username, password } = args;
|
|
356
252
|
|
|
357
253
|
const templatePath = projectTemplate
|
|
358
254
|
? path.resolve(process.cwd(), projectTemplate)
|
|
@@ -364,14 +260,10 @@ const generateApiFunctionCode = async (args, outputPaths) => {
|
|
|
364
260
|
uri,
|
|
365
261
|
username,
|
|
366
262
|
password,
|
|
367
|
-
httpClient,
|
|
368
263
|
templates: templatePath,
|
|
369
|
-
|
|
370
|
-
prettier: { parser: "typescript" },
|
|
264
|
+
prettier: false, // prettier 비활성화
|
|
371
265
|
});
|
|
372
266
|
|
|
373
|
-
const generatedModules = new Set();
|
|
374
|
-
|
|
375
267
|
for (const { fileName, fileContent } of apiFunctionCode.files) {
|
|
376
268
|
if (fileName === "http-client") continue;
|
|
377
269
|
|
|
@@ -382,8 +274,7 @@ const generateApiFunctionCode = async (args, outputPaths) => {
|
|
|
382
274
|
formatWithProjectPrettier(outputPath);
|
|
383
275
|
console.log(`✅ Generated DTO: ${outputPaths.dto.relativePath}`);
|
|
384
276
|
} else {
|
|
385
|
-
const moduleName =
|
|
386
|
-
generatedModules.add(moduleName);
|
|
277
|
+
const moduleName = fileName.replace("Route", "").toLowerCase();
|
|
387
278
|
|
|
388
279
|
if (fileName.match(/Route$/)) {
|
|
389
280
|
outputPath = outputPaths.apiInstance.absolutePath.replace(
|
|
@@ -408,8 +299,6 @@ const generateApiFunctionCode = async (args, outputPaths) => {
|
|
|
408
299
|
}
|
|
409
300
|
}
|
|
410
301
|
}
|
|
411
|
-
|
|
412
|
-
return generatedModules;
|
|
413
302
|
};
|
|
414
303
|
|
|
415
304
|
/**
|
|
@@ -418,7 +307,7 @@ const generateApiFunctionCode = async (args, outputPaths) => {
|
|
|
418
307
|
* @param {Object} outputPaths - 출력 경로 설정
|
|
419
308
|
*/
|
|
420
309
|
const generateTanstackQueryCode = async (args, outputPaths) => {
|
|
421
|
-
const { projectTemplate, uri, username, password
|
|
310
|
+
const { projectTemplate, uri, username, password } = args;
|
|
422
311
|
|
|
423
312
|
const templatePath = projectTemplate
|
|
424
313
|
? path.resolve(process.cwd(), projectTemplate, "tanstack-query")
|
|
@@ -430,19 +319,14 @@ const generateTanstackQueryCode = async (args, outputPaths) => {
|
|
|
430
319
|
uri,
|
|
431
320
|
username,
|
|
432
321
|
password,
|
|
433
|
-
httpClient,
|
|
434
322
|
templates: templatePath,
|
|
435
|
-
|
|
436
|
-
prettier: { parser: "typescript" },
|
|
323
|
+
prettier: false, // prettier 비활성화
|
|
437
324
|
});
|
|
438
325
|
|
|
439
|
-
const generatedModules = new Set();
|
|
440
|
-
|
|
441
326
|
for (const { fileName, fileContent } of tanstackQueryCode.files) {
|
|
442
327
|
if (fileName === "http-client" || fileName === "data-contracts") continue;
|
|
443
328
|
|
|
444
|
-
const moduleName =
|
|
445
|
-
generatedModules.add(moduleName);
|
|
329
|
+
const moduleName = fileName.replace("Route", "").toLowerCase();
|
|
446
330
|
let outputPath;
|
|
447
331
|
|
|
448
332
|
if (fileName.match(/Route$/)) {
|
|
@@ -467,8 +351,6 @@ const generateTanstackQueryCode = async (args, outputPaths) => {
|
|
|
467
351
|
);
|
|
468
352
|
}
|
|
469
353
|
}
|
|
470
|
-
|
|
471
|
-
return generatedModules;
|
|
472
354
|
};
|
|
473
355
|
|
|
474
356
|
/**
|
|
@@ -487,25 +369,11 @@ const main = async () => {
|
|
|
487
369
|
}
|
|
488
370
|
|
|
489
371
|
try {
|
|
490
|
-
args.httpClient = resolveHttpClient(args.httpClient);
|
|
491
|
-
console.log(`🌐 HTTP client mode: ${args.httpClient}`);
|
|
492
|
-
|
|
493
372
|
// 1. API 클래스와 DTO 생성
|
|
494
|
-
|
|
373
|
+
await generateApiFunctionCode(args, outputPaths);
|
|
495
374
|
|
|
496
375
|
// 2. TanStack Query 훅 생성
|
|
497
|
-
|
|
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
|
-
}
|
|
376
|
+
await generateTanstackQueryCode(args, outputPaths);
|
|
509
377
|
|
|
510
378
|
console.log("\n🎉 API client generation completed successfully!");
|
|
511
379
|
console.log("\n📁 Generated files:");
|
package/templates/api.ejs
CHANGED
|
@@ -2,14 +2,13 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* API 클래스 생성 템플릿
|
|
4
4
|
* - Swagger 태그별로 API 클래스 생성
|
|
5
|
-
* -
|
|
5
|
+
* - 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';
|
|
13
12
|
|
|
14
13
|
// 모듈명을 PascalCase로 변환하여 클래스명 생성 (예: user -> UserApi)
|
|
15
14
|
const apiClassName = pascalCase(route.moduleName);
|
|
@@ -21,32 +20,27 @@ const routes = route.routes;
|
|
|
21
20
|
const dataContracts = _.map(modelTypes, "name");
|
|
22
21
|
%>
|
|
23
22
|
|
|
24
|
-
|
|
25
|
-
import type { AxiosInstance, AxiosRequestConfig } from 'axios';
|
|
26
|
-
<% } else { %>
|
|
27
|
-
import type { KyInstance, Options } from 'ky';
|
|
28
|
-
<% } %>
|
|
23
|
+
import { KyInstance, Options } from 'ky';
|
|
29
24
|
|
|
30
25
|
<%~ includeFile('./dto-import.ejs', { ...it }) %>
|
|
31
26
|
|
|
32
27
|
/**
|
|
33
28
|
* <%= route.moduleName %> 모듈 API 클래스
|
|
34
|
-
* -
|
|
29
|
+
* - ky HTTP 클라이언트를 사용한 API 호출
|
|
35
30
|
* - 타입 안전성을 위한 TypeScript 지원
|
|
36
31
|
* - 쿼리 파라미터 자동 변환 기능
|
|
37
32
|
*/
|
|
38
33
|
export class <%= apiClassName %>Api {
|
|
39
|
-
private readonly instance:
|
|
34
|
+
private readonly instance: KyInstance;
|
|
40
35
|
|
|
41
36
|
/**
|
|
42
37
|
* API 클래스 생성자
|
|
43
|
-
* @param instance - HTTP 클라이언트 인스턴스
|
|
38
|
+
* @param instance - ky HTTP 클라이언트 인스턴스
|
|
44
39
|
*/
|
|
45
|
-
constructor(instance:
|
|
40
|
+
constructor(instance: KyInstance) {
|
|
46
41
|
this.instance = instance;
|
|
47
42
|
}
|
|
48
43
|
|
|
49
|
-
<% if (!isAxios) { %>
|
|
50
44
|
/**
|
|
51
45
|
* 객체를 URLSearchParams로 변환하는 정적 유틸리티 메서드
|
|
52
46
|
* - 중첩된 객체나 배열을 쿼리 파라미터로 변환
|
|
@@ -76,7 +70,6 @@ static createSearchParams(
|
|
|
76
70
|
|
|
77
71
|
return urlSearchParams;
|
|
78
72
|
}
|
|
79
|
-
<% } %>
|
|
80
73
|
|
|
81
74
|
<% for (const route of routes) { %>
|
|
82
75
|
<%~ includeFile('./procedure-call.ejs', { ...it, route, apiClassName }) %>
|
|
@@ -10,7 +10,6 @@
|
|
|
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';
|
|
14
13
|
|
|
15
14
|
/**
|
|
16
15
|
* API 메서드명 생성
|
|
@@ -43,7 +42,6 @@ const headerName = headers ? `${pascalCase(route.routeName.usage)}Headers` : nul
|
|
|
43
42
|
|
|
44
43
|
// 쿼리 파라미터 타입명 생성
|
|
45
44
|
const queryTypeName = query ? `${pascalCase(route.routeName.usage)}QueryParams` : null;
|
|
46
|
-
const instanceParamName = isAxios ? 'axiosInstance' : 'kyInstance';
|
|
47
45
|
|
|
48
46
|
/**
|
|
49
47
|
* 메서드 파라미터 문자열 생성
|
|
@@ -63,21 +61,20 @@ const generateParams = (params, query, payload, headerName) => {
|
|
|
63
61
|
...(payload ? [`data${payload.optional ? '?' : ''}: ${payload.type}`] : [])
|
|
64
62
|
];
|
|
65
63
|
|
|
66
|
-
//
|
|
64
|
+
// ky Options에서 제외할 타입들 (중복 방지)
|
|
67
65
|
const omitTypes = [
|
|
68
|
-
...(query ? [
|
|
69
|
-
...(payload ? [
|
|
66
|
+
...(query ? ["'searchParams'"] : []),
|
|
67
|
+
...(payload ? ["'json'"] : []),
|
|
70
68
|
...(headerName ? ["'headers'"] : [])
|
|
71
69
|
];
|
|
72
70
|
|
|
73
|
-
//
|
|
74
|
-
const optionsBaseType = isAxios ? 'AxiosRequestConfig' : 'Options';
|
|
71
|
+
// ky Options 타입 생성 (중복되는 속성 제외)
|
|
75
72
|
const optionsType = omitTypes.length > 0
|
|
76
|
-
? `Omit
|
|
77
|
-
:
|
|
73
|
+
? `Omit<Options, ${omitTypes.join(' | ')}>${headerName ? ` & { headers: ${headerName} }` : ''}`
|
|
74
|
+
: 'Options';
|
|
78
75
|
|
|
79
|
-
//
|
|
80
|
-
paramList.push(
|
|
76
|
+
// ky 인스턴스와 옵션 파라미터 추가
|
|
77
|
+
paramList.push('kyInstance?: KyInstance', `options?: ${optionsType}`);
|
|
81
78
|
return paramList.join(', ');
|
|
82
79
|
};
|
|
83
80
|
%>
|
|
@@ -85,49 +82,14 @@ const generateParams = (params, query, payload, headerName) => {
|
|
|
85
82
|
/**
|
|
86
83
|
<%~ routeDocs.lines %> */
|
|
87
84
|
<%= functionName %>(<%~ generateParams(pathParams, query, payload, headerName) %>) {
|
|
88
|
-
//
|
|
89
|
-
const instance =
|
|
85
|
+
// ky 인스턴스 선택 (전달된 인스턴스 또는 기본 인스턴스)
|
|
86
|
+
const instance = kyInstance ?? this.instance;
|
|
90
87
|
|
|
91
|
-
<% if (query
|
|
88
|
+
<% if (query) { %>
|
|
92
89
|
// 쿼리 파라미터를 URLSearchParams로 변환
|
|
93
90
|
const urlSearchParams = <%= apiClassName %>Api.createSearchParams(params);
|
|
94
91
|
<% } %>
|
|
95
92
|
|
|
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 { %>
|
|
131
93
|
// HTTP 요청 실행 및 JSON 응답 반환
|
|
132
94
|
return instance.<%= route.request.method.toLowerCase() %><<%= responseType %>>(`<%= route.request.path.replace(/{/g, '{').replace(/}/g, '}').slice(1,) %>`,{
|
|
133
95
|
<% if (query) { %>
|
|
@@ -138,5 +100,4 @@ return instance.<%= route.request.method.toLowerCase() %><<%= responseType %>>(`
|
|
|
138
100
|
<% } %>
|
|
139
101
|
...options, // 추가 ky 옵션 병합
|
|
140
102
|
}).json();
|
|
141
|
-
<% } %>
|
|
142
103
|
}
|
|
@@ -4,13 +4,12 @@ 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';
|
|
8
7
|
%>
|
|
9
8
|
|
|
10
|
-
import {
|
|
9
|
+
import { kyInstance } from "@/shared/api"
|
|
11
10
|
|
|
12
11
|
import { <%= apiClassName %>Api } from './index'
|
|
13
12
|
|
|
14
|
-
const <%= route.moduleName %>Api = new <%= apiClassName %>Api(
|
|
13
|
+
const <%= route.moduleName %>Api = new <%= apiClassName %>Api(kyInstance);
|
|
15
14
|
|
|
16
15
|
export { <%= route.moduleName %>Api };
|
|
@@ -6,10 +6,6 @@
|
|
|
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';
|
|
13
9
|
|
|
14
10
|
const getFunctionName = ({ request: { method, path } }) =>
|
|
15
11
|
`${method}${pascalCase(path
|
|
@@ -93,7 +89,7 @@ if (mutationConfigs.length > 0) {
|
|
|
93
89
|
.filter(config => config.hasVariables)
|
|
94
90
|
.map(config => {
|
|
95
91
|
const headerName = config.route.specificArgs.headers ? `${pascalCase(config.route.routeName.usage)}Headers` : null;
|
|
96
|
-
return `type T${pascalCase(config.functionName)}Variables = { ${config.requestParams},
|
|
92
|
+
return `type T${pascalCase(config.functionName)}Variables = { ${config.requestParams}, kyInstance?: KyInstance; options?: ${headerName ? `Omit<Options, 'headers'> & { headers: ${headerName} }` : 'Options'}; };`
|
|
97
93
|
})
|
|
98
94
|
.join('\n');
|
|
99
95
|
|
|
@@ -107,11 +103,7 @@ UseMutationOptions,
|
|
|
107
103
|
useQueryClient,
|
|
108
104
|
} from '@tanstack/react-query';
|
|
109
105
|
import { useMutation } from '@tanstack/react-query';
|
|
110
|
-
<% if (isAxios) { %>
|
|
111
|
-
import type{ AxiosInstance, AxiosRequestConfig } from 'axios';
|
|
112
|
-
<% } else { %>
|
|
113
106
|
import type{ KyInstance, Options } from 'ky';
|
|
114
|
-
<% } %>
|
|
115
107
|
|
|
116
108
|
<%~ includeFile('../dto-import.ejs', { ...it }) %>
|
|
117
109
|
|
|
@@ -127,8 +119,8 @@ const mutations = {
|
|
|
127
119
|
<% for (const config of mutationConfigs) { %>
|
|
128
120
|
<%= config.functionName %>: () => ({
|
|
129
121
|
mutationFn: <% if (config.hasVariables) { %>(variables: T<%= pascalCase(config.functionName) %>Variables) => {
|
|
130
|
-
const { <%= config.requestParamsWithoutTypes %>,
|
|
131
|
-
return <%= moduleName %>Api.<%= config.functionName %>(<%= config.requestParamsWithoutTypes %>,
|
|
122
|
+
const { <%= config.requestParamsWithoutTypes %>, kyInstance, options } = variables;
|
|
123
|
+
return <%= moduleName %>Api.<%= config.functionName %>(<%= config.requestParamsWithoutTypes %>, kyInstance, options);
|
|
132
124
|
}<% } else { %>() => <%= moduleName %>Api.<%= config.functionName %>()<% } %>,
|
|
133
125
|
mutationKey: <% ~moduleName.toUpperCase() %>_MUTATION_KEY.<% ~config.mutationKeyName %>,
|
|
134
126
|
}),
|