Skip to content

Commit

Permalink
[IDP-2107] Add React Query Plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
tjosepo committed Aug 26, 2024
1 parent 85e7d61 commit 11edea5
Show file tree
Hide file tree
Showing 29 changed files with 3,114 additions and 417 deletions.
5 changes: 5 additions & 0 deletions .changeset/honest-items-trade.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@workleap/create-schemas": minor
---

Add new load hook for Plugins
5 changes: 5 additions & 0 deletions .changeset/small-cobras-hide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@workleap/create-schemas": minor
---

Add new React Client Plugin
10 changes: 5 additions & 5 deletions docs/retype.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ edit:
branch: main

links:
- text: Github
icon: mark-github
link:https://gsoft-inc.github.io/wl-openapi-typescript
target: blank
- text: Github
icon: mark-github
link: https://gsoft-inc.github.io/wl-openapi-typescript
target: blank

footer:
copyright: "© Copyright {{ year }} - Workleap"
Expand All @@ -37,4 +37,4 @@ hub:
alt: Workleap's IDP homepage

start:
pro: true
pro: true
2 changes: 1 addition & 1 deletion docs/src/javascript-api.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
icon: code
label: JavaScript API
order: -5
order: -6
---

The JavaScript API allows you to use the features of `@workleap/create-schemas`
Expand Down
2 changes: 1 addition & 1 deletion docs/src/msw.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
icon: <svg fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19.8 21.1H5.2a2.7 2.7 0 0 1-2.7-3L4.3 3.7h5.4l10 11.2.1 6.2Zm-12-14-1 10h10.1l-9.1-10Z" fill-opacity=".5"/><path d="M5.1 3.2h14.6l1 .4.7.7.4.8c.2.4.2.7.1 1.1l-1.5 14.3c-.2.5-.2.6-.6 1l-.9.8c-.5.2-1 .3-1.6.2-.5-.2-.8-.2-1.2-.6L3.3 8C1.8 6 3 3.2 5.1 3.1Zm11.7 13.6 1-9.6H8.2l8.7 9.6Z"/></svg>
label: Mock Service Worker
order: -6
order: -7
---

