Skip to content

Commit

Permalink
feat(prefetch): add prefetch query hook generation (#92)
Browse files Browse the repository at this point in the history
  • Loading branch information
seriouslag committed Apr 28, 2024
1 parent cf175e6 commit c7f76ec
Show file tree
Hide file tree
Showing 11 changed files with 340 additions and 28 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,16 @@ $ openapi-rq -i ./petstore.yaml
```
- openapi
- queries
- index.ts <- main file that exports common types, variables, and hooks
- index.ts <- main file that exports common types, variables, and queries. Does not export suspense or prefetch hooks
- common.ts <- common types
- queries.ts <- generated query hooks
- suspenses.ts <- generated suspense hooks
- prefetch.ts <- generated prefetch hooks learn more about prefetching in in link below
- requests <- output code generated by @hey-api/openapi-ts
```

- [Prefetching docs](https://tanstack.com/query/latest/docs/framework/react/guides/advanced-ssr#prefetching-and-dehydrating-data)

### In your app

#### Using the generated hooks
Expand Down
21 changes: 14 additions & 7 deletions examples/react-app/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,18 @@ import App from "./App";
import "./index.css";
import { QueryClientProvider } from "@tanstack/react-query";
import { queryClient } from "./queryClient";
import { prefetchUseDefaultServiceFindPets } from "../openapi/queries/prefetch";

ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</React.StrictMode>
);
async function PrefetchData() {
await prefetchUseDefaultServiceFindPets(queryClient);
}

PrefetchData().then(() => {
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</React.StrictMode>
);
});
8 changes: 8 additions & 0 deletions src/constants.mts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,11 @@ export const requestsOutputPath = "requests";

export const serviceFileName = "services.gen";
export const modalsFileName = "types.gen";

export const OpenApiRqFiles = {
queries: "queries",
common: "common",
suspense: "suspense",
index: "index",
prefetch: "prefetch",
} as const;
12 changes: 12 additions & 0 deletions src/createExports.mts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createUseQuery } from "./createUseQuery.mjs";
import { createUseMutation } from "./createUseMutation.mjs";
import { Service } from "./service.mjs";
import { createPrefetch } from "./createPrefetch.mjs";

export const createExports = (service: Service) => {
const { klasses } = service;
Expand All @@ -23,6 +24,7 @@ export const createExports = (service: Service) => {
);

const allGetQueries = allGet.map((m) => createUseQuery(m));
const allPrefetchQueries = allGet.map((m) => createPrefetch(m));

const allPostMutations = allPost.map((m) => createUseMutation(m));
const allPutMutations = allPut.map((m) => createUseMutation(m));
Expand Down Expand Up @@ -59,6 +61,12 @@ export const createExports = (service: Service) => {

const suspenseExports = [...suspenseQueries];

const allPrefetches = allPrefetchQueries
.map(({ prefetchHook }) => [prefetchHook])
.flat();

const allPrefetchExports = [...allPrefetches];

return {
/**
* Common types and variables between queries (regular and suspense) and mutations
Expand All @@ -72,5 +80,9 @@ export const createExports = (service: Service) => {
* Suspense exports are the hooks that are used in the suspense components
*/
suspenseExports,
/**
* Prefetch exports are the hooks that are used in the prefetch components
*/
allPrefetchExports,
};
};
159 changes: 159 additions & 0 deletions src/createPrefetch.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import ts from "typescript";
import { MethodDeclaration } from "ts-morph";
import {
BuildCommonTypeName,
extractPropertiesFromObjectParam,
getNameFromMethod,
} from "./common.mjs";
import { type MethodDescription } from "./common.mjs";
import {
createQueryKeyFromMethod,
getRequestParamFromMethod,
hookNameFromMethod,
} from "./createUseQuery.mjs";
import { addJSDocToNode } from "./util.mjs";

/**
* Creates a prefetch function for a query
*/
function createPrefetchHook({
requestParams,
method,
className,
}: {
requestParams: ts.ParameterDeclaration[];
method: MethodDeclaration;
className: string;
}) {
const methodName = getNameFromMethod(method);
const queryName = hookNameFromMethod({ method, className });
const customHookName = `prefetch${queryName.charAt(0).toUpperCase() + queryName.slice(1)}`;
const queryKey = createQueryKeyFromMethod({ method, className });

// const
const hookExport = ts.factory.createVariableStatement(
// export
[ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)],
ts.factory.createVariableDeclarationList(
[
ts.factory.createVariableDeclaration(
ts.factory.createIdentifier(customHookName),
undefined,
undefined,
ts.factory.createArrowFunction(
undefined,
undefined,
[
ts.factory.createParameterDeclaration(
undefined,
undefined,
"queryClient",
undefined,
ts.factory.createTypeReferenceNode(
ts.factory.createIdentifier("QueryClient")
)
),
...requestParams,
],
undefined,
ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
ts.factory.createCallExpression(
ts.factory.createIdentifier("queryClient.prefetchQuery"),
undefined,
[
ts.factory.createObjectLiteralExpression([
ts.factory.createPropertyAssignment(
ts.factory.createIdentifier("queryKey"),
ts.factory.createArrayLiteralExpression(
[
BuildCommonTypeName(queryKey),
method.getParameters().length
? ts.factory.createArrayLiteralExpression([
ts.factory.createObjectLiteralExpression(
method
.getParameters()
.map((param) =>
extractPropertiesFromObjectParam(param).map(
(p) =>
ts.factory.createShorthandPropertyAssignment(
ts.factory.createIdentifier(p.name)
)
)
)
.flat()
),
])
: ts.factory.createArrayLiteralExpression([]),
],
false
)
),
ts.factory.createPropertyAssignment(
ts.factory.createIdentifier("queryFn"),
ts.factory.createArrowFunction(
undefined,
undefined,
[],
undefined,
ts.factory.createToken(
ts.SyntaxKind.EqualsGreaterThanToken
),
ts.factory.createCallExpression(
ts.factory.createPropertyAccessExpression(
ts.factory.createIdentifier(className),
ts.factory.createIdentifier(methodName)
),
undefined,
method.getParameters().length
? [
ts.factory.createObjectLiteralExpression(
method
.getParameters()
.map((param) =>
extractPropertiesFromObjectParam(param).map(
(p) =>
ts.factory.createShorthandPropertyAssignment(
ts.factory.createIdentifier(p.name)
)
)
)
.flat()
),
]
: undefined
)
)
),
]),
]
)
)
),
],
ts.NodeFlags.Const
)
);
return hookExport;
}

export const createPrefetch = ({
className,
method,
jsDoc,
}: MethodDescription) => {
const requestParam = getRequestParamFromMethod(method);

const requestParams = requestParam ? [requestParam] : [];

const prefetchHook = createPrefetchHook({
requestParams,
method,
className,
});

const hookWithJsDoc = addJSDocToNode(prefetchHook, jsDoc);

return {
prefetchHook: hookWithJsDoc,
};
};

0 comments on commit c7f76ec

Please sign in to comment.