Skip to content

Commit 01f62f0

Browse files
authored
Merge pull request #353 from OpenAPI-Qraft/fix/queryable-write-operations-body-usage
fix(react-client): improve `body` parameter handling in queryable write operations
2 parents 92ded42 + 2a4db33 commit 01f62f0

6 files changed

Lines changed: 172 additions & 14 deletions

File tree

.changeset/ten-monkeys-lie.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@openapi-qraft/react': patch
3+
---
4+
5+
Refactored parameter preparation logic for queryable write operations to correctly handle body parameters in both regular and infinite queries. The update extracts shared logic into a reusable utility and ensures consistent parameter handling across all affected query methods.

packages/react-client/src/lib/callQueryClientFetchMethod.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import type { CreateAPIQueryClientOptions } from '../qraftAPIClient.js';
33
import type { OperationSchema, RequestFn } from './requestFn.js';
44
import { composeInfiniteQueryKey } from './composeInfiniteQueryKey.js';
55
import { composeQueryKey } from './composeQueryKey.js';
6+
import { prepareRequestFnParameters } from './prepareRequestFnParameters.js';
67
import { requestFnResponseRejecter } from './requestFnResponseRejecter.js';
78
import { requestFnResponseResolver } from './requestFnResponseResolver.js';
8-
import { shelfMerge } from './shelfMerge.js';
99

1010
/**
1111
* Calls a query client fetch method with parameters and options,
@@ -50,11 +50,16 @@ export function callQueryClientMethodWithQueryKey<
5050
(requestFn
5151
? // @ts-expect-error - Too complex union to type
5252
function ({ queryKey: [, queryParams], signal, meta, pageParam }) {
53+
const { parameters, body } = prepareRequestFnParameters(
54+
queryParams,
55+
pageParam,
56+
infinite
57+
);
58+
5359
return requestFn(schema, {
54-
parameters: infinite
55-
? (shelfMerge(2, queryParams, pageParam) as never)
56-
: queryParams,
60+
parameters: parameters as never,
5761
baseUrl,
62+
body,
5863
signal,
5964
meta,
6065
}).then(requestFnResponseResolver, requestFnResponseRejecter);
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { shelfMerge } from './shelfMerge.js';
2+
3+
/**
4+
* Prepares parameters and body for requestFn calls, handling infinite query logic
5+
* @param queryParams - The query parameters that may contain body
6+
* @param pageParam - The page parameter for infinite queries
7+
* @param infinite - Whether this is for an infinite query
8+
* @returns Object with prepared parameters and body
9+
*/
10+
export function prepareRequestFnParameters<TQueryParams>(
11+
queryParams: TQueryParams,
12+
pageParam: unknown,
13+
infinite: boolean
14+
): {
15+
parameters: TQueryParams extends { body: any }
16+
? Omit<TQueryParams, 'body'>
17+
: TQueryParams;
18+
body: BodyInit | undefined;
19+
} {
20+
const { body, ...parameters } = queryParams as {
21+
body?: BodyInit;
22+
} & TQueryParams;
23+
24+
if (!infinite) {
25+
return {
26+
parameters: parameters as never,
27+
body: body as never,
28+
};
29+
}
30+
31+
// Handle infinite query parameters
32+
const processedParameters = shelfMerge(
33+
2,
34+
parameters,
35+
omitBodyFromPageParam(pageParam)
36+
) as never;
37+
38+
const processedBody = mergeBodyWithPageParam(body, pageParam) as never;
39+
40+
return {
41+
parameters: processedParameters,
42+
body: processedBody,
43+
};
44+
}
45+
46+
/**
47+
* Remove body from pageParam if it exists
48+
*/
49+
function omitBodyFromPageParam(pageParam: unknown) {
50+
if (pageParam && typeof pageParam === 'object' && 'body' in pageParam) {
51+
const { body: _body, ...pageParameters } = pageParam;
52+
return pageParameters;
53+
}
54+
55+
return pageParam;
56+
}
57+
58+
/**
59+
* Merge body with pageParam.body if pageParam contains body
60+
*/
61+
function mergeBodyWithPageParam(
62+
body: BodyInit | undefined,
63+
pageParam: unknown
64+
): BodyInit | undefined {
65+
if (pageParam && typeof pageParam === 'object' && 'body' in pageParam) {
66+
return shelfMerge(1, body, pageParam.body) as BodyInit;
67+
}
68+
69+
return body;
70+
}

