From 3926cc55c4e87a45c412b3b7a36fd4eb1a2e0a4b Mon Sep 17 00:00:00 2001 From: Adam Engebretson Date: Fri, 23 Aug 2024 15:55:04 -0500 Subject: [PATCH 1/5] Adding initialPageParam support --- src/cli.mts | 6 ++++++ src/common.mts | 9 ++++++--- src/createExports.mts | 3 ++- src/createSource.mts | 11 ++++++++++- src/createUseQuery.mts | 18 +++++++++++++++--- src/generate.mts | 1 + tests/__snapshots__/generate.test.ts.snap | 2 +- tests/createExports.test.ts | 2 +- tests/generate.test.ts | 1 + 9 files changed, 43 insertions(+), 10 deletions(-) diff --git a/src/cli.mts b/src/cli.mts index c9918d7..c4ec10c 100644 --- a/src/cli.mts +++ b/src/cli.mts @@ -25,6 +25,7 @@ export type LimitedUserConfig = { schemaType?: "form" | "json"; pageParam: string; nextPageParam: string; + initialPageParam: string | number; }; async function setupProgram() { @@ -102,6 +103,11 @@ async function setupProgram() { "Name of the response parameter used for next page", "nextPage", ) + .option( + "--initialPageParam ", + "Initial page value to query", + "initialPageParam", + ) .parse(); const options = program.opts(); diff --git a/src/common.mts b/src/common.mts index 6d31415..0a65cdf 100644 --- a/src/common.mts +++ b/src/common.mts @@ -163,9 +163,12 @@ export function formatOptions(options: LimitedUserConfig) { } else if (!Number.isNaN(parsedNumber)) { (acc as unknown as Record)[typedKey] = parsedNumber; } else { - (acc as unknown as Record)[ - typedKey - ] = typedValue; + ( + acc as unknown as Record< + string, + string | number | undefined | boolean + > + )[typedKey] = typedValue; } return acc; }, diff --git a/src/createExports.mts b/src/createExports.mts index 74eb6c2..58ddc0a 100644 --- a/src/createExports.mts +++ b/src/createExports.mts @@ -8,6 +8,7 @@ export const createExports = ( service: Service, pageParam: string, nextPageParam: string, + initialPageParam: string, ) => { const { klasses } = service; const methods = klasses.flatMap((k) => k.methods); @@ -29,7 +30,7 @@ export const createExports = ( ); const allGetQueries = allGet.map((m) => - createUseQuery(m, pageParam, nextPageParam), + createUseQuery(m, pageParam, nextPageParam, initialPageParam), ); const allPrefetchQueries = allGet.map((m) => createPrefetch(m)); diff --git a/src/createSource.mts b/src/createSource.mts index 3d8d870..8a0a6b0 100644 --- a/src/createSource.mts +++ b/src/createSource.mts @@ -11,6 +11,7 @@ const createSourceFile = async ( serviceEndName: string, pageParam: string, nextPageParam: string, + initialPageParam: string, ) => { const project = new Project({ // Optionally specify compiler options, tsconfig.json, in-memory file system, and more here. @@ -30,7 +31,12 @@ const createSourceFile = async ( project, }); - const exports = createExports(service, pageParam, nextPageParam); + const exports = createExports( + service, + pageParam, + nextPageParam, + initialPageParam, + ); const commonSource = ts.factory.createSourceFile( [...imports, ...exports.allCommon], @@ -111,12 +117,14 @@ export const createSource = async ({ serviceEndName, pageParam, nextPageParam, + initialPageParam, }: { outputPath: string; version: string; serviceEndName: string; pageParam: string; nextPageParam: string; + initialPageParam: string; }) => { const queriesFile = ts.createSourceFile( `${OpenApiRqFiles.queries}.ts`, @@ -180,6 +188,7 @@ export const createSource = async ({ serviceEndName, pageParam, nextPageParam, + initialPageParam, ); const comment = `// generated with @7nohe/openapi-react-query-codegen@${version} \n\n`; diff --git a/src/createUseQuery.mts b/src/createUseQuery.mts index db16dc2..33cd3dd 100644 --- a/src/createUseQuery.mts +++ b/src/createUseQuery.mts @@ -229,6 +229,7 @@ export function createQueryHook({ className, pageParam, nextPageParam, + initialPageParam, }: { queryString: "useSuspenseQuery" | "useQuery" | "useInfiniteQuery"; suffix: string; @@ -238,6 +239,7 @@ export function createQueryHook({ className: string; pageParam?: string; nextPageParam?: string; + initialPageParam?: string; }) { const methodName = getNameFromMethod(method); const customHookName = hookNameFromMethod({ method, className }); @@ -447,7 +449,11 @@ export function createQueryHook({ ), ), ), - ...createInfiniteQueryParams(pageParam, nextPageParam), + ...createInfiniteQueryParams( + pageParam, + nextPageParam, + initialPageParam, + ), ts.factory.createSpreadAssignment( ts.factory.createIdentifier("options"), ), @@ -467,6 +473,7 @@ export const createUseQuery = ( { className, method, jsDoc }: MethodDescription, pageParam: string, nextPageParam: string, + initialPageParam: string, ) => { const methodName = getNameFromMethod(method); const queryKey = createQueryKeyFromMethod({ method, className }); @@ -517,6 +524,7 @@ export const createUseQuery = ( className, pageParam, nextPageParam, + initialPageParam, }) : undefined; @@ -625,14 +633,18 @@ function queryKeyFn( ); } -function createInfiniteQueryParams(pageParam?: string, nextPageParam?: string) { +function createInfiniteQueryParams( + pageParam?: string, + nextPageParam?: string, + initialPageParam = "1", +) { if (pageParam === undefined || nextPageParam === undefined) { return []; } return [ ts.factory.createPropertyAssignment( ts.factory.createIdentifier("initialPageParam"), - ts.factory.createNumericLiteral(1), + ts.factory.createStringLiteral(initialPageParam), ), ts.factory.createPropertyAssignment( ts.factory.createIdentifier("getNextPageParam"), diff --git a/src/generate.mts b/src/generate.mts index 691d119..6026d4f 100644 --- a/src/generate.mts +++ b/src/generate.mts @@ -49,6 +49,7 @@ export async function generate(options: LimitedUserConfig, version: string) { serviceEndName: "Service", // we are hard coding this because changing the service end name was depreciated in @hey-api/openapi-ts pageParam: formattedOptions.pageParam, nextPageParam: formattedOptions.nextPageParam, + initialPageParam: formattedOptions.initialPageParam.toString(), }); await print(source, formattedOptions); const queriesOutputPath = buildQueriesOutputPath(options.output); diff --git a/tests/__snapshots__/generate.test.ts.snap b/tests/__snapshots__/generate.test.ts.snap index a5a0fa9..d70b20c 100644 --- a/tests/__snapshots__/generate.test.ts.snap +++ b/tests/__snapshots__/generate.test.ts.snap @@ -62,7 +62,7 @@ import * as Common from "./common"; export const useDefaultServiceFindPaginatedPetsInfinite = , TError = unknown, TQueryKey extends Array = unknown[]>({ limit, tags }: { limit?: number; tags?: string[]; -} = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useInfiniteQuery({ queryKey: Common.UseDefaultServiceFindPaginatedPetsKeyFn({ limit, tags }, queryKey), queryFn: ({ pageParam }) => DefaultService.findPaginatedPets({ limit, page: pageParam as number, tags }) as TData, initialPageParam: 1, getNextPageParam: response => (response as { nextPage: number }).nextPage, ...options }); +} = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useInfiniteQuery({ queryKey: Common.UseDefaultServiceFindPaginatedPetsKeyFn({ limit, tags }, queryKey), queryFn: ({ pageParam }) => DefaultService.findPaginatedPets({ limit, page: pageParam as number, tags }) as TData, initialPageParam: "initial", getNextPageParam: response => (response as { nextPage: number }).nextPage, ...options }); " `; diff --git a/tests/createExports.test.ts b/tests/createExports.test.ts index f8accd3..2784f49 100644 --- a/tests/createExports.test.ts +++ b/tests/createExports.test.ts @@ -17,7 +17,7 @@ describe(fileName, () => { }); project.addSourceFilesAtPaths(path.join(outputPath(fileName), "**", "*")); const service = await getServices(project); - const exports = createExports(service, "page", "nextPage"); + const exports = createExports(service, "page", "nextPage", "initial"); const commonTypes = exports.allCommon .filter((c) => c.kind === SyntaxKind.TypeAliasDeclaration) diff --git a/tests/generate.test.ts b/tests/generate.test.ts index 26359de..e7f41b9 100644 --- a/tests/generate.test.ts +++ b/tests/generate.test.ts @@ -21,6 +21,7 @@ describe("generate", () => { lint: "eslint", pageParam: "page", nextPageParam: "nextPage", + initialPageParam: "initial", }; await generate(options, "1.0.0"); }); From d471752d2d08d897009643448551db1fd1bd2659 Mon Sep 17 00:00:00 2001 From: Adam Engebretson Date: Fri, 23 Aug 2024 17:16:51 -0500 Subject: [PATCH 2/5] Adding support for nested nextPageParam keys --- src/createUseQuery.mts | 15 ++++++++++++--- tests/__snapshots__/generate.test.ts.snap | 8 +++++++- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/createUseQuery.mts b/src/createUseQuery.mts index 33cd3dd..2dd225e 100644 --- a/src/createUseQuery.mts +++ b/src/createUseQuery.mts @@ -667,9 +667,18 @@ function createInfiniteQueryParams( ts.factory.createParenthesizedExpression( ts.factory.createAsExpression( ts.factory.createIdentifier("response"), - ts.factory.createTypeReferenceNode( - ts.factory.createIdentifier(`{ ${nextPageParam}: number }`), - ), + nextPageParam.split(".").reduceRight((acc, segment) => { + return ts.factory.createTypeLiteralNode([ + ts.factory.createPropertySignature( + undefined, + ts.factory.createIdentifier(segment), + undefined, + acc, + ), + ]); + }, ts.factory.createKeywordTypeNode( + ts.SyntaxKind.NumberKeyword, + ) as ts.TypeNode), ), ), ts.factory.createIdentifier(nextPageParam), diff --git a/tests/__snapshots__/generate.test.ts.snap b/tests/__snapshots__/generate.test.ts.snap index d70b20c..5820e8f 100644 --- a/tests/__snapshots__/generate.test.ts.snap +++ b/tests/__snapshots__/generate.test.ts.snap @@ -62,7 +62,13 @@ import * as Common from "./common"; export const useDefaultServiceFindPaginatedPetsInfinite = , TError = unknown, TQueryKey extends Array = unknown[]>({ limit, tags }: { limit?: number; tags?: string[]; -} = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useInfiniteQuery({ queryKey: Common.UseDefaultServiceFindPaginatedPetsKeyFn({ limit, tags }, queryKey), queryFn: ({ pageParam }) => DefaultService.findPaginatedPets({ limit, page: pageParam as number, tags }) as TData, initialPageParam: "initial", getNextPageParam: response => (response as { nextPage: number }).nextPage, ...options }); +} = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useInfiniteQuery({ + queryKey: Common.UseDefaultServiceFindPaginatedPetsKeyFn({ limit, tags }, queryKey), queryFn: ({ pageParam }) => DefaultService.findPaginatedPets({ limit, page: pageParam as number, tags }) as TData, initialPageParam: 1, getNextPageParam: response => (response as { + meta: { + nextPage: number; + }; + }).meta.nextPage, ...options +}); " `; From ccd97399316b3453a2ca8c91185ec2fc31a453e5 Mon Sep 17 00:00:00 2001 From: Adam Engebretson Date: Mon, 26 Aug 2024 11:37:55 -0500 Subject: [PATCH 3/5] Merge conflicts + snapshot --- tests/__snapshots__/generate.test.ts.snap | 6 +++--- tests/generate.test.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/__snapshots__/generate.test.ts.snap b/tests/__snapshots__/generate.test.ts.snap index 5820e8f..7b9140c 100644 --- a/tests/__snapshots__/generate.test.ts.snap +++ b/tests/__snapshots__/generate.test.ts.snap @@ -63,11 +63,11 @@ export const useDefaultServiceFindPaginatedPetsInfinite = , "queryKey" | "queryFn">) => useInfiniteQuery({ - queryKey: Common.UseDefaultServiceFindPaginatedPetsKeyFn({ limit, tags }, queryKey), queryFn: ({ pageParam }) => DefaultService.findPaginatedPets({ limit, page: pageParam as number, tags }) as TData, initialPageParam: 1, getNextPageParam: response => (response as { + queryKey: Common.UseDefaultServiceFindPaginatedPetsKeyFn({ limit, tags }, queryKey), queryFn: ({ pageParam }) => DefaultService.findPaginatedPets({ limit, page: pageParam as number, tags }) as TData, initialPageParam: "initial", getNextPageParam: response => (response as { meta: { - nextPage: number; + next: number; }; - }).meta.nextPage, ...options + }).meta.next, ...options }); " `; diff --git a/tests/generate.test.ts b/tests/generate.test.ts index e7f41b9..056c741 100644 --- a/tests/generate.test.ts +++ b/tests/generate.test.ts @@ -20,7 +20,7 @@ describe("generate", () => { output: path.join("tests", "outputs"), lint: "eslint", pageParam: "page", - nextPageParam: "nextPage", + nextPageParam: "meta.next", initialPageParam: "initial", }; await generate(options, "1.0.0"); From bd64c952fc9e099850c10633589bcb82c62b6d2c Mon Sep 17 00:00:00 2001 From: Adam Engebretson Date: Mon, 26 Aug 2024 11:38:10 -0500 Subject: [PATCH 4/5] Adding docs --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 9acf1ea..bfa0f8a 100644 --- a/README.md +++ b/README.md @@ -241,6 +241,8 @@ export default App; 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 From bdc6cc5bea4465d2181da02490f5c4302b22beae Mon Sep 17 00:00:00 2001 From: Adam Engebretson Date: Tue, 27 Aug 2024 13:51:05 -0500 Subject: [PATCH 5/5] Adding param to cli doc --- README.md | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index bfa0f8a..f23ee85 100644 --- a/README.md +++ b/README.md @@ -42,24 +42,25 @@ Usage: openapi-rq [options] Generate React Query code based on OpenAPI Options: - -V, --version output the version number - -i, --input OpenAPI specification, can be a path, url or string content (required) - -o, --output Output directory (default: "openapi") - -c, --client HTTP client to generate (choices: "angular", "axios", "fetch", "node", "xhr", default: "fetch") - --request Path to custom request file - --format Process output folder with formatter? (choices: "biome", "prettier") - --lint Process output folder with linter? (choices: "biome", "eslint") - --operationId Use operation ID to generate operation names? - --serviceResponse Define shape of returned value from service calls (choices: "body", "response", default: "body") - --base Manually set base in OpenAPI config instead of inferring from server value - --enums Generate JavaScript objects from enum definitions? ['javascript', 'typescript', 'typescript+namespace'] - --enums 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 Type of JSON schema [Default: 'json'] (choices: "form", "json") - --pageParam Name of the query parameter used for pagination (default: "page") - --nextPageParam Name of the response parameter used for next page (default: "nextPage") + -V, --version output the version number + -i, --input OpenAPI specification, can be a path, url or string content (required) + -o, --output Output directory (default: "openapi") + -c, --client HTTP client to generate (choices: "angular", "axios", "fetch", "node", "xhr", default: "fetch") + --request Path to custom request file + --format Process output folder with formatter? (choices: "biome", "prettier") + --lint Process output folder with linter? (choices: "biome", "eslint") + --operationId Use operation ID to generate operation names? + --serviceResponse Define shape of returned value from service calls (choices: "body", "response", default: "body") + --base Manually set base in OpenAPI config instead of inferring from server value + --enums Generate JavaScript objects from enum definitions? ['javascript', 'typescript', 'typescript+namespace'] + --enums 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 Type of JSON schema [Default: 'json'] (choices: "form", "json") + --pageParam Name of the query parameter used for pagination (default: "page") + --nextPageParam Name of the response parameter used for next page (default: "nextPage") + --initialPageParam Initial value for the pagination parameter (default: "1") -h, --help display help for command ```