Skip to content

Commit 8b12827

Browse files
rajzikjan.silhan
authored andcommitted
feat: Add suspense (#261)
Co-authored-by: jan.silhan <[email protected]>
1 parent d03d6c7 commit 8b12827

File tree

3 files changed

+387
-23
lines changed

3 files changed

+387
-23
lines changed

plugins/typescript/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
Collection of typescript generators & utils
44

5+
## Options
6+
7+
### generateSuspenseQueries
8+
9+
Generate `useSuspenseQuery` wrapper along side `useQuery`.
10+
511
## Generators
612

713
### generateSchemaType

plugins/typescript/src/generators/generateReactQueryComponents.test.ts

Lines changed: 342 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,101 @@ describe("generateReactQueryComponents", () => {
124124
"
125125
`);
126126
});
127+
it("should generate a useSuspenseQuery wrapper (no parameters)", async () => {
128+
const writeFile = jest.fn();
129+
const openAPIDocument: OpenAPIObject = {
130+
openapi: "3.0.0",
131+
info: {
132+
title: "petshop",
133+
version: "1.0.0",
134+
},
135+
paths: {
136+
"/pets": {
137+
get: {
138+
operationId: "listPets",
139+
description: "Get all the pets",
140+
responses: {
141+
"200": {
142+
description: "pet response",
143+
content: {
144+
"application/json": {
145+
schema: {
146+
type: "array",
147+
items: {
148+
$ref: "#/components/schemas/Pet",
149+
},
150+
},
151+
},
152+
},
153+
},
154+
},
155+
},
156+
},
157+
},
158+
};
159+
160+
await generateReactQueryComponents(
161+
{
162+
openAPIDocument,
163+
writeFile,
164+
existsFile: () => true,
165+
readFile: async () => "",
166+
},
167+
{ ...config, generateSuspenseQueries: true },
168+
);
169+
170+
expect(writeFile.mock.calls[0][0]).toBe("petstoreComponents.ts");
171+
expect(writeFile.mock.calls[0][1]).toMatchInlineSnapshot(`
172+
"/**
173+
* Generated by @openapi-codegen
174+
*
175+
* @version 1.0.0
176+
*/
177+
import * as reactQuery from "@tanstack/react-query";
178+
import { usePetstoreContext, PetstoreContext } from "./petstoreContext";
179+
import type * as Fetcher from "./petstoreFetcher";
180+
import { petstoreFetch } from "./petstoreFetcher";
181+
import type * as Schemas from "./petstoreSchemas";
182+
183+
export type ListPetsError = Fetcher.ErrorWrapper<undefined>;
184+
185+
export type ListPetsResponse = Schemas.Pet[];
186+
187+
export type ListPetsVariables = PetstoreContext["fetcherOptions"];
188+
189+
/**
190+
* Get all the pets
191+
*/
192+
export const fetchListPets = (variables: ListPetsVariables, signal?: AbortSignal) => petstoreFetch<ListPetsResponse, ListPetsError, undefined, {}, {}, {}>({ url: "/pets", method: "get", ...variables, signal });
193+
194+
/**
195+
* Get all the pets
196+
*/
197+
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>({
198+
queryKey: queryKeyFn({ path: "/pets", operationId: "listPets", variables }),
199+
queryFn: ({ signal }) => fetchListPets({ ...fetcherOptions, ...variables }, signal),
200+
...options,
201+
...queryOptions
202+
}); };
127203
204+
/**
205+
* Get all the pets
206+
*/
207+
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>({
208+
queryKey: queryKeyFn({ path: "/pets", operationId: "listPets", variables }),
209+
queryFn: ({ signal }) => fetchListPets({ ...fetcherOptions, ...variables }, signal),
210+
...options,
211+
...queryOptions
212+
}); };
213+
214+
export type QueryOperation = {
215+
path: "/pets";
216+
operationId: "listPets";
217+
variables: ListPetsVariables;
218+
};
219+
"
220+
`);
221+
});
128222
it("should generate a useQuery wrapper (with queryParams)", async () => {
129223
const writeFile = jest.fn();
130224
const openAPIDocument: OpenAPIObject = {
@@ -248,7 +342,139 @@ describe("generateReactQueryComponents", () => {
248342
"
249343
`);
250344
});
345+
it("should generate a useSuspenseQuery wrapper (with queryParams)", async () => {
346+
const writeFile = jest.fn();
347+
const openAPIDocument: OpenAPIObject = {
348+
openapi: "3.0.0",
349+
info: {
350+
title: "petshop",
351+
version: "1.0.0",
352+
},
353+
paths: {
354+
"/pets": {
355+
get: {
356+
operationId: "listPets",
357+
description: "Get all the pets",
358+
parameters: [
359+
{
360+
in: "query",
361+
name: "breed",
362+
description: "Filter on the dog breed",
363+
required: true,
364+
schema: {
365+
type: "string",
366+
},
367+
},
368+
{ $ref: "#/components/parameters/colorParam" },
369+
],
370+
responses: {
371+
"200": {
372+
description: "pet response",
373+
content: {
374+
"application/json": {
375+
schema: {
376+
type: "array",
377+
items: {
378+
$ref: "#/components/schemas/Pet",
379+
},
380+
},
381+
},
382+
},
383+
},
384+
},
385+
},
386+
},
387+
},
388+
components: {
389+
parameters: {
390+
colorParam: {
391+
in: "query",
392+
description: "Color of the dog",
393+
name: "color",
394+
schema: {
395+
type: "string",
396+
enum: ["white", "black", "grey"],
397+
},
398+
},
399+
},
400+
},
401+
};
251402

403+
await generateReactQueryComponents(
404+
{
405+
openAPIDocument,
406+
writeFile,
407+
existsFile: () => true,
408+
readFile: async () => "",
409+
},
410+
{ ...config, generateSuspenseQueries: true },
411+
);
412+
413+
expect(writeFile.mock.calls[0][0]).toBe("petstoreComponents.ts");
414+
expect(writeFile.mock.calls[0][1]).toMatchInlineSnapshot(`
415+
"/**
416+
* Generated by @openapi-codegen
417+
*
418+
* @version 1.0.0
419+
*/
420+
import * as reactQuery from "@tanstack/react-query";
421+
import { usePetstoreContext, PetstoreContext } from "./petstoreContext";
422+
import type * as Fetcher from "./petstoreFetcher";
423+
import { petstoreFetch } from "./petstoreFetcher";
424+
import type * as Schemas from "./petstoreSchemas";
425+
426+
export type ListPetsQueryParams = {
427+
/**
428+
* Filter on the dog breed
429+
*/
430+
breed: string;
431+
/**
432+
* Color of the dog
433+
*/
434+
color?: "white" | "black" | "grey";
435+
};
436+
437+
export type ListPetsError = Fetcher.ErrorWrapper<undefined>;
438+
439+
export type ListPetsResponse = Schemas.Pet[];
440+
441+
export type ListPetsVariables = {
442+
queryParams: ListPetsQueryParams;
443+
} & PetstoreContext["fetcherOptions"];
444+
445+
/**
446+
* Get all the pets
447+
*/
448+
export const fetchListPets = (variables: ListPetsVariables, signal?: AbortSignal) => petstoreFetch<ListPetsResponse, ListPetsError, undefined, {}, ListPetsQueryParams, {}>({ url: "/pets", method: "get", ...variables, signal });
449+
450+
/**
451+
* Get all the pets
452+
*/
453+
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>({
454+
queryKey: queryKeyFn({ path: "/pets", operationId: "listPets", variables }),
455+
queryFn: ({ signal }) => fetchListPets({ ...fetcherOptions, ...variables }, signal),
456+
...options,
457+
...queryOptions
458+
}); };
459+
460+
/**
461+
* Get all the pets
462+
*/
463+
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>({
464+
queryKey: queryKeyFn({ path: "/pets", operationId: "listPets", variables }),
465+
queryFn: ({ signal }) => fetchListPets({ ...fetcherOptions, ...variables }, signal),
466+
...options,
467+
...queryOptions
468+
}); };
469+
470+
export type QueryOperation = {
471+
path: "/pets";
472+
operationId: "listPets";
473+
variables: ListPetsVariables;
474+
};
475+
"
476+
`);
477+
});
252478
it("should generate a useQuery wrapper (with pathParams)", async () => {
253479
const writeFile = jest.fn();
254480
const openAPIDocument: OpenAPIObject = {
@@ -355,6 +581,122 @@ describe("generateReactQueryComponents", () => {
355581
`);
356582
});
357583

584+
it("should generate a useSuspenseQuery wrapper (with pathParams)", async () => {
585+
const writeFile = jest.fn();
586+
const openAPIDocument: OpenAPIObject = {
587+
openapi: "3.0.0",
588+
info: {
589+
title: "petshop",
590+
version: "1.0.0",
591+
},
592+
paths: {
593+
"/pets/{pet_id}": {
594+
get: {
595+
operationId: "showPetById",
596+
description: "Info for a specific pet",
597+
parameters: [
598+
{
599+
in: "path",
600+
name: "pet_id",
601+
description: "The id of the pet to retrieve",
602+
required: true,
603+
schema: {
604+
type: "string",
605+
},
606+
},
607+
],
608+
responses: {
609+
"200": {
610+
description: "pet response",
611+
content: {
612+
"application/json": {
613+
schema: {
614+
type: "array",
615+
items: {
616+
$ref: "#/components/schemas/Pet",
617+
},
618+
},
619+
},
620+
},
621+
},
622+
},
623+
},
624+
},
625+
},
626+
};
627+
628+
await generateReactQueryComponents(
629+
{
630+
openAPIDocument,
631+
writeFile,
632+
existsFile: () => true,
633+
readFile: async () => "",
634+
},
635+
{ ...config, generateSuspenseQueries: true },
636+
);
637+
638+
expect(writeFile.mock.calls[0][0]).toBe("petstoreComponents.ts");
639+
expect(writeFile.mock.calls[0][1]).toMatchInlineSnapshot(`
640+
"/**
641+
* Generated by @openapi-codegen
642+
*
643+
* @version 1.0.0
644+
*/
645+
import * as reactQuery from "@tanstack/react-query";
646+
import { usePetstoreContext, PetstoreContext } from "./petstoreContext";
647+
import type * as Fetcher from "./petstoreFetcher";
648+
import { petstoreFetch } from "./petstoreFetcher";
649+
import type * as Schemas from "./petstoreSchemas";
650+
651+
export type ShowPetByIdPathParams = {
652+
/**
653+
* The id of the pet to retrieve
654+
*/
655+
petId: string;
656+
};
657+
658+
export type ShowPetByIdError = Fetcher.ErrorWrapper<undefined>;
659+
660+
export type ShowPetByIdResponse = Schemas.Pet[];
661+
662+
export type ShowPetByIdVariables = {
663+
pathParams: ShowPetByIdPathParams;
664+
} & PetstoreContext["fetcherOptions"];
665+
666+
/**
667+
* Info for a specific pet
668+
*/
669+
export const fetchShowPetById = (variables: ShowPetByIdVariables, signal?: AbortSignal) => petstoreFetch<ShowPetByIdResponse, ShowPetByIdError, undefined, {}, {}, ShowPetByIdPathParams>({ url: "/pets/{petId}", method: "get", ...variables, signal });
670+
671+
/**
672+
* Info for a specific pet
673+
*/
674+
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>({
675+
queryKey: queryKeyFn({ path: "/pets/{petId}", operationId: "showPetById", variables }),
676+
queryFn: ({ signal }) => fetchShowPetById({ ...fetcherOptions, ...variables }, signal),
677+
...options,
678+
...queryOptions
679+
}); };
680+
681+
/**
682+
* Info for a specific pet
683+
*/
684+
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>({
685+
queryKey: queryKeyFn({ path: "/pets/{petId}", operationId: "showPetById", variables }),
686+
queryFn: ({ signal }) => fetchShowPetById({ ...fetcherOptions, ...variables }, signal),
687+
...options,
688+
...queryOptions
689+
}); };
690+
691+
export type QueryOperation = {
692+
path: "/pets/{petId}";
693+
operationId: "showPetById";
694+
variables: ShowPetByIdVariables;
695+
};
696+
"
697+
`);
698+
});
699+
358700
it("should deal with injected headers (marked them as optional)", async () => {
359701
const writeFile = jest.fn();
360702
const openAPIDocument: OpenAPIObject = {

0 commit comments

Comments
 (0)