@acrool/rtk-query-codegen-openapi 0.0.2-test.5 → 0.0.2-test.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/README.md CHANGED
@@ -23,4 +23,5 @@ This is a utility library meant to be used with [RTK Query](https://redux-toolki
23
23
 
24
24
  ```bash
25
25
  yarn build && npx acrool-rtk-query-codegen-openapi ./rtk-query-codegen.config.ts
26
+ yarn build && npx acrool-rtk-query-codegen-openapi ./rtk-query-codegen-slice.config.ts
26
27
  ```
package/lib/index.d.mts CHANGED
@@ -122,6 +122,7 @@ interface OutputFileOptions extends Partial<CommonOptions> {
122
122
  * If passed as true it will generate TS enums instead of union of strings
123
123
  */
124
124
  useEnumType?: boolean;
125
+ sharedTypesFile?: string;
125
126
  }
126
127
  type EndpointOverrides = {
127
128
  pattern: EndpointMatcher;
@@ -129,13 +130,17 @@ type EndpointOverrides = {
129
130
  type: 'mutation' | 'query';
130
131
  parameterFilter: ParameterMatcher;
131
132
  }>;
132
- type ConfigFile = Id<Require<CommonOptions & OutputFileOptions, 'outputFile'>> | Id<Omit<CommonOptions, 'outputFile'> & {
133
- outputFiles: {
134
- [outputFile: string]: Omit<OutputFileOptions, 'outputFile'>;
133
+ type OutputFilesConfig = {
134
+ [outputFile: string]: {
135
+ groupMatch: RegExp;
136
+ filterEndpoint: (groupName: string) => RegExp;
135
137
  };
138
+ };
139
+ type ConfigFile = Id<Require<CommonOptions & OutputFileOptions, 'outputFile'>> | Id<Omit<CommonOptions, 'outputFile'> & {
140
+ outputFiles: OutputFilesConfig;
136
141
  }>;
137
142
 
138
143
  declare function generateEndpoints(options: GenerationOptions): Promise<string | void>;
139
144
  declare function parseConfig(fullConfig: ConfigFile): (CommonOptions & OutputFileOptions)[];
140
145
 
141
- export { type ConfigFile, generateEndpoints, parseConfig };
146
+ export { type ConfigFile, type OutputFilesConfig, generateEndpoints, parseConfig };
package/lib/index.d.ts CHANGED
@@ -122,6 +122,7 @@ interface OutputFileOptions extends Partial<CommonOptions> {
122
122
  * If passed as true it will generate TS enums instead of union of strings
123
123
  */
124
124
  useEnumType?: boolean;
125
+ sharedTypesFile?: string;
125
126
  }
126
127
  type EndpointOverrides = {
127
128
  pattern: EndpointMatcher;
@@ -129,13 +130,17 @@ type EndpointOverrides = {
129
130
  type: 'mutation' | 'query';
130
131
  parameterFilter: ParameterMatcher;
131
132
  }>;
132
- type ConfigFile = Id<Require<CommonOptions & OutputFileOptions, 'outputFile'>> | Id<Omit<CommonOptions, 'outputFile'> & {
133
- outputFiles: {
134
- [outputFile: string]: Omit<OutputFileOptions, 'outputFile'>;
133
+ type OutputFilesConfig = {
134
+ [outputFile: string]: {
135
+ groupMatch: RegExp;
136
+ filterEndpoint: (groupName: string) => RegExp;
135
137
  };
138
+ };
139
+ type ConfigFile = Id<Require<CommonOptions & OutputFileOptions, 'outputFile'>> | Id<Omit<CommonOptions, 'outputFile'> & {
140
+ outputFiles: OutputFilesConfig;
136
141
  }>;
137
142
 
138
143
  declare function generateEndpoints(options: GenerationOptions): Promise<string | void>;
139
144
  declare function parseConfig(fullConfig: ConfigFile): (CommonOptions & OutputFileOptions)[];
140
145
 
141
- export { type ConfigFile, generateEndpoints, parseConfig };
146
+ export { type ConfigFile, type OutputFilesConfig, generateEndpoints, parseConfig };
package/lib/index.js CHANGED
@@ -326,14 +326,14 @@ function removeUndefined(t) {
326
326
 
327
327
  // src/generators/react-hooks.ts
328
328
  var createBinding = ({
329
- operationDefinition: { verb, path: path4, operation },
329
+ operationDefinition: { verb, path: path4 },
330
330
  overrides,
331
331
  isLazy = false
332
332
  }) => factory.createBindingElement(
333
333
  void 0,
334
334
  void 0,
335
335
  factory.createIdentifier(
336
- `use${isLazy ? "Lazy" : ""}${capitalize((0, import_generate.getOperationName)(verb, path4, operation.operationId))}${isQuery(verb, overrides) ? "Query" : "Mutation"}`
336
+ `use${isLazy ? "Lazy" : ""}${capitalize((0, import_generate.getOperationName)(verb, path4, void 0))}${isQuery(verb, overrides) ? "Query" : "Mutation"}`
337
337
  ),
338
338
  void 0
339
339
  );
@@ -387,8 +387,8 @@ function defaultIsDataResponse(code, includeDefault) {
387
387
  const parsedCode = Number(code);
388
388
  return !Number.isNaN(parsedCode) && parsedCode >= 200 && parsedCode < 300;
389
389
  }
390
- function getOperationName2({ verb, path: path4, operation }) {
391
- return (0, import_generate3.getOperationName)(verb, path4, operation.operationId);
390
+ function getOperationName2({ verb, path: path4 }) {
391
+ return (0, import_generate3.getOperationName)(verb, path4, void 0);
392
392
  }
393
393
  function getTags({ verb, pathItem }) {
394
394
  return verb ? pathItem[verb]?.tags || [] : [];
@@ -453,7 +453,8 @@ async function generateApi(spec, {
453
453
  includeDefault = false,
454
454
  useEnumType = false,
455
455
  mergeReadWriteOnly = false,
456
- httpResolverOptions
456
+ httpResolverOptions,
457
+ sharedTypesFile
457
458
  }) {
458
459
  const v3Doc = v3DocCache[spec] ??= await getV3Doc(spec, httpResolverOptions);
459
460
  const apiGen = new import_generate3.default(v3Doc, {
@@ -461,6 +462,62 @@ async function generateApi(spec, {
461
462
  useEnumType,
462
463
  mergeReadWriteOnly
463
464
  });
465
+ const schemeTypeNames = /* @__PURE__ */ new Set();
466
+ function addSchemeTypeName(name) {
467
+ schemeTypeNames.add(name);
468
+ schemeTypeNames.add((0, import_lodash.default)(name));
469
+ schemeTypeNames.add(capitalize((0, import_lodash.default)(name)));
470
+ }
471
+ if (sharedTypesFile) {
472
+ const resultFile2 = import_typescript4.default.createSourceFile(
473
+ "sharedTypes.ts",
474
+ "",
475
+ import_typescript4.default.ScriptTarget.Latest,
476
+ /*setParentNodes*/
477
+ false,
478
+ import_typescript4.default.ScriptKind.TS
479
+ );
480
+ const printer2 = import_typescript4.default.createPrinter({ newLine: import_typescript4.default.NewLineKind.LineFeed });
481
+ const allTypeDefinitions = [];
482
+ const components = v3Doc.components;
483
+ if (components) {
484
+ const componentDefinitions = Object.entries(components).map(([componentType, componentDefs]) => {
485
+ const typeEntries = Object.entries(componentDefs).map(([name, def]) => {
486
+ addSchemeTypeName(name);
487
+ const typeName = capitalize((0, import_lodash.default)(name));
488
+ const typeNode = wrapWithSchemeIfComponent(apiGen.getTypeFromSchema(def));
489
+ return factory.createTypeAliasDeclaration(
490
+ [factory.createModifier(import_typescript4.default.SyntaxKind.ExportKeyword)],
491
+ factory.createIdentifier(typeName),
492
+ void 0,
493
+ typeNode
494
+ );
495
+ });
496
+ return factory.createModuleDeclaration(
497
+ [factory.createModifier(import_typescript4.default.SyntaxKind.ExportKeyword)],
498
+ factory.createIdentifier("Scheme"),
499
+ factory.createModuleBlock(typeEntries),
500
+ import_typescript4.default.NodeFlags.Namespace
501
+ );
502
+ });
503
+ allTypeDefinitions.push(...componentDefinitions);
504
+ }
505
+ if (useEnumType) {
506
+ allTypeDefinitions.push(...apiGen.enumAliases);
507
+ }
508
+ allTypeDefinitions.push(...apiGen.aliases);
509
+ const output = printer2.printNode(
510
+ import_typescript4.default.EmitHint.Unspecified,
511
+ factory.createSourceFile(
512
+ allTypeDefinitions,
513
+ factory.createToken(import_typescript4.default.SyntaxKind.EndOfFileToken),
514
+ import_typescript4.default.NodeFlags.None
515
+ ),
516
+ resultFile2
517
+ );
518
+ const fs2 = await import("node:fs/promises");
519
+ await fs2.writeFile(sharedTypesFile, output, "utf-8");
520
+ }
464
521
  if (apiGen.spec.components?.schemas) {
465
522
  apiGen.preprocessComponents(apiGen.spec.components.schemas);
466
523
  }
@@ -492,12 +549,18 @@ async function generateApi(spec, {
492
549
  }
493
550
  }
494
551
  apiFile = apiFile.replace(/\.[jt]sx?$/, "");
552
+ const sharedTypesImportPath = sharedTypesFile && outputFile ? (() => {
553
+ let rel = import_node_path2.default.relative(import_node_path2.default.dirname(outputFile), sharedTypesFile).replace(/\\/g, "/").replace(/\.[jt]sx?$/, "");
554
+ if (!rel.startsWith(".")) rel = "./" + rel;
555
+ return rel;
556
+ })() : "./shared-types";
495
557
  return printer.printNode(
496
558
  import_typescript4.default.EmitHint.Unspecified,
497
559
  factory.createSourceFile(
498
560
  [
499
561
  generateImportNode(apiFile, { [apiImport]: "api" }),
500
562
  generateImportNode("@acrool/react-fetcher", { IRestFulEndpointsQueryReturn: "IRestFulEndpointsQueryReturn" }),
563
+ ...sharedTypesFile ? [generateImportNode(sharedTypesImportPath, { Scheme: "Scheme" })] : [],
501
564
  ...tag ? [generateTagTypes({ addTagTypes: extractAllTagTypes({ operationDefinitions }) })] : [],
502
565
  generateCreateApiCall({
503
566
  tag,
@@ -505,7 +568,8 @@ async function generateApi(spec, {
505
568
  operationDefinitions.map(
506
569
  (operationDefinition) => generateEndpoint({
507
570
  operationDefinition,
508
- overrides: getOverrides(operationDefinition, endpointOverrides)
571
+ overrides: getOverrides(operationDefinition, endpointOverrides),
572
+ sharedTypesFile: !!sharedTypesFile
509
573
  })
510
574
  ),
511
575
  true
@@ -517,8 +581,7 @@ async function generateApi(spec, {
517
581
  factory.createIdentifier(generatedApiName)
518
582
  ),
519
583
  ...Object.values(interfaces),
520
- ...apiGen.aliases,
521
- ...apiGen.enumAliases,
584
+ ...sharedTypesFile ? [] : [...apiGen.aliases, ...apiGen.enumAliases],
522
585
  ...hooks ? [
523
586
  generateReactHooks({
524
587
  exportName: generatedApiName,
@@ -545,7 +608,8 @@ async function generateApi(spec, {
545
608
  }
546
609
  function generateEndpoint({
547
610
  operationDefinition,
548
- overrides
611
+ overrides,
612
+ sharedTypesFile: sharedTypesFile2
549
613
  }) {
550
614
  const {
551
615
  verb,
@@ -554,7 +618,7 @@ async function generateApi(spec, {
554
618
  operation,
555
619
  operation: { responses, requestBody }
556
620
  } = operationDefinition;
557
- const operationName = getOperationName2({ verb, path: path4, operation });
621
+ const operationName = getOperationName2({ verb, path: path4 });
558
622
  const tags = tag ? getTags({ verb, pathItem }) : [];
559
623
  const isQuery2 = isQuery(verb, overrides);
560
624
  const returnsJson = apiGen.getResponseType(responses) === "json";
@@ -564,18 +628,39 @@ async function generateApi(spec, {
564
628
  ([code, response]) => [
565
629
  code,
566
630
  apiGen.resolve(response),
567
- apiGen.getTypeFromResponse(response, "readOnly") || factory.createKeywordTypeNode(import_typescript4.default.SyntaxKind.UndefinedKeyword)
631
+ wrapWithSchemeIfComponent(
632
+ apiGen.getTypeFromResponse(response, "readOnly") || factory.createKeywordTypeNode(import_typescript4.default.SyntaxKind.UndefinedKeyword)
633
+ )
568
634
  ]
569
635
  ).filter(
570
636
  ([status, response]) => isDataResponse(status, includeDefault, apiGen.resolve(response), responses || {})
571
- ).filter(([_1, _2, type]) => type !== import_generate3.keywordType.void).map(
572
- ([code, response, type]) => import_typescript4.default.addSyntheticLeadingComment(
573
- { ...type },
637
+ ).filter(([_1, _2, type]) => type !== import_generate3.keywordType.void).map(([code, response, type]) => {
638
+ if (sharedTypesFile2 && import_typescript4.default.isTypeReferenceNode(type) && type.typeName) {
639
+ if (import_typescript4.default.isIdentifier(type.typeName)) {
640
+ const typeName = type.typeName.text;
641
+ if (typeName in apiGen.aliases || typeName in apiGen.enumAliases) {
642
+ return import_typescript4.default.addSyntheticLeadingComment(
643
+ factory.createTypeReferenceNode(
644
+ factory.createQualifiedName(
645
+ factory.createIdentifier("sharedTypes"),
646
+ factory.createIdentifier((0, import_lodash.default)(typeName))
647
+ ),
648
+ type.typeArguments
649
+ ),
650
+ import_typescript4.default.SyntaxKind.MultiLineCommentTrivia,
651
+ `* status ${code} ${response.description} `,
652
+ false
653
+ );
654
+ }
655
+ }
656
+ }
657
+ return import_typescript4.default.addSyntheticLeadingComment(
658
+ type,
574
659
  import_typescript4.default.SyntaxKind.MultiLineCommentTrivia,
575
660
  `* status ${code} ${response.description} `,
576
661
  false
577
- )
578
- );
662
+ );
663
+ });
579
664
  if (returnTypes.length > 0) {
580
665
  ResponseType = factory.createUnionTypeNode(returnTypes);
581
666
  }
@@ -616,7 +701,7 @@ async function generateApi(spec, {
616
701
  origin: "param",
617
702
  name,
618
703
  originalName: param.name,
619
- type: apiGen.getTypeFromSchema((0, import_generate3.isReference)(param) ? param : param.schema, void 0, "writeOnly"),
704
+ type: wrapWithSchemeIfComponent(apiGen.getTypeFromSchema((0, import_generate3.isReference)(param) ? param : param.schema, void 0, "writeOnly")),
620
705
  required: param.required,
621
706
  param
622
707
  };
@@ -624,7 +709,7 @@ async function generateApi(spec, {
624
709
  if (requestBody) {
625
710
  const body = apiGen.resolve(requestBody);
626
711
  const schema = apiGen.getSchemaFromContent(body.content);
627
- const type = apiGen.getTypeFromSchema(schema);
712
+ const type = wrapWithSchemeIfComponent(apiGen.getTypeFromSchema(schema));
628
713
  const schemaName = (0, import_lodash.default)(
629
714
  type.name || (0, import_generate3.getReferenceName)(schema) || typeof schema === "object" && "title" in schema && schema.title || "body"
630
715
  );
@@ -633,7 +718,7 @@ async function generateApi(spec, {
633
718
  origin: "body",
634
719
  name,
635
720
  originalName: schemaName,
636
- type: apiGen.getTypeFromSchema(schema, void 0, "writeOnly"),
721
+ type: wrapWithSchemeIfComponent(apiGen.getTypeFromSchema(schema, void 0, "writeOnly")),
637
722
  required: true,
638
723
  body
639
724
  };
@@ -774,6 +859,49 @@ async function generateApi(spec, {
774
859
  function generateMutationEndpointProps({}) {
775
860
  return {};
776
861
  }
862
+ function wrapWithSchemeIfComponent(typeNode) {
863
+ if (import_typescript4.default.isTypeReferenceNode(typeNode) && import_typescript4.default.isIdentifier(typeNode.typeName)) {
864
+ const typeName = typeNode.typeName.text;
865
+ if (schemeTypeNames.has(typeName)) {
866
+ return factory.createTypeReferenceNode(
867
+ factory.createQualifiedName(
868
+ factory.createIdentifier("Scheme"),
869
+ typeNode.typeName
870
+ ),
871
+ typeNode.typeArguments?.map(wrapWithSchemeIfComponent)
872
+ );
873
+ }
874
+ if (typeNode.typeArguments) {
875
+ return factory.createTypeReferenceNode(
876
+ typeNode.typeName,
877
+ typeNode.typeArguments.map(wrapWithSchemeIfComponent)
878
+ );
879
+ }
880
+ }
881
+ if (import_typescript4.default.isArrayTypeNode(typeNode)) {
882
+ return factory.createArrayTypeNode(wrapWithSchemeIfComponent(typeNode.elementType));
883
+ }
884
+ if (import_typescript4.default.isUnionTypeNode(typeNode)) {
885
+ return factory.createUnionTypeNode(typeNode.types.map(wrapWithSchemeIfComponent));
886
+ }
887
+ if (import_typescript4.default.isTypeLiteralNode(typeNode)) {
888
+ return factory.createTypeLiteralNode(
889
+ typeNode.members.map((member) => {
890
+ if (import_typescript4.default.isPropertySignature(member) && member.type) {
891
+ return factory.updatePropertySignature(
892
+ member,
893
+ member.modifiers,
894
+ member.name,
895
+ member.questionToken,
896
+ wrapWithSchemeIfComponent(member.type)
897
+ );
898
+ }
899
+ return member;
900
+ })
901
+ );
902
+ }
903
+ return typeNode;
904
+ }
777
905
  }
778
906
  function generatePathExpression(path4, pathParameters, rootObject, isFlatArg, encodePathParams) {
779
907
  const expressions = [];
@@ -801,7 +929,55 @@ function generatePathExpression(path4, pathParameters, rootObject, isFlatArg, en
801
929
  }
802
930
 
803
931
  // src/index.ts
932
+ var import_lodash2 = __toESM(require("lodash.camelcase"));
804
933
  var require2 = (0, import_node_module.createRequire)(__filename);
934
+ async function ensureDirectoryExists(filePath) {
935
+ const dirname = import_node_path3.default.dirname(filePath);
936
+ if (!import_node_fs.default.existsSync(dirname)) {
937
+ await import_node_fs.default.promises.mkdir(dirname, { recursive: true });
938
+ }
939
+ }
940
+ function fileExists(filePath) {
941
+ try {
942
+ return import_node_fs.default.statSync(filePath).isFile();
943
+ } catch {
944
+ return false;
945
+ }
946
+ }
947
+ function getApiNameFromDir(dirPath) {
948
+ const dirName = import_node_path3.default.basename(dirPath);
949
+ return `${dirName}Api`;
950
+ }
951
+ async function ensureBaseFilesExist(outputDir) {
952
+ const enhanceEndpointsPath = import_node_path3.default.join(outputDir, "enhanceEndpoints.ts");
953
+ const indexPath = import_node_path3.default.join(outputDir, "index.ts");
954
+ const apiName = getApiNameFromDir(outputDir);
955
+ if (!fileExists(enhanceEndpointsPath)) {
956
+ const enhanceEndpointsContent = `import api from './query.generated';
957
+
958
+ const enhancedApi = api.enhanceEndpoints({
959
+ endpoints: {
960
+ },
961
+ });
962
+
963
+ export default enhancedApi;
964
+ `;
965
+ await import_node_fs.default.promises.writeFile(enhanceEndpointsPath, enhanceEndpointsContent, "utf-8");
966
+ }
967
+ if (!fileExists(indexPath)) {
968
+ const indexContent = `export * from './query.generated';
969
+ export {default as ${apiName}} from './enhanceEndpoints';
970
+ `;
971
+ await import_node_fs.default.promises.writeFile(indexPath, indexContent, "utf-8");
972
+ }
973
+ }
974
+ function getGroupNameFromPath(path4, pattern) {
975
+ const match = path4.match(pattern);
976
+ if (match && match[1]) {
977
+ return (0, import_lodash2.default)(match[1]);
978
+ }
979
+ return "common";
980
+ }
805
981
  async function generateEndpoints(options) {
806
982
  const schemaLocation = options.schemaFile;
807
983
  const schemaAbsPath = isValidUrl(options.schemaFile) ? options.schemaFile : import_node_path3.default.resolve(process.cwd(), schemaLocation);
@@ -810,8 +986,12 @@ async function generateEndpoints(options) {
810
986
  });
811
987
  const { outputFile, prettierConfigFile } = options;
812
988
  if (outputFile) {
989
+ const outputPath = import_node_path3.default.resolve(process.cwd(), outputFile);
990
+ await ensureDirectoryExists(outputPath);
991
+ const outputDir = import_node_path3.default.dirname(outputPath);
992
+ await ensureBaseFilesExist(outputDir);
813
993
  import_node_fs.default.writeFileSync(
814
- import_node_path3.default.resolve(process.cwd(), outputFile),
994
+ outputPath,
815
995
  await prettify(outputFile, sourceCode, prettierConfigFile)
816
996
  );
817
997
  } else {
@@ -822,13 +1002,29 @@ function parseConfig(fullConfig) {
822
1002
  const outFiles = [];
823
1003
  if ("outputFiles" in fullConfig) {
824
1004
  const { outputFiles, ...commonConfig } = fullConfig;
825
- for (const [outputFile, specificConfig] of Object.entries(outputFiles)) {
1005
+ const openApiDoc = JSON.parse(import_node_fs.default.readFileSync(fullConfig.schemaFile, "utf-8"));
1006
+ const paths = Object.keys(openApiDoc.paths);
1007
+ const [outputPath, config] = Object.entries(outputFiles)[0];
1008
+ const patterns = config.groupMatch;
1009
+ const filterEndpoint = config.filterEndpoint;
1010
+ const pattern = patterns;
1011
+ const groupedPaths = paths.reduce((acc, path4) => {
1012
+ const groupName = getGroupNameFromPath(path4, pattern);
1013
+ if (!acc[groupName]) {
1014
+ acc[groupName] = [];
1015
+ }
1016
+ acc[groupName].push(path4);
1017
+ return acc;
1018
+ }, {});
1019
+ Object.entries(groupedPaths).forEach(([groupName, paths2]) => {
1020
+ const finalOutputPath = outputPath.replace("$1", groupName);
1021
+ const filterEndpoints = filterEndpoint(groupName);
826
1022
  outFiles.push({
827
1023
  ...commonConfig,
828
- ...specificConfig,
829
- outputFile
1024
+ outputFile: finalOutputPath,
1025
+ filterEndpoints: [filterEndpoints]
830
1026
  });
831
- }
1027
+ });
832
1028
  } else {
833
1029
  outFiles.push(fullConfig);
834
1030
  }