Skip to content

Commit d67c594

Browse files
authored
Adding initialPageParam and nested nextPageParam support (#143)
1 parent 0582f1d commit d67c594

10 files changed

+83
-32
lines changed

README.md

+21-18
Original file line numberDiff line numberDiff line change
@@ -42,24 +42,25 @@ Usage: openapi-rq [options]
4242
Generate React Query code based on OpenAPI
4343
4444
Options:
45-
-V, --version output the version number
46-
-i, --input <value> OpenAPI specification, can be a path, url or string content (required)
47-
-o, --output <value> Output directory (default: "openapi")
48-
-c, --client <value> HTTP client to generate (choices: "angular", "axios", "fetch", "node", "xhr", default: "fetch")
49-
--request <value> Path to custom request file
50-
--format <value> Process output folder with formatter? (choices: "biome", "prettier")
51-
--lint <value> Process output folder with linter? (choices: "biome", "eslint")
52-
--operationId Use operation ID to generate operation names?
53-
--serviceResponse <value> Define shape of returned value from service calls (choices: "body", "response", default: "body")
54-
--base <value> Manually set base in OpenAPI config instead of inferring from server value
55-
--enums <value> Generate JavaScript objects from enum definitions? ['javascript', 'typescript', 'typescript+namespace']
56-
--enums <value> Generate JavaScript objects from enum definitions? (choices: "javascript", "typescript")
57-
--useDateType Use Date type instead of string for date types for models, this will not convert the data to a Date object
58-
--debug Run in debug mode?
59-
--noSchemas Disable generating JSON schemas
60-
--schemaType <value> Type of JSON schema [Default: 'json'] (choices: "form", "json")
61-
--pageParam <value> Name of the query parameter used for pagination (default: "page")
62-
--nextPageParam <value> Name of the response parameter used for next page (default: "nextPage")
45+
-V, --version output the version number
46+
-i, --input <value> OpenAPI specification, can be a path, url or string content (required)
47+
-o, --output <value> Output directory (default: "openapi")
48+
-c, --client <value> HTTP client to generate (choices: "angular", "axios", "fetch", "node", "xhr", default: "fetch")
49+
--request <value> Path to custom request file
50+
--format <value> Process output folder with formatter? (choices: "biome", "prettier")
51+
--lint <value> Process output folder with linter? (choices: "biome", "eslint")
52+
--operationId Use operation ID to generate operation names?
53+
--serviceResponse <value> Define shape of returned value from service calls (choices: "body", "response", default: "body")
54+
--base <value> Manually set base in OpenAPI config instead of inferring from server value
55+
--enums <value> Generate JavaScript objects from enum definitions? ['javascript', 'typescript', 'typescript+namespace']
56+
--enums <value> Generate JavaScript objects from enum definitions? (choices: "javascript", "typescript")
57+
--useDateType Use Date type instead of string for date types for models, this will not convert the data to a Date object
58+
--debug Run in debug mode?
59+
--noSchemas Disable generating JSON schemas
60+
--schemaType <value> Type of JSON schema [Default: 'json'] (choices: "form", "json")
61+
--pageParam <value> Name of the query parameter used for pagination (default: "page")
62+
--nextPageParam <value> Name of the response parameter used for next page (default: "nextPage")
63+
--initialPageParam <value> Initial value for the pagination parameter (default: "1")
6364
-h, --help display help for command
6465
```
6566

@@ -241,6 +242,8 @@ export default App;
241242

242243
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.
243244

245+
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`).
246+
244247
Example Schema:
245248

