@acrool/rtk-query-codegen-openapi 1.1.0 → 1.1.2
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/lib/bin/cli.mjs +26 -26
- package/lib/bin/cli.mjs.map +1 -1
- package/lib/index.d.mts +9 -0
- package/lib/index.d.ts +9 -0
- package/lib/index.js +42 -17
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +42 -17
- package/lib/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/generators/rtk-enhance-endpoints-generator.ts +13 -5
- package/src/generators/rtk-query-generator.ts +3 -3
- package/src/generators/types-generator.ts +75 -43
- package/src/types.ts +27 -18
|
@@ -56,30 +56,30 @@ export function generateTypesFile(
|
|
|
56
56
|
// [Warning] Generated automatically - do not edit manually
|
|
57
57
|
|
|
58
58
|
`;
|
|
59
|
-
|
|
59
|
+
|
|
60
60
|
// 檢查是否需要引入 schema.ts
|
|
61
61
|
const hasSchemaTypes = schemaInterfaces && Object.keys(schemaInterfaces).length > 0;
|
|
62
62
|
if (hasSchemaTypes) {
|
|
63
63
|
importStatement += `import * as Schema from "../schema";\n`;
|
|
64
64
|
}
|
|
65
|
-
|
|
65
|
+
|
|
66
66
|
importStatement += '\n';
|
|
67
|
-
|
|
67
|
+
|
|
68
68
|
// 收集所有需要的類型定義
|
|
69
69
|
const typeDefinitions: string[] = [];
|
|
70
|
-
|
|
70
|
+
|
|
71
71
|
// 注意:不再在 types.ts 中重複生成 schema 類型
|
|
72
72
|
// schema 類型已經在 schema.ts 中生成,這裡直接使用 Schema.* 引用
|
|
73
|
-
|
|
73
|
+
|
|
74
74
|
// 無論是否有 schema,都要生成 endpoint 特定的 Req/Res 類型
|
|
75
75
|
const endpointTypes: string[] = [];
|
|
76
|
-
|
|
76
|
+
|
|
77
77
|
// 為每個端點生成 Req/Res 類型
|
|
78
78
|
endpointInfos.forEach(endpoint => {
|
|
79
79
|
// 使用 endpoint 中提供的準確類型名稱
|
|
80
80
|
const reqTypeName = endpoint.argTypeName;
|
|
81
81
|
const resTypeName = endpoint.responseTypeName;
|
|
82
|
-
|
|
82
|
+
|
|
83
83
|
// 生成 Request 類型(總是生成)
|
|
84
84
|
if (reqTypeName) {
|
|
85
85
|
const requestTypeContent = generateRequestTypeContent(endpoint, operationDefinitions, schemaTypeMap);
|
|
@@ -99,7 +99,7 @@ export function generateTypesFile(
|
|
|
99
99
|
);
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
|
-
|
|
102
|
+
|
|
103
103
|
// 生成 Response 類型(總是生成)
|
|
104
104
|
if (resTypeName) {
|
|
105
105
|
const responseTypeContent = generateResponseTypeContent(endpoint, operationDefinitions, schemaTypeMap);
|
|
@@ -120,11 +120,11 @@ export function generateTypesFile(
|
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
});
|
|
123
|
-
|
|
123
|
+
|
|
124
124
|
if (endpointTypes.length > 0) {
|
|
125
125
|
typeDefinitions.push(endpointTypes.join('\n'));
|
|
126
126
|
}
|
|
127
|
-
|
|
127
|
+
|
|
128
128
|
// 如果沒有任何類型定義,至少添加一些基本說明
|
|
129
129
|
if (typeDefinitions.length === 0) {
|
|
130
130
|
typeDefinitions.push(
|
|
@@ -133,7 +133,7 @@ export function generateTypesFile(
|
|
|
133
133
|
``
|
|
134
134
|
);
|
|
135
135
|
}
|
|
136
|
-
|
|
136
|
+
|
|
137
137
|
return importStatement + typeDefinitions.join('\n\n');
|
|
138
138
|
}
|
|
139
139
|
|
|
@@ -142,7 +142,7 @@ export function generateTypesFile(
|
|
|
142
142
|
*/
|
|
143
143
|
function generateRequestTypeContent(endpoint: EndpointInfo, operationDefinitions?: any[], schemaTypeMap: Record<string, string> = {}): string {
|
|
144
144
|
const properties: string[] = [];
|
|
145
|
-
|
|
145
|
+
|
|
146
146
|
// 如果有 query 參數
|
|
147
147
|
if (endpoint.queryParams && endpoint.queryParams.length > 0) {
|
|
148
148
|
endpoint.queryParams.forEach(param => {
|
|
@@ -151,7 +151,7 @@ function generateRequestTypeContent(endpoint: EndpointInfo, operationDefinitions
|
|
|
151
151
|
properties.push(` ${param.name}${optional}: ${paramType};`);
|
|
152
152
|
});
|
|
153
153
|
}
|
|
154
|
-
|
|
154
|
+
|
|
155
155
|
// 如果有 path 參數
|
|
156
156
|
if (endpoint.pathParams && endpoint.pathParams.length > 0) {
|
|
157
157
|
endpoint.pathParams.forEach(param => {
|
|
@@ -160,22 +160,22 @@ function generateRequestTypeContent(endpoint: EndpointInfo, operationDefinitions
|
|
|
160
160
|
properties.push(` ${param.name}${optional}: ${paramType};`);
|
|
161
161
|
});
|
|
162
162
|
}
|
|
163
|
-
|
|
163
|
+
|
|
164
164
|
// 如果有 request body(從 operationDefinitions 中獲取)
|
|
165
165
|
const operationDef = operationDefinitions?.find(op => {
|
|
166
166
|
// 嘗試多種匹配方式
|
|
167
167
|
return op.operation?.operationId === endpoint.operationName ||
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
168
|
+
op.operation?.operationId === endpoint.operationName.toLowerCase() ||
|
|
169
|
+
// 也嘗試匹配 verb + path 組合
|
|
170
|
+
(op.verb === endpoint.verb.toLowerCase() && op.path === endpoint.path);
|
|
171
171
|
});
|
|
172
|
-
|
|
172
|
+
|
|
173
173
|
if (operationDef?.operation?.requestBody) {
|
|
174
174
|
const requestBody = operationDef.operation.requestBody;
|
|
175
175
|
const content = requestBody.content;
|
|
176
176
|
|
|
177
|
-
// 處理不同的 content types
|
|
178
|
-
const jsonContent = content['application/json'];
|
|
177
|
+
// 處理不同的 content types,優先使用 application/json,其次嘗試其他類型
|
|
178
|
+
const jsonContent = content['application/json'] || content['*/*'];
|
|
179
179
|
const formContent = content['multipart/form-data'] || content['application/x-www-form-urlencoded'];
|
|
180
180
|
|
|
181
181
|
if (jsonContent?.schema) {
|
|
@@ -187,15 +187,22 @@ function generateRequestTypeContent(endpoint: EndpointInfo, operationDefinitions
|
|
|
187
187
|
const bodyType = getTypeFromSchema(formContent.schema, schemaTypeMap, 1);
|
|
188
188
|
properties.push(` body: ${bodyType};`);
|
|
189
189
|
} else {
|
|
190
|
-
|
|
190
|
+
// fallback 到第一個可用的 content-type
|
|
191
|
+
const firstContent = Object.values(content)[0] as any;
|
|
192
|
+
if (firstContent?.schema) {
|
|
193
|
+
const bodyType = getTypeFromSchema(firstContent.schema, schemaTypeMap, 1);
|
|
194
|
+
properties.push(` body: ${bodyType};`);
|
|
195
|
+
} else {
|
|
196
|
+
properties.push(` body?: any; // Request body from OpenAPI`);
|
|
197
|
+
}
|
|
191
198
|
}
|
|
192
199
|
}
|
|
193
|
-
|
|
200
|
+
|
|
194
201
|
// 如果沒有任何參數,返回空內容(將由調用方處理為 void)
|
|
195
202
|
if (properties.length === 0) {
|
|
196
203
|
return ''; // 返回空字串,讓調用方決定使用 void
|
|
197
204
|
}
|
|
198
|
-
|
|
205
|
+
|
|
199
206
|
return properties.join('\n');
|
|
200
207
|
}
|
|
201
208
|
|
|
@@ -204,23 +211,27 @@ function generateRequestTypeContent(endpoint: EndpointInfo, operationDefinitions
|
|
|
204
211
|
*/
|
|
205
212
|
function generateResponseTypeContent(endpoint: EndpointInfo, operationDefinitions?: any[], schemaTypeMap: Record<string, string> = {}): string {
|
|
206
213
|
const properties: string[] = [];
|
|
207
|
-
|
|
214
|
+
|
|
208
215
|
// 嘗試從 operationDefinitions 中獲取響應結構
|
|
209
216
|
const operationDef = operationDefinitions?.find(op => {
|
|
210
217
|
// 嘗試多種匹配方式
|
|
211
218
|
return op.operation?.operationId === endpoint.operationName ||
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
219
|
+
op.operation?.operationId === endpoint.operationName.toLowerCase() ||
|
|
220
|
+
// 也嘗試匹配 verb + path 組合
|
|
221
|
+
(op.verb === endpoint.verb.toLowerCase() && op.path === endpoint.path);
|
|
215
222
|
});
|
|
216
|
-
|
|
223
|
+
|
|
217
224
|
if (operationDef?.operation?.responses) {
|
|
218
225
|
// 檢查 200 響應
|
|
219
|
-
const successResponse = operationDef.operation.responses['200'] ||
|
|
220
|
-
|
|
221
|
-
|
|
226
|
+
const successResponse = operationDef.operation.responses['200'] ||
|
|
227
|
+
operationDef.operation.responses['201'];
|
|
228
|
+
|
|
222
229
|
if (successResponse?.content) {
|
|
223
|
-
|
|
230
|
+
// 優先使用 application/json,其次嘗試其他 content-type(包括 */*)
|
|
231
|
+
const jsonContent = successResponse.content['application/json'] ||
|
|
232
|
+
successResponse.content['*/*'] ||
|
|
233
|
+
Object.values(successResponse.content)[0]; // fallback 到第一個可用的 content-type
|
|
234
|
+
|
|
224
235
|
if (jsonContent?.schema) {
|
|
225
236
|
const responseProps = parseSchemaProperties(jsonContent.schema, schemaTypeMap);
|
|
226
237
|
properties.push(...responseProps);
|
|
@@ -230,12 +241,12 @@ function generateResponseTypeContent(endpoint: EndpointInfo, operationDefinition
|
|
|
230
241
|
}
|
|
231
242
|
}
|
|
232
243
|
}
|
|
233
|
-
|
|
244
|
+
|
|
234
245
|
// 如果沒有響應定義,返回空內容(將由調用方處理為 void)
|
|
235
246
|
if (properties.length === 0) {
|
|
236
247
|
return ''; // 返回空字串,讓調用方決定使用 void
|
|
237
248
|
}
|
|
238
|
-
|
|
249
|
+
|
|
239
250
|
return properties.join('\n');
|
|
240
251
|
}
|
|
241
252
|
|
|
@@ -253,13 +264,16 @@ function parseSchemaProperties(schema: any, schemaTypeMap: Record<string, string
|
|
|
253
264
|
const optional = isRequired ? '' : '?';
|
|
254
265
|
// indentLevel=1 因為屬性已經在類型定義內(有 2 個空格縮排)
|
|
255
266
|
const propType = getTypeFromSchema(propSchema, schemaTypeMap, 1);
|
|
256
|
-
const description = propSchema.description ? ` // ${propSchema.description}` : '';
|
|
257
267
|
|
|
258
268
|
// 如果屬性名包含特殊字符(如 -),需要加上引號
|
|
259
269
|
const needsQuotes = /[^a-zA-Z0-9_$]/.test(propName);
|
|
260
270
|
const quotedPropName = needsQuotes ? `"${propName}"` : propName;
|
|
261
271
|
|
|
262
|
-
|
|
272
|
+
// 生成 JSDoc 註解
|
|
273
|
+
if (propSchema.description) {
|
|
274
|
+
properties.push(` /** ${propSchema.description} */`);
|
|
275
|
+
}
|
|
276
|
+
properties.push(` ${quotedPropName}${optional}: ${propType};`);
|
|
263
277
|
});
|
|
264
278
|
}
|
|
265
279
|
|
|
@@ -320,15 +334,23 @@ function getTypeFromSchema(schema: any, schemaTypeMap: Record<string, string> =
|
|
|
320
334
|
// 如果有具體的屬性定義,生成內聯對象類型(多行格式)
|
|
321
335
|
const entries = Object.entries(schema.properties);
|
|
322
336
|
|
|
323
|
-
//
|
|
337
|
+
// 如果沒有屬性但有 additionalProperties,生成 Record 類型
|
|
324
338
|
if (entries.length === 0) {
|
|
325
|
-
|
|
339
|
+
if (schema.additionalProperties) {
|
|
340
|
+
const valueType = schema.additionalProperties === true
|
|
341
|
+
? 'any'
|
|
342
|
+
: getTypeFromSchema(schema.additionalProperties, schemaTypeMap, indentLevel);
|
|
343
|
+
baseType = `Record<string, ${valueType}>`;
|
|
344
|
+
} else {
|
|
345
|
+
baseType = '{}';
|
|
346
|
+
}
|
|
326
347
|
} else {
|
|
327
348
|
// 計算下一層的縮排
|
|
328
349
|
const nextIndent = ' '.repeat(indentLevel + 1);
|
|
329
350
|
const currentIndent = ' '.repeat(indentLevel);
|
|
330
351
|
|
|
331
|
-
const props
|
|
352
|
+
const props: string[] = [];
|
|
353
|
+
entries.forEach(([key, propSchema]: [string, any]) => {
|
|
332
354
|
const required = schema.required || [];
|
|
333
355
|
const optional = required.includes(key) ? '' : '?';
|
|
334
356
|
const type = getTypeFromSchema(propSchema, schemaTypeMap, indentLevel + 1);
|
|
@@ -337,11 +359,21 @@ function getTypeFromSchema(schema: any, schemaTypeMap: Record<string, string> =
|
|
|
337
359
|
const needsQuotes = /[^a-zA-Z0-9_$]/.test(key);
|
|
338
360
|
const quotedKey = needsQuotes ? `"${key}"` : key;
|
|
339
361
|
|
|
340
|
-
|
|
341
|
-
|
|
362
|
+
// 生成 JSDoc 註解
|
|
363
|
+
if (propSchema.description) {
|
|
364
|
+
props.push(`${nextIndent}/** ${propSchema.description} */`);
|
|
365
|
+
}
|
|
366
|
+
props.push(`${nextIndent}${quotedKey}${optional}: ${type};`);
|
|
367
|
+
});
|
|
342
368
|
|
|
343
|
-
baseType = `{\n${props}\n${currentIndent}}`;
|
|
369
|
+
baseType = `{\n${props.join('\n')}\n${currentIndent}}`;
|
|
344
370
|
}
|
|
371
|
+
} else if (schema.additionalProperties) {
|
|
372
|
+
// 如果沒有 properties 但有 additionalProperties
|
|
373
|
+
const valueType = schema.additionalProperties === true
|
|
374
|
+
? 'any'
|
|
375
|
+
: getTypeFromSchema(schema.additionalProperties, schemaTypeMap, indentLevel);
|
|
376
|
+
baseType = `Record<string, ${valueType}>`;
|
|
345
377
|
} else {
|
|
346
378
|
baseType = 'any';
|
|
347
379
|
}
|
|
@@ -361,4 +393,4 @@ function getTypeFromSchema(schema: any, schemaTypeMap: Record<string, string> =
|
|
|
361
393
|
function getTypeFromParameter(param: any, schemaTypeMap: Record<string, string> = {}): string {
|
|
362
394
|
if (!param.schema) return 'any';
|
|
363
395
|
return getTypeFromSchema(param.schema, schemaTypeMap);
|
|
364
|
-
}
|
|
396
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -3,13 +3,13 @@ import type { OpenAPIV3 } from 'openapi-types';
|
|
|
3
3
|
import ts from 'typescript';
|
|
4
4
|
|
|
5
5
|
// 重新匯出服務相關類型
|
|
6
|
-
export type {
|
|
6
|
+
export type {
|
|
7
7
|
GroupConfig,
|
|
8
|
-
GroupInfo
|
|
8
|
+
GroupInfo
|
|
9
9
|
} from './services/group-service';
|
|
10
10
|
|
|
11
|
-
export type {
|
|
12
|
-
FileWriteResult
|
|
11
|
+
export type {
|
|
12
|
+
FileWriteResult
|
|
13
13
|
} from './services/file-writer-service';
|
|
14
14
|
|
|
15
15
|
|
|
@@ -38,14 +38,14 @@ export const operationKeys = ['get', 'put', 'post', 'delete', 'options', 'head',
|
|
|
38
38
|
|
|
39
39
|
export type GenerationOptions = Id<
|
|
40
40
|
CommonOptions &
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
41
|
+
Optional<OutputFileOptions, 'outputFile'> & {
|
|
42
|
+
isDataResponse?(
|
|
43
|
+
code: string,
|
|
44
|
+
includeDefault: boolean,
|
|
45
|
+
response: OpenAPIV3.ResponseObject,
|
|
46
|
+
allResponses: OpenAPIV3.ResponsesObject
|
|
47
|
+
): boolean;
|
|
48
|
+
}
|
|
49
49
|
>;
|
|
50
50
|
|
|
51
51
|
export interface CommonOptions {
|
|
@@ -73,6 +73,15 @@ export interface CommonOptions {
|
|
|
73
73
|
file: string;
|
|
74
74
|
importReturnTypeName: string; // 用於指定別名導入,例如 IRestFulEndpointsQueryReturn
|
|
75
75
|
};
|
|
76
|
+
/**
|
|
77
|
+
* Cache tag types configuration for RTK Query cache invalidation
|
|
78
|
+
* If provided, will import the specified type from the given file in enhanceEndpoints.ts
|
|
79
|
+
* Example: { file: "@/store/tagTypes", importName: "ECacheTagTypes" }
|
|
80
|
+
*/
|
|
81
|
+
cacheTagTypes?: {
|
|
82
|
+
file: string;
|
|
83
|
+
importReturnTypeName: string;
|
|
84
|
+
};
|
|
76
85
|
/**
|
|
77
86
|
* defaults to "enhancedApi"
|
|
78
87
|
*/
|
|
@@ -189,11 +198,11 @@ export type OutputFilesConfig = {
|
|
|
189
198
|
export type ConfigFile =
|
|
190
199
|
| Id<Require<CommonOptions & OutputFileOptions, 'outputFile'>>
|
|
191
200
|
| Id<
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
201
|
+
Omit<CommonOptions, 'outputFile'> & {
|
|
202
|
+
// outputFiles: { [outputFile: string]: Omit<OutputFileOptions, 'outputFile'> };
|
|
203
|
+
outputFiles: OutputFilesConfig
|
|
204
|
+
}
|
|
205
|
+
>;
|
|
197
206
|
|
|
198
207
|
export type GenerateApiResult = {
|
|
199
208
|
operationNames: string[];
|
|
@@ -226,4 +235,4 @@ export type QueryArgDefinition = {
|
|
|
226
235
|
}
|
|
227
236
|
);
|
|
228
237
|
|
|
229
|
-
export type QueryArgDefinitions = Record<string, QueryArgDefinition>;
|
|
238
|
+
export type QueryArgDefinitions = Record<string, QueryArgDefinition>;
|