@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.
@@ -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
+ }
@@ -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
+ }
@@ -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
+ }