Skip to content

Commit

Permalink
patch(return types): union return type with undefined. (#33)
Browse files Browse the repository at this point in the history
  • Loading branch information
seriouslag authored Jan 3, 2024
1 parent e4cc854 commit af2fea7
Show file tree
Hide file tree
Showing 6 changed files with 387 additions and 303 deletions.
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

0 comments on commit af2fea7

Please sign in to comment.