@openapi-qraft/react 2.6.3 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/dist/cjs/Unstable_QraftSecureRequestFn.cjs +8 -1
- package/dist/cjs/Unstable_QraftSecureRequestFn.cjs.map +1 -1
- package/dist/cjs/callbacks/useMutation.cjs +0 -1
- package/dist/cjs/callbacks/useMutation.cjs.map +1 -1
- package/dist/cjs/lib/responseUtils.cjs +15 -5
- package/dist/cjs/lib/responseUtils.cjs.map +1 -1
- package/dist/cjs/qraftAPIClient.cjs.map +1 -1
- package/dist/esm/Unstable_QraftSecureRequestFn.js +8 -1
- package/dist/esm/Unstable_QraftSecureRequestFn.js.map +1 -1
- package/dist/esm/callbacks/useMutation.js +0 -1
- package/dist/esm/callbacks/useMutation.js.map +1 -1
- package/dist/esm/lib/responseUtils.js +15 -5
- package/dist/esm/lib/responseUtils.js.map +1 -1
- package/dist/esm/qraftAPIClient.js.map +1 -1
- package/dist/types/Unstable_QraftSecureRequestFn.d.ts +9 -9
- package/dist/types/Unstable_QraftSecureRequestFn.d.ts.map +1 -1
- package/dist/types/callbacks/useMutation.d.ts.map +1 -1
- package/dist/types/lib/__tests__/responseUtils.test.d.ts +2 -0
- package/dist/types/lib/__tests__/responseUtils.test.d.ts.map +1 -0
- package/dist/types/qraftAPIClient.d.ts +1 -1
- package/dist/types/qraftAPIClient.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/Unstable_QraftSecureRequestFn.ts +40 -31
- package/src/callbacks/useMutation.ts +0 -1
- package/src/lib/__tests__/responseUtils.test.ts +105 -0
- package/src/lib/responseUtils.ts +15 -6
- package/src/qraftAPIClient.ts +2 -2
|
@@ -12,31 +12,29 @@ import { QueryClient, useQueries } from '@tanstack/react-query';
|
|
|
12
12
|
import { createElement, Fragment, useEffect, useMemo } from 'react';
|
|
13
13
|
import { jwtDecode } from './lib/jwt-decode/index.js';
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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;
|
|
21
23
|
securitySchemes: SecuritySchemeHandlers<string>;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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;
|
|
31
29
|
queryClient?: QueryClient;
|
|
32
30
|
}
|
|
33
31
|
|
|
34
|
-
export function QraftSecureRequestFn({
|
|
32
|
+
export function QraftSecureRequestFn<TRequestFn extends RequestFn>({
|
|
35
33
|
children,
|
|
36
34
|
requestFn,
|
|
37
35
|
securitySchemes,
|
|
38
36
|
queryClient: inQueryClient,
|
|
39
|
-
}: QraftSecureRequestFnProps) {
|
|
37
|
+
}: QraftSecureRequestFnProps<TRequestFn>) {
|
|
40
38
|
const queryClient = useMemo(
|
|
41
39
|
() =>
|
|
42
40
|
inQueryClient ??
|
|
@@ -72,13 +70,13 @@ export function QraftSecureRequestFn({
|
|
|
72
70
|
});
|
|
73
71
|
}
|
|
74
72
|
|
|
75
|
-
export const useSecuritySchemeAuth = ({
|
|
73
|
+
export const useSecuritySchemeAuth = <TRequestFn extends RequestFn>({
|
|
76
74
|
securitySchemes,
|
|
77
75
|
requestFn,
|
|
78
76
|
queryClient,
|
|
79
|
-
}: QraftSecureRequestFnBaseProps & {
|
|
77
|
+
}: QraftSecureRequestFnBaseProps<TRequestFn> & {
|
|
80
78
|
queryClient: QueryClient;
|
|
81
|
-
}):
|
|
79
|
+
}): TRequestFn => {
|
|
82
80
|
useQueries(
|
|
83
81
|
{
|
|
84
82
|
queries: Object.entries(securitySchemes).map(([securitySchemeName]) => {
|
|
@@ -101,7 +99,7 @@ export const useSecuritySchemeAuth = ({
|
|
|
101
99
|
return useMemo(
|
|
102
100
|
() => createSecureRequestFn(securitySchemes, requestFn, queryClient),
|
|
103
101
|
[securitySchemes, requestFn, queryClient]
|
|
104
|
-
);
|
|
102
|
+
) as TRequestFn;
|
|
105
103
|
};
|
|
106
104
|
|
|
107
105
|
/**
|
|
@@ -113,11 +111,11 @@ export const useSecuritySchemeAuth = ({
|
|
|
113
111
|
* @param requestFn Qraft `requestFn`
|
|
114
112
|
* @param queryClient QueryClient instance.
|
|
115
113
|
*/
|
|
116
|
-
export function createSecureRequestFn(
|
|
114
|
+
export function createSecureRequestFn<TRequestFn extends RequestFn>(
|
|
117
115
|
securitySchemes: SecuritySchemeHandlers<string>,
|
|
118
|
-
requestFn:
|
|
116
|
+
requestFn: TRequestFn,
|
|
119
117
|
queryClient: QueryClient
|
|
120
|
-
):
|
|
118
|
+
): TRequestFn {
|
|
121
119
|
return async function secureRequestFn(
|
|
122
120
|
schema: OperationSchema,
|
|
123
121
|
requestInfo: RequestFnInfo,
|
|
@@ -147,7 +145,7 @@ export function createSecureRequestFn(
|
|
|
147
145
|
),
|
|
148
146
|
options
|
|
149
147
|
);
|
|
150
|
-
};
|
|
148
|
+
} as TRequestFn;
|
|
151
149
|
}
|
|
152
150
|
|
|
153
151
|
/**
|
|
@@ -174,14 +172,25 @@ async function createSecureRequestInfo(
|
|
|
174
172
|
): Promise<RequestFnInfo> {
|
|
175
173
|
const prevSecurityResult = queryClient.getQueryData<SecurityScheme>(queryKey);
|
|
176
174
|
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
+
);
|
|
185
194
|
|
|
186
195
|
if (!shallowEqualObjects(securityResult, prevSecurityResult)) {
|
|
187
196
|
const securityRefreshInterval = getSecurityRefreshInterval(securityResult);
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { resolveResponse } from '../responseUtils.js';
|
|
3
|
+
|
|
4
|
+
describe('resolveResponse', () => {
|
|
5
|
+
it('should handle Error without promise', async () => {
|
|
6
|
+
const testError = new Error('Test error');
|
|
7
|
+
|
|
8
|
+
const result = await resolveResponse<unknown, Error>(testError);
|
|
9
|
+
|
|
10
|
+
expect(result).toEqual({
|
|
11
|
+
error: testError,
|
|
12
|
+
response: undefined,
|
|
13
|
+
data: undefined,
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('should handle Response without promise', async () => {
|
|
18
|
+
const mockResponse = new Response(null);
|
|
19
|
+
|
|
20
|
+
// We use casting to bypass type checking, since we know that the function accepts Response
|
|
21
|
+
// without a promise in the implementation, although types do not allow it directly.
|
|
22
|
+
const result = await (resolveResponse as any)(mockResponse);
|
|
23
|
+
|
|
24
|
+
expect(result).toEqual({
|
|
25
|
+
error: expect.any(Error),
|
|
26
|
+
response: mockResponse,
|
|
27
|
+
data: undefined,
|
|
28
|
+
});
|
|
29
|
+
expect((result.error as Error).message).toBe(
|
|
30
|
+
'Unhandled response without promise to resolve'
|
|
31
|
+
);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should resolve promise with data and response', async () => {
|
|
35
|
+
const mockResponse = new Response(null);
|
|
36
|
+
const testData = { test: 'data' };
|
|
37
|
+
const dataPromise = Promise.resolve(testData);
|
|
38
|
+
|
|
39
|
+
const result = await resolveResponse<typeof testData, Error>(
|
|
40
|
+
mockResponse,
|
|
41
|
+
dataPromise
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
expect(result).toEqual({
|
|
45
|
+
data: testData,
|
|
46
|
+
response: mockResponse,
|
|
47
|
+
error: undefined,
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should handle rejected promise with error and response', async () => {
|
|
52
|
+
const mockResponse = new Response(null);
|
|
53
|
+
const testError = new Error('Test rejection');
|
|
54
|
+
const dataPromise = Promise.reject(testError);
|
|
55
|
+
|
|
56
|
+
const result = await resolveResponse<unknown, Error>(
|
|
57
|
+
mockResponse,
|
|
58
|
+
dataPromise
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
expect(result).toEqual({
|
|
62
|
+
error: testError,
|
|
63
|
+
response: mockResponse,
|
|
64
|
+
data: undefined,
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should handle custom error objects in rejected promise', async () => {
|
|
69
|
+
const mockResponse = new Response(null);
|
|
70
|
+
type CustomError = { code: string; message: string };
|
|
71
|
+
const customError: CustomError = {
|
|
72
|
+
code: 'CUSTOM_ERROR',
|
|
73
|
+
message: 'Custom error object',
|
|
74
|
+
};
|
|
75
|
+
const dataPromise = Promise.reject(customError);
|
|
76
|
+
|
|
77
|
+
const result = await resolveResponse<unknown, CustomError>(
|
|
78
|
+
mockResponse,
|
|
79
|
+
dataPromise
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
expect(result).toEqual({
|
|
83
|
+
error: customError,
|
|
84
|
+
response: mockResponse,
|
|
85
|
+
data: undefined,
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should preserve response object when promise resolves', async () => {
|
|
90
|
+
const headers = new Headers();
|
|
91
|
+
headers.append('Content-Type', 'application/json');
|
|
92
|
+
const mockResponse = new Response('{"key":"value"}', { headers });
|
|
93
|
+
const testData = { key: 'value' };
|
|
94
|
+
const dataPromise = Promise.resolve(testData);
|
|
95
|
+
|
|
96
|
+
const result = await resolveResponse<typeof testData, Error>(
|
|
97
|
+
mockResponse,
|
|
98
|
+
dataPromise
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
expect(result.response).toBe(mockResponse);
|
|
102
|
+
expect(result.data).toEqual(testData);
|
|
103
|
+
expect(result.error).toBeUndefined();
|
|
104
|
+
});
|
|
105
|
+
});
|
package/src/lib/responseUtils.ts
CHANGED
|
@@ -49,12 +49,21 @@ export function resolveResponse<TData, TError>(
|
|
|
49
49
|
responseToReturn: Response | Error,
|
|
50
50
|
responsePromise?: Promise<TData>
|
|
51
51
|
): Promise<RequestFnResponse<TData, TError>> {
|
|
52
|
-
if (!responsePromise)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
52
|
+
if (!responsePromise) {
|
|
53
|
+
if (responseToReturn instanceof Response) {
|
|
54
|
+
return Promise.resolve({
|
|
55
|
+
error: new Error('Unhandled response without promise to resolve'),
|
|
56
|
+
response: responseToReturn,
|
|
57
|
+
data: undefined,
|
|
58
|
+
});
|
|
59
|
+
} else {
|
|
60
|
+
return Promise.resolve({
|
|
61
|
+
error: responseToReturn,
|
|
62
|
+
response: undefined,
|
|
63
|
+
data: undefined,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
58
67
|
|
|
59
68
|
return responsePromise
|
|
60
69
|
.then(
|
package/src/qraftAPIClient.ts
CHANGED
|
@@ -10,10 +10,10 @@ import type {
|
|
|
10
10
|
import { createRecursiveProxy } from './lib/createRecursiveProxy.js';
|
|
11
11
|
|
|
12
12
|
export interface CreateAPIBasicClientOptions {
|
|
13
|
-
requestFn
|
|
13
|
+
requestFn: (
|
|
14
14
|
schema: OperationSchema,
|
|
15
15
|
requestInfo: RequestFnInfo
|
|
16
|
-
)
|
|
16
|
+
) => Promise<RequestFnResponse<any, any>>;
|
|
17
17
|
baseUrl: string;
|
|
18
18
|
}
|
|
19
19
|
|