# Mock Service Worker
Expand Down
1 change: 1 addition & 0 deletions docs/src/resources/tanstack.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
222 changes: 222 additions & 0 deletions docs/src/tanstack-react-query.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
---
icon: <svg fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill-rule="evenodd" clip-rule="evenodd" d="M4.8 3.14c-.26 1.08-.17 2.57.22 4.5.01.07.03.14.06.2-.71.2-1.35.4-1.9.65a5.54 5.54 0 0 0-2.83 2.2 2.5 2.5 0 0 0-.3 1.74c.1.56.41 1.07.88 1.52.78.77 2.08 1.43 3.9 2.05l.24.06c-.2.74-.34 1.43-.42 2.06-.2 1.49-.06 2.7.46 3.6.31.56.76.96 1.31 1.15.53.18 1.11.17 1.73-.02 1.04-.32 2.25-1.13 3.7-2.45l.2-.23c.5.53 1 .99 1.48 1.36 1.16.92 2.25 1.4 3.28 1.4.63 0 1.19-.2 1.63-.6.41-.37.69-.9.84-1.54.25-1.08.17-2.56-.22-4.5a1.15 1.15 0 0 0-.08-.23c.7-.19 1.3-.4 1.85-.63a5.54 5.54 0 0 0 2.82-2.2 2.5 2.5 0 0 0 .3-1.74 2.9 2.9 0 0 0-.88-1.52c-.78-.77-2.08-1.43-3.9-2.05a1.08 1.08 0 0 0-.22-.05c.19-.72.32-1.38.4-1.99.2-1.49.06-2.7-.46-3.6a2.41 2.41 0 0 0-1.31-1.15 2.76 2.76 0 0 0-1.73.02c-1.04.32-2.25 1.13-3.7 2.45-.05.05-.1.1-.13.15-.51-.53-1-.98-1.48-1.35C9.38 1.48 8.3 1 7.26 1c-.62 0-1.18.2-1.62.6-.42.37-.7.9-.84 1.54Zm2.58 10.8v-.02a.34.34 0 0 0-.5-.1c-.06.04-.1.1-.12.16-1.3 3.48-1.57 5.88-.82 7.2.77 1.35 2.43.9 4.99-1.34l.08-.07v-.01a.36.36 0 0 0 .04-.49 36.75 36.75 0 0 1-3.61-5.23l-.06-.1Zm3.05-5.87h2.96a1.19 1.19 0 0 1 1.05.62l1.49 2.65a1.26 1.26 0 0 1 0 1.23l-1.49 2.65a1.22 1.22 0 0 1-1.05.62h-2.96a1.2 1.2 0 0 1-1.04-.62l-1.5-2.65a1.26 1.26 0 0 1 0-1.23l1.5-2.65a1.22 1.22 0 0 1 1.04-.62Zm7.6 8.46a.34.34 0 0 0-.25-.05 34.7 34.7 0 0 1-6.18.55h-.13a.34.34 0 0 0-.3.2.36.36 0 0 0 .04.38c2.29 2.89 4.18 4.33 5.66 4.33 1.51 0 1.96-1.7 1.33-5.08l-.02-.1v-.01a.35.35 0 0 0-.15-.22Zm1.05-7.6.1.03c3.17 1.13 4.37 2.37 3.6 3.71-.74 1.32-2.9 2.28-6.47 2.88a.34.34 0 0 1-.34-.15.36.36 0 0 1-.01-.38 34.78 34.78 0 0 0 2.7-5.88.35.35 0 0 1 .16-.19.34.34 0 0 1 .25-.03Zm-1.84 1.09c1.3-3.48 1.57-5.88.82-7.2-.77-1.35-2.43-.9-4.99 1.34l-.08.07v.01a.36.36 0 0 0-.04.49 36.73 36.73 0 0 1 3.61 5.23l.06.1v.02c.04.06.09.1.15.13a.34.34 0 0 0 .36-.03c.05-.04.1-.1.11-.16Zm-9.98-8c1.48 0 3.37 1.44 5.66 4.33a.36.36 0 0 1-.08.52.34.34 0 0 1-.18.05h-.13a32.42 32.42 0 0 0-6.18.56.34.34 0 0 1-.25-.06.35.35 0 0 1-.15-.21V7.2l-.02-.1c-.63-3.4-.18-5.08 1.33-5.08Zm.76 6.43a.34.34 0 0 0-.33-.14c-3.58.6-5.73 1.57-6.48 2.88-.76 1.35.44 2.59 3.6 3.71l.1.04h.01c.09.03.18.02.26-.02a.35.35 0 0 0 .17-.2 37.22 37.22 0 0 1 2.73-6 .36.36 0 0 0-.06-.27Z"/><path d="M10.43 8.07h2.96a1.19 1.19 0 0 1 1.05.62l1.49 2.65a1.26 1.26 0 0 1 0 1.23l-1.49 2.65a1.22 1.22 0 0 1-1.05.62h-2.96a1.2 1.2 0 0 1-1.04-.62l-1.5-2.65a1.26 1.26 0 0 1 0-1.23l1.5-2.65a1.22 1.22 0 0 1 1.04-.62Z" fill-opacity=".25"/><path d="M7.37 13.92v.01l.07.1a34.5 34.5 0 0 0 3.6 5.24.36.36 0 0 1-.02.49H11l-.08.08c-2.56 2.24-4.22 2.69-5 1.34-.74-1.32-.47-3.72.83-7.2a.35.35 0 0 1 .11-.16.34.34 0 0 1 .5.1Zm10.41 2.56c.09-.02.18 0 .25.05s.13.13.15.22v.01l.02.1c.63 3.39.18 5.08-1.33 5.08-1.48 0-3.37-1.44-5.66-4.33a.36.36 0 0 1 .26-.58h.13a32.4 32.4 0 0 0 6.18-.55Zm1.3-7.55.1.03c3.17 1.13 4.37 2.37 3.6 3.71-.74 1.32-2.9 2.28-6.47 2.88a.34.34 0 0 1-.34-.15.36.36 0 0 1-.01-.38 34.78 34.78 0 0 0 2.7-5.88.35.35 0 0 1 .16-.19.34.34 0 0 1 .25-.03Zm-1.02-6.11c.75 1.32.48 3.72-.82 7.2a.34.34 0 0 1-.62.06v-.01l-.06-.1a34.5 34.5 0 0 0-3.6-5.24.36.36 0 0 1 .02-.49H13l.08-.08c2.56-2.24 4.22-2.69 5-1.34Zm-10.8-.8c1.48 0 3.37 1.44 5.66 4.33a.36.36 0 0 1-.08.52.34.34 0 0 1-.18.05h-.13a32.42 32.42 0 0 0-6.18.56.34.34 0 0 1-.25-.06.35.35 0 0 1-.15-.21V7.2l-.02-.1c-.63-3.4-.18-5.08 1.33-5.08Zm.66 6.34a.35.35 0 0 1 .16.22.36.36 0 0 1-.04.26 34.7 34.7 0 0 0-2.7 5.88.35.35 0 0 1-.16.2.34.34 0 0 1-.26.02l-.1-.04c-3.17-1.12-4.37-2.36-3.6-3.7.74-1.32 2.9-2.28 6.47-2.88.08-.02.16 0 .23.04Z" fill-opacity=".5"/></svg>
label: TanStack React Query
order: -5
---

