@bitstack/ng-query-codegen-openapi 0.0.30 → 0.0.31-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -1
- package/lib/bin/cli.mjs +78 -60
- package/lib/bin/cli.mjs.map +1 -1
- package/lib/index.d.mts +4 -13
- package/lib/index.d.ts +4 -13
- package/lib/index.js +351 -290
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +351 -290
- package/lib/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/bin/utils.ts +74 -3
- package/src/generators/common-types-generator.ts +6 -2
- package/src/generators/component-schema-generator.ts +40 -7
- package/src/generators/do-not-modify-generator.ts +2 -2
- package/src/generators/rtk-enhance-endpoints-generator.ts +113 -0
- package/src/generators/{query-service-generator.ts → rtk-query-generator.ts} +45 -33
- package/src/generators/tag-types-generator.ts +30 -0
- package/src/generators/types-generator.ts +216 -112
- package/src/generators/utils-generator.ts +2 -4
- package/src/index.ts +6 -3
- package/src/services/api-code-generator.ts +62 -74
- package/src/services/endpoint-info-extractor.ts +66 -14
- package/src/services/file-writer-service.ts +27 -23
- package/src/services/openapi-parser-service.ts +10 -2
- package/src/services/unified-code-generator.ts +55 -26
- package/src/types.ts +19 -40
- package/src/generators/api-service-generator.ts +0 -112
- package/src/generators/cache-keys-generator.ts +0 -43
- package/src/generators/index-generator.ts +0 -11
- package/src/services/api-service-generator.ts +0 -24
- package/src/services/query-code-generator.ts +0 -24
|
@@ -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?:
|
|
28
|
+
(void | {fetchOptions?: IRequestConfig;}):
|
|
25
29
|
{
|
|
26
30
|
variables: TVariables;
|
|
27
|
-
fetchOptions?:
|
|
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
|
-
//
|
|
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
|
-
${
|
|
24
|
-
|
|
25
|
-
}
|
|
54
|
+
// [Warning] Generated automatically - do not edit manually
|
|
55
|
+
|
|
56
|
+
${renamedInterfaces.join('\n')}
|
|
57
|
+
`;
|
|
58
|
+
}
|
|
@@ -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
|
+
summary: string;
|
|
16
|
+
contentType: string;
|
|
17
|
+
hasRequestBody: boolean;
|
|
18
|
+
tags: string[];
|
|
19
|
+
}>,
|
|
20
|
+
options: GenerationOptions
|
|
21
|
+
) {
|
|
22
|
+
const { apiConfiguration, httpClient, groupKey } = options;
|
|
23
|
+
|
|
24
|
+
// 確定服務名稱,使用業務名稱
|
|
25
|
+
const apiServiceClassName = groupKey
|
|
26
|
+
? `${groupKey.charAt(0).toUpperCase() + groupKey.slice(1)}ApiService`
|
|
27
|
+
: 'ApiService';
|
|
28
|
+
|
|
29
|
+
return `/* eslint-disable */
|
|
30
|
+
// [Warning] Generated automatically - do not edit manually
|
|
31
|
+
|
|
32
|
+
import { ${httpClient!.importReturnTypeName} } from '${httpClient!.file}';
|
|
33
|
+
import { Injectable, inject } from '@angular/core';
|
|
34
|
+
import { Observable } from 'rxjs';
|
|
35
|
+
import { ${apiConfiguration!.importName} } from '${apiConfiguration!.file}';
|
|
36
|
+
import { RequestOptions, IRestFulEndpointsQueryReturn } from '../common-types';
|
|
37
|
+
import { withoutUndefined } from '../utils';
|
|
38
|
+
import {${endpointInfos.map((info) => ` ${info.argTypeName}, ${info.responseTypeName}`).join(',')} } from './types';
|
|
39
|
+
|
|
40
|
+
@Injectable({
|
|
41
|
+
providedIn: 'root',
|
|
42
|
+
})
|
|
43
|
+
export class ${apiServiceClassName} {
|
|
44
|
+
private config = inject(${apiConfiguration!.importName});
|
|
45
|
+
private http = inject(${httpClient!.importReturnTypeName});
|
|
46
|
+
|
|
47
|
+
${endpointInfos
|
|
48
|
+
.map((info) => {
|
|
49
|
+
const isGet = info.verb.toUpperCase() === 'GET';
|
|
50
|
+
const hasArgs = info.argTypeName !== 'VoidApiArg';
|
|
51
|
+
const hasRequestBody = info.hasRequestBody;
|
|
52
|
+
|
|
53
|
+
// 生成 URL 模板字面量,直接替換路徑參數
|
|
54
|
+
const generateUrlTemplate = (path: string) => {
|
|
55
|
+
const hasPathParams = path.includes('{');
|
|
56
|
+
if (hasPathParams && hasArgs) {
|
|
57
|
+
// 將 {paramName} 替換為 ${args.variables.paramName}
|
|
58
|
+
const urlTemplate = path.replace(/\{([^}]+)\}/g, '${args.variables.$1}');
|
|
59
|
+
return `\`\${this.config.rootUrl}${urlTemplate}\``;
|
|
60
|
+
} else {
|
|
61
|
+
return `\`\${this.config.rootUrl}${path}\``;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const hasQueryParams = info.queryParams.length > 0;
|
|
66
|
+
const hasBody = !isGet && hasArgs;
|
|
67
|
+
|
|
68
|
+
// 生成 HTTP 請求選項
|
|
69
|
+
const generateRequestOptions = () => {
|
|
70
|
+
// 處理 query parameters
|
|
71
|
+
let paramsSection = '';
|
|
72
|
+
if (info.queryParams && info.queryParams.length > 0) {
|
|
73
|
+
const paramsLines = info.queryParams
|
|
74
|
+
.map((param: any) => `${param.name}: args.variables.${param.name},`)
|
|
75
|
+
.join('\n');
|
|
76
|
+
paramsSection = `params: {${paramsLines}},`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return `{
|
|
80
|
+
...args.fetchOptions,
|
|
81
|
+
headers: { 'Content-Type': '${info.contentType}', ...args.fetchOptions?.headers },
|
|
82
|
+
${paramsSection}${info.hasRequestBody ? `body: args.variables.body,` : ''}
|
|
83
|
+
}`;
|
|
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
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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(
|
|
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
|
-
}`
|
|
48
|
+
}`
|
|
49
|
+
)
|
|
50
|
+
.join('');
|
|
45
51
|
|
|
46
52
|
const mutationMethods = endpointInfos
|
|
47
|
-
.filter(info => !info.isQuery)
|
|
48
|
-
.map(
|
|
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
|
-
}`
|
|
69
|
+
}`
|
|
70
|
+
)
|
|
71
|
+
.join('');
|
|
63
72
|
|
|
64
73
|
const lazyQueryMethods = endpointInfos
|
|
65
|
-
.filter(info => info.isQuery)
|
|
66
|
-
.map(
|
|
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
|
-
}`
|
|
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
|
+
}
|