246249
```yml

src/cli.mts

+6
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export type LimitedUserConfig = {
2525
schemaType?: "form" | "json";
2626
pageParam: string;
2727
nextPageParam: string;
28+
initialPageParam: string | number;
2829
};
2930

3031
async function setupProgram() {
@@ -102,6 +103,11 @@ async function setupProgram() {
102103
"Name of the response parameter used for next page",
103104
"nextPage",
104105
)
106+
.option(
107+
"--initialPageParam <value>",
108+
"Initial page value to query",
109+
"initialPageParam",
110+
)
105111
.parse();
106112

107113
const options = program.opts<LimitedUserConfig>();

src/common.mts

+6-3
Original file line numberDiff line numberDiff line change
@@ -163,9 +163,12 @@ export function formatOptions(options: LimitedUserConfig) {
163163
} else if (!Number.isNaN(parsedNumber)) {
164164
(acc as unknown as Record<string, number>)[typedKey] = parsedNumber;
165165
} else {
166-
(acc as unknown as Record<string, string | undefined | boolean>)[
167-
typedKey
168-
] = typedValue;
166+
(
167+
acc as unknown as Record<
168+
string,
169+
string | number | undefined | boolean
170+
>
171+
)[typedKey] = typedValue;
169172
}
170173
return acc;
171174
},

src/createExports.mts

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export const createExports = (
88
service: Service,
99
pageParam: string,
1010
nextPageParam: string,
11+
initialPageParam: string,
1112
) => {
1213
const { klasses } = service;
1314
const methods = klasses.flatMap((k) => k.methods);
@@ -29,7 +30,7 @@ export const createExports = (
2930
);
3031

3132
const allGetQueries = allGet.map((m) =>
32-
createUseQuery(m, pageParam, nextPageParam),
33+
createUseQuery(m, pageParam, nextPageParam, initialPageParam),
3334
);
3435
const allPrefetchQueries = allGet.map((m) => createPrefetch(m));
3536

src/createSource.mts

+10-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const createSourceFile = async (
1111
serviceEndName: string,
1212
pageParam: string,
1313
nextPageParam: string,
14+
initialPageParam: string,
1415
) => {
1516
const project = new Project({
1617
// Optionally specify compiler options, tsconfig.json, in-memory file system, and more here.
@@ -30,7 +31,12 @@ const createSourceFile = async (
3031
project,
3132
});
3233

33-
const exports = createExports(service, pageParam, nextPageParam);
34+
const exports = createExports(
35+
service,
36+
pageParam,
37+
nextPageParam,
38+
initialPageParam,
39+
);
3440

3541
const commonSource = ts.factory.createSourceFile(
3642
[...imports, ...exports.allCommon],
@@ -111,12 +117,14 @@ export const createSource = async ({
111117
serviceEndName,
112118
pageParam,
113119
nextPageParam,
120+
initialPageParam,
114121
}: {
115122
outputPath: string;
116123
version: string;
117124
serviceEndName: string;
118125
pageParam: string;
119126
nextPageParam: string;
127+
initialPageParam: string;
120128
}) => {
121129
const queriesFile = ts.createSourceFile(
122130
`${OpenApiRqFiles.queries}.ts`,
@@ -180,6 +188,7 @@ export const createSource = async ({
180188
serviceEndName,
181189
pageParam,
182190
nextPageParam,
191+
initialPageParam,
183192
);
184193

185194
const comment = `// generated with @7nohe/openapi-react-query-codegen@${version} \n\n`;

src/createUseQuery.mts

