Skip to content

Commit

Permalink
Add suspense (#261)
Browse files Browse the repository at this point in the history
Co-authored-by: jan.silhan <[email protected]>
  • Loading branch information
rajzik and jan.silhan authored Dec 2, 2024
1 parent 81da68f commit 7c494d4
Show file tree
Hide file tree
Showing 3 changed files with 387 additions and 23 deletions.
6 changes: 6 additions & 0 deletions plugins/typescript/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

Collection of typescript generators & utils

## Options

### generateSuspenseQueries

Generate `useSuspenseQuery` wrapper along side `useQuery`.

## Generators

### generateSchemaType
Expand Down
342 changes: 342 additions & 0 deletions plugins/typescript/src/generators/generateReactQueryComponents.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,101 @@ describe("generateReactQueryComponents", () => {
"
`);
});
it("should generate a useSuspenseQuery wrapper (no parameters)", async () => {
const writeFile = jest.fn();
const openAPIDocument: OpenAPIObject = {
openapi: "3.0.0",
info: {
title: "petshop",
version: "1.0.0",
},
paths: {
"/pets": {
get: {
operationId: "listPets",
description: "Get all the pets",
responses: {
"200": {
description: "pet response",
content: {
"application/json": {
schema: {
type: "array",
items: {
$ref: "#/components/schemas/Pet",
},
},
},
},
},
},
},
},
},
};

await generateReactQueryComponents(
{
openAPIDocument,
writeFile,
existsFile: () => true,
readFile: async () => "",
},
{ ...config, generateSuspenseQueries: true },
);

expect(writeFile.mock.calls[0][0]).toBe("petstoreComponents.ts");
expect(writeFile.mock.calls[0][1]).toMatchInlineSnapshot(`
"/**
* Generated by @openapi-codegen
*
* @version 1.0.0
*/
import * as reactQuery from "@tanstack/react-query";
import { usePetstoreContext, PetstoreContext } from "./petstoreContext";
import type * as Fetcher from "./petstoreFetcher";
import { petstoreFetch } from "./petstoreFetcher";
import type * as Schemas from "./petstoreSchemas";
export type ListPetsError = Fetcher.ErrorWrapper<undefined>;
export type ListPetsResponse = Schemas.Pet[];
export type ListPetsVariables = PetstoreContext["fetcherOptions"];
/**
* Get all the pets
*/
export const fetchListPets = (variables: ListPetsVariables, signal?: AbortSignal) => petstoreFetch<ListPetsResponse, ListPetsError, undefined, {}, {}, {}>({ url: "/pets", method: "get", ...variables, signal });
/**
* Get all the pets
*/
export const useListPets = <TData = ListPetsResponse>(variables: ListPetsVariables, options?: Omit<reactQuery.UseQueryOptions<ListPetsResponse, ListPetsError, TData>, "queryKey" | "queryFn" | "initialData">) => { const { fetcherOptions, queryOptions, queryKeyFn } = usePetstoreContext(options); return reactQuery.useQuery<ListPetsResponse, ListPetsError, TData>({
queryKey: queryKeyFn({ path: "/pets", operationId: "listPets", variables }),
queryFn: ({ signal }) => fetchListPets({ ...fetcherOptions, ...variables }, signal),
...options,
...queryOptions
}); };
/**
* Get all the pets
*/
export const useSuspenseListPets = <TData = ListPetsResponse>(variables: ListPetsVariables, options?: Omit<reactQuery.UseQueryOptions<ListPetsResponse, ListPetsError, TData>, "queryKey" | "queryFn" | "initialData">) => { const { fetcherOptions, queryOptions, queryKeyFn } = usePetstoreContext(options); return reactQuery.useSuspenseQuery<ListPetsResponse, ListPetsError, TData>({
queryKey: queryKeyFn({ path: "/pets", operationId: "listPets", variables }),
queryFn: ({ signal }) => fetchListPets({ ...fetcherOptions, ...variables }, signal),
...options,
...queryOptions
}); };
export type QueryOperation = {
path: "/pets";
operationId: "listPets";
variables: ListPetsVariables;
};
"
`);
});
it("should generate a useQuery wrapper (with queryParams)", async () => {
const writeFile = jest.fn();
const openAPIDocument: OpenAPIObject = {
Expand Down Expand Up @@ -248,7 +342,139 @@ describe("generateReactQueryComponents", () => {
"
`);
});
it("should generate a useSuspenseQuery wrapper (with queryParams)", async () => {
const writeFile = jest.fn();
const openAPIDocument: OpenAPIObject = {
openapi: "3.0.0",
info: {
title: "petshop",
version: "1.0.0",
},
paths: {
"/pets": {
get: {
operationId: "listPets",
description: "Get all the pets",
parameters: [
{
in: "query",
name: "breed",
description: "Filter on the dog breed",
required: true,
schema: {
type: "string",
},
},
{ $ref: "#/components/parameters/colorParam" },
],
responses: {
"200": {
description: "pet response",
content: {
"application/json": {
schema: {
type: "array",
items: {
$ref: "#/components/schemas/Pet",
},
},
},
},
},
},
},
},
},
components: {
parameters: {
colorParam: {
in: "query",
description: "Color of the dog",
name: "color",
schema: {
type: "string",
enum: ["white", "black", "grey"],
},
},
},
},
};

await generateReactQueryComponents(
{
openAPIDocument,
writeFile,
existsFile: () => true,
readFile: async () => "",
},
{ ...config, generateSuspenseQueries: true },
);

expect(writeFile.mock.calls[0][0]).toBe("petstoreComponents.ts");
expect(writeFile.mock.calls[0][1]).toMatchInlineSnapshot(`
"/**
* Generated by @openapi-codegen
*
* @version 1.0.0
*/
import * as reactQuery from "@tanstack/react-query";
import { usePetstoreContext, PetstoreContext } from "./petstoreContext";
import type * as Fetcher from "./petstoreFetcher";
import { petstoreFetch } from "./petstoreFetcher";
import type * as Schemas from "./petstoreSchemas";
export type ListPetsQueryParams = {
/**
* Filter on the dog breed
*/
breed: string;
/**
* Color of the dog
*/
color?: "white" | "black" | "grey";
};
export type ListPetsError = Fetcher.ErrorWrapper<undefined>;
export type ListPetsResponse = Schemas.Pet[];
export type ListPetsVariables = {
queryParams: ListPetsQueryParams;
} & PetstoreContext["fetcherOptions"];
/**
* Get all the pets
*/
export const fetchListPets = (variables: ListPetsVariables, signal?: AbortSignal) => petstoreFetch<ListPetsResponse, ListPetsError, undefined, {}, ListPetsQueryParams, {}>({ url: "/pets", method: "get", ...variables, signal });
/**
* Get all the pets
*/
export const useListPets = <TData = ListPetsResponse>(variables: ListPetsVariables, options?: Omit<reactQuery.UseQueryOptions<ListPetsResponse, ListPetsError, TData>, "queryKey" | "queryFn" | "initialData">) => { const { fetcherOptions, queryOptions, queryKeyFn } = usePetstoreContext(options); return reactQuery.useQuery<ListPetsResponse, ListPetsError, TData>({
queryKey: queryKeyFn({ path: "/pets", operationId: "listPets", variables }),
queryFn: ({ signal }) => fetchListPets({ ...fetcherOptions, ...variables }, signal),
...options,
...queryOptions
}); };
/**
* Get all the pets
*/
export const useSuspenseListPets = <TData = ListPetsResponse>(variables: ListPetsVariables, options?: Omit<reactQuery.UseQueryOptions<ListPetsResponse, ListPetsError, TData>, "queryKey" | "queryFn" | "initialData">) => { const { fetcherOptions, queryOptions, queryKeyFn } = usePetstoreContext(options); return reactQuery.useSuspenseQuery<ListPetsResponse, ListPetsError, TData>({
queryKey: queryKeyFn({ path: "/pets", operationId: "listPets", variables }),
queryFn: ({ signal }) => fetchListPets({ ...fetcherOptions, ...variables }, signal),
...options,
...queryOptions
}); };
export type QueryOperation = {
path: "/pets";
operationId: "listPets";
variables: ListPetsVariables;
};
"
`);
});
it("should generate a useQuery wrapper (with pathParams)", async () => {
const writeFile = jest.fn();
const openAPIDocument: OpenAPIObject = {
Expand Down Expand Up @@ -355,6 +581,122 @@ describe("generateReactQueryComponents", () => {
`);
});

it("should generate a useSuspenseQuery wrapper (with pathParams)", async () => {
const writeFile = jest.fn();
const openAPIDocument: OpenAPIObject = {
openapi: "3.0.0",
info: {
title: "petshop",
version: "1.0.0",
},
paths: {
"/pets/{pet_id}": {
get: {
operationId: "showPetById",
description: "Info for a specific pet",
parameters: [
{
in: "path",
name: "pet_id",
description: "The id of the pet to retrieve",
required: true,
schema: {
type: "string",
},
},
],
responses: {
"200": {
description: "pet response",
content: {
"application/json": {
schema: {
type: "array",
items: {
$ref: "#/components/schemas/Pet",
},
},
},
},
},
},
},
},
},
};

await generateReactQueryComponents(
{
openAPIDocument,
writeFile,
existsFile: () => true,
readFile: async () => "",
},
{ ...config, generateSuspenseQueries: true },
);

expect(writeFile.mock.calls[0][0]).toBe("petstoreComponents.ts");
expect(writeFile.mock.calls[0][1]).toMatchInlineSnapshot(`
"/**
* Generated by @openapi-codegen
*
* @version 1.0.0
*/
import * as reactQuery from "@tanstack/react-query";
import { usePetstoreContext, PetstoreContext } from "./petstoreContext";
import type * as Fetcher from "./petstoreFetcher";
import { petstoreFetch } from "./petstoreFetcher";
import type * as Schemas from "./petstoreSchemas";
export type ShowPetByIdPathParams = {
/**
* The id of the pet to retrieve
*/
petId: string;
};
export type ShowPetByIdError = Fetcher.ErrorWrapper<undefined>;
export type ShowPetByIdResponse = Schemas.Pet[];
export type ShowPetByIdVariables = {
pathParams: ShowPetByIdPathParams;
} & PetstoreContext["fetcherOptions"];
/**
* Info for a specific pet
*/
export const fetchShowPetById = (variables: ShowPetByIdVariables, signal?: AbortSignal) => petstoreFetch<ShowPetByIdResponse, ShowPetByIdError, undefined, {}, {}, ShowPetByIdPathParams>({ url: "/pets/{petId}", method: "get", ...variables, signal });
/**
* Info for a specific pet
*/
export const useShowPetById = <TData = ShowPetByIdResponse>(variables: ShowPetByIdVariables, options?: Omit<reactQuery.UseQueryOptions<ShowPetByIdResponse, ShowPetByIdError, TData>, "queryKey" | "queryFn" | "initialData">) => { const { fetcherOptions, queryOptions, queryKeyFn } = usePetstoreContext(options); return reactQuery.useQuery<ShowPetByIdResponse, ShowPetByIdError, TData>({
queryKey: queryKeyFn({ path: "/pets/{petId}", operationId: "showPetById", variables }),
queryFn: ({ signal }) => fetchShowPetById({ ...fetcherOptions, ...variables }, signal),
...options,
...queryOptions
}); };
/**
* Info for a specific pet
*/
export const useSuspenseShowPetById = <TData = ShowPetByIdResponse>(variables: ShowPetByIdVariables, options?: Omit<reactQuery.UseQueryOptions<ShowPetByIdResponse, ShowPetByIdError, TData>, "queryKey" | "queryFn" | "initialData">) => { const { fetcherOptions, queryOptions, queryKeyFn } = usePetstoreContext(options); return reactQuery.useSuspenseQuery<ShowPetByIdResponse, ShowPetByIdError, TData>({
queryKey: queryKeyFn({ path: "/pets/{petId}", operationId: "showPetById", variables }),
queryFn: ({ signal }) => fetchShowPetById({ ...fetcherOptions, ...variables }, signal),
...options,
...queryOptions
}); };
export type QueryOperation = {
path: "/pets/{petId}";
operationId: "showPetById";
variables: ShowPetByIdVariables;
};
"
`);
});

it("should deal with injected headers (marked them as optional)", async () => {
const writeFile = jest.fn();
const openAPIDocument: OpenAPIObject = {
Expand Down
Loading

0 comments on commit 7c494d4

Please sign in to comment.