@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.
@@ -20,11 +20,15 @@ export interface QueryConfig {
20
20
  keepUnusedDataFor?: number,
21
21
  }
22
22
 
23
+ export interface IRequestConfig extends RequestOptions { {
24
+ timeout?: number;
25
+ }
26
+
23
27
  export type IRestFulEndpointsQueryReturn<TVariables> = TVariables extends void ?
24
- void | {fetchOptions?: RequestOptions;config?: QueryConfig;}:
28
+ void | {fetchOptions?: IRequestConfig;}:
25
29
  {
26
30
  variables: TVariables;
27
- fetchOptions?: RequestOptions;
31
+ fetchOptions?: IRequestConfig;
28
32
  config?: QueryConfig;
29
33
  };
30
34
  `;
@@ -1,5 +1,25 @@
1
1
  import ts from 'typescript';
2
2
 
3
+ /**
4
+ * 轉換類型名稱為大駝峰命名
5
+ */
6
+ function toPascalCase(name: string): string {
7
+ return name.charAt(0).toUpperCase() + name.slice(1);
8
+ }
9
+
10
+ /**
11
+ * 重新命名 TypeScript 節點中的標識符
12
+ */
13
+ function renameIdentifier(node: ts.Node, oldName: string, newName: string): ts.Node {
14
+ return ts.transform(node, [
15
+ context => rootNode => ts.visitNode(rootNode, function visit(node): ts.Node {
16
+ if (ts.isIdentifier(node) && node.text === oldName) {
17
+ return ts.factory.createIdentifier(newName);
18
+ }
19
+ return ts.visitEachChild(node, visit, context);
20
+ })
21
+ ]).transformed[0];
22
+ }
3
23
 
4
24
  /**
5
25
  * 產生 component-schema.ts 內容
@@ -7,7 +27,7 @@ import ts from 'typescript';
7
27
  */
8
28
  export function generateComponentSchemaFile(interfaces: Record<string, ts.InterfaceDeclaration | ts.TypeAliasDeclaration>) {
9
29
  const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
10
-
30
+
11
31
  const resultFile = ts.createSourceFile(
12
32
  'component-schema.ts',
13
33
  '',
@@ -16,10 +36,23 @@ export function generateComponentSchemaFile(interfaces: Record<string, ts.Interf
16
36
  ts.ScriptKind.TS
17
37
  );
18
38
 
19
- // 分析接口內容以找出需要從 shared-types 導入的類型
39
+ // 處理類型名稱轉換為大駝峰,並建立映射表
40
+ const renamedInterfaces: Array<string> = [];
41
+ const typeNameMapping: Record<string, string> = {};
42
+
43
+ Object.entries(interfaces).forEach(([originalName, node]) => {
44
+ const pascalCaseName = toPascalCase(originalName);
45
+ typeNameMapping[originalName] = pascalCaseName;
46
+
47
+ // 重新命名節點
48
+ const renamedNode = renameIdentifier(node, originalName, pascalCaseName);
49
+ const printed = printer.printNode(ts.EmitHint.Unspecified, renamedNode, resultFile);
50
+ renamedInterfaces.push(printed);
51
+ });
52
+
20
53
  return `/* eslint-disable */
21
- // [Warning] Generated automatically - do not edit manually
22
-
23
- ${Object.values(interfaces).map(i => printer.printNode(ts.EmitHint.Unspecified, i, resultFile)).join('\n')}
24
- `
25
- }
54
+ // [Warning] Generated automatically - do not edit manually
55
+
56
+ ${renamedInterfaces.join('\n')}
57
+ `;
58
+ }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * 產生 DO_NOT_MODIFY.md 說明檔案
3
- *
3
+ *
4
4
  * 此檔案用於提醒開發者不要修改產生器生成的程式碼
5
5
  */
