@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.
@@ -3,81 +3,79 @@ import ts from 'typescript';
3
3
  import { OpenApiParserService } from './openapi-parser-service';
4
4
  import { EndpointInfoExtractor } from './endpoint-info-extractor';
5
5
  import { generateTypesFile } from '../generators/types-generator';
6
- import { generateIndexFile } from '../generators/index-generator';
7
- import { QueryCodeGenerator } from './query-code-generator';
8
- import { ApiServiceGenerator } from './api-service-generator';
6
+ import { generateRtkQueryFile } from '../generators/rtk-query-generator';
7
+ import { generateRtkEnhanceEndpointsFile } from '../generators/rtk-enhance-endpoints-generator';
9
8
  import type { GenerationOptions, GenerateApiResult } from '../types';
10
9
 
11
10
  /**
12
- * API 程式碼生成器 - 負責生成單一群組的 API 相關程式碼
13
- *
11
+ * API 程式碼生成器 - 負責生成單一群組的 RTK Query 相關程式碼
12
+ *
14
13
  * 設計理念:
15
14
  * - 類別化管理:使用 class 封裝邏輯
16
- * - 分離關注點:將 Query API 服務分開生成
15
+ * - RTK Query 專用:只生成 React/RTK Query 代碼
17
16
  * - 重用資源:接受外部已處理的 v3Doc,避免重複處理
18
17
  */
19
18
  export class ApiCodeGenerator {
20
19
  private infoExtractor: EndpointInfoExtractor;
21
- private queryGenerator: QueryCodeGenerator;
22
- private apiServiceGenerator: ApiServiceGenerator;
23
20
 
24
21
  constructor(
25
22
  private parserService: OpenApiParserService,
26
23
  private options: GenerationOptions
27
24
  ) {
28
-
29
- // // 初始化端點資訊提取器
25
+ // 初始化端點資訊提取器
30
26
  this.infoExtractor = new EndpointInfoExtractor(options);
31
- //
32
- // 初始化分離的生成器
33
- this.queryGenerator = new QueryCodeGenerator(options);
34
- this.apiServiceGenerator = new ApiServiceGenerator(options);
35
27
  }
36
28
 
37
29
  /**
38
- * 生成完整的 API 程式碼
30
+ * 生成完整的 RTK Query 程式碼
39
31
  */
40
32
  async generate(): Promise<GenerateApiResult> {
41
- // console.log('ApiCodeGenerator: 開始生成 API 程式碼...');
42
-
43
33
  // 步驟 1: 獲取操作定義
44
34
  const operationDefinitions = this.parserService.getOperationDefinitions(this.options.filterEndpoints);
45
35
 
46
36
  // 步驟 2: 提取端點資訊
47
37
  const endpointInfos = this.infoExtractor.extractEndpointInfos(operationDefinitions);
48
38
 
39
+ // 步驟 3: 生成 RTK Query 代碼
40
+ return this.generateRtkQueryCode(endpointInfos);
41
+ }
49
42
 
50
- // 步驟 3: 生成各種內容
43
+ /**
44
+ * 生成 RTK Query 代碼
45
+ */
46
+ private generateRtkQueryCode(endpointInfos: Array<any>): GenerateApiResult {
47
+ // 步驟 1: 生成 types 檔案 (React 模式也需要)
51
48
  const typesContent = this.generateTypes(endpointInfos);
52
-
53
49
 
54
- const apiServiceContent = this.generateApiService(endpointInfos);
55
- const queryServiceContent = this.generateQueryService(endpointInfos);
56
- const indexContent = this.generateIndex();
50
+ // 步驟 2: 生成 RTK Query 檔案
51
+ const rtkQueryContent = generateRtkQueryFile(endpointInfos, this.options);
52
+
53
+ // 步驟 3: 生成 enhance endpoints 檔案
54
+ const enhanceEndpointsContent = generateRtkEnhanceEndpointsFile(endpointInfos, this.options);
57
55
 
58
- // 步驟 4: 收集端點快取鍵資訊(只收集 Query 類型的端點)
59
- const allEndpointCacheKeys = endpointInfos
60
- .filter(info => info.isQuery) // 只收集查詢類型的端點
61
- .map(info => ({
62
- operationName: info.operationName,
63
- queryKeyName: info.queryKeyName,
64
- groupKey: this.options.groupKey || '_common'
65
- }));
56
+ // 步驟 4: 生成 index 檔案
57
+ const indexContent = this.generateIndex();
66
58
 
67
- // 步驟 5: 收集操作名稱
68
- const operationNames = endpointInfos.map(info => info.operationName);
59
+ // 步驟 6: 收集操作名稱
60
+ const operationNames = endpointInfos.map((info) => info.operationName);
69
61
 
70
- // console.log('ApiCodeGenerator: API 程式碼生成完成');
62
+ // 步驟 7: 收集所有 tags
63
+ const allTags = new Set<string>();
64
+ endpointInfos.forEach((info) => {
65
+ if (info.tags && Array.isArray(info.tags)) {
66
+ info.tags.forEach((tag: string) => allTags.add(tag));
67
+ }
68
+ });
71
69
 
72
70
  return {
73
71
  operationNames,
72
+ tags: Array.from(allTags),
74
73
  files: {
75
74
  types: typesContent,
76
- apiService: apiServiceContent,
77
- queryService: queryServiceContent,
75
+ queryService: rtkQueryContent, // RTK Query 檔案
78
76
  index: indexContent,
79
- allEndpointCacheKeys: allEndpointCacheKeys
80
- }
77
+ enhanceEndpoints: enhanceEndpointsContent, // 新增的 enhance endpoints 檔案
78
+ },
81
79
  };
82
80
  }
