@acrool/rtk-query-codegen-openapi 0.0.6 → 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/src/generate.ts CHANGED
@@ -38,7 +38,7 @@ 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 }: Pick<OperationDefinition, 'verb' | 'path' >) {
41
+ function getOperationName({ verb, path }: Pick<OperationDefinition, 'verb' | 'path'>) {
42
42
  return _getOperationName(verb, path, undefined);
43
43
  }
44
44
 
@@ -151,34 +151,36 @@ export async function generateApi(
151
151
 
152
152
  const components = v3Doc.components;
153
153
  if (components) {
154
- const componentDefinitions = Object.entries(components).map(([componentType, componentDefs]) => {
155
- const typeEntries = Object.entries(componentDefs as Record<string, unknown>)
156
- .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));
161
- return factory.createTypeAliasDeclaration(
162
- [factory.createModifier(ts.SyntaxKind.ExportKeyword)],
163
- factory.createIdentifier(typeName),
164
- undefined,
165
- typeNode
166
- );
167
- });
168
- return factory.createModuleDeclaration(
169
- [factory.createModifier(ts.SyntaxKind.ExportKeyword)],
170
- factory.createIdentifier('Scheme'),
171
- factory.createModuleBlock(typeEntries),
172
- ts.NodeFlags.Namespace
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));
161
+ return factory.createTypeAliasDeclaration(
162
+ [factory.createModifier(ts.SyntaxKind.ExportKeyword)],
163
+ factory.createIdentifier(typeName),
164
+ undefined,
165
+ typeNode
166
+ );
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
+ )
173
176
  );
174
- });
175
- allTypeDefinitions.push(...componentDefinitions);
177
+ }
176
178
  }
177
179
 