# TanStack React Query


The React Query plugin lets you generate type-safe React Query hooks to fetch data.

## Usage

First, add the `reactQueryPlugin` to your configuration:

```ts #2,5 create-schemas.config.ts
import { defineConfig } from "@workleap/create-schemas";
import { reactQueryPlugin } from "@workleap/create-schemas/plugins";

export default defineConfig({
plugins: [reactQueryPlugin()]
input: "petstore.yaml",
outdir: "codegen",
});
```

Add the generated provider to the root of your app. You typically want to add the provider next to where you define React Query's `QueryClientProvider`. This provider will be named after your API:

```tsx #1,4,10,16 src/App.tsx
import { PetStoreAPIClientProvider, OpenAPIClient } from "./codegen/queries.tsx";
import { QueryClientProvider, QueryClient } from "@tanstack/react-query";

const client = new OpenAPIClient({ baseURL: "https://petstore.io/v3" });

const queryClient = new QueryClient();

export function App() {
return (
<PetStoreAPIClientProvider client={client}>
<QueryClientProvider client={queryClient}>
<Suspense>
<Home />
</Suspense>
</QueryClientProvider>
</PetStoreAPIClientProvider>
);
}

```

Then, you can then import the client from the `./queries.tsx` file. The client will be named after the `info.title` property:

```tsx
import { useListPetsSuspenseQuery } from "./codegen/queries.tsx";

export function Home() {
const { data: pets } = useListPetsSuspenseQuery({ query: { limit: 10 }});

return (
<div>
<h1>Pets</h1>
<ul>
{pets.map(pet => <li key={pet.id}>{pet.name}</li>)}
</ul>
</div>
);
}
```

## Hooks

The `reactQueryPlugin` generates the following functions for each endpoints, where `___` is the name of the endpoint's `operationId`:

## `use___Query(options)`

Hook used to fetch data using the `useQuery` hook.

**Example:**
```tsx
const { data, error, isLoading } = useListPetsQuery();

if (error) {
return <div>Something went wrong!</div>;
} else if (isLoading) {
return <div>Loading...</div>;
} else {
return data.map(pet => <li key={pet.id}>{pet.name}</li>);
}

```

## `use___SuspenseQuery(options)`

Hook used to fetch data using the `useSuspenseQuery` hook.

Unlike the `useQuery` version, you don't need to check if the data is loading or if there was an error. If execution resumes, the data is guaranteed to be available. This can lead to simplified code.

If there is an error, the hook will throw to the closest parent error boundary.

**Example:**
```tsx
const { data } = useListPetsSuspenseQuery();

return data.map(pet => <li key={pet.id}>{pet.name}</li>);
```

## `use___Mutation(options)`

!!!
This hook is only generated for the methods `POST`, `PUT`, `PATCH` and `DELETE`.
!!!

Hook used to mutate data using the `useMutation` hook.

**Example:**
```tsx
const { mutate, isPending } = useAddPetMutation();

function handleClick() {
mutate({
body: { name: "fido", kind: "dog" },
request: { signal: AbortSignal.timeout(500) }
}, {
onSuccess: assistant => {
queryClient.invalidateQueries({ queryKey: listPetsQueryKey() });
}
});
}

return <button onClick={handleClick} disabled={isPending}>Add pet</button>
```

## `prefetch___(client, queryClient, init)`

Function used to prefetch data. You typically want to call this in a `loader` function for your route.

## `___QueryKey(init)`

Function used to obtain the QueryKey of a specific endpoint. The query key is based on the values passed to the `init` parameter.

## Middlewares

You can add logic before or after every requests by using a middleware. You can
add a middleware to the client using the `.addMiddleware(middleware, [options])` method, and you can
remove it using the `.removeMiddleware(middleware)` method or using an abort signal.

There are three different functions that middlewares can use:

- `onRequest(request)`: Runs before the client makes a request. Lets you modify the request object before it is sent.
- `onResponse(response)`: Runs after the client receives a response.
- This function will be called on `4xx` and `5xx`.
- `onError(error)`: Runs when the client is unable to complete a request due to the request being aborted or due to a network error.
- This function will be called on `4xx` or `5xx`.

**Example:**

```tsx
const client = new OpenAPIClient({ baseURL: "https://petstore.io/v3" });

client.addMiddleware({
onResponse(response) {
if (response.status === 401) {
window.location.replace("/login");
return new Promise();
}
}
});
```

**Usage with React:**

Sometimes, you may want a middleware to use data and functions defined inside of your React application. This can be done by adding a middleware inside the `useEffect` hook:

