Skip to content

Commit

Permalink
feat: Add ensureQueryData support (#150)
Browse files Browse the repository at this point in the history
  • Loading branch information
7nohe authored Oct 6, 2024
1 parent 58a6089 commit be5cd9d
Show file tree
Hide file tree
Showing 20 changed files with 8,596 additions and 4,604 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
## Features

- Generates custom react hooks that use React Query's `useQuery`, `useSuspenseQuery`, `useMutation` and `useInfiniteQuery` hooks
- Generates custom functions that use React Query's `ensureQueryData` and `prefetchQuery` functions
- Generates query keys and functions for query caching
- Generates pure TypeScript clients generated by [@hey-api/openapi-ts](https://github.com/hey-api/openapi-ts)

Expand Down Expand Up @@ -79,9 +80,10 @@ $ openapi-rq -i ./petstore.yaml
- queries
- index.ts <- main file that exports common types, variables, and queries. Does not export suspense or prefetch hooks
- common.ts <- common types
- ensureQueryData.ts <- generated ensureQueryData functions
- queries.ts <- generated query hooks
- suspenses.ts <- generated suspense hooks
- prefetch.ts <- generated prefetch hooks learn more about prefetching in in link below
- prefetch.ts <- generated prefetch functions learn more about prefetching in in link below
- requests <- output code generated by @hey-api/openapi-ts
```

Expand Down
2 changes: 2 additions & 0 deletions biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
"coverage",
"examples/nextjs-app/openapi",
"examples/nextjs-app/.next",
"examples/tanstack-router-app/openapi",
"examples/tanstack-router-app/src/routeTree.gen.ts",
".vscode"
]
},
Expand Down
18 changes: 18 additions & 0 deletions examples/tanstack-router-app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Local
.DS_Store
*.local
*.log*

# Dist
node_modules
dist/
.vinxi
.output
.vercel
.netlify
.wrangler

# IDE
.vscode/*
!.vscode/extensions.json
.idea
24 changes: 24 additions & 0 deletions examples/tanstack-router-app/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
<script src="https://cdn.tailwindcss.com"></script>
<style type="text/tailwindcss">
html {
color-scheme: light dark;
}
* {
@apply border-gray-200 dark:border-gray-800;
}
body {
@apply bg-gray-50 text-gray-950 dark:bg-gray-900 dark:text-gray-200;
}
</style>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
36 changes: 36 additions & 0 deletions examples/tanstack-router-app/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"name": "tanstack-router-app",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "run-p dev:mock dev:tanstack",
"dev:mock": "prism mock ../petstore.yaml --dynamic",
"typecheck": "tsc --noEmit",
"dev:tanstack": "vite --port=3001",
"build": "vite build",
"serve": "vite preview",
"start": "vite",
"generate:api": "rimraf ./openapi && node ../../dist/cli.mjs -i ../petstore.yaml -c axios --request ./request.ts --format=biome --lint=biome"
},
"devDependencies": {
"@stoplight/prism-cli": "^5.5.2",
"@tanstack/router-plugin": "^1.58.4",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.1",
"npm-run-all": "^4.1.5",
"vite": "^5.4.4"
},
"dependencies": {
"@tanstack/react-query": "^5.32.1",
"@tanstack/react-query-devtools": "^5.32.1",
"@tanstack/react-router": "^1.58.7",
"@tanstack/react-router-with-query": "^1.58.7",
"@tanstack/router-devtools": "^1.58.7",
"@tanstack/start": "^1.58.7",
"axios": "^1.6.7",
"react": "^18.3.1",
"react-dom": "^18.3.1"
}
}
94 changes: 94 additions & 0 deletions examples/tanstack-router-app/request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import axios from "axios";
import type { RawAxiosRequestHeaders } from "axios";

import type { ApiRequestOptions } from "./ApiRequestOptions";
import { CancelablePromise } from "./CancelablePromise";
import type { OpenAPIConfig } from "./OpenAPI";

// Optional: Get and link the cancelation token, so the request can be aborted.
const source = axios.CancelToken.source();

const axiosInstance = axios.create({
// Your custom Axios instance config
baseURL: "http://localhost:4010",
headers: {
// Your custom headers
} satisfies RawAxiosRequestHeaders,
});

// Add a request interceptor
axiosInstance.interceptors.request.use(
(config) => {
// Do something before request is sent
if (!config.url || !config.params) {
return config;
}

for (const [key, value] of Object.entries<string>(config.params)) {
const stringToSearch = `{${key}}`;
if (
config.url !== undefined &&
config.url.search(stringToSearch) !== -1
) {
config.url = config.url.replace(`{${key}}`, encodeURIComponent(value));
delete config.params[key];
}
}

return config;
},
(error) => {
// Do something with request error
return Promise.reject(error);
},
);

// Add a response interceptor
axiosInstance.interceptors.response.use(
(response) => {
// Any status code that lie within the range of 2xx cause this function to trigger
// Do something with response data
return response;
},
(error) => {
// Any status codes that falls outside the range of 2xx cause this function to trigger
// Do something with response error
return Promise.reject(error);
},
);

export const request = <T>(
config: OpenAPIConfig,
options: ApiRequestOptions,
): CancelablePromise<T> => {
return new CancelablePromise((resolve, reject, onCancel) => {
onCancel(() => source.cancel("The user aborted a request."));

let formattedHeaders = options.headers as RawAxiosRequestHeaders;
if (options.mediaType) {
formattedHeaders = {
...options.headers,
"Content-Type": options.mediaType,
} satisfies RawAxiosRequestHeaders;
}

return axiosInstance
.request({
url: options.url,
data: options.body,
method: options.method,
params: {
...options.query,
...options.path,
},
headers: formattedHeaders,
cancelToken: source.token,
})
.then((res) => {
resolve(res.data);
})
.catch((error) => {
reject(error);
});
});
};
44 changes: 44 additions & 0 deletions examples/tanstack-router-app/src/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { RouterProvider, createRouter } from "@tanstack/react-router";
import React from "react";
import ReactDOM from "react-dom/client";
import { routeTree } from "./routeTree.gen";

const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000,
},
},
});

// Set up a Router instance
const router = createRouter({
routeTree,
defaultPreload: "intent",
// Since we're using React Query, we don't want loader calls to ever be stale
// This will ensure that the loader is always called when the route is preloaded or visited
defaultPreloadStaleTime: 0,
context: {
queryClient,
},
});

// Register things for typesafety
declare module "@tanstack/react-router" {
interface Register {
router: typeof router;
}
}

// biome-ignore lint/style/noNonNullAssertion: This is a demo app
const rootElement = document.getElementById("app")!;

if (!rootElement.innerHTML) {
const root = ReactDOM.createRoot(rootElement);
root.render(
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} />
</QueryClientProvider>,
);
}
111 changes: 111 additions & 0 deletions examples/tanstack-router-app/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/* prettier-ignore-start */

/* eslint-disable */

// @ts-nocheck

// noinspection JSUnusedGlobalSymbols

// This file is auto-generated by TanStack Router

// Import Routes

import { Route as rootRoute } from "./routes/__root";
import { Route as AboutImport } from "./routes/about";
import { Route as IndexImport } from "./routes/index";

// Create/Update Routes

const AboutRoute = AboutImport.update({
path: "/about",
getParentRoute: () => rootRoute,
} as any);

const IndexRoute = IndexImport.update({
path: "/",
getParentRoute: () => rootRoute,
} as any);

// Populate the FileRoutesByPath interface

declare module "@tanstack/react-router" {
interface FileRoutesByPath {
"/": {
id: "/";
path: "/";
fullPath: "/";
preLoaderRoute: typeof IndexImport;
parentRoute: typeof rootRoute;
};
"/about": {
id: "/about";
path: "/about";
fullPath: "/about";
preLoaderRoute: typeof AboutImport;
parentRoute: typeof rootRoute;
};
}
}

// Create and export the route tree

export interface FileRoutesByFullPath {
"/": typeof IndexRoute;
"/about": typeof AboutRoute;
}

export interface FileRoutesByTo {
"/": typeof IndexRoute;
"/about": typeof AboutRoute;
}

export interface FileRoutesById {
__root__: typeof rootRoute;
"/": typeof IndexRoute;
"/about": typeof AboutRoute;
}

export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath;
fullPaths: "/" | "/about";
fileRoutesByTo: FileRoutesByTo;
to: "/" | "/about";
id: "__root__" | "/" | "/about";
fileRoutesById: FileRoutesById;
}

export interface RootRouteChildren {
IndexRoute: typeof IndexRoute;
AboutRoute: typeof AboutRoute;
}

const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
AboutRoute: AboutRoute,
};

export const routeTree = rootRoute
._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>();

/* prettier-ignore-end */

/* ROUTE_MANIFEST_START
{
"routes": {
"__root__": {
"filePath": "__root.tsx",
"children": [
"/",
"/about"
]
},
"/": {
"filePath": "index.tsx"
},
"/about": {
"filePath": "about.tsx"
}
}
}
ROUTE_MANIFEST_END */
Loading

0 comments on commit be5cd9d

Please sign in to comment.