@bitstack/ng-query-codegen-openapi 0.0.30

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.
Files changed (45) hide show
  1. package/README.md +77 -0
  2. package/lib/bin/cli.mjs +186 -0
  3. package/lib/bin/cli.mjs.map +1 -0
  4. package/lib/index.d.mts +191 -0
  5. package/lib/index.d.ts +191 -0
  6. package/lib/index.js +1392 -0
  7. package/lib/index.js.map +1 -0
  8. package/lib/index.mjs +1376 -0
  9. package/lib/index.mjs.map +1 -0
  10. package/package.json +91 -0
  11. package/src/bin/cli.ts +77 -0
  12. package/src/bin/utils.ts +85 -0
  13. package/src/generators/api-service-generator.ts +112 -0
  14. package/src/generators/cache-keys-generator.ts +43 -0
  15. package/src/generators/common-types-generator.ts +33 -0
  16. package/src/generators/component-schema-generator.ts +25 -0
  17. package/src/generators/do-not-modify-generator.ts +27 -0
  18. package/src/generators/index-generator.ts +11 -0
  19. package/src/generators/query-service-generator.ts +108 -0
  20. package/src/generators/types-generator.ts +285 -0
  21. package/src/generators/utils-generator.ts +33 -0
  22. package/src/index.ts +21 -0
  23. package/src/services/api-code-generator.ts +157 -0
  24. package/src/services/api-service-generator.ts +24 -0
  25. package/src/services/endpoint-info-extractor.ts +119 -0
  26. package/src/services/file-writer-service.ts +148 -0
  27. package/src/services/group-service.ts +84 -0
  28. package/src/services/openapi-parser-service.ts +72 -0
  29. package/src/services/openapi-service.ts +61 -0
  30. package/src/services/query-code-generator.ts +24 -0
  31. package/src/services/unified-code-generator.ts +353 -0
  32. package/src/types.ts +248 -0
  33. package/src/utils/capitalize.ts +3 -0
  34. package/src/utils/directory.ts +75 -0
  35. package/src/utils/downloadSchema.ts +33 -0
  36. package/src/utils/factory.ts +29 -0
  37. package/src/utils/getOperationDefinitions.ts +20 -0
  38. package/src/utils/getV3Doc.ts +24 -0
  39. package/src/utils/http.ts +86 -0
  40. package/src/utils/index.ts +9 -0
  41. package/src/utils/isQuery.ts +16 -0
  42. package/src/utils/isValidUrl.ts +9 -0
  43. package/src/utils/messages.ts +7 -0
  44. package/src/utils/prettier.ts +51 -0
  45. package/src/utils/removeUndefined.ts +3 -0