```tsx #6-12
const client = new OpenAPIClient({ baseURL: "https://petstore.io/v3" });

function ShowToastsOnNetworkError() {
const toast = useToast();

useEffect(() => {
const middleware = { onError: () => toast.error("Something went wrong!") };

client.addMiddleware(middleware);

return () => client.removeMiddleware(middleware);
}, [toast]);

return null;
}

function App() {
return (
<>
<ShowToastsOnNetworkError />
<Home />
</>
)
}
```

## API

```ts
interface OpenAPIClientOptions {
baseURL?: string;
query?: SerializeQueryOptions;
headers?: Record<string, string>;
}

interface Middleware {
onRequest?: (request: Request) => void | Request | Promise<void | Request>;
onResponse?: (response: Response) => void | Response | Promise<void | Response>;
onError?: (error: unknown) => void;
request?: RequestInit;
}

class OpenAPIClient {
constructor(options?: OpenAPIClientOptions);
addMiddleware(middleware: Middleware, options?: { signal?: AbortSignal }): void;
removeMiddleware(middleware: Middleware): void;
}
```
7 changes: 4 additions & 3 deletions docs/src/using-plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,9 +197,10 @@ flowchart TB
subgraph generate
direction TB
gen_A(startBuild):::parallel
--> gen_B(transform):::sequential
--> gen_C(buildEnd):::parallel
load:::sequential
--> startBuild:::parallel
--> transform:::sequential
--> buildEnd:::parallel
end
subgraph &nbsp;
Expand Down
10 changes: 10 additions & 0 deletions packages/create-schemas/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@
"./plugins": {
"import": "./dist/plugins/index.js",
"types": "./dist/plugins/index.d.ts"
},
"./plugins/react-query-plugin/openapi-client": {
"import": "./dist/plugins/react-query-plugin/openapi-client.js",
"types": "./dist/plugins/react-query-plugin/openapi-client.d.ts"
}
},
"files": [
Expand All @@ -38,11 +42,16 @@
},
"devDependencies": {
"@swc/helpers": "0.5.11",
"@tanstack/react-query": "^5.52.0",
"@types/node": "20.14.1",
"@types/react": "^18.3.4",
"@workleap/eslint-plugin": "3.2.2",
"@workleap/swc-configs": "2.2.3",
"@workleap/tsup-configs": "3.0.6",
"eslint": "8.57.0",
"msw": "^2.3.5",
"openapi-types": "12.1.3",
"react": "^18.3.1",
"tsup": "8.1.0",
"vitest": "1.6.0"
},
Expand All @@ -56,6 +65,7 @@
"kleur": "4.1.5",
"openapi-typescript": "7.0.2",
"typescript": "5.5.3",
"yaml": "2.4.5",
"zod": "3.23.8"
}
}
3 changes: 3 additions & 0 deletions packages/create-schemas/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type { Plugin } from "./plugins/plugin.ts";
import { headerPlugin } from "./plugins/header-plugin.ts";
import { typesPlugin } from "./plugins/types-plugin.ts";
import { openapiTypeScriptPlugin } from "./plugins/openapi-typescript-plugin.ts";
import { loaderPlugin } from "./plugins/loader-plugin.ts";

const DEFAULT_CONFIG: InlineConfig = {
configFile: "create-schemas.config",
Expand All @@ -16,6 +17,7 @@ const DEFAULT_CONFIG: InlineConfig = {
} as const;

const DEFAULT_PLUGINS_PRE = [
loaderPlugin(),
openapiTypeScriptPlugin(),
typesPlugin()
];
Expand All @@ -42,6 +44,7 @@ export interface InlineConfig extends UserConfig {

const pluginSchema = z.object({
name: z.string(),
load: z.custom<Plugin["load"]>().optional(),
buildStart: z.custom<Plugin["buildStart"]>().optional(),
transform: z.custom<Plugin["transform"]>().optional(),
buildEnd: z.custom<Plugin["buildEnd"]>().optional()
Expand Down
22 changes: 21 additions & 1 deletion packages/create-schemas/src/generate.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { ResolvedConfig } from "./config.ts";
import type { OpenAPIDocument } from "./types.ts";

export interface GenerationResult {
duration: number;
Expand All @@ -20,10 +21,29 @@ export async function generate(config: ResolvedConfig): Promise<GenerationResult
files.push(file);
}

// ====== Load ======
let document: OpenAPIDocument | undefined = undefined;
for (const plugin of config.plugins) {
if (plugin.load) {
const loadResult = await plugin.load({
config,
url: config.input
});
if (loadResult) {
document = loadResult;
break;
}
}
}

if (!document) {
throw new Error(`Failed to load OpenAPI document: "${config.input}"`);
}

// ====== Build start ======
await Promise.all(config.plugins.map(async plugin => {
if (plugin.buildStart) {
await plugin.buildStart({ config, emitFile });
await plugin.buildStart({ config, emitFile, document });
}
}));

Expand Down
Loading

0 comments on commit 11edea5

Please sign in to comment.