83
81
 
@@ -87,24 +85,27 @@ export class ApiCodeGenerator {
87
85
  private generateTypes(endpointInfos: Array<any>): string {
88
86
  const generatorOptions = {
89
87
  ...this.options,
90
- apiConfiguration: this.options.apiConfiguration || {
91
- file: '@/store/webapi',
92
- importName: 'WebApiConfiguration'
93
- }
88
+ apiConfiguration: this.options.apiConfiguration || {
89
+ file: '@/store/webapi',
90
+ importName: 'WebApiConfiguration',
91
+ },
94
92
  };
95
93
 
96
94
  // 從 parser service 獲取 schema interfaces
97
95
  const apiGen = this.parserService.getApiGenerator();
98
- const schemaInterfaces = apiGen.aliases.reduce<Record<string, ts.InterfaceDeclaration | ts.TypeAliasDeclaration>>((curr, alias) => {
99
- if (ts.isInterfaceDeclaration(alias) || ts.isTypeAliasDeclaration(alias)) {
100
- const name = alias.name.text;
101
- return {
102
- ...curr,
103
- [name]: alias,
104
- };
105
- }
106
- return curr;
107
- }, {});
96
+ const schemaInterfaces = apiGen.aliases.reduce<Record<string, ts.InterfaceDeclaration | ts.TypeAliasDeclaration>>(
97
+ (curr, alias) => {
98
+ if (ts.isInterfaceDeclaration(alias) || ts.isTypeAliasDeclaration(alias)) {
99
+ const name = alias.name.text;
100
+ return {
101
+ ...curr,
102
+ [name]: alias,
103
+ };
104
+ }
105
+ return curr;
106
+ },
107
+ {}
108
+ );
108
109
 
109
110
  // 獲取操作定義以供類型生成使用
110
111
  const operationDefinitions = this.parserService.getOperationDefinitions(this.options.filterEndpoints);
@@ -112,33 +113,20 @@ export class ApiCodeGenerator {
112
113
  return generateTypesFile(endpointInfos, generatorOptions, schemaInterfaces, operationDefinitions);
113
114
  }
114
115
 
115
- /**
116
- * 生成 API Service 檔案內容
117
- */
118
- private generateApiService(endpointInfos: Array<any>): string {
119
- return this.apiServiceGenerator.generateApiService(endpointInfos);
120
- }
121
-
122
- /**
123
- * 生成 Query Service 檔案內容
124
- */
125
- private generateQueryService(endpointInfos: Array<any>): string {
126
- return this.queryGenerator.generateQueryService(endpointInfos);
127
- }
128
-
129
116
  /**
130
117
  * 生成 Index 檔案內容
131
118
  */
132
119
  private generateIndex(): string {
133
- const generatorOptions = {
134
- ...this.options,
135
- apiConfiguration: this.options.apiConfiguration || {
136
- file: '@/store/webapi',
137
- importName: 'WebApiConfiguration'
138
- }
139
- };
120
+ const groupKey = this.options.groupKey || '';
121
+ const exportName = groupKey ? `${groupKey.charAt(0).toLowerCase() + groupKey.slice(1)}Api` : 'api';
122
+
123
+ return `/* eslint-disable */
124
+ // [Warning] Generated automatically - do not edit manually
140
125
 
141
- return generateIndexFile(this.options.groupKey || '', generatorOptions);
126
+ export { default as ${exportName} } from "./enhanceEndpoints";
127
+ export * from "./query.generated";
128
+ export * from "./types";
129
+ `;
142
130
  }
143
131
 
144
132
  // /**
@@ -154,4 +142,4 @@ export class ApiCodeGenerator {
154
142
  // getInfoExtractor(): EndpointInfoExtractor {
155
143
  // return this.infoExtractor;
156
144
  // }
157
- }
145
+ }
@@ -18,6 +18,9 @@ export interface EndpointInfo {
18
18
  pathParams: any[];
19
19
  isVoidArg: boolean;
20
20
  summary: string;
21
+ contentType: string;
22
+ hasRequestBody: boolean;
23
+ tags: string[];
21
24
  }
22
25
 
23
26
  /**
@@ -25,7 +28,10 @@ export interface EndpointInfo {
25
28
  */
26
29
  export class EndpointInfoExtractor {
27
30
  constructor(
28
- private options: Pick<GenerationOptions, 'operationNameSuffix' | 'argSuffix' | 'responseSuffix' | 'queryMatch' | 'endpointOverrides'>
31
+ private options: Pick<
32
+ GenerationOptions,
33
+ 'operationNameSuffix' | 'argSuffix' | 'responseSuffix' | 'queryMatch' | 'endpointOverrides'
34
+ >
29
35
  ) {}
30
36
 
31
37
  /**
@@ -44,7 +50,13 @@ export class EndpointInfoExtractor {
44
50
  */
45
51
  private extractSingleEndpointInfo(operationDefinition: OperationDefinition): EndpointInfo {
46
52
  const { verb, path, operation } = operationDefinition;
47
- const { operationNameSuffix = '', argSuffix = 'Req', responseSuffix = 'Res', queryMatch, endpointOverrides } = this.options;
53
+ const {
54
+ operationNameSuffix = '',
55
+ argSuffix = 'Req',
56
+ responseSuffix = 'Res',
57
+ queryMatch,
58
+ endpointOverrides,
59
+ } = this.options;
48
60
 
49
61
  // 獲取操作名稱
50
62
  const operationName = getOperationName({ verb, path });
@@ -56,7 +68,7 @@ export class EndpointInfoExtractor {
56
68
 
57
69
  // 判斷是否為查詢類型
58
70
  const isQuery = testIsQuery(verb, path, getOverrides(operationDefinition, endpointOverrides), queryMatch);
59
-
71
+
60
72
  // 生成查詢鍵名稱
61
73
  const queryKeyName = `${operationName.replace(/([A-Z])/g, '_$1').toUpperCase()}`;
62
74
 
@@ -64,7 +76,13 @@ export class EndpointInfoExtractor {
64
76
  const summary = operation.summary || `${verb.toUpperCase()} ${path}`;
65
77
 
66
78
  // 解析參數
67
- const { queryParams, pathParams, isVoidArg } = this.extractParameters(operationDefinition);
79
+ const { queryParams, pathParams, isVoidArg, hasRequestBody } = this.extractParameters(operationDefinition);
80
+
81
+ // 提取 content type
82
+ const contentType = this.extractContentType(operation);
83
+
84
+ // 提取 tags
85
+ const tags = Array.isArray(operation.tags) ? operation.tags : [];
68
86
 
69
87
  return {
70
88
  operationName: finalOperationName,
@@ -77,7 +95,10 @@ export class EndpointInfoExtractor {
77
95
  queryParams,
78
96
  pathParams,
79
97
  isVoidArg,
80
- summary
98
+ summary,
99
+ contentType,
100
+ hasRequestBody,
101
+ tags,
81
102
  };
82
103
  }
83
104
 
@@ -90,14 +111,19 @@ export class EndpointInfoExtractor {
90
111
 
91
112
  // 解析參數
92
113
  const operationParameters = this.resolveArray(operation.parameters);
93
- const pathItemParameters = this.resolveArray(pathItem.parameters)
94
- .filter((pp) => !operationParameters.some((op) => op.name === pp.name && op.in === pp.in));
95
-
96
- const allParameters = supportDeepObjects([...pathItemParameters, ...operationParameters])
97
- .filter((param) => param.in !== 'header');
114
+ const pathItemParameters = this.resolveArray(pathItem.parameters).filter(
115
+ (pp) => !operationParameters.some((op) => op.name === pp.name && op.in === pp.in)
116
+ );
98
117
 
99
- const queryParams = allParameters.filter(param => param.in === 'query');
100
- const pathParams = allParameters.filter(param => param.in === 'path');
118
+ const allParameters = supportDeepObjects([...pathItemParameters, ...operationParameters]).filter(
119
+ (param) => param.in !== 'header'
120
+ );
121
+
122
+ const queryParams = allParameters.filter((param) => param.in === 'query');
123
+ const pathParams = allParameters.filter((param) => param.in === 'path');
124
+
125
+ // 檢查是否有 request body
126
+ const hasRequestBody = !!operation.requestBody;
101
127
 
102
128
  // 檢查是否為 void 類型參數
103
129
  const isVoidArg = queryParams.length === 0 && pathParams.length === 0 && !operation.requestBody;
@@ -105,7 +131,8 @@ export class EndpointInfoExtractor {
105
131
  return {
106
132
  queryParams,
107
133
  pathParams,
108
- isVoidArg
134
+ isVoidArg,
135
+ hasRequestBody,
109
136
  };
110
137
  }
111
138
 
@@ -116,4 +143,29 @@ export class EndpointInfoExtractor {
116
143
  if (!parameters) return [];
117
144
  return Array.isArray(parameters) ? parameters : [parameters];
118
145
  }
119
- }
146
+
147
+ /**
148
+ * 提取操作的 content type
149
+ * @param operation - 操作對象
150
+ */
151
+ private extractContentType(operation: any): string {
152
+ // 檢查 requestBody 是否存在
153
+ if (!operation.requestBody) {
154
+ return 'application/json';
155
+ }
156
+
157
+ // 從 requestBody.content 中獲取第一個 content type
158
+ const content = operation.requestBody.content;
159
+ if (!content || typeof content !== 'object') {
160
+ return 'application/json';
161
+ }
162
+
163
+ const contentTypes = Object.keys(content);
164
+ if (contentTypes.length === 0) {
165
+ return 'application/json';
166
+ }
167
+
168
+ // 返回第一個 content type
169
+ return contentTypes[0];
170
+ }
171
+ }
@@ -23,10 +23,20 @@ export class FileWriterService {
23
23
  async writeFile(filePath: string, content: string): Promise<FileWriteResult> {
24
24
  try {
25
25
  const resolvedPath = path.resolve(process.cwd(), filePath);
26
+ const fileName = path.basename(resolvedPath);
27
+
28
+ // enhanceEndpoints.ts 如果已存在則跳過寫入
29
+ if (fileName === 'enhanceEndpoints.ts' && fs.existsSync(resolvedPath)) {
30
+ return {
31
+ path: resolvedPath,
32
+ success: true
33
+ };
34
+ }
35
+
26
36
  await ensureDirectoryExists(resolvedPath);
27
-
37
+
28
38
  fs.writeFileSync(resolvedPath, content);
29
-
39
+
30
40
  return {
31
41
  path: resolvedPath,
32
42
  success: true
@@ -46,27 +56,28 @@ export class FileWriterService {
46
56
  */
47
57
  async writeFiles(files: Record<string, string>): Promise<FileWriteResult[]> {
48
58
  const results: FileWriteResult[] = [];
49
-
59
+
50
60
  for (const [filePath, content] of Object.entries(files)) {
51
61
  const result = await this.writeFile(filePath, content);
52
62
  results.push(result);
53
63
  }
54
-
64
+
55
65
  return results;
56
66
  }
57
67
 
68
+
58
69
  /**
59
- * 為群組寫入標準檔案結構
70
+ * 為群組寫入 RTK Query 檔案結構
60
71
  * @param groupOutputDir - 群組輸出目錄
61
72
  * @param files - 檔案內容
62
73
  */
63
74
  async writeGroupFiles(
64
75
  groupOutputDir: string,
65
76
  files: {
66
- types?: string;
67
- apiService?: string;
68
- queryService?: string;
69
- index?: string;
77
+ types?: string; // types.ts
78
+ queryService?: string; // query.generated.ts
79
+ enhanceEndpoints?: string; // enhanceEndpoints.ts
80
+ index?: string; // index.ts
70
81
  }
71
82
  ): Promise<FileWriteResult[]> {
72
83
  const filesToWrite: Record<string, string> = {};
@@ -74,15 +85,15 @@ export class FileWriterService {
74
85
  if (files.types) {
75
86
  filesToWrite[path.join(groupOutputDir, 'types.ts')] = files.types;
76
87
  }
77
-
78
- if (files.apiService) {
79
- filesToWrite[path.join(groupOutputDir, 'api.service.ts')] = files.apiService;
80
- }
81
-
88
+
82
89
  if (files.queryService) {
83
- filesToWrite[path.join(groupOutputDir, 'query.service.ts')] = files.queryService;
90
+ filesToWrite[path.join(groupOutputDir, 'query.generated.ts')] = files.queryService;
84
91
  }
85
-
92
+
93
+ if (files.enhanceEndpoints) {
94
+ filesToWrite[path.join(groupOutputDir, 'enhanceEndpoints.ts')] = files.enhanceEndpoints;
95
+ }
96
+
86
97
  if (files.index) {
87
98
  filesToWrite[path.join(groupOutputDir, 'index.ts')] = files.index;
88
99
  }
@@ -99,7 +110,6 @@ export class FileWriterService {
99
110
  outputDir: string,
100
111
  sharedFiles: {
101
112
  commonTypes?: string;
102
- cacheKeys?: string;
103
113
  doNotModify?: string;
104
114
  utils?: string;
105
115
  }
@@ -110,9 +120,6 @@ export class FileWriterService {
110
120
  filesToWrite[path.join(outputDir, 'common-types.ts')] = sharedFiles.commonTypes;
111
121
  }
112
122
 
113
- if (sharedFiles.cacheKeys) {
114
- filesToWrite[path.join(outputDir, 'cache-keys.ts')] = sharedFiles.cacheKeys;
115
- }
116
123
 
117
124
  if (sharedFiles.doNotModify) {
118
125
  filesToWrite[path.join(outputDir, 'DO_NOT_MODIFY.md')] = sharedFiles.doNotModify;
@@ -125,9 +132,6 @@ export class FileWriterService {
125
132
  return this.writeFiles(filesToWrite);
126
133
  }
127
134
 
128
-
129
-
130
-
131
135
  /**
132
136
  * 寫入共享檔案
133
137
  * @param outputDir - 輸出目錄
@@ -15,7 +15,6 @@ export class OpenApiParserService {
15
15
  ) {
16
16
  this.apiGen = new ApiGenerator(v3Doc, {
17
17
  unionUndefined: options.unionUndefined,
18
- useEnumType: options.useEnumType,
19
18
  mergeReadWriteOnly: options.mergeReadWriteOnly,
20
19
  });
21
20
  }
@@ -25,8 +24,17 @@ export class OpenApiParserService {
25
24
  */
26
25
  initialize(): void {
27
26
  if (this.apiGen.spec.components?.schemas) {
27
+ // 原因:oazapfts 會優先使用 schema.title 作為類型名稱,但應該使用 schema key
28
+ // 這只在記憶體中修改,不影響原始 OpenAPI 文件
29
+ Object.keys(this.apiGen.spec.components.schemas).forEach(schemaName => {
30
+ const schema = this.apiGen.spec.components!.schemas![schemaName];
31
+ if (schema && typeof schema === 'object' && 'title' in schema) {
32
+ delete schema.title;
33
+ }
34
+ });
35
+
28
36
  this.apiGen.preprocessComponents(this.apiGen.spec.components.schemas);
29
-
37
+
30
38
  // 手動為每個 schema 生成 type alias
31
39
  Object.keys(this.apiGen.spec.components.schemas).forEach(schemaName => {
32
40
  try {
@@ -4,19 +4,14 @@ import type { OpenAPIV3 } from 'openapi-types';
4
4
  import { OpenApiService } from './openapi-service';
5
5
  import { GroupService, type GroupConfig } from './group-service';
6
6
  import { FileWriterService, type FileWriteResult } from './file-writer-service';
7
- export interface EndpointCacheKey {
8
- operationName: string;
9
- queryKeyName: string;
10
- groupKey: string;
11
- }
12
7
  import { OpenApiParserService } from './openapi-parser-service';
13
- import { generateCacheKeysFile } from '../generators/cache-keys-generator';
14
8
  import { generateCommonTypesFile } from '../generators/common-types-generator';
15
9
  import { generateComponentSchemaFile } from '../generators/component-schema-generator';
16
10
  import { generateDoNotModifyFile } from '../generators/do-not-modify-generator';
17
11
  import type { GenerationOptions, CommonOptions } from '../types';
18
12
  import { ApiCodeGenerator } from './api-code-generator';
19
13
  import { generateUtilsFile } from '../generators/utils-generator';
14
+ import { generateTagTypesFile } from '../generators/tag-types-generator';
20
15
 
21
16
  /**
22
17
  * 統一代碼生成器選項
@@ -61,7 +56,6 @@ export class UnifiedCodeGenerator {
61
56
  private openApiDoc: OpenAPIV3.Document | null = null;
62
57
  private parserService: OpenApiParserService | null = null;
63
58
  private schemaInterfaces: Record<string, ts.InterfaceDeclaration | ts.TypeAliasDeclaration> = {};
64
- private allEndpointCacheKeys: EndpointCacheKey[] = [];
65
59
  private actualSchemaFile: string = '';
66
60
 
67
61
  // 生成內容存儲
@@ -71,20 +65,23 @@ export class UnifiedCodeGenerator {
71
65
  outputPath: string;
72
66
  content: any;
73
67
  }>;
74
- cacheKeys: string | null;
75
68
  commonTypes: string;
76
69
  componentSchema: string;
77
70
  doNotModify: string;
78
71
  utils: string;
72
+ tagTypes: string;
79
73
  } = {
80
74
  groups: [],
81
- cacheKeys: null,
82
75
  commonTypes: '',
83
76
  componentSchema: '',
84
77
  doNotModify: '',
85
- utils: ''
78
+ utils: '',
79
+ tagTypes: ''
86
80
  };
87
81
 
82
+ // 收集所有 tags
83
+ private allTags: Set<string> = new Set();
84
+
88
85
 
89
86
  constructor(options: UnifiedGenerationOptions) {
90
87
  this._options = options;
@@ -103,11 +100,11 @@ export class UnifiedCodeGenerator {
103
100
  // await this.generateQuery();
104
101
 
105
102
  // 生成共用
106
- this.generateCacheKeysContent()
107
103
  this.generateCommonTypesContent()
108
104
  this.generateSchemaContent()
109
105
  this.generateUtilsContent()
110
106
  this.generateDoNotModifyContent()
107
+ this.generateTagTypesContent()
111
108
 
112
109
  return await this.release();
113
110
  }
@@ -186,6 +183,11 @@ export class UnifiedCodeGenerator {
186
183
  outputPath: groupInfo.outputPath,
187
184
  content: groupContent
188
185
  });
186
+
187
+ // 收集此群組的所有 tags
188
+ if (groupContent.tags && Array.isArray(groupContent.tags)) {
189
+ groupContent.tags.forEach((tag: string) => this.allTags.add(tag));
190
+ }
189
191
  }
190
192
  // 如果沒有任何 endpoint,則跳過此群組,不創建資料夾
191
193
  } catch (error) {
@@ -197,12 +199,6 @@ export class UnifiedCodeGenerator {
197
199
  }
198
200
 
199
201
 
200
- /**
201
- * 生成 cache keys
202
- */
203
- private async generateCacheKeysContent(): Promise<void> {
204
- this.generatedContent.cacheKeys = generateCacheKeysFile(this.allEndpointCacheKeys);
205
- }
206
202
 
207
203
  /**
208
204
  * 生成 common types
@@ -233,6 +229,14 @@ export class UnifiedCodeGenerator {
233
229
  this.generatedContent.utils = generateUtilsFile();
234
230
  }
235
231
 
232
+ /**
233
+ * 生成 Tag Types
234
+ */
235
+ private async generateTagTypesContent(): Promise<void> {
236
+ const tagsArray = Array.from(this.allTags);
237
+ this.generatedContent.tagTypes = generateTagTypesFile(tagsArray);
238
+ }
239
+
236
240
 
237
241
 
238
242
 
@@ -252,15 +256,17 @@ export class UnifiedCodeGenerator {
252
256
  try {
253
257
  if (group.content?.files) {
254
258
  const groupOutputDir = path.dirname(group.outputPath);
259
+ // RTK Query 模式 (唯一支援模式)
255
260
  const groupResults = await this.fileWriterService.writeGroupFiles(
256
261
  groupOutputDir,
257
262
  {
258
263
  types: group.content.files.types,
259
- apiService: group.content.files.apiService,
260
264
  queryService: group.content.files.queryService,
265
+ enhanceEndpoints: group.content.files.enhanceEndpoints,
261
266
  index: group.content.files.index
262
267
  }
263
268
  );
269
+
264
270
  results.push(...groupResults);
265
271
  generatedGroups.push(group.groupKey);
266
272
  }
@@ -275,11 +281,10 @@ export class UnifiedCodeGenerator {
275
281
  './generated';
276
282
 
277
283
  // 寫入共用檔案 (包含 DO_NOT_MODIFY.md)
278
- if (this.generatedContent.cacheKeys || this.generatedContent.commonTypes || this.generatedContent.doNotModify || this.generatedContent.utils) {
284
+ if (this.generatedContent.commonTypes || this.generatedContent.doNotModify || this.generatedContent.utils) {
279
285
  const sharedResults = await this.fileWriterService.writeSharedFiles(
280
286
  outputDir,
281
- {
282
- cacheKeys: this.generatedContent.cacheKeys || undefined,
287
+ {
283
288
  commonTypes: this.generatedContent.commonTypes || undefined,
284
289
  doNotModify: this.generatedContent.doNotModify || undefined,
285
290
  utils: this.generatedContent.utils || undefined
@@ -288,6 +293,15 @@ export class UnifiedCodeGenerator {
288
293
  results.push(...sharedResults);
289
294
  }
290
295
 
296
+ // 寫入 tagTypes.ts
297
+ if (this.generatedContent.tagTypes) {
298
+ const tagTypesResult = await this.fileWriterService.writeFile(
299
+ path.join(outputDir, 'tagTypes.ts'),
300
+ this.generatedContent.tagTypes
301
+ );
302
+ results.push(tagTypesResult);
303
+ }
304
+
291
305
  // 寫入 component schema
292
306
  if (this.generatedContent.componentSchema) {
293
307
  const schemaResults = await this.fileWriterService.writeSchemaFile(
@@ -297,6 +311,14 @@ export class UnifiedCodeGenerator {
297
311
  results.push(...schemaResults);
298
312
  }
299
313
 
314
+ // 生成主 index.ts 檔案
315
+ const mainIndexContent = this.generateMainIndex(generatedGroups);
316
+ const mainIndexResult = await this.fileWriterService.writeFile(
317
+ path.join(outputDir, 'index.ts'),
318
+ mainIndexContent
319
+ );
320
+ results.push(mainIndexResult);
321
+
300
322
  } catch (error) {
301
323
  errors.push(error as Error);
302
324
  }
@@ -340,14 +362,21 @@ export class UnifiedCodeGenerator {
340
362
  const apiGenerator = new ApiCodeGenerator(this.parserService, groupOptions);
341
363
  const result = await apiGenerator.generate();
342
364
 
343
- // 提取並儲存快取鍵
344
- if (result.files && 'allEndpointCacheKeys' in result.files) {
345
- const cacheKeys = result.files.allEndpointCacheKeys as EndpointCacheKey[];
346
- this.allEndpointCacheKeys.push(...cacheKeys);
347
- }
348
365
 
349
366
  return result;
350
367
  }
351
368
 
369
+ /**
370
+ * 生成主 index.ts 檔案
371
+ */
372
+ private generateMainIndex(generatedGroups: string[]): string {
373
+ const exports = generatedGroups.map(groupKey => `export * from "./${groupKey}";`).join('\n');
374
+
375
+ return `/* eslint-disable */
376
+ // [Warning] Generated automatically - do not edit manually
377
+
378
+ ${exports}
379
+ `;
380
+ }
352
381
 
353
382
  }