178
180
  const enumEntries = [
179
- ...apiGen.enumAliases.filter(e => ts.isEnumDeclaration(e)),
180
- ...apiGen.enumAliases.filter(e => ts.isTypeAliasDeclaration(e)),
181
- ].map(enumDecl => {
181
+ ...apiGen.enumAliases.filter((e) => ts.isEnumDeclaration(e)),
182
+ ...apiGen.enumAliases.filter((e) => ts.isTypeAliasDeclaration(e)),
183
+ ].map((enumDecl) => {
182
184
  if (ts.isEnumDeclaration(enumDecl)) {
183
185
  return factory.createEnumDeclaration(
184
186
  [factory.createModifier(ts.SyntaxKind.ExportKeyword)],
@@ -195,15 +197,15 @@ export async function generateApi(
195
197
  }
196
198
  return enumDecl;
197
199
  });
198
-
200
+
199
201
  const unionTypeEnums = apiGen.aliases
200
- .filter(alias => {
202
+ .filter((alias) => {
201
203
  if (ts.isTypeAliasDeclaration(alias) && alias.type) {
202
204
  return ts.isUnionTypeNode(alias.type);
203
205
  }
204
206
  return false;
205
207
  })
206
- .map(alias => {
208
+ .map((alias) => {
207
209
  if (ts.isTypeAliasDeclaration(alias)) {
208
210
  return factory.createTypeAliasDeclaration(
209
211
  [factory.createModifier(ts.SyntaxKind.ExportKeyword)],
@@ -214,9 +216,9 @@ export async function generateApi(
214
216
  }
215
217
  return alias;
216
218
  });
217
-
219
+
218
220
  const allEnumEntries = [...enumEntries, ...unionTypeEnums];
219
-
221
+
220
222
  if (allEnumEntries.length > 0) {
221
223
  allTypeDefinitions.push(
222
224
  factory.createModuleDeclaration(
@@ -230,7 +232,7 @@ export async function generateApi(
230
232
 
231
233
  if (apiGen.aliases.length > 0) {
232
234
  const aliasEntries = apiGen.aliases
233
- .filter(alias => {
235
+ .filter((alias) => {
234
236
  if (ts.isTypeAliasDeclaration(alias)) {
235
237
  const isDefinedInComponents = definedTypeNames.has(alias.name.text);
236
238
  const isUnionTypeEnum = ts.isUnionTypeNode(alias.type);
@@ -238,7 +240,7 @@ export async function generateApi(
238
240
  }
239
241
  return false;
240
242
  })
241
- .map(alias => {
243
+ .map((alias) => {
242
244
  if (ts.isTypeAliasDeclaration(alias)) {
243
245
  return factory.createTypeAliasDeclaration(
244
246
  [factory.createModifier(ts.SyntaxKind.ExportKeyword)],
@@ -251,10 +253,8 @@ export async function generateApi(
251
253
  });
252
254
 
253
255
  if (aliasEntries.length > 0) {
254
- const existingSchemeIndex = allTypeDefinitions.findIndex(def =>
255
- ts.isModuleDeclaration(def) &&
256
- ts.isIdentifier(def.name) &&
257
- def.name.text === 'Scheme'
256
+ const existingSchemeIndex = allTypeDefinitions.findIndex(
257
+ (def) => ts.isModuleDeclaration(def) && ts.isIdentifier(def.name) && def.name.text === 'Scheme'
258
258
  );
259
259
 
260
260
  if (existingSchemeIndex >= 0) {
@@ -281,10 +281,10 @@ export async function generateApi(
281
281
 
282
282
  const fs = await import('node:fs/promises');
283
283
  const path = await import('node:path');
284
-
284
+
285
285
  const sharedTypesDir = path.dirname(sharedTypesFile);
286
286
  await fs.mkdir(sharedTypesDir, { recursive: true });
287
-
287
+
288
288
  const output = printer.printNode(
289
289
  ts.EmitHint.Unspecified,
290
290
  factory.createSourceFile(
@@ -314,6 +314,7 @@ export async function generateApi(
314
314
  const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
315
315
 
316
316
  const interfaces: Record<string, ts.InterfaceDeclaration | ts.TypeAliasDeclaration> = {};
317
+
317
318
  function registerInterface(declaration: ts.InterfaceDeclaration | ts.TypeAliasDeclaration) {
318
319
  const name = declaration.name.escapedText.toString();
319
320
  if (name in interfaces) {
@@ -333,15 +334,17 @@ export async function generateApi(
333
334
  }
334
335
  apiFile = apiFile.replace(/\.[jt]sx?$/, '');
335
336
 
336
- const sharedTypesImportPath = sharedTypesFile && outputFile
337
- ? (() => {
338
- let rel = path.relative(path.dirname(outputFile), sharedTypesFile)
339
- .replace(/\\/g, '/')
340
- .replace(/\.[jt]sx?$/, '');
341
- if (!rel.startsWith('.')) rel = './' + rel;
342
- return rel;
343
- })()
344
- : './shared-types';
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';
345
348
 
346
349
  return printer.printNode(
347
350
  ts.EmitHint.Unspecified,
@@ -349,12 +352,14 @@ export async function generateApi(
349
352
  [
350
353
  generateImportNode(apiFile, { [apiImport]: 'api' }),
351
354
  generateImportNode('@acrool/react-fetcher', { IRestFulEndpointsQueryReturn: 'IRestFulEndpointsQueryReturn' }),
352
- ...(sharedTypesFile ? [
353
- generateImportNode(sharedTypesImportPath, {
354
- Scheme: 'Scheme',
355
- ...(useEnumType ? { Enum: 'Enum' } : {})
356
- })
357
- ] : []),
355
+ ...(sharedTypesFile
356
+ ? [
357
+ generateImportNode(sharedTypesImportPath, {
358
+ Scheme: 'Scheme',
359
+ ...(useEnumType ? { Enum: 'Enum' } : {}),
360
+ }),
361
+ ]
362
+ : []),
358
363
  ...(tag ? [generateTagTypes({ addTagTypes: extractAllTagTypes({ operationDefinitions }) })] : []),
359
364
  generateCreateApiCall({
360
365
  tag,
@@ -369,11 +374,7 @@ export async function generateApi(
369
374
  true
370
375
  ),
371
376
  }),
372
- factory.createExportAssignment(
373
- undefined,
374
- undefined,
375
- factory.createIdentifier(generatedApiName)
376
- ),
377
+ factory.createExportAssignment(undefined, undefined, factory.createIdentifier(generatedApiName)),
377
378
  ...Object.values(interfaces),
378
379
  ...(sharedTypesFile ? [] : [...apiGen.aliases, ...apiGen.enumAliases]),
379
380
  ...(hooks
@@ -475,10 +476,11 @@ export async function generateApi(
475
476
 
476
477
  const parameters = supportDeepObjects([...pathItemParameters, ...operationParameters])
477
478
  .filter(argumentMatches(overrides?.parameterFilter))
478
- .filter(param => param.in !== 'header');
479
+ .filter((param) => param.in !== 'header');
479
480
 
480
481
  const allNames = parameters.map((p) => p.name);
481
482
  const queryArg: QueryArgDefinitions = {};
483
+
482
484
  function generateName(name: string, potentialPrefix: string) {
483
485
  const isPureSnakeCase = /^[a-zA-Z][a-zA-Z0-9_]*$/.test(name);
484
486
  const hasNamingConflict = allNames.filter((n) => n === name).length > 1;
@@ -501,7 +503,9 @@ export async function generateApi(
501
503
  origin: 'param',
502
504
  name,
503
505
  originalName: param.name,
504
- type: wrapWithSchemeIfComponent(apiGen.getTypeFromSchema(isReference(param) ? param : param.schema, undefined, 'writeOnly')),
506
+ type: wrapWithSchemeIfComponent(
507
+ apiGen.getTypeFromSchema(isReference(param) ? param : param.schema, undefined, 'writeOnly')
508
+ ),
505
509
  required: param.required,
506
510
  param,
507
511
  };
@@ -580,10 +584,7 @@ export async function generateApi(
580
584
  operationName: operationNameSuffix ? capitalize(operationName + operationNameSuffix) : operationName,
581
585
  type: isQuery ? 'query' : 'mutation',
582
586
  Response: ResponseTypeName,
583
- QueryArg: factory.createTypeReferenceNode(
584
- factory.createIdentifier('IRestFulEndpointsQueryReturn'),
585
- [QueryArg]
586
- ),
587
+ QueryArg: factory.createTypeReferenceNode(factory.createIdentifier('IRestFulEndpointsQueryReturn'), [QueryArg]),
587
588
  queryFn: generateQueryFn({
588
589
  operationDefinition,
589
590
  queryArg,
@@ -614,13 +615,24 @@ export async function generateApi(
614
615
  encodePathParams: boolean;
615
616
  encodeQueryParams: boolean;
616
617
  }) {
617
- const { path, verb } = operationDefinition;
618
+ const { path, verb, operation } = operationDefinition;
618
619
 
619
620
  const bodyParameter = Object.values(queryArg).find((def) => def.origin === 'body');
620
621
 
621
622
  const rootObject = factory.createIdentifier('queryArg');
622
623
  const variablesObject = factory.createPropertyAccessExpression(rootObject, factory.createIdentifier('variables'));
623
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
+
624
636
  function pickParams(paramIn: string) {
625
637
  return Object.values(queryArg).filter((def) => def.origin === 'param' && def.param.in === paramIn);
626
638
  }
@@ -629,8 +641,8 @@ export async function generateApi(
629
641
  if (parameters.length === 0) return undefined;
630
642
 
631
643
  const properties = parameters.map((param) => {
632
- const value = isFlatArg
633
- ? variablesObject
644
+ const value = isFlatArg
645
+ ? variablesObject
634
646
  : factory.createPropertyAccessExpression(variablesObject, factory.createIdentifier(param.name));
635
647
 
636
648
  const encodedValue =
@@ -655,6 +667,8 @@ export async function generateApi(
655
667
  );
656
668
  }
657
669
 
670
+ const contentType = getContentType();
671
+
658
672
  return factory.createArrowFunction(
659
673
  undefined,
660
674
  undefined,
@@ -674,13 +688,22 @@ export async function generateApi(
674
688
  factory.createIdentifier('method'),
675
689
  factory.createStringLiteral(verb.toUpperCase())
676
690
  ),
691
+ contentType
692
+ ? factory.createPropertyAssignment(
693
+ factory.createIdentifier('contentType'),
694
+ factory.createStringLiteral(contentType)
695
+ )
696
+ : undefined,
677
697
  bodyParameter === undefined
678
698
  ? undefined
679
699
  : factory.createPropertyAssignment(
680
700
  factory.createIdentifier('body'),
681
701
  isFlatArg
682
702
  ? variablesObject
683
- : factory.createPropertyAccessExpression(variablesObject, factory.createIdentifier(bodyParameter.name))
703
+ : factory.createPropertyAccessExpression(
704
+ variablesObject,
705
+ factory.createIdentifier(bodyParameter.name)
706
+ )
684
707
  ),
685
708
  createObjectLiteralProperty(pickParams('cookie'), 'cookies'),
686
709
  createObjectLiteralProperty(pickParams('query'), 'params'),
@@ -710,42 +733,36 @@ export async function generateApi(
710
733
  function wrapWithSchemeIfComponent(typeNode: ts.TypeNode): ts.TypeNode {
711
734
  if (ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName)) {
712
735
  const typeName = typeNode.typeName.text;
713
-
736
+
714
737
  // 檢查是否為 enum 類型(包括在 enumAliases 和 aliases 中的)
715
- const isEnumType = useEnumType && (
716
- apiGen.enumAliases.some(enumDecl => {
738
+ const isEnumType =
739
+ useEnumType &&
740
+ (apiGen.enumAliases.some((enumDecl) => {
717
741
  if (ts.isEnumDeclaration(enumDecl) || ts.isTypeAliasDeclaration(enumDecl)) {
718
742
  return enumDecl.name.text === typeName;
719
743
  }
720
744
  return false;
721
745
  }) ||
722
- apiGen.aliases.some(alias => {
723
- if (ts.isTypeAliasDeclaration(alias) && alias.type) {
724
- // 檢查是否為 union type 的 enum
725
- if (ts.isUnionTypeNode(alias.type)) {
726
- return alias.name.text === typeName;
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
+ }
727
752
  }
728
- }
729
- return false;
730
- })
731
- );
732
-
753
+ return false;
754
+ }));
755
+
733
756
  if (isEnumType) {
734
757
  return factory.createTypeReferenceNode(
735
- factory.createQualifiedName(
736
- factory.createIdentifier('Enum'),
737
- typeNode.typeName
738
- ),
758
+ factory.createQualifiedName(factory.createIdentifier('Enum'), typeNode.typeName),
739
759
  typeNode.typeArguments?.map(wrapWithSchemeIfComponent)
740
760
  );
741
761
  }
742
-
762
+
743
763
  if (schemeTypeNames.has(typeName)) {
744
764
  return factory.createTypeReferenceNode(
745
- factory.createQualifiedName(
746
- factory.createIdentifier('Scheme'),
747
- typeNode.typeName
748
- ),
765
+ factory.createQualifiedName(factory.createIdentifier('Scheme'), typeNode.typeName),
749
766
  typeNode.typeArguments?.map(wrapWithSchemeIfComponent)
750
767
  );
751
768
  }
@@ -762,42 +779,48 @@ export async function generateApi(
762
779
  if (ts.isUnionTypeNode(typeNode)) {
763
780
  // 檢查是否為 enum 的 union type
764
781
  const unionTypes = typeNode.types;
765
- if (unionTypes.length > 0 && unionTypes.every(type =>
766
- ts.isLiteralTypeNode(type) &&
767
- (ts.isStringLiteral(type.literal) || ts.isNumericLiteral(type.literal))
768
- )) {
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
+ ) {
769
789
  // 這是一個 enum 的 union type,我們需要找到對應的 enum 類型
770
- const enumValues = unionTypes.map(type => {
771
- if (ts.isLiteralTypeNode(type)) {
772
- if (ts.isStringLiteral(type.literal)) {
773
- return type.literal.text;
774
- } else if (ts.isNumericLiteral(type.literal)) {
775
- return type.literal.text;
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
+ }
776
798
  }
777
- }
778
- return null;
779
- }).filter(Boolean);
780
-
799
+ return null;
800
+ })
801
+ .filter(Boolean);
802
+
781
803
  // 查找對應的 enum 類型
782
- const matchingEnum = apiGen.aliases.find(alias => {
804
+ const matchingEnum = apiGen.aliases.find((alias) => {
783
805
  if (ts.isTypeAliasDeclaration(alias) && ts.isUnionTypeNode(alias.type)) {
784
- const aliasValues = alias.type.types.map(type => {
785
- if (ts.isLiteralTypeNode(type)) {
786
- if (ts.isStringLiteral(type.literal)) {
787
- return type.literal.text;
788
- } else if (ts.isNumericLiteral(type.literal)) {
789
- return type.literal.text;
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
+ }
790
814
  }
791
- }
792
- return null;
793
- }).filter(Boolean);
794
-
795
- return aliasValues.length === enumValues.length &&
796
- aliasValues.every(val => enumValues.includes(val));
815
+ return null;
816
+ })
817
+ .filter(Boolean);
818
+
819
+ return aliasValues.length === enumValues.length && aliasValues.every((val) => enumValues.includes(val));
797
820
  }
798
821
  return false;
799
822
  });
800
-
823
+
801
824
  // 對於所有的 enum 類型,直接使用字串型別,不轉換為 Enum
802
825
  // 這樣可以避免自動命名造成的變更問題
803
826
  if (matchingEnum && ts.isTypeAliasDeclaration(matchingEnum)) {
@@ -805,12 +828,12 @@ export async function generateApi(
805
828
  return typeNode;
806
829
  }
807
830
  }
808
-
831
+
809
832
  return factory.createUnionTypeNode(typeNode.types.map(wrapWithSchemeIfComponent));
810
833
  }
811
834
  if (ts.isTypeLiteralNode(typeNode)) {
812
835
  return factory.createTypeLiteralNode(
813
- typeNode.members.map(member => {
836
+ typeNode.members.map((member) => {
814
837
  if (ts.isPropertySignature(member) && member.type) {
815
838
  return factory.updatePropertySignature(
816
839
  member,
@@ -856,8 +879,8 @@ function generatePathExpression(
856
879
  ? factory.createTemplateExpression(
857
880
  factory.createTemplateHead(head),
858
881
  expressions.map(([prop, literal], index) => {
859
- const value = isFlatArg
860
- ? rootObject
882
+ const value = isFlatArg
883
+ ? rootObject
861
884
  : factory.createPropertyAccessExpression(rootObject, factory.createIdentifier(prop));
862
885
  const encodedValue = encodePathParams
863
886
  ? factory.createCallExpression(factory.createIdentifier('encodeURIComponent'), undefined, [