@acrool/rtk-query-codegen-openapi 0.0.6 → 0.0.9
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/README.md +33 -0
- package/lib/index.d.mts +15 -5
- package/lib/index.d.ts +15 -5
- package/lib/index.js +219 -157
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +219 -157
- package/lib/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/generate.ts +180 -132
- package/src/generators/react-hooks.ts +10 -4
- package/src/index.ts +76 -77
- package/src/types.ts +20 -5
- package/src/utils/downloadSchema.ts +33 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/isQuery.ts +9 -1
package/src/generate.ts
CHANGED
|
@@ -23,6 +23,7 @@ import type {
|
|
|
23
23
|
ParameterDefinition,
|
|
24
24
|
ParameterMatcher,
|
|
25
25
|
TextMatcher,
|
|
26
|
+
GenerateApiResult,
|
|
26
27
|
} from './types';
|
|
27
28
|
import { capitalize, getOperationDefinitions, getV3Doc, removeUndefined, isQuery as testIsQuery } from './utils';
|
|
28
29
|
import { factory } from './utils/factory';
|
|
@@ -38,7 +39,7 @@ function defaultIsDataResponse(code: string, includeDefault: boolean) {
|
|
|
38
39
|
return !Number.isNaN(parsedCode) && parsedCode >= 200 && parsedCode < 300;
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
function getOperationName({ verb, path }: Pick<OperationDefinition, 'verb' | 'path'
|
|
42
|
+
function getOperationName({ verb, path }: Pick<OperationDefinition, 'verb' | 'path'>) {
|
|
42
43
|
return _getOperationName(verb, path, undefined);
|
|
43
44
|
}
|
|
44
45
|
|
|
@@ -118,8 +119,10 @@ export async function generateApi(
|
|
|
118
119
|
mergeReadWriteOnly = false,
|
|
119
120
|
httpResolverOptions,
|
|
120
121
|
sharedTypesFile,
|
|
122
|
+
queryMatch,
|
|
123
|
+
endpointsQueryReturnTypeFile = './endpointsQueryReturnType',
|
|
121
124
|
}: GenerationOptions
|
|
122
|
-
) {
|
|
125
|
+
): Promise<GenerateApiResult> {
|
|
123
126
|
const v3Doc = (v3DocCache[spec] ??= await getV3Doc(spec, httpResolverOptions));
|
|
124
127
|
|
|
125
128
|
const apiGen = new ApiGenerator(v3Doc, {
|
|
@@ -151,34 +154,36 @@ export async function generateApi(
|
|
|
151
154
|
|
|
152
155
|
const components = v3Doc.components;
|
|
153
156
|
if (components) {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
factory.
|
|
171
|
-
|
|
172
|
-
|
|
157
|
+
// 只處理 schemas,其他 component 類型暫時不處理
|
|
158
|
+
if (components.schemas) {
|
|
159
|
+
const typeEntries = Object.entries(components.schemas).map(([name, def]) => {
|
|
160
|
+
addSchemeTypeName(name);
|
|
161
|
+
const typeName = capitalize(camelCase(name));
|
|
162
|
+
definedTypeNames.add(typeName);
|
|
163
|
+
const typeNode = wrapWithSchemeIfComponent(apiGen.getTypeFromSchema(def as OpenAPIV3.SchemaObject));
|
|
164
|
+
return factory.createTypeAliasDeclaration(
|
|
165
|
+
[factory.createModifier(ts.SyntaxKind.ExportKeyword)],
|
|
166
|
+
factory.createIdentifier(typeName),
|
|
167
|
+
undefined,
|
|
168
|
+
typeNode
|
|
169
|
+
);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
allTypeDefinitions.push(
|
|
173
|
+
factory.createModuleDeclaration(
|
|
174
|
+
[factory.createModifier(ts.SyntaxKind.ExportKeyword)],
|
|
175
|
+
factory.createIdentifier('Scheme'),
|
|
176
|
+
factory.createModuleBlock(typeEntries),
|
|
177
|
+
ts.NodeFlags.Namespace
|
|
178
|
+
)
|
|
173
179
|
);
|
|
174
|
-
}
|
|
175
|
-
allTypeDefinitions.push(...componentDefinitions);
|
|
180
|
+
}
|
|
176
181
|
}
|
|
177
182
|
|
|
178
183
|
const enumEntries = [
|
|
179
|
-
...apiGen.enumAliases.filter(e => ts.isEnumDeclaration(e)),
|
|
180
|
-
...apiGen.enumAliases.filter(e => ts.isTypeAliasDeclaration(e)),
|
|
181
|
-
].map(enumDecl => {
|
|
184
|
+
...apiGen.enumAliases.filter((e) => ts.isEnumDeclaration(e)),
|
|
185
|
+
...apiGen.enumAliases.filter((e) => ts.isTypeAliasDeclaration(e)),
|
|
186
|
+
].map((enumDecl) => {
|
|
182
187
|
if (ts.isEnumDeclaration(enumDecl)) {
|
|
183
188
|
return factory.createEnumDeclaration(
|
|
184
189
|
[factory.createModifier(ts.SyntaxKind.ExportKeyword)],
|
|
@@ -195,15 +200,15 @@ export async function generateApi(
|
|
|
195
200
|
}
|
|
196
201
|
return enumDecl;
|
|
197
202
|
});
|
|
198
|
-
|
|
203
|
+
|
|
199
204
|
const unionTypeEnums = apiGen.aliases
|
|
200
|
-
.filter(alias => {
|
|
205
|
+
.filter((alias) => {
|
|
201
206
|
if (ts.isTypeAliasDeclaration(alias) && alias.type) {
|
|
202
207
|
return ts.isUnionTypeNode(alias.type);
|
|
203
208
|
}
|
|
204
209
|
return false;
|
|
205
210
|
})
|
|
206
|
-
.map(alias => {
|
|
211
|
+
.map((alias) => {
|
|
207
212
|
if (ts.isTypeAliasDeclaration(alias)) {
|
|
208
213
|
return factory.createTypeAliasDeclaration(
|
|
209
214
|
[factory.createModifier(ts.SyntaxKind.ExportKeyword)],
|
|
@@ -214,9 +219,9 @@ export async function generateApi(
|
|
|
214
219
|
}
|
|
215
220
|
return alias;
|
|
216
221
|
});
|
|
217
|
-
|
|
222
|
+
|
|
218
223
|
const allEnumEntries = [...enumEntries, ...unionTypeEnums];
|
|
219
|
-
|
|
224
|
+
|
|
220
225
|
if (allEnumEntries.length > 0) {
|
|
221
226
|
allTypeDefinitions.push(
|
|
222
227
|
factory.createModuleDeclaration(
|
|
@@ -230,7 +235,7 @@ export async function generateApi(
|
|
|
230
235
|
|
|
231
236
|
if (apiGen.aliases.length > 0) {
|
|
232
237
|
const aliasEntries = apiGen.aliases
|
|
233
|
-
.filter(alias => {
|
|
238
|
+
.filter((alias) => {
|
|
234
239
|
if (ts.isTypeAliasDeclaration(alias)) {
|
|
235
240
|
const isDefinedInComponents = definedTypeNames.has(alias.name.text);
|
|
236
241
|
const isUnionTypeEnum = ts.isUnionTypeNode(alias.type);
|
|
@@ -238,7 +243,7 @@ export async function generateApi(
|
|
|
238
243
|
}
|
|
239
244
|
return false;
|
|
240
245
|
})
|
|
241
|
-
.map(alias => {
|
|
246
|
+
.map((alias) => {
|
|
242
247
|
if (ts.isTypeAliasDeclaration(alias)) {
|
|
243
248
|
return factory.createTypeAliasDeclaration(
|
|
244
249
|
[factory.createModifier(ts.SyntaxKind.ExportKeyword)],
|
|
@@ -251,10 +256,8 @@ export async function generateApi(
|
|
|
251
256
|
});
|
|
252
257
|
|
|
253
258
|
if (aliasEntries.length > 0) {
|
|
254
|
-
const existingSchemeIndex = allTypeDefinitions.findIndex(
|
|
255
|
-
ts.isModuleDeclaration(def) &&
|
|
256
|
-
ts.isIdentifier(def.name) &&
|
|
257
|
-
def.name.text === 'Scheme'
|
|
259
|
+
const existingSchemeIndex = allTypeDefinitions.findIndex(
|
|
260
|
+
(def) => ts.isModuleDeclaration(def) && ts.isIdentifier(def.name) && def.name.text === 'Scheme'
|
|
258
261
|
);
|
|
259
262
|
|
|
260
263
|
if (existingSchemeIndex >= 0) {
|
|
@@ -281,10 +284,10 @@ export async function generateApi(
|
|
|
281
284
|
|
|
282
285
|
const fs = await import('node:fs/promises');
|
|
283
286
|
const path = await import('node:path');
|
|
284
|
-
|
|
287
|
+
|
|
285
288
|
const sharedTypesDir = path.dirname(sharedTypesFile);
|
|
286
289
|
await fs.mkdir(sharedTypesDir, { recursive: true });
|
|
287
|
-
|
|
290
|
+
|
|
288
291
|
const output = printer.printNode(
|
|
289
292
|
ts.EmitHint.Unspecified,
|
|
290
293
|
factory.createSourceFile(
|
|
@@ -314,6 +317,7 @@ export async function generateApi(
|
|
|
314
317
|
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
|
|
315
318
|
|
|
316
319
|
const interfaces: Record<string, ts.InterfaceDeclaration | ts.TypeAliasDeclaration> = {};
|
|
320
|
+
|
|
317
321
|
function registerInterface(declaration: ts.InterfaceDeclaration | ts.TypeAliasDeclaration) {
|
|
318
322
|
const name = declaration.name.escapedText.toString();
|
|
319
323
|
if (name in interfaces) {
|
|
@@ -330,50 +334,64 @@ export async function generateApi(
|
|
|
330
334
|
apiFile = apiFile.replace(/\\/g, '/');
|
|
331
335
|
if (!apiFile.startsWith('.')) apiFile = `./${apiFile}`;
|
|
332
336
|
}
|
|
337
|
+
if (endpointsQueryReturnTypeFile.startsWith('.')) {
|
|
338
|
+
endpointsQueryReturnTypeFile = path.relative(path.dirname(outputFile), endpointsQueryReturnTypeFile);
|
|
339
|
+
endpointsQueryReturnTypeFile = endpointsQueryReturnTypeFile.replace(/\\/g, '/');
|
|
340
|
+
if (!endpointsQueryReturnTypeFile.startsWith('.')) endpointsQueryReturnTypeFile = `./${endpointsQueryReturnTypeFile}`;
|
|
341
|
+
}
|
|
333
342
|
}
|
|
334
343
|
apiFile = apiFile.replace(/\.[jt]sx?$/, '');
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
344
|
+
endpointsQueryReturnTypeFile = endpointsQueryReturnTypeFile.replace(/\.[jt]sx?$/, '');
|
|
345
|
+
|
|
346
|
+
const sharedTypesImportPath =
|
|
347
|
+
sharedTypesFile && outputFile
|
|
348
|
+
? (() => {
|
|
349
|
+
let rel = path
|
|
350
|
+
.relative(path.dirname(outputFile), sharedTypesFile)
|
|
351
|
+
.replace(/\\/g, '/')
|
|
352
|
+
.replace(/\.[jt]sx?$/, '');
|
|
353
|
+
if (!rel.startsWith('.')) rel = './' + rel;
|
|
354
|
+
return rel;
|
|
355
|
+
})()
|
|
356
|
+
: './shared-types';
|
|
357
|
+
|
|
358
|
+
// 收集操作名稱
|
|
359
|
+
const operationNames: string[] = [];
|
|
360
|
+
|
|
361
|
+
const sourceCode = printer.printNode(
|
|
347
362
|
ts.EmitHint.Unspecified,
|
|
348
363
|
factory.createSourceFile(
|
|
349
364
|
[
|
|
350
365
|
generateImportNode(apiFile, { [apiImport]: 'api' }),
|
|
351
|
-
generateImportNode(
|
|
352
|
-
...(sharedTypesFile
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
366
|
+
generateImportNode(endpointsQueryReturnTypeFile, { IRestFulEndpointsQueryReturn: 'IRestFulEndpointsQueryReturn' }),
|
|
367
|
+
...(sharedTypesFile
|
|
368
|
+
? [
|
|
369
|
+
generateImportNode(sharedTypesImportPath, {
|
|
370
|
+
Scheme: 'Scheme',
|
|
371
|
+
...(useEnumType ? { Enum: 'Enum' } : {}),
|
|
372
|
+
}),
|
|
373
|
+
]
|
|
374
|
+
: []),
|
|
358
375
|
...(tag ? [generateTagTypes({ addTagTypes: extractAllTagTypes({ operationDefinitions }) })] : []),
|
|
359
376
|
generateCreateApiCall({
|
|
360
377
|
tag,
|
|
361
378
|
endpointDefinitions: factory.createObjectLiteralExpression(
|
|
362
|
-
operationDefinitions.map((operationDefinition) =>
|
|
363
|
-
|
|
379
|
+
operationDefinitions.map((operationDefinition) => {
|
|
380
|
+
const operationName = getOperationName({ verb: operationDefinition.verb, path: operationDefinition.path });
|
|
381
|
+
const finalOperationName = operationNameSuffix ? capitalize(operationName + operationNameSuffix) : operationName;
|
|
382
|
+
operationNames.push(finalOperationName);
|
|
383
|
+
|
|
384
|
+
return generateEndpoint({
|
|
364
385
|
operationDefinition,
|
|
365
386
|
overrides: getOverrides(operationDefinition, endpointOverrides),
|
|
366
387
|
sharedTypesFile: !!sharedTypesFile,
|
|
367
|
-
|
|
368
|
-
|
|
388
|
+
queryMatch,
|
|
389
|
+
});
|
|
390
|
+
}),
|
|
369
391
|
true
|
|
370
392
|
),
|
|
371
393
|
}),
|
|
372
|
-
factory.createExportAssignment(
|
|
373
|
-
undefined,
|
|
374
|
-
undefined,
|
|
375
|
-
factory.createIdentifier(generatedApiName)
|
|
376
|
-
),
|
|
394
|
+
factory.createExportAssignment(undefined, undefined, factory.createIdentifier(generatedApiName)),
|
|
377
395
|
...Object.values(interfaces),
|
|
378
396
|
...(sharedTypesFile ? [] : [...apiGen.aliases, ...apiGen.enumAliases]),
|
|
379
397
|
...(hooks
|
|
@@ -383,6 +401,7 @@ export async function generateApi(
|
|
|
383
401
|
operationDefinitions,
|
|
384
402
|
endpointOverrides,
|
|
385
403
|
config: hooks,
|
|
404
|
+
queryMatch,
|
|
386
405
|
}),
|
|
387
406
|
]
|
|
388
407
|
: []),
|
|
@@ -393,6 +412,11 @@ export async function generateApi(
|
|
|
393
412
|
resultFile
|
|
394
413
|
);
|
|
395
414
|
|
|
415
|
+
return {
|
|
416
|
+
sourceCode,
|
|
417
|
+
operationNames,
|
|
418
|
+
};
|
|
419
|
+
|
|
396
420
|
function extractAllTagTypes({ operationDefinitions }: { operationDefinitions: OperationDefinition[] }) {
|
|
397
421
|
const allTagTypes = new Set<string>();
|
|
398
422
|
|
|
@@ -409,10 +433,12 @@ export async function generateApi(
|
|
|
409
433
|
operationDefinition,
|
|
410
434
|
overrides,
|
|
411
435
|
sharedTypesFile,
|
|
436
|
+
queryMatch,
|
|
412
437
|
}: {
|
|
413
438
|
operationDefinition: OperationDefinition;
|
|
414
439
|
overrides?: EndpointOverrides;
|
|
415
440
|
sharedTypesFile: boolean;
|
|
441
|
+
queryMatch?: (method: string, path: string) => boolean;
|
|
416
442
|
}) {
|
|
417
443
|
const {
|
|
418
444
|
verb,
|
|
@@ -423,7 +449,7 @@ export async function generateApi(
|
|
|
423
449
|
} = operationDefinition;
|
|
424
450
|
const operationName = getOperationName({ verb, path });
|
|
425
451
|
const tags = tag ? getTags({ verb, pathItem }) : [];
|
|
426
|
-
const isQuery = testIsQuery(verb, overrides);
|
|
452
|
+
const isQuery = testIsQuery(verb, path, overrides, queryMatch);
|
|
427
453
|
|
|
428
454
|
const returnsJson = apiGen.getResponseType(responses) === 'json';
|
|
429
455
|
let ResponseType: ts.TypeNode = factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
|
|
@@ -475,10 +501,11 @@ export async function generateApi(
|
|
|
475
501
|
|
|
476
502
|
const parameters = supportDeepObjects([...pathItemParameters, ...operationParameters])
|
|
477
503
|
.filter(argumentMatches(overrides?.parameterFilter))
|
|
478
|
-
.filter(param => param.in !== 'header');
|
|
504
|
+
.filter((param) => param.in !== 'header');
|
|
479
505
|
|
|
480
506
|
const allNames = parameters.map((p) => p.name);
|
|
481
507
|
const queryArg: QueryArgDefinitions = {};
|
|
508
|
+
|
|
482
509
|
function generateName(name: string, potentialPrefix: string) {
|
|
483
510
|
const isPureSnakeCase = /^[a-zA-Z][a-zA-Z0-9_]*$/.test(name);
|
|
484
511
|
const hasNamingConflict = allNames.filter((n) => n === name).length > 1;
|
|
@@ -501,7 +528,9 @@ export async function generateApi(
|
|
|
501
528
|
origin: 'param',
|
|
502
529
|
name,
|
|
503
530
|
originalName: param.name,
|
|
504
|
-
type: wrapWithSchemeIfComponent(
|
|
531
|
+
type: wrapWithSchemeIfComponent(
|
|
532
|
+
apiGen.getTypeFromSchema(isReference(param) ? param : param.schema, undefined, 'writeOnly')
|
|
533
|
+
),
|
|
505
534
|
required: param.required,
|
|
506
535
|
param,
|
|
507
536
|
};
|
|
@@ -580,10 +609,7 @@ export async function generateApi(
|
|
|
580
609
|
operationName: operationNameSuffix ? capitalize(operationName + operationNameSuffix) : operationName,
|
|
581
610
|
type: isQuery ? 'query' : 'mutation',
|
|
582
611
|
Response: ResponseTypeName,
|
|
583
|
-
QueryArg: factory.createTypeReferenceNode(
|
|
584
|
-
factory.createIdentifier('IRestFulEndpointsQueryReturn'),
|
|
585
|
-
[QueryArg]
|
|
586
|
-
),
|
|
612
|
+
QueryArg: factory.createTypeReferenceNode(factory.createIdentifier('IRestFulEndpointsQueryReturn'), [QueryArg]),
|
|
587
613
|
queryFn: generateQueryFn({
|
|
588
614
|
operationDefinition,
|
|
589
615
|
queryArg,
|
|
@@ -614,13 +640,24 @@ export async function generateApi(
|
|
|
614
640
|
encodePathParams: boolean;
|
|
615
641
|
encodeQueryParams: boolean;
|
|
616
642
|
}) {
|
|
617
|
-
const { path, verb } = operationDefinition;
|
|
643
|
+
const { path, verb, operation } = operationDefinition;
|
|
618
644
|
|
|
619
645
|
const bodyParameter = Object.values(queryArg).find((def) => def.origin === 'body');
|
|
620
646
|
|
|
621
647
|
const rootObject = factory.createIdentifier('queryArg');
|
|
622
648
|
const variablesObject = factory.createPropertyAccessExpression(rootObject, factory.createIdentifier('variables'));
|
|
623
649
|
|
|
650
|
+
// 提取 content type 的輔助函數
|
|
651
|
+
function getContentType(): string | undefined {
|
|
652
|
+
if (operation.requestBody) {
|
|
653
|
+
const requestBody = apiGen.resolve(operation.requestBody);
|
|
654
|
+
const contentTypes = Object.keys(requestBody.content || {});
|
|
655
|
+
// 直接返回第一個可用的 content type
|
|
656
|
+
return contentTypes[0];
|
|
657
|
+
}
|
|
658
|
+
return undefined;
|
|
659
|
+
}
|
|
660
|
+
|
|
624
661
|
function pickParams(paramIn: string) {
|
|
625
662
|
return Object.values(queryArg).filter((def) => def.origin === 'param' && def.param.in === paramIn);
|
|
626
663
|
}
|
|
@@ -629,8 +666,8 @@ export async function generateApi(
|
|
|
629
666
|
if (parameters.length === 0) return undefined;
|
|
630
667
|
|
|
631
668
|
const properties = parameters.map((param) => {
|
|
632
|
-
const value = isFlatArg
|
|
633
|
-
? variablesObject
|
|
669
|
+
const value = isFlatArg
|
|
670
|
+
? variablesObject
|
|
634
671
|
: factory.createPropertyAccessExpression(variablesObject, factory.createIdentifier(param.name));
|
|
635
672
|
|
|
636
673
|
const encodedValue =
|
|
@@ -655,6 +692,8 @@ export async function generateApi(
|
|
|
655
692
|
);
|
|
656
693
|
}
|
|
657
694
|
|
|
695
|
+
const contentType = getContentType();
|
|
696
|
+
|
|
658
697
|
return factory.createArrowFunction(
|
|
659
698
|
undefined,
|
|
660
699
|
undefined,
|
|
@@ -674,13 +713,22 @@ export async function generateApi(
|
|
|
674
713
|
factory.createIdentifier('method'),
|
|
675
714
|
factory.createStringLiteral(verb.toUpperCase())
|
|
676
715
|
),
|
|
716
|
+
contentType
|
|
717
|
+
? factory.createPropertyAssignment(
|
|
718
|
+
factory.createIdentifier('contentType'),
|
|
719
|
+
factory.createStringLiteral(contentType)
|
|
720
|
+
)
|
|
721
|
+
: undefined,
|
|
677
722
|
bodyParameter === undefined
|
|
678
723
|
? undefined
|
|
679
724
|
: factory.createPropertyAssignment(
|
|
680
725
|
factory.createIdentifier('body'),
|
|
681
726
|
isFlatArg
|
|
682
727
|
? variablesObject
|
|
683
|
-
: factory.createPropertyAccessExpression(
|
|
728
|
+
: factory.createPropertyAccessExpression(
|
|
729
|
+
variablesObject,
|
|
730
|
+
factory.createIdentifier(bodyParameter.name)
|
|
731
|
+
)
|
|
684
732
|
),
|
|
685
733
|
createObjectLiteralProperty(pickParams('cookie'), 'cookies'),
|
|
686
734
|
createObjectLiteralProperty(pickParams('query'), 'params'),
|
|
@@ -710,42 +758,36 @@ export async function generateApi(
|
|
|
710
758
|
function wrapWithSchemeIfComponent(typeNode: ts.TypeNode): ts.TypeNode {
|
|
711
759
|
if (ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName)) {
|
|
712
760
|
const typeName = typeNode.typeName.text;
|
|
713
|
-
|
|
761
|
+
|
|
714
762
|
// 檢查是否為 enum 類型(包括在 enumAliases 和 aliases 中的)
|
|
715
|
-
const isEnumType =
|
|
716
|
-
|
|
763
|
+
const isEnumType =
|
|
764
|
+
useEnumType &&
|
|
765
|
+
(apiGen.enumAliases.some((enumDecl) => {
|
|
717
766
|
if (ts.isEnumDeclaration(enumDecl) || ts.isTypeAliasDeclaration(enumDecl)) {
|
|
718
767
|
return enumDecl.name.text === typeName;
|
|
719
768
|
}
|
|
720
769
|
return false;
|
|
721
770
|
}) ||
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
771
|
+
apiGen.aliases.some((alias) => {
|
|
772
|
+
if (ts.isTypeAliasDeclaration(alias) && alias.type) {
|
|
773
|
+
// 檢查是否為 union type 的 enum
|
|
774
|
+
if (ts.isUnionTypeNode(alias.type)) {
|
|
775
|
+
return alias.name.text === typeName;
|
|
776
|
+
}
|
|
727
777
|
}
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
);
|
|
732
|
-
|
|
778
|
+
return false;
|
|
779
|
+
}));
|
|
780
|
+
|
|
733
781
|
if (isEnumType) {
|
|
734
782
|
return factory.createTypeReferenceNode(
|
|
735
|
-
factory.createQualifiedName(
|
|
736
|
-
factory.createIdentifier('Enum'),
|
|
737
|
-
typeNode.typeName
|
|
738
|
-
),
|
|
783
|
+
factory.createQualifiedName(factory.createIdentifier('Enum'), typeNode.typeName),
|
|
739
784
|
typeNode.typeArguments?.map(wrapWithSchemeIfComponent)
|
|
740
785
|
);
|
|
741
786
|
}
|
|
742
|
-
|
|
787
|
+
|
|
743
788
|
if (schemeTypeNames.has(typeName)) {
|
|
744
789
|
return factory.createTypeReferenceNode(
|
|
745
|
-
factory.createQualifiedName(
|
|
746
|
-
factory.createIdentifier('Scheme'),
|
|
747
|
-
typeNode.typeName
|
|
748
|
-
),
|
|
790
|
+
factory.createQualifiedName(factory.createIdentifier('Scheme'), typeNode.typeName),
|
|
749
791
|
typeNode.typeArguments?.map(wrapWithSchemeIfComponent)
|
|
750
792
|
);
|
|
751
793
|
}
|
|
@@ -762,42 +804,48 @@ export async function generateApi(
|
|
|
762
804
|
if (ts.isUnionTypeNode(typeNode)) {
|
|
763
805
|
// 檢查是否為 enum 的 union type
|
|
764
806
|
const unionTypes = typeNode.types;
|
|
765
|
-
if (
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
807
|
+
if (
|
|
808
|
+
unionTypes.length > 0 &&
|
|
809
|
+
unionTypes.every(
|
|
810
|
+
(type) =>
|
|
811
|
+
ts.isLiteralTypeNode(type) && (ts.isStringLiteral(type.literal) || ts.isNumericLiteral(type.literal))
|
|
812
|
+
)
|
|
813
|
+
) {
|
|
769
814
|
// 這是一個 enum 的 union type,我們需要找到對應的 enum 類型
|
|
770
|
-
const enumValues = unionTypes
|
|
771
|
-
|
|
772
|
-
if (ts.
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
815
|
+
const enumValues = unionTypes
|
|
816
|
+
.map((type) => {
|
|
817
|
+
if (ts.isLiteralTypeNode(type)) {
|
|
818
|
+
if (ts.isStringLiteral(type.literal)) {
|
|
819
|
+
return type.literal.text;
|
|
820
|
+
} else if (ts.isNumericLiteral(type.literal)) {
|
|
821
|
+
return type.literal.text;
|
|
822
|
+
}
|
|
776
823
|
}
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
824
|
+
return null;
|
|
825
|
+
})
|
|
826
|
+
.filter(Boolean);
|
|
827
|
+
|
|
781
828
|
// 查找對應的 enum 類型
|
|
782
|
-
const matchingEnum = apiGen.aliases.find(alias => {
|
|
829
|
+
const matchingEnum = apiGen.aliases.find((alias) => {
|
|
783
830
|
if (ts.isTypeAliasDeclaration(alias) && ts.isUnionTypeNode(alias.type)) {
|
|
784
|
-
const aliasValues = alias.type.types
|
|
785
|
-
|
|
786
|
-
if (ts.
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
831
|
+
const aliasValues = alias.type.types
|
|
832
|
+
.map((type) => {
|
|
833
|
+
if (ts.isLiteralTypeNode(type)) {
|
|
834
|
+
if (ts.isStringLiteral(type.literal)) {
|
|
835
|
+
return type.literal.text;
|
|
836
|
+
} else if (ts.isNumericLiteral(type.literal)) {
|
|
837
|
+
return type.literal.text;
|
|
838
|
+
}
|
|
790
839
|
}
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
return aliasValues.length === enumValues.length &&
|
|
796
|
-
aliasValues.every(val => enumValues.includes(val));
|
|
840
|
+
return null;
|
|
841
|
+
})
|
|
842
|
+
.filter(Boolean);
|
|
843
|
+
|
|
844
|
+
return aliasValues.length === enumValues.length && aliasValues.every((val) => enumValues.includes(val));
|
|
797
845
|
}
|
|
798
846
|
return false;
|
|
799
847
|
});
|
|
800
|
-
|
|
848
|
+
|
|
801
849
|
// 對於所有的 enum 類型,直接使用字串型別,不轉換為 Enum
|
|
802
850
|
// 這樣可以避免自動命名造成的變更問題
|
|
803
851
|
if (matchingEnum && ts.isTypeAliasDeclaration(matchingEnum)) {
|
|
@@ -805,12 +853,12 @@ export async function generateApi(
|
|
|
805
853
|
return typeNode;
|
|
806
854
|
}
|
|
807
855
|
}
|
|
808
|
-
|
|
856
|
+
|
|
809
857
|
return factory.createUnionTypeNode(typeNode.types.map(wrapWithSchemeIfComponent));
|
|
810
858
|
}
|
|
811
859
|
if (ts.isTypeLiteralNode(typeNode)) {
|
|
812
860
|
return factory.createTypeLiteralNode(
|
|
813
|
-
typeNode.members.map(member => {
|
|
861
|
+
typeNode.members.map((member) => {
|
|
814
862
|
if (ts.isPropertySignature(member) && member.type) {
|
|
815
863
|
return factory.updatePropertySignature(
|
|
816
864
|
member,
|
|
@@ -856,8 +904,8 @@ function generatePathExpression(
|
|
|
856
904
|
? factory.createTemplateExpression(
|
|
857
905
|
factory.createTemplateHead(head),
|
|
858
906
|
expressions.map(([prop, literal], index) => {
|
|
859
|
-
const value = isFlatArg
|
|
860
|
-
? rootObject
|
|
907
|
+
const value = isFlatArg
|
|
908
|
+
? rootObject
|
|
861
909
|
: factory.createPropertyAccessExpression(rootObject, factory.createIdentifier(prop));
|
|
862
910
|
const encodedValue = encodePathParams
|
|
863
911
|
? factory.createCallExpression(factory.createIdentifier('encodeURIComponent'), undefined, [
|
|
@@ -11,39 +11,43 @@ type GetReactHookNameParams = {
|
|
|
11
11
|
operationDefinition: OperationDefinition;
|
|
12
12
|
endpointOverrides: EndpointOverrides[] | undefined;
|
|
13
13
|
config: HooksConfigOptions;
|
|
14
|
+
queryMatch?: (method: string, path: string) => boolean;
|
|
14
15
|
};
|
|
15
16
|
|
|
16
17
|
type CreateBindingParams = {
|
|
17
18
|
operationDefinition: OperationDefinition;
|
|
18
19
|
overrides?: EndpointOverrides;
|
|
19
20
|
isLazy?: boolean;
|
|
21
|
+
queryMatch?: (method: string, path: string) => boolean;
|
|
20
22
|
};
|
|
21
23
|
|
|
22
24
|
const createBinding = ({
|
|
23
25
|
operationDefinition: { verb, path },
|
|
24
26
|
overrides,
|
|
25
27
|
isLazy = false,
|
|
28
|
+
queryMatch,
|
|
26
29
|
}: CreateBindingParams) =>
|
|
27
30
|
factory.createBindingElement(
|
|
28
31
|
undefined,
|
|
29
32
|
undefined,
|
|
30
33
|
factory.createIdentifier(
|
|
31
34
|
`use${isLazy ? 'Lazy' : ''}${capitalize(getOperationName(verb, path, undefined))}${
|
|
32
|
-
isQuery(verb, overrides) ? 'Query' : 'Mutation'
|
|
35
|
+
isQuery(verb, path, overrides, queryMatch) ? 'Query' : 'Mutation'
|
|
33
36
|
}`
|
|
34
37
|
),
|
|
35
38
|
undefined
|
|
36
39
|
);
|
|
37
40
|
|
|
38
|
-
const getReactHookName = ({ operationDefinition, endpointOverrides, config }: GetReactHookNameParams) => {
|
|
41
|
+
const getReactHookName = ({ operationDefinition, endpointOverrides, config, queryMatch }: GetReactHookNameParams) => {
|
|
39
42
|
const overrides = getOverrides(operationDefinition, endpointOverrides);
|
|
40
43
|
|
|
41
44
|
const baseParams = {
|
|
42
45
|
operationDefinition,
|
|
43
46
|
overrides,
|
|
47
|
+
queryMatch,
|
|
44
48
|
};
|
|
45
49
|
|
|
46
|
-
const _isQuery = isQuery(operationDefinition.verb, overrides);
|
|
50
|
+
const _isQuery = isQuery(operationDefinition.verb, operationDefinition.path, overrides, queryMatch);
|
|
47
51
|
|
|
48
52
|
// If `config` is true, just generate everything
|
|
49
53
|
if (typeof config === 'boolean') {
|
|
@@ -66,12 +70,14 @@ type GenerateReactHooksParams = {
|
|
|
66
70
|
operationDefinitions: OperationDefinition[];
|
|
67
71
|
endpointOverrides: EndpointOverrides[] | undefined;
|
|
68
72
|
config: HooksConfigOptions;
|
|
73
|
+
queryMatch?: (method: string, path: string) => boolean;
|
|
69
74
|
};
|
|
70
75
|
export const generateReactHooks = ({
|
|
71
76
|
exportName,
|
|
72
77
|
operationDefinitions,
|
|
73
78
|
endpointOverrides,
|
|
74
79
|
config,
|
|
80
|
+
queryMatch,
|
|
75
81
|
}: GenerateReactHooksParams) =>
|
|
76
82
|
factory.createVariableStatement(
|
|
77
83
|
[factory.createModifier(ts.SyntaxKind.ExportKeyword)],
|
|
@@ -80,7 +86,7 @@ export const generateReactHooks = ({
|
|
|
80
86
|
factory.createVariableDeclaration(
|
|
81
87
|
factory.createObjectBindingPattern(
|
|
82
88
|
operationDefinitions
|
|
83
|
-
.map((operationDefinition) => getReactHookName({ operationDefinition, endpointOverrides, config }))
|
|
89
|
+
.map((operationDefinition) => getReactHookName({ operationDefinition, endpointOverrides, config, queryMatch }))
|
|
84
90
|
.flat()
|
|
85
91
|
),
|
|
86
92
|
undefined,
|