6
6
  export function generateDoNotModifyFile(): string {
@@ -24,4 +24,4 @@ export function generateDoNotModifyFile(): string {
24
24
 
25
25
  如有疑問,請參考專案文件或聯繫開發團隊。
26
26
  `;
27
- }
27
+ }
@@ -0,0 +1,113 @@
1
+ import type { GenerationOptions } from '../types';
2
+
3
+ export function generateRtkEnhanceEndpointsFile(
4
+ endpointInfos: Array<{
5
+ operationName: string;
6
+ argTypeName: string;
7
+ responseTypeName: string;
8
+ isQuery: boolean;
9
+ verb: string;
10
+ path: string;
11
+ queryKeyName: string;
12
+ queryParams: any[];
13
+ pathParams: any[];
14
+ isVoidArg: boolean;
15
+ hasRequestBody: boolean;
16
+ summary: string;
17
+ }>,
18
+ options: GenerationOptions
19
+ ) {
20
+ const { apiConfiguration, httpClient, groupKey } = options;
21
+
22
+ // 確定服務名稱,使用業務名稱
23
+ const apiServiceClassName = groupKey
24
+ ? `${groupKey.charAt(0).toUpperCase() + groupKey.slice(1)}ApiService`
25
+ : 'ApiService';
26
+
27
+ return `/* eslint-disable */
28
+ // [Warning] Generated automatically - do not edit manually
29
+
30
+ import { ${httpClient!.importName} } from '${httpClient!.file}';
31
+ import { Injectable, inject } from '@angular/core';
32
+ import { Observable } from 'rxjs';
33
+ import { ${apiConfiguration!.importName} } from '${apiConfiguration!.file}';
34
+ import { RequestOptions, IRestFulEndpointsQueryReturn } from '../common-types';
35
+ import { withoutUndefined } from '../utils';
36
+ import {${endpointInfos.map((info) => ` ${info.argTypeName}, ${info.responseTypeName}`).join(',')} } from './types';
37
+
38
+ @Injectable({
39
+ providedIn: 'root',
40
+ })
41
+ export class ${apiServiceClassName} {
42
+ private config = inject(${apiConfiguration!.importName});
43
+ private http = inject(${httpClient!.importName});
44
+
45
+ ${endpointInfos
46
+ .map((info) => {
47
+ const isGet = info.verb.toUpperCase() === 'GET';
48
+ const hasArgs = info.argTypeName !== 'VoidApiArg';
49
+ const hasRequestBody = info.hasRequestBody;
50
+
51
+ // 生成 URL 模板字面量,直接替換路徑參數
52
+ const generateUrlTemplate = (path: string) => {
53
+ const hasPathParams = path.includes('{');
54
+ if (hasPathParams && hasArgs) {
55
+ // 將 {paramName} 替換為 ${args.variables.paramName}
56
+ const urlTemplate = path.replace(/\{([^}]+)\}/g, '${args.variables.$1}');
57
+ return `\`\${this.config.rootUrl}${urlTemplate}\``;
58
+ } else {
59
+ return `\`\${this.config.rootUrl}${path}\``;
60
+ }
61
+ };
62
+
63
+ const hasQueryParams = info.queryParams.length > 0;
64
+ const hasBody = !isGet && hasArgs;
65
+
66
+ // 生成 HTTP 請求選項
67
+ const generateRequestOptions = () => {
68
+ const options = ['...args.fetchOptions'];
69
+
70
+ // 添加 Content-Type header,特別是對於有 body 的請求
71
+ if (hasRequestBody || !isGet) {
72
+ options.push(`headers: { 'Content-Type': 'application/json', ...args.fetchOptions?.headers }`);
73
+ }
74
+
75
+ if (hasQueryParams && hasArgs) {
76
+ options.push(generateQueryParams(info.queryParams));
77
+ }
78
+
79
+ if (hasRequestBody) {
80
+ options.push('body: args.variables.body');
81
+ }
82
+
83
+ return options.length > 0 ? `{ ${options.join(', ')} }` : '{}';
84
+ };
85
+
86
+ return `
87
+ /**
88
+ * ${info.summary}
89
+ */
90
+ ${info.operationName}(
91
+ ${hasArgs ? `args: IRestFulEndpointsQueryReturn<${info.argTypeName}>, ` : ''}
92
+ ): Observable<${info.responseTypeName}> {
93
+ const url = ${generateUrlTemplate(info.path)};
94
+ return this.http.request('${info.verb.toLowerCase()}', url, ${generateRequestOptions()});
95
+ }`;
96
+ })
97
+ .join('')}
98
+ }
99
+ `;
100
+ }
101
+
102
+
103
+ /**
104
+ * 產生 QueryParams 參數
105
+ * @param queryParams
106
+ */
107
+ function generateQueryParams(queryParams: Record<string, string>[]) {
108
+ const paramEntries = queryParams.map(param =>
109
+ `${param.name}: args.variables.${param.name}`
110
+ ).join(', ');
111
+
112
+ return `params: withoutUndefined({ ${paramEntries} })`;
113
+ }
@@ -1,34 +1,38 @@
1
1
  import type { GenerationOptions } from '../types';
2
2
 
3
- export function generateQueryServiceFile(endpointInfos: Array<{
4
- operationName: string;
5
- argTypeName: string;
6
- responseTypeName: string;
7
- isQuery: boolean;
8
- verb: string;
9
- path: string;
10
- queryKeyName: string;
11
- queryParams: any[];
12
- pathParams: any[];
13
- isVoidArg: boolean;
14
- summary: string;
15
- }>, options: GenerationOptions) {
16
-
3
+ export function generateRtkQueryFile(
4
+ endpointInfos: Array<{
5
+ operationName: string;
6
+ argTypeName: string;
7
+ responseTypeName: string;
8
+ isQuery: boolean;
9
+ verb: string;
10
+ path: string;
11
+ queryKeyName: string;
12
+ queryParams: any[];
13
+ pathParams: any[];
14
+ isVoidArg: boolean;
15
+ summary: string;
16
+ contentType: string;
17
+ hasRequestBody: boolean;
18
+ tags: string[];
19
+ }>,
20
+ options: GenerationOptions
21
+ ) {
17
22
  const { groupKey, refetchOnMountOrArgChange = 60 } = options;
18
-
23
+
19
24
  // 確定服務名稱 - 使用 groupKey 如果有提供的話
20
- const queryServiceName = groupKey ?
21
- `${groupKey.charAt(0).toUpperCase() + groupKey.slice(1)}QueryService` :
22
- 'QueryService';
23
-
24
- const apiServiceName = groupKey ?
25
- `${groupKey.charAt(0).toUpperCase() + groupKey.slice(1)}ApiService` :
26
- 'ApiService';
25
+ const queryServiceName = groupKey
26
+ ? `${groupKey.charAt(0).toUpperCase() + groupKey.slice(1)}QueryService`
27
+ : 'QueryService';
28
+
29
+ const apiServiceName = groupKey ? `${groupKey.charAt(0).toUpperCase() + groupKey.slice(1)}ApiService` : 'ApiService';
27
30
 
28
31
  // 分別處理 Query 和 Mutation 方法
29
32
  const queryMethods = endpointInfos
30
- .filter(info => info.isQuery)
31
- .map(info => `
33
+ .filter((info) => info.isQuery)
34
+ .map(
35
+ (info) => `
32
36
  /**
33
37
  * ${info.summary}
34
38
  */
@@ -41,11 +45,14 @@ export function generateQueryServiceFile(endpointInfos: Array<{
41
45
  fetchOptions: args?.fetchOptions,
42
46
  config: args?.config,
43
47
  })${info.argTypeName !== 'VoidApiArg' ? '(args)' : '()'};
44
- }`).join('');
48
+ }`
49
+ )
50
+ .join('');
45
51
 
46
52
  const mutationMethods = endpointInfos
47
- .filter(info => !info.isQuery)
48
- .map(info => `
53
+ .filter((info) => !info.isQuery)
54
+ .map(
55
+ (info) => `
49
56
  /**
50
57
  * ${info.summary}
51
58
  */
@@ -59,11 +66,14 @@ export function generateQueryServiceFile(endpointInfos: Array<{
59
66
  return this.apiService.${info.operationName}(${info.argTypeName !== 'VoidApiArg' ? 'args' : ''});
60
67
  },
61
68
  });
62
- }`).join('');
69
+ }`
70
+ )
71
+ .join('');
63
72
 
64
73
  const lazyQueryMethods = endpointInfos
65
- .filter(info => info.isQuery)
66
- .map(info => `
74
+ .filter((info) => info.isQuery)
75
+ .map(
76
+ (info) => `
67
77
  /**
68
78
  * ${info.summary}
69
79
  */
@@ -82,7 +92,9 @@ export function generateQueryServiceFile(endpointInfos: Array<{
82
92
  return this.apiService.${info.operationName}(${info.argTypeName !== 'VoidApiArg' ? 'args' : ''});
83
93
  }
84
94
  };
85
- }`).join('');
95
+ }`
96
+ )
97
+ .join('');
86
98
 
87
99
  return `/* eslint-disable */