packages/react-client/src/lib/useComposeUseQueryOptions.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ import type { CreateAPIQueryClientOptions } from '../qraftAPIClient.js';
66
import type { OperationSchema } from './requestFn.js';
77
import { composeInfiniteQueryKey } from './composeInfiniteQueryKey.js';
88
import { composeQueryKey } from './composeQueryKey.js';
9+
import { prepareRequestFnParameters } from './prepareRequestFnParameters.js';
910
import { requestFnResponseRejecter } from './requestFnResponseRejecter.js';
1011
import { requestFnResponseResolver } from './requestFnResponseResolver.js';
11-
import { shelfMerge } from './shelfMerge.js';
1212

1313
/**
1414
* Composes the options for useQuery, useInfiniteQuery, useSuspenseQuery, and useSuspenseQueries.
@@ -25,17 +25,15 @@ export function useComposeUseQueryOptions(
2525
const queryFn =
2626
options?.queryFn ??
2727
function ({ queryKey: [, queryParams], signal, meta, pageParam }) {
28-
const { body, ...parameters } = queryParams as {
29-
body: BodyInit;
30-
parameters: Record<string, unknown>;
31-
};
28+
const { parameters, body } = prepareRequestFnParameters(
29+
queryParams,
30+
pageParam,
31+
infinite
32+
);
3233

3334
return qraftOptions
3435
.requestFn(schema, {
35-
// @ts-expect-error - Too complex to type
36-
parameters: infinite
37-
? (shelfMerge(2, parameters, pageParam) as never)
38-
: parameters,
36+
parameters: parameters as never,
3937
baseUrl: qraftOptions.baseUrl,
4038
body,
4139
signal,

packages/react-client/src/tests/msw/handlers.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ export const handlers = [
6969
name: body.name!,
7070
description: body.description!,
7171
id: path.approval_policy_id,
72+
trigger: body.trigger,
7273
});
7374
}
7475
),

packages/react-client/src/tests/qraftAPIClient.queryable-write-operations.test.tsx

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { createAPIClient } from './fixtures/queryable-write-operations-api/index
99
const baseUrl = 'https://api.sandbox.monite.com/v1';
1010

1111
describe('Queryable write operations', () => {
12-
it('queries writable operations with a specific body', async () => {
12+
it('queries writable operations with a specific body for useQuery', async () => {
1313
const queryClient = new QueryClient();
1414
const qraft = createAPIClient({
1515
requestFn,
@@ -48,6 +48,85 @@ describe('Queryable write operations', () => {
4848
});
4949
});
5050

51+
it('queries writable operations with a specific body for fetchQuery', async () => {
52+
const queryClient = new QueryClient();
53+
const qraft = createAPIClient({
54+
requestFn,
55+
baseUrl,
56+
queryClient,
57+
});
58+
59+
const result =
60+
await qraft.approvalPolicies.patchApprovalPoliciesId.fetchQuery({
61+
parameters: {
62+
header: {
63+
'x-monite-version': '1',
64+
'x-monite-entity-id': '2',
65+
},
66+
path: {
67+
approval_policy_id: '2',
68+
},
69+
body: {
70+
name: 'New Name',
71+
description: 'New Description',
72+
},
73+
},
74+
});
75+
76+
await waitFor(() => {
77+
expect(result).toEqual({
78+
id: '2',
79+
name: 'New Name',
80+
description: 'New Description',
81+
});
82+
});
83+
});
84+
85+
it('queries writable operations with a specific body for fetchInfiniteQuery', async () => {
86+
const queryClient = new QueryClient();
87+
const qraft = createAPIClient({
88+
requestFn,
89+
baseUrl,
90+
queryClient,
91+
});
92+
93+
const result =
94+
await qraft.approvalPolicies.patchApprovalPoliciesId.fetchInfiniteQuery({
95+
parameters: {
96+
header: {
97+
'x-monite-version': '1',
98+
'x-monite-entity-id': '2',
99+
},
100+
path: {
101+
approval_policy_id: '2',
102+
},
103+
body: {
104+
name: 'New Name',
105+
description: 'New Description',
106+
},
107+
},
108+
initialPageParam: {
109+
body: {
110+
trigger: true,
111+
},
112+
},
113+
});
114+
115+
await waitFor(() => {
116+
expect(result).toEqual({
117+
pageParams: [{ body: { trigger: true } }],
118+
pages: [
119+
{
120+
id: '2',
121+
name: 'New Name',
122+
description: 'New Description',
123+
trigger: true,
124+
},
125+
],
126+
});
127+
});
128+
});
129+
51130
it('queries writable operations without body', async () => {
52131
const queryClient = new QueryClient();
53132
const qraft = createAPIClient({

0 commit comments

Comments
 (0)