@7nohe/openapi-react-query-codegen 0.0.0-1712acfb083e0b1eec43cfc6f917f780ae5b5815
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 +300 -0
- package/dist/cli.mjs +43 -0
- package/dist/common.mjs +137 -0
- package/dist/constants.mjs +13 -0
- package/dist/createExports.mjs +73 -0
- package/dist/createImports.mjs +43 -0
- package/dist/createPrefetch.mjs +53 -0
- package/dist/createSource.mjs +93 -0
- package/dist/createUseMutation.mjs +76 -0
- package/dist/createUseQuery.mjs +268 -0
- package/dist/format.mjs +21 -0
- package/dist/generate.mjs +48 -0
- package/dist/print.mjs +22 -0
- package/dist/service.mjs +79 -0
- package/dist/util.mjs +16 -0
- package/package.json +67 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { Project } from "ts-morph";
|
|
3
|
+
import ts from "typescript";
|
|
4
|
+
import { OpenApiRqFiles } from "./constants.mjs";
|
|
5
|
+
import { createExports } from "./createExports.mjs";
|
|
6
|
+
import { createImports } from "./createImports.mjs";
|
|
7
|
+
import { getServices } from "./service.mjs";
|
|
8
|
+
const createSourceFile = async (outputPath, serviceEndName, pageParam, nextPageParam) => {
|
|
9
|
+
const project = new Project({
|
|
10
|
+
// Optionally specify compiler options, tsconfig.json, in-memory file system, and more here.
|
|
11
|
+
// If you initialize with a tsconfig.json, then it will automatically populate the project
|
|
12
|
+
// with the associated source files.
|
|
13
|
+
// Read more: https://ts-morph.com/setup/
|
|
14
|
+
skipAddingFilesFromTsConfig: true,
|
|
15
|
+
});
|
|
16
|
+
const sourceFiles = join(process.cwd(), outputPath);
|
|
17
|
+
project.addSourceFilesAtPaths(`${sourceFiles}/**/*`);
|
|
18
|
+
const service = await getServices(project);
|
|
19
|
+
const imports = createImports({
|
|
20
|
+
serviceEndName,
|
|
21
|
+
project,
|
|
22
|
+
});
|
|
23
|
+
const exports = createExports(service, pageParam, nextPageParam);
|
|
24
|
+
const commonSource = ts.factory.createSourceFile([...imports, ...exports.allCommon], ts.factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None);
|
|
25
|
+
const commonImport = ts.factory.createImportDeclaration(undefined, ts.factory.createImportClause(false, ts.factory.createIdentifier("* as Common"), undefined), ts.factory.createStringLiteral(`./${OpenApiRqFiles.common}`), undefined);
|
|
26
|
+
const commonExport = ts.factory.createExportDeclaration(undefined, false, undefined, ts.factory.createStringLiteral(`./${OpenApiRqFiles.common}`), undefined);
|
|
27
|
+
const queriesExport = ts.factory.createExportDeclaration(undefined, false, undefined, ts.factory.createStringLiteral(`./${OpenApiRqFiles.queries}`), undefined);
|
|
28
|
+
const mainSource = ts.factory.createSourceFile([commonImport, ...imports, ...exports.mainExports], ts.factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None);
|
|
29
|
+
const infiniteQueriesSource = ts.factory.createSourceFile([commonImport, ...imports, ...exports.infiniteQueriesExports], ts.factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None);
|
|
30
|
+
const suspenseSource = ts.factory.createSourceFile([commonImport, ...imports, ...exports.suspenseExports], ts.factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None);
|
|
31
|
+
const indexSource = ts.factory.createSourceFile([commonExport, queriesExport], ts.factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None);
|
|
32
|
+
const prefetchSource = ts.factory.createSourceFile([commonImport, ...imports, ...exports.allPrefetchExports], ts.factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None);
|
|
33
|
+
return {
|
|
34
|
+
commonSource,
|
|
35
|
+
infiniteQueriesSource,
|
|
36
|
+
mainSource,
|
|
37
|
+
suspenseSource,
|
|
38
|
+
indexSource,
|
|
39
|
+
prefetchSource,
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
export const createSource = async ({ outputPath, version, serviceEndName, pageParam, nextPageParam, }) => {
|
|
43
|
+
const queriesFile = ts.createSourceFile(`${OpenApiRqFiles.queries}.ts`, "", ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
|
|
44
|
+
const infiniteQueriesFile = ts.createSourceFile(`${OpenApiRqFiles.infiniteQueries}.ts`, "", ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
|
|
45
|
+
const commonFile = ts.createSourceFile(`${OpenApiRqFiles.common}.ts`, "", ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
|
|
46
|
+
const suspenseFile = ts.createSourceFile(`${OpenApiRqFiles.suspense}.ts`, "", ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
|
|
47
|
+
const indexFile = ts.createSourceFile(`${OpenApiRqFiles.index}.ts`, "", ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
|
|
48
|
+
const prefetchFile = ts.createSourceFile(`${OpenApiRqFiles.prefetch}.ts`, "", ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
|
|
49
|
+
const printer = ts.createPrinter({
|
|
50
|
+
newLine: ts.NewLineKind.LineFeed,
|
|
51
|
+
removeComments: false,
|
|
52
|
+
});
|
|
53
|
+
const { commonSource, mainSource, infiniteQueriesSource, suspenseSource, indexSource, prefetchSource, } = await createSourceFile(outputPath, serviceEndName, pageParam, nextPageParam);
|
|
54
|
+
const comment = `// generated with @7nohe/openapi-react-query-codegen@${version} \n\n`;
|
|
55
|
+
const commonResult = comment +
|
|
56
|
+
printer.printNode(ts.EmitHint.Unspecified, commonSource, commonFile);
|
|
57
|
+
const mainResult = comment +
|
|
58
|
+
printer.printNode(ts.EmitHint.Unspecified, mainSource, queriesFile);
|
|
59
|
+
const infiniteQueriesResult = comment +
|
|
60
|
+
printer.printNode(ts.EmitHint.Unspecified, infiniteQueriesSource, infiniteQueriesFile);
|
|
61
|
+
const suspenseResult = comment +
|
|
62
|
+
printer.printNode(ts.EmitHint.Unspecified, suspenseSource, suspenseFile);
|
|
63
|
+
const indexResult = comment +
|
|
64
|
+
printer.printNode(ts.EmitHint.Unspecified, indexSource, indexFile);
|
|
65
|
+
const prefetchResult = comment +
|
|
66
|
+
printer.printNode(ts.EmitHint.Unspecified, prefetchSource, prefetchFile);
|
|
67
|
+
return [
|
|
68
|
+
{
|
|
69
|
+
name: `${OpenApiRqFiles.index}.ts`,
|
|
70
|
+
content: indexResult,
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: `${OpenApiRqFiles.common}.ts`,
|
|
74
|
+
content: commonResult,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: `${OpenApiRqFiles.infiniteQueries}.ts`,
|
|
78
|
+
content: infiniteQueriesResult,
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: `${OpenApiRqFiles.queries}.ts`,
|
|
82
|
+
content: mainResult,
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: `${OpenApiRqFiles.suspense}.ts`,
|
|
86
|
+
content: suspenseResult,
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: `${OpenApiRqFiles.prefetch}.ts`,
|
|
90
|
+
content: prefetchResult,
|
|
91
|
+
},
|
|
92
|
+
];
|
|
93
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import { BuildCommonTypeName, TContext, TData, TError, capitalizeFirstLetter, extractPropertiesFromObjectParam, getNameFromMethod, getShortType, } from "./common.mjs";
|
|
3
|
+
import { addJSDocToNode } from "./util.mjs";
|
|
4
|
+
/**
|
|
5
|
+
* Awaited<ReturnType<typeof myClass.myMethod>>
|
|
6
|
+
*/
|
|
7
|
+
function generateAwaitedReturnType({ className, methodName, }) {
|
|
8
|
+
return ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("Awaited"), [
|
|
9
|
+
ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("ReturnType"), [
|
|
10
|
+
ts.factory.createTypeQueryNode(ts.factory.createQualifiedName(ts.factory.createIdentifier(className), ts.factory.createIdentifier(methodName)), undefined),
|
|
11
|
+
]),
|
|
12
|
+
]);
|
|
13
|
+
}
|
|
14
|
+
export const createUseMutation = ({ className, method, jsDoc, }) => {
|
|
15
|
+
const methodName = getNameFromMethod(method);
|
|
16
|
+
const awaitedResponseDataType = generateAwaitedReturnType({
|
|
17
|
+
className,
|
|
18
|
+
methodName,
|
|
19
|
+
});
|
|
20
|
+
const mutationResult = ts.factory.createTypeAliasDeclaration([ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], ts.factory.createIdentifier(`${className}${capitalizeFirstLetter(methodName)}MutationResult`), undefined, awaitedResponseDataType);
|
|
21
|
+
const responseDataType = ts.factory.createTypeParameterDeclaration(undefined, TData, undefined, ts.factory.createTypeReferenceNode(BuildCommonTypeName(mutationResult.name)));
|
|
22
|
+
const methodParameters = method.getParameters().length !== 0
|
|
23
|
+
? ts.factory.createTypeLiteralNode(method.getParameters().flatMap((param) => {
|
|
24
|
+
const paramNodes = extractPropertiesFromObjectParam(param);
|
|
25
|
+
return paramNodes.map((refParam) => ts.factory.createPropertySignature(undefined, ts.factory.createIdentifier(refParam.name), refParam.optional
|
|
26
|
+
? ts.factory.createToken(ts.SyntaxKind.QuestionToken)
|
|
27
|
+
: undefined, ts.factory.createTypeReferenceNode(getShortType(refParam.type.getText(param)))));
|
|
28
|
+
}))
|
|
29
|
+
: ts.factory.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword);
|
|
30
|
+
const exportHook = ts.factory.createVariableStatement([ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], ts.factory.createVariableDeclarationList([
|
|
31
|
+
ts.factory.createVariableDeclaration(ts.factory.createIdentifier(`use${className}${capitalizeFirstLetter(methodName)}`), undefined, undefined, ts.factory.createArrowFunction(undefined, ts.factory.createNodeArray([
|
|
32
|
+
responseDataType,
|
|
33
|
+
ts.factory.createTypeParameterDeclaration(undefined, TError, undefined, ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword)),
|
|
34
|
+
ts.factory.createTypeParameterDeclaration(undefined, TContext, undefined, ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword)),
|
|
35
|
+
]), [
|
|
36
|
+
ts.factory.createParameterDeclaration(undefined, undefined, ts.factory.createIdentifier("options"), ts.factory.createToken(ts.SyntaxKind.QuestionToken), ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("Omit"), [
|
|
37
|
+
ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("UseMutationOptions"), [
|
|
38
|
+
ts.factory.createTypeReferenceNode(TData),
|
|
39
|
+
ts.factory.createTypeReferenceNode(TError),
|
|
40
|
+
methodParameters,
|
|
41
|
+
ts.factory.createTypeReferenceNode(TContext),
|
|
42
|
+
]),
|
|
43
|
+
ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral("mutationFn")),
|
|
44
|
+
]), undefined),
|
|
45
|
+
], undefined, ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), ts.factory.createCallExpression(ts.factory.createIdentifier("useMutation"), [
|
|
46
|
+
ts.factory.createTypeReferenceNode(TData),
|
|
47
|
+
ts.factory.createTypeReferenceNode(TError),
|
|
48
|
+
methodParameters,
|
|
49
|
+
ts.factory.createTypeReferenceNode(TContext),
|
|
50
|
+
], [
|
|
51
|
+
ts.factory.createObjectLiteralExpression([
|
|
52
|
+
ts.factory.createPropertyAssignment(ts.factory.createIdentifier("mutationFn"), ts.factory.createArrowFunction(undefined, undefined, method.getParameters().length !== 0
|
|
53
|
+
? [
|
|
54
|
+
ts.factory.createParameterDeclaration(undefined, undefined, ts.factory.createObjectBindingPattern(method.getParameters().flatMap((param) => {
|
|
55
|
+
const paramNodes = extractPropertiesFromObjectParam(param);
|
|
56
|
+
return paramNodes.map((refParam) => ts.factory.createBindingElement(undefined, undefined, ts.factory.createIdentifier(refParam.name), undefined));
|
|
57
|
+
})), undefined, undefined, undefined),
|
|
58
|
+
]
|
|
59
|
+
: [], undefined, ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), ts.factory.createAsExpression(ts.factory.createAsExpression(ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(className), ts.factory.createIdentifier(methodName)), undefined, method.getParameters().length !== 0
|
|
60
|
+
? [
|
|
61
|
+
ts.factory.createObjectLiteralExpression(method.getParameters().flatMap((params) => {
|
|
62
|
+
const paramNodes = extractPropertiesFromObjectParam(params);
|
|
63
|
+
return paramNodes.map((refParam) => ts.factory.createShorthandPropertyAssignment(refParam.name));
|
|
64
|
+
})),
|
|
65
|
+
]
|
|
66
|
+
: []), ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword)), ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("Promise"), [ts.factory.createTypeReferenceNode(TData)])))),
|
|
67
|
+
ts.factory.createSpreadAssignment(ts.factory.createIdentifier("options")),
|
|
68
|
+
]),
|
|
69
|
+
]))),
|
|
70
|
+
], ts.NodeFlags.Const));
|
|
71
|
+
const hookWithJsDoc = addJSDocToNode(exportHook, jsDoc);
|
|
72
|
+
return {
|
|
73
|
+
mutationResult,
|
|
74
|
+
mutationHook: hookWithJsDoc,
|
|
75
|
+
};
|
|
76
|
+
};
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import { BuildCommonTypeName, EqualsOrGreaterThanToken, QuestionToken, TData, TError, capitalizeFirstLetter, extractPropertiesFromObjectParam, getNameFromMethod, getShortType, queryKeyConstraint, queryKeyGenericType, } from "./common.mjs";
|
|
3
|
+
import { addJSDocToNode } from "./util.mjs";
|
|
4
|
+
export const createApiResponseType = ({ className, methodName, }) => {
|
|
5
|
+
/** Awaited<ReturnType<typeof myClass.myMethod>> */
|
|
6
|
+
const awaitedResponseDataType = ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("Awaited"), [
|
|
7
|
+
ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("ReturnType"), [
|
|
8
|
+
ts.factory.createTypeQueryNode(ts.factory.createQualifiedName(ts.factory.createIdentifier(className), ts.factory.createIdentifier(methodName)), undefined),
|
|
9
|
+
]),
|
|
10
|
+
]);
|
|
11
|
+
/** DefaultResponseDataType
|
|
12
|
+
* export type MyClassMethodDefaultResponse = Awaited<ReturnType<typeof myClass.myMethod>>
|
|
13
|
+
*/
|
|
14
|
+
const apiResponse = ts.factory.createTypeAliasDeclaration([ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], ts.factory.createIdentifier(`${capitalizeFirstLetter(className)}${capitalizeFirstLetter(methodName)}DefaultResponse`), undefined, awaitedResponseDataType);
|
|
15
|
+
const responseDataType = ts.factory.createTypeParameterDeclaration(undefined, TData.text, undefined, ts.factory.createTypeReferenceNode(BuildCommonTypeName(apiResponse.name)));
|
|
16
|
+
return {
|
|
17
|
+
/**
|
|
18
|
+
* DefaultResponseDataType
|
|
19
|
+
*
|
|
20
|
+
* export type MyClassMethodDefaultResponse = Awaited<ReturnType<typeof myClass.myMethod>>
|
|
21
|
+
*/
|
|
22
|
+
apiResponse,
|
|
23
|
+
/**
|
|
24
|
+
* This will be the name of the type of the response type of the method
|
|
25
|
+
*
|
|
26
|
+
* MyClassMethodDefaultResponse
|
|
27
|
+
*/
|
|
28
|
+
responseDataType,
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
export function getRequestParamFromMethod(method, pageParam) {
|
|
32
|
+
if (!method.getParameters().length) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
const params = method.getParameters().flatMap((param) => {
|
|
36
|
+
const paramNodes = extractPropertiesFromObjectParam(param);
|
|
37
|
+
return paramNodes
|
|
38
|
+
.filter((p) => p.name !== pageParam)
|
|
39
|
+
.map((refParam) => ({
|
|
40
|
+
name: refParam.name,
|
|
41
|
+
typeName: getShortType(refParam.type.getText()),
|
|
42
|
+
optional: refParam.optional,
|
|
43
|
+
}));
|
|
44
|
+
});
|
|
45
|
+
const areAllPropertiesOptional = params.every((param) => param.optional);
|
|
46
|
+
return ts.factory.createParameterDeclaration(undefined, undefined, ts.factory.createObjectBindingPattern(params.map((refParam) => ts.factory.createBindingElement(undefined, undefined, ts.factory.createIdentifier(refParam.name), undefined))), undefined, ts.factory.createTypeLiteralNode(params.map((refParam) => {
|
|
47
|
+
return ts.factory.createPropertySignature(undefined, ts.factory.createIdentifier(refParam.name), refParam.optional
|
|
48
|
+
? ts.factory.createToken(ts.SyntaxKind.QuestionToken)
|
|
49
|
+
: undefined, ts.factory.createTypeReferenceNode(refParam.typeName));
|
|
50
|
+
})),
|
|
51
|
+
// if all params are optional, we create an empty object literal
|
|
52
|
+
// so the hook can be called without any parameters
|
|
53
|
+
areAllPropertiesOptional
|
|
54
|
+
? ts.factory.createObjectLiteralExpression()
|
|
55
|
+
: undefined);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Return Type
|
|
59
|
+
*
|
|
60
|
+
* export const classNameMethodNameQueryResult<TData = MyClassMethodDefaultResponse, TError = unknown> = UseQueryResult<TData, TError>;
|
|
61
|
+
*/
|
|
62
|
+
export function createReturnTypeExport({ className, methodName, defaultApiResponse, }) {
|
|
63
|
+
return ts.factory.createTypeAliasDeclaration([ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], ts.factory.createIdentifier(`${capitalizeFirstLetter(className)}${capitalizeFirstLetter(methodName)}QueryResult`), [
|
|
64
|
+
ts.factory.createTypeParameterDeclaration(undefined, TData, undefined, ts.factory.createTypeReferenceNode(defaultApiResponse.name)),
|
|
65
|
+
ts.factory.createTypeParameterDeclaration(undefined, TError, undefined, ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword)),
|
|
66
|
+
], ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("UseQueryResult"), [
|
|
67
|
+
ts.factory.createTypeReferenceNode(TData),
|
|
68
|
+
ts.factory.createTypeReferenceNode(TError),
|
|
69
|
+
]));
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* QueryKey
|
|
73
|
+
*/
|
|
74
|
+
export function createQueryKeyExport({ className, methodName, queryKey, }) {
|
|
75
|
+
return ts.factory.createVariableStatement([ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], ts.factory.createVariableDeclarationList([
|
|
76
|
+
ts.factory.createVariableDeclaration(ts.factory.createIdentifier(queryKey), undefined, undefined, ts.factory.createStringLiteral(`${className}${capitalizeFirstLetter(methodName)}`)),
|
|
77
|
+
], ts.NodeFlags.Const));
|
|
78
|
+
}
|
|
79
|
+
export function hookNameFromMethod({ method, className, }) {
|
|
80
|
+
const methodName = getNameFromMethod(method);
|
|
81
|
+
return `use${className}${capitalizeFirstLetter(methodName)}`;
|
|
82
|
+
}
|
|
83
|
+
export function createQueryKeyFromMethod({ method, className, }) {
|
|
84
|
+
const customHookName = hookNameFromMethod({ method, className });
|
|
85
|
+
const queryKey = `${customHookName}Key`;
|
|
86
|
+
return queryKey;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Creates a custom hook for a query
|
|
90
|
+
* @param queryString The type of query to use from react-query
|
|
91
|
+
* @param suffix The suffix to append to the hook name
|
|
92
|
+
*/
|
|
93
|
+
export function createQueryHook({ queryString, suffix, responseDataType, requestParams, method, className, pageParam, nextPageParam, }) {
|
|
94
|
+
const methodName = getNameFromMethod(method);
|
|
95
|
+
const customHookName = hookNameFromMethod({ method, className });
|
|
96
|
+
const queryKey = createQueryKeyFromMethod({ method, className });
|
|
97
|
+
const isInfiniteQuery = pageParam !== undefined && nextPageParam !== undefined;
|
|
98
|
+
const responseDataTypeRef = responseDataType.default;
|
|
99
|
+
const responseDataTypeIdentifier = responseDataTypeRef.typeName;
|
|
100
|
+
const hookExport = ts.factory.createVariableStatement([ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], ts.factory.createVariableDeclarationList([
|
|
101
|
+
ts.factory.createVariableDeclaration(ts.factory.createIdentifier(`${customHookName}${suffix}`), undefined, undefined, ts.factory.createArrowFunction(undefined, ts.factory.createNodeArray([
|
|
102
|
+
isInfiniteQuery
|
|
103
|
+
? ts.factory.createTypeParameterDeclaration(undefined, TData, undefined, ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("InfiniteData"), [
|
|
104
|
+
ts.factory.createTypeReferenceNode(responseDataTypeIdentifier),
|
|
105
|
+
]))
|
|
106
|
+
: responseDataType,
|
|
107
|
+
ts.factory.createTypeParameterDeclaration(undefined, TError, undefined, ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword)),
|
|
108
|
+
ts.factory.createTypeParameterDeclaration(undefined, "TQueryKey", queryKeyConstraint, ts.factory.createArrayTypeNode(ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword))),
|
|
109
|
+
]), [
|
|
110
|
+
...requestParams,
|
|
111
|
+
ts.factory.createParameterDeclaration(undefined, undefined, ts.factory.createIdentifier("queryKey"), ts.factory.createToken(ts.SyntaxKind.QuestionToken), queryKeyGenericType),
|
|
112
|
+
ts.factory.createParameterDeclaration(undefined, undefined, ts.factory.createIdentifier("options"), ts.factory.createToken(ts.SyntaxKind.QuestionToken), ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("Omit"), [
|
|
113
|
+
ts.factory.createTypeReferenceNode(ts.factory.createIdentifier(isInfiniteQuery
|
|
114
|
+
? "UseInfiniteQueryOptions"
|
|
115
|
+
: "UseQueryOptions"), [
|
|
116
|
+
ts.factory.createTypeReferenceNode(TData),
|
|
117
|
+
ts.factory.createTypeReferenceNode(TError),
|
|
118
|
+
]),
|
|
119
|
+
ts.factory.createUnionTypeNode([
|
|
120
|
+
ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral("queryKey")),
|
|
121
|
+
ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral("queryFn")),
|
|
122
|
+
]),
|
|
123
|
+
])),
|
|
124
|
+
], undefined, EqualsOrGreaterThanToken, ts.factory.createCallExpression(ts.factory.createIdentifier(queryString), isInfiniteQuery
|
|
125
|
+
? []
|
|
126
|
+
: [
|
|
127
|
+
ts.factory.createTypeReferenceNode(TData),
|
|
128
|
+
ts.factory.createTypeReferenceNode(TError),
|
|
129
|
+
], [
|
|
130
|
+
ts.factory.createObjectLiteralExpression([
|
|
131
|
+
ts.factory.createPropertyAssignment(ts.factory.createIdentifier("queryKey"), ts.factory.createCallExpression(BuildCommonTypeName(getQueryKeyFnName(queryKey)), undefined, method.getParameters().length
|
|
132
|
+
? [
|
|
133
|
+
ts.factory.createObjectLiteralExpression(method.getParameters().flatMap((param) => extractPropertiesFromObjectParam(param)
|
|
134
|
+
.filter((p) => p.name !== pageParam)
|
|
135
|
+
.map((p) => ts.factory.createShorthandPropertyAssignment(ts.factory.createIdentifier(p.name))))),
|
|
136
|
+
ts.factory.createIdentifier("queryKey"),
|
|
137
|
+
]
|
|
138
|
+
: [ts.factory.createIdentifier("queryKey")])),
|
|
139
|
+
ts.factory.createPropertyAssignment(ts.factory.createIdentifier("queryFn"), ts.factory.createArrowFunction(undefined, undefined, isInfiniteQuery
|
|
140
|
+
? [
|
|
141
|
+
ts.factory.createParameterDeclaration(undefined, undefined, ts.factory.createObjectBindingPattern([
|
|
142
|
+
ts.factory.createBindingElement(undefined, undefined, ts.factory.createIdentifier("pageParam"), undefined),
|
|
143
|
+
]), undefined, undefined),
|
|
144
|
+
]
|
|
145
|
+
: [], undefined, EqualsOrGreaterThanToken, ts.factory.createAsExpression(ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(className), ts.factory.createIdentifier(methodName)), undefined, method.getParameters().length
|
|
146
|
+
? [
|
|
147
|
+
ts.factory.createObjectLiteralExpression(method
|
|
148
|
+
.getParameters()
|
|
149
|
+
.flatMap((param) => extractPropertiesFromObjectParam(param).map((p) => p.name === pageParam
|
|
150
|
+
? ts.factory.createPropertyAssignment(ts.factory.createIdentifier(p.name), ts.factory.createAsExpression(ts.factory.createIdentifier("pageParam"), ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword)))
|
|
151
|
+
: ts.factory.createShorthandPropertyAssignment(ts.factory.createIdentifier(p.name))))),
|
|
152
|
+
]
|
|
153
|
+
: undefined), ts.factory.createTypeReferenceNode(TData)))),
|
|
154
|
+
...createInfiniteQueryParams(pageParam, nextPageParam),
|
|
155
|
+
ts.factory.createSpreadAssignment(ts.factory.createIdentifier("options")),
|
|
156
|
+
]),
|
|
157
|
+
]))),
|
|
158
|
+
], ts.NodeFlags.Const));
|
|
159
|
+
return hookExport;
|
|
160
|
+
}
|
|
161
|
+
export const createUseQuery = ({ className, method, jsDoc }, pageParam, nextPageParam) => {
|
|
162
|
+
const methodName = getNameFromMethod(method);
|
|
163
|
+
const queryKey = createQueryKeyFromMethod({ method, className });
|
|
164
|
+
const { apiResponse: defaultApiResponse, responseDataType } = createApiResponseType({
|
|
165
|
+
className,
|
|
166
|
+
methodName,
|
|
167
|
+
});
|
|
168
|
+
const requestParam = getRequestParamFromMethod(method);
|
|
169
|
+
const infiniteRequestParam = getRequestParamFromMethod(method, pageParam);
|
|
170
|
+
const requestParams = requestParam ? [requestParam] : [];
|
|
171
|
+
const requestParamNames = requestParams
|
|
172
|
+
.filter((p) => p.name.kind === ts.SyntaxKind.ObjectBindingPattern)
|
|
173
|
+
.map((p) => p.name);
|
|
174
|
+
const requestParamTexts = requestParamNames
|
|
175
|
+
.at(0)
|
|
176
|
+
?.elements.filter((e) => e.name.kind === ts.SyntaxKind.Identifier)
|
|
177
|
+
.map((e) => e.name.escapedText);
|
|
178
|
+
const queryHook = createQueryHook({
|
|
179
|
+
queryString: "useQuery",
|
|
180
|
+
suffix: "",
|
|
181
|
+
responseDataType,
|
|
182
|
+
requestParams,
|
|
183
|
+
method,
|
|
184
|
+
className,
|
|
185
|
+
});
|
|
186
|
+
const suspenseQueryHook = createQueryHook({
|
|
187
|
+
queryString: "useSuspenseQuery",
|
|
188
|
+
suffix: "Suspense",
|
|
189
|
+
responseDataType,
|
|
190
|
+
requestParams,
|
|
191
|
+
method,
|
|
192
|
+
className,
|
|
193
|
+
});
|
|
194
|
+
const isInfiniteQuery = requestParamTexts?.includes(pageParam) ?? false;
|
|
195
|
+
const infiniteQueryHook = isInfiniteQuery
|
|
196
|
+
? createQueryHook({
|
|
197
|
+
queryString: "useInfiniteQuery",
|
|
198
|
+
suffix: "Infinite",
|
|
199
|
+
responseDataType,
|
|
200
|
+
requestParams: infiniteRequestParam ? [infiniteRequestParam] : [],
|
|
201
|
+
method,
|
|
202
|
+
className,
|
|
203
|
+
pageParam,
|
|
204
|
+
nextPageParam,
|
|
205
|
+
})
|
|
206
|
+
: undefined;
|
|
207
|
+
const hookWithJsDoc = addJSDocToNode(queryHook, jsDoc);
|
|
208
|
+
const suspenseHookWithJsDoc = addJSDocToNode(suspenseQueryHook, jsDoc);
|
|
209
|
+
const infiniteHookWithJsDoc = infiniteQueryHook
|
|
210
|
+
? addJSDocToNode(infiniteQueryHook, jsDoc)
|
|
211
|
+
: undefined;
|
|
212
|
+
const returnTypeExport = createReturnTypeExport({
|
|
213
|
+
className,
|
|
214
|
+
methodName,
|
|
215
|
+
defaultApiResponse,
|
|
216
|
+
});
|
|
217
|
+
const queryKeyExport = createQueryKeyExport({
|
|
218
|
+
className,
|
|
219
|
+
methodName,
|
|
220
|
+
queryKey,
|
|
221
|
+
});
|
|
222
|
+
const queryKeyFn = createQueryKeyFnExport(queryKey, method);
|
|
223
|
+
return {
|
|
224
|
+
apiResponse: defaultApiResponse,
|
|
225
|
+
returnType: returnTypeExport,
|
|
226
|
+
key: queryKeyExport,
|
|
227
|
+
queryHook: hookWithJsDoc,
|
|
228
|
+
suspenseQueryHook: suspenseHookWithJsDoc,
|
|
229
|
+
infiniteQueryHook: infiniteHookWithJsDoc,
|
|
230
|
+
queryKeyFn,
|
|
231
|
+
};
|
|
232
|
+
};
|
|
233
|
+
export function getQueryKeyFnName(queryKey) {
|
|
234
|
+
return `${capitalizeFirstLetter(queryKey)}Fn`;
|
|
235
|
+
}
|
|
236
|
+
function createQueryKeyFnExport(queryKey, method) {
|
|
237
|
+
const params = getRequestParamFromMethod(method);
|
|
238
|
+
// override key is used to allow the user to override the the queryKey values
|
|
239
|
+
const overrideKey = ts.factory.createParameterDeclaration(undefined, undefined, ts.factory.createIdentifier("queryKey"), QuestionToken, ts.factory.createTypeReferenceNode("Array<unknown>", []));
|
|
240
|
+
return ts.factory.createVariableStatement([ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], ts.factory.createVariableDeclarationList([
|
|
241
|
+
ts.factory.createVariableDeclaration(ts.factory.createIdentifier(getQueryKeyFnName(queryKey)), undefined, undefined, ts.factory.createArrowFunction(undefined, undefined, params ? [params, overrideKey] : [overrideKey], undefined, EqualsOrGreaterThanToken, queryKeyFn(queryKey, method))),
|
|
242
|
+
], ts.NodeFlags.Const));
|
|
243
|
+
}
|
|
244
|
+
function queryKeyFn(queryKey, method) {
|
|
245
|
+
return ts.factory.createArrayLiteralExpression([
|
|
246
|
+
ts.factory.createIdentifier(queryKey),
|
|
247
|
+
ts.factory.createSpreadElement(ts.factory.createParenthesizedExpression(ts.factory.createBinaryExpression(ts.factory.createIdentifier("queryKey"), ts.factory.createToken(ts.SyntaxKind.QuestionQuestionToken), method.getParameters().length
|
|
248
|
+
? ts.factory.createArrayLiteralExpression([
|
|
249
|
+
ts.factory.createObjectLiteralExpression(method
|
|
250
|
+
.getParameters()
|
|
251
|
+
.flatMap((param) => extractPropertiesFromObjectParam(param).map((p) => ts.factory.createShorthandPropertyAssignment(ts.factory.createIdentifier(p.name))))),
|
|
252
|
+
])
|
|
253
|
+
: ts.factory.createArrayLiteralExpression([])))),
|
|
254
|
+
], false);
|
|
255
|
+
}
|
|
256
|
+
function createInfiniteQueryParams(pageParam, nextPageParam) {
|
|
257
|
+
if (pageParam === undefined || nextPageParam === undefined) {
|
|
258
|
+
return [];
|
|
259
|
+
}
|
|
260
|
+
return [
|
|
261
|
+
ts.factory.createPropertyAssignment(ts.factory.createIdentifier("initialPageParam"), ts.factory.createNumericLiteral(1)),
|
|
262
|
+
ts.factory.createPropertyAssignment(ts.factory.createIdentifier("getNextPageParam"),
|
|
263
|
+
// (response) => (response as { nextPage: number }).nextPage,
|
|
264
|
+
ts.factory.createArrowFunction(undefined, undefined, [
|
|
265
|
+
ts.factory.createParameterDeclaration(undefined, undefined, ts.factory.createIdentifier("response"), undefined, undefined),
|
|
266
|
+
], undefined, EqualsOrGreaterThanToken, ts.factory.createPropertyAccessExpression(ts.factory.createParenthesizedExpression(ts.factory.createAsExpression(ts.factory.createIdentifier("response"), ts.factory.createTypeReferenceNode(ts.factory.createIdentifier(`{ ${nextPageParam}: number }`)))), ts.factory.createIdentifier(nextPageParam)))),
|
|
267
|
+
];
|
|
268
|
+
}
|
package/dist/format.mjs
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { IndentationText, NewLineKind, Project, QuoteKind } from "ts-morph";
|
|
2
|
+
export const formatOutput = async (outputPath) => {
|
|
3
|
+
const project = new Project({
|
|
4
|
+
skipAddingFilesFromTsConfig: true,
|
|
5
|
+
manipulationSettings: {
|
|
6
|
+
indentationText: IndentationText.TwoSpaces,
|
|
7
|
+
newLineKind: NewLineKind.LineFeed,
|
|
8
|
+
quoteKind: QuoteKind.Double,
|
|
9
|
+
usePrefixAndSuffixTextForRename: false,
|
|
10
|
+
useTrailingCommas: true,
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
const sourceFiles = project.addSourceFilesAtPaths(`${outputPath}/**/*`);
|
|
14
|
+
const tasks = sourceFiles.map((sourceFile) => {
|
|
15
|
+
sourceFile.formatText();
|
|
16
|
+
sourceFile.fixMissingImports();
|
|
17
|
+
sourceFile.organizeImports();
|
|
18
|
+
return sourceFile.save();
|
|
19
|
+
});
|
|
20
|
+
await Promise.all(tasks);
|
|
21
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { createClient } from "@hey-api/openapi-ts";
|
|
2
|
+
import { buildQueriesOutputPath, buildRequestsOutputPath, formatOptions, } from "./common.mjs";
|
|
3
|
+
import { createSource } from "./createSource.mjs";
|
|
4
|
+
import { formatOutput } from "./format.mjs";
|
|
5
|
+
import { print } from "./print.mjs";
|
|
6
|
+
export async function generate(options, version) {
|
|
7
|
+
const openApiOutputPath = buildRequestsOutputPath(options.output);
|
|
8
|
+
const formattedOptions = formatOptions(options);
|
|
9
|
+
const config = {
|
|
10
|
+
base: formattedOptions.base,
|
|
11
|
+
client: formattedOptions.client,
|
|
12
|
+
debug: formattedOptions.debug,
|
|
13
|
+
dryRun: false,
|
|
14
|
+
exportCore: true,
|
|
15
|
+
output: {
|
|
16
|
+
format: formattedOptions.format,
|
|
17
|
+
lint: formattedOptions.lint,
|
|
18
|
+
path: openApiOutputPath,
|
|
19
|
+
},
|
|
20
|
+
input: formattedOptions.input,
|
|
21
|
+
request: formattedOptions.request,
|
|
22
|
+
schemas: {
|
|
23
|
+
export: !formattedOptions.noSchemas,
|
|
24
|
+
type: formattedOptions.schemaType,
|
|
25
|
+
},
|
|
26
|
+
services: {
|
|
27
|
+
export: true,
|
|
28
|
+
response: formattedOptions.serviceResponse,
|
|
29
|
+
},
|
|
30
|
+
types: {
|
|
31
|
+
dates: formattedOptions.useDateType,
|
|
32
|
+
export: true,
|
|
33
|
+
enums: formattedOptions.enums,
|
|
34
|
+
},
|
|
35
|
+
useOptions: true,
|
|
36
|
+
};
|
|
37
|
+
await createClient(config);
|
|
38
|
+
const source = await createSource({
|
|
39
|
+
outputPath: openApiOutputPath,
|
|
40
|
+
version,
|
|
41
|
+
serviceEndName: "Service", // we are hard coding this because changing the service end name was depreciated in @hey-api/openapi-ts
|
|
42
|
+
pageParam: formattedOptions.pageParam,
|
|
43
|
+
nextPageParam: formattedOptions.nextPageParam,
|
|
44
|
+
});
|
|
45
|
+
await print(source, formattedOptions);
|
|
46
|
+
const queriesOutputPath = buildQueriesOutputPath(options.output);
|
|
47
|
+
await formatOutput(queriesOutputPath);
|
|
48
|
+
}
|
package/dist/print.mjs
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { buildQueriesOutputPath, exists } from "./common.mjs";
|
|
4
|
+
async function printGeneratedTS(result, options) {
|
|
5
|
+
const dir = buildQueriesOutputPath(options.output);
|
|
6
|
+
const dirExists = await exists(dir);
|
|
7
|
+
if (!dirExists) {
|
|
8
|
+
await mkdir(dir, { recursive: true });
|
|
9
|
+
}
|
|
10
|
+
await writeFile(path.join(dir, result.name), result.content);
|
|
11
|
+
}
|
|
12
|
+
export async function print(results, options) {
|
|
13
|
+
const outputPath = options.output;
|
|
14
|
+
const dirExists = await exists(outputPath);
|
|
15
|
+
if (!dirExists) {
|
|
16
|
+
await mkdir(outputPath);
|
|
17
|
+
}
|
|
18
|
+
const promises = results.map(async (result) => {
|
|
19
|
+
await printGeneratedTS(result, options);
|
|
20
|
+
});
|
|
21
|
+
await Promise.all(promises);
|
|
22
|
+
}
|
package/dist/service.mjs
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import { getClassNameFromClassNode, getClassesFromService, } from "./common.mjs";
|
|
3
|
+
import { serviceFileName } from "./constants.mjs";
|
|
4
|
+
export async function getServices(project) {
|
|
5
|
+
const node = project
|
|
6
|
+
.getSourceFiles()
|
|
7
|
+
.find((sourceFile) => sourceFile.getFilePath().includes(serviceFileName));
|
|
8
|
+
if (!node) {
|
|
9
|
+
throw new Error("No service node found");
|
|
10
|
+
}
|
|
11
|
+
const klasses = getClassesFromService(node);
|
|
12
|
+
return {
|
|
13
|
+
klasses: klasses.map(({ klass, className }) => ({
|
|
14
|
+
className,
|
|
15
|
+
klass,
|
|
16
|
+
methods: getMethodsFromService(node, klass),
|
|
17
|
+
})),
|
|
18
|
+
node,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function getMethodsFromService(node, klass) {
|
|
22
|
+
const methods = klass.getMethods();
|
|
23
|
+
if (!methods.length) {
|
|
24
|
+
throw new Error("No methods found");
|
|
25
|
+
}
|
|
26
|
+
return methods.map((method) => {
|
|
27
|
+
const methodBlockNode = method.compilerNode
|
|
28
|
+
.getChildren(node.compilerNode)
|
|
29
|
+
.find((child) => child.kind === ts.SyntaxKind.Block);
|
|
30
|
+
if (!methodBlockNode) {
|
|
31
|
+
throw new Error("Method block not found");
|
|
32
|
+
}
|
|
33
|
+
const methodBlock = methodBlockNode;
|
|
34
|
+
const foundReturnStatement = methodBlock.statements.find((s) => s.kind === ts.SyntaxKind.ReturnStatement);
|
|
35
|
+
if (!foundReturnStatement) {
|
|
36
|
+
throw new Error("Return statement not found");
|
|
37
|
+
}
|
|
38
|
+
const returnStatement = foundReturnStatement;
|
|
39
|
+
const foundCallExpression = returnStatement.expression;
|
|
40
|
+
if (!foundCallExpression) {
|
|
41
|
+
throw new Error("Call expression not found");
|
|
42
|
+
}
|
|
43
|
+
const callExpression = foundCallExpression;
|
|
44
|
+
const properties = callExpression.arguments[1].properties;
|
|
45
|
+
const httpMethodName = properties
|
|
46
|
+
.find((p) => p.name?.getText(node.compilerNode) === "method")
|
|
47
|
+
?.initializer?.getText(node.compilerNode);
|
|
48
|
+
if (!httpMethodName) {
|
|
49
|
+
throw new Error("httpMethodName not found");
|
|
50
|
+
}
|
|
51
|
+
const getAllChildren = (tsNode) => {
|
|
52
|
+
const childItems = tsNode.getChildren(node.compilerNode);
|
|
53
|
+
if (childItems.length) {
|
|
54
|
+
const allChildren = childItems.map(getAllChildren);
|
|
55
|
+
return [tsNode].concat(allChildren.flat());
|
|
56
|
+
}
|
|
57
|
+
return [tsNode];
|
|
58
|
+
};
|
|
59
|
+
const children = getAllChildren(method.compilerNode);
|
|
60
|
+
// get all JSDoc comments
|
|
61
|
+
// this should be an array of 1 or 0
|
|
62
|
+
const jsDocs = children
|
|
63
|
+
.filter((c) => c.kind === ts.SyntaxKind.JSDoc)
|
|
64
|
+
.map((c) => c.getText(node.compilerNode));
|
|
65
|
+
// get the first JSDoc comment
|
|
66
|
+
const jsDoc = jsDocs?.[0];
|
|
67
|
+
const isDeprecated = children.some((c) => c.kind === ts.SyntaxKind.JSDocDeprecatedTag);
|
|
68
|
+
const className = getClassNameFromClassNode(klass);
|
|
69
|
+
return {
|
|
70
|
+
className,
|
|
71
|
+
node,
|
|
72
|
+
method,
|
|
73
|
+
methodBlock,
|
|
74
|
+
httpMethodName,
|
|
75
|
+
jsDoc,
|
|
76
|
+
isDeprecated,
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
}
|
package/dist/util.mjs
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
export function addJSDocToNode(node, jsDoc) {
|
|
3
|
+
if (!jsDoc) {
|
|
4
|
+
return node;
|
|
5
|
+
}
|
|
6
|
+
// replace the first /** with *
|
|
7
|
+
// we do this because ts.addSyntheticLeadingComment will add /* to the beginning but we want /**
|
|
8
|
+
const removedFirstLine = jsDoc.trim().replace(/^\/\*\*/, "*");
|
|
9
|
+
// remove the last */ because ts.addSyntheticLeadingComment will add it
|
|
10
|
+
const removedSecondLine = removedFirstLine.replace(/\*\/$/, "");
|
|
11
|
+
const split = removedSecondLine.split("\n");
|
|
12
|
+
const trimmed = split.map((line) => line.trim());
|
|
13
|
+
const joined = trimmed.join("\n");
|
|
14
|
+
const nodeWithJSDoc = ts.addSyntheticLeadingComment(node, ts.SyntaxKind.MultiLineCommentTrivia, joined, true);
|
|
15
|
+
return nodeWithJSDoc;
|
|
16
|
+
}
|