@acrool/rtk-query-codegen-openapi 0.0.2 → 0.0.7
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/index.d.mts +8 -4
- package/lib/index.d.ts +8 -4
- package/lib/index.js +460 -124
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +460 -124
- package/lib/index.mjs.map +1 -1
- package/package.json +11 -2
- package/src/generate.ts +365 -142
- package/src/generators/react-hooks.ts +2 -2
- package/src/index.ts +250 -9
- package/src/types.ts +9 -1
package/src/generate.ts
CHANGED
|
@@ -38,8 +38,8 @@ function defaultIsDataResponse(code: string, includeDefault: boolean) {
|
|
|
38
38
|
return !Number.isNaN(parsedCode) && parsedCode >= 200 && parsedCode < 300;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
function getOperationName({ verb, path
|
|
42
|
-
return _getOperationName(verb, path,
|
|
41
|
+
function getOperationName({ verb, path }: Pick<OperationDefinition, 'verb' | 'path'>) {
|
|
42
|
+
return _getOperationName(verb, path, undefined);
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
function getTags({ verb, pathItem }: Pick<OperationDefinition, 'verb' | 'pathItem'>): string[] {
|
|
@@ -128,49 +128,176 @@ export async function generateApi(
|
|
|
128
128
|
mergeReadWriteOnly,
|
|
129
129
|
});
|
|
130
130
|
|
|
131
|
-
|
|
131
|
+
const schemeTypeNames = new Set<string>();
|
|
132
|
+
|
|
133
|
+
function addSchemeTypeName(name: string) {
|
|
134
|
+
schemeTypeNames.add(name);
|
|
135
|
+
schemeTypeNames.add(camelCase(name));
|
|
136
|
+
schemeTypeNames.add(capitalize(camelCase(name)));
|
|
137
|
+
}
|
|
138
|
+
|
|
132
139
|
if (sharedTypesFile) {
|
|
140
|
+
const resultFile = ts.createSourceFile(
|
|
141
|
+
'sharedTypes.ts',
|
|
142
|
+
'',
|
|
143
|
+
ts.ScriptTarget.Latest,
|
|
144
|
+
/*setParentNodes*/ false,
|
|
145
|
+
ts.ScriptKind.TS
|
|
146
|
+
);
|
|
147
|
+
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
|
|
148
|
+
|
|
149
|
+
const allTypeDefinitions: ts.Statement[] = [];
|
|
150
|
+
const definedTypeNames = new Set<string>();
|
|
151
|
+
|
|
133
152
|
const components = v3Doc.components;
|
|
134
153
|
if (components) {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
|
|
143
|
-
|
|
144
|
-
// 將 components 轉換為 TypeScript 類型定義
|
|
145
|
-
const typeDefinitions = Object.entries(components).flatMap(([_, componentDefs]) => {
|
|
146
|
-
return Object.entries(componentDefs as Record<string, unknown>).map(([name, def]) => {
|
|
147
|
-
const typeNode = apiGen.getTypeFromSchema(def as OpenAPIV3.SchemaObject);
|
|
154
|
+
// 只處理 schemas,其他 component 類型暫時不處理
|
|
155
|
+
if (components.schemas) {
|
|
156
|
+
const typeEntries = Object.entries(components.schemas).map(([name, def]) => {
|
|
157
|
+
addSchemeTypeName(name);
|
|
158
|
+
const typeName = capitalize(camelCase(name));
|
|
159
|
+
definedTypeNames.add(typeName);
|
|
160
|
+
const typeNode = wrapWithSchemeIfComponent(apiGen.getTypeFromSchema(def as OpenAPIV3.SchemaObject));
|
|
148
161
|
return factory.createTypeAliasDeclaration(
|
|
149
162
|
[factory.createModifier(ts.SyntaxKind.ExportKeyword)],
|
|
150
|
-
factory.createIdentifier(
|
|
163
|
+
factory.createIdentifier(typeName),
|
|
151
164
|
undefined,
|
|
152
165
|
typeNode
|
|
153
166
|
);
|
|
154
167
|
});
|
|
168
|
+
|
|
169
|
+
allTypeDefinitions.push(
|
|
170
|
+
factory.createModuleDeclaration(
|
|
171
|
+
[factory.createModifier(ts.SyntaxKind.ExportKeyword)],
|
|
172
|
+
factory.createIdentifier('Scheme'),
|
|
173
|
+
factory.createModuleBlock(typeEntries),
|
|
174
|
+
ts.NodeFlags.Namespace
|
|
175
|
+
)
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const enumEntries = [
|
|
181
|
+
...apiGen.enumAliases.filter((e) => ts.isEnumDeclaration(e)),
|
|
182
|
+
...apiGen.enumAliases.filter((e) => ts.isTypeAliasDeclaration(e)),
|
|
183
|
+
].map((enumDecl) => {
|
|
184
|
+
if (ts.isEnumDeclaration(enumDecl)) {
|
|
185
|
+
return factory.createEnumDeclaration(
|
|
186
|
+
[factory.createModifier(ts.SyntaxKind.ExportKeyword)],
|
|
187
|
+
enumDecl.name,
|
|
188
|
+
enumDecl.members
|
|
189
|
+
);
|
|
190
|
+
} else if (ts.isTypeAliasDeclaration(enumDecl)) {
|
|
191
|
+
return factory.createTypeAliasDeclaration(
|
|
192
|
+
[factory.createModifier(ts.SyntaxKind.ExportKeyword)],
|
|
193
|
+
enumDecl.name,
|
|
194
|
+
enumDecl.typeParameters,
|
|
195
|
+
enumDecl.type
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
return enumDecl;
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const unionTypeEnums = apiGen.aliases
|
|
202
|
+
.filter((alias) => {
|
|
203
|
+
if (ts.isTypeAliasDeclaration(alias) && alias.type) {
|
|
204
|
+
return ts.isUnionTypeNode(alias.type);
|
|
205
|
+
}
|
|
206
|
+
return false;
|
|
207
|
+
})
|
|
208
|
+
.map((alias) => {
|
|
209
|
+
if (ts.isTypeAliasDeclaration(alias)) {
|
|
210
|
+
return factory.createTypeAliasDeclaration(
|
|
211
|
+
[factory.createModifier(ts.SyntaxKind.ExportKeyword)],
|
|
212
|
+
alias.name,
|
|
213
|
+
alias.typeParameters,
|
|
214
|
+
alias.type
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
return alias;
|
|
155
218
|
});
|
|
156
219
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
ts.
|
|
163
|
-
|
|
164
|
-
|
|
220
|
+
const allEnumEntries = [...enumEntries, ...unionTypeEnums];
|
|
221
|
+
|
|
222
|
+
if (allEnumEntries.length > 0) {
|
|
223
|
+
allTypeDefinitions.push(
|
|
224
|
+
factory.createModuleDeclaration(
|
|
225
|
+
[factory.createModifier(ts.SyntaxKind.ExportKeyword)],
|
|
226
|
+
factory.createIdentifier('Enum'),
|
|
227
|
+
factory.createModuleBlock(allEnumEntries),
|
|
228
|
+
ts.NodeFlags.Namespace
|
|
229
|
+
)
|
|
165
230
|
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (apiGen.aliases.length > 0) {
|
|
234
|
+
const aliasEntries = apiGen.aliases
|
|
235
|
+
.filter((alias) => {
|
|
236
|
+
if (ts.isTypeAliasDeclaration(alias)) {
|
|
237
|
+
const isDefinedInComponents = definedTypeNames.has(alias.name.text);
|
|
238
|
+
const isUnionTypeEnum = ts.isUnionTypeNode(alias.type);
|
|
239
|
+
return !isDefinedInComponents && !isUnionTypeEnum;
|
|
240
|
+
}
|
|
241
|
+
return false;
|
|
242
|
+
})
|
|
243
|
+
.map((alias) => {
|
|
244
|
+
if (ts.isTypeAliasDeclaration(alias)) {
|
|
245
|
+
return factory.createTypeAliasDeclaration(
|
|
246
|
+
[factory.createModifier(ts.SyntaxKind.ExportKeyword)],
|
|
247
|
+
alias.name,
|
|
248
|
+
alias.typeParameters,
|
|
249
|
+
alias.type
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
return alias;
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
if (aliasEntries.length > 0) {
|
|
256
|
+
const existingSchemeIndex = allTypeDefinitions.findIndex(
|
|
257
|
+
(def) => ts.isModuleDeclaration(def) && ts.isIdentifier(def.name) && def.name.text === 'Scheme'
|
|
258
|
+
);
|
|
166
259
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
260
|
+
if (existingSchemeIndex >= 0) {
|
|
261
|
+
const existingScheme = allTypeDefinitions[existingSchemeIndex] as ts.ModuleDeclaration;
|
|
262
|
+
const mergedMembers = [...(existingScheme.body as ts.ModuleBlock).statements, ...aliasEntries];
|
|
263
|
+
allTypeDefinitions[existingSchemeIndex] = factory.createModuleDeclaration(
|
|
264
|
+
[factory.createModifier(ts.SyntaxKind.ExportKeyword)],
|
|
265
|
+
factory.createIdentifier('Scheme'),
|
|
266
|
+
factory.createModuleBlock(mergedMembers),
|
|
267
|
+
ts.NodeFlags.Namespace
|
|
268
|
+
);
|
|
269
|
+
} else {
|
|
270
|
+
allTypeDefinitions.push(
|
|
271
|
+
factory.createModuleDeclaration(
|
|
272
|
+
[factory.createModifier(ts.SyntaxKind.ExportKeyword)],
|
|
273
|
+
factory.createIdentifier('Scheme'),
|
|
274
|
+
factory.createModuleBlock(aliasEntries),
|
|
275
|
+
ts.NodeFlags.Namespace
|
|
276
|
+
)
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
170
280
|
}
|
|
281
|
+
|
|
282
|
+
const fs = await import('node:fs/promises');
|
|
283
|
+
const path = await import('node:path');
|
|
284
|
+
|
|
285
|
+
const sharedTypesDir = path.dirname(sharedTypesFile);
|
|
286
|
+
await fs.mkdir(sharedTypesDir, { recursive: true });
|
|
287
|
+
|
|
288
|
+
const output = printer.printNode(
|
|
289
|
+
ts.EmitHint.Unspecified,
|
|
290
|
+
factory.createSourceFile(
|
|
291
|
+
allTypeDefinitions,
|
|
292
|
+
factory.createToken(ts.SyntaxKind.EndOfFileToken),
|
|
293
|
+
ts.NodeFlags.None
|
|
294
|
+
),
|
|
295
|
+
resultFile
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
await fs.writeFile(sharedTypesFile, output, 'utf-8');
|
|
171
299
|
}
|
|
172
300
|
|
|
173
|
-
// temporary workaround for https://github.com/oazapfts/oazapfts/issues/491
|
|
174
301
|
if (apiGen.spec.components?.schemas) {
|
|
175
302
|
apiGen.preprocessComponents(apiGen.spec.components.schemas);
|
|
176
303
|
}
|
|
@@ -187,6 +314,7 @@ export async function generateApi(
|
|
|
187
314
|
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
|
|
188
315
|
|
|
189
316
|
const interfaces: Record<string, ts.InterfaceDeclaration | ts.TypeAliasDeclaration> = {};
|
|
317
|
+
|
|
190
318
|
function registerInterface(declaration: ts.InterfaceDeclaration | ts.TypeAliasDeclaration) {
|
|
191
319
|
const name = declaration.name.escapedText.toString();
|
|
192
320
|
if (name in interfaces) {
|
|
@@ -203,17 +331,20 @@ export async function generateApi(
|
|
|
203
331
|
apiFile = apiFile.replace(/\\/g, '/');
|
|
204
332
|
if (!apiFile.startsWith('.')) apiFile = `./${apiFile}`;
|
|
205
333
|
}
|
|
206
|
-
// 處理 sharedTypesFile 的路徑
|
|
207
|
-
if (sharedTypesFile && sharedTypesFile.startsWith('.')) {
|
|
208
|
-
sharedTypesFile = path.relative(path.dirname(outputFile), sharedTypesFile);
|
|
209
|
-
sharedTypesFile = sharedTypesFile.replace(/\\/g, '/');
|
|
210
|
-
if (!sharedTypesFile.startsWith('.')) sharedTypesFile = `./${sharedTypesFile}`;
|
|
211
|
-
}
|
|
212
334
|
}
|
|
213
335
|
apiFile = apiFile.replace(/\.[jt]sx?$/, '');
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
336
|
+
|
|
337
|
+
const sharedTypesImportPath =
|
|
338
|
+
sharedTypesFile && outputFile
|
|
339
|
+
? (() => {
|
|
340
|
+
let rel = path
|
|
341
|
+
.relative(path.dirname(outputFile), sharedTypesFile)
|
|
342
|
+
.replace(/\\/g, '/')
|
|
343
|
+
.replace(/\.[jt]sx?$/, '');
|
|
344
|
+
if (!rel.startsWith('.')) rel = './' + rel;
|
|
345
|
+
return rel;
|
|
346
|
+
})()
|
|
347
|
+
: './shared-types';
|
|
217
348
|
|
|
218
349
|
return printer.printNode(
|
|
219
350
|
ts.EmitHint.Unspecified,
|
|
@@ -221,18 +352,14 @@ export async function generateApi(
|
|
|
221
352
|
[
|
|
222
353
|
generateImportNode(apiFile, { [apiImport]: 'api' }),
|
|
223
354
|
generateImportNode('@acrool/react-fetcher', { IRestFulEndpointsQueryReturn: 'IRestFulEndpointsQueryReturn' }),
|
|
224
|
-
...(sharedTypesFile
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
factory.createStringLiteral(sharedTypesFile),
|
|
233
|
-
undefined
|
|
234
|
-
)
|
|
235
|
-
] : []),
|
|
355
|
+
...(sharedTypesFile
|
|
356
|
+
? [
|
|
357
|
+
generateImportNode(sharedTypesImportPath, {
|
|
358
|
+
Scheme: 'Scheme',
|
|
359
|
+
...(useEnumType ? { Enum: 'Enum' } : {}),
|
|
360
|
+
}),
|
|
361
|
+
]
|
|
362
|
+
: []),
|
|
236
363
|
...(tag ? [generateTagTypes({ addTagTypes: extractAllTagTypes({ operationDefinitions }) })] : []),
|
|
237
364
|
generateCreateApiCall({
|
|
238
365
|
tag,
|
|
@@ -241,16 +368,13 @@ export async function generateApi(
|
|
|
241
368
|
generateEndpoint({
|
|
242
369
|
operationDefinition,
|
|
243
370
|
overrides: getOverrides(operationDefinition, endpointOverrides),
|
|
371
|
+
sharedTypesFile: !!sharedTypesFile,
|
|
244
372
|
})
|
|
245
373
|
),
|
|
246
374
|
true
|
|
247
375
|
),
|
|
248
376
|
}),
|
|
249
|
-
factory.createExportAssignment(
|
|
250
|
-
undefined,
|
|
251
|
-
undefined,
|
|
252
|
-
factory.createIdentifier(generatedApiName)
|
|
253
|
-
),
|
|
377
|
+
factory.createExportAssignment(undefined, undefined, factory.createIdentifier(generatedApiName)),
|
|
254
378
|
...Object.values(interfaces),
|
|
255
379
|
...(sharedTypesFile ? [] : [...apiGen.aliases, ...apiGen.enumAliases]),
|
|
256
380
|
...(hooks
|
|
@@ -285,9 +409,11 @@ export async function generateApi(
|
|
|
285
409
|
function generateEndpoint({
|
|
286
410
|
operationDefinition,
|
|
287
411
|
overrides,
|
|
412
|
+
sharedTypesFile,
|
|
288
413
|
}: {
|
|
289
414
|
operationDefinition: OperationDefinition;
|
|
290
415
|
overrides?: EndpointOverrides;
|
|
416
|
+
sharedTypesFile: boolean;
|
|
291
417
|
}) {
|
|
292
418
|
const {
|
|
293
419
|
verb,
|
|
@@ -296,73 +422,37 @@ export async function generateApi(
|
|
|
296
422
|
operation,
|
|
297
423
|
operation: { responses, requestBody },
|
|
298
424
|
} = operationDefinition;
|
|
299
|
-
const operationName = getOperationName({ verb, path
|
|
425
|
+
const operationName = getOperationName({ verb, path });
|
|
300
426
|
const tags = tag ? getTags({ verb, pathItem }) : [];
|
|
301
427
|
const isQuery = testIsQuery(verb, overrides);
|
|
302
428
|
|
|
303
429
|
const returnsJson = apiGen.getResponseType(responses) === 'json';
|
|
304
430
|
let ResponseType: ts.TypeNode = factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
|
|
305
|
-
|
|
306
|
-
function replaceReferences(schema: any): ts.TypeNode {
|
|
307
|
-
if (!schema) return factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
|
|
308
|
-
|
|
309
|
-
const refName = getReferenceName(schema);
|
|
310
|
-
if (refName && sharedTypesFile) {
|
|
311
|
-
return factory.createTypeReferenceNode(
|
|
312
|
-
factory.createQualifiedName(
|
|
313
|
-
factory.createIdentifier('SharedTypes'),
|
|
314
|
-
factory.createIdentifier(refName)
|
|
315
|
-
),
|
|
316
|
-
undefined
|
|
317
|
-
);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
if (schema.type === 'object' && schema.properties) {
|
|
321
|
-
const members = Object.entries(schema.properties).map(([key, value]: [string, any]) => {
|
|
322
|
-
return factory.createPropertySignature(
|
|
323
|
-
undefined,
|
|
324
|
-
factory.createIdentifier(key),
|
|
325
|
-
schema.required?.includes(key) ? undefined : factory.createToken(ts.SyntaxKind.QuestionToken),
|
|
326
|
-
replaceReferences(value)
|
|
327
|
-
);
|
|
328
|
-
});
|
|
329
|
-
return factory.createTypeLiteralNode(members);
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
if (schema.type === 'array' && schema.items) {
|
|
333
|
-
return factory.createArrayTypeNode(replaceReferences(schema.items));
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
return apiGen.getTypeFromSchema(schema);
|
|
337
|
-
}
|
|
338
|
-
|
|
339
431
|
if (returnsJson) {
|
|
340
432
|
const returnTypes = Object.entries(responses || {})
|
|
341
433
|
.map(
|
|
342
|
-
([code, response]) =>
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
return [code, resolvedResponse, type] as const;
|
|
352
|
-
}
|
|
434
|
+
([code, response]) =>
|
|
435
|
+
[
|
|
436
|
+
code,
|
|
437
|
+
apiGen.resolve(response),
|
|
438
|
+
wrapWithSchemeIfComponent(
|
|
439
|
+
apiGen.getTypeFromResponse(response, 'readOnly') ||
|
|
440
|
+
factory.createKeywordTypeNode(ts.SyntaxKind.UndefinedKeyword)
|
|
441
|
+
),
|
|
442
|
+
] as const
|
|
353
443
|
)
|
|
354
444
|
.filter(([status, response]) =>
|
|
355
445
|
isDataResponse(status, includeDefault, apiGen.resolve(response), responses || {})
|
|
356
446
|
)
|
|
357
447
|
.filter(([_1, _2, type]) => type !== keywordType.void)
|
|
358
|
-
.map(([code, response, type]) =>
|
|
359
|
-
ts.addSyntheticLeadingComment(
|
|
360
|
-
|
|
448
|
+
.map(([code, response, type]) => {
|
|
449
|
+
return ts.addSyntheticLeadingComment(
|
|
450
|
+
type,
|
|
361
451
|
ts.SyntaxKind.MultiLineCommentTrivia,
|
|
362
452
|
`* status ${code} ${response.description} `,
|
|
363
453
|
false
|
|
364
|
-
)
|
|
365
|
-
);
|
|
454
|
+
);
|
|
455
|
+
});
|
|
366
456
|
if (returnTypes.length > 0) {
|
|
367
457
|
ResponseType = factory.createUnionTypeNode(returnTypes);
|
|
368
458
|
}
|
|
@@ -386,34 +476,45 @@ export async function generateApi(
|
|
|
386
476
|
|
|
387
477
|
const parameters = supportDeepObjects([...pathItemParameters, ...operationParameters])
|
|
388
478
|
.filter(argumentMatches(overrides?.parameterFilter))
|
|
389
|
-
.filter(param => param.in !== 'header');
|
|
479
|
+
.filter((param) => param.in !== 'header');
|
|
390
480
|
|
|
391
481
|
const allNames = parameters.map((p) => p.name);
|
|
392
482
|
const queryArg: QueryArgDefinitions = {};
|
|
483
|
+
|
|
393
484
|
function generateName(name: string, potentialPrefix: string) {
|
|
394
485
|
const isPureSnakeCase = /^[a-zA-Z][a-zA-Z0-9_]*$/.test(name);
|
|
395
|
-
// prefix with `query`, `path` or `body` if there are multiple paramters with the same name
|
|
396
486
|
const hasNamingConflict = allNames.filter((n) => n === name).length > 1;
|
|
397
487
|
if (hasNamingConflict) {
|
|
398
488
|
name = `${potentialPrefix}_${name}`;
|
|
399
489
|
}
|
|
400
|
-
// convert to camelCase if the name is pure snake_case and there are no naming conflicts
|
|
401
490
|
const camelCaseName = camelCase(name);
|
|
402
491
|
if (isPureSnakeCase && !allNames.includes(camelCaseName)) {
|
|
403
492
|
name = camelCaseName;
|
|
404
493
|
}
|
|
405
|
-
// if there are still any naming conflicts, prepend with underscore
|
|
406
494
|
while (name in queryArg) {
|
|
407
495
|
name = `_${name}`;
|
|
408
496
|
}
|
|
409
497
|
return name;
|
|
410
498
|
}
|
|
411
499
|
|
|
500
|
+
for (const param of parameters) {
|
|
501
|
+
const name = generateName(param.name, param.in);
|
|
502
|
+
queryArg[name] = {
|
|
503
|
+
origin: 'param',
|
|
504
|
+
name,
|
|
505
|
+
originalName: param.name,
|
|
506
|
+
type: wrapWithSchemeIfComponent(
|
|
507
|
+
apiGen.getTypeFromSchema(isReference(param) ? param : param.schema, undefined, 'writeOnly')
|
|
508
|
+
),
|
|
509
|
+
required: param.required,
|
|
510
|
+
param,
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
|
|
412
514
|
if (requestBody) {
|
|
413
515
|
const body = apiGen.resolve(requestBody);
|
|
414
516
|
const schema = apiGen.getSchemaFromContent(body.content);
|
|
415
|
-
const type =
|
|
416
|
-
|
|
517
|
+
const type = wrapWithSchemeIfComponent(apiGen.getTypeFromSchema(schema));
|
|
417
518
|
const schemaName = camelCase(
|
|
418
519
|
(type as any).name ||
|
|
419
520
|
getReferenceName(schema) ||
|
|
@@ -426,27 +527,12 @@ export async function generateApi(
|
|
|
426
527
|
origin: 'body',
|
|
427
528
|
name,
|
|
428
529
|
originalName: schemaName,
|
|
429
|
-
type,
|
|
530
|
+
type: wrapWithSchemeIfComponent(apiGen.getTypeFromSchema(schema, undefined, 'writeOnly')),
|
|
430
531
|
required: true,
|
|
431
532
|
body,
|
|
432
533
|
};
|
|
433
534
|
}
|
|
434
535
|
|
|
435
|
-
for (const param of parameters) {
|
|
436
|
-
const name = generateName(param.name, param.in);
|
|
437
|
-
const paramSchema = isReference(param) ? param : param.schema;
|
|
438
|
-
const type = replaceReferences(paramSchema);
|
|
439
|
-
|
|
440
|
-
queryArg[name] = {
|
|
441
|
-
origin: 'param',
|
|
442
|
-
name,
|
|
443
|
-
originalName: param.name,
|
|
444
|
-
type,
|
|
445
|
-
required: param.required,
|
|
446
|
-
param,
|
|
447
|
-
};
|
|
448
|
-
}
|
|
449
|
-
|
|
450
536
|
const propertyName = (name: string | ts.PropertyName): ts.PropertyName => {
|
|
451
537
|
if (typeof name === 'string') {
|
|
452
538
|
return isValidIdentifier(name) ? factory.createIdentifier(name) : factory.createStringLiteral(name);
|
|
@@ -498,10 +584,7 @@ export async function generateApi(
|
|
|
498
584
|
operationName: operationNameSuffix ? capitalize(operationName + operationNameSuffix) : operationName,
|
|
499
585
|
type: isQuery ? 'query' : 'mutation',
|
|
500
586
|
Response: ResponseTypeName,
|
|
501
|
-
QueryArg: factory.createTypeReferenceNode(
|
|
502
|
-
factory.createIdentifier('IRestFulEndpointsQueryReturn'),
|
|
503
|
-
[QueryArg]
|
|
504
|
-
),
|
|
587
|
+
QueryArg: factory.createTypeReferenceNode(factory.createIdentifier('IRestFulEndpointsQueryReturn'), [QueryArg]),
|
|
505
588
|
queryFn: generateQueryFn({
|
|
506
589
|
operationDefinition,
|
|
507
590
|
queryArg,
|
|
@@ -532,13 +615,24 @@ export async function generateApi(
|
|
|
532
615
|
encodePathParams: boolean;
|
|
533
616
|
encodeQueryParams: boolean;
|
|
534
617
|
}) {
|
|
535
|
-
const { path, verb } = operationDefinition;
|
|
618
|
+
const { path, verb, operation } = operationDefinition;
|
|
536
619
|
|
|
537
620
|
const bodyParameter = Object.values(queryArg).find((def) => def.origin === 'body');
|
|
538
621
|
|
|
539
622
|
const rootObject = factory.createIdentifier('queryArg');
|
|
540
623
|
const variablesObject = factory.createPropertyAccessExpression(rootObject, factory.createIdentifier('variables'));
|
|
541
624
|
|
|
625
|
+
// 提取 content type 的輔助函數
|
|
626
|
+
function getContentType(): string | undefined {
|
|
627
|
+
if (operation.requestBody) {
|
|
628
|
+
const requestBody = apiGen.resolve(operation.requestBody);
|
|
629
|
+
const contentTypes = Object.keys(requestBody.content || {});
|
|
630
|
+
// 直接返回第一個可用的 content type
|
|
631
|
+
return contentTypes[0];
|
|
632
|
+
}
|
|
633
|
+
return undefined;
|
|
634
|
+
}
|
|
635
|
+
|
|
542
636
|
function pickParams(paramIn: string) {
|
|
543
637
|
return Object.values(queryArg).filter((def) => def.origin === 'param' && def.param.in === paramIn);
|
|
544
638
|
}
|
|
@@ -547,8 +641,8 @@ export async function generateApi(
|
|
|
547
641
|
if (parameters.length === 0) return undefined;
|
|
548
642
|
|
|
549
643
|
const properties = parameters.map((param) => {
|
|
550
|
-
const value = isFlatArg
|
|
551
|
-
? variablesObject
|
|
644
|
+
const value = isFlatArg
|
|
645
|
+
? variablesObject
|
|
552
646
|
: factory.createPropertyAccessExpression(variablesObject, factory.createIdentifier(param.name));
|
|
553
647
|
|
|
554
648
|
const encodedValue =
|
|
@@ -573,6 +667,8 @@ export async function generateApi(
|
|
|
573
667
|
);
|
|
574
668
|
}
|
|
575
669
|
|
|
670
|
+
const contentType = getContentType();
|
|
671
|
+
|
|
576
672
|
return factory.createArrowFunction(
|
|
577
673
|
undefined,
|
|
578
674
|
undefined,
|
|
@@ -592,13 +688,22 @@ export async function generateApi(
|
|
|
592
688
|
factory.createIdentifier('method'),
|
|
593
689
|
factory.createStringLiteral(verb.toUpperCase())
|
|
594
690
|
),
|
|
691
|
+
contentType
|
|
692
|
+
? factory.createPropertyAssignment(
|
|
693
|
+
factory.createIdentifier('contentType'),
|
|
694
|
+
factory.createStringLiteral(contentType)
|
|
695
|
+
)
|
|
696
|
+
: undefined,
|
|
595
697
|
bodyParameter === undefined
|
|
596
698
|
? undefined
|
|
597
699
|
: factory.createPropertyAssignment(
|
|
598
700
|
factory.createIdentifier('body'),
|
|
599
701
|
isFlatArg
|
|
600
702
|
? variablesObject
|
|
601
|
-
: factory.createPropertyAccessExpression(
|
|
703
|
+
: factory.createPropertyAccessExpression(
|
|
704
|
+
variablesObject,
|
|
705
|
+
factory.createIdentifier(bodyParameter.name)
|
|
706
|
+
)
|
|
602
707
|
),
|
|
603
708
|
createObjectLiteralProperty(pickParams('cookie'), 'cookies'),
|
|
604
709
|
createObjectLiteralProperty(pickParams('query'), 'params'),
|
|
@@ -617,14 +722,132 @@ export async function generateApi(
|
|
|
617
722
|
);
|
|
618
723
|
}
|
|
619
724
|
|
|
620
|
-
// eslint-disable-next-line no-empty-pattern
|
|
621
725
|
function generateQueryEndpointProps({}: { operationDefinition: OperationDefinition }): ObjectPropertyDefinitions {
|
|
622
|
-
return {};
|
|
726
|
+
return {};
|
|
623
727
|
}
|
|
624
728
|
|
|
625
|
-
// eslint-disable-next-line no-empty-pattern
|
|
626
729
|
function generateMutationEndpointProps({}: { operationDefinition: OperationDefinition }): ObjectPropertyDefinitions {
|
|
627
|
-
return {};
|
|
730
|
+
return {};
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function wrapWithSchemeIfComponent(typeNode: ts.TypeNode): ts.TypeNode {
|
|
734
|
+
if (ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName)) {
|
|
735
|
+
const typeName = typeNode.typeName.text;
|
|
736
|
+
|
|
737
|
+
// 檢查是否為 enum 類型(包括在 enumAliases 和 aliases 中的)
|
|
738
|
+
const isEnumType =
|
|
739
|
+
useEnumType &&
|
|
740
|
+
(apiGen.enumAliases.some((enumDecl) => {
|
|
741
|
+
if (ts.isEnumDeclaration(enumDecl) || ts.isTypeAliasDeclaration(enumDecl)) {
|
|
742
|
+
return enumDecl.name.text === typeName;
|
|
743
|
+
}
|
|
744
|
+
return false;
|
|
745
|
+
}) ||
|
|
746
|
+
apiGen.aliases.some((alias) => {
|
|
747
|
+
if (ts.isTypeAliasDeclaration(alias) && alias.type) {
|
|
748
|
+
// 檢查是否為 union type 的 enum
|
|
749
|
+
if (ts.isUnionTypeNode(alias.type)) {
|
|
750
|
+
return alias.name.text === typeName;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
return false;
|
|
754
|
+
}));
|
|
755
|
+
|
|
756
|
+
if (isEnumType) {
|
|
757
|
+
return factory.createTypeReferenceNode(
|
|
758
|
+
factory.createQualifiedName(factory.createIdentifier('Enum'), typeNode.typeName),
|
|
759
|
+
typeNode.typeArguments?.map(wrapWithSchemeIfComponent)
|
|
760
|
+
);
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
if (schemeTypeNames.has(typeName)) {
|
|
764
|
+
return factory.createTypeReferenceNode(
|
|
765
|
+
factory.createQualifiedName(factory.createIdentifier('Scheme'), typeNode.typeName),
|
|
766
|
+
typeNode.typeArguments?.map(wrapWithSchemeIfComponent)
|
|
767
|
+
);
|
|
768
|
+
}
|
|
769
|
+
if (typeNode.typeArguments) {
|
|
770
|
+
return factory.createTypeReferenceNode(
|
|
771
|
+
typeNode.typeName,
|
|
772
|
+
typeNode.typeArguments.map(wrapWithSchemeIfComponent)
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
if (ts.isArrayTypeNode(typeNode)) {
|
|
777
|
+
return factory.createArrayTypeNode(wrapWithSchemeIfComponent(typeNode.elementType));
|
|
778
|
+
}
|
|
779
|
+
if (ts.isUnionTypeNode(typeNode)) {
|
|
780
|
+
// 檢查是否為 enum 的 union type
|
|
781
|
+
const unionTypes = typeNode.types;
|
|
782
|
+
if (
|
|
783
|
+
unionTypes.length > 0 &&
|
|
784
|
+
unionTypes.every(
|
|
785
|
+
(type) =>
|
|
786
|
+
ts.isLiteralTypeNode(type) && (ts.isStringLiteral(type.literal) || ts.isNumericLiteral(type.literal))
|
|
787
|
+
)
|
|
788
|
+
) {
|
|
789
|
+
// 這是一個 enum 的 union type,我們需要找到對應的 enum 類型
|
|
790
|
+
const enumValues = unionTypes
|
|
791
|
+
.map((type) => {
|
|
792
|
+
if (ts.isLiteralTypeNode(type)) {
|
|
793
|
+
if (ts.isStringLiteral(type.literal)) {
|
|
794
|
+
return type.literal.text;
|
|
795
|
+
} else if (ts.isNumericLiteral(type.literal)) {
|
|
796
|
+
return type.literal.text;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
return null;
|
|
800
|
+
})
|
|
801
|
+
.filter(Boolean);
|
|
802
|
+
|
|
803
|
+
// 查找對應的 enum 類型
|
|
804
|
+
const matchingEnum = apiGen.aliases.find((alias) => {
|
|
805
|
+
if (ts.isTypeAliasDeclaration(alias) && ts.isUnionTypeNode(alias.type)) {
|
|
806
|
+
const aliasValues = alias.type.types
|
|
807
|
+
.map((type) => {
|
|
808
|
+
if (ts.isLiteralTypeNode(type)) {
|
|
809
|
+
if (ts.isStringLiteral(type.literal)) {
|
|
810
|
+
return type.literal.text;
|
|
811
|
+
} else if (ts.isNumericLiteral(type.literal)) {
|
|
812
|
+
return type.literal.text;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
return null;
|
|
816
|
+
})
|
|
817
|
+
.filter(Boolean);
|
|
818
|
+
|
|
819
|
+
return aliasValues.length === enumValues.length && aliasValues.every((val) => enumValues.includes(val));
|
|
820
|
+
}
|
|
821
|
+
return false;
|
|
822
|
+
});
|
|
823
|
+
|
|
824
|
+
// 對於所有的 enum 類型,直接使用字串型別,不轉換為 Enum
|
|
825
|
+
// 這樣可以避免自動命名造成的變更問題
|
|
826
|
+
if (matchingEnum && ts.isTypeAliasDeclaration(matchingEnum)) {
|
|
827
|
+
// 直接返回原始的 union type,不轉換為 Enum
|
|
828
|
+
return typeNode;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
return factory.createUnionTypeNode(typeNode.types.map(wrapWithSchemeIfComponent));
|
|
833
|
+
}
|
|
834
|
+
if (ts.isTypeLiteralNode(typeNode)) {
|
|
835
|
+
return factory.createTypeLiteralNode(
|
|
836
|
+
typeNode.members.map((member) => {
|
|
837
|
+
if (ts.isPropertySignature(member) && member.type) {
|
|
838
|
+
return factory.updatePropertySignature(
|
|
839
|
+
member,
|
|
840
|
+
member.modifiers,
|
|
841
|
+
member.name,
|
|
842
|
+
member.questionToken,
|
|
843
|
+
wrapWithSchemeIfComponent(member.type)
|
|
844
|
+
);
|
|
845
|
+
}
|
|
846
|
+
return member;
|
|
847
|
+
})
|
|
848
|
+
);
|
|
849
|
+
}
|
|
850
|
+
return typeNode;
|
|
628
851
|
}
|
|
629
852
|
}
|
|
630
853
|
|
|
@@ -656,8 +879,8 @@ function generatePathExpression(
|
|
|
656
879
|
? factory.createTemplateExpression(
|
|
657
880
|
factory.createTemplateHead(head),
|
|
658
881
|
expressions.map(([prop, literal], index) => {
|
|
659
|
-
const value = isFlatArg
|
|
660
|
-
? rootObject
|
|
882
|
+
const value = isFlatArg
|
|
883
|
+
? rootObject
|
|
661
884
|
: factory.createPropertyAccessExpression(rootObject, factory.createIdentifier(prop));
|
|
662
885
|
const encodedValue = encodePathParams
|
|
663
886
|
? factory.createCallExpression(factory.createIdentifier('encodeURIComponent'), undefined, [
|