@bitstack/ng-query-codegen-openapi 0.0.31-alpha.1 → 0.0.31-alpha.4

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.
@@ -1,30 +1,46 @@
1
- /**
2
- * 生成 RTK Query 的 cache tag types 枚举文件
3
- * @param tags - 去重后的 tags 数组
4
- * @returns 生成的 TypeScript 枚举代码
5
- */
6
- export function generateTagTypesFile(tags: string[]): string {
7
- // 如果没有 tags,生成一个空的枚举
8
- if (tags.length === 0) {
9
- return `/* eslint-disable */
10
- // [Warning] Generated automatically - do not edit manually
1
+ import { toCamelCase } from './utils-generator';
11
2
 
12
- export enum ECacheTagTypes {
13
- }
14
- `;
15
- }
3
+ export function generateTagTypesFile(
4
+ allEndpointInfos: Array<{
5
+ operationName: string;
6
+ queryKeyName: string;
7
+ groupKey: string;
8
+ }>
9
+ ) {
10
+ // 按 group 分組
11
+ const groupedKeys = allEndpointInfos.reduce(
12
+ (acc, info) => {
13
+ if (!acc[info.groupKey]) {
14
+ acc[info.groupKey] = [];
15
+ }
16
+ acc[info.groupKey].push({
17
+ operationName: info.operationName,
18
+ queryKeyName: info.queryKeyName,
19
+ });
20
+ return acc;
21
+ },
22
+ {} as Record<string, Array<{ operationName: string; queryKeyName: string }>>
23
+ );
16
24
 
17
- // 生成枚举项
18
- const enumEntries = tags
19
- .sort() // 按字母顺序排序
20
- .map(tag => ` ${tag} = '${tag}',`)
21
- .join('\n');
25
+ // 生成 enum 內容
26
+ const enumEntries = Object.entries(groupedKeys)
27
+ .map(([groupKey, keys]) => {
28
+ const groupComment = ` // ${groupKey.charAt(0).toUpperCase() + groupKey.slice(1)} related cache keys`;
29
+ const keyEntries = keys.map((key) => ` ${key.queryKeyName} = '${toCamelCase(key.queryKeyName)}',`).join('\n');
22
30
 
23
- return `/* eslint-disable */
24
- // [Warning] Generated automatically - do not edit manually
31
+ return `${groupComment}\n${keyEntries}`;
32
+ })
33
+ .join('\n\n');
25
34
 
35
+ return `// [Warning] Generated automatically - do not edit manually
36
+
37
+ /**
38
+ * Cache keys enum for all API queries
39
+ */
26
40
  export enum ECacheTagTypes {
27
41
  ${enumEntries}
28
42
  }
43
+
44
+ export default ECacheTagTypes;
29
45
  `;
30
46
  }
@@ -56,6 +56,15 @@ export class ApiCodeGenerator {
56
56
  // 步驟 4: 生成 index 檔案
57
57
  const indexContent = this.generateIndex();
58
58
 
59
+ // 步驟 5: 收集端點快取鍵資訊(只收集 Query 類型的端點)
60
+ const allEndpointCacheKeys = endpointInfos
61
+ .filter((info) => info.isQuery) // 只收集查詢類型的端點
62
+ .map((info) => ({
63
+ operationName: info.operationName,
64
+ queryKeyName: info.queryKeyName,
65
+ groupKey: this.options.groupKey || '_common',
66
+ }));
67
+
59
68
  // 步驟 6: 收集操作名稱
60
69
  const operationNames = endpointInfos.map((info) => info.operationName);
61
70
 
@@ -75,6 +84,7 @@ export class ApiCodeGenerator {
75
84
  queryService: rtkQueryContent, // RTK Query 檔案
76
85
  index: indexContent,
77
86
  enhanceEndpoints: enhanceEndpointsContent, // 新增的 enhance endpoints 檔案
87
+ allEndpointCacheKeys: allEndpointCacheKeys,
78
88
  },
79
89
  };
80
90
  }
@@ -29,7 +29,7 @@ export class FileWriterService {
29
29
  if (fileName === 'enhanceEndpoints.ts' && fs.existsSync(resolvedPath)) {
30
30
  return {
31
31
  path: resolvedPath,
32
- success: true
32
+ success: true,
33
33
  };
34
34
  }
35
35
 
@@ -39,13 +39,13 @@ export class FileWriterService {
39
39
 
40
40
  return {
41
41
  path: resolvedPath,
42
- success: true
42
+ success: true,
43
43
  };
44
44
  } catch (error) {
45
45
  return {
46
46
  path: filePath,
47
47
  success: false,
48
- error: error as Error
48
+ error: error as Error,
49
49
  };
50
50
  }
51
51
  }
@@ -65,7 +65,6 @@ export class FileWriterService {
65
65
  return results;
66
66
  }
67
67
 
68
-
69
68
  /**
70
69
  * 為群組寫入 RTK Query 檔案結構
71
70
  * @param groupOutputDir - 群組輸出目錄
@@ -110,6 +109,7 @@ export class FileWriterService {
110
109
  outputDir: string,
111
110
  sharedFiles: {
112
111
  commonTypes?: string;
112
+ tagTypes?: string;
113
113
  doNotModify?: string;
114
114
  utils?: string;
115
115
  }
@@ -119,7 +119,10 @@ export class FileWriterService {
119
119
  if (sharedFiles.commonTypes) {
120
120
  filesToWrite[path.join(outputDir, 'common-types.ts')] = sharedFiles.commonTypes;
121
121
  }
122
-
122
+
123
+ if (sharedFiles.tagTypes) {
124
+ filesToWrite[path.join(outputDir, 'tagTypes.ts')] = sharedFiles.tagTypes;
125
+ }
123
126
 
124
127
  if (sharedFiles.doNotModify) {
125
128
  filesToWrite[path.join(outputDir, 'DO_NOT_MODIFY.md')] = sharedFiles.doNotModify;
@@ -137,16 +140,11 @@ export class FileWriterService {
137
140
  * @param outputDir - 輸出目錄
138
141
  * @param schema
139
142
  */
140
- async writeSchemaFile(
141
- outputDir: string,
142
- schema: string
143
- ): Promise<FileWriteResult[]> {
143
+ async writeSchemaFile(outputDir: string, schema: string): Promise<FileWriteResult[]> {
144
144
  const filesToWrite: Record<string, string> = {};
145
145
 
146
146
  filesToWrite[path.join(outputDir, 'schema.ts')] = schema;
147
147
 
148
148
  return this.writeFiles(filesToWrite);
149
149
  }
150
-
151
-
152
- }
150
+ }
@@ -13,6 +13,13 @@ import { ApiCodeGenerator } from './api-code-generator';
13
13
  import { generateUtilsFile } from '../generators/utils-generator';
14
14
  import { generateTagTypesFile } from '../generators/tag-types-generator';
15
15
 
16
+ export interface EndpointCacheKey {
17
+ operationName: string;
18
+ queryKeyName: string;
19
+ groupKey: string;
20
+ }
21
+
22
+
16
23
  /**
17
24
  * 統一代碼生成器選項
18
25
  */
@@ -51,13 +58,14 @@ export class UnifiedCodeGenerator {
51
58
  private openApiService = new OpenApiService();
52
59
  private groupService = new GroupService();
53
60
  private fileWriterService = new FileWriterService();
54
-
61
+ private allEndpointCacheKeys: EndpointCacheKey[] = [];
62
+
55
63
  // 內部狀態存儲
56
64
  private openApiDoc: OpenAPIV3.Document | null = null;
57
65
  private parserService: OpenApiParserService | null = null;
58
66
  private schemaInterfaces: Record<string, ts.InterfaceDeclaration | ts.TypeAliasDeclaration> = {};
59
67
  private actualSchemaFile: string = '';
60
-
68
+
61
69
  // 生成內容存儲
62
70
  private generatedContent: {
63
71
  groups: Array<{
@@ -76,19 +84,16 @@ export class UnifiedCodeGenerator {
76
84
  componentSchema: '',
77
85
  doNotModify: '',
78
86
  utils: '',
79
- tagTypes: ''
87
+ tagTypes: '',
80
88
  };
81
89
 
82
90
  // 收集所有 tags
83
91
  private allTags: Set<string> = new Set();
84
92
 
85
-
86
93
  constructor(options: UnifiedGenerationOptions) {
87
94
  this._options = options;
88
95
  }
89
96
 
90
-
91
-
92
97
  /**
93
98
  * 一次性生成(整合所有階段)
94
99
  */
@@ -100,23 +105,21 @@ export class UnifiedCodeGenerator {
100
105
  // await this.generateQuery();
101
106
 
102
107
  // 生成共用
103
- this.generateCommonTypesContent()
104
- this.generateSchemaContent()
105
- this.generateUtilsContent()
106
- this.generateDoNotModifyContent()
107
- this.generateTagTypesContent()
108
+ this.generateCommonTypesContent();
109
+ this.generateSchemaContent();
110
+ this.generateUtilsContent();
111
+ this.generateDoNotModifyContent();
112
+ this.generatECacheTagTypesContent();
108
113
 
109
114
  return await this.release();
110
115
  }
111
116
 
112
-
113
-
114
117
  /**
115
118
  * 準備階段:解析 schema 並初始化所有服務
116
119
  */
117
120
  async prepare(): Promise<void> {
118
121
  // console.log('UnifiedCodeGenerator: 準備階段開始...');
119
-
122
+
120
123
  // 步驟 1: 解析實際的 schema 檔案路徑
121
124
  this.actualSchemaFile = this._options.schemaFile;
122
125
  if (this._options.remoteFile) {
@@ -127,10 +130,7 @@ export class UnifiedCodeGenerator {
127
130
  }
128
131
 
129
132
  // 步驟 2: 獲取 OpenAPI 文檔(只初始化一次)
130
- this.openApiDoc = await this.openApiService.getDocument(
131
- this.actualSchemaFile,
132
- this._options.httpResolverOptions
133
- );
133
+ this.openApiDoc = await this.openApiService.getDocument(this.actualSchemaFile, this._options.httpResolverOptions);
134
134
 
135
135
  // 步驟 3: 初始化解析器服務並處理 schema
136
136
  this.parserService = new OpenApiParserService(this.openApiDoc, this._options);
@@ -138,22 +138,23 @@ export class UnifiedCodeGenerator {
138
138
 
139
139
  // 步驟 4: 提取並儲存 schema interfaces
140
140
  const apiGen = this.parserService.getApiGenerator();
141
- this.schemaInterfaces = apiGen.aliases.reduce<Record<string, ts.InterfaceDeclaration | ts.TypeAliasDeclaration>>((curr, alias) => {
142
- if (ts.isInterfaceDeclaration(alias) || ts.isTypeAliasDeclaration(alias)) {
143
- const name = alias.name.text;
144
- return {
145
- ...curr,
146
- [name]: alias,
147
- };
148
- }
149
- return curr;
150
- }, {});
141
+ this.schemaInterfaces = apiGen.aliases.reduce<Record<string, ts.InterfaceDeclaration | ts.TypeAliasDeclaration>>(
142
+ (curr, alias) => {
143
+ if (ts.isInterfaceDeclaration(alias) || ts.isTypeAliasDeclaration(alias)) {
144
+ const name = alias.name.text;
145
+ return {
146
+ ...curr,
147
+ [name]: alias,
148
+ };
149
+ }
150
+ return curr;
151
+ },
152
+ {}
153
+ );
151
154
 
152
155
  // console.log('UnifiedCodeGenerator: 準備階段完成');
153
156
  }
154
157
 
155
-
156
-
157
158
  /**
158
159
  * 生成階段:產生所有內容但不寫檔
159
160
  */
@@ -171,17 +172,14 @@ export class UnifiedCodeGenerator {
171
172
  // 為每個群組生成內容
172
173
  for (const groupInfo of Object.values(groupInfos)) {
173
174
  try {
174
- const groupContent = await this.generateApiGroupContent(
175
- this._options,
176
- groupInfo
177
- );
175
+ const groupContent = await this.generateApiGroupContent(this._options, groupInfo);
178
176
 
179
177
  // 檢查群組是否有任何有效的 endpoint
180
178
  if (groupContent.operationNames.length > 0) {
181
179
  this.generatedContent.groups.push({
182
180
  groupKey: groupInfo.groupKey,
183
181
  outputPath: groupInfo.outputPath,
184
- content: groupContent
182
+ content: groupContent,
185
183
  });
186
184
 
187
185
  // 收集此群組的所有 tags
@@ -198,7 +196,12 @@ export class UnifiedCodeGenerator {
198
196
  // console.log('UnifiedCodeGenerator: 內容生成階段完成');
199
197
  }
200
198
 
201
-
199
+ /**
200
+ * 生成 cache keys
201
+ */
202
+ private async generatECacheTagTypesContent(): Promise<void> {
203
+ this.generatedContent.tagTypes = generateTagTypesFile(this.allEndpointCacheKeys);
204
+ }
202
205
 
203
206
  /**
204
207
  * 生成 common types
@@ -221,7 +224,6 @@ export class UnifiedCodeGenerator {
221
224
  this.generatedContent.doNotModify = generateDoNotModifyFile();
222
225
  }
223
226
 
224
-
225
227
  /**
226
228
  * 生成 Utils Function
227
229
  */
@@ -232,13 +234,10 @@ export class UnifiedCodeGenerator {
232
234
  /**
233
235
  * 生成 Tag Types
234
236
  */
235
- private async generateTagTypesContent(): Promise<void> {
236
- const tagsArray = Array.from(this.allTags);
237
- this.generatedContent.tagTypes = generateTagTypesFile(tagsArray);
238
- }
239
-
240
-
241
-
237
+ // private async generateTagTypesContent(): Promise<void> {
238
+ // const tagsArray = Array.from(this.allTags);
239
+ // this.generatedContent.tagTypes = generateTagTypesFile(tagsArray);
240
+ // }
242
241
 
243
242
  /**
244
243
  * 發佈階段:統一寫入所有檔案
@@ -257,15 +256,12 @@ export class UnifiedCodeGenerator {
257
256
  if (group.content?.files) {
258
257
  const groupOutputDir = path.dirname(group.outputPath);
259
258
  // RTK Query 模式 (唯一支援模式)
260
- const groupResults = await this.fileWriterService.writeGroupFiles(
261
- groupOutputDir,
262
- {
263
- types: group.content.files.types,
264
- queryService: group.content.files.queryService,
265
- enhanceEndpoints: group.content.files.enhanceEndpoints,
266
- index: group.content.files.index
267
- }
268
- );
259
+ const groupResults = await this.fileWriterService.writeGroupFiles(groupOutputDir, {
260
+ types: group.content.files.types,
261
+ queryService: group.content.files.queryService,
262
+ enhanceEndpoints: group.content.files.enhanceEndpoints,
263
+ index: group.content.files.index,
264
+ });
269
265
 
270
266
  results.push(...groupResults);
271
267
  generatedGroups.push(group.groupKey);
@@ -276,31 +272,26 @@ export class UnifiedCodeGenerator {
276
272
  }
277
273
 
278
274
  // 寫入共用檔案
279
- const outputDir = this.generatedContent.groups[0] ?
280
- path.dirname(path.dirname(this.generatedContent.groups[0].outputPath)) :
281
- './generated';
275
+ const outputDir = this.generatedContent.groups[0]
276
+ ? path.dirname(path.dirname(this.generatedContent.groups[0].outputPath))
277
+ : './generated';
282
278
 
283
279
  // 寫入共用檔案 (包含 DO_NOT_MODIFY.md)
284
- if (this.generatedContent.commonTypes || this.generatedContent.doNotModify || this.generatedContent.utils) {
285
- const sharedResults = await this.fileWriterService.writeSharedFiles(
286
- outputDir,
287
- {
288
- commonTypes: this.generatedContent.commonTypes || undefined,
289
- doNotModify: this.generatedContent.doNotModify || undefined,
290
- utils: this.generatedContent.utils || undefined
291
- }
292
- );
280
+ if (
281
+ this.generatedContent.tagTypes ||
282
+ this.generatedContent.commonTypes ||
283
+ this.generatedContent.doNotModify ||
284
+ this.generatedContent.utils
285
+ ) {
286
+ const sharedResults = await this.fileWriterService.writeSharedFiles(outputDir, {
287
+ tagTypes: this.generatedContent.tagTypes || undefined,
288
+ commonTypes: this.generatedContent.commonTypes || undefined,
289
+ doNotModify: this.generatedContent.doNotModify || undefined,
290
+ utils: this.generatedContent.utils || undefined,
291
+ });
293
292
  results.push(...sharedResults);
294
293
  }
295
294
 
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
295
 
305
296
  // 寫入 component schema
306
297
  if (this.generatedContent.componentSchema) {
@@ -318,7 +309,6 @@ export class UnifiedCodeGenerator {
318
309
  mainIndexContent
319
310
  );
320
311
  results.push(mainIndexResult);
321
-
322
312
  } catch (error) {
323
313
  errors.push(error as Error);
324
314
  }
@@ -329,11 +319,10 @@ export class UnifiedCodeGenerator {
329
319
  success: errors.length === 0,
330
320
  writtenFiles: results,
331
321
  errors,
332
- generatedGroups
322
+ generatedGroups,
333
323
  };
334
324
  }
335
325
 
336
-
337
326
  /**
338
327
  * 為單一群組生成內容
339
328
  */
@@ -358,10 +347,15 @@ export class UnifiedCodeGenerator {
358
347
  if (!this.openApiDoc || !this.parserService) {
359
348
  throw new Error('OpenAPI 文檔未初始化,請先調用 prepare()');
360
349
  }
361
-
350
+
362
351
  const apiGenerator = new ApiCodeGenerator(this.parserService, groupOptions);
363
352
  const result = await apiGenerator.generate();
364
353
 
354
+ // 提取並儲存快取鍵
355
+ if (result.files && 'allEndpointCacheKeys' in result.files) {
356
+ const cacheKeys = result.files.allEndpointCacheKeys as EndpointCacheKey[];
357
+ this.allEndpointCacheKeys.push(...cacheKeys);
358
+ }
365
359
 
366
360
  return result;
367
361
  }
@@ -370,7 +364,7 @@ export class UnifiedCodeGenerator {
370
364
  * 生成主 index.ts 檔案
371
365
  */
372
366
  private generateMainIndex(generatedGroups: string[]): string {
373
- const exports = generatedGroups.map(groupKey => `export * from "./${groupKey}";`).join('\n');
367
+ const exports = generatedGroups.map((groupKey) => `export * from "./${groupKey}";`).join('\n');
374
368
 
375
369
  return `/* eslint-disable */
376
370
  // [Warning] Generated automatically - do not edit manually
@@ -378,5 +372,4 @@ export class UnifiedCodeGenerator {
378
372
  ${exports}
379
373
  `;
380
374
  }
381
-
382
- }
375
+ }
package/src/types.ts CHANGED
@@ -204,6 +204,11 @@ export type GenerateApiResult = {
204
204
  enhanceEndpoints?: string; // RTK Query enhance endpoints file
205
205
  commonTypes?: string;
206
206
  componentSchema?: string;
207
+ allEndpointCacheKeys?: Array<{
208
+ operationName: string;
209
+ queryKeyName: string;
210
+ groupKey: string;
211
+ }>;
207
212
  };
208
213
  };
209
214