Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

patch(return types): union return type with undefined. #33

Merged
merged 4 commits into from
Jan 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions examples/react-app/petstore.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,19 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/Error'
/not-defined:
get:
deprecated: true
description: This path is not fully defined.
responses:
default:
description: unexpected error
post:
deprecated: true
description: This path is not defined at all.
responses:
default:
description: unexpected error
/pets/{id}:
get:
description: Returns a user based on a single ID, if the user does not have access to the pet
Expand Down
8 changes: 8 additions & 0 deletions examples/react-app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import {
useDefaultClientAddPet,
useDefaultClientFindPets,
useDefaultClientFindPetsKey,
useDefaultClientGetNotDefined,
useDefaultClientPostNotDefined,
} from "../openapi/queries";
import { useState } from "react";
import { queryClient } from "./queryClient";
Expand All @@ -13,6 +15,12 @@ function App() {

const { data, error, refetch } = useDefaultClientFindPets({ tags, limit });

// This is an example of a query that is not defined in the OpenAPI spec
// this defaults to any - here we are showing how to override the type
// Note - this is marked as deprecated in the OpenAPI spec and being passed to the client
const { data: notDefined } = useDefaultClientGetNotDefined<undefined>();
const { mutate: mutateNotDefined } = useDefaultClientPostNotDefined<undefined>();

const { mutate: addPet } = useDefaultClientAddPet();

if (error)
Expand Down
24 changes: 21 additions & 3 deletions src/createExports.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import ts from "typescript";
import ts, { JSDoc } from "typescript";
import { sync } from "glob";
import { join } from "path";
import fs from "fs";
Expand Down Expand Up @@ -45,9 +45,27 @@ export const createExports = (generatedClientsPath: string) => {
const httpMethodName = properties
.find((p) => p.name?.getText(node) === "method")
?.initializer?.getText(node)!;


const getAllChildren = (tsNode: ts.Node): Array<ts.Node> => {
const childItems = tsNode.getChildren(node);
if (childItems.length) {
const allChildren = childItems.map(getAllChildren);
return [tsNode].concat(allChildren.flat());
}
return [tsNode];
}

const children = getAllChildren(method);
const jsDoc = children.filter((c) => c.kind === ts.SyntaxKind.JSDoc).map((c) => {
return (c as JSDoc).comment
});
const hasDeprecated = children
.some((c) => c.kind === ts.SyntaxKind.JSDocDeprecatedTag);

return httpMethodName === "'GET'"
? createUseQuery(node, className, method)
: createUseMutation(node, className, method);
? createUseQuery(node, className, method, jsDoc, hasDeprecated)
: createUseMutation(node, className, method, jsDoc, hasDeprecated);
})
.flat();
})
Expand Down
192 changes: 92 additions & 100 deletions src/createUseMutation.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import ts from "typescript";
import { capitalizeFirstLetter } from "./common";
import { addJSDocToNode } from "./util";

export const createUseMutation = (
node: ts.SourceFile,
className: string,
method: ts.MethodDeclaration
method: ts.MethodDeclaration,
jsDoc: (string | ts.NodeArray<ts.JSDocComment> | undefined)[] = [],
deprecated: boolean = false
) => {
const methodName = method.name?.getText(node)!;
// Awaited<ReturnType<typeof myClass.myMethod>>
Expand All @@ -26,11 +29,22 @@ export const createUseMutation = (
]
);

const TData = ts.factory.createIdentifier("TData");
const TError = ts.factory.createIdentifier("TError");
const TContext = ts.factory.createIdentifier("TContext");

const mutationResult = ts.factory.createTypeAliasDeclaration(
[ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)],
ts.factory.createIdentifier(`${className}${methodName}MutationResult`),
undefined,
awaitedResponseDataType
);

const responseDataType = ts.factory.createTypeParameterDeclaration(
undefined,
"TData",
TData,
undefined,
awaitedResponseDataType
ts.factory.createTypeReferenceNode(mutationResult.name)
);

const methodParameters =
Expand All @@ -49,7 +63,7 @@ export const createUseMutation = (
)
: ts.factory.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword);

