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

feat: Add ensureQueryData support #150

Merged
merged 3 commits into from
Oct 6, 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
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