Skip to content

Commit 568c39c

Browse files
authored
Merge pull request #336 from OpenAPI-Qraft/fix/react-client-secure
chore: simplify `requestFn` type usage
2 parents 38d578b + 167874b commit 568c39c

File tree

7 files changed

+130
-35
lines changed

7 files changed

+130
-35
lines changed

.changeset/petite-games-occur.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+
Make the type of the input `requestFn` parameter more flexible.

.changeset/poor-forks-roll.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+
Fix the handling of the external abort signal for the `requestInfo` in the `createSecureRequest()` function.

packages/react-client/src/Unstable_QraftSecureRequestFn.ts

Lines changed: 40 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -12,31 +12,29 @@ import { QueryClient, useQueries } from '@tanstack/react-query';
1212
import { createElement, Fragment, useEffect, useMemo } from 'react';
1313
import { jwtDecode } from './lib/jwt-decode/index.js';
1414

15-
interface QraftSecureRequestFnBaseProps {
16-
requestFn<TData, TError>(
17-
schema: OperationSchema,
18-
requestInfo: RequestFnInfo,
19-
options?: RequestFnOptions
20-
): Promise<RequestFnResponse<TData, TError>>;
15+
type RequestFn = <TData, TError>(
16+
schema: OperationSchema,
17+
requestInfo: RequestFnInfo,
18+
options?: RequestFnOptions
19+
) => Promise<RequestFnResponse<TData, TError>>;
20+
21+
interface QraftSecureRequestFnBaseProps<TRequestFn extends RequestFn> {
22+
requestFn: TRequestFn;
2123
securitySchemes: SecuritySchemeHandlers<string>;
2224
}
2325

24-
type RequestFn = QraftSecureRequestFnBaseProps['requestFn'];
25-
26-
export interface QraftSecureRequestFnProps
27-
extends QraftSecureRequestFnBaseProps {
28-
children(
29-
secureRequestFn: QraftSecureRequestFnBaseProps['requestFn']
30-
): ReactNode;
26+
export interface QraftSecureRequestFnProps<TRequestFn extends RequestFn>
27+
extends QraftSecureRequestFnBaseProps<TRequestFn> {
28+
children(secureRequestFn: TRequestFn): ReactNode;
3129
queryClient?: QueryClient;
3230
}
3331

34-
export function QraftSecureRequestFn({
32+
export function QraftSecureRequestFn<TRequestFn extends RequestFn>({
3533
children,
3634
requestFn,
3735
securitySchemes,
3836
queryClient: inQueryClient,
39-
}: QraftSecureRequestFnProps) {
37+
}: QraftSecureRequestFnProps<TRequestFn>) {
4038
const queryClient = useMemo(
4139
() =>
4240
inQueryClient ??
@@ -72,13 +70,13 @@ export function QraftSecureRequestFn({
7270
});
7371
}
7472

75-
export const useSecuritySchemeAuth = ({
73+
export const useSecuritySchemeAuth = <TRequestFn extends RequestFn>({
7674
securitySchemes,
7775
requestFn,
7876
queryClient,
79-
}: QraftSecureRequestFnBaseProps & {
77+
}: QraftSecureRequestFnBaseProps<TRequestFn> & {
8078
queryClient: QueryClient;
81-
}): RequestFn => {
79+
}): TRequestFn => {
8280
useQueries(
8381
{
8482
queries: Object.entries(securitySchemes).map(([securitySchemeName]) => {
@@ -101,7 +99,7 @@ export const useSecuritySchemeAuth = ({
10199
return useMemo(
102100
() => createSecureRequestFn(securitySchemes, requestFn, queryClient),
103101
[securitySchemes, requestFn, queryClient]
104-
);
102+
) as TRequestFn;
105103
};
106104

107105
/**
@@ -113,11 +111,11 @@ export const useSecuritySchemeAuth = ({
113111
* @param requestFn Qraft `requestFn`
114112
* @param queryClient QueryClient instance.
115113
*/
116-
export function createSecureRequestFn(
114+
export function createSecureRequestFn<TRequestFn extends RequestFn>(
117115
securitySchemes: SecuritySchemeHandlers<string>,
118-
requestFn: RequestFn,
116+
requestFn: TRequestFn,
119117
queryClient: QueryClient
120-
): RequestFn {
118+
): TRequestFn {
121119
return async function secureRequestFn(
122120
schema: OperationSchema,
123121
requestInfo: RequestFnInfo,
@@ -147,7 +145,7 @@ export function createSecureRequestFn(
147145
),
148146
options
149147
);
150-
};
148+
} as TRequestFn;
151149
}
152150

153151
/**
@@ -174,14 +172,25 @@ async function createSecureRequestInfo(
174172
): Promise<RequestFnInfo> {
175173
const prevSecurityResult = queryClient.getQueryData<SecurityScheme>(queryKey);
176174

177-
const securityResult = await queryClient.fetchQuery({
178-
queryKey,
179-
queryFn: ({ signal }) =>
180-
handler({
181-
signal,
182-
isRefreshing: Boolean(prevSecurityResult),
183-
}),
184-
});
175+
const abortQuery = () => queryClient.cancelQueries({ queryKey, exact: true });
176+
177+
requestInfo.signal?.addEventListener('abort', abortQuery);
178+
179+
const securityResult = await queryClient
180+
.fetchQuery({
181+
queryKey,
182+
queryFn: ({ signal }) =>
183+
handler({
184+
signal,
185+
isRefreshing: Boolean(prevSecurityResult),
186+
}),
187+
})
188+
.catch((error) => {
189+
throw requestInfo.signal?.aborted ? requestInfo.signal.reason : error;
190+
})
191+
.finally(
192+
() => void requestInfo.signal?.removeEventListener('abort', abortQuery)
193+
);
185194

186195
if (!shallowEqualObjects(securityResult, prevSecurityResult)) {
187196
const securityRefreshInterval = getSecurityRefreshInterval(securityResult);

packages/react-client/src/callbacks/useMutation.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ export const useMutation: <
5959
{
6060
...options,
6161
mutationKey,
62-
// @ts-expect-error - Too complex to type
6362
mutationFn:
6463
options?.mutationFn ??
6564
(parameters

packages/react-client/src/qraftAPIClient.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ import type {
1010
import { createRecursiveProxy } from './lib/createRecursiveProxy.js';
1111

1212
export interface CreateAPIBasicClientOptions {
13-
requestFn<TData, TError>(
13+
requestFn: (
1414
schema: OperationSchema,
1515
requestInfo: RequestFnInfo
16-
): Promise<RequestFnResponse<TData, TError>>;
16+
) => Promise<RequestFnResponse<any, any>>;
1717
baseUrl: string;
1818
}
1919

packages/react-client/src/tests/QraftSecureRequestFn.test.tsx

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,84 @@ describe('QraftSecureRequestFn', { timeout: 10_000 }, () => {
618618
],
619619
]);
620620
});
621+
622+
it('should cancel token fetching when request is aborted', async () => {
623+
const abortController = new AbortController();
624+
625+
const fetchPartnerToken = vi
626+
.fn()
627+
.mockImplementation(() => new Promise(() => {}));
628+
629+
const queryClient = new QueryClient();
630+
631+
const { result } = renderHook(
632+
() =>
633+
useQraft().entities.postEntitiesIdDocuments({
634+
signal: abortController.signal,
635+
parameters: mutationParams,
636+
body: { verification_document_front: '' },
637+
}),
638+
{
639+
wrapper: (props) => (
640+
<QraftSecureRequestFn
641+
requestFn={requestFn}
642+
queryClient={queryClient}
643+
securitySchemes={{
644+
partnerToken: fetchPartnerToken,
645+
}}
646+
>
647+
{(secureRequestFn) => (
648+
<Providers {...props} requestFn={secureRequestFn} />
649+
)}
650+
</QraftSecureRequestFn>
651+
),
652+
}
653+
);
654+
655+
await vi.advanceTimersByTimeAsync(100);
656+
await queryClient.cancelQueries();
657+
658+
await expect(result.current).rejects.toThrow('CancelledError');
659+
});
660+
661+
it('should cancel token fetching when externally aborted', async () => {
662+
const abortController = new AbortController();
663+
664+
const fetchPartnerToken = vi
665+
.fn()
666+
.mockImplementation(() => new Promise(() => {}));
667+
668+
const queryClient = new QueryClient();
669+
670+
const { result } = renderHook(
671+
() =>
672+
useQraft().entities.postEntitiesIdDocuments({
673+
signal: abortController.signal,
674+
parameters: mutationParams,
675+
body: { verification_document_front: '' },
676+
}),
677+
{
678+
wrapper: (props) => (
679+
<QraftSecureRequestFn
680+
requestFn={requestFn}
681+
queryClient={queryClient}
682+
securitySchemes={{
683+
partnerToken: fetchPartnerToken,
684+
}}
685+
>
686+
{(secureRequestFn) => (
687+
<Providers {...props} requestFn={secureRequestFn} />
688+
)}
689+
</QraftSecureRequestFn>
690+
),
691+
}
692+
);
693+
694+
await vi.advanceTimersByTimeAsync(100);
695+
abortController.abort(new Error('My Custom Abort Error'));
696+
697+
await expect(result.current).rejects.toThrow('My Custom Abort Error');
698+
});
621699
});
622700

623701
function Providers({

packages/react-client/src/tests/qraftAPIClient.test.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1920,7 +1920,6 @@ describe('Qraft uses "fetchQuery(...) & "prefetchQuery(...)" & "ensureQueryData(
19201920
const requestFnSpy = vi.fn(requestFn);
19211921

19221922
const { qraft } = createClient({
1923-
// @ts-expect-error - vi.fn types are not correct
19241923
requestFn: requestFnSpy,
19251924
});
19261925

0 commit comments

Comments
 (0)