return ts.factory.createVariableStatement(
const exportHook = ts.factory.createVariableStatement(
[ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)],
ts.factory.createVariableDeclarationList(
[
Expand All @@ -65,13 +79,13 @@ export const createUseMutation = (
responseDataType,
ts.factory.createTypeParameterDeclaration(
undefined,
"TError",
TError,
undefined,
ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword)
),
ts.factory.createTypeParameterDeclaration(
undefined,
"TContext",
TContext,
undefined,
ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword)
),
Expand All @@ -88,14 +102,10 @@ export const createUseMutation = (
ts.factory.createTypeReferenceNode(
ts.factory.createIdentifier("UseMutationOptions"),
[
awaitedResponseDataType,
ts.factory.createKeywordTypeNode(
ts.SyntaxKind.UnknownKeyword
),
ts.factory.createTypeReferenceNode(TData),
ts.factory.createTypeReferenceNode(TError),
methodParameters,
ts.factory.createKeywordTypeNode(
ts.SyntaxKind.UnknownKeyword
),
ts.factory.createTypeReferenceNode(TContext),
]
),
ts.factory.createLiteralTypeNode(
Expand All @@ -108,106 +118,88 @@ export const createUseMutation = (
],
undefined,
ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
ts.factory.createAsExpression(
ts.factory.createCallExpression(
ts.factory.createIdentifier("useMutation"),
undefined,
[
ts.factory.createObjectLiteralExpression([
ts.factory.createPropertyAssignment(
ts.factory.createIdentifier("mutationFn"),
ts.factory.createArrowFunction(
undefined,
undefined,
method.parameters.length !== 0
? [
ts.factory.createParameterDeclaration(
undefined,
undefined,
ts.factory.createObjectBindingPattern(
method.parameters.map((param) => {
return ts.factory.createBindingElement(
undefined,
undefined,
ts.factory.createIdentifier(
param.name.getText(node)
),
undefined
);
})
),
undefined,
undefined,
undefined
ts.factory.createCallExpression(
ts.factory.createIdentifier("useMutation"),
[
ts.factory.createTypeReferenceNode(TData),
ts.factory.createTypeReferenceNode(TError),
methodParameters,
ts.factory.createTypeReferenceNode(TContext),
],
[
ts.factory.createObjectLiteralExpression([
ts.factory.createPropertyAssignment(
ts.factory.createIdentifier("mutationFn"),
ts.factory.createArrowFunction(
undefined,
undefined,
method.parameters.length !== 0
? [
ts.factory.createParameterDeclaration(
undefined,
undefined,
ts.factory.createObjectBindingPattern(
method.parameters.map((param) => {
return ts.factory.createBindingElement(
undefined,
undefined,
ts.factory.createIdentifier(
param.name.getText(node)
),
undefined
);
})
),
]
: [],
undefined,
ts.factory.createToken(
ts.SyntaxKind.EqualsGreaterThanToken
),
ts.factory.createCallExpression(
ts.factory.createPropertyAccessExpression(
ts.factory.createIdentifier(className),
ts.factory.createIdentifier(methodName)
),
undefined,
method.parameters.map((params) =>
ts.factory.createIdentifier(
params.name.getText(node)
undefined,
undefined,
undefined
),
]
: [],
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.parameters.map((params) =>
ts.factory.createIdentifier(
params.name.getText(node)
)
)
),
ts.factory.createKeywordTypeNode(
ts.SyntaxKind.UnknownKeyword
)
)
)
),
ts.factory.createSpreadAssignment(
ts.factory.createIdentifier("options")
),
]),
]
),
// Omit<UseMutationResult<Awaited<ReturnType<typeof myClass.myMethod>>, TError, params, TContext>, 'data'> & { data: TData };
ts.factory.createIntersectionTypeNode([
ts.factory.createTypeReferenceNode(
ts.factory.createIdentifier("Omit"),
[
ts.factory.createTypeReferenceNode(
ts.factory.createIdentifier("UseMutationResult"),
[
awaitedResponseDataType,
ts.factory.createTypeReferenceNode(
ts.factory.createIdentifier("TError"),
undefined
),
methodParameters,

ts.factory.createTypeReferenceNode(
ts.factory.createIdentifier("TContext"),
undefined
),
]
),
ts.factory.createLiteralTypeNode(
ts.factory.createStringLiteral("data")
),
]
),
ts.factory.createTypeLiteralNode([
ts.factory.createPropertySignature(
undefined,
ts.factory.createIdentifier("data"),
undefined,
ts.factory.createTypeReferenceNode(
ts.factory.createIdentifier("TData"),
undefined
ts.factory.createIdentifier("Promise"),
[ts.factory.createTypeReferenceNode(TData)]
)
)
)
),
ts.factory.createSpreadAssignment(
ts.factory.createIdentifier("options")
),
]),
])
]
)
)
),
],
ts.NodeFlags.Const
)
);

const hookWithJsDoc = addJSDocToNode(exportHook, node, deprecated, jsDoc);

return [mutationResult, hookWithJsDoc];
};
Loading