@7nohe/openapi-react-query-codegen 0.0.0-13d458778df8db161111e788ed4207a13a110724
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 +41 -0
- package/dist/common.mjs +151 -0
- package/dist/constants.mjs +12 -0
- package/dist/createExports.mjs +66 -0
- package/dist/createImports.mjs +43 -0
- package/dist/createPrefetch.mjs +48 -0
- package/dist/createSource.mjs +83 -0
- package/dist/createUseMutation.mjs +76 -0
- package/dist/createUseQuery.mjs +201 -0
- package/dist/format.mjs +67 -0
- package/dist/generate.mjs +50 -0
- package/dist/print.mjs +22 -0
- package/dist/service.mjs +79 -0
- package/dist/util.mjs +16 -0
- package/package.json +78 -0
|
@@ -0,0 +1,83 @@
|
|
|
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) => {
|
|
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
|
+
project,
|
|
21
|
+
});
|
|
22
|
+
const exports = createExports(service);
|
|
23
|
+
const commonSource = ts.factory.createSourceFile([...imports, ...exports.allCommon], ts.factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None);
|
|
24
|
+
const commonImport = ts.factory.createImportDeclaration(undefined, ts.factory.createImportClause(false, ts.factory.createIdentifier("* as Common"), undefined), ts.factory.createStringLiteral(`./${OpenApiRqFiles.common}`), undefined);
|
|
25
|
+
const commonExport = ts.factory.createExportDeclaration(undefined, false, undefined, ts.factory.createStringLiteral(`./${OpenApiRqFiles.common}`), undefined);
|
|
26
|
+
const queriesExport = ts.factory.createExportDeclaration(undefined, false, undefined, ts.factory.createStringLiteral(`./${OpenApiRqFiles.queries}`), undefined);
|
|
27
|
+
const mainSource = ts.factory.createSourceFile([commonImport, ...imports, ...exports.mainExports], ts.factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None);
|
|
28
|
+
const suspenseSource = ts.factory.createSourceFile([commonImport, ...imports, ...exports.suspenseExports], ts.factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None);
|
|
29
|
+
const indexSource = ts.factory.createSourceFile([commonExport, queriesExport], ts.factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None);
|
|
30
|
+
const prefetchSource = ts.factory.createSourceFile([commonImport, ...imports, ...exports.allPrefetchExports], ts.factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None);
|
|
31
|
+
return {
|
|
32
|
+
commonSource,
|
|
33
|
+
mainSource,
|
|
34
|
+
suspenseSource,
|
|
35
|
+
indexSource,
|
|
36
|
+
prefetchSource,
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
export const createSource = async ({ outputPath, version, }) => {
|
|
40
|
+
const queriesFile = ts.createSourceFile(`${OpenApiRqFiles.queries}.ts`, "", ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
|
|
41
|
+
const commonFile = ts.createSourceFile(`${OpenApiRqFiles.common}.ts`, "", ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
|
|
42
|
+
const suspenseFile = ts.createSourceFile(`${OpenApiRqFiles.suspense}.ts`, "", ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
|
|
43
|
+
const indexFile = ts.createSourceFile(`${OpenApiRqFiles.index}.ts`, "", ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
|
|
44
|
+
const prefetchFile = ts.createSourceFile(`${OpenApiRqFiles.prefetch}.ts`, "", ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
|
|
45
|
+
const printer = ts.createPrinter({
|
|
46
|
+
newLine: ts.NewLineKind.LineFeed,
|
|
47
|
+
removeComments: false,
|
|
48
|
+
});
|
|
49
|
+
const { commonSource, mainSource, suspenseSource, indexSource, prefetchSource, } = await createSourceFile(outputPath);
|
|
50
|
+
const comment = `// generated with @7nohe/openapi-react-query-codegen@${version} \n\n`;
|
|
51
|
+
const commonResult = comment +
|
|
52
|
+
printer.printNode(ts.EmitHint.Unspecified, commonSource, commonFile);
|
|
53
|
+
const mainResult = comment +
|
|
54
|
+
printer.printNode(ts.EmitHint.Unspecified, mainSource, queriesFile);
|
|
55
|
+
const suspenseResult = comment +
|
|
56
|
+
printer.printNode(ts.EmitHint.Unspecified, suspenseSource, suspenseFile);
|
|
57
|
+
const indexResult = comment +
|
|
58
|
+
printer.printNode(ts.EmitHint.Unspecified, indexSource, indexFile);
|
|
59
|
+
const prefetchResult = comment +
|
|
60
|
+
printer.printNode(ts.EmitHint.Unspecified, prefetchSource, prefetchFile);
|
|
61
|
+
return [
|
|
62
|
+
{
|
|
63
|
+
name: `${OpenApiRqFiles.index}.ts`,
|
|
64
|
+
content: indexResult,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: `${OpenApiRqFiles.common}.ts`,
|
|
68
|
+
content: commonResult,
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: `${OpenApiRqFiles.queries}.ts`,
|
|
72
|
+
content: mainResult,
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: `${OpenApiRqFiles.suspense}.ts`,
|
|
76
|
+
content: suspenseResult,
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: `${OpenApiRqFiles.prefetch}.ts`,
|
|
80
|
+
content: prefetchResult,
|
|
81
|
+
},
|
|
82
|
+
];
|
|
83
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import { BuildCommonTypeName, TContext, TData, TError, capitalizeFirstLetter, extractPropertiesFromObjectParam, getNameFromVariable, getShortType, getVariableArrowFunctionParameters, } from "./common.mjs";
|
|
3
|
+
import { addJSDocToNode } from "./util.mjs";
|
|
4
|
+
/**
|
|
5
|
+
* Awaited<ReturnType<typeof myClass.myMethod>>
|
|
6
|
+
*/
|
|
7
|
+
function generateAwaitedReturnType({ methodName, }) {
|
|
8
|
+
return ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("Awaited"), [
|
|
9
|
+
ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("ReturnType"), [
|
|
10
|
+
ts.factory.createTypeQueryNode(ts.factory.createIdentifier(methodName), undefined),
|
|
11
|
+
]),
|
|
12
|
+
]);
|
|
13
|
+
}
|
|
14
|
+
export const createUseMutation = ({ method, jsDoc }) => {
|
|
15
|
+
const methodName = getNameFromVariable(method);
|
|
16
|
+
const awaitedResponseDataType = generateAwaitedReturnType({
|
|
17
|
+
methodName,
|
|
18
|
+
});
|
|
19
|
+
const mutationResult = ts.factory.createTypeAliasDeclaration([ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], ts.factory.createIdentifier(`${capitalizeFirstLetter(methodName)}MutationResult`), undefined, awaitedResponseDataType);
|
|
20
|
+
const responseDataType = ts.factory.createTypeParameterDeclaration(undefined, TData, undefined, ts.factory.createTypeReferenceNode(BuildCommonTypeName(mutationResult.name)));
|
|
21
|
+
const methodParameters = getVariableArrowFunctionParameters(method).length !== 0
|
|
22
|
+
? ts.factory.createTypeLiteralNode(getVariableArrowFunctionParameters(method).flatMap((param) => {
|
|
23
|
+
const paramNodes = extractPropertiesFromObjectParam(param);
|
|
24
|
+
return paramNodes.map((refParam) => ts.factory.createPropertySignature(undefined, ts.factory.createIdentifier(refParam.name), refParam.optional
|
|
25
|
+
? ts.factory.createToken(ts.SyntaxKind.QuestionToken)
|
|
26
|
+
: undefined, ts.factory.createTypeReferenceNode(getShortType(refParam.type.getText(param)))));
|
|
27
|
+
}))
|
|
28
|
+
: ts.factory.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword);
|
|
29
|
+
const exportHook = ts.factory.createVariableStatement([ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], ts.factory.createVariableDeclarationList([
|
|
30
|
+
ts.factory.createVariableDeclaration(ts.factory.createIdentifier(`use${capitalizeFirstLetter(methodName)}`), undefined, undefined, ts.factory.createArrowFunction(undefined, ts.factory.createNodeArray([
|
|
31
|
+
responseDataType,
|
|
32
|
+
ts.factory.createTypeParameterDeclaration(undefined, TError, undefined, ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword)),
|
|
33
|
+
ts.factory.createTypeParameterDeclaration(undefined, TContext, undefined, ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword)),
|
|
34
|
+
]), [
|
|
35
|
+
ts.factory.createParameterDeclaration(undefined, undefined, ts.factory.createIdentifier("options"), ts.factory.createToken(ts.SyntaxKind.QuestionToken), ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("Omit"), [
|
|
36
|
+
ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("UseMutationOptions"), [
|
|
37
|
+
ts.factory.createTypeReferenceNode(TData),
|
|
38
|
+
ts.factory.createTypeReferenceNode(TError),
|
|
39
|
+
methodParameters,
|
|
40
|
+
ts.factory.createTypeReferenceNode(TContext),
|
|
41
|
+
]),
|
|
42
|
+
ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral("mutationFn")),
|
|
43
|
+
]), undefined),
|
|
44
|
+
], undefined, ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), ts.factory.createCallExpression(ts.factory.createIdentifier("useMutation"), [
|
|
45
|
+
ts.factory.createTypeReferenceNode(TData),
|
|
46
|
+
ts.factory.createTypeReferenceNode(TError),
|
|
47
|
+
methodParameters,
|
|
48
|
+
ts.factory.createTypeReferenceNode(TContext),
|
|
49
|
+
], [
|
|
50
|
+
ts.factory.createObjectLiteralExpression([
|
|
51
|
+
ts.factory.createPropertyAssignment(ts.factory.createIdentifier("mutationFn"), ts.factory.createArrowFunction(undefined, undefined, getVariableArrowFunctionParameters(method).length !== 0
|
|
52
|
+
? [
|
|
53
|
+
ts.factory.createParameterDeclaration(undefined, undefined, ts.factory.createObjectBindingPattern(getVariableArrowFunctionParameters(method).flatMap((param) => {
|
|
54
|
+
const paramNodes = extractPropertiesFromObjectParam(param);
|
|
55
|
+
return paramNodes.map((refParam) => ts.factory.createBindingElement(undefined, undefined, ts.factory.createIdentifier(refParam.name), undefined));
|
|
56
|
+
})), undefined, undefined, undefined),
|
|
57
|
+
]
|
|
58
|
+
: [], undefined, ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), ts.factory.createAsExpression(ts.factory.createAsExpression(ts.factory.createCallExpression(ts.factory.createIdentifier(methodName), undefined, getVariableArrowFunctionParameters(method)
|
|
59
|
+
.length !== 0
|
|
60
|
+
? [
|
|
61
|
+
ts.factory.createObjectLiteralExpression(getVariableArrowFunctionParameters(method).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,201 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import { BuildCommonTypeName, EqualsOrGreaterThanToken, QuestionToken, TData, TError, capitalizeFirstLetter, extractPropertiesFromObjectParam, getNameFromVariable, getShortType, getVariableArrowFunctionParameters, queryKeyConstraint, queryKeyGenericType, } from "./common.mjs";
|
|
3
|
+
import { addJSDocToNode } from "./util.mjs";
|
|
4
|
+
export const createApiResponseType = ({ methodName, }) => {
|
|
5
|
+
/** Awaited<ReturnType<typeof myClass.myMethod>> */
|
|
6
|
+
const awaitedResponseDataType = ts.factory.createIndexedAccessTypeNode(ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("Awaited"), [
|
|
7
|
+
ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("ReturnType"), [
|
|
8
|
+
ts.factory.createTypeQueryNode(ts.factory.createIdentifier(methodName), undefined),
|
|
9
|
+
]),
|
|
10
|
+
]), ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral("data")));
|
|
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(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) {
|
|
32
|
+
if (!getVariableArrowFunctionParameters(method).length) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
const params = getVariableArrowFunctionParameters(method).flatMap((param) => {
|
|
36
|
+
const paramNodes = extractPropertiesFromObjectParam(param);
|
|
37
|
+
return paramNodes.map((refParam) => ({
|
|
38
|
+
name: refParam.name,
|
|
39
|
+
typeName: getShortType(refParam.type.getText()),
|
|
40
|
+
optional: refParam.optional,
|
|
41
|
+
}));
|
|
42
|
+
});
|
|
43
|
+
const areAllPropertiesOptional = params.every((param) => param.optional);
|
|
44
|
+
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) => {
|
|
45
|
+
return ts.factory.createPropertySignature(undefined, ts.factory.createIdentifier(refParam.name), refParam.optional
|
|
46
|
+
? ts.factory.createToken(ts.SyntaxKind.QuestionToken)
|
|
47
|
+
: undefined, ts.factory.createTypeReferenceNode(refParam.typeName));
|
|
48
|
+
})),
|
|
49
|
+
// if all params are optional, we create an empty object literal
|
|
50
|
+
// so the hook can be called without any parameters
|
|
51
|
+
areAllPropertiesOptional
|
|
52
|
+
? ts.factory.createObjectLiteralExpression()
|
|
53
|
+
: undefined);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Return Type
|
|
57
|
+
*
|
|
58
|
+
* export const classNameMethodNameQueryResult<TData = MyClassMethodDefaultResponse, TError = unknown> = UseQueryResult<TData, TError>;
|
|
59
|
+
*/
|
|
60
|
+
export function createReturnTypeExport({ methodName, defaultApiResponse, }) {
|
|
61
|
+
return ts.factory.createTypeAliasDeclaration([ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], ts.factory.createIdentifier(`${capitalizeFirstLetter(methodName)}QueryResult`), [
|
|
62
|
+
ts.factory.createTypeParameterDeclaration(undefined, TData, undefined, ts.factory.createTypeReferenceNode(defaultApiResponse.name)),
|
|
63
|
+
ts.factory.createTypeParameterDeclaration(undefined, TError, undefined, ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword)),
|
|
64
|
+
], ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("UseQueryResult"), [
|
|
65
|
+
ts.factory.createTypeReferenceNode(TData),
|
|
66
|
+
ts.factory.createTypeReferenceNode(TError),
|
|
67
|
+
]));
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* QueryKey
|
|
71
|
+
*/
|
|
72
|
+
export function createQueryKeyExport({ methodName, queryKey, }) {
|
|
73
|
+
return ts.factory.createVariableStatement([ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], ts.factory.createVariableDeclarationList([
|
|
74
|
+
ts.factory.createVariableDeclaration(ts.factory.createIdentifier(queryKey), undefined, undefined, ts.factory.createStringLiteral(`${capitalizeFirstLetter(methodName)}`)),
|
|
75
|
+
], ts.NodeFlags.Const));
|
|
76
|
+
}
|
|
77
|
+
export function hookNameFromMethod({ method, }) {
|
|
78
|
+
const methodName = getNameFromVariable(method);
|
|
79
|
+
return `use${capitalizeFirstLetter(methodName)}`;
|
|
80
|
+
}
|
|
81
|
+
export function createQueryKeyFromMethod({ method, }) {
|
|
82
|
+
const customHookName = hookNameFromMethod({ method });
|
|
83
|
+
const queryKey = `${customHookName}Key`;
|
|
84
|
+
return queryKey;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Creates a custom hook for a query
|
|
88
|
+
* @param queryString The type of query to use from react-query
|
|
89
|
+
* @param suffix The suffix to append to the hook name
|
|
90
|
+
*/
|
|
91
|
+
export function createQueryHook({ queryString, suffix, responseDataType, requestParams, method, }) {
|
|
92
|
+
const methodName = getNameFromVariable(method);
|
|
93
|
+
const customHookName = hookNameFromMethod({ method });
|
|
94
|
+
const queryKey = createQueryKeyFromMethod({ method });
|
|
95
|
+
const hookExport = ts.factory.createVariableStatement([ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], ts.factory.createVariableDeclarationList([
|
|
96
|
+
ts.factory.createVariableDeclaration(ts.factory.createIdentifier(`${customHookName}${suffix}`), undefined, undefined, ts.factory.createArrowFunction(undefined, ts.factory.createNodeArray([
|
|
97
|
+
responseDataType,
|
|
98
|
+
ts.factory.createTypeParameterDeclaration(undefined, TError, undefined, ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword)),
|
|
99
|
+
ts.factory.createTypeParameterDeclaration(undefined, "TQueryKey", queryKeyConstraint, ts.factory.createArrayTypeNode(ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword))),
|
|
100
|
+
]), [
|
|
101
|
+
...requestParams,
|
|
102
|
+
ts.factory.createParameterDeclaration(undefined, undefined, ts.factory.createIdentifier("queryKey"), ts.factory.createToken(ts.SyntaxKind.QuestionToken), queryKeyGenericType),
|
|
103
|
+
ts.factory.createParameterDeclaration(undefined, undefined, ts.factory.createIdentifier("options"), ts.factory.createToken(ts.SyntaxKind.QuestionToken), ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("Omit"), [
|
|
104
|
+
ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("UseQueryOptions"), [
|
|
105
|
+
ts.factory.createTypeReferenceNode(TData),
|
|
106
|
+
ts.factory.createTypeReferenceNode(TError),
|
|
107
|
+
]),
|
|
108
|
+
ts.factory.createUnionTypeNode([
|
|
109
|
+
ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral("queryKey")),
|
|
110
|
+
ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral("queryFn")),
|
|
111
|
+
]),
|
|
112
|
+
])),
|
|
113
|
+
], undefined, EqualsOrGreaterThanToken, ts.factory.createCallExpression(ts.factory.createIdentifier(queryString), [
|
|
114
|
+
ts.factory.createTypeReferenceNode(TData),
|
|
115
|
+
ts.factory.createTypeReferenceNode(TError),
|
|
116
|
+
], [
|
|
117
|
+
ts.factory.createObjectLiteralExpression([
|
|
118
|
+
ts.factory.createPropertyAssignment(ts.factory.createIdentifier("queryKey"), ts.factory.createCallExpression(BuildCommonTypeName(getQueryKeyFnName(queryKey)), undefined, getVariableArrowFunctionParameters(method).length
|
|
119
|
+
? [
|
|
120
|
+
ts.factory.createObjectLiteralExpression(getVariableArrowFunctionParameters(method).flatMap((param) => extractPropertiesFromObjectParam(param).map((p) => ts.factory.createShorthandPropertyAssignment(ts.factory.createIdentifier(p.name))))),
|
|
121
|
+
ts.factory.createIdentifier("queryKey"),
|
|
122
|
+
]
|
|
123
|
+
: [ts.factory.createIdentifier("queryKey")])),
|
|
124
|
+
ts.factory.createPropertyAssignment(ts.factory.createIdentifier("queryFn"), ts.factory.createArrowFunction(undefined, undefined, [], undefined, EqualsOrGreaterThanToken, ts.factory.createAsExpression(ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(ts.factory.createCallExpression(ts.factory.createIdentifier(methodName), undefined, getVariableArrowFunctionParameters(method).length
|
|
125
|
+
? [
|
|
126
|
+
ts.factory.createObjectLiteralExpression(getVariableArrowFunctionParameters(method).flatMap((param) => extractPropertiesFromObjectParam(param).map((p) => ts.factory.createShorthandPropertyAssignment(ts.factory.createIdentifier(p.name))))),
|
|
127
|
+
]
|
|
128
|
+
: undefined), ts.factory.createIdentifier("then")), undefined, [
|
|
129
|
+
ts.factory.createArrowFunction(undefined, undefined, [
|
|
130
|
+
ts.factory.createParameterDeclaration(undefined, undefined, ts.factory.createIdentifier("response"), undefined, undefined, undefined),
|
|
131
|
+
], undefined, EqualsOrGreaterThanToken, ts.factory.createAsExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier("response"), ts.factory.createIdentifier("data")), ts.factory.createTypeReferenceNode(TData))),
|
|
132
|
+
]), ts.factory.createTypeReferenceNode(TData)))),
|
|
133
|
+
ts.factory.createSpreadAssignment(ts.factory.createIdentifier("options")),
|
|
134
|
+
]),
|
|
135
|
+
]))),
|
|
136
|
+
], ts.NodeFlags.Const));
|
|
137
|
+
return hookExport;
|
|
138
|
+
}
|
|
139
|
+
export const createUseQuery = ({ method, jsDoc }) => {
|
|
140
|
+
const methodName = getNameFromVariable(method);
|
|
141
|
+
const queryKey = createQueryKeyFromMethod({ method });
|
|
142
|
+
const { apiResponse: defaultApiResponse, responseDataType } = createApiResponseType({
|
|
143
|
+
methodName,
|
|
144
|
+
});
|
|
145
|
+
const requestParam = getRequestParamFromMethod(method);
|
|
146
|
+
const requestParams = requestParam ? [requestParam] : [];
|
|
147
|
+
const queryHook = createQueryHook({
|
|
148
|
+
queryString: "useQuery",
|
|
149
|
+
suffix: "",
|
|
150
|
+
responseDataType,
|
|
151
|
+
requestParams,
|
|
152
|
+
method,
|
|
153
|
+
});
|
|
154
|
+
const suspenseQueryHook = createQueryHook({
|
|
155
|
+
queryString: "useSuspenseQuery",
|
|
156
|
+
suffix: "Suspense",
|
|
157
|
+
responseDataType,
|
|
158
|
+
requestParams,
|
|
159
|
+
method,
|
|
160
|
+
});
|
|
161
|
+
const hookWithJsDoc = addJSDocToNode(queryHook, jsDoc);
|
|
162
|
+
const suspenseHookWithJsDoc = addJSDocToNode(suspenseQueryHook, jsDoc);
|
|
163
|
+
const returnTypeExport = createReturnTypeExport({
|
|
164
|
+
methodName,
|
|
165
|
+
defaultApiResponse,
|
|
166
|
+
});
|
|
167
|
+
const queryKeyExport = createQueryKeyExport({
|
|
168
|
+
methodName,
|
|
169
|
+
queryKey,
|
|
170
|
+
});
|
|
171
|
+
const queryKeyFn = createQueryKeyFnExport(queryKey, method);
|
|
172
|
+
return {
|
|
173
|
+
apiResponse: defaultApiResponse,
|
|
174
|
+
returnType: returnTypeExport,
|
|
175
|
+
key: queryKeyExport,
|
|
176
|
+
queryHook: hookWithJsDoc,
|
|
177
|
+
suspenseQueryHook: suspenseHookWithJsDoc,
|
|
178
|
+
queryKeyFn,
|
|
179
|
+
};
|
|
180
|
+
};
|
|
181
|
+
export function getQueryKeyFnName(queryKey) {
|
|
182
|
+
return `${capitalizeFirstLetter(queryKey)}Fn`;
|
|
183
|
+
}
|
|
184
|
+
function createQueryKeyFnExport(queryKey, method) {
|
|
185
|
+
const params = getRequestParamFromMethod(method);
|
|
186
|
+
// override key is used to allow the user to override the the queryKey values
|
|
187
|
+
const overrideKey = ts.factory.createParameterDeclaration(undefined, undefined, ts.factory.createIdentifier("queryKey"), QuestionToken, ts.factory.createTypeReferenceNode("Array<unknown>", []));
|
|
188
|
+
return ts.factory.createVariableStatement([ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], ts.factory.createVariableDeclarationList([
|
|
189
|
+
ts.factory.createVariableDeclaration(ts.factory.createIdentifier(getQueryKeyFnName(queryKey)), undefined, undefined, ts.factory.createArrowFunction(undefined, undefined, params ? [params, overrideKey] : [overrideKey], undefined, EqualsOrGreaterThanToken, queryKeyFn(queryKey, method))),
|
|
190
|
+
], ts.NodeFlags.Const));
|
|
191
|
+
}
|
|
192
|
+
function queryKeyFn(queryKey, method) {
|
|
193
|
+
return ts.factory.createArrayLiteralExpression([
|
|
194
|
+
ts.factory.createIdentifier(queryKey),
|
|
195
|
+
ts.factory.createSpreadElement(ts.factory.createParenthesizedExpression(ts.factory.createBinaryExpression(ts.factory.createIdentifier("queryKey"), ts.factory.createToken(ts.SyntaxKind.QuestionQuestionToken), getVariableArrowFunctionParameters(method)
|
|
196
|
+
? ts.factory.createArrayLiteralExpression([
|
|
197
|
+
ts.factory.createObjectLiteralExpression(getVariableArrowFunctionParameters(method).flatMap((param) => extractPropertiesFromObjectParam(param).map((p) => ts.factory.createShorthandPropertyAssignment(ts.factory.createIdentifier(p.name))))),
|
|
198
|
+
])
|
|
199
|
+
: ts.factory.createArrayLiteralExpression([])))),
|
|
200
|
+
], false);
|
|
201
|
+
}
|
package/dist/format.mjs
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { sync } from "cross-spawn";
|
|
2
|
+
import { IndentationText, NewLineKind, Project, QuoteKind } from "ts-morph";
|
|
3
|
+
export const formatOutput = async (outputPath) => {
|
|
4
|
+
const project = new Project({
|
|
5
|
+
skipAddingFilesFromTsConfig: true,
|
|
6
|
+
manipulationSettings: {
|
|
7
|
+
indentationText: IndentationText.TwoSpaces,
|
|
8
|
+
newLineKind: NewLineKind.LineFeed,
|
|
9
|
+
quoteKind: QuoteKind.Double,
|
|
10
|
+
usePrefixAndSuffixTextForRename: false,
|
|
11
|
+
useTrailingCommas: true,
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
const sourceFiles = project.addSourceFilesAtPaths(`${outputPath}/**/*`);
|
|
15
|
+
const tasks = sourceFiles.map((sourceFile) => {
|
|
16
|
+
sourceFile.formatText();
|
|
17
|
+
sourceFile.fixMissingImports();
|
|
18
|
+
sourceFile.organizeImports();
|
|
19
|
+
return sourceFile.save();
|
|
20
|
+
});
|
|
21
|
+
await Promise.all(tasks);
|
|
22
|
+
};
|
|
23
|
+
const formatters = {
|
|
24
|
+
biome: {
|
|
25
|
+
args: (path) => ["format", "--write", path],
|
|
26
|
+
command: "biome",
|
|
27
|
+
name: "Biome (Format)",
|
|
28
|
+
},
|
|
29
|
+
prettier: {
|
|
30
|
+
args: (path) => [
|
|
31
|
+
"--ignore-unknown",
|
|
32
|
+
path,
|
|
33
|
+
"--write",
|
|
34
|
+
"--ignore-path",
|
|
35
|
+
"./.prettierignore",
|
|
36
|
+
],
|
|
37
|
+
command: "prettier",
|
|
38
|
+
name: "Prettier",
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Map of supported linters
|
|
43
|
+
*/
|
|
44
|
+
const linters = {
|
|
45
|
+
biome: {
|
|
46
|
+
args: (path) => ["lint", "--apply", path],
|
|
47
|
+
command: "biome",
|
|
48
|
+
name: "Biome (Lint)",
|
|
49
|
+
},
|
|
50
|
+
eslint: {
|
|
51
|
+
args: (path) => [path, "--fix"],
|
|
52
|
+
command: "eslint",
|
|
53
|
+
name: "ESLint",
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
export const processOutput = async ({ output, format, lint, }) => {
|
|
57
|
+
if (format) {
|
|
58
|
+
const module = formatters[format];
|
|
59
|
+
console.log(`✨ Running ${module.name} on queries`);
|
|
60
|
+
sync(module.command, module.args(output));
|
|
61
|
+
}
|
|
62
|
+
if (lint) {
|
|
63
|
+
const module = linters[lint];
|
|
64
|
+
console.log(`✨ Running ${module.name} on queries`);
|
|
65
|
+
sync(module.command, module.args(output));
|
|
66
|
+
}
|
|
67
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
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, processOutput } 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
|
+
});
|
|
42
|
+
await print(source, formattedOptions);
|
|
43
|
+
const queriesOutputPath = buildQueriesOutputPath(options.output);
|
|
44
|
+
await formatOutput(queriesOutputPath);
|
|
45
|
+
await processOutput({
|
|
46
|
+
output: queriesOutputPath,
|
|
47
|
+
format: formattedOptions.format,
|
|
48
|
+
lint: formattedOptions.lint,
|
|
49
|
+
});
|
|
50
|
+
}
|
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 { serviceFileName } from "./constants.mjs";
|
|
3
|
+
export async function getServices(project) {
|
|
4
|
+
const node = project
|
|
5
|
+
.getSourceFiles()
|
|
6
|
+
.find((sourceFile) => sourceFile.getFilePath().includes(serviceFileName));
|
|
7
|
+
if (!node) {
|
|
8
|
+
throw new Error("No service node found");
|
|
9
|
+
}
|
|
10
|
+
const methods = getMethodsFromService(node);
|
|
11
|
+
return {
|
|
12
|
+
methods,
|
|
13
|
+
node,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function getMethodsFromService(node) {
|
|
17
|
+
const variableStatements = node.getVariableStatements();
|
|
18
|
+
// const methodDescriptions: FunctionDescription[] = [];
|
|
19
|
+
return variableStatements.flatMap((variableStatement) => {
|
|
20
|
+
const declarations = variableStatement.getDeclarations();
|
|
21
|
+
return declarations.map((declaration) => {
|
|
22
|
+
if (!ts.isVariableDeclaration(declaration.compilerNode)) {
|
|
23
|
+
throw new Error("Variable declaration not found");
|
|
24
|
+
}
|
|
25
|
+
const initializer = declaration.getInitializer();
|
|
26
|
+
if (!initializer) {
|
|
27
|
+
throw new Error("Initializer not found");
|
|
28
|
+
}
|
|
29
|
+
if (!ts.isArrowFunction(initializer.compilerNode)) {
|
|
30
|
+
throw new Error("Arrow function not found");
|
|
31
|
+
}
|
|
32
|
+
const methodBlockNode = initializer.compilerNode.body;
|
|
33
|
+
if (!methodBlockNode || !ts.isBlock(methodBlockNode)) {
|
|
34
|
+
throw new Error("Method block not found");
|
|
35
|
+
}
|
|
36
|
+
const foundReturnStatement = methodBlockNode.statements.find((s) => s.kind === ts.SyntaxKind.ReturnStatement);
|
|
37
|
+
if (!foundReturnStatement) {
|
|
38
|
+
throw new Error("Return statement not found");
|
|
39
|
+
}
|
|
40
|
+
const returnStatement = foundReturnStatement;
|
|
41
|
+
const foundCallExpression = returnStatement.expression;
|
|
42
|
+
if (!foundCallExpression) {
|
|
43
|
+
throw new Error("Call expression not found");
|
|
44
|
+
}
|
|
45
|
+
const callExpression = foundCallExpression;
|
|
46
|
+
const propertyAccessExpression = callExpression.expression;
|
|
47
|
+
const httpMethodName = propertyAccessExpression.name.getText();
|
|
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(initializer.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 methodDescription = {
|
|
69
|
+
node,
|
|
70
|
+
method: declaration,
|
|
71
|
+
methodBlock: methodBlockNode,
|
|
72
|
+
httpMethodName,
|
|
73
|
+
jsDoc,
|
|
74
|
+
isDeprecated,
|
|
75
|
+
};
|
|
76
|
+
return methodDescription;
|
|
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
|
+
}
|