@bitstack/ng-query-codegen-openapi 0.0.30 → 0.0.31-alpha.0

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.js CHANGED
@@ -326,6 +326,13 @@ var FileWriterService = class {
326
326
  async writeFile(filePath, content) {
327
327
  try {
328
328
  const resolvedPath = import_node_path3.default.resolve(process.cwd(), filePath);
329
+ const fileName = import_node_path3.default.basename(resolvedPath);
330
+ if (fileName === "enhanceEndpoints.ts" && import_node_fs3.default.existsSync(resolvedPath)) {
331
+ return {
332
+ path: resolvedPath,
333
+ success: true
334
+ };
335
+ }
329
336
  await ensureDirectoryExists(resolvedPath);
330
337
  import_node_fs3.default.writeFileSync(resolvedPath, content);
331
338
  return {
@@ -353,7 +360,7 @@ var FileWriterService = class {
353
360
  return results;
354
361
  }
355
362
  /**
356
- * 為群組寫入標準檔案結構
363
+ * 為群組寫入 RTK Query 檔案結構
357
364
  * @param groupOutputDir - 群組輸出目錄
358
365
  * @param files - 檔案內容
359
366
  */
@@ -362,11 +369,11 @@ var FileWriterService = class {
362
369
  if (files.types) {
363
370
  filesToWrite[import_node_path3.default.join(groupOutputDir, "types.ts")] = files.types;
364
371
  }
365
- if (files.apiService) {
366
- filesToWrite[import_node_path3.default.join(groupOutputDir, "api.service.ts")] = files.apiService;
367
- }
368
372
  if (files.queryService) {
369
- filesToWrite[import_node_path3.default.join(groupOutputDir, "query.service.ts")] = files.queryService;
373
+ filesToWrite[import_node_path3.default.join(groupOutputDir, "query.generated.ts")] = files.queryService;
374
+ }
375
+ if (files.enhanceEndpoints) {
376
+ filesToWrite[import_node_path3.default.join(groupOutputDir, "enhanceEndpoints.ts")] = files.enhanceEndpoints;
370
377
  }
371
378
  if (files.index) {
372
379
  filesToWrite[import_node_path3.default.join(groupOutputDir, "index.ts")] = files.index;
@@ -383,9 +390,6 @@ var FileWriterService = class {
383
390
  if (sharedFiles.commonTypes) {
384
391
  filesToWrite[import_node_path3.default.join(outputDir, "common-types.ts")] = sharedFiles.commonTypes;
385
392
  }
386
- if (sharedFiles.cacheKeys) {
387
- filesToWrite[import_node_path3.default.join(outputDir, "cache-keys.ts")] = sharedFiles.cacheKeys;
388
- }
389
393
  if (sharedFiles.doNotModify) {
390
394
  filesToWrite[import_node_path3.default.join(outputDir, "DO_NOT_MODIFY.md")] = sharedFiles.doNotModify;
391
395
  }
@@ -414,7 +418,6 @@ var OpenApiParserService = class {
414
418
  this.v3Doc = v3Doc;
415
419
  this.apiGen = new import_generate2.default(v3Doc, {
416
420
  unionUndefined: options.unionUndefined,
417
- useEnumType: options.useEnumType,
418
421
  mergeReadWriteOnly: options.mergeReadWriteOnly
419
422
  });
420
423
  }
@@ -424,6 +427,12 @@ var OpenApiParserService = class {
424
427
  */
425
428
  initialize() {
426
429
  if (this.apiGen.spec.components?.schemas) {
430
+ Object.keys(this.apiGen.spec.components.schemas).forEach((schemaName) => {
431
+ const schema = this.apiGen.spec.components.schemas[schemaName];
432
+ if (schema && typeof schema === "object" && "title" in schema) {
433
+ delete schema.title;
434
+ }
435
+ });
427
436
  this.apiGen.preprocessComponents(this.apiGen.spec.components.schemas);
428
437
  Object.keys(this.apiGen.spec.components.schemas).forEach((schemaName) => {
429
438
  try {
@@ -462,64 +471,6 @@ var OpenApiParserService = class {
462
471
  }
463
472
  };
464
473
 
465
- // src/generators/cache-keys-generator.ts
466
- init_cjs_shims();
467
-
468
- // src/generators/utils-generator.ts
469
- init_cjs_shims();
470
- function generateUtilsFile() {
471
- return `/* eslint-disable */
472
- // [Warning] Generated automatically - do not edit manually
473
-
474
- /**
475
- * Clear undefined in object
476
- */
477
- export function withoutUndefined(obj?: Record<string, any>) {
478
- if(typeof obj === 'undefined') return;
479
- return Object.fromEntries(
480
- Object.entries(obj).filter(([_, v]) => v !== undefined && v !== null)
481
- );
482
- }
483
-
484
- `;
485
- }
486
- function toCamelCase(str) {
487
- return str.toLowerCase().replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
488
- }
489
-
490
- // src/generators/cache-keys-generator.ts
491
- function generateCacheKeysFile(allEndpointInfos) {
492
- const groupedKeys = allEndpointInfos.reduce((acc, info) => {
493
- if (!acc[info.groupKey]) {
494
- acc[info.groupKey] = [];
495
- }
496
- acc[info.groupKey].push({
497
- operationName: info.operationName,
498
- queryKeyName: info.queryKeyName
499
- });
500
- return acc;
501
- }, {});
502
- const enumEntries = Object.entries(groupedKeys).map(([groupKey, keys]) => {
503
- const groupComment = ` // ${groupKey.charAt(0).toUpperCase() + groupKey.slice(1)} related cache keys`;
504
- const keyEntries = keys.map(
505
- (key) => ` ${key.queryKeyName} = '${toCamelCase(key.queryKeyName)}',`
506
- ).join("\n");
507
- return `${groupComment}
508
- ${keyEntries}`;
509
- }).join("\n\n");
510
- return `// [Warning] Generated automatically - do not edit manually
511
-
512
- /**
513
- * Cache keys enum for all API queries
514
- */
515
- export enum ECacheKeys {
516
- ${enumEntries}
517
- }
518
-
519
- export default ECacheKeys;
520
- `;
521
- }
522
-
523
474
  // src/generators/common-types-generator.ts
524
475
  init_cjs_shims();
525
476
  function generateCommonTypesFile() {
@@ -540,11 +491,15 @@ export interface QueryConfig {
540
491
  keepUnusedDataFor?: number,
541
492
  }
542
493
 
494
+ export interface IRequestConfig extends RequestOptions { {
495
+ timeout?: number;
496
+ }
497
+
543
498
  export type IRestFulEndpointsQueryReturn<TVariables> = TVariables extends void ?
544
- void | {fetchOptions?: RequestOptions;config?: QueryConfig;}:
499
+ void | {fetchOptions?: IRequestConfig;}:
545
500
  {
546
501
  variables: TVariables;
547
- fetchOptions?: RequestOptions;
502
+ fetchOptions?: IRequestConfig;
548
503
  config?: QueryConfig;
549
504
  };
550
505
  `;
@@ -553,6 +508,19 @@ export type IRestFulEndpointsQueryReturn<TVariables> = TVariables extends void ?
553
508
  // src/generators/component-schema-generator.ts
554
509
  init_cjs_shims();
555
510
  var import_typescript = __toESM(require("typescript"));
511
+ function toPascalCase(name) {
512
+ return name.charAt(0).toUpperCase() + name.slice(1);
513
+ }
514
+ function renameIdentifier(node, oldName, newName) {
515
+ return import_typescript.default.transform(node, [
516
+ (context) => (rootNode) => import_typescript.default.visitNode(rootNode, function visit(node2) {
517
+ if (import_typescript.default.isIdentifier(node2) && node2.text === oldName) {
518
+ return import_typescript.default.factory.createIdentifier(newName);
519
+ }
520
+ return import_typescript.default.visitEachChild(node2, visit, context);
521
+ })
522
+ ]).transformed[0];
523
+ }
556
524
  function generateComponentSchemaFile(interfaces) {
557
525
  const printer = import_typescript.default.createPrinter({ newLine: import_typescript.default.NewLineKind.LineFeed });
558
526
  const resultFile = import_typescript.default.createSourceFile(
@@ -562,10 +530,19 @@ function generateComponentSchemaFile(interfaces) {
562
530
  false,
563
531
  import_typescript.default.ScriptKind.TS
564
532
  );
533
+ const renamedInterfaces = [];
534
+ const typeNameMapping = {};
535
+ Object.entries(interfaces).forEach(([originalName, node]) => {
536
+ const pascalCaseName = toPascalCase(originalName);
537
+ typeNameMapping[originalName] = pascalCaseName;
538
+ const renamedNode = renameIdentifier(node, originalName, pascalCaseName);
539
+ const printed = printer.printNode(import_typescript.default.EmitHint.Unspecified, renamedNode, resultFile);
540
+ renamedInterfaces.push(printed);
541
+ });
565
542
  return `/* eslint-disable */
566
- // [Warning] Generated automatically - do not edit manually
567
-
568
- ${Object.values(interfaces).map((i) => printer.printNode(import_typescript.default.EmitHint.Unspecified, i, resultFile)).join("\n")}
543
+ // [Warning] Generated automatically - do not edit manually
544
+
545
+ ${renamedInterfaces.join("\n")}
569
546
  `;
570
547
  }
571
548
 
@@ -621,7 +598,13 @@ var EndpointInfoExtractor = class {
621
598
  */
622
599
  extractSingleEndpointInfo(operationDefinition) {
623
600
  const { verb, path: path5, operation } = operationDefinition;
624
- const { operationNameSuffix = "", argSuffix = "Req", responseSuffix = "Res", queryMatch, endpointOverrides } = this.options;
601
+ const {
602
+ operationNameSuffix = "",
603
+ argSuffix = "Req",
604
+ responseSuffix = "Res",
605
+ queryMatch,
606
+ endpointOverrides
607
+ } = this.options;
625
608
  const operationName = getOperationName({ verb, path: path5 });
626
609
  const finalOperationName = operationNameSuffix ? capitalize(operationName + operationNameSuffix) : operationName;
627
610
  const argTypeName = capitalize(operationName + operationNameSuffix + argSuffix);
@@ -629,7 +612,9 @@ var EndpointInfoExtractor = class {
629
612
  const isQuery2 = isQuery(verb, path5, getOverrides(operationDefinition, endpointOverrides), queryMatch);
630
613
  const queryKeyName = `${operationName.replace(/([A-Z])/g, "_$1").toUpperCase()}`;
631
614
  const summary = operation.summary || `${verb.toUpperCase()} ${path5}`;
632
- const { queryParams, pathParams, isVoidArg } = this.extractParameters(operationDefinition);
615
+ const { queryParams, pathParams, isVoidArg, hasRequestBody } = this.extractParameters(operationDefinition);
616
+ const contentType = this.extractContentType(operation);
617
+ const tags = Array.isArray(operation.tags) ? operation.tags : [];
633
618
  return {
634
619
  operationName: finalOperationName,
635
620
  argTypeName,
@@ -641,7 +626,10 @@ var EndpointInfoExtractor = class {
641
626
  queryParams,
642
627
  pathParams,
643
628
  isVoidArg,
644
- summary
629
+ summary,
630
+ contentType,
631
+ hasRequestBody,
632
+ tags
645
633
  };
646
634
  }
647
635
  /**
@@ -651,15 +639,21 @@ var EndpointInfoExtractor = class {
651
639
  extractParameters(operationDefinition) {
652
640
  const { operation, pathItem } = operationDefinition;
653
641
  const operationParameters = this.resolveArray(operation.parameters);
654
- const pathItemParameters = this.resolveArray(pathItem.parameters).filter((pp) => !operationParameters.some((op) => op.name === pp.name && op.in === pp.in));
655
- const allParameters = (0, import_generate3.supportDeepObjects)([...pathItemParameters, ...operationParameters]).filter((param) => param.in !== "header");
642
+ const pathItemParameters = this.resolveArray(pathItem.parameters).filter(
643
+ (pp) => !operationParameters.some((op) => op.name === pp.name && op.in === pp.in)
644
+ );
645
+ const allParameters = (0, import_generate3.supportDeepObjects)([...pathItemParameters, ...operationParameters]).filter(
646
+ (param) => param.in !== "header"
647
+ );
656
648
  const queryParams = allParameters.filter((param) => param.in === "query");
657
649
  const pathParams = allParameters.filter((param) => param.in === "path");
650
+ const hasRequestBody = !!operation.requestBody;
658
651
  const isVoidArg = queryParams.length === 0 && pathParams.length === 0 && !operation.requestBody;
659
652
  return {
660
653
  queryParams,
661
654
  pathParams,
662
- isVoidArg
655
+ isVoidArg,
656
+ hasRequestBody
663
657
  };
664
658
  }
665
659
  /**
@@ -669,11 +663,46 @@ var EndpointInfoExtractor = class {
669
663
  if (!parameters) return [];
670
664
  return Array.isArray(parameters) ? parameters : [parameters];
671
665
  }
666
+ /**
667
+ * 提取操作的 content type
668
+ * @param operation - 操作對象
669
+ */
670
+ extractContentType(operation) {
671
+ if (!operation.requestBody) {
672
+ return "application/json";
673
+ }
674
+ const content = operation.requestBody.content;
675
+ if (!content || typeof content !== "object") {
676
+ return "application/json";
677
+ }
678
+ const contentTypes = Object.keys(content);
679
+ if (contentTypes.length === 0) {
680
+ return "application/json";
681
+ }
682
+ return contentTypes[0];
683
+ }
672
684
  };
673
685
 
674
686
  // src/generators/types-generator.ts
675
687
  init_cjs_shims();
688
+ var toPascalCase2 = (name) => {
689
+ return name.charAt(0).toUpperCase() + name.slice(1);
690
+ };
676
691
  function generateTypesFile(endpointInfos, _options, schemaInterfaces, operationDefinitions) {
692
+ const schemaTypeMap = {};
693
+ if (schemaInterfaces) {
694
+ Object.keys(schemaInterfaces).forEach((actualTypeName) => {
695
+ schemaTypeMap[actualTypeName] = actualTypeName;
696
+ if (actualTypeName.endsWith("Vo")) {
697
+ const openApiName = actualTypeName.slice(0, -2) + "VO";
698
+ schemaTypeMap[openApiName] = actualTypeName;
699
+ }
700
+ if (actualTypeName.endsWith("Dto")) {
701
+ const openApiName = actualTypeName.slice(0, -3) + "DTO";
702
+ schemaTypeMap[openApiName] = actualTypeName;
703
+ }
704
+ });
705
+ }
677
706
  let importStatement = `/* eslint-disable */
678
707
  // [Warning] Generated automatically - do not edit manually
679
708
 
@@ -690,35 +719,19 @@ function generateTypesFile(endpointInfos, _options, schemaInterfaces, operationD
690
719
  const reqTypeName = endpoint.argTypeName;
691
720
  const resTypeName = endpoint.responseTypeName;
692
721
  if (reqTypeName) {
693
- const requestTypeContent = generateRequestTypeContent(endpoint, operationDefinitions);
694
- if (requestTypeContent.trim() === "" || requestTypeContent.includes("TODO") || requestTypeContent.includes("[key: string]: any")) {
695
- endpointTypes.push(
696
- `export type ${reqTypeName} = void;`,
697
- ``
698
- );
722
+ const requestTypeContent = generateRequestTypeContent(endpoint, operationDefinitions, schemaTypeMap);
723
+ if (requestTypeContent.trim() === "") {
724
+ endpointTypes.push(`export type ${reqTypeName} = void;`, ``);
699
725
  } else {
700
- endpointTypes.push(
701
- `export type ${reqTypeName} = {`,
702
- requestTypeContent,
703
- `};`,
704
- ``
705
- );
726
+ endpointTypes.push(`export type ${reqTypeName} = {`, requestTypeContent, `};`, ``);
706
727
  }
707
728
  }
708
729
  if (resTypeName) {
709
- const responseTypeContent = generateResponseTypeContent(endpoint, operationDefinitions);
710
- if (responseTypeContent.trim() === "" || responseTypeContent.includes("TODO") || responseTypeContent.includes("[key: string]: any")) {
711
- endpointTypes.push(
712
- `export type ${resTypeName} = void;`,
713
- ``
714
- );
730
+ const responseTypeContent = generateResponseTypeContent(endpoint, operationDefinitions, schemaTypeMap);
731
+ if (responseTypeContent.trim() === "") {
732
+ endpointTypes.push(`export type ${resTypeName} = void;`, ``);
715
733
  } else {
716
- endpointTypes.push(
717
- `export type ${resTypeName} = {`,
718
- responseTypeContent,
719
- `};`,
720
- ``
721
- );
734
+ endpointTypes.push(`export type ${resTypeName} = {`, responseTypeContent, `};`, ``);
722
735
  }
723
736
  }
724
737
  });
@@ -726,27 +739,23 @@ function generateTypesFile(endpointInfos, _options, schemaInterfaces, operationD
726
739
  typeDefinitions.push(endpointTypes.join("\n"));
727
740
  }
728
741
  if (typeDefinitions.length === 0) {
729
- typeDefinitions.push(
730
- `// \u6B64\u6A94\u6848\u7528\u65BC\u5B9A\u7FA9 API \u76F8\u95DC\u7684\u985E\u578B`,
731
- `// \u985E\u578B\u5B9A\u7FA9\u6703\u6839\u64DA OpenAPI Schema \u81EA\u52D5\u751F\u6210`,
732
- ``
733
- );
742
+ typeDefinitions.push(`// \u6B64\u6A94\u6848\u7528\u65BC\u5B9A\u7FA9 API \u76F8\u95DC\u7684\u985E\u578B`, `// \u985E\u578B\u5B9A\u7FA9\u6703\u6839\u64DA OpenAPI Schema \u81EA\u52D5\u751F\u6210`, ``);
734
743
  }
735
744
  return importStatement + typeDefinitions.join("\n\n");
736
745
  }
737
- function generateRequestTypeContent(endpoint, operationDefinitions) {
746
+ function generateRequestTypeContent(endpoint, operationDefinitions, schemaTypeMap = {}) {
738
747
  const properties = [];
739
748
  if (endpoint.queryParams && endpoint.queryParams.length > 0) {
740
749
  endpoint.queryParams.forEach((param) => {
741
750
  const optional = param.required ? "" : "?";
742
- const paramType = getTypeFromParameter(param);
751
+ const paramType = getTypeFromParameter(param, schemaTypeMap);
743
752
  properties.push(` ${param.name}${optional}: ${paramType};`);
744
753
  });
745
754
  }
746
755
  if (endpoint.pathParams && endpoint.pathParams.length > 0) {
747
756
  endpoint.pathParams.forEach((param) => {
748
757
  const optional = param.required ? "" : "?";
749
- const paramType = getTypeFromParameter(param);
758
+ const paramType = getTypeFromParameter(param, schemaTypeMap);
750
759
  properties.push(` ${param.name}${optional}: ${paramType};`);
751
760
  });
752
761
  }
@@ -757,16 +766,22 @@ function generateRequestTypeContent(endpoint, operationDefinitions) {
757
766
  if (operationDef?.operation?.requestBody) {
758
767
  const requestBody = operationDef.operation.requestBody;
759
768
  const content = requestBody.content;
760
- const jsonContent = content["application/json"];
769
+ const jsonContent = content["application/json"] || content["*/*"];
761
770
  const formContent = content["multipart/form-data"] || content["application/x-www-form-urlencoded"];
762
771
  if (jsonContent?.schema) {
763
- const bodyType = getTypeFromSchema(jsonContent.schema);
772
+ const bodyType = getTypeFromSchema(jsonContent.schema, schemaTypeMap, 1);
764
773
  properties.push(` body: ${bodyType};`);
765
774
  } else if (formContent?.schema) {
766
- const bodyType = getTypeFromSchema(formContent.schema);
775
+ const bodyType = getTypeFromSchema(formContent.schema, schemaTypeMap, 1);
767
776
  properties.push(` body: ${bodyType};`);
768
777
  } else {
769
- properties.push(` body?: any; // Request body from OpenAPI`);
778
+ const firstContent = Object.values(content)[0];
779
+ if (firstContent?.schema) {
780
+ const bodyType = getTypeFromSchema(firstContent.schema, schemaTypeMap, 1);
781
+ properties.push(` body: ${bodyType};`);
782
+ } else {
783
+ properties.push(` body?: any; // Request body from OpenAPI`);
784
+ }
770
785
  }
771
786
  }
772
787
  if (properties.length === 0) {
@@ -774,7 +789,7 @@ function generateRequestTypeContent(endpoint, operationDefinitions) {
774
789
  }
775
790
  return properties.join("\n");
776
791
  }
777
- function generateResponseTypeContent(endpoint, operationDefinitions) {
792
+ function generateResponseTypeContent(endpoint, operationDefinitions, schemaTypeMap = {}) {
778
793
  const properties = [];
779
794
  const operationDef = operationDefinitions?.find((op) => {
780
795
  return op.operation?.operationId === endpoint.operationName || op.operation?.operationId === endpoint.operationName.toLowerCase() || // 也嘗試匹配 verb + path 組合
@@ -783,9 +798,9 @@ function generateResponseTypeContent(endpoint, operationDefinitions) {
783
798
  if (operationDef?.operation?.responses) {
784
799
  const successResponse = operationDef.operation.responses["200"] || operationDef.operation.responses["201"];
785
800
  if (successResponse?.content) {
786
- const jsonContent = successResponse.content["application/json"];
801
+ const jsonContent = successResponse.content["application/json"] || successResponse.content["*/*"] || Object.values(successResponse.content)[0];
787
802
  if (jsonContent?.schema) {
788
- const responseProps = parseSchemaProperties(jsonContent.schema);
803
+ const responseProps = parseSchemaProperties(jsonContent.schema, schemaTypeMap);
789
804
  properties.push(...responseProps);
790
805
  } else {
791
806
  properties.push(` // Success response from OpenAPI`);
@@ -798,83 +813,114 @@ function generateResponseTypeContent(endpoint, operationDefinitions) {
798
813
  }
799
814
  return properties.join("\n");
800
815
  }
801
- function parseSchemaProperties(schema) {
816
+ function parseSchemaProperties(schema, schemaTypeMap = {}) {
802
817
  const properties = [];
803
818
  if (schema.type === "object" && schema.properties) {
804
819
  const required = schema.required || [];
805
820
  Object.entries(schema.properties).forEach(([propName, propSchema]) => {
806
821
  const isRequired = required.includes(propName);
807
822
  const optional = isRequired ? "" : "?";
808
- const propType = getTypeFromSchema(propSchema);
809
- const description = propSchema.description ? ` // ${propSchema.description}` : "";
810
- properties.push(` ${propName}${optional}: ${propType};${description}`);
823
+ const propType = getTypeFromSchema(propSchema, schemaTypeMap, 1);
824
+ const needsQuotes = /[^a-zA-Z0-9_$]/.test(propName);
825
+ const quotedPropName = needsQuotes ? `"${propName}"` : propName;
826
+ if (propSchema.description) {
827
+ properties.push(` /** ${propSchema.description} */`);
828
+ }
829
+ properties.push(` ${quotedPropName}${optional}: ${propType};`);
811
830
  });
812
831
  }
813
832
  return properties;
814
833
  }
815
- function getTypeFromSchema(schema) {
834
+ function getTypeFromSchema(schema, schemaTypeMap = {}, indentLevel = 0) {
816
835
  if (!schema) return "any";
817
836
  if (schema.$ref) {
818
837
  const refPath = schema.$ref;
819
838
  if (refPath.startsWith("#/components/schemas/")) {
820
- const typeName = refPath.replace("#/components/schemas/", "");
821
- return `Schema.${typeName}`;
839
+ const originalTypeName = refPath.replace("#/components/schemas/", "");
840
+ const actualTypeName = schemaTypeMap[originalTypeName] || originalTypeName;
841
+ const pascalCaseTypeName = toPascalCase2(actualTypeName);
842
+ const baseType2 = `Schema.${pascalCaseTypeName}`;
843
+ return schema.nullable ? `${baseType2} | null` : baseType2;
822
844
  }
823
845
  }
846
+ let baseType;
824
847
  switch (schema.type) {
825
848
  case "string":
826
849
  if (schema.enum) {
827
- return schema.enum.map((val) => `"${val}"`).join(" | ");
850
+ baseType = schema.enum.map((val) => `"${val}"`).join(" | ");
851
+ } else if (schema.format === "binary") {
852
+ baseType = "Blob";
853
+ } else {
854
+ baseType = "string";
828
855
  }
829
- return "string";
856
+ break;
830
857
  case "number":
831
858
  case "integer":
832
- return "number";
859
+ baseType = "number";
860
+ break;
833
861
  case "boolean":
834
- return "boolean";
862
+ baseType = "boolean";
863
+ break;
835
864
  case "array":
836
- const itemType = schema.items ? getTypeFromSchema(schema.items) : "any";
837
- return `${itemType}[]`;
865
+ const itemType = schema.items ? getTypeFromSchema(schema.items, schemaTypeMap, indentLevel) : "any";
866
+ const needsParentheses = itemType.includes("|");
867
+ baseType = needsParentheses ? `(${itemType})[]` : `${itemType}[]`;
868
+ break;
838
869
  case "object":
839
870
  if (schema.properties) {
840
- const props = Object.entries(schema.properties).map(([key, propSchema]) => {
841
- const required = schema.required || [];
842
- const optional = required.includes(key) ? "" : "?";
843
- const type = getTypeFromSchema(propSchema);
844
- return `${key}${optional}: ${type}`;
845
- }).join("; ");
846
- return `{ ${props} }`;
871
+ const entries = Object.entries(schema.properties);
872
+ if (entries.length === 0) {
873
+ if (schema.additionalProperties) {
874
+ const valueType = schema.additionalProperties === true ? "any" : getTypeFromSchema(schema.additionalProperties, schemaTypeMap, indentLevel);
875
+ baseType = `Record<string, ${valueType}>`;
876
+ } else {
877
+ baseType = "{}";
878
+ }
879
+ } else {
880
+ const nextIndent = " ".repeat(indentLevel + 1);
881
+ const currentIndent = " ".repeat(indentLevel);
882
+ const props = [];
883
+ entries.forEach(([key, propSchema]) => {
884
+ const required = schema.required || [];
885
+ const optional = required.includes(key) ? "" : "?";
886
+ const type = getTypeFromSchema(propSchema, schemaTypeMap, indentLevel + 1);
887
+ const needsQuotes = /[^a-zA-Z0-9_$]/.test(key);
888
+ const quotedKey = needsQuotes ? `"${key}"` : key;
889
+ if (propSchema.description) {
890
+ props.push(`${nextIndent}/** ${propSchema.description} */`);
891
+ }
892
+ props.push(`${nextIndent}${quotedKey}${optional}: ${type};`);
893
+ });
894
+ baseType = `{
895
+ ${props.join("\n")}
896
+ ${currentIndent}}`;
897
+ }
898
+ } else if (schema.additionalProperties) {
899
+ const valueType = schema.additionalProperties === true ? "any" : getTypeFromSchema(schema.additionalProperties, schemaTypeMap, indentLevel);
900
+ baseType = `Record<string, ${valueType}>`;
901
+ } else {
902
+ baseType = "any";
847
903
  }
848
- return "any";
904
+ break;
849
905
  default:
850
- return "any";
906
+ baseType = "any";
907
+ break;
851
908
  }
909
+ return schema.nullable ? `${baseType} | null` : baseType;
852
910
  }
853
- function getTypeFromParameter(param) {
911
+ function getTypeFromParameter(param, schemaTypeMap = {}) {
854
912
  if (!param.schema) return "any";
855
- return getTypeFromSchema(param.schema);
913
+ return getTypeFromSchema(param.schema, schemaTypeMap);
856
914
  }
857
915
 
858
- // src/generators/index-generator.ts
916
+ // src/generators/rtk-query-generator.ts
859
917
  init_cjs_shims();
860
- function generateIndexFile(groupKey, options) {
861
- const { groupKey: optionsGroupKey } = options;
862
- return `export * from './types';
863
- export * from './api.service';
864
- export * from './query.service';
865
- `;
866
- }
867
-
868
- // src/services/query-code-generator.ts
869
- init_cjs_shims();
870
-
871
- // src/generators/query-service-generator.ts
872
- init_cjs_shims();
873
- function generateQueryServiceFile(endpointInfos, options) {
918
+ function generateRtkQueryFile(endpointInfos, options) {
874
919
  const { groupKey, refetchOnMountOrArgChange = 60 } = options;
875
920
  const queryServiceName = groupKey ? `${groupKey.charAt(0).toUpperCase() + groupKey.slice(1)}QueryService` : "QueryService";
876
921
  const apiServiceName = groupKey ? `${groupKey.charAt(0).toUpperCase() + groupKey.slice(1)}ApiService` : "ApiService";
877
- const queryMethods = endpointInfos.filter((info) => info.isQuery).map((info) => `
922
+ const queryMethods = endpointInfos.filter((info) => info.isQuery).map(
923
+ (info) => `
878
924
  /**
879
925
  * ${info.summary}
880
926
  */
@@ -887,8 +933,10 @@ function generateQueryServiceFile(endpointInfos, options) {
887
933
  fetchOptions: args?.fetchOptions,
888
934
  config: args?.config,
889
935
  })${info.argTypeName !== "VoidApiArg" ? "(args)" : "()"};
890
- }`).join("");
891
- const mutationMethods = endpointInfos.filter((info) => !info.isQuery).map((info) => `
936
+ }`
937
+ ).join("");
938
+ const mutationMethods = endpointInfos.filter((info) => !info.isQuery).map(
939
+ (info) => `
892
940
  /**
893
941
  * ${info.summary}
894
942
  */
@@ -902,8 +950,10 @@ function generateQueryServiceFile(endpointInfos, options) {
902
950
  return this.apiService.${info.operationName}(${info.argTypeName !== "VoidApiArg" ? "args" : ""});
903
951
  },
904
952
  });
905
- }`).join("");
906
- const lazyQueryMethods = endpointInfos.filter((info) => info.isQuery).map((info) => `
953
+ }`
954
+ ).join("");
955
+ const lazyQueryMethods = endpointInfos.filter((info) => info.isQuery).map(
956
+ (info) => `
907
957
  /**
908
958
  * ${info.summary}
909
959
  */
@@ -922,7 +972,8 @@ function generateQueryServiceFile(endpointInfos, options) {
922
972
  return this.apiService.${info.operationName}(${info.argTypeName !== "VoidApiArg" ? "args" : ""});
923
973
  }
924
974
  };
925
- }`).join("");
975
+ }`
976
+ ).join("");
926
977
  return `/* eslint-disable */
927
978
  // [Warning] Generated automatically - do not edit manually
928
979
 
@@ -946,32 +997,9 @@ ${queryMethods}${mutationMethods}${lazyQueryMethods}
946
997
  `;
947
998
  }
948
999
 
949
- // src/services/query-code-generator.ts
950
- var QueryCodeGenerator = class {
951
- constructor(options) {
952
- this.options = options;
953
- }
954
- /**
955
- * 生成 Query Service 檔案內容
956
- */
957
- generateQueryService(endpointInfos) {
958
- const generatorOptions = {
959
- ...this.options,
960
- apiConfiguration: this.options.apiConfiguration || {
961
- file: "@/store/webapi",
962
- importName: "WebApiConfiguration"
963
- }
964
- };
965
- return generateQueryServiceFile(endpointInfos, generatorOptions);
966
- }
967
- };
968
-
969
- // src/services/api-service-generator.ts
1000
+ // src/generators/rtk-enhance-endpoints-generator.ts
970
1001
  init_cjs_shims();
971
-
972
- // src/generators/api-service-generator.ts
973
- init_cjs_shims();
974
- function generateApiServiceFile(endpointInfos, options) {
1002
+ function generateRtkEnhanceEndpointsFile(endpointInfos, options) {
975
1003
  const { apiConfiguration, httpClient, groupKey } = options;
976
1004
  const apiServiceClassName = groupKey ? `${groupKey.charAt(0).toUpperCase() + groupKey.slice(1)}ApiService` : "ApiService";
977
1005
  return `/* eslint-disable */
@@ -995,6 +1023,7 @@ export class ${apiServiceClassName} {
995
1023
  ${endpointInfos.map((info) => {
996
1024
  const isGet = info.verb.toUpperCase() === "GET";
997
1025
  const hasArgs = info.argTypeName !== "VoidApiArg";
1026
+ const hasRequestBody = info.hasRequestBody;
998
1027
  const generateUrlTemplate = (path5) => {
999
1028
  const hasPathParams = path5.includes("{");
1000
1029
  if (hasPathParams && hasArgs) {
@@ -1005,18 +1034,16 @@ ${endpointInfos.map((info) => {
1005
1034
  }
1006
1035
  };
1007
1036
  const hasQueryParams = info.queryParams.length > 0;
1008
- const hasBody = !isGet && hasArgs && !info.isVoidArg;
1037
+ const hasBody = !isGet && hasArgs;
1009
1038
  const generateRequestOptions = () => {
1010
- const options2 = [
1011
- "...args.fetchOptions"
1012
- ];
1013
- if (hasBody || !isGet) {
1039
+ const options2 = ["...args.fetchOptions"];
1040
+ if (hasRequestBody || !isGet) {
1014
1041
  options2.push(`headers: { 'Content-Type': 'application/json', ...args.fetchOptions?.headers }`);
1015
1042
  }
1016
1043
  if (hasQueryParams && hasArgs) {
1017
1044
  options2.push(generateQueryParams(info.queryParams));
1018
1045
  }
1019
- if (hasBody) {
1046
+ if (hasRequestBody) {
1020
1047
  options2.push("body: args.variables.body");
1021
1048
  }
1022
1049
  return options2.length > 0 ? `{ ${options2.join(", ")} }` : "{}";
@@ -1042,62 +1069,47 @@ function generateQueryParams(queryParams) {
1042
1069
  return `params: withoutUndefined({ ${paramEntries} })`;
1043
1070
  }
1044
1071
 
1045
- // src/services/api-service-generator.ts
1046
- var ApiServiceGenerator = class {
1047
- constructor(options) {
1048
- this.options = options;
1049
- }
1050
- /**
1051
- * 生成 API Service 檔案內容
1052
- */
1053
- generateApiService(endpointInfos) {
1054
- const generatorOptions = {
1055
- ...this.options,
1056
- apiConfiguration: this.options.apiConfiguration || {
1057
- file: "@/store/webapi",
1058
- importName: "WebApiConfiguration"
1059
- }
1060
- };
1061
- return generateApiServiceFile(endpointInfos, generatorOptions);
1062
- }
1063
- };
1064
-
1065
1072
  // src/services/api-code-generator.ts
1066
1073
  var ApiCodeGenerator = class {
1067
1074
  constructor(parserService, options) {
1068
1075
  this.parserService = parserService;
1069
1076
  this.options = options;
1070
1077
  this.infoExtractor = new EndpointInfoExtractor(options);
1071
- this.queryGenerator = new QueryCodeGenerator(options);
1072
- this.apiServiceGenerator = new ApiServiceGenerator(options);
1073
1078
  }
1074
1079
  infoExtractor;
1075
- queryGenerator;
1076
- apiServiceGenerator;
1077
1080
  /**
1078
- * 生成完整的 API 程式碼
1081
+ * 生成完整的 RTK Query 程式碼
1079
1082
  */
1080
1083
  async generate() {
1081
1084
  const operationDefinitions = this.parserService.getOperationDefinitions(this.options.filterEndpoints);
1082
1085
  const endpointInfos = this.infoExtractor.extractEndpointInfos(operationDefinitions);
1086
+ return this.generateRtkQueryCode(endpointInfos);
1087
+ }
1088
+ /**
1089
+ * 生成 RTK Query 代碼
1090
+ */
1091
+ generateRtkQueryCode(endpointInfos) {
1083
1092
  const typesContent = this.generateTypes(endpointInfos);
1084
- const apiServiceContent = this.generateApiService(endpointInfos);
1085
- const queryServiceContent = this.generateQueryService(endpointInfos);
1093
+ const rtkQueryContent = generateRtkQueryFile(endpointInfos, this.options);
1094
+ const enhanceEndpointsContent = generateRtkEnhanceEndpointsFile(endpointInfos, this.options);
1086
1095
  const indexContent = this.generateIndex();
1087
- const allEndpointCacheKeys = endpointInfos.filter((info) => info.isQuery).map((info) => ({
1088
- operationName: info.operationName,
1089
- queryKeyName: info.queryKeyName,
1090
- groupKey: this.options.groupKey || "_common"
1091
- }));
1092
1096
  const operationNames = endpointInfos.map((info) => info.operationName);
1097
+ const allTags = /* @__PURE__ */ new Set();
1098
+ endpointInfos.forEach((info) => {
1099
+ if (info.tags && Array.isArray(info.tags)) {
1100
+ info.tags.forEach((tag) => allTags.add(tag));
1101
+ }
1102
+ });
1093
1103
  return {
1094
1104
  operationNames,
1105
+ tags: Array.from(allTags),
1095
1106
  files: {
1096
1107
  types: typesContent,
1097
- apiService: apiServiceContent,
1098
- queryService: queryServiceContent,
1108
+ queryService: rtkQueryContent,
1109
+ // RTK Query 檔案
1099
1110
  index: indexContent,
1100
- allEndpointCacheKeys
1111
+ enhanceEndpoints: enhanceEndpointsContent
1112
+ // 新增的 enhance endpoints 檔案
1101
1113
  }
1102
1114
  };
1103
1115
  }
@@ -1113,43 +1125,35 @@ var ApiCodeGenerator = class {
1113
1125
  }
1114
1126
  };
1115
1127
  const apiGen = this.parserService.getApiGenerator();
1116
- const schemaInterfaces = apiGen.aliases.reduce((curr, alias) => {
1117
- if (import_typescript2.default.isInterfaceDeclaration(alias) || import_typescript2.default.isTypeAliasDeclaration(alias)) {
1118
- const name = alias.name.text;
1119
- return {
1120
- ...curr,
1121
- [name]: alias
1122
- };
1123
- }
1124
- return curr;
1125
- }, {});
1128
+ const schemaInterfaces = apiGen.aliases.reduce(
1129
+ (curr, alias) => {
1130
+ if (import_typescript2.default.isInterfaceDeclaration(alias) || import_typescript2.default.isTypeAliasDeclaration(alias)) {
1131
+ const name = alias.name.text;
1132
+ return {
1133
+ ...curr,
1134
+ [name]: alias
1135
+ };
1136
+ }
1137
+ return curr;
1138
+ },
1139
+ {}
1140
+ );
1126
1141
  const operationDefinitions = this.parserService.getOperationDefinitions(this.options.filterEndpoints);
1127
1142
  return generateTypesFile(endpointInfos, generatorOptions, schemaInterfaces, operationDefinitions);
1128
1143
  }
1129
- /**
1130
- * 生成 API Service 檔案內容
1131
- */
1132
- generateApiService(endpointInfos) {
1133
- return this.apiServiceGenerator.generateApiService(endpointInfos);
1134
- }
1135
- /**
1136
- * 生成 Query Service 檔案內容
1137
- */
1138
- generateQueryService(endpointInfos) {
1139
- return this.queryGenerator.generateQueryService(endpointInfos);
1140
- }
1141
1144
  /**
1142
1145
  * 生成 Index 檔案內容
1143
1146
  */
1144
1147
  generateIndex() {
1145
- const generatorOptions = {
1146
- ...this.options,
1147
- apiConfiguration: this.options.apiConfiguration || {
1148
- file: "@/store/webapi",
1149
- importName: "WebApiConfiguration"
1150
- }
1151
- };
1152
- return generateIndexFile(this.options.groupKey || "", generatorOptions);
1148
+ const groupKey = this.options.groupKey || "";
1149
+ const exportName = groupKey ? `${groupKey.charAt(0).toLowerCase() + groupKey.slice(1)}Api` : "api";
1150
+ return `/* eslint-disable */
1151
+ // [Warning] Generated automatically - do not edit manually
1152
+
1153
+ export { default as ${exportName} } from "./enhanceEndpoints";
1154
+ export * from "./query.generated";
1155
+ export * from "./types";
1156
+ `;
1153
1157
  }
1154
1158
  // /**
1155
1159
  // * 獲取已解析的 parser service(供外部使用)
@@ -1166,6 +1170,46 @@ var ApiCodeGenerator = class {
1166
1170
  // }
1167
1171
  };
1168
1172
 
1173
+ // src/generators/utils-generator.ts
1174
+ init_cjs_shims();
1175
+ function generateUtilsFile() {
1176
+ return `/* eslint-disable */
1177
+ // [Warning] Generated automatically - do not edit manually
1178
+
1179
+ /**
1180
+ * Clear undefined in object
1181
+ */
1182
+ export function withoutUndefined(obj?: Record<string, any>) {
1183
+ if(typeof obj === 'undefined') return;
1184
+ return Object.fromEntries(
1185
+ Object.entries(obj).filter(([_, v]) => v !== undefined && v !== null)
1186
+ );
1187
+ }
1188
+
1189
+ `;
1190
+ }
1191
+
1192
+ // src/generators/tag-types-generator.ts
1193
+ init_cjs_shims();
1194
+ function generateTagTypesFile(tags) {
1195
+ if (tags.length === 0) {
1196
+ return `/* eslint-disable */
1197
+ // [Warning] Generated automatically - do not edit manually
1198
+
1199
+ export enum ECacheTagTypes {
1200
+ }
1201
+ `;
1202
+ }
1203
+ const enumEntries = tags.sort().map((tag) => ` ${tag} = '${tag}',`).join("\n");
1204
+ return `/* eslint-disable */
1205
+ // [Warning] Generated automatically - do not edit manually
1206
+
1207
+ export enum ECacheTagTypes {
1208
+ ${enumEntries}
1209
+ }
1210
+ `;
1211
+ }
1212
+
1169
1213
  // src/services/unified-code-generator.ts
1170
1214
  var UnifiedCodeGenerator = class {
1171
1215
  _options;
@@ -1176,17 +1220,18 @@ var UnifiedCodeGenerator = class {
1176
1220
  openApiDoc = null;
1177
1221
  parserService = null;
1178
1222
  schemaInterfaces = {};
1179
- allEndpointCacheKeys = [];
1180
1223
  actualSchemaFile = "";
1181
1224
  // 生成內容存儲
1182
1225
  generatedContent = {
1183
1226
  groups: [],
1184
- cacheKeys: null,
1185
1227
  commonTypes: "",
1186
1228
  componentSchema: "",
1187
1229
  doNotModify: "",
1188
- utils: ""
1230
+ utils: "",
1231
+ tagTypes: ""
1189
1232
  };
1233
+ // 收集所有 tags
1234
+ allTags = /* @__PURE__ */ new Set();
1190
1235
  constructor(options) {
1191
1236
  this._options = options;
1192
1237
  }
@@ -1196,11 +1241,11 @@ var UnifiedCodeGenerator = class {
1196
1241
  async generateAll() {
1197
1242
  await this.prepare();
1198
1243
  await this.generateApi();
1199
- this.generateCacheKeysContent();
1200
1244
  this.generateCommonTypesContent();
1201
1245
  this.generateSchemaContent();
1202
1246
  this.generateUtilsContent();
1203
1247
  this.generateDoNotModifyContent();
1248
+ this.generateTagTypesContent();
1204
1249
  return await this.release();
1205
1250
  }
1206
1251
  /**
@@ -1253,18 +1298,15 @@ var UnifiedCodeGenerator = class {
1253
1298
  outputPath: groupInfo.outputPath,
1254
1299
  content: groupContent
1255
1300
  });
1301
+ if (groupContent.tags && Array.isArray(groupContent.tags)) {
1302
+ groupContent.tags.forEach((tag) => this.allTags.add(tag));
1303
+ }
1256
1304
  }
1257
1305
  } catch (error) {
1258
1306
  throw new Error(`\u7FA4\u7D44 ${groupInfo.groupKey} \u751F\u6210\u5931\u6557: ${error}`);
1259
1307
  }
1260
1308
  }
1261
1309
  }
1262
- /**
1263
- * 生成 cache keys
1264
- */
1265
- async generateCacheKeysContent() {
1266
- this.generatedContent.cacheKeys = generateCacheKeysFile(this.allEndpointCacheKeys);
1267
- }
1268
1310
  /**
1269
1311
  * 生成 common types
1270
1312
  */
@@ -1289,6 +1331,13 @@ var UnifiedCodeGenerator = class {
1289
1331
  async generateUtilsContent() {
1290
1332
  this.generatedContent.utils = generateUtilsFile();
1291
1333
  }
1334
+ /**
1335
+ * 生成 Tag Types
1336
+ */
1337
+ async generateTagTypesContent() {
1338
+ const tagsArray = Array.from(this.allTags);
1339
+ this.generatedContent.tagTypes = generateTagTypesFile(tagsArray);
1340
+ }
1292
1341
  /**
1293
1342
  * 發佈階段:統一寫入所有檔案
1294
1343
  */
@@ -1305,8 +1354,8 @@ var UnifiedCodeGenerator = class {
1305
1354
  groupOutputDir,
1306
1355
  {
1307
1356
  types: group.content.files.types,
1308
- apiService: group.content.files.apiService,
1309
1357
  queryService: group.content.files.queryService,
1358
+ enhanceEndpoints: group.content.files.enhanceEndpoints,
1310
1359
  index: group.content.files.index
1311
1360
  }
1312
1361
  );
@@ -1318,11 +1367,10 @@ var UnifiedCodeGenerator = class {
1318
1367
  }
1319
1368
  }
1320
1369
  const outputDir = this.generatedContent.groups[0] ? import_node_path4.default.dirname(import_node_path4.default.dirname(this.generatedContent.groups[0].outputPath)) : "./generated";
1321
- if (this.generatedContent.cacheKeys || this.generatedContent.commonTypes || this.generatedContent.doNotModify || this.generatedContent.utils) {
1370
+ if (this.generatedContent.commonTypes || this.generatedContent.doNotModify || this.generatedContent.utils) {
1322
1371
  const sharedResults = await this.fileWriterService.writeSharedFiles(
1323
1372
  outputDir,
1324
1373
  {
1325
- cacheKeys: this.generatedContent.cacheKeys || void 0,
1326
1374
  commonTypes: this.generatedContent.commonTypes || void 0,
1327
1375
  doNotModify: this.generatedContent.doNotModify || void 0,
1328
1376
  utils: this.generatedContent.utils || void 0
@@ -1330,6 +1378,13 @@ var UnifiedCodeGenerator = class {
1330
1378
  );
1331
1379
  results.push(...sharedResults);
1332
1380
  }
1381
+ if (this.generatedContent.tagTypes) {
1382
+ const tagTypesResult = await this.fileWriterService.writeFile(
1383
+ import_node_path4.default.join(outputDir, "tagTypes.ts"),
1384
+ this.generatedContent.tagTypes
1385
+ );
1386
+ results.push(tagTypesResult);
1387
+ }
1333
1388
  if (this.generatedContent.componentSchema) {
1334
1389
  const schemaResults = await this.fileWriterService.writeSchemaFile(
1335
1390
  outputDir,
@@ -1337,6 +1392,12 @@ var UnifiedCodeGenerator = class {
1337
1392
  );
1338
1393
  results.push(...schemaResults);
1339
1394
  }
1395
+ const mainIndexContent = this.generateMainIndex(generatedGroups);
1396
+ const mainIndexResult = await this.fileWriterService.writeFile(
1397
+ import_node_path4.default.join(outputDir, "index.ts"),
1398
+ mainIndexContent
1399
+ );
1400
+ results.push(mainIndexResult);
1340
1401
  } catch (error) {
1341
1402
  errors.push(error);
1342
1403
  }
@@ -1366,12 +1427,19 @@ var UnifiedCodeGenerator = class {
1366
1427
  }
1367
1428
  const apiGenerator = new ApiCodeGenerator(this.parserService, groupOptions);
1368
1429
  const result = await apiGenerator.generate();
1369
- if (result.files && "allEndpointCacheKeys" in result.files) {
1370
- const cacheKeys = result.files.allEndpointCacheKeys;
1371
- this.allEndpointCacheKeys.push(...cacheKeys);
1372
- }
1373
1430
  return result;
1374
1431
  }
1432
+ /**
1433
+ * 生成主 index.ts 檔案
1434
+ */
1435
+ generateMainIndex(generatedGroups) {
1436
+ const exports2 = generatedGroups.map((groupKey) => `export * from "./${groupKey}";`).join("\n");
1437
+ return `/* eslint-disable */
1438
+ // [Warning] Generated automatically - do not edit manually
1439
+
1440
+ ${exports2}
1441
+ `;
1442
+ }
1375
1443
  };
1376
1444
 
1377
1445
  // src/index.ts