@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.mjs CHANGED
@@ -311,6 +311,13 @@ var FileWriterService = class {
311
311
  async writeFile(filePath, content) {
312
312
  try {
313
313
  const resolvedPath = path3.resolve(process.cwd(), filePath);
314
+ const fileName = path3.basename(resolvedPath);
315
+ if (fileName === "enhanceEndpoints.ts" && fs3.existsSync(resolvedPath)) {
316
+ return {
317
+ path: resolvedPath,
318
+ success: true
319
+ };
320
+ }
314
321
  await ensureDirectoryExists(resolvedPath);
315
322
  fs3.writeFileSync(resolvedPath, content);
316
323
  return {
@@ -338,7 +345,7 @@ var FileWriterService = class {
338
345
  return results;
339
346
  }
340
347
  /**
341
- * 為群組寫入標準檔案結構
348
+ * 為群組寫入 RTK Query 檔案結構
342
349
  * @param groupOutputDir - 群組輸出目錄
343
350
  * @param files - 檔案內容
344
351
  */
@@ -347,11 +354,11 @@ var FileWriterService = class {
347
354
  if (files.types) {
348
355
  filesToWrite[path3.join(groupOutputDir, "types.ts")] = files.types;
349
356
  }
350
- if (files.apiService) {
351
- filesToWrite[path3.join(groupOutputDir, "api.service.ts")] = files.apiService;
352
- }
353
357
  if (files.queryService) {
354
- filesToWrite[path3.join(groupOutputDir, "query.service.ts")] = files.queryService;
358
+ filesToWrite[path3.join(groupOutputDir, "query.generated.ts")] = files.queryService;
359
+ }
360
+ if (files.enhanceEndpoints) {
361
+ filesToWrite[path3.join(groupOutputDir, "enhanceEndpoints.ts")] = files.enhanceEndpoints;
355
362
  }
356
363
  if (files.index) {
357
364
  filesToWrite[path3.join(groupOutputDir, "index.ts")] = files.index;
@@ -368,9 +375,6 @@ var FileWriterService = class {
368
375
  if (sharedFiles.commonTypes) {
369
376
  filesToWrite[path3.join(outputDir, "common-types.ts")] = sharedFiles.commonTypes;
370
377
  }
371
- if (sharedFiles.cacheKeys) {
372
- filesToWrite[path3.join(outputDir, "cache-keys.ts")] = sharedFiles.cacheKeys;
373
- }
374
378
  if (sharedFiles.doNotModify) {
375
379
  filesToWrite[path3.join(outputDir, "DO_NOT_MODIFY.md")] = sharedFiles.doNotModify;
376
380
  }
@@ -399,7 +403,6 @@ var OpenApiParserService = class {
399
403
  this.v3Doc = v3Doc;
400
404
  this.apiGen = new ApiGenerator(v3Doc, {
401
405
  unionUndefined: options.unionUndefined,
402
- useEnumType: options.useEnumType,
403
406
  mergeReadWriteOnly: options.mergeReadWriteOnly
404
407
  });
405
408
  }
@@ -409,6 +412,12 @@ var OpenApiParserService = class {
409
412
  */
410
413
  initialize() {
411
414
  if (this.apiGen.spec.components?.schemas) {
415
+ Object.keys(this.apiGen.spec.components.schemas).forEach((schemaName) => {
416
+ const schema = this.apiGen.spec.components.schemas[schemaName];
417
+ if (schema && typeof schema === "object" && "title" in schema) {
418
+ delete schema.title;
419
+ }
420
+ });
412
421
  this.apiGen.preprocessComponents(this.apiGen.spec.components.schemas);
413
422
  Object.keys(this.apiGen.spec.components.schemas).forEach((schemaName) => {
414
423
  try {
@@ -447,64 +456,6 @@ var OpenApiParserService = class {
447
456
  }
448
457
  };
449
458
 
450
- // src/generators/cache-keys-generator.ts
451
- init_esm_shims();
452
-
453
- // src/generators/utils-generator.ts
454
- init_esm_shims();
455
- function generateUtilsFile() {
456
- return `/* eslint-disable */
457
- // [Warning] Generated automatically - do not edit manually
458
-
459
- /**
460
- * Clear undefined in object
461
- */
462
- export function withoutUndefined(obj?: Record<string, any>) {
463
- if(typeof obj === 'undefined') return;
464
- return Object.fromEntries(
465
- Object.entries(obj).filter(([_, v]) => v !== undefined && v !== null)
466
- );
467
- }
468
-
469
- `;
470
- }
471
- function toCamelCase(str) {
472
- return str.toLowerCase().replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
473
- }
474
-
475
- // src/generators/cache-keys-generator.ts
476
- function generateCacheKeysFile(allEndpointInfos) {
477
- const groupedKeys = allEndpointInfos.reduce((acc, info) => {
478
- if (!acc[info.groupKey]) {
479
- acc[info.groupKey] = [];
480
- }
481
- acc[info.groupKey].push({
482
- operationName: info.operationName,
483
- queryKeyName: info.queryKeyName
484
- });
485
- return acc;
486
- }, {});
487
- const enumEntries = Object.entries(groupedKeys).map(([groupKey, keys]) => {
488
- const groupComment = ` // ${groupKey.charAt(0).toUpperCase() + groupKey.slice(1)} related cache keys`;
489
- const keyEntries = keys.map(
490
- (key) => ` ${key.queryKeyName} = '${toCamelCase(key.queryKeyName)}',`
491
- ).join("\n");
492
- return `${groupComment}
493
- ${keyEntries}`;
494
- }).join("\n\n");
495
- return `// [Warning] Generated automatically - do not edit manually
496
-
497
- /**
498
- * Cache keys enum for all API queries
499
- */
500
- export enum ECacheKeys {
501
- ${enumEntries}
502
- }
503
-
504
- export default ECacheKeys;
505
- `;
506
- }
507
-
508
459
  // src/generators/common-types-generator.ts
509
460
  init_esm_shims();
510
461
  function generateCommonTypesFile() {
@@ -525,11 +476,15 @@ export interface QueryConfig {
525
476
  keepUnusedDataFor?: number,
526
477
  }
527
478
 
479
+ export interface IRequestConfig extends RequestOptions { {
480
+ timeout?: number;
481
+ }
482
+
528
483
  export type IRestFulEndpointsQueryReturn<TVariables> = TVariables extends void ?
529
- void | {fetchOptions?: RequestOptions;config?: QueryConfig;}:
484
+ void | {fetchOptions?: IRequestConfig;}:
530
485
  {
531
486
  variables: TVariables;
532
- fetchOptions?: RequestOptions;
487
+ fetchOptions?: IRequestConfig;
533
488
  config?: QueryConfig;
534
489
  };
535
490
  `;
@@ -538,6 +493,19 @@ export type IRestFulEndpointsQueryReturn<TVariables> = TVariables extends void ?
538
493
  // src/generators/component-schema-generator.ts
539
494
  init_esm_shims();
540
495
  import ts from "typescript";
496
+ function toPascalCase(name) {
497
+ return name.charAt(0).toUpperCase() + name.slice(1);
498
+ }
499
+ function renameIdentifier(node, oldName, newName) {
500
+ return ts.transform(node, [
501
+ (context) => (rootNode) => ts.visitNode(rootNode, function visit(node2) {
502
+ if (ts.isIdentifier(node2) && node2.text === oldName) {
503
+ return ts.factory.createIdentifier(newName);
504
+ }
505
+ return ts.visitEachChild(node2, visit, context);
506
+ })
507
+ ]).transformed[0];
508
+ }
541
509
  function generateComponentSchemaFile(interfaces) {
542
510
  const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
543
511
  const resultFile = ts.createSourceFile(
@@ -547,10 +515,19 @@ function generateComponentSchemaFile(interfaces) {
547
515
  false,
548
516
  ts.ScriptKind.TS
549
517
  );
518
+ const renamedInterfaces = [];
519
+ const typeNameMapping = {};
520
+ Object.entries(interfaces).forEach(([originalName, node]) => {
521
+ const pascalCaseName = toPascalCase(originalName);
522
+ typeNameMapping[originalName] = pascalCaseName;
523
+ const renamedNode = renameIdentifier(node, originalName, pascalCaseName);
524
+ const printed = printer.printNode(ts.EmitHint.Unspecified, renamedNode, resultFile);
525
+ renamedInterfaces.push(printed);
526
+ });
550
527
  return `/* eslint-disable */
551
- // [Warning] Generated automatically - do not edit manually
552
-
553
- ${Object.values(interfaces).map((i) => printer.printNode(ts.EmitHint.Unspecified, i, resultFile)).join("\n")}
528
+ // [Warning] Generated automatically - do not edit manually
529
+
530
+ ${renamedInterfaces.join("\n")}
554
531
  `;
555
532
  }
556
533
 
@@ -606,7 +583,13 @@ var EndpointInfoExtractor = class {
606
583
  */
607
584
  extractSingleEndpointInfo(operationDefinition) {
608
585
  const { verb, path: path5, operation } = operationDefinition;
609
- const { operationNameSuffix = "", argSuffix = "Req", responseSuffix = "Res", queryMatch, endpointOverrides } = this.options;
586
+ const {
587
+ operationNameSuffix = "",
588
+ argSuffix = "Req",
589
+ responseSuffix = "Res",
590
+ queryMatch,
591
+ endpointOverrides
592
+ } = this.options;
610
593
  const operationName = getOperationName({ verb, path: path5 });
611
594
  const finalOperationName = operationNameSuffix ? capitalize(operationName + operationNameSuffix) : operationName;
612
595
  const argTypeName = capitalize(operationName + operationNameSuffix + argSuffix);
@@ -614,7 +597,9 @@ var EndpointInfoExtractor = class {
614
597
  const isQuery2 = isQuery(verb, path5, getOverrides(operationDefinition, endpointOverrides), queryMatch);
615
598
  const queryKeyName = `${operationName.replace(/([A-Z])/g, "_$1").toUpperCase()}`;
616
599
  const summary = operation.summary || `${verb.toUpperCase()} ${path5}`;
617
- const { queryParams, pathParams, isVoidArg } = this.extractParameters(operationDefinition);
600
+ const { queryParams, pathParams, isVoidArg, hasRequestBody } = this.extractParameters(operationDefinition);
601
+ const contentType = this.extractContentType(operation);
602
+ const tags = Array.isArray(operation.tags) ? operation.tags : [];
618
603
  return {
619
604
  operationName: finalOperationName,
620
605
  argTypeName,
@@ -626,7 +611,10 @@ var EndpointInfoExtractor = class {
626
611
  queryParams,
627
612
  pathParams,
628
613
  isVoidArg,
629
- summary
614
+ summary,
615
+ contentType,
616
+ hasRequestBody,
617
+ tags
630
618
  };
631
619
  }
632
620
  /**
@@ -636,15 +624,21 @@ var EndpointInfoExtractor = class {
636
624
  extractParameters(operationDefinition) {
637
625
  const { operation, pathItem } = operationDefinition;
638
626
  const operationParameters = this.resolveArray(operation.parameters);
639
- const pathItemParameters = this.resolveArray(pathItem.parameters).filter((pp) => !operationParameters.some((op) => op.name === pp.name && op.in === pp.in));
640
- const allParameters = supportDeepObjects([...pathItemParameters, ...operationParameters]).filter((param) => param.in !== "header");
627
+ const pathItemParameters = this.resolveArray(pathItem.parameters).filter(
628
+ (pp) => !operationParameters.some((op) => op.name === pp.name && op.in === pp.in)
629
+ );
630
+ const allParameters = supportDeepObjects([...pathItemParameters, ...operationParameters]).filter(
631
+ (param) => param.in !== "header"
632
+ );
641
633
  const queryParams = allParameters.filter((param) => param.in === "query");
642
634
  const pathParams = allParameters.filter((param) => param.in === "path");
635
+ const hasRequestBody = !!operation.requestBody;
643
636
  const isVoidArg = queryParams.length === 0 && pathParams.length === 0 && !operation.requestBody;
644
637
  return {
645
638
  queryParams,
646
639
  pathParams,
647
- isVoidArg
640
+ isVoidArg,
641
+ hasRequestBody
648
642
  };
649
643
  }
650
644
  /**
@@ -654,11 +648,46 @@ var EndpointInfoExtractor = class {
654
648
  if (!parameters) return [];
655
649
  return Array.isArray(parameters) ? parameters : [parameters];
656
650
  }
651
+ /**
652
+ * 提取操作的 content type
653
+ * @param operation - 操作對象
654
+ */
655
+ extractContentType(operation) {
656
+ if (!operation.requestBody) {
657
+ return "application/json";
658
+ }
659
+ const content = operation.requestBody.content;
660
+ if (!content || typeof content !== "object") {
661
+ return "application/json";
662
+ }
663
+ const contentTypes = Object.keys(content);
664
+ if (contentTypes.length === 0) {
665
+ return "application/json";
666
+ }
667
+ return contentTypes[0];
668
+ }
657
669
  };
658
670
 
659
671
  // src/generators/types-generator.ts
660
672
  init_esm_shims();
673
+ var toPascalCase2 = (name) => {
674
+ return name.charAt(0).toUpperCase() + name.slice(1);
675
+ };
661
676
  function generateTypesFile(endpointInfos, _options, schemaInterfaces, operationDefinitions) {
677
+ const schemaTypeMap = {};
678
+ if (schemaInterfaces) {
679
+ Object.keys(schemaInterfaces).forEach((actualTypeName) => {
680
+ schemaTypeMap[actualTypeName] = actualTypeName;
681
+ if (actualTypeName.endsWith("Vo")) {
682
+ const openApiName = actualTypeName.slice(0, -2) + "VO";
683
+ schemaTypeMap[openApiName] = actualTypeName;
684
+ }
685
+ if (actualTypeName.endsWith("Dto")) {
686
+ const openApiName = actualTypeName.slice(0, -3) + "DTO";
687
+ schemaTypeMap[openApiName] = actualTypeName;
688
+ }
689
+ });
690
+ }
662
691
  let importStatement = `/* eslint-disable */
663
692
  // [Warning] Generated automatically - do not edit manually
664
693
 
@@ -675,35 +704,19 @@ function generateTypesFile(endpointInfos, _options, schemaInterfaces, operationD
675
704
  const reqTypeName = endpoint.argTypeName;
676
705
  const resTypeName = endpoint.responseTypeName;
677
706
  if (reqTypeName) {
678
- const requestTypeContent = generateRequestTypeContent(endpoint, operationDefinitions);
679
- if (requestTypeContent.trim() === "" || requestTypeContent.includes("TODO") || requestTypeContent.includes("[key: string]: any")) {
680
- endpointTypes.push(
681
- `export type ${reqTypeName} = void;`,
682
- ``
683
- );
707
+ const requestTypeContent = generateRequestTypeContent(endpoint, operationDefinitions, schemaTypeMap);
708
+ if (requestTypeContent.trim() === "") {
709
+ endpointTypes.push(`export type ${reqTypeName} = void;`, ``);
684
710
  } else {
685
- endpointTypes.push(
686
- `export type ${reqTypeName} = {`,
687
- requestTypeContent,
688
- `};`,
689
- ``
690
- );
711
+ endpointTypes.push(`export type ${reqTypeName} = {`, requestTypeContent, `};`, ``);
691
712
  }
692
713
  }
693
714
  if (resTypeName) {
694
- const responseTypeContent = generateResponseTypeContent(endpoint, operationDefinitions);
695
- if (responseTypeContent.trim() === "" || responseTypeContent.includes("TODO") || responseTypeContent.includes("[key: string]: any")) {
696
- endpointTypes.push(
697
- `export type ${resTypeName} = void;`,
698
- ``
699
- );
715
+ const responseTypeContent = generateResponseTypeContent(endpoint, operationDefinitions, schemaTypeMap);
716
+ if (responseTypeContent.trim() === "") {
717
+ endpointTypes.push(`export type ${resTypeName} = void;`, ``);
700
718
  } else {
701
- endpointTypes.push(
702
- `export type ${resTypeName} = {`,
703
- responseTypeContent,
704
- `};`,
705
- ``
706
- );
719
+ endpointTypes.push(`export type ${resTypeName} = {`, responseTypeContent, `};`, ``);
707
720
  }
708
721
  }
709
722
  });
@@ -711,27 +724,23 @@ function generateTypesFile(endpointInfos, _options, schemaInterfaces, operationD
711
724
  typeDefinitions.push(endpointTypes.join("\n"));
712
725
  }
713
726
  if (typeDefinitions.length === 0) {
714
- typeDefinitions.push(
715
- `// \u6B64\u6A94\u6848\u7528\u65BC\u5B9A\u7FA9 API \u76F8\u95DC\u7684\u985E\u578B`,
716
- `// \u985E\u578B\u5B9A\u7FA9\u6703\u6839\u64DA OpenAPI Schema \u81EA\u52D5\u751F\u6210`,
717
- ``
718
- );
727
+ 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`, ``);
719
728
  }
720
729
  return importStatement + typeDefinitions.join("\n\n");
721
730
  }
722
- function generateRequestTypeContent(endpoint, operationDefinitions) {
731
+ function generateRequestTypeContent(endpoint, operationDefinitions, schemaTypeMap = {}) {
723
732
  const properties = [];
724
733
  if (endpoint.queryParams && endpoint.queryParams.length > 0) {
725
734
  endpoint.queryParams.forEach((param) => {
726
735
  const optional = param.required ? "" : "?";
727
- const paramType = getTypeFromParameter(param);
736
+ const paramType = getTypeFromParameter(param, schemaTypeMap);
728
737
  properties.push(` ${param.name}${optional}: ${paramType};`);
729
738
  });
730
739
  }
731
740
  if (endpoint.pathParams && endpoint.pathParams.length > 0) {
732
741
  endpoint.pathParams.forEach((param) => {
733
742
  const optional = param.required ? "" : "?";
734
- const paramType = getTypeFromParameter(param);
743
+ const paramType = getTypeFromParameter(param, schemaTypeMap);
735
744
  properties.push(` ${param.name}${optional}: ${paramType};`);
736
745
  });
737
746
  }
@@ -742,16 +751,22 @@ function generateRequestTypeContent(endpoint, operationDefinitions) {
742
751
  if (operationDef?.operation?.requestBody) {
743
752
  const requestBody = operationDef.operation.requestBody;
744
753
  const content = requestBody.content;
745
- const jsonContent = content["application/json"];
754
+ const jsonContent = content["application/json"] || content["*/*"];
746
755
  const formContent = content["multipart/form-data"] || content["application/x-www-form-urlencoded"];
747
756
  if (jsonContent?.schema) {
748
- const bodyType = getTypeFromSchema(jsonContent.schema);
757
+ const bodyType = getTypeFromSchema(jsonContent.schema, schemaTypeMap, 1);
749
758
  properties.push(` body: ${bodyType};`);
750
759
  } else if (formContent?.schema) {
751
- const bodyType = getTypeFromSchema(formContent.schema);
760
+ const bodyType = getTypeFromSchema(formContent.schema, schemaTypeMap, 1);
752
761
  properties.push(` body: ${bodyType};`);
753
762
  } else {
754
- properties.push(` body?: any; // Request body from OpenAPI`);
763
+ const firstContent = Object.values(content)[0];
764
+ if (firstContent?.schema) {
765
+ const bodyType = getTypeFromSchema(firstContent.schema, schemaTypeMap, 1);
766
+ properties.push(` body: ${bodyType};`);
767
+ } else {
768
+ properties.push(` body?: any; // Request body from OpenAPI`);
769
+ }
755
770
  }
756
771
  }
757
772
  if (properties.length === 0) {
@@ -759,7 +774,7 @@ function generateRequestTypeContent(endpoint, operationDefinitions) {
759
774
  }
760
775
  return properties.join("\n");
761
776
  }
762
- function generateResponseTypeContent(endpoint, operationDefinitions) {
777
+ function generateResponseTypeContent(endpoint, operationDefinitions, schemaTypeMap = {}) {
763
778
  const properties = [];
764
779
  const operationDef = operationDefinitions?.find((op) => {
765
780
  return op.operation?.operationId === endpoint.operationName || op.operation?.operationId === endpoint.operationName.toLowerCase() || // 也嘗試匹配 verb + path 組合
@@ -768,9 +783,9 @@ function generateResponseTypeContent(endpoint, operationDefinitions) {
768
783
  if (operationDef?.operation?.responses) {
769
784
  const successResponse = operationDef.operation.responses["200"] || operationDef.operation.responses["201"];
770
785
  if (successResponse?.content) {
771
- const jsonContent = successResponse.content["application/json"];
786
+ const jsonContent = successResponse.content["application/json"] || successResponse.content["*/*"] || Object.values(successResponse.content)[0];
772
787
  if (jsonContent?.schema) {
773
- const responseProps = parseSchemaProperties(jsonContent.schema);
788
+ const responseProps = parseSchemaProperties(jsonContent.schema, schemaTypeMap);
774
789
  properties.push(...responseProps);
775
790
  } else {
776
791
  properties.push(` // Success response from OpenAPI`);
@@ -783,83 +798,114 @@ function generateResponseTypeContent(endpoint, operationDefinitions) {
783
798
  }
784
799
  return properties.join("\n");
785
800
  }
786
- function parseSchemaProperties(schema) {
801
+ function parseSchemaProperties(schema, schemaTypeMap = {}) {
787
802
  const properties = [];
788
803
  if (schema.type === "object" && schema.properties) {
789
804
  const required = schema.required || [];
790
805
  Object.entries(schema.properties).forEach(([propName, propSchema]) => {
791
806
  const isRequired = required.includes(propName);
792
807
  const optional = isRequired ? "" : "?";
793
- const propType = getTypeFromSchema(propSchema);
794
- const description = propSchema.description ? ` // ${propSchema.description}` : "";
795
- properties.push(` ${propName}${optional}: ${propType};${description}`);
808
+ const propType = getTypeFromSchema(propSchema, schemaTypeMap, 1);
809
+ const needsQuotes = /[^a-zA-Z0-9_$]/.test(propName);
810
+ const quotedPropName = needsQuotes ? `"${propName}"` : propName;
811
+ if (propSchema.description) {
812
+ properties.push(` /** ${propSchema.description} */`);
813
+ }
814
+ properties.push(` ${quotedPropName}${optional}: ${propType};`);
796
815
  });
797
816
  }
798
817
  return properties;
799
818
  }
800
- function getTypeFromSchema(schema) {
819
+ function getTypeFromSchema(schema, schemaTypeMap = {}, indentLevel = 0) {
801
820
  if (!schema) return "any";
802
821
  if (schema.$ref) {
803
822
  const refPath = schema.$ref;
804
823
  if (refPath.startsWith("#/components/schemas/")) {
805
- const typeName = refPath.replace("#/components/schemas/", "");
806
- return `Schema.${typeName}`;
824
+ const originalTypeName = refPath.replace("#/components/schemas/", "");
825
+ const actualTypeName = schemaTypeMap[originalTypeName] || originalTypeName;
826
+ const pascalCaseTypeName = toPascalCase2(actualTypeName);
827
+ const baseType2 = `Schema.${pascalCaseTypeName}`;
828
+ return schema.nullable ? `${baseType2} | null` : baseType2;
807
829
  }
808
830
  }
831
+ let baseType;
809
832
  switch (schema.type) {
810
833
  case "string":
811
834
  if (schema.enum) {
812
- return schema.enum.map((val) => `"${val}"`).join(" | ");
835
+ baseType = schema.enum.map((val) => `"${val}"`).join(" | ");
836
+ } else if (schema.format === "binary") {
837
+ baseType = "Blob";
838
+ } else {
839
+ baseType = "string";
813
840
  }
814
- return "string";
841
+ break;
815
842
  case "number":
816
843
  case "integer":
817
- return "number";
844
+ baseType = "number";
845
+ break;
818
846
  case "boolean":
819
- return "boolean";
847
+ baseType = "boolean";
848
+ break;
820
849
  case "array":
821
- const itemType = schema.items ? getTypeFromSchema(schema.items) : "any";
822
- return `${itemType}[]`;
850
+ const itemType = schema.items ? getTypeFromSchema(schema.items, schemaTypeMap, indentLevel) : "any";
851
+ const needsParentheses = itemType.includes("|");
852
+ baseType = needsParentheses ? `(${itemType})[]` : `${itemType}[]`;
853
+ break;
823
854
  case "object":
824
855
  if (schema.properties) {
825
- const props = Object.entries(schema.properties).map(([key, propSchema]) => {
826
- const required = schema.required || [];
827
- const optional = required.includes(key) ? "" : "?";
828
- const type = getTypeFromSchema(propSchema);
829
- return `${key}${optional}: ${type}`;
830
- }).join("; ");
831
- return `{ ${props} }`;
856
+ const entries = Object.entries(schema.properties);
857
+ if (entries.length === 0) {
858
+ if (schema.additionalProperties) {
859
+ const valueType = schema.additionalProperties === true ? "any" : getTypeFromSchema(schema.additionalProperties, schemaTypeMap, indentLevel);
860
+ baseType = `Record<string, ${valueType}>`;
861
+ } else {
862
+ baseType = "{}";
863
+ }
864
+ } else {
865
+ const nextIndent = " ".repeat(indentLevel + 1);
866
+ const currentIndent = " ".repeat(indentLevel);
867
+ const props = [];
868
+ entries.forEach(([key, propSchema]) => {
869
+ const required = schema.required || [];
870
+ const optional = required.includes(key) ? "" : "?";
871
+ const type = getTypeFromSchema(propSchema, schemaTypeMap, indentLevel + 1);
872
+ const needsQuotes = /[^a-zA-Z0-9_$]/.test(key);
873
+ const quotedKey = needsQuotes ? `"${key}"` : key;
874
+ if (propSchema.description) {
875
+ props.push(`${nextIndent}/** ${propSchema.description} */`);
876
+ }
877
+ props.push(`${nextIndent}${quotedKey}${optional}: ${type};`);
878
+ });
879
+ baseType = `{
880
+ ${props.join("\n")}
881
+ ${currentIndent}}`;
882
+ }
883
+ } else if (schema.additionalProperties) {
884
+ const valueType = schema.additionalProperties === true ? "any" : getTypeFromSchema(schema.additionalProperties, schemaTypeMap, indentLevel);
885
+ baseType = `Record<string, ${valueType}>`;
886
+ } else {
887
+ baseType = "any";
832
888
  }
833
- return "any";
889
+ break;
834
890
  default:
835
- return "any";
891
+ baseType = "any";
892
+ break;
836
893
  }
894
+ return schema.nullable ? `${baseType} | null` : baseType;
837
895
  }
838
- function getTypeFromParameter(param) {
896
+ function getTypeFromParameter(param, schemaTypeMap = {}) {
839
897
  if (!param.schema) return "any";
840
- return getTypeFromSchema(param.schema);
898
+ return getTypeFromSchema(param.schema, schemaTypeMap);
841
899
  }
842
900
 
843
- // src/generators/index-generator.ts
901
+ // src/generators/rtk-query-generator.ts
844
902
  init_esm_shims();
845
- function generateIndexFile(groupKey, options) {
846
- const { groupKey: optionsGroupKey } = options;
847
- return `export * from './types';
848
- export * from './api.service';
849
- export * from './query.service';
850
- `;
851
- }
852
-
853
- // src/services/query-code-generator.ts
854
- init_esm_shims();
855
-
856
- // src/generators/query-service-generator.ts
857
- init_esm_shims();
858
- function generateQueryServiceFile(endpointInfos, options) {
903
+ function generateRtkQueryFile(endpointInfos, options) {
859
904
  const { groupKey, refetchOnMountOrArgChange = 60 } = options;
860
905
  const queryServiceName = groupKey ? `${groupKey.charAt(0).toUpperCase() + groupKey.slice(1)}QueryService` : "QueryService";
861
906
  const apiServiceName = groupKey ? `${groupKey.charAt(0).toUpperCase() + groupKey.slice(1)}ApiService` : "ApiService";
862
- const queryMethods = endpointInfos.filter((info) => info.isQuery).map((info) => `
907
+ const queryMethods = endpointInfos.filter((info) => info.isQuery).map(
908
+ (info) => `
863
909
  /**
864
910
  * ${info.summary}
865
911
  */
@@ -872,8 +918,10 @@ function generateQueryServiceFile(endpointInfos, options) {
872
918
  fetchOptions: args?.fetchOptions,
873
919
  config: args?.config,
874
920
  })${info.argTypeName !== "VoidApiArg" ? "(args)" : "()"};
875
- }`).join("");
876
- const mutationMethods = endpointInfos.filter((info) => !info.isQuery).map((info) => `
921
+ }`
922
+ ).join("");
923
+ const mutationMethods = endpointInfos.filter((info) => !info.isQuery).map(
924
+ (info) => `
877
925
  /**
878
926
  * ${info.summary}
879
927
  */
@@ -887,8 +935,10 @@ function generateQueryServiceFile(endpointInfos, options) {
887
935
  return this.apiService.${info.operationName}(${info.argTypeName !== "VoidApiArg" ? "args" : ""});
888
936
  },
889
937
  });
890
- }`).join("");
891
- const lazyQueryMethods = endpointInfos.filter((info) => info.isQuery).map((info) => `
938
+ }`
939
+ ).join("");
940
+ const lazyQueryMethods = endpointInfos.filter((info) => info.isQuery).map(
941
+ (info) => `
892
942
  /**
893
943
  * ${info.summary}
894
944
  */
@@ -907,7 +957,8 @@ function generateQueryServiceFile(endpointInfos, options) {
907
957
  return this.apiService.${info.operationName}(${info.argTypeName !== "VoidApiArg" ? "args" : ""});
908
958
  }
909
959
  };
910
- }`).join("");
960
+ }`
961
+ ).join("");
911
962
  return `/* eslint-disable */
912
963
  // [Warning] Generated automatically - do not edit manually
913
964
 
@@ -931,32 +982,9 @@ ${queryMethods}${mutationMethods}${lazyQueryMethods}
931
982
  `;
932
983
  }
933
984
 
934
- // src/services/query-code-generator.ts
935
- var QueryCodeGenerator = class {
936
- constructor(options) {
937
- this.options = options;
938
- }
939
- /**
940
- * 生成 Query Service 檔案內容
941
- */
942
- generateQueryService(endpointInfos) {
943
- const generatorOptions = {
944
- ...this.options,
945
- apiConfiguration: this.options.apiConfiguration || {
946
- file: "@/store/webapi",
947
- importName: "WebApiConfiguration"
948
- }
949
- };
950
- return generateQueryServiceFile(endpointInfos, generatorOptions);
951
- }
952
- };
953
-
954
- // src/services/api-service-generator.ts
985
+ // src/generators/rtk-enhance-endpoints-generator.ts
955
986
  init_esm_shims();
956
-
957
- // src/generators/api-service-generator.ts
958
- init_esm_shims();
959
- function generateApiServiceFile(endpointInfos, options) {
987
+ function generateRtkEnhanceEndpointsFile(endpointInfos, options) {
960
988
  const { apiConfiguration, httpClient, groupKey } = options;
961
989
  const apiServiceClassName = groupKey ? `${groupKey.charAt(0).toUpperCase() + groupKey.slice(1)}ApiService` : "ApiService";
962
990
  return `/* eslint-disable */
@@ -980,6 +1008,7 @@ export class ${apiServiceClassName} {
980
1008
  ${endpointInfos.map((info) => {
981
1009
  const isGet = info.verb.toUpperCase() === "GET";
982
1010
  const hasArgs = info.argTypeName !== "VoidApiArg";
1011
+ const hasRequestBody = info.hasRequestBody;
983
1012
  const generateUrlTemplate = (path5) => {
984
1013
  const hasPathParams = path5.includes("{");
985
1014
  if (hasPathParams && hasArgs) {
@@ -990,18 +1019,16 @@ ${endpointInfos.map((info) => {
990
1019
  }
991
1020
  };
992
1021
  const hasQueryParams = info.queryParams.length > 0;
993
- const hasBody = !isGet && hasArgs && !info.isVoidArg;
1022
+ const hasBody = !isGet && hasArgs;
994
1023
  const generateRequestOptions = () => {
995
- const options2 = [
996
- "...args.fetchOptions"
997
- ];
998
- if (hasBody || !isGet) {
1024
+ const options2 = ["...args.fetchOptions"];
1025
+ if (hasRequestBody || !isGet) {
999
1026
  options2.push(`headers: { 'Content-Type': 'application/json', ...args.fetchOptions?.headers }`);
1000
1027
  }
1001
1028
  if (hasQueryParams && hasArgs) {
1002
1029
  options2.push(generateQueryParams(info.queryParams));
1003
1030
  }
1004
- if (hasBody) {
1031
+ if (hasRequestBody) {
1005
1032
  options2.push("body: args.variables.body");
1006
1033
  }
1007
1034
  return options2.length > 0 ? `{ ${options2.join(", ")} }` : "{}";
@@ -1027,62 +1054,47 @@ function generateQueryParams(queryParams) {
1027
1054
  return `params: withoutUndefined({ ${paramEntries} })`;
1028
1055
  }
1029
1056
 
1030
- // src/services/api-service-generator.ts
1031
- var ApiServiceGenerator = class {
1032
- constructor(options) {
1033
- this.options = options;
1034
- }
1035
- /**
1036
- * 生成 API Service 檔案內容
1037
- */
1038
- generateApiService(endpointInfos) {
1039
- const generatorOptions = {
1040
- ...this.options,
1041
- apiConfiguration: this.options.apiConfiguration || {
1042
- file: "@/store/webapi",
1043
- importName: "WebApiConfiguration"
1044
- }
1045
- };
1046
- return generateApiServiceFile(endpointInfos, generatorOptions);
1047
- }
1048
- };
1049
-
1050
1057
  // src/services/api-code-generator.ts
1051
1058
  var ApiCodeGenerator = class {
1052
1059
  constructor(parserService, options) {
1053
1060
  this.parserService = parserService;
1054
1061
  this.options = options;
1055
1062
  this.infoExtractor = new EndpointInfoExtractor(options);
1056
- this.queryGenerator = new QueryCodeGenerator(options);
1057
- this.apiServiceGenerator = new ApiServiceGenerator(options);
1058
1063
  }
1059
1064
  infoExtractor;
1060
- queryGenerator;
1061
- apiServiceGenerator;
1062
1065
  /**
1063
- * 生成完整的 API 程式碼
1066
+ * 生成完整的 RTK Query 程式碼
1064
1067
  */
1065
1068
  async generate() {
1066
1069
  const operationDefinitions = this.parserService.getOperationDefinitions(this.options.filterEndpoints);
1067
1070
  const endpointInfos = this.infoExtractor.extractEndpointInfos(operationDefinitions);
1071
+ return this.generateRtkQueryCode(endpointInfos);
1072
+ }
1073
+ /**
1074
+ * 生成 RTK Query 代碼
1075
+ */
1076
+ generateRtkQueryCode(endpointInfos) {
1068
1077
  const typesContent = this.generateTypes(endpointInfos);
1069
- const apiServiceContent = this.generateApiService(endpointInfos);
1070
- const queryServiceContent = this.generateQueryService(endpointInfos);
1078
+ const rtkQueryContent = generateRtkQueryFile(endpointInfos, this.options);
1079
+ const enhanceEndpointsContent = generateRtkEnhanceEndpointsFile(endpointInfos, this.options);
1071
1080
  const indexContent = this.generateIndex();
1072
- const allEndpointCacheKeys = endpointInfos.filter((info) => info.isQuery).map((info) => ({
1073
- operationName: info.operationName,
1074
- queryKeyName: info.queryKeyName,
1075
- groupKey: this.options.groupKey || "_common"
1076
- }));
1077
1081
  const operationNames = endpointInfos.map((info) => info.operationName);
1082
+ const allTags = /* @__PURE__ */ new Set();
1083
+ endpointInfos.forEach((info) => {
1084
+ if (info.tags && Array.isArray(info.tags)) {
1085
+ info.tags.forEach((tag) => allTags.add(tag));
1086
+ }
1087
+ });
1078
1088
  return {
1079
1089
  operationNames,
1090
+ tags: Array.from(allTags),
1080
1091
  files: {
1081
1092
  types: typesContent,
1082
- apiService: apiServiceContent,
1083
- queryService: queryServiceContent,
1093
+ queryService: rtkQueryContent,
1094
+ // RTK Query 檔案
1084
1095
  index: indexContent,
1085
- allEndpointCacheKeys
1096
+ enhanceEndpoints: enhanceEndpointsContent
1097
+ // 新增的 enhance endpoints 檔案
1086
1098
  }
1087
1099
  };
1088
1100
  }
@@ -1098,43 +1110,35 @@ var ApiCodeGenerator = class {
1098
1110
  }
1099
1111
  };
1100
1112
  const apiGen = this.parserService.getApiGenerator();
1101
- const schemaInterfaces = apiGen.aliases.reduce((curr, alias) => {
1102
- if (ts2.isInterfaceDeclaration(alias) || ts2.isTypeAliasDeclaration(alias)) {
1103
- const name = alias.name.text;
1104
- return {
1105
- ...curr,
1106
- [name]: alias
1107
- };
1108
- }
1109
- return curr;
1110
- }, {});
1113
+ const schemaInterfaces = apiGen.aliases.reduce(
1114
+ (curr, alias) => {
1115
+ if (ts2.isInterfaceDeclaration(alias) || ts2.isTypeAliasDeclaration(alias)) {
1116
+ const name = alias.name.text;
1117
+ return {
1118
+ ...curr,
1119
+ [name]: alias
1120
+ };
1121
+ }
1122
+ return curr;
1123
+ },
1124
+ {}
1125
+ );
1111
1126
  const operationDefinitions = this.parserService.getOperationDefinitions(this.options.filterEndpoints);
1112
1127
  return generateTypesFile(endpointInfos, generatorOptions, schemaInterfaces, operationDefinitions);
1113
1128
  }
1114
- /**
1115
- * 生成 API Service 檔案內容
1116
- */
1117
- generateApiService(endpointInfos) {
1118
- return this.apiServiceGenerator.generateApiService(endpointInfos);
1119
- }
1120
- /**
1121
- * 生成 Query Service 檔案內容
1122
- */
1123
- generateQueryService(endpointInfos) {
1124
- return this.queryGenerator.generateQueryService(endpointInfos);
1125
- }
1126
1129
  /**
1127
1130
  * 生成 Index 檔案內容
1128
1131
  */
1129
1132
  generateIndex() {
1130
- const generatorOptions = {
1131
- ...this.options,
1132
- apiConfiguration: this.options.apiConfiguration || {
1133
- file: "@/store/webapi",
1134
- importName: "WebApiConfiguration"
1135
- }
1136
- };
1137
- return generateIndexFile(this.options.groupKey || "", generatorOptions);
1133
+ const groupKey = this.options.groupKey || "";
1134
+ const exportName = groupKey ? `${groupKey.charAt(0).toLowerCase() + groupKey.slice(1)}Api` : "api";
1135
+ return `/* eslint-disable */
1136
+ // [Warning] Generated automatically - do not edit manually
1137
+
1138
+ export { default as ${exportName} } from "./enhanceEndpoints";
1139
+ export * from "./query.generated";
1140
+ export * from "./types";
1141
+ `;
1138
1142
  }
1139
1143
  // /**
1140
1144
  // * 獲取已解析的 parser service(供外部使用)
@@ -1151,6 +1155,46 @@ var ApiCodeGenerator = class {
1151
1155
  // }
1152
1156
  };
1153
1157
 
1158
+ // src/generators/utils-generator.ts
1159
+ init_esm_shims();
1160
+ function generateUtilsFile() {
1161
+ return `/* eslint-disable */
1162
+ // [Warning] Generated automatically - do not edit manually
1163
+
1164
+ /**
1165
+ * Clear undefined in object
1166
+ */
1167
+ export function withoutUndefined(obj?: Record<string, any>) {
1168
+ if(typeof obj === 'undefined') return;
1169
+ return Object.fromEntries(
1170
+ Object.entries(obj).filter(([_, v]) => v !== undefined && v !== null)
1171
+ );
1172
+ }
1173
+
1174
+ `;
1175
+ }
1176
+
1177
+ // src/generators/tag-types-generator.ts
1178
+ init_esm_shims();
1179
+ function generateTagTypesFile(tags) {
1180
+ if (tags.length === 0) {
1181
+ return `/* eslint-disable */
1182
+ // [Warning] Generated automatically - do not edit manually
1183
+
1184
+ export enum ECacheTagTypes {
1185
+ }
1186
+ `;
1187
+ }
1188
+ const enumEntries = tags.sort().map((tag) => ` ${tag} = '${tag}',`).join("\n");
1189
+ return `/* eslint-disable */
1190
+ // [Warning] Generated automatically - do not edit manually
1191
+
1192
+ export enum ECacheTagTypes {
1193
+ ${enumEntries}
1194
+ }
1195
+ `;
1196
+ }
1197
+
1154
1198
  // src/services/unified-code-generator.ts
1155
1199
  var UnifiedCodeGenerator = class {
1156
1200
  _options;
@@ -1161,17 +1205,18 @@ var UnifiedCodeGenerator = class {
1161
1205
  openApiDoc = null;
1162
1206
  parserService = null;
1163
1207
  schemaInterfaces = {};
1164
- allEndpointCacheKeys = [];
1165
1208
  actualSchemaFile = "";
1166
1209
  // 生成內容存儲
1167
1210
  generatedContent = {
1168
1211
  groups: [],
1169
- cacheKeys: null,
1170
1212
  commonTypes: "",
1171
1213
  componentSchema: "",
1172
1214
  doNotModify: "",
1173
- utils: ""
1215
+ utils: "",
1216
+ tagTypes: ""
1174
1217
  };
1218
+ // 收集所有 tags
1219
+ allTags = /* @__PURE__ */ new Set();
1175
1220
  constructor(options) {
1176
1221
  this._options = options;
1177
1222
  }
@@ -1181,11 +1226,11 @@ var UnifiedCodeGenerator = class {
1181
1226
  async generateAll() {
1182
1227
  await this.prepare();
1183
1228
  await this.generateApi();
1184
- this.generateCacheKeysContent();
1185
1229
  this.generateCommonTypesContent();
1186
1230
  this.generateSchemaContent();
1187
1231
  this.generateUtilsContent();
1188
1232
  this.generateDoNotModifyContent();
1233
+ this.generateTagTypesContent();
1189
1234
  return await this.release();
1190
1235
  }
1191
1236
  /**
@@ -1238,18 +1283,15 @@ var UnifiedCodeGenerator = class {
1238
1283
  outputPath: groupInfo.outputPath,
1239
1284
  content: groupContent
1240
1285
  });
1286
+ if (groupContent.tags && Array.isArray(groupContent.tags)) {
1287
+ groupContent.tags.forEach((tag) => this.allTags.add(tag));
1288
+ }
1241
1289
  }
1242
1290
  } catch (error) {
1243
1291
  throw new Error(`\u7FA4\u7D44 ${groupInfo.groupKey} \u751F\u6210\u5931\u6557: ${error}`);
1244
1292
  }
1245
1293
  }
1246
1294
  }
1247
- /**
1248
- * 生成 cache keys
1249
- */
1250
- async generateCacheKeysContent() {
1251
- this.generatedContent.cacheKeys = generateCacheKeysFile(this.allEndpointCacheKeys);
1252
- }
1253
1295
  /**
1254
1296
  * 生成 common types
1255
1297
  */
@@ -1274,6 +1316,13 @@ var UnifiedCodeGenerator = class {
1274
1316
  async generateUtilsContent() {
1275
1317
  this.generatedContent.utils = generateUtilsFile();
1276
1318
  }
1319
+ /**
1320
+ * 生成 Tag Types
1321
+ */
1322
+ async generateTagTypesContent() {
1323
+ const tagsArray = Array.from(this.allTags);
1324
+ this.generatedContent.tagTypes = generateTagTypesFile(tagsArray);
1325
+ }
1277
1326
  /**
1278
1327
  * 發佈階段:統一寫入所有檔案
1279
1328
  */
@@ -1290,8 +1339,8 @@ var UnifiedCodeGenerator = class {
1290
1339
  groupOutputDir,
1291
1340
  {
1292
1341
  types: group.content.files.types,
1293
- apiService: group.content.files.apiService,
1294
1342
  queryService: group.content.files.queryService,
1343
+ enhanceEndpoints: group.content.files.enhanceEndpoints,
1295
1344
  index: group.content.files.index
1296
1345
  }
1297
1346
  );
@@ -1303,11 +1352,10 @@ var UnifiedCodeGenerator = class {
1303
1352
  }
1304
1353
  }
1305
1354
  const outputDir = this.generatedContent.groups[0] ? path4.dirname(path4.dirname(this.generatedContent.groups[0].outputPath)) : "./generated";
1306
- if (this.generatedContent.cacheKeys || this.generatedContent.commonTypes || this.generatedContent.doNotModify || this.generatedContent.utils) {
1355
+ if (this.generatedContent.commonTypes || this.generatedContent.doNotModify || this.generatedContent.utils) {
1307
1356
  const sharedResults = await this.fileWriterService.writeSharedFiles(
1308
1357
  outputDir,
1309
1358
  {
1310
- cacheKeys: this.generatedContent.cacheKeys || void 0,
1311
1359
  commonTypes: this.generatedContent.commonTypes || void 0,
1312
1360
  doNotModify: this.generatedContent.doNotModify || void 0,
1313
1361
  utils: this.generatedContent.utils || void 0
@@ -1315,6 +1363,13 @@ var UnifiedCodeGenerator = class {
1315
1363
  );
1316
1364
  results.push(...sharedResults);
1317
1365
  }
1366
+ if (this.generatedContent.tagTypes) {
1367
+ const tagTypesResult = await this.fileWriterService.writeFile(
1368
+ path4.join(outputDir, "tagTypes.ts"),
1369
+ this.generatedContent.tagTypes
1370
+ );
1371
+ results.push(tagTypesResult);
1372
+ }
1318
1373
  if (this.generatedContent.componentSchema) {
1319
1374
  const schemaResults = await this.fileWriterService.writeSchemaFile(
1320
1375
  outputDir,
@@ -1322,6 +1377,12 @@ var UnifiedCodeGenerator = class {
1322
1377
  );
1323
1378
  results.push(...schemaResults);
1324
1379
  }
1380
+ const mainIndexContent = this.generateMainIndex(generatedGroups);
1381
+ const mainIndexResult = await this.fileWriterService.writeFile(
1382
+ path4.join(outputDir, "index.ts"),
1383
+ mainIndexContent
1384
+ );
1385
+ results.push(mainIndexResult);
1325
1386
  } catch (error) {
1326
1387
  errors.push(error);
1327
1388
  }
@@ -1351,12 +1412,19 @@ var UnifiedCodeGenerator = class {
1351
1412
  }
1352
1413
  const apiGenerator = new ApiCodeGenerator(this.parserService, groupOptions);
1353
1414
  const result = await apiGenerator.generate();
1354
- if (result.files && "allEndpointCacheKeys" in result.files) {
1355
- const cacheKeys = result.files.allEndpointCacheKeys;
1356
- this.allEndpointCacheKeys.push(...cacheKeys);
1357
- }
1358
1415
  return result;
1359
1416
  }
1417
+ /**
1418
+ * 生成主 index.ts 檔案
1419
+ */
1420
+ generateMainIndex(generatedGroups) {
1421
+ const exports = generatedGroups.map((groupKey) => `export * from "./${groupKey}";`).join("\n");
1422
+ return `/* eslint-disable */
1423
+ // [Warning] Generated automatically - do not edit manually
1424
+
1425
+ ${exports}
1426
+ `;
1427
+ }
1360
1428
  };
1361
1429
 
1362
1430
  // src/index.ts