Skip to content

Commit e0b7423

Browse files
committed
Merge branch 'v1' into fix/operation-id
2 parents ef2157e + 03001cb commit e0b7423

31 files changed

+8955
-4645
lines changed

README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
## Features
88

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

@@ -79,9 +80,10 @@ $ openapi-rq -i ./petstore.yaml
7980
- queries
8081
- index.ts <- main file that exports common types, variables, and queries. Does not export suspense or prefetch hooks
8182
- common.ts <- common types
83+
- ensureQueryData.ts <- generated ensureQueryData functions
8284
- queries.ts <- generated query hooks
8385
- suspenses.ts <- generated suspense hooks
84-
- prefetch.ts <- generated prefetch hooks learn more about prefetching in in link below
86+
- prefetch.ts <- generated prefetch functions learn more about prefetching in in link below
8587
- requests <- output code generated by @hey-api/openapi-ts
8688
```
8789

biome.json

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
"coverage",
1111
"examples/nextjs-app/openapi",
1212
"examples/nextjs-app/.next",
13+
"examples/tanstack-router-app/openapi",
14+
"examples/tanstack-router-app/src/routeTree.gen.ts",
1315
".vscode"
1416
]
1517
},

examples/nextjs-app/app/components/PaginatedPets.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export default function PaginatedPets() {
2222
</React.Fragment>
2323
))}
2424
</ul>
25-
{data?.pages.at(-1)?.nextPage && (
25+
{data?.pages.at(-1)?.meta?.next && (
2626
<button
2727
type="button"
2828
onClick={() => fetchNextPage()}

examples/nextjs-app/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"build": "next build",
1010
"start": "next start",
1111
"lint": "next lint",
12-
"generate:api": "rimraf ./openapi && node ../../dist/cli.mjs -i ../petstore.yaml -c axios --request ./request.ts --format=biome --lint=biome --operationId"
12+
"generate:api": "rimraf ./openapi && node ../../dist/cli.mjs -i ../petstore.yaml -c axios --request ./request.ts --format=biome --lint=biome --operationId --nextPageParam=meta.next"
1313
},
1414
"dependencies": {
1515
"@tanstack/react-query": "^5.32.1",

examples/petstore.yaml

+56-4
Original file line numberDiff line numberDiff line change
@@ -176,10 +176,62 @@ paths:
176176
type: array
177177
items:
178178
$ref: '#/components/schemas/Pet'
179-
nextPage:
180-
type: integer
181-
format: int32
182-
minimum: 1
179+
meta:
180+
type: object
181+
properties:
182+
next:
183+
type: integer
184+
format: int32
185+
minimum: 1
186+
total:
187+
type: integer
188+
/cursor-based-pets:
189+
get:
190+
description: |
191+
Returns cursor-based pets from the system that the user has access to
192+
operationId: findCursorBasedPets
193+
parameters:
194+
- name: page
195+
in: query
196+
description: string to start from
197+
required: false
198+
schema:
199+
type: string
200+
- name: tags
201+
in: query
202+
description: tags to filter by
203+
required: false
204+
style: form
205+
schema:
206+
type: array
207+
items:
208+
type: string
209+
- name: limit
210+
in: query
211+
description: maximum number of results to return
212+
required: false
213+
schema:
214+
type: integer
215+
format: int32
216+
responses:
217+
'200':
218+
description: pet response
219+
content:
220+
application/json:
221+
schema:
222+
type: object
223+
properties:
224+
pets:
225+
type: array
226+
items:
227+
$ref: '#/components/schemas/Pet'
228+
meta:
229+
type: object
230+
properties:
231+
next:
232+
type: string
233+
total:
234+
type: integer
183235

184236
components:
185237
schemas:

examples/react-app/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"dev:mock": "prism mock ../petstore.yaml --dynamic",
1010
"build": "tsc && vite build",
1111
"preview": "vite preview",
12-
"generate:api": "rimraf ./openapi && node ../../dist/cli.mjs -i ../petstore.yaml -c axios --request ./request.ts --format=biome --lint=biome --operationId",
12+
"generate:api": "rimraf ./openapi && node ../../dist/cli.mjs -i ../petstore.yaml -c axios --request ./request.ts --format=biome --lint=biome --operationId --nextPageParam=meta.next",
1313
"test:generated": "tsc -p ./tsconfig.openapi.json --noEmit"
1414
},
1515
"dependencies": {
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Local
2+
.DS_Store
3+
*.local
4+
*.log*
5+
6+
# Dist
7+
node_modules
8+
dist/
9+
.vinxi
10+
.output
11+
.vercel
12+
.netlify
13+
.wrangler
14+
15+
# IDE
16+
.vscode/*
17+
!.vscode/extensions.json
18+
.idea
+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Vite App</title>
7+
<script src="https://cdn.tailwindcss.com"></script>
8+
<style type="text/tailwindcss">
9+
html {
10+
color-scheme: light dark;
11+
}
12+
* {
13+
@apply border-gray-200 dark:border-gray-800;
14+
}
15+
body {
16+
@apply bg-gray-50 text-gray-950 dark:bg-gray-900 dark:text-gray-200;
17+
}
18+
</style>
19+
</head>
20+
<body>
21+
<div id="app"></div>
22+
<script type="module" src="/src/main.tsx"></script>
23+
</body>
24+
</html>
+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"name": "tanstack-router-app",
3+
"version": "0.0.0",
4+
"private": true,
5+
"type": "module",
6+
"scripts": {
7+
"dev": "run-p dev:mock dev:tanstack",
8+
"dev:mock": "prism mock ../petstore.yaml --dynamic",
9+
"typecheck": "tsc --noEmit",
10+
"dev:tanstack": "vite --port=3001",
11+
"build": "vite build",
12+
"serve": "vite preview",
13+
"start": "vite",
14+
"generate:api": "rimraf ./openapi && node ../../dist/cli.mjs -i ../petstore.yaml -c axios --request ./request.ts --format=biome --lint=biome"
15+
},
16+
"devDependencies": {
17+
"@stoplight/prism-cli": "^5.5.2",
18+
"@tanstack/router-plugin": "^1.58.4",
19+
"@types/react": "^18.3.3",
20+
"@types/react-dom": "^18.3.0",
21+
"@vitejs/plugin-react": "^4.3.1",
22+
"npm-run-all": "^4.1.5",
23+
"vite": "^5.4.4"
24+
},
25+
"dependencies": {
26+
"@tanstack/react-query": "^5.32.1",
27+
"@tanstack/react-query-devtools": "^5.32.1",
28+
"@tanstack/react-router": "^1.58.7",
29+
"@tanstack/react-router-with-query": "^1.58.7",
30+
"@tanstack/router-devtools": "^1.58.7",
31+
"@tanstack/start": "^1.58.7",
32+
"axios": "^1.6.7",
33+
"react": "^18.3.1",
34+
"react-dom": "^18.3.1"
35+
}
36+
}
+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import axios from "axios";
2+
import type { RawAxiosRequestHeaders } from "axios";
3+
4+
import type { ApiRequestOptions } from "./ApiRequestOptions";
5+
import { CancelablePromise } from "./CancelablePromise";
6+
import type { OpenAPIConfig } from "./OpenAPI";
7+
8+
// Optional: Get and link the cancelation token, so the request can be aborted.
9+
const source = axios.CancelToken.source();
10+
11+
const axiosInstance = axios.create({
12+
// Your custom Axios instance config
13+
baseURL: "http://localhost:4010",
14+
headers: {
15+
// Your custom headers
16+
} satisfies RawAxiosRequestHeaders,
17+
});
18+
19+
// Add a request interceptor
20+
axiosInstance.interceptors.request.use(
21+
(config) => {
22+
// Do something before request is sent
23+
if (!config.url || !config.params) {
24+
return config;
25+
}
26+
27+
for (const [key, value] of Object.entries<string>(config.params)) {
28+
const stringToSearch = `{${key}}`;
29+
if (
30+
config.url !== undefined &&
31+
config.url.search(stringToSearch) !== -1
32+
) {
33+
config.url = config.url.replace(`{${key}}`, encodeURIComponent(value));
34+
delete config.params[key];
35+
}
36+
}
37+
38+
return config;
39+
},
40+
(error) => {
41+
// Do something with request error
42+
return Promise.reject(error);
43+
},
44+
);
45+
46+
// Add a response interceptor
47+
axiosInstance.interceptors.response.use(
48+
(response) => {
49+
// Any status code that lie within the range of 2xx cause this function to trigger
50+
// Do something with response data
51+
return response;
52+
},
53+
(error) => {
54+
// Any status codes that falls outside the range of 2xx cause this function to trigger
55+
// Do something with response error
56+
return Promise.reject(error);
57+
},
58+
);
59+
60+
export const request = <T>(
61+
config: OpenAPIConfig,
62+
options: ApiRequestOptions,
63+
): CancelablePromise<T> => {
64+
return new CancelablePromise((resolve, reject, onCancel) => {
65+
onCancel(() => source.cancel("The user aborted a request."));
66+
67+
let formattedHeaders = options.headers as RawAxiosRequestHeaders;
68+
if (options.mediaType) {
69+
formattedHeaders = {
70+
...options.headers,
71+
"Content-Type": options.mediaType,
72+
} satisfies RawAxiosRequestHeaders;
73+
}
74+
75+
return axiosInstance
76+
.request({
77+
url: options.url,
78+
data: options.body,
79+
method: options.method,
80+
params: {
81+
...options.query,
82+
...options.path,
83+
},
84+
headers: formattedHeaders,
85+
cancelToken: source.token,
86+
})
87+
.then((res) => {
88+
resolve(res.data);
89+
})
90+
.catch((error) => {
91+
reject(error);
92+
});
93+
});
94+
};
+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
2+
import { RouterProvider, createRouter } from "@tanstack/react-router";
3+
import React from "react";
4+
import ReactDOM from "react-dom/client";
5+
import { routeTree } from "./routeTree.gen";
6+
7+
const queryClient = new QueryClient({
8+
defaultOptions: {
9+
queries: {
10+
staleTime: 60 * 1000,
11+
},
12+
},
13+
});
14+
15+
// Set up a Router instance
16+
const router = createRouter({
17+
routeTree,
18+
defaultPreload: "intent",
19+
// Since we're using React Query, we don't want loader calls to ever be stale
20+
// This will ensure that the loader is always called when the route is preloaded or visited
21+
defaultPreloadStaleTime: 0,
22+
context: {
23+
queryClient,
24+
},
25+
});
26+
27+
// Register things for typesafety
28+
declare module "@tanstack/react-router" {
29+
interface Register {
30+
router: typeof router;
31+
}
32+
}
33+
34+
// biome-ignore lint/style/noNonNullAssertion: This is a demo app
35+
const rootElement = document.getElementById("app")!;
36+
37+
if (!rootElement.innerHTML) {
38+
const root = ReactDOM.createRoot(rootElement);
39+
root.render(
40+
<QueryClientProvider client={queryClient}>
41+
<RouterProvider router={router} />
42+
</QueryClientProvider>,
43+
);
44+
}

0 commit comments

Comments
 (0)