@geekmidas/client 0.4.0 → 0.5.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/dist/auth-fetcher.cjs +0 -1
- package/dist/auth-fetcher.cjs.map +1 -1
- package/dist/auth-fetcher.d.cts +1 -1
- package/dist/auth-fetcher.d.cts.map +1 -1
- package/dist/auth-fetcher.d.mts +1 -1
- package/dist/auth-fetcher.d.mts.map +1 -1
- package/dist/auth-fetcher.mjs +0 -1
- package/dist/auth-fetcher.mjs.map +1 -1
- package/dist/endpoint-hooks.cjs +10 -5
- package/dist/endpoint-hooks.cjs.map +1 -1
- package/dist/endpoint-hooks.d.cts +2 -2
- package/dist/endpoint-hooks.d.cts.map +1 -1
- package/dist/endpoint-hooks.d.mts +2 -2
- package/dist/endpoint-hooks.d.mts.map +1 -1
- package/dist/endpoint-hooks.mjs +10 -5
- package/dist/endpoint-hooks.mjs.map +1 -1
- package/dist/fetcher-5fnBE7Dk.mjs.map +1 -1
- package/dist/fetcher-CgLEaIAC.cjs.map +1 -1
- package/dist/fetcher.d.cts +1 -1
- package/dist/fetcher.d.cts.map +1 -1
- package/dist/fetcher.d.mts +1 -1
- package/dist/fetcher.d.mts.map +1 -1
- package/dist/infer.d.cts.map +1 -1
- package/dist/infer.d.mts.map +1 -1
- package/dist/openapi-hooks.cjs.map +1 -1
- package/dist/openapi-hooks.d.cts +2 -2
- package/dist/openapi-hooks.d.cts.map +1 -1
- package/dist/openapi-hooks.d.mts +2 -2
- package/dist/openapi-hooks.d.mts.map +1 -1
- package/dist/openapi-hooks.mjs.map +1 -1
- package/dist/openapi-types.d.cts.map +1 -1
- package/dist/openapi-types.d.mts.map +1 -1
- package/dist/openapi.cjs.map +1 -1
- package/dist/openapi.mjs.map +1 -1
- package/dist/react-query.cjs.map +1 -1
- package/dist/react-query.d.cts +1 -1
- package/dist/react-query.d.cts.map +1 -1
- package/dist/react-query.d.mts +1 -1
- package/dist/react-query.d.mts.map +1 -1
- package/dist/react-query.mjs.map +1 -1
- package/dist/{types-FxummKaL.d.mts → types-4K4N-Fl1.d.cts} +2 -2
- package/dist/types-4K4N-Fl1.d.cts.map +1 -0
- package/dist/{types-vutATyWv.d.cts → types-hhkZWjQn.d.mts} +2 -2
- package/dist/types-hhkZWjQn.d.mts.map +1 -0
- package/dist/types.d.cts +1 -1
- package/dist/types.d.mts +1 -1
- package/package.json +3 -3
- package/src/__tests__/auth-fetcher.spec.ts +476 -476
- package/src/__tests__/endpoint-hooks.spec.tsx +348 -348
- package/src/__tests__/fetcher.spec.ts +403 -403
- package/src/__tests__/infer.integration.spec.ts +171 -171
- package/src/__tests__/infer.spec.ts +182 -182
- package/src/__tests__/method-restrictions.spec.tsx +165 -165
- package/src/__tests__/openapi-hooks.spec.tsx +536 -536
- package/src/__tests__/react-query-infinite.spec.tsx +989 -989
- package/src/__tests__/react-query-invalidation.spec.tsx +223 -222
- package/src/__tests__/react-query.spec.tsx +567 -567
- package/src/__tests__/setup.ts +260 -260
- package/src/__tests__/types.spec.ts +127 -127
- package/src/__tests__/url-parsing.spec.ts +191 -191
- package/src/auth-fetcher.ts +160 -162
- package/src/endpoint-hooks.ts +151 -156
- package/src/fetcher.ts +163 -163
- package/src/infer.ts +64 -63
- package/src/openapi-hooks.ts +146 -146
- package/src/openapi-types.d.ts +428 -428
- package/src/openapi.json +593 -593
- package/src/react-query.ts +281 -281
- package/src/types.ts +121 -124
- package/dist/types-FxummKaL.d.mts.map +0 -1
- package/dist/types-vutATyWv.d.cts.map +0 -1
package/src/fetcher.ts
CHANGED
|
@@ -1,175 +1,175 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
EndpointString,
|
|
3
|
+
ExtractEndpointResponse,
|
|
4
|
+
FetcherOptions,
|
|
5
|
+
FilteredRequestConfig,
|
|
6
|
+
ParseEndpoint,
|
|
7
|
+
TypedEndpoint,
|
|
8
8
|
} from './types';
|
|
9
9
|
|
|
10
10
|
export type { FetcherOptions } from './types';
|
|
11
11
|
|
|
12
12
|
export class TypedFetcher<Paths> {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
13
|
+
private baseURL: string;
|
|
14
|
+
private defaultHeaders: Record<string, string>;
|
|
15
|
+
private options: FetcherOptions;
|
|
16
|
+
private fetchFn: FetchFn;
|
|
17
|
+
|
|
18
|
+
static getFetchFn(fn?: FetchFn): FetchFn {
|
|
19
|
+
if (fn) {
|
|
20
|
+
return fn;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (typeof window !== 'undefined' && typeof window.fetch === 'function') {
|
|
24
|
+
return window.fetch.bind(window);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (
|
|
28
|
+
typeof globalThis !== 'undefined' &&
|
|
29
|
+
typeof globalThis.fetch === 'function'
|
|
30
|
+
) {
|
|
31
|
+
return globalThis.fetch.bind(globalThis);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
throw new Error('No fetch implementation found');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
constructor(options: FetcherOptions = {}) {
|
|
38
|
+
this.baseURL = options.baseURL || '';
|
|
39
|
+
this.defaultHeaders = options.headers || {};
|
|
40
|
+
this.options = options;
|
|
41
|
+
this.fetchFn = TypedFetcher.getFetchFn(options.fetch);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async request<T extends TypedEndpoint<Paths>>(
|
|
45
|
+
endpoint: T,
|
|
46
|
+
config?: FilteredRequestConfig<Paths, T>,
|
|
47
|
+
): Promise<ExtractEndpointResponse<Paths, T>> {
|
|
48
|
+
const { method, route } = this.parseEndpoint(endpoint);
|
|
49
|
+
|
|
50
|
+
// Replace path parameters
|
|
51
|
+
let url = route;
|
|
52
|
+
if (config && 'params' in config && config.params) {
|
|
53
|
+
Object.entries(config.params as Record<string, unknown>).forEach(
|
|
54
|
+
([key, value]) => {
|
|
55
|
+
url = url.replace(`{${key}}`, encodeURIComponent(String(value)));
|
|
56
|
+
},
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Add query parameters
|
|
61
|
+
if (config && 'query' in config && config.query) {
|
|
62
|
+
const queryParams = new URLSearchParams();
|
|
63
|
+
|
|
64
|
+
// Recursive function to handle nested objects and arrays
|
|
65
|
+
const appendQueryParam = (prefix: string, value: unknown) => {
|
|
66
|
+
if (value === undefined || value === null) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (Array.isArray(value)) {
|
|
71
|
+
// Handle arrays by appending multiple values with the same key
|
|
72
|
+
value.forEach((item) => {
|
|
73
|
+
queryParams.append(prefix, String(item));
|
|
74
|
+
});
|
|
75
|
+
} else if (typeof value === 'object') {
|
|
76
|
+
// For objects, recursively flatten into dot notation
|
|
77
|
+
Object.entries(value as Record<string, unknown>).forEach(
|
|
78
|
+
([subKey, subValue]) => {
|
|
79
|
+
appendQueryParam(`${prefix}.${subKey}`, subValue);
|
|
80
|
+
},
|
|
81
|
+
);
|
|
82
|
+
} else {
|
|
83
|
+
queryParams.append(prefix, String(value));
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Process all query parameters
|
|
88
|
+
Object.entries(config.query as Record<string, unknown>).forEach(
|
|
89
|
+
([key, value]) => {
|
|
90
|
+
appendQueryParam(key, value);
|
|
91
|
+
},
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
const queryString = queryParams.toString();
|
|
95
|
+
if (queryString) {
|
|
96
|
+
url += `?${queryString}`;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Build request configuration
|
|
101
|
+
let requestConfig: RequestInit = {
|
|
102
|
+
method: method.toUpperCase(),
|
|
103
|
+
headers: {
|
|
104
|
+
...this.defaultHeaders,
|
|
105
|
+
...((config && 'headers' in config && config.headers) || {}),
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// Add body if present
|
|
110
|
+
if (config && 'body' in config && config.body) {
|
|
111
|
+
requestConfig.body = JSON.stringify(config.body);
|
|
112
|
+
requestConfig.headers = {
|
|
113
|
+
...requestConfig.headers,
|
|
114
|
+
'Content-Type': 'application/json',
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Apply request interceptor
|
|
119
|
+
if (this.options.onRequest) {
|
|
120
|
+
requestConfig = await this.options.onRequest(requestConfig);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
// Make the request
|
|
125
|
+
let response = await this.fetchFn(`${this.baseURL}${url}`, requestConfig);
|
|
126
|
+
|
|
127
|
+
// Apply response interceptor
|
|
128
|
+
if (this.options.onResponse) {
|
|
129
|
+
response = await this.options.onResponse(response);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Handle errors
|
|
133
|
+
if (!response.ok) {
|
|
134
|
+
throw response;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Handle empty responses (204 No Content, etc.)
|
|
138
|
+
if (
|
|
139
|
+
response.status === 204 ||
|
|
140
|
+
response.headers.get('content-length') === '0'
|
|
141
|
+
) {
|
|
142
|
+
return undefined as ExtractEndpointResponse<Paths, T>;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Parse JSON response
|
|
146
|
+
const data = await response.json();
|
|
147
|
+
return data as ExtractEndpointResponse<Paths, T>;
|
|
148
|
+
} catch (error) {
|
|
149
|
+
// Apply error handler
|
|
150
|
+
if (this.options.onError) {
|
|
151
|
+
// @ts-expect-error
|
|
152
|
+
await this.options.onError(error);
|
|
153
|
+
}
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private parseEndpoint<T extends EndpointString>(
|
|
159
|
+
endpoint: T,
|
|
160
|
+
): ParseEndpoint<T> {
|
|
161
|
+
const [method, ...routeParts] = endpoint.split(' ');
|
|
162
|
+
const route = routeParts.join(' ');
|
|
163
|
+
return { method: method?.toLowerCase() ?? '', route } as ParseEndpoint<T>;
|
|
164
|
+
}
|
|
165
165
|
}
|
|
166
166
|
|
|
167
167
|
export function createTypedFetcher<Paths>(options?: FetcherOptions) {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
168
|
+
const fetcher = new TypedFetcher<Paths>(options);
|
|
169
|
+
return <T extends TypedEndpoint<Paths>>(
|
|
170
|
+
endpoint: T,
|
|
171
|
+
config?: FilteredRequestConfig<Paths, T>,
|
|
172
|
+
) => fetcher.request(endpoint, config);
|
|
173
173
|
}
|
|
174
174
|
|
|
175
175
|
export type FetchFn = typeof fetch;
|
package/src/infer.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
Endpoint,
|
|
3
|
+
EndpointSchemas,
|
|
4
4
|
} from '@geekmidas/constructs/endpoints';
|
|
5
5
|
import type { HttpMethod } from '@geekmidas/constructs/types';
|
|
6
6
|
import type { InferStandardSchema } from '@geekmidas/schema';
|
|
@@ -11,9 +11,9 @@ import type { StandardSchemaV1 } from '@standard-schema/spec';
|
|
|
11
11
|
* @example '/users/{id}/posts/{postId}' -> { id: string, postId: string }
|
|
12
12
|
*/
|
|
13
13
|
type InferPathParams<TRoute extends string> =
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
TRoute extends `${string}{${infer Param}}${infer Rest}`
|
|
15
|
+
? { [K in Param]: string } & InferPathParams<Rest>
|
|
16
|
+
: {};
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Converts an HTTP method to lowercase for TypedFetcher compatibility
|
|
@@ -24,50 +24,50 @@ type LowercaseMethod<T extends HttpMethod> = Lowercase<T>;
|
|
|
24
24
|
* Infers route-level parameters (path params)
|
|
25
25
|
*/
|
|
26
26
|
type InferRouteParameters<TRoute extends string> =
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
27
|
+
InferPathParams<TRoute> extends Record<string, never>
|
|
28
|
+
? {}
|
|
29
|
+
: {
|
|
30
|
+
parameters: {
|
|
31
|
+
path: InferPathParams<TRoute>;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
36
|
* Infers operation-level parameters (query params)
|
|
37
37
|
*/
|
|
38
38
|
type InferOperationParameters<TInput extends EndpointSchemas> = TInput extends {
|
|
39
|
-
|
|
39
|
+
query: infer Q;
|
|
40
40
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
? {
|
|
42
|
+
parameters: {
|
|
43
|
+
query: InferStandardSchema<Q>;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
: {};
|
|
47
47
|
|
|
48
48
|
/**
|
|
49
49
|
* Infers the operation object compatible with TypedFetcher
|
|
50
50
|
*/
|
|
51
51
|
type InferOperation<
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
TInput extends EndpointSchemas,
|
|
53
|
+
TOutput extends StandardSchemaV1 | undefined,
|
|
54
54
|
> = InferOperationParameters<TInput> & {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
55
|
+
requestBody?: TInput extends { body: infer B }
|
|
56
|
+
? {
|
|
57
|
+
content: {
|
|
58
|
+
'application/json': InferStandardSchema<B>;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
: never;
|
|
62
|
+
responses: {
|
|
63
|
+
200: {
|
|
64
|
+
content: TOutput extends StandardSchemaV1
|
|
65
|
+
? {
|
|
66
|
+
'application/json': InferStandardSchema<TOutput>;
|
|
67
|
+
}
|
|
68
|
+
: never;
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
71
|
};
|
|
72
72
|
|
|
73
73
|
/**
|
|
@@ -94,23 +94,24 @@ type InferOperation<
|
|
|
94
94
|
* const user = await client('GET /users/{id}', { params: { id: '123' } });
|
|
95
95
|
* ```
|
|
96
96
|
*/
|
|
97
|
-
export type InferOpenApiFromEndpoint<T> =
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
97
|
+
export type InferOpenApiFromEndpoint<T> =
|
|
98
|
+
T extends Endpoint<
|
|
99
|
+
infer TRoute,
|
|
100
|
+
infer TMethod,
|
|
101
|
+
infer TInput,
|
|
102
|
+
infer TOutput,
|
|
103
|
+
any,
|
|
104
|
+
any,
|
|
105
|
+
any
|
|
106
|
+
>
|
|
107
|
+
? {
|
|
108
|
+
paths: {
|
|
109
|
+
[K in TRoute]: InferRouteParameters<TRoute> & {
|
|
110
|
+
[M in LowercaseMethod<TMethod>]: InferOperation<TInput, TOutput>;
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
: never;
|
|
114
115
|
|
|
115
116
|
/**
|
|
116
117
|
* Infers TypedFetcher-compatible paths structure from multiple endpoints
|
|
@@ -150,12 +151,12 @@ export type InferOpenApiFromEndpoint<T> = T extends Endpoint<
|
|
|
150
151
|
* ```
|
|
151
152
|
*/
|
|
152
153
|
export type InferOpenApi<TEndpoints extends readonly any[]> =
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
154
|
+
TEndpoints extends readonly [infer First, ...infer Rest]
|
|
155
|
+
? InferOpenApiFromEndpoint<First> extends { paths: infer P1 }
|
|
156
|
+
? Rest extends []
|
|
157
|
+
? { paths: P1 }
|
|
158
|
+
: InferOpenApi<Rest> extends { paths: infer P2 }
|
|
159
|
+
? { paths: P1 & P2 }
|
|
160
|
+
: { paths: P1 }
|
|
161
|
+
: InferOpenApi<Rest>
|
|
162
|
+
: { paths: {} };
|