88
100
  // [Warning] Generated automatically - do not edit manually
@@ -94,7 +106,7 @@ import {NtkQueryService, MutationState, QueryState, MutationResponse} from '@cor
94
106
  import {ECacheKeys} from '../cache-keys';
95
107
  import {${apiServiceName}} from './api.service';
96
108
  import {IRestFulEndpointsQueryReturn, RequestOptions} from '../common-types';
97
- import {${endpointInfos.map(info => ` ${info.argTypeName}, ${info.responseTypeName}`).join(',')}} from './types';
109
+ import {${endpointInfos.map((info) => ` ${info.argTypeName}, ${info.responseTypeName}`).join(',')}} from './types';
98
110
 
99
111
  @Injectable({
100
112
  providedIn: 'root',
@@ -105,4 +117,4 @@ export class ${queryServiceName} {
105
117
  ${queryMethods}${mutationMethods}${lazyQueryMethods}
106
118
  }
107
119
  `;
108
- }
120
+ }
@@ -0,0 +1,30 @@
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
11
+
12
+ export enum ECacheTagTypes {
13
+ }
14
+ `;
15
+ }
16
+
17
+ // 生成枚举项
18
+ const enumEntries = tags
19
+ .sort() // 按字母顺序排序
20
+ .map(tag => ` ${tag} = '${tag}',`)
21
+ .join('\n');
22
+
23
+ return `/* eslint-disable */
24
+ // [Warning] Generated automatically - do not edit manually
25
+
26
+ export enum ECacheTagTypes {
27
+ ${enumEntries}
28
+ }
29
+ `;
30
+ }