Skip to content

Commit

Permalink
feat: Create a docs site (#162)
Browse files Browse the repository at this point in the history
  • Loading branch information
7nohe authored Oct 14, 2024
1 parent fd82abb commit f9021da
Show file tree
Hide file tree
Showing 30 changed files with 4,316 additions and 2,366 deletions.
4 changes: 4 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"recommendations": ["astro-build.astro-vscode"],
"unwantedRecommendations": []
}
335 changes: 1 addition & 334 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# OpenAPI React Query Codegen

> Node.js library that generates [React Query (also called TanStack Query)](https://tanstack.com/query) hooks based on an OpenAPI specification file.
> Code generator for creating [React Query (also known as TanStack Query)](https://tanstack.com/query) hooks based on your OpenAPI schema.
[![npm version](https://badge.fury.io/js/%407nohe%2Fopenapi-react-query-codegen.svg)](https://badge.fury.io/js/%407nohe%2Fopenapi-react-query-codegen)

Expand All @@ -10,336 +10,3 @@
- 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)

## Installation

```
$ npm install -D @7nohe/openapi-react-query-codegen
```

Register the command to the `scripts` property in your package.json file.

```json
{
"scripts": {
"codegen": "openapi-rq -i ./petstore.yaml -c @hey-api/client-fetch"
}
}
```

You can also run the command without installing it in your project using the npx command.

```bash
$ npx --package @7nohe/openapi-react-query-codegen openapi-rq -i ./petstore.yaml -c @hey-api/client-fetch
```

## Usage

```
$ openapi-rq --help
Usage: openapi-rq [options]
Generate React Query code based on OpenAPI
Options:
-V, --version output the version number
-i, --input <value> OpenAPI specification, can be a path, url or string content (required)
-o, --output <value> Output directory (default: "openapi")
-c, --client <value> HTTP client to generate (choices: "@hey-api/client-fetch", "@hey-api/client-axios", default: "@hey-api/client-fetch")
--format <value> Process output folder with formatter? (choices: "biome", "prettier")
--lint <value> Process output folder with linter? (choices: "biome", "eslint")
--operationId Use operation ID to generate operation names?
--serviceResponse <value> Define shape of returned value from service calls (choices: "body", "response", default: "body")
--enums <value> Generate JavaScript objects from enum definitions? (choices: "javascript", "typescript")
--useDateType Use Date type instead of string for date types for models, this will not convert the data to a Date object
--debug Run in debug mode?
--noSchemas Disable generating JSON schemas
--schemaType <value> Type of JSON schema [Default: 'json'] (choices: "form", "json")
--pageParam <value> Name of the query parameter used for pagination (default: "page")
--nextPageParam <value> Name of the response parameter used for next page (default: "nextPage")
--initialPageParam <value> Initial page value to query (default: "initialPageParam")
-h, --help display help for command
```

### Example Usage

#### Command

```
$ openapi-rq -i ./petstore.yaml
```

#### Output directory structure

```
- openapi
- 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 functions 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

```tsx
// App.tsx
import { useFindPets } from "../openapi/queries";
function App() {
const { data } = useFindPets();

return (
<div className="App">
<h1>Pet List</h1>
<ul>{data?.map((pet) => <li key={pet.id}>{pet.name}</li>)}</ul>
</div>
);
}

export default App;
```

##### Using the generated typescript client

```tsx
import { useQuery } from "@tanstack/react-query";
import { findPets } from "../openapi/requests/services.gen";
import { useFindPetsKey } from "../openapi/queries";

function App() {
// You can still use the auto-generated query key
const { data } = useQuery({
queryKey: [useFindPetsKey],
queryFn: () => {
// Do something here
return findPets();
},
});

return <div className="App">{/* .... */}</div>;
}

export default App;
```

##### Using Suspense Hooks

```tsx
// App.tsx
import { useFindPetsSuspense } from "../openapi/queries/suspense";
function ChildComponent() {
const { data } = useFindPetsSuspense({
query: { tags: [], limit: 10 },
});

return <ul>{data?.map((pet, index) => <li key={pet.id}>{pet.name}</li>)}</ul>;
}

function ParentComponent() {
return (
<>
<Suspense fallback={<>loading...</>}>
<ChildComponent />
</Suspense>
</>
);
}

function App() {
return (
<div className="App">
<h1>Pet List</h1>
<ParentComponent />
</div>
);
}

export default App;
```

##### Using Mutation hooks

```tsx
// App.tsx
import { useAddPet } from "../openapi/queries";

function App() {
const { mutate } = useAddPet();

const handleAddPet = () => {
mutate({ body: { name: "Fluffy" } });
};

return (
<div className="App">
<h1>Add Pet</h1>
<button onClick={handleAddPet}>Add Pet</button>
</div>
);
}

export default App;
```

##### Invalidating queries after mutation

Invalidating queries after a mutation is important to ensure the cache is updated with the new data. This is done by calling the `queryClient.invalidateQueries` function with the query key used by the query hook.

Learn more about invalidating queries [here](https://tanstack.com/query/latest/docs/framework/react/guides/query-invalidation).

To ensure the query key is created the same way as the query hook, you can use the query key function exported by the generated query hooks.

```tsx
import {
useFindPetsByStatus,
useAddPet,
UseFindPetsByStatusKeyFn,
} from "../openapi/queries";

// App.tsx
function App() {
const [status, setStatus] = React.useState(["available"]);
const { data } = useFindPetsByStatus({ status });
const { mutate } = useAddPet({
onSuccess: () => {
queryClient.invalidateQueries({
// Call the query key function to get the query key
// This is important to ensure the query key is created the same way as the query hook
// This insures the cache is invalidated correctly and is typed correctly
queryKey: [UseFindPetsByStatusKeyFn({
status
})],
});
},
});

return (
<div className="App">
<h1>Pet List</h1>
<ul>{data?.map((pet) => <li key={pet.id}>{pet.name}</li>)}</ul>
<button
onClick={() => {
mutate({ name: "Fluffy", status: "available" });
}}
>
Add Pet
</button>
</div>
);
}

export default App;
```

##### Using Infinite Query hooks

This feature will generate a function in infiniteQueries.ts when the name specified by the `pageParam` option exists in the query parameters and the name specified by the `nextPageParam` option exists in the response.

The `initialPageParam` option can be specified to set the intial page to load, defaults to 1. The `nextPageParam` supports dot notation for nested values (i.e. `meta.next`).

Example Schema:

```yml
paths:
/paginated-pets:
get:
description: |
Returns paginated pets from the system that the user has access to
operationId: findPaginatedPets
parameters:
- name: page
in: query
description: page number
required: false
schema:
type: integer
format: int32
- name: tags
in: query
description: tags to filter by
required: false
style: form
schema:
type: array
items:
type: string
- name: limit
in: query
description: maximum number of results to return
required: false
schema:
type: integer
format: int32
responses:
'200':
description: pet response
content:
application/json:
schema:
type: object
properties:
pets:
type: array
items:
$ref: '#/components/schemas/Pet'
nextPage:
type: integer
format: int32
minimum: 1
```
Usage of Generated Hooks:
```ts
import { useFindPaginatedPetsInfinite } from "@/openapi/queries/infiniteQueries";

const { data, fetchNextPage } = useFindPaginatedPetsInfinite({
query: { tags: [], limit: 10 }
});
```

## Development

### Install dependencies

```bash
pnpm install
```

### Run tests
```bash
pnpm test
```

### Run linter
```bash
pnpm lint
```

### Run linter and fix
```bash
pnpm lint:fix
```

### Update snapshots
```bash
pnpm snapshot
```

### Build example and validate generated code

```bash
npm run build && pnpm --filter @7nohe/react-app generate:api && pnpm --filter @7nohe/react-app test:generated
```

## License

MIT
5 changes: 2 additions & 3 deletions biome.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$schema": "https://biomejs.dev/schemas/1.7.2/schema.json",
"$schema": "https://biomejs.dev/schemas/1.9.3/schema.json",
"organizeImports": {
"enabled": true
},
Expand All @@ -12,8 +12,7 @@
"examples/nextjs-app/openapi",
"examples/nextjs-app/.next",
"examples/tanstack-router-app/openapi",
"examples/tanstack-router-app/src/routeTree.gen.ts",
".vscode"
"examples/tanstack-router-app/src/routeTree.gen.ts"
]
},
"linter": {
Expand Down
21 changes: 21 additions & 0 deletions docs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# build output
dist/
# generated types
.astro/

# dependencies
node_modules/

# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*


# environment variables
.env
.env.production

# macOS-specific files
.DS_Store
Loading

0 comments on commit f9021da

Please sign in to comment.