package/lib/index.mjs ADDED
@@ -0,0 +1,1376 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __esm = (fn, res) => function __init() {
6
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
7
+ };
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
21
+
22
+ // node_modules/tsup/assets/esm_shims.js
23
+ var init_esm_shims = __esm({
24
+ "node_modules/tsup/assets/esm_shims.js"() {
25
+ "use strict";
26
+ }
27
+ });
28
+
29
+ // src/utils/http.ts
30
+ var http_exports = {};
31
+ __export(http_exports, {
32
+ argumentMatches: () => argumentMatches,
33
+ defaultIsDataResponse: () => defaultIsDataResponse,
34
+ getOperationName: () => getOperationName,
35
+ getOverrides: () => getOverrides,
36
+ getTags: () => getTags,
37
+ operationMatches: () => operationMatches
38
+ });
39
+ import {
40
+ getOperationName as _getOperationName
41
+ } from "oazapfts/generate";
42
+ function defaultIsDataResponse(code, includeDefault) {
43
+ if (includeDefault && code === "default") {
44
+ return true;
45
+ }
46
+ const parsedCode = Number(code);
47
+ return !Number.isNaN(parsedCode) && parsedCode >= 200 && parsedCode < 300;
48
+ }
49
+ function getOperationName({ verb, path: path5 }) {
50
+ return _getOperationName(verb, path5, void 0);
51
+ }
52
+ function getTags({ verb, pathItem }) {
53
+ return verb ? pathItem[verb]?.tags || [] : [];
54
+ }
55
+ function patternMatches(pattern) {
56
+ const filters = Array.isArray(pattern) ? pattern : [pattern];
57
+ return function matcher(operationName) {
58
+ if (!pattern) return true;
59
+ return filters.some(
60
+ (filter) => typeof filter === "string" ? filter === operationName : filter?.test(operationName)
61
+ );
62
+ };
63
+ }
64
+ function operationMatches(pattern) {
65
+ const checkMatch = typeof pattern === "function" ? pattern : patternMatches(pattern);
66
+ return function matcher(operationDefinition) {
67
+ if (!pattern) return true;
68
+ const operationName = getOperationName(operationDefinition);
69
+ return checkMatch(operationName, operationDefinition);
70
+ };
71
+ }
72
+ function argumentMatches(pattern) {
73
+ const checkMatch = typeof pattern === "function" ? pattern : patternMatches(pattern);
74
+ return function matcher(argumentDefinition) {
75
+ if (!pattern || argumentDefinition.in === "path") return true;
76
+ const argumentName = argumentDefinition.name;
77
+ return checkMatch(argumentName, argumentDefinition);
78
+ };
79
+ }
80
+ function getOverrides(operation, endpointOverrides) {
81
+ return endpointOverrides?.find((override) => operationMatches(override.pattern)(operation));
82
+ }
83
+ var init_http = __esm({
84
+ "src/utils/http.ts"() {
85
+ "use strict";
86
+ init_esm_shims();
87
+ }
88
+ });
89
+
90
+ // src/index.ts
91
+ init_esm_shims();
92
+
93
+ // src/services/unified-code-generator.ts
94
+ init_esm_shims();
95
+ import path4 from "node:path";
96
+ import ts3 from "typescript";
97
+
98
+ // src/services/openapi-service.ts
99
+ init_esm_shims();
100
+
101
+ // src/utils/capitalize.ts
102
+ init_esm_shims();
103
+ function capitalize(str) {
104
+ return str.replace(str[0], str[0].toUpperCase());
105
+ }
106
+
107
+ // src/utils/downloadSchema.ts
108
+ init_esm_shims();
109
+ import fs from "node:fs";
110
+ import path from "node:path";
111
+
112
+ // src/utils/isValidUrl.ts
113
+ init_esm_shims();
114
+ function isValidUrl(string) {
115
+ try {
116
+ new URL(string);
117
+ } catch (_) {
118
+ return false;
119
+ }
120
+ return true;
121
+ }
122
+
123
+ // src/utils/downloadSchema.ts
124
+ async function downloadSchemaFile(remoteFile, targetPath) {
125
+ if (!isValidUrl(remoteFile)) {
126
+ throw new Error(`remoteFile must be a valid URL: ${remoteFile}`);
127
+ }
128
+ try {
129
+ const dir = path.dirname(targetPath);
130
+ if (!fs.existsSync(dir)) {
131
+ await fs.promises.mkdir(dir, { recursive: true });
132
+ }
133
+ const response = await fetch(remoteFile);
134
+ if (!response.ok) {
135
+ throw new Error(`Failed to download schema from ${remoteFile}: ${response.statusText}`);
136
+ }
137
+ const content = await response.text();
138
+ await fs.promises.writeFile(targetPath, content, "utf-8");
139
+ console.log(`Schema downloaded from ${remoteFile} to ${targetPath}`);
140
+ return targetPath;
141
+ } catch (error) {
142
+ console.error(`Error downloading schema from ${remoteFile}:`, error);
143
+ throw error;
144
+ }
145
+ }
146
+
147
+ // src/utils/getOperationDefinitions.ts
148
+ init_esm_shims();
149
+
150
+ // src/types.ts
151
+ init_esm_shims();
152
+ var operationKeys = ["get", "put", "post", "delete", "options", "head", "patch", "trace"];
153
+
154
+ // src/utils/getOperationDefinitions.ts
155
+ function getOperationDefinitions(v3Doc) {
156
+ return Object.entries(v3Doc.paths).flatMap(
157
+ ([path5, pathItem]) => !pathItem ? [] : Object.entries(pathItem).filter(
158
+ (arg) => operationKeys.includes(arg[0])
159
+ ).map(([verb, operation]) => ({
160
+ path: path5,
161
+ verb,
162
+ pathItem,
163
+ operation
164
+ }))
165
+ );
166
+ }
167
+
168
+ // src/utils/getV3Doc.ts
169
+ init_esm_shims();
170
+ import SwaggerParser from "@apidevtools/swagger-parser";
171
+ import converter from "swagger2openapi";
172
+ async function getV3Doc(spec, httpResolverOptions) {
173
+ const doc = await SwaggerParser.bundle(spec, {
174
+ resolve: {
175
+ http: httpResolverOptions
176
+ }
177
+ });
178
+ const isOpenApiV3 = "openapi" in doc && doc.openapi.startsWith("3");
179
+ if (isOpenApiV3) {
180
+ return doc;
181
+ } else {
182
+ const result = await converter.convertObj(doc, {});
183
+ return result.openapi;
184
+ }
185
+ }
186
+
187
+ // src/utils/isQuery.ts
188
+ init_esm_shims();
189
+ function isQuery(verb, path5, overrides, queryMatch) {
190
+ if (queryMatch) {
191
+ return queryMatch(verb, path5);
192
+ }
193
+ if (overrides?.type) {
194
+ return overrides.type === "query";
195
+ }
196
+ return verb === "get";
197
+ }
198
+
199
+ // src/services/openapi-service.ts
200
+ var OpenApiService = class {
201
+ docCache = {};
202
+ /**
203
+ * 獲取 OpenAPI 文檔
204
+ * @param schemaLocation - Schema 位置 (URL 或本地路徑)
205
+ * @param httpResolverOptions - HTTP 解析選項
206
+ */
207
+ async getDocument(schemaLocation, httpResolverOptions) {
208
+ if (this.docCache[schemaLocation]) {
209
+ return this.docCache[schemaLocation];
210
+ }
211
+ const doc = await getV3Doc(schemaLocation, httpResolverOptions);
212
+ this.docCache[schemaLocation] = doc;
213
+ return doc;
214
+ }
215
+ /**
216
+ * 下載遠程 Schema 文件
217
+ * @param remoteUrl - 遠程 URL
218
+ * @param localPath - 本地儲存路徑
219
+ */
220
+ async downloadSchema(remoteUrl, localPath) {
221
+ return downloadSchemaFile(remoteUrl, localPath);
222
+ }
223
+ /**
224
+ * 獲取所有 API 路徑
225
+ * @param doc - OpenAPI 文檔
226
+ */
227
+ getPaths(doc) {
228
+ return Object.keys(doc.paths || {});
229
+ }
230
+ /**
231
+ * 清除快取
232
+ */
233
+ clearCache() {
234
+ this.docCache = {};
235
+ }
236
+ };
237
+
238
+ // src/services/group-service.ts
239
+ init_esm_shims();
240
+ import camelCase from "lodash.camelcase";
241
+ var GroupService = class {
242
+ /**
243
+ * 根據配置對路徑進行分組
244
+ * @param paths - API 路徑陣列
245
+ * @param config - 群組配置
246
+ */
247
+ groupPaths(paths, config) {
248
+ const { groupKeyMatch, outputDir } = config;
249
+ const groupedPaths = paths.reduce((acc, path5) => {
250
+ const rawGroupKey = groupKeyMatch(path5);
251
+ const groupKey = rawGroupKey ? camelCase(rawGroupKey) : "_common";
252
+ if (!acc[groupKey]) {
253
+ acc[groupKey] = [];
254
+ }
255
+ acc[groupKey].push(path5);
256
+ return acc;
257
+ }, {});
258
+ const result = {};
259
+ for (const [groupKey, paths2] of Object.entries(groupedPaths)) {
260
+ result[groupKey] = {
261
+ groupKey,
262
+ paths: paths2,
263
+ outputPath: `${outputDir}/${groupKey}/query.service.ts`
264
+ };
265
+ }
266
+ return result;
267
+ }
268
+ /**
269
+ * 為特定群組建立篩選函數
270
+ * @param groupKey - 群組鍵
271
+ * @param config - 群組配置
272
+ */
273
+ createGroupFilter(groupKey, config) {
274
+ return (operationName, operationDefinition) => {
275
+ const path5 = operationDefinition.path;
276
+ const pathGroupKey = camelCase(config.groupKeyMatch(path5) || "");
277
+ if (pathGroupKey !== groupKey && (pathGroupKey || "_common") !== groupKey) {
278
+ return false;
279
+ }
280
+ if (config.filterEndpoint) {
281
+ return config.filterEndpoint(operationName, path5, groupKey);
282
+ }
283
+ return true;
284
+ };
285
+ }
286
+ };
287
+
288
+ // src/services/file-writer-service.ts
289
+ init_esm_shims();
290
+ import fs3 from "node:fs";
291
+ import path3 from "node:path";
292
+
293
+ // src/utils/directory.ts
294
+ init_esm_shims();
295
+ import path2 from "node:path";
296
+ import fs2 from "node:fs";
297
+ async function ensureDirectoryExists(filePath) {
298
+ const dirname = path2.dirname(filePath);
299
+ if (!fs2.existsSync(dirname)) {
300
+ await fs2.promises.mkdir(dirname, { recursive: true });
301
+ }
302
+ }
303
+
304
+ // src/services/file-writer-service.ts
305
+ var FileWriterService = class {
306
+ /**
307
+ * 寫入單一檔案
308
+ * @param filePath - 檔案路徑
309
+ * @param content - 檔案內容
310
+ */
311
+ async writeFile(filePath, content) {
312
+ try {
313
+ const resolvedPath = path3.resolve(process.cwd(), filePath);
314
+ await ensureDirectoryExists(resolvedPath);
315
+ fs3.writeFileSync(resolvedPath, content);
316
+ return {
317
+ path: resolvedPath,
318
+ success: true
319
+ };
320
+ } catch (error) {
321
+ return {
322
+ path: filePath,
323
+ success: false,
324
+ error
325
+ };
326
+ }
327
+ }
328
+ /**
329
+ * 批次寫入多個檔案
330
+ * @param files - 檔案路徑與內容的對應表
331
+ */
332
+ async writeFiles(files) {
333
+ const results = [];
334
+ for (const [filePath, content] of Object.entries(files)) {
335
+ const result = await this.writeFile(filePath, content);
336
+ results.push(result);
337
+ }
338
+ return results;
339
+ }
340
+ /**
341
+ * 為群組寫入標準檔案結構
342
+ * @param groupOutputDir - 群組輸出目錄
343
+ * @param files - 檔案內容
344
+ */
345
+ async writeGroupFiles(groupOutputDir, files) {
346
+ const filesToWrite = {};
347
+ if (files.types) {
348
+ filesToWrite[path3.join(groupOutputDir, "types.ts")] = files.types;
349
+ }
350
+ if (files.apiService) {
351
+ filesToWrite[path3.join(groupOutputDir, "api.service.ts")] = files.apiService;
352
+ }
353
+ if (files.queryService) {
354
+ filesToWrite[path3.join(groupOutputDir, "query.service.ts")] = files.queryService;
355
+ }
356
+ if (files.index) {
357
+ filesToWrite[path3.join(groupOutputDir, "index.ts")] = files.index;
358
+ }
359
+ return this.writeFiles(filesToWrite);
360
+ }
361
+ /**
362
+ * 寫入共享檔案
363
+ * @param outputDir - 輸出目錄
364
+ * @param sharedFiles - 共享檔案內容
365
+ */
366
+ async writeSharedFiles(outputDir, sharedFiles) {
367
+ const filesToWrite = {};
368
+ if (sharedFiles.commonTypes) {
369
+ filesToWrite[path3.join(outputDir, "common-types.ts")] = sharedFiles.commonTypes;
370
+ }
371
+ if (sharedFiles.cacheKeys) {
372
+ filesToWrite[path3.join(outputDir, "cache-keys.ts")] = sharedFiles.cacheKeys;
373
+ }
374
+ if (sharedFiles.doNotModify) {
375
+ filesToWrite[path3.join(outputDir, "DO_NOT_MODIFY.md")] = sharedFiles.doNotModify;
376
+ }
377
+ if (sharedFiles.utils) {
378
+ filesToWrite[path3.join(outputDir, "utils.ts")] = sharedFiles.utils;
379
+ }
380
+ return this.writeFiles(filesToWrite);
381
+ }
382
+ /**
383
+ * 寫入共享檔案
384
+ * @param outputDir - 輸出目錄
385
+ * @param schema
386
+ */
387
+ async writeSchemaFile(outputDir, schema) {
388
+ const filesToWrite = {};
389
+ filesToWrite[path3.join(outputDir, "schema.ts")] = schema;
390
+ return this.writeFiles(filesToWrite);
391
+ }
392
+ };
393
+
394
+ // src/services/openapi-parser-service.ts
395
+ init_esm_shims();
396
+ import ApiGenerator from "oazapfts/generate";
397
+ var OpenApiParserService = class {
398
+ constructor(v3Doc, options) {
399
+ this.v3Doc = v3Doc;
400
+ this.apiGen = new ApiGenerator(v3Doc, {
401
+ unionUndefined: options.unionUndefined,
402
+ useEnumType: options.useEnumType,
403
+ mergeReadWriteOnly: options.mergeReadWriteOnly
404
+ });
405
+ }
406
+ apiGen;
407
+ /**
408
+ * 初始化 - 預處理組件
409
+ */
410
+ initialize() {
411
+ if (this.apiGen.spec.components?.schemas) {
412
+ this.apiGen.preprocessComponents(this.apiGen.spec.components.schemas);
413
+ Object.keys(this.apiGen.spec.components.schemas).forEach((schemaName) => {
414
+ try {
415
+ this.apiGen.getRefAlias({ $ref: `#/components/schemas/${schemaName}` });
416
+ } catch (error) {
417
+ }
418
+ });
419
+ }
420
+ }
421
+ /**
422
+ * 獲取操作定義列表
423
+ * @param filterEndpoints - 端點過濾函數
424
+ */
425
+ getOperationDefinitions(filterEndpoints) {
426
+ const { operationMatches: operationMatches2 } = (init_http(), __toCommonJS(http_exports));
427
+ return getOperationDefinitions(this.v3Doc).filter(operationMatches2(filterEndpoints));
428
+ }
429
+ /**
430
+ * 獲取 API 生成器實例
431
+ */
432
+ getApiGenerator() {
433
+ return this.apiGen;
434
+ }
435
+ /**
436
+ * 獲取 OpenAPI 文檔
437
+ */
438
+ getDocument() {
439
+ return this.v3Doc;
440
+ }
441
+ /**
442
+ * 獲取所有 schema 類型名稱
443
+ */
444
+ getSchemaTypeNames() {
445
+ const schemeTypeNames = /* @__PURE__ */ new Set();
446
+ return schemeTypeNames;
447
+ }
448
+ };
449
+
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
+ // src/generators/common-types-generator.ts
509
+ init_esm_shims();
510
+ function generateCommonTypesFile() {
511
+ return `/* eslint-disable */
512
+ // [Warning] Generated automatically - do not edit manually
513
+
514
+ export interface RequestOptions {
515
+ headers?: Record<string, string>;
516
+ observe?: 'body' | 'events' | 'response';
517
+ responseType?: 'arraybuffer' | 'blob' | 'json' | 'text';
518
+ reportProgress?: boolean;
519
+ withCredentials?: boolean;
520
+ timeout?: number;
521
+ }
522
+
523
+ export interface QueryConfig {
524
+ refetchOnMountOrArgChange?: boolean|number,
525
+ keepUnusedDataFor?: number,
526
+ }
527
+
528
+ export type IRestFulEndpointsQueryReturn<TVariables> = TVariables extends void ?
529
+ void | {fetchOptions?: RequestOptions;config?: QueryConfig;}:
530
+ {
531
+ variables: TVariables;
532
+ fetchOptions?: RequestOptions;
533
+ config?: QueryConfig;
534
+ };
535
+ `;
536
+ }
537
+
538
+ // src/generators/component-schema-generator.ts
539
+ init_esm_shims();
540
+ import ts from "typescript";
541
+ function generateComponentSchemaFile(interfaces) {
542
+ const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
543
+ const resultFile = ts.createSourceFile(
544
+ "component-schema.ts",
545
+ "",
546
+ ts.ScriptTarget.Latest,
547
+ false,
548
+ ts.ScriptKind.TS
549
+ );
550
+ 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")}
554
+ `;
555
+ }
556
+
557
+ // src/generators/do-not-modify-generator.ts
558
+ init_esm_shims();
559
+ function generateDoNotModifyFile() {
560
+ return `# \u8ACB\u52FF\u4FEE\u6539\u6B64\u8CC7\u6599\u593E
561
+
562
+ \u26A0\uFE0F **\u91CD\u8981\u63D0\u9192\uFF1A\u8ACB\u52FF\u4FEE\u6539\u6B64\u8CC7\u6599\u593E\u4E2D\u7684\u4EFB\u4F55\u6A94\u6848**
563
+
564
+ \u6B64\u8CC7\u6599\u593E\u4E2D\u7684\u6240\u6709\u6A94\u6848\u90FD\u662F\u900F\u904E\u7A0B\u5F0F\u78BC\u7522\u751F\u5668\u81EA\u52D5\u7522\u751F\u7684\u3002\u4EFB\u4F55\u624B\u52D5\u4FEE\u6539\u7684\u5167\u5BB9\u5728\u4E0B\u6B21\u91CD\u65B0\u7522\u751F\u6642\u90FD\u5C07\u6703\u88AB\u8986\u84CB\u3002
565
+
566
+ ## \u5982\u4F55\u4FEE\u6539\u9019\u4E9B\u6A94\u6848\uFF1F
567
+
568
+ \u5982\u679C\u60A8\u9700\u8981\u4FEE\u6539\u9019\u4E9B\u6A94\u6848\u7684\u5167\u5BB9\uFF0C\u8ACB\uFF1A
569
+
570
+ 1. \u4FEE\u6539\u76F8\u5C0D\u61C9\u7684\u8A2D\u5B9A\u6A94\u6848\u6216\u6A21\u677F
571
+ 2. \u91CD\u65B0\u57F7\u884C\u7A0B\u5F0F\u78BC\u7522\u751F\u5668
572
+ 3. \u8B93\u7522\u751F\u5668\u81EA\u52D5\u66F4\u65B0\u9019\u4E9B\u6A94\u6848
573
+
574
+ ## \u7522\u751F\u5668\u76F8\u95DC\u8CC7\u8A0A
575
+
576
+ \u9019\u4E9B\u6A94\u6848\u662F\u7531 @bitstack/ng-query-codegen-openapi \u7522\u751F\u5668\u6240\u5EFA\u7ACB\u3002
577
+
578
+ \u5982\u6709\u7591\u554F\uFF0C\u8ACB\u53C3\u8003\u5C08\u6848\u6587\u4EF6\u6216\u806F\u7E6B\u958B\u767C\u5718\u968A\u3002
579
+ `;
580
+ }
581
+
582
+ // src/services/api-code-generator.ts
583
+ init_esm_shims();
584
+ import ts2 from "typescript";
585
+
586
+ // src/services/endpoint-info-extractor.ts
587
+ init_esm_shims();
588
+ init_http();
589
+ import { supportDeepObjects } from "oazapfts/generate";
590
+ var EndpointInfoExtractor = class {
591
+ constructor(options) {
592
+ this.options = options;
593
+ }
594
+ /**
595
+ * 從操作定義列表提取端點資訊
596
+ * @param operationDefinitions - 操作定義列表
597
+ */
598
+ extractEndpointInfos(operationDefinitions) {
599
+ return operationDefinitions.map((operationDefinition) => {
600
+ return this.extractSingleEndpointInfo(operationDefinition);
601
+ });
602
+ }
603
+ /**
604
+ * 從單一操作定義提取端點資訊
605
+ * @param operationDefinition - 操作定義
606
+ */
607
+ extractSingleEndpointInfo(operationDefinition) {
608
+ const { verb, path: path5, operation } = operationDefinition;
609
+ const { operationNameSuffix = "", argSuffix = "Req", responseSuffix = "Res", queryMatch, endpointOverrides } = this.options;
610
+ const operationName = getOperationName({ verb, path: path5 });
611
+ const finalOperationName = operationNameSuffix ? capitalize(operationName + operationNameSuffix) : operationName;
612
+ const argTypeName = capitalize(operationName + operationNameSuffix + argSuffix);
613
+ const responseTypeName = capitalize(operationName + operationNameSuffix + responseSuffix);
614
+ const isQuery2 = isQuery(verb, path5, getOverrides(operationDefinition, endpointOverrides), queryMatch);
615
+ const queryKeyName = `${operationName.replace(/([A-Z])/g, "_$1").toUpperCase()}`;
616
+ const summary = operation.summary || `${verb.toUpperCase()} ${path5}`;
617
+ const { queryParams, pathParams, isVoidArg } = this.extractParameters(operationDefinition);
618
+ return {
619
+ operationName: finalOperationName,
620
+ argTypeName,
621
+ responseTypeName,
622
+ isQuery: isQuery2,
623
+ verb: verb.toUpperCase(),
624
+ path: path5,
625
+ queryKeyName,
626
+ queryParams,
627
+ pathParams,
628
+ isVoidArg,
629
+ summary
630
+ };
631
+ }
632
+ /**
633
+ * 提取操作的參數資訊
634
+ * @param operationDefinition - 操作定義
635
+ */
636
+ extractParameters(operationDefinition) {
637
+ const { operation, pathItem } = operationDefinition;
638
+ 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");
641
+ const queryParams = allParameters.filter((param) => param.in === "query");
642
+ const pathParams = allParameters.filter((param) => param.in === "path");
643
+ const isVoidArg = queryParams.length === 0 && pathParams.length === 0 && !operation.requestBody;
644
+ return {
645
+ queryParams,
646
+ pathParams,
647
+ isVoidArg
648
+ };
649
+ }
650
+ /**
651
+ * 解析參數陣列 (模擬 apiGen.resolveArray)
652
+ */
653
+ resolveArray(parameters) {
654
+ if (!parameters) return [];
655
+ return Array.isArray(parameters) ? parameters : [parameters];
656
+ }
657
+ };
658
+
659
+ // src/generators/types-generator.ts
660
+ init_esm_shims();
661
+ function generateTypesFile(endpointInfos, _options, schemaInterfaces, operationDefinitions) {
662
+ let importStatement = `/* eslint-disable */
663
+ // [Warning] Generated automatically - do not edit manually
664
+
665
+ `;
666
+ const hasSchemaTypes = schemaInterfaces && Object.keys(schemaInterfaces).length > 0;
667
+ if (hasSchemaTypes) {
668
+ importStatement += `import * as Schema from "../schema";
669
+ `;
670
+ }
671
+ importStatement += "\n";
672
+ const typeDefinitions = [];
673
+ const endpointTypes = [];
674
+ endpointInfos.forEach((endpoint) => {
675
+ const reqTypeName = endpoint.argTypeName;
676
+ const resTypeName = endpoint.responseTypeName;
677
+ 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
+ );
684
+ } else {
685
+ endpointTypes.push(
686
+ `export type ${reqTypeName} = {`,
687
+ requestTypeContent,
688
+ `};`,
689
+ ``
690
+ );
691
+ }
692
+ }
693
+ 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
+ );
700
+ } else {
701
+ endpointTypes.push(
702
+ `export type ${resTypeName} = {`,
703
+ responseTypeContent,
704
+ `};`,
705
+ ``
706
+ );
707
+ }
708
+ }
709
+ });
710
+ if (endpointTypes.length > 0) {
711
+ typeDefinitions.push(endpointTypes.join("\n"));
712
+ }
713
+ 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
+ );
719
+ }
720
+ return importStatement + typeDefinitions.join("\n\n");
721
+ }
722
+ function generateRequestTypeContent(endpoint, operationDefinitions) {
723
+ const properties = [];
724
+ if (endpoint.queryParams && endpoint.queryParams.length > 0) {
725
+ endpoint.queryParams.forEach((param) => {
726
+ const optional = param.required ? "" : "?";
727
+ const paramType = getTypeFromParameter(param);
728
+ properties.push(` ${param.name}${optional}: ${paramType};`);
729
+ });
730
+ }
731
+ if (endpoint.pathParams && endpoint.pathParams.length > 0) {
732
+ endpoint.pathParams.forEach((param) => {
733
+ const optional = param.required ? "" : "?";
734
+ const paramType = getTypeFromParameter(param);
735
+ properties.push(` ${param.name}${optional}: ${paramType};`);
736
+ });
737
+ }
738
+ const operationDef = operationDefinitions?.find((op) => {
739
+ return op.operation?.operationId === endpoint.operationName || op.operation?.operationId === endpoint.operationName.toLowerCase() || // 也嘗試匹配 verb + path 組合
740
+ op.verb === endpoint.verb.toLowerCase() && op.path === endpoint.path;
741
+ });
742
+ if (operationDef?.operation?.requestBody) {
743
+ const requestBody = operationDef.operation.requestBody;
744
+ const content = requestBody.content;
745
+ const jsonContent = content["application/json"];
746
+ const formContent = content["multipart/form-data"] || content["application/x-www-form-urlencoded"];
747
+ if (jsonContent?.schema) {
748
+ const bodyType = getTypeFromSchema(jsonContent.schema);
749
+ properties.push(` body: ${bodyType};`);
750
+ } else if (formContent?.schema) {
751
+ const bodyType = getTypeFromSchema(formContent.schema);
752
+ properties.push(` body: ${bodyType};`);
753
+ } else {
754
+ properties.push(` body?: any; // Request body from OpenAPI`);
755
+ }
756
+ }
757
+ if (properties.length === 0) {
758
+ return "";
759
+ }
760
+ return properties.join("\n");
761
+ }
762
+ function generateResponseTypeContent(endpoint, operationDefinitions) {
763
+ const properties = [];
764
+ const operationDef = operationDefinitions?.find((op) => {
765
+ return op.operation?.operationId === endpoint.operationName || op.operation?.operationId === endpoint.operationName.toLowerCase() || // 也嘗試匹配 verb + path 組合
766
+ op.verb === endpoint.verb.toLowerCase() && op.path === endpoint.path;
767
+ });
768
+ if (operationDef?.operation?.responses) {
769
+ const successResponse = operationDef.operation.responses["200"] || operationDef.operation.responses["201"];
770
+ if (successResponse?.content) {
771
+ const jsonContent = successResponse.content["application/json"];
772
+ if (jsonContent?.schema) {
773
+ const responseProps = parseSchemaProperties(jsonContent.schema);
774
+ properties.push(...responseProps);
775
+ } else {
776
+ properties.push(` // Success response from OpenAPI`);
777
+ properties.push(` data?: any;`);
778
+ }
779
+ }
780
+ }
781
+ if (properties.length === 0) {
782
+ return "";
783
+ }
784
+ return properties.join("\n");
785
+ }
786
+ function parseSchemaProperties(schema) {
787
+ const properties = [];
788
+ if (schema.type === "object" && schema.properties) {
789
+ const required = schema.required || [];
790
+ Object.entries(schema.properties).forEach(([propName, propSchema]) => {
791
+ const isRequired = required.includes(propName);
792
+ const optional = isRequired ? "" : "?";
793
+ const propType = getTypeFromSchema(propSchema);
794
+ const description = propSchema.description ? ` // ${propSchema.description}` : "";
795
+ properties.push(` ${propName}${optional}: ${propType};${description}`);
796
+ });
797
+ }
798
+ return properties;
799
+ }
800
+ function getTypeFromSchema(schema) {
801
+ if (!schema) return "any";
802
+ if (schema.$ref) {
803
+ const refPath = schema.$ref;
804
+ if (refPath.startsWith("#/components/schemas/")) {
805
+ const typeName = refPath.replace("#/components/schemas/", "");
806
+ return `Schema.${typeName}`;
807
+ }
808
+ }
809
+ switch (schema.type) {
810
+ case "string":
811
+ if (schema.enum) {
812
+ return schema.enum.map((val) => `"${val}"`).join(" | ");
813
+ }
814
+ return "string";
815
+ case "number":
816
+ case "integer":
817
+ return "number";
818
+ case "boolean":
819
+ return "boolean";
820
+ case "array":
821
+ const itemType = schema.items ? getTypeFromSchema(schema.items) : "any";
822
+ return `${itemType}[]`;
823
+ case "object":
824
+ 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} }`;
832
+ }
833
+ return "any";
834
+ default:
835
+ return "any";
836
+ }
837
+ }
838
+ function getTypeFromParameter(param) {
839
+ if (!param.schema) return "any";
840
+ return getTypeFromSchema(param.schema);
841
+ }
842
+
843
+ // src/generators/index-generator.ts
844
+ 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) {
859
+ const { groupKey, refetchOnMountOrArgChange = 60 } = options;
860
+ const queryServiceName = groupKey ? `${groupKey.charAt(0).toUpperCase() + groupKey.slice(1)}QueryService` : "QueryService";
861
+ const apiServiceName = groupKey ? `${groupKey.charAt(0).toUpperCase() + groupKey.slice(1)}ApiService` : "ApiService";
862
+ const queryMethods = endpointInfos.filter((info) => info.isQuery).map((info) => `
863
+ /**
864
+ * ${info.summary}
865
+ */
866
+ ${info.operationName}Query(${info.argTypeName !== "VoidApiArg" ? `args: IRestFulEndpointsQueryReturn<${info.argTypeName}>` : ""}): Observable<QueryState<${info.responseTypeName}>> {
867
+ return this.ntkQuery.query<${info.responseTypeName}, ${info.argTypeName !== "VoidApiArg" ? `IRestFulEndpointsQueryReturn<${info.argTypeName}>` : "void"}>({
868
+ queryKey: [ECacheKeys.${info.queryKeyName}${info.argTypeName !== "VoidApiArg" ? ", args.variables" : ""}],
869
+ queryFn: () => {
870
+ return this.apiService.${info.operationName}(${info.argTypeName !== "VoidApiArg" ? "args" : ""});
871
+ },
872
+ fetchOptions: args?.fetchOptions,
873
+ config: args?.config,
874
+ })${info.argTypeName !== "VoidApiArg" ? "(args)" : "()"};
875
+ }`).join("");
876
+ const mutationMethods = endpointInfos.filter((info) => !info.isQuery).map((info) => `
877
+ /**
878
+ * ${info.summary}
879
+ */
880
+ ${info.operationName}Mutation(): MutationResponse<${info.responseTypeName}, {
881
+ variables: ${info.argTypeName !== "VoidApiArg" ? info.argTypeName : "void"};
882
+ fetchOptions?: RequestOptions;
883
+ }, HttpErrorResponse> {
884
+ return this.ntkQuery.mutation<${info.responseTypeName}, ${info.argTypeName !== "VoidApiArg" ? `IRestFulEndpointsQueryReturn<${info.argTypeName}>` : "void"}, HttpErrorResponse>({
885
+ endpointName: '${info.operationName}',
886
+ mutationFn: (${info.argTypeName !== "VoidApiArg" ? "args" : ""}) => {
887
+ return this.apiService.${info.operationName}(${info.argTypeName !== "VoidApiArg" ? "args" : ""});
888
+ },
889
+ });
890
+ }`).join("");
891
+ const lazyQueryMethods = endpointInfos.filter((info) => info.isQuery).map((info) => `
892
+ /**
893
+ * ${info.summary}
894
+ */
895
+ ${info.operationName}LazyQuery(): {
896
+ trigger: (${info.argTypeName !== "VoidApiArg" ? `args: IRestFulEndpointsQueryReturn<${info.argTypeName}>, ` : ""}options?: { skipCache?: boolean }) => Observable<${info.responseTypeName}>
897
+ } {
898
+ return {
899
+ trigger: (${info.argTypeName !== "VoidApiArg" ? "args, " : ""}options = {}) => {
900
+ const { skipCache = true } = options;
901
+ if (!skipCache) {
902
+ const cachedResult = this.ntkQuery.getQueryCache<${info.responseTypeName}>([ECacheKeys.${info.queryKeyName}${info.argTypeName !== "VoidApiArg" ? ", args.variables" : ""}]);
903
+ if (cachedResult) {
904
+ return cachedResult;
905
+ }
906
+ }
907
+ return this.apiService.${info.operationName}(${info.argTypeName !== "VoidApiArg" ? "args" : ""});
908
+ }
909
+ };
910
+ }`).join("");
911
+ return `/* eslint-disable */
912
+ // [Warning] Generated automatically - do not edit manually
913
+
914
+ import {Injectable, inject} from '@angular/core';
915
+ import {Observable} from 'rxjs';
916
+ import {HttpErrorResponse} from '@angular/common/http';
917
+ import {NtkQueryService, MutationState, QueryState, MutationResponse} from '@core/ntk-query';
918
+ import {ECacheKeys} from '../cache-keys';
919
+ import {${apiServiceName}} from './api.service';
920
+ import {IRestFulEndpointsQueryReturn, RequestOptions} from '../common-types';
921
+ import {${endpointInfos.map((info) => ` ${info.argTypeName}, ${info.responseTypeName}`).join(",")}} from './types';
922
+
923
+ @Injectable({
924
+ providedIn: 'root',
925
+ })
926
+ export class ${queryServiceName} {
927
+ private apiService = inject(${apiServiceName});
928
+ private ntkQuery = inject(NtkQueryService);
929
+ ${queryMethods}${mutationMethods}${lazyQueryMethods}
930
+ }
931
+ `;
932
+ }
933
+
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
955
+ init_esm_shims();
956
+
957
+ // src/generators/api-service-generator.ts
958
+ init_esm_shims();
959
+ function generateApiServiceFile(endpointInfos, options) {
960
+ const { apiConfiguration, httpClient, groupKey } = options;
961
+ const apiServiceClassName = groupKey ? `${groupKey.charAt(0).toUpperCase() + groupKey.slice(1)}ApiService` : "ApiService";
962
+ return `/* eslint-disable */
963
+ // [Warning] Generated automatically - do not edit manually
964
+
965
+ import { ${httpClient.importName} } from '${httpClient.file}';
966
+ import { Injectable, inject } from '@angular/core';
967
+ import { Observable } from 'rxjs';
968
+ import { ${apiConfiguration.importName} } from '${apiConfiguration.file}';
969
+ import { RequestOptions, IRestFulEndpointsQueryReturn } from '../common-types';
970
+ import { withoutUndefined } from '../utils';
971
+ import {${endpointInfos.map((info) => ` ${info.argTypeName}, ${info.responseTypeName}`).join(",")} } from './types';
972
+
973
+ @Injectable({
974
+ providedIn: 'root',
975
+ })
976
+ export class ${apiServiceClassName} {
977
+ private config = inject(${apiConfiguration.importName});
978
+ private http = inject(${httpClient.importName});
979
+
980
+ ${endpointInfos.map((info) => {
981
+ const isGet = info.verb.toUpperCase() === "GET";
982
+ const hasArgs = info.argTypeName !== "VoidApiArg";
983
+ const generateUrlTemplate = (path5) => {
984
+ const hasPathParams = path5.includes("{");
985
+ if (hasPathParams && hasArgs) {
986
+ const urlTemplate = path5.replace(/\{([^}]+)\}/g, "${args.variables.$1}");
987
+ return `\`\${this.config.rootUrl}${urlTemplate}\``;
988
+ } else {
989
+ return `\`\${this.config.rootUrl}${path5}\``;
990
+ }
991
+ };
992
+ const hasQueryParams = info.queryParams.length > 0;
993
+ const hasBody = !isGet && hasArgs && !info.isVoidArg;
994
+ const generateRequestOptions = () => {
995
+ const options2 = [
996
+ "...args.fetchOptions"
997
+ ];
998
+ if (hasBody || !isGet) {
999
+ options2.push(`headers: { 'Content-Type': 'application/json', ...args.fetchOptions?.headers }`);
1000
+ }
1001
+ if (hasQueryParams && hasArgs) {
1002
+ options2.push(generateQueryParams(info.queryParams));
1003
+ }
1004
+ if (hasBody) {
1005
+ options2.push("body: args.variables.body");
1006
+ }
1007
+ return options2.length > 0 ? `{ ${options2.join(", ")} }` : "{}";
1008
+ };
1009
+ return `
1010
+ /**
1011
+ * ${info.summary}
1012
+ */
1013
+ ${info.operationName}(
1014
+ ${hasArgs ? `args: IRestFulEndpointsQueryReturn<${info.argTypeName}>, ` : ""}
1015
+ ): Observable<${info.responseTypeName}> {
1016
+ const url = ${generateUrlTemplate(info.path)};
1017
+ return this.http.request('${info.verb.toLowerCase()}', url, ${generateRequestOptions()});
1018
+ }`;
1019
+ }).join("")}
1020
+ }
1021
+ `;
1022
+ }
1023
+ function generateQueryParams(queryParams) {
1024
+ const paramEntries = queryParams.map(
1025
+ (param) => `${param.name}: args.variables.${param.name}`
1026
+ ).join(", ");
1027
+ return `params: withoutUndefined({ ${paramEntries} })`;
1028
+ }
1029
+
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
+ // src/services/api-code-generator.ts
1051
+ var ApiCodeGenerator = class {
1052
+ constructor(parserService, options) {
1053
+ this.parserService = parserService;
1054
+ this.options = options;
1055
+ this.infoExtractor = new EndpointInfoExtractor(options);
1056
+ this.queryGenerator = new QueryCodeGenerator(options);
1057
+ this.apiServiceGenerator = new ApiServiceGenerator(options);
1058
+ }
1059
+ infoExtractor;
1060
+ queryGenerator;
1061
+ apiServiceGenerator;
1062
+ /**
1063
+ * 生成完整的 API 程式碼
1064
+ */
1065
+ async generate() {
1066
+ const operationDefinitions = this.parserService.getOperationDefinitions(this.options.filterEndpoints);
1067
+ const endpointInfos = this.infoExtractor.extractEndpointInfos(operationDefinitions);
1068
+ const typesContent = this.generateTypes(endpointInfos);
1069
+ const apiServiceContent = this.generateApiService(endpointInfos);
1070
+ const queryServiceContent = this.generateQueryService(endpointInfos);
1071
+ 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
+ const operationNames = endpointInfos.map((info) => info.operationName);
1078
+ return {
1079
+ operationNames,
1080
+ files: {
1081
+ types: typesContent,
1082
+ apiService: apiServiceContent,
1083
+ queryService: queryServiceContent,
1084
+ index: indexContent,
1085
+ allEndpointCacheKeys
1086
+ }
1087
+ };
1088
+ }
1089
+ /**
1090
+ * 生成 Types 檔案內容
1091
+ */
1092
+ generateTypes(endpointInfos) {
1093
+ const generatorOptions = {
1094
+ ...this.options,
1095
+ apiConfiguration: this.options.apiConfiguration || {
1096
+ file: "@/store/webapi",
1097
+ importName: "WebApiConfiguration"
1098
+ }
1099
+ };
1100
+ 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
+ }, {});
1111
+ const operationDefinitions = this.parserService.getOperationDefinitions(this.options.filterEndpoints);
1112
+ return generateTypesFile(endpointInfos, generatorOptions, schemaInterfaces, operationDefinitions);
1113
+ }
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
+ /**
1127
+ * 生成 Index 檔案內容
1128
+ */
1129
+ 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);
1138
+ }
1139
+ // /**
1140
+ // * 獲取已解析的 parser service(供外部使用)
1141
+ // */
1142
+ // getParserService(): OpenApiParserService {
1143
+ // return this.parserService;
1144
+ // }
1145
+ //
1146
+ // /**
1147
+ // * 獲取端點資訊提取器(供外部使用)
1148
+ // */
1149
+ // getInfoExtractor(): EndpointInfoExtractor {
1150
+ // return this.infoExtractor;
1151
+ // }
1152
+ };
1153
+
1154
+ // src/services/unified-code-generator.ts
1155
+ var UnifiedCodeGenerator = class {
1156
+ _options;
1157
+ openApiService = new OpenApiService();
1158
+ groupService = new GroupService();
1159
+ fileWriterService = new FileWriterService();
1160
+ // 內部狀態存儲
1161
+ openApiDoc = null;
1162
+ parserService = null;
1163
+ schemaInterfaces = {};
1164
+ allEndpointCacheKeys = [];
1165
+ actualSchemaFile = "";
1166
+ // 生成內容存儲
1167
+ generatedContent = {
1168
+ groups: [],
1169
+ cacheKeys: null,
1170
+ commonTypes: "",
1171
+ componentSchema: "",
1172
+ doNotModify: "",
1173
+ utils: ""
1174
+ };
1175
+ constructor(options) {
1176
+ this._options = options;
1177
+ }
1178
+ /**
1179
+ * 一次性生成(整合所有階段)
1180
+ */
1181
+ async generateAll() {
1182
+ await this.prepare();
1183
+ await this.generateApi();
1184
+ this.generateCacheKeysContent();
1185
+ this.generateCommonTypesContent();
1186
+ this.generateSchemaContent();
1187
+ this.generateUtilsContent();
1188
+ this.generateDoNotModifyContent();
1189
+ return await this.release();
1190
+ }
1191
+ /**
1192
+ * 準備階段:解析 schema 並初始化所有服務
1193
+ */
1194
+ async prepare() {
1195
+ this.actualSchemaFile = this._options.schemaFile;
1196
+ if (this._options.remoteFile) {
1197
+ this.actualSchemaFile = await this.openApiService.downloadSchema(
1198
+ this._options.remoteFile,
1199
+ this._options.schemaFile
1200
+ );
1201
+ }
1202
+ this.openApiDoc = await this.openApiService.getDocument(
1203
+ this.actualSchemaFile,
1204
+ this._options.httpResolverOptions
1205
+ );
1206
+ this.parserService = new OpenApiParserService(this.openApiDoc, this._options);
1207
+ this.parserService.initialize();
1208
+ const apiGen = this.parserService.getApiGenerator();
1209
+ this.schemaInterfaces = apiGen.aliases.reduce((curr, alias) => {
1210
+ if (ts3.isInterfaceDeclaration(alias) || ts3.isTypeAliasDeclaration(alias)) {
1211
+ const name = alias.name.text;
1212
+ return {
1213
+ ...curr,
1214
+ [name]: alias
1215
+ };
1216
+ }
1217
+ return curr;
1218
+ }, {});
1219
+ }
1220
+ /**
1221
+ * 生成階段:產生所有內容但不寫檔
1222
+ */
1223
+ async generateApi() {
1224
+ if (!this.openApiDoc || !this.parserService) {
1225
+ throw new Error("\u8ACB\u5148\u8ABF\u7528 prepare() \u65B9\u6CD5");
1226
+ }
1227
+ const paths = this.openApiService.getPaths(this.openApiDoc);
1228
+ const groupInfos = this.groupService.groupPaths(paths, this._options.outputFiles);
1229
+ for (const groupInfo of Object.values(groupInfos)) {
1230
+ try {
1231
+ const groupContent = await this.generateApiGroupContent(
1232
+ this._options,
1233
+ groupInfo
1234
+ );
1235
+ if (groupContent.operationNames.length > 0) {
1236
+ this.generatedContent.groups.push({
1237
+ groupKey: groupInfo.groupKey,
1238
+ outputPath: groupInfo.outputPath,
1239
+ content: groupContent
1240
+ });
1241
+ }
1242
+ } catch (error) {
1243
+ throw new Error(`\u7FA4\u7D44 ${groupInfo.groupKey} \u751F\u6210\u5931\u6557: ${error}`);
1244
+ }
1245
+ }
1246
+ }
1247
+ /**
1248
+ * 生成 cache keys
1249
+ */
1250
+ async generateCacheKeysContent() {
1251
+ this.generatedContent.cacheKeys = generateCacheKeysFile(this.allEndpointCacheKeys);
1252
+ }
1253
+ /**
1254
+ * 生成 common types
1255
+ */
1256
+ async generateCommonTypesContent() {
1257
+ this.generatedContent.commonTypes = generateCommonTypesFile();
1258
+ }
1259
+ /**
1260
+ * 生成Schema
1261
+ */
1262
+ async generateSchemaContent() {
1263
+ this.generatedContent.componentSchema = generateComponentSchemaFile(this.schemaInterfaces);
1264
+ }
1265
+ /**
1266
+ * 生成 DO_NOT_MODIFY.md
1267
+ */
1268
+ async generateDoNotModifyContent() {
1269
+ this.generatedContent.doNotModify = generateDoNotModifyFile();
1270
+ }
1271
+ /**
1272
+ * 生成 Utils Function
1273
+ */
1274
+ async generateUtilsContent() {
1275
+ this.generatedContent.utils = generateUtilsFile();
1276
+ }
1277
+ /**
1278
+ * 發佈階段:統一寫入所有檔案
1279
+ */
1280
+ async release() {
1281
+ const results = [];
1282
+ const errors = [];
1283
+ const generatedGroups = [];
1284
+ try {
1285
+ for (const group of this.generatedContent.groups) {
1286
+ try {
1287
+ if (group.content?.files) {
1288
+ const groupOutputDir = path4.dirname(group.outputPath);
1289
+ const groupResults = await this.fileWriterService.writeGroupFiles(
1290
+ groupOutputDir,
1291
+ {
1292
+ types: group.content.files.types,
1293
+ apiService: group.content.files.apiService,
1294
+ queryService: group.content.files.queryService,
1295
+ index: group.content.files.index
1296
+ }
1297
+ );
1298
+ results.push(...groupResults);
1299
+ generatedGroups.push(group.groupKey);
1300
+ }
1301
+ } catch (error) {
1302
+ errors.push(new Error(`\u5BEB\u5165\u7FA4\u7D44 ${group.groupKey} \u5931\u6557: ${error}`));
1303
+ }
1304
+ }
1305
+ 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) {
1307
+ const sharedResults = await this.fileWriterService.writeSharedFiles(
1308
+ outputDir,
1309
+ {
1310
+ cacheKeys: this.generatedContent.cacheKeys || void 0,
1311
+ commonTypes: this.generatedContent.commonTypes || void 0,
1312
+ doNotModify: this.generatedContent.doNotModify || void 0,
1313
+ utils: this.generatedContent.utils || void 0
1314
+ }
1315
+ );
1316
+ results.push(...sharedResults);
1317
+ }
1318
+ if (this.generatedContent.componentSchema) {
1319
+ const schemaResults = await this.fileWriterService.writeSchemaFile(
1320
+ outputDir,
1321
+ this.generatedContent.componentSchema
1322
+ );
1323
+ results.push(...schemaResults);
1324
+ }
1325
+ } catch (error) {
1326
+ errors.push(error);
1327
+ }
1328
+ return {
1329
+ success: errors.length === 0,
1330
+ writtenFiles: results,
1331
+ errors,
1332
+ generatedGroups
1333
+ };
1334
+ }
1335
+ /**
1336
+ * 為單一群組生成內容
1337
+ */
1338
+ async generateApiGroupContent(options, groupInfo) {
1339
+ const { outputFiles, ...commonConfig } = options;
1340
+ const groupOptions = {
1341
+ ...commonConfig,
1342
+ schemaFile: this.actualSchemaFile,
1343
+ outputFile: groupInfo.outputPath,
1344
+ sharedTypesFile: `${outputFiles.outputDir}/common-types.ts`,
1345
+ filterEndpoints: this.groupService.createGroupFilter(groupInfo.groupKey, outputFiles),
1346
+ queryMatch: outputFiles.queryMatch,
1347
+ groupKey: groupInfo.groupKey
1348
+ };
1349
+ if (!this.openApiDoc || !this.parserService) {
1350
+ throw new Error("OpenAPI \u6587\u6A94\u672A\u521D\u59CB\u5316\uFF0C\u8ACB\u5148\u8ABF\u7528 prepare()");
1351
+ }
1352
+ const apiGenerator = new ApiCodeGenerator(this.parserService, groupOptions);
1353
+ 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
+ return result;
1359
+ }
1360
+ };
1361
+
1362
+ // src/index.ts
1363
+ async function generateEndpoints(options) {
1364
+ const generator = new UnifiedCodeGenerator(options);
1365
+ const result = await generator.generateAll();
1366
+ if (!result.success) {
1367
+ if (result.errors.length > 0) {
1368
+ throw result.errors[0];
1369
+ }
1370
+ }
1371
+ return;
1372
+ }
1373
+ export {
1374
+ generateEndpoints
1375
+ };
1376
+ //# sourceMappingURL=index.mjs.map