+27-6
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ export function createQueryHook({
229229
className,
230230
pageParam,
231231
nextPageParam,
232+
initialPageParam,
232233
}: {
233234
queryString: "useSuspenseQuery" | "useQuery" | "useInfiniteQuery";
234235
suffix: string;
@@ -238,6 +239,7 @@ export function createQueryHook({
238239
className: string;
239240
pageParam?: string;
240241
nextPageParam?: string;
242+
initialPageParam?: string;
241243
}) {
242244
const methodName = getNameFromMethod(method);
243245
const customHookName = hookNameFromMethod({ method, className });
@@ -447,7 +449,11 @@ export function createQueryHook({
447449
),
448450
),
449451
),
450-
...createInfiniteQueryParams(pageParam, nextPageParam),
452+
...createInfiniteQueryParams(
453+
pageParam,
454+
nextPageParam,
455+
initialPageParam,
456+
),
451457
ts.factory.createSpreadAssignment(
452458
ts.factory.createIdentifier("options"),
453459
),
@@ -467,6 +473,7 @@ export const createUseQuery = (
467473
{ className, method, jsDoc }: MethodDescription,
468474
pageParam: string,
469475
nextPageParam: string,
476+
initialPageParam: string,
470477
) => {
471478
const methodName = getNameFromMethod(method);
472479
const queryKey = createQueryKeyFromMethod({ method, className });
@@ -517,6 +524,7 @@ export const createUseQuery = (
517524
className,
518525
pageParam,
519526
nextPageParam,
527+
initialPageParam,
520528
})
521529
: undefined;
522530

@@ -625,14 +633,18 @@ function queryKeyFn(
625633
);
626634
}
627635

628-
function createInfiniteQueryParams(pageParam?: string, nextPageParam?: string) {
636+
function createInfiniteQueryParams(
637+
pageParam?: string,
638+
nextPageParam?: string,
639+
initialPageParam = "1",
640+
) {
629641
if (pageParam === undefined || nextPageParam === undefined) {
630642
return [];
631643
}
632644
return [
633645
ts.factory.createPropertyAssignment(
634646
ts.factory.createIdentifier("initialPageParam"),
635-
ts.factory.createNumericLiteral(1),
647+
ts.factory.createStringLiteral(initialPageParam),
636648
),
637649
ts.factory.createPropertyAssignment(
638650
ts.factory.createIdentifier("getNextPageParam"),
@@ -655,9 +667,18 @@ function createInfiniteQueryParams(pageParam?: string, nextPageParam?: string) {
655667
ts.factory.createParenthesizedExpression(
656668
ts.factory.createAsExpression(
657669
ts.factory.createIdentifier("response"),
658-
ts.factory.createTypeReferenceNode(
659-
ts.factory.createIdentifier(`{ ${nextPageParam}: number }`),
660-
),
670+
nextPageParam.split(".").reduceRight((acc, segment) => {
671+
return ts.factory.createTypeLiteralNode([
672+
ts.factory.createPropertySignature(
673+
undefined,
674+
ts.factory.createIdentifier(segment),
675+
undefined,
676+
acc,
677+
),
678+
]);
679+
}, ts.factory.createKeywordTypeNode(
680+
ts.SyntaxKind.NumberKeyword,
681+
) as ts.TypeNode),
661682
),
662683
),
663684
ts.factory.createIdentifier(nextPageParam),

src/generate.mts

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export async function generate(options: LimitedUserConfig, version: string) {
4949
serviceEndName: "Service", // we are hard coding this because changing the service end name was depreciated in @hey-api/openapi-ts
5050
pageParam: formattedOptions.pageParam,
5151
nextPageParam: formattedOptions.nextPageParam,
52+
initialPageParam: formattedOptions.initialPageParam.toString(),
5253
});
5354
await print(source, formattedOptions);
5455
const queriesOutputPath = buildQueriesOutputPath(options.output);

tests/__snapshots__/generate.test.ts.snap

+7-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,13 @@ import * as Common from "./common";
6262
export const useDefaultServiceFindPaginatedPetsInfinite = <TData = InfiniteData<Common.DefaultServiceFindPaginatedPetsDefaultResponse>, TError = unknown, TQueryKey extends Array<unknown> = unknown[]>({ limit, tags }: {
6363
limit?: number;
6464
tags?: string[];
65-
} = {}, queryKey?: TQueryKey, options?: Omit<UseInfiniteQueryOptions<TData, TError>, "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 });
65+
} = {}, queryKey?: TQueryKey, options?: Omit<UseInfiniteQueryOptions<TData, TError>, "queryKey" | "queryFn">) => useInfiniteQuery({
66+
queryKey: Common.UseDefaultServiceFindPaginatedPetsKeyFn({ limit, tags }, queryKey), queryFn: ({ pageParam }) => DefaultService.findPaginatedPets({ limit, page: pageParam as number, tags }) as TData, initialPageParam: "initial", getNextPageParam: response => (response as {
67+
meta: {
68+
next: number;
69+
};
70+
}).meta.next, ...options
71+
});
6672
"
6773
`;
6874

tests/createExports.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ describe(fileName, () => {
1717
});
1818
project.addSourceFilesAtPaths(path.join(outputPath(fileName), "**", "*"));
1919
const service = await getServices(project);
20-
const exports = createExports(service, "page", "nextPage");
20+
const exports = createExports(service, "page", "nextPage", "initial");
2121

2222
const commonTypes = exports.allCommon
2323
.filter((c) => c.kind === SyntaxKind.TypeAliasDeclaration)

tests/generate.test.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ describe("generate", () => {
2020
output: path.join("tests", "outputs"),
2121
lint: "eslint",
2222
pageParam: "page",
23-
nextPageParam: "nextPage",
23+
nextPageParam: "meta.next",
24+
initialPageParam: "initial",
2425
};
2526
await generate(options, "1.0.0");
2627
});

0 commit comments

Comments
 (0)