@agentuity/react 1.0.48 → 2.0.0-beta.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/AGENTS.md +26 -45
- package/README.md +28 -182
- package/dist/client-entrypoint.d.ts +8 -12
- package/dist/client-entrypoint.d.ts.map +1 -1
- package/dist/client-entrypoint.js +8 -15
- package/dist/client-entrypoint.js.map +1 -1
- package/dist/context.d.ts +1 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +3 -12
- package/dist/context.js.map +1 -1
- package/dist/index.d.ts +1 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -5
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +6 -14
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +5 -10
- package/dist/server.js.map +1 -1
- package/dist/webrtc.d.ts.map +1 -1
- package/dist/webrtc.js +2 -2
- package/dist/webrtc.js.map +1 -1
- package/package.json +5 -5
- package/src/client-entrypoint.tsx +8 -22
- package/src/context.tsx +3 -14
- package/src/index.ts +1 -52
- package/src/server.ts +5 -43
- package/src/webrtc.tsx +2 -1
- package/dist/api.d.ts +0 -302
- package/dist/api.d.ts.map +0 -1
- package/dist/api.js +0 -487
- package/dist/api.js.map +0 -1
- package/dist/client.d.ts +0 -75
- package/dist/client.d.ts.map +0 -1
- package/dist/client.js +0 -102
- package/dist/client.js.map +0 -1
- package/dist/eventstream.d.ts +0 -79
- package/dist/eventstream.d.ts.map +0 -1
- package/dist/eventstream.js +0 -122
- package/dist/eventstream.js.map +0 -1
- package/dist/types.d.ts +0 -18
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -18
- package/dist/types.js.map +0 -1
- package/dist/websocket.d.ts +0 -88
- package/dist/websocket.d.ts.map +0 -1
- package/dist/websocket.js +0 -151
- package/dist/websocket.js.map +0 -1
- package/src/api.ts +0 -954
- package/src/client.ts +0 -136
- package/src/eventstream.ts +0 -188
- package/src/types.ts +0 -23
- package/src/websocket.ts +0 -241
package/src/api.ts
DELETED
|
@@ -1,954 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
import { useCallback, useContext, useEffect, useRef, useState } from 'react';
|
|
3
|
-
import type { InferInput, InferOutput } from '@agentuity/core';
|
|
4
|
-
import { deserializeData, buildUrl, type RouteRegistry } from '@agentuity/frontend';
|
|
5
|
-
import { AgentuityContext } from './context';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Extract route keys from RouteRegistry (e.g., 'GET /users', 'POST /users')
|
|
9
|
-
*/
|
|
10
|
-
export type RouteKey = keyof RouteRegistry;
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Extract HTTP method from route key
|
|
14
|
-
*/
|
|
15
|
-
export type ExtractMethod<TRoute extends RouteKey> = TRoute extends `${infer Method} ${string}`
|
|
16
|
-
? Method
|
|
17
|
-
: never;
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Extract path from route key given a method
|
|
21
|
-
* E.g., ExtractPath<'GET /users', 'GET'> = '/users'
|
|
22
|
-
*/
|
|
23
|
-
export type ExtractPath<
|
|
24
|
-
TRoute extends RouteKey,
|
|
25
|
-
M extends string,
|
|
26
|
-
> = TRoute extends `${M} ${infer Path}` ? Path : never;
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Reconstruct the route key from method and path
|
|
30
|
-
* This ensures proper type inference when using {method, path} form
|
|
31
|
-
* E.g., RouteFromMethodPath<'GET', '/users'> = 'GET /users'
|
|
32
|
-
*/
|
|
33
|
-
export type RouteFromMethodPath<M extends string, P extends string> = Extract<
|
|
34
|
-
RouteKey,
|
|
35
|
-
`${M} ${P}`
|
|
36
|
-
>;
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Check if a route is a streaming route
|
|
40
|
-
*/
|
|
41
|
-
export type RouteIsStream<TRoute extends RouteKey> = TRoute extends keyof RouteRegistry
|
|
42
|
-
? RouteRegistry[TRoute] extends { stream: true }
|
|
43
|
-
? true
|
|
44
|
-
: false
|
|
45
|
-
: false;
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Extract input type for a specific route
|
|
49
|
-
*/
|
|
50
|
-
export type RouteInput<TRoute extends RouteKey> = TRoute extends keyof RouteRegistry
|
|
51
|
-
? RouteRegistry[TRoute] extends { inputSchema: any }
|
|
52
|
-
? InferInput<RouteRegistry[TRoute]['inputSchema']>
|
|
53
|
-
: never
|
|
54
|
-
: never;
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Extract output type for a specific route
|
|
58
|
-
* Returns void if no outputSchema is defined (e.g., 204 No Content)
|
|
59
|
-
* For streaming routes, returns T[] (accumulated chunks)
|
|
60
|
-
*/
|
|
61
|
-
export type RouteOutput<TRoute extends RouteKey> = TRoute extends keyof RouteRegistry
|
|
62
|
-
? RouteRegistry[TRoute] extends { outputSchema: infer TSchema; stream: true }
|
|
63
|
-
? TSchema extends undefined | never
|
|
64
|
-
? unknown[]
|
|
65
|
-
: InferOutput<TSchema>[]
|
|
66
|
-
: RouteRegistry[TRoute] extends { outputSchema: infer TSchema }
|
|
67
|
-
? TSchema extends undefined | never
|
|
68
|
-
? void
|
|
69
|
-
: InferOutput<TSchema>
|
|
70
|
-
: void
|
|
71
|
-
: void;
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Helper to extract single chunk type from RouteOutput
|
|
75
|
-
*/
|
|
76
|
-
type RouteChunkType<TRoute extends RouteKey> = TRoute extends keyof RouteRegistry
|
|
77
|
-
? RouteRegistry[TRoute] extends { outputSchema: infer TSchema }
|
|
78
|
-
? TSchema extends undefined | never
|
|
79
|
-
? unknown
|
|
80
|
-
: InferOutput<TSchema>
|
|
81
|
-
: unknown
|
|
82
|
-
: unknown;
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Extract path params type for a specific route.
|
|
86
|
-
* Returns the typed params object if the route has path parameters, otherwise never.
|
|
87
|
-
*/
|
|
88
|
-
export type RoutePathParams<TRoute extends RouteKey> = TRoute extends keyof RouteRegistry
|
|
89
|
-
? RouteRegistry[TRoute] extends { params: infer P }
|
|
90
|
-
? P
|
|
91
|
-
: never
|
|
92
|
-
: never;
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Base options shared by all UseAPI configurations (without params handling).
|
|
96
|
-
* This is used as a foundation for both route-form and method/path-form options.
|
|
97
|
-
*/
|
|
98
|
-
type UseAPIBaseOptions<TRoute extends RouteKey> = {
|
|
99
|
-
/** Additional query parameters */
|
|
100
|
-
query?: URLSearchParams | Record<string, string>;
|
|
101
|
-
/** Additional request headers */
|
|
102
|
-
headers?: Record<string, string>;
|
|
103
|
-
/** Whether to execute the request immediately on mount (default: true for GET, false for others) */
|
|
104
|
-
enabled?: boolean;
|
|
105
|
-
/** How long data stays fresh before refetching (ms, default: 0 - always stale) */
|
|
106
|
-
staleTime?: number;
|
|
107
|
-
/** Refetch interval in milliseconds (default: disabled) */
|
|
108
|
-
refetchInterval?: number;
|
|
109
|
-
/** Callback when request succeeds */
|
|
110
|
-
onSuccess?: (data: RouteOutput<TRoute>) => void;
|
|
111
|
-
/** Callback when request fails */
|
|
112
|
-
onError?: (error: Error) => void;
|
|
113
|
-
} & (RouteIsStream<TRoute> extends true
|
|
114
|
-
? {
|
|
115
|
-
/** Delimiter for splitting stream chunks (default: \n for JSON lines) */
|
|
116
|
-
delimiter?: string;
|
|
117
|
-
/** Optional transform callback for each chunk */
|
|
118
|
-
onChunk?: (
|
|
119
|
-
chunk: RouteChunkType<TRoute>
|
|
120
|
-
) => Promise<RouteChunkType<TRoute>> | RouteChunkType<TRoute>;
|
|
121
|
-
}
|
|
122
|
-
: // eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
123
|
-
{}) &
|
|
124
|
-
(ExtractMethod<TRoute> extends 'GET'
|
|
125
|
-
? {
|
|
126
|
-
/** GET requests cannot have input (use query params instead) */
|
|
127
|
-
input?: never;
|
|
128
|
-
}
|
|
129
|
-
: {
|
|
130
|
-
/** Input data for the request (required for POST/PUT/PATCH/DELETE) */
|
|
131
|
-
input?: RouteInput<TRoute>;
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Options for the route form with strict params handling.
|
|
136
|
-
* When TRoute is a single route key (not a union), this correctly requires
|
|
137
|
-
* params only for routes that have path parameters.
|
|
138
|
-
*/
|
|
139
|
-
type UseAPIRouteFormOptions<TRoute extends RouteKey> = UseAPIBaseOptions<TRoute> &
|
|
140
|
-
(RoutePathParams<TRoute> extends never
|
|
141
|
-
? // eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
142
|
-
{} // No params property - omit entirely for routes without path parameters
|
|
143
|
-
: {
|
|
144
|
-
/** Path parameters for routes with dynamic segments (e.g., { id: '123' } for /users/:id) */
|
|
145
|
-
params: RoutePathParams<TRoute>;
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Options for the method/path form with optional params.
|
|
150
|
-
*
|
|
151
|
-
* When using {method, path}, TRoute is inferred as a union of all matching routes,
|
|
152
|
-
* which causes RoutePathParams<TRoute> to be a union of all params types.
|
|
153
|
-
* This makes params incorrectly required even for routes without path parameters.
|
|
154
|
-
*
|
|
155
|
-
* To fix this (GitHub Issue #417), we make params optional in the method/path form.
|
|
156
|
-
* Users who want strict params enforcement should use the route form instead.
|
|
157
|
-
*/
|
|
158
|
-
type UseAPIMethodPathFormOptions<TRoute extends RouteKey> = UseAPIBaseOptions<TRoute> & {
|
|
159
|
-
/**
|
|
160
|
-
* Optional path parameters for dynamic segments when using method+path.
|
|
161
|
-
* These are optional because TRoute may be inferred as a union of routes.
|
|
162
|
-
* For strict params typing, use the route form: useAPI({ route: 'GET /users/:id', params: {...} })
|
|
163
|
-
*/
|
|
164
|
-
params?: RoutePathParams<TRoute> extends never ? never : RoutePathParams<TRoute>;
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Options with route property (e.g., { route: 'GET /users' })
|
|
169
|
-
* Uses strict params handling - params required only for routes with path parameters.
|
|
170
|
-
*/
|
|
171
|
-
type UseAPIOptionsWithRoute<TRoute extends RouteKey> = UseAPIRouteFormOptions<TRoute> & {
|
|
172
|
-
/** Route key (e.g., 'GET /users', 'POST /users') */
|
|
173
|
-
route: TRoute;
|
|
174
|
-
/** Method cannot be specified when using route */
|
|
175
|
-
method?: never;
|
|
176
|
-
/** Path cannot be specified when using route */
|
|
177
|
-
path?: never;
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Options with method and path properties (e.g., { method: 'GET', path: '/users' })
|
|
182
|
-
* Uses optional params handling to avoid false positives when TRoute is a union.
|
|
183
|
-
*
|
|
184
|
-
* Note: For full type safety with params, prefer using the route form:
|
|
185
|
-
* useAPI({ route: 'GET /users/:id', params: { id: '123' } })
|
|
186
|
-
*/
|
|
187
|
-
type UseAPIOptionsWithMethodPath<TRoute extends RouteKey> = UseAPIMethodPathFormOptions<TRoute> & {
|
|
188
|
-
/** HTTP method (e.g., 'GET', 'POST') */
|
|
189
|
-
method: ExtractMethod<TRoute>;
|
|
190
|
-
/** Request path (e.g., '/users') */
|
|
191
|
-
path: string;
|
|
192
|
-
/** Route cannot be specified when using method/path */
|
|
193
|
-
route?: never;
|
|
194
|
-
};
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Options for useAPI hook with smart input typing based on HTTP method
|
|
198
|
-
* Supports either route OR {method, path} but not both
|
|
199
|
-
*/
|
|
200
|
-
export type UseAPIOptions<TRoute extends RouteKey> =
|
|
201
|
-
| UseAPIOptionsWithRoute<TRoute>
|
|
202
|
-
| UseAPIOptionsWithMethodPath<TRoute>;
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Base return value from useAPI hook (without data property or execute)
|
|
206
|
-
*/
|
|
207
|
-
interface UseAPIResultBase {
|
|
208
|
-
/** Error if request failed */
|
|
209
|
-
error: Error | null;
|
|
210
|
-
/** Whether request is currently in progress */
|
|
211
|
-
isLoading: boolean;
|
|
212
|
-
/** Whether request has completed successfully at least once */
|
|
213
|
-
isSuccess: boolean;
|
|
214
|
-
/** Whether request has failed */
|
|
215
|
-
isError: boolean;
|
|
216
|
-
/** Whether data is currently being fetched (including refetches) */
|
|
217
|
-
isFetching: boolean;
|
|
218
|
-
/** Reset state to initial values */
|
|
219
|
-
reset: () => void;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* Return value for GET requests (auto-executing)
|
|
224
|
-
*/
|
|
225
|
-
type UseAPIResultQuery<TRoute extends RouteKey> = UseAPIResultBase &
|
|
226
|
-
(RouteOutput<TRoute> extends void
|
|
227
|
-
? {
|
|
228
|
-
/** No data property - route returns no content (204 No Content) */
|
|
229
|
-
data?: never;
|
|
230
|
-
/** Manually trigger a refetch */
|
|
231
|
-
refetch: () => Promise<void>;
|
|
232
|
-
}
|
|
233
|
-
: {
|
|
234
|
-
/** Response data (undefined until loaded) */
|
|
235
|
-
data: RouteOutput<TRoute> | undefined;
|
|
236
|
-
/** Manually trigger a refetch */
|
|
237
|
-
refetch: () => Promise<void>;
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Options that can be passed to invoke() at invocation time.
|
|
242
|
-
* Allows dynamic path parameter substitution when calling mutations.
|
|
243
|
-
*/
|
|
244
|
-
export type InvokeOptions<TRoute extends RouteKey> =
|
|
245
|
-
RoutePathParams<TRoute> extends never
|
|
246
|
-
? { params?: never }
|
|
247
|
-
: { params?: RoutePathParams<TRoute> };
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* Return value for POST/PUT/PATCH/DELETE requests (manual execution)
|
|
251
|
-
*/
|
|
252
|
-
type UseAPIResultMutation<TRoute extends RouteKey> = UseAPIResultBase &
|
|
253
|
-
(RouteOutput<TRoute> extends void
|
|
254
|
-
? {
|
|
255
|
-
/** No data property - route returns no content (204 No Content) */
|
|
256
|
-
data?: never;
|
|
257
|
-
/**
|
|
258
|
-
* Invoke the mutation with optional input and options.
|
|
259
|
-
* @param input - Request body (required for routes with inputSchema)
|
|
260
|
-
* @param options - Optional invocation options including dynamic path params
|
|
261
|
-
* @example
|
|
262
|
-
* // Route without input
|
|
263
|
-
* await invoke(undefined, { params: { itemId: '123' } });
|
|
264
|
-
*
|
|
265
|
-
* // Route with input
|
|
266
|
-
* await invoke({ name: 'New Name' }, { params: { itemId: '123' } });
|
|
267
|
-
*/
|
|
268
|
-
invoke: RouteInput<TRoute> extends never
|
|
269
|
-
? (input?: undefined, options?: InvokeOptions<TRoute>) => Promise<void>
|
|
270
|
-
: (input: RouteInput<TRoute>, options?: InvokeOptions<TRoute>) => Promise<void>;
|
|
271
|
-
}
|
|
272
|
-
: {
|
|
273
|
-
/** Response data (undefined until invoked) */
|
|
274
|
-
data: RouteOutput<TRoute> | undefined;
|
|
275
|
-
/**
|
|
276
|
-
* Invoke the mutation with optional input and options.
|
|
277
|
-
* @param input - Request body (required for routes with inputSchema)
|
|
278
|
-
* @param options - Optional invocation options including dynamic path params
|
|
279
|
-
* @example
|
|
280
|
-
* // Route without input
|
|
281
|
-
* const result = await invoke(undefined, { params: { itemId: '123' } });
|
|
282
|
-
*
|
|
283
|
-
* // Route with input
|
|
284
|
-
* const result = await invoke({ name: 'New Name' }, { params: { itemId: '123' } });
|
|
285
|
-
*/
|
|
286
|
-
invoke: RouteInput<TRoute> extends never
|
|
287
|
-
? (
|
|
288
|
-
input?: undefined,
|
|
289
|
-
options?: InvokeOptions<TRoute>
|
|
290
|
-
) => Promise<RouteOutput<TRoute>>
|
|
291
|
-
: (
|
|
292
|
-
input: RouteInput<TRoute>,
|
|
293
|
-
options?: InvokeOptions<TRoute>
|
|
294
|
-
) => Promise<RouteOutput<TRoute>>;
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
/**
|
|
298
|
-
* Return value from useAPI hook
|
|
299
|
-
* - GET requests: Auto-executes, returns refetch()
|
|
300
|
-
* - POST/PUT/PATCH/DELETE: Manual execution, returns execute(input)
|
|
301
|
-
*/
|
|
302
|
-
export type UseAPIResult<TRoute extends RouteKey> =
|
|
303
|
-
ExtractMethod<TRoute> extends 'GET' ? UseAPIResultQuery<TRoute> : UseAPIResultMutation<TRoute>;
|
|
304
|
-
|
|
305
|
-
/**
|
|
306
|
-
* Parse route key into method and path
|
|
307
|
-
*/
|
|
308
|
-
function parseRouteKey(routeKey: string): { method: string; path: string } {
|
|
309
|
-
const parts = routeKey.split(' ');
|
|
310
|
-
const method = parts[0];
|
|
311
|
-
const path = parts[1];
|
|
312
|
-
if (parts.length !== 2 || !method || !path) {
|
|
313
|
-
throw new Error(`Invalid route key format: "${routeKey}". Expected "METHOD /path"`);
|
|
314
|
-
}
|
|
315
|
-
return { method, path };
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
/**
|
|
319
|
-
* Convert query object to URLSearchParams
|
|
320
|
-
*/
|
|
321
|
-
function toSearchParams(
|
|
322
|
-
query?: URLSearchParams | Record<string, string>
|
|
323
|
-
): URLSearchParams | undefined {
|
|
324
|
-
if (!query) return undefined;
|
|
325
|
-
if (query instanceof URLSearchParams) return query;
|
|
326
|
-
return new URLSearchParams(query);
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
/**
|
|
330
|
-
* Substitute path parameters in a URL path template.
|
|
331
|
-
* E.g., '/users/:id' with { id: '123' } becomes '/users/123'
|
|
332
|
-
*/
|
|
333
|
-
function substitutePathParams(pathTemplate: string, pathParams?: Record<string, string>): string {
|
|
334
|
-
if (!pathParams) return pathTemplate;
|
|
335
|
-
|
|
336
|
-
let result = pathTemplate;
|
|
337
|
-
for (const [key, value] of Object.entries(pathParams)) {
|
|
338
|
-
result = result.replace(new RegExp(`:${key}\\??`, 'g'), encodeURIComponent(value));
|
|
339
|
-
result = result.replace(new RegExp(`\\*${key}`, 'g'), encodeURIComponent(value));
|
|
340
|
-
}
|
|
341
|
-
return result;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
/**
|
|
345
|
-
* Process ReadableStream with delimiter-based parsing
|
|
346
|
-
*/
|
|
347
|
-
async function processStream<T>(
|
|
348
|
-
stream: ReadableStream<Uint8Array>,
|
|
349
|
-
options: {
|
|
350
|
-
delimiter: string;
|
|
351
|
-
onChunk?: (chunk: T) => Promise<T> | T;
|
|
352
|
-
onData: (chunk: T) => void;
|
|
353
|
-
onError: (error: Error) => void;
|
|
354
|
-
mountedRef: React.MutableRefObject<boolean>;
|
|
355
|
-
}
|
|
356
|
-
): Promise<boolean> {
|
|
357
|
-
const { delimiter, onChunk, onData, onError, mountedRef } = options;
|
|
358
|
-
const reader = stream.getReader();
|
|
359
|
-
const decoder = new TextDecoder();
|
|
360
|
-
let buffer = '';
|
|
361
|
-
|
|
362
|
-
try {
|
|
363
|
-
while (true) {
|
|
364
|
-
const { done, value } = await reader.read();
|
|
365
|
-
|
|
366
|
-
if (!mountedRef.current) {
|
|
367
|
-
reader.cancel();
|
|
368
|
-
return false;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
if (done) break;
|
|
372
|
-
|
|
373
|
-
buffer += decoder.decode(value, { stream: true });
|
|
374
|
-
|
|
375
|
-
const parts = buffer.split(delimiter);
|
|
376
|
-
buffer = parts.pop() || '';
|
|
377
|
-
|
|
378
|
-
for (const part of parts) {
|
|
379
|
-
if (!part.trim()) continue;
|
|
380
|
-
|
|
381
|
-
try {
|
|
382
|
-
// Try JSON parsing first, fall back to plain string for text streams
|
|
383
|
-
let parsed: T;
|
|
384
|
-
try {
|
|
385
|
-
parsed = JSON.parse(part) as T;
|
|
386
|
-
} catch {
|
|
387
|
-
// Not valid JSON - treat as plain string
|
|
388
|
-
parsed = part as T;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
if (onChunk) {
|
|
392
|
-
parsed = await Promise.resolve(onChunk(parsed));
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
if (!mountedRef.current) {
|
|
396
|
-
reader.cancel();
|
|
397
|
-
return false;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
onData(parsed);
|
|
401
|
-
} catch (err) {
|
|
402
|
-
if (!mountedRef.current) {
|
|
403
|
-
reader.cancel();
|
|
404
|
-
return false;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
408
|
-
onError(error);
|
|
409
|
-
return false;
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
if (buffer.trim()) {
|
|
415
|
-
try {
|
|
416
|
-
// Try JSON parsing first, fall back to plain string for text streams
|
|
417
|
-
let parsed: T;
|
|
418
|
-
try {
|
|
419
|
-
parsed = JSON.parse(buffer) as T;
|
|
420
|
-
} catch {
|
|
421
|
-
// Not valid JSON - treat as plain string
|
|
422
|
-
parsed = buffer as T;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
if (onChunk) {
|
|
426
|
-
parsed = await Promise.resolve(onChunk(parsed));
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
if (mountedRef.current) {
|
|
430
|
-
onData(parsed);
|
|
431
|
-
}
|
|
432
|
-
} catch (err) {
|
|
433
|
-
if (!mountedRef.current) return false;
|
|
434
|
-
|
|
435
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
436
|
-
onError(error);
|
|
437
|
-
return false;
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
} catch (err) {
|
|
441
|
-
if (!mountedRef.current) return false;
|
|
442
|
-
|
|
443
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
444
|
-
onError(error);
|
|
445
|
-
return false;
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
return true;
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
/**
|
|
452
|
-
* Type-safe API client hook with TanStack Query-inspired features.
|
|
453
|
-
*
|
|
454
|
-
* Provides automatic type inference for route inputs and outputs based on
|
|
455
|
-
* the RouteRegistry generated from your routes.
|
|
456
|
-
*
|
|
457
|
-
* **Smart input typing:**
|
|
458
|
-
* - GET requests: `input` is `never` (use `query` params instead)
|
|
459
|
-
* - POST/PUT/PATCH/DELETE: `input` is properly typed from route schema
|
|
460
|
-
*
|
|
461
|
-
* @template TRoute - Route key from RouteRegistry (e.g., 'GET /users')
|
|
462
|
-
*
|
|
463
|
-
* @example Simple GET request with string
|
|
464
|
-
* ```typescript
|
|
465
|
-
* const { data, isLoading } = useAPI('GET /users');
|
|
466
|
-
* ```
|
|
467
|
-
*
|
|
468
|
-
* @example GET request with route property
|
|
469
|
-
* ```typescript
|
|
470
|
-
* const { data, isLoading } = useAPI({
|
|
471
|
-
* route: 'GET /users',
|
|
472
|
-
* query: { search: 'alice' }, // ✅ Use query params for GET
|
|
473
|
-
* // input: { ... }, // ❌ TypeScript error - GET cannot have input!
|
|
474
|
-
* staleTime: 5000,
|
|
475
|
-
* });
|
|
476
|
-
* ```
|
|
477
|
-
*
|
|
478
|
-
* @example GET request with method and path
|
|
479
|
-
* ```typescript
|
|
480
|
-
* const { data, isLoading } = useAPI({
|
|
481
|
-
* method: 'GET',
|
|
482
|
-
* path: '/users',
|
|
483
|
-
* query: { search: 'alice' },
|
|
484
|
-
* });
|
|
485
|
-
* ```
|
|
486
|
-
*
|
|
487
|
-
* @example POST request with manual invocation
|
|
488
|
-
* ```typescript
|
|
489
|
-
* const { invoke, data, isLoading } = useAPI('POST /users');
|
|
490
|
-
*
|
|
491
|
-
* // Invoke manually with input
|
|
492
|
-
* await invoke({ name: 'Alice', email: 'alice@example.com' }); // ✅ Fully typed!
|
|
493
|
-
* // data now contains the created user
|
|
494
|
-
* ```
|
|
495
|
-
*
|
|
496
|
-
* @example GET with query parameters
|
|
497
|
-
* ```typescript
|
|
498
|
-
* const { data } = useAPI({
|
|
499
|
-
* route: 'GET /search',
|
|
500
|
-
* query: { q: 'react', limit: '10' },
|
|
501
|
-
* });
|
|
502
|
-
* ```
|
|
503
|
-
*
|
|
504
|
-
* @example DELETE with no response body (204 No Content)
|
|
505
|
-
* ```typescript
|
|
506
|
-
* const { invoke, isSuccess } = useAPI('DELETE /users/:id');
|
|
507
|
-
*
|
|
508
|
-
* // Invoke when needed
|
|
509
|
-
* await invoke({ reason: 'Account closed' });
|
|
510
|
-
*
|
|
511
|
-
* // result.data doesn't exist! ❌ TypeScript error
|
|
512
|
-
* // Use isSuccess instead ✅
|
|
513
|
-
* if (isSuccess) {
|
|
514
|
-
* console.log('User deleted');
|
|
515
|
-
* }
|
|
516
|
-
* ```
|
|
517
|
-
*/
|
|
518
|
-
// Overload 1: String route form - direct route key
|
|
519
|
-
export function useAPI<TRoute extends RouteKey>(route: TRoute): UseAPIResult<TRoute>;
|
|
520
|
-
|
|
521
|
-
// Overload 2: Object with route property - uses route key directly
|
|
522
|
-
export function useAPI<TRoute extends RouteKey>(
|
|
523
|
-
options: UseAPIOptionsWithRoute<TRoute>
|
|
524
|
-
): UseAPIResult<TRoute>;
|
|
525
|
-
|
|
526
|
-
// Overload 3: Object with method and path - reconstructs route key from method+path
|
|
527
|
-
export function useAPI<M extends string, P extends string>(
|
|
528
|
-
options: { method: M; path: P } & Omit<
|
|
529
|
-
UseAPIMethodPathFormOptions<RouteFromMethodPath<M, P>>,
|
|
530
|
-
'method' | 'path'
|
|
531
|
-
>
|
|
532
|
-
): UseAPIResult<RouteFromMethodPath<M, P>>;
|
|
533
|
-
|
|
534
|
-
// Implementation signature
|
|
535
|
-
export function useAPI(routeOrOptions: unknown): any {
|
|
536
|
-
// Normalize to options object - use plain object type since we're in the implementation
|
|
537
|
-
const options: Record<string, any> =
|
|
538
|
-
typeof routeOrOptions === 'string'
|
|
539
|
-
? { route: routeOrOptions }
|
|
540
|
-
: (routeOrOptions as Record<string, any>);
|
|
541
|
-
const context = useContext(AgentuityContext);
|
|
542
|
-
const {
|
|
543
|
-
input,
|
|
544
|
-
query,
|
|
545
|
-
headers,
|
|
546
|
-
enabled,
|
|
547
|
-
staleTime = 0,
|
|
548
|
-
refetchInterval,
|
|
549
|
-
onSuccess,
|
|
550
|
-
onError,
|
|
551
|
-
} = options;
|
|
552
|
-
|
|
553
|
-
// Extract params safely
|
|
554
|
-
const pathParams = 'params' in options ? options.params : undefined;
|
|
555
|
-
|
|
556
|
-
const delimiter = 'delimiter' in options ? (options.delimiter ?? '\n') : '\n';
|
|
557
|
-
const onChunk = 'onChunk' in options ? options.onChunk : undefined;
|
|
558
|
-
|
|
559
|
-
if (!context) {
|
|
560
|
-
throw new Error('useAPI must be used within AgentuityProvider');
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
// Extract method and path from either route OR {method, path}
|
|
564
|
-
let method: string;
|
|
565
|
-
let basePath: string;
|
|
566
|
-
|
|
567
|
-
if ('route' in options && options.route) {
|
|
568
|
-
const parsed = parseRouteKey(options.route as string);
|
|
569
|
-
method = parsed.method;
|
|
570
|
-
basePath = parsed.path;
|
|
571
|
-
} else if ('method' in options && 'path' in options && options.method && options.path) {
|
|
572
|
-
method = options.method;
|
|
573
|
-
basePath = options.path;
|
|
574
|
-
} else {
|
|
575
|
-
throw new Error('useAPI requires either route OR {method, path}');
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
// Substitute path parameters
|
|
579
|
-
const path = substitutePathParams(basePath, pathParams as Record<string, string> | undefined);
|
|
580
|
-
|
|
581
|
-
const [data, setData] = useState<any>(undefined);
|
|
582
|
-
const [error, setError] = useState<Error | null>(null);
|
|
583
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
584
|
-
const [isFetching, setIsFetching] = useState(false);
|
|
585
|
-
const [isSuccess, setIsSuccess] = useState(false);
|
|
586
|
-
const [isError, setIsError] = useState(false);
|
|
587
|
-
const lastFetchTimeRef = useRef<number>(0);
|
|
588
|
-
|
|
589
|
-
// Track mounted state to prevent state updates after unmount
|
|
590
|
-
const mountedRef = useRef(true);
|
|
591
|
-
useEffect(() => {
|
|
592
|
-
mountedRef.current = true;
|
|
593
|
-
return () => {
|
|
594
|
-
mountedRef.current = false;
|
|
595
|
-
};
|
|
596
|
-
}, []);
|
|
597
|
-
|
|
598
|
-
// Use refs to store latest delimiter/onChunk values to avoid stale closures
|
|
599
|
-
// without causing infinite loops in useCallback dependencies
|
|
600
|
-
const delimiterRef = useRef(delimiter);
|
|
601
|
-
const onChunkRef = useRef(onChunk);
|
|
602
|
-
|
|
603
|
-
useEffect(() => {
|
|
604
|
-
delimiterRef.current = delimiter;
|
|
605
|
-
onChunkRef.current = onChunk;
|
|
606
|
-
});
|
|
607
|
-
|
|
608
|
-
const fetchData = useCallback(async () => {
|
|
609
|
-
if (!mountedRef.current) return;
|
|
610
|
-
|
|
611
|
-
const now = Date.now();
|
|
612
|
-
const lastFetchTime = lastFetchTimeRef.current;
|
|
613
|
-
|
|
614
|
-
// Check if data is still fresh based on last fetch time
|
|
615
|
-
const isFresh = staleTime > 0 && lastFetchTime !== 0 && now - lastFetchTime < staleTime;
|
|
616
|
-
if (isFresh) {
|
|
617
|
-
return;
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
setIsFetching(true);
|
|
621
|
-
|
|
622
|
-
// isLoading = only for first load (or after reset)
|
|
623
|
-
if (lastFetchTime === 0) {
|
|
624
|
-
setIsLoading(true);
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
setError(null);
|
|
628
|
-
setIsError(false);
|
|
629
|
-
|
|
630
|
-
try {
|
|
631
|
-
const url = buildUrl(context.baseUrl || '', path, undefined, toSearchParams(query));
|
|
632
|
-
const requestInit: RequestInit = {
|
|
633
|
-
method,
|
|
634
|
-
headers: {
|
|
635
|
-
'Content-Type': 'application/json',
|
|
636
|
-
...(context.authHeader && { Authorization: context.authHeader }),
|
|
637
|
-
...headers,
|
|
638
|
-
},
|
|
639
|
-
};
|
|
640
|
-
|
|
641
|
-
// Add body for non-GET requests
|
|
642
|
-
if (method !== 'GET' && input !== undefined) {
|
|
643
|
-
requestInit.body = JSON.stringify(input);
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
const response = await fetch(url, requestInit);
|
|
647
|
-
|
|
648
|
-
if (!response.ok) {
|
|
649
|
-
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
let responseData: any;
|
|
653
|
-
|
|
654
|
-
// Handle 204 No Content - no response body expected
|
|
655
|
-
if (response.status === 204) {
|
|
656
|
-
responseData = undefined;
|
|
657
|
-
} else {
|
|
658
|
-
const contentType = response.headers.get('Content-Type') || '';
|
|
659
|
-
|
|
660
|
-
if (
|
|
661
|
-
contentType.includes('text/event-stream') ||
|
|
662
|
-
contentType.includes('application/octet-stream')
|
|
663
|
-
) {
|
|
664
|
-
if (!response.body) {
|
|
665
|
-
throw new Error('Response body is null for streaming response');
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
setData([] as any);
|
|
669
|
-
|
|
670
|
-
// Track accumulated chunks locally to avoid stale closure
|
|
671
|
-
const accumulatedChunks: any[] = [];
|
|
672
|
-
|
|
673
|
-
const success = await processStream<any>(response.body, {
|
|
674
|
-
delimiter: delimiterRef.current,
|
|
675
|
-
onChunk: onChunkRef.current as any,
|
|
676
|
-
onData: (chunk) => {
|
|
677
|
-
if (!mountedRef.current) return;
|
|
678
|
-
|
|
679
|
-
accumulatedChunks.push(chunk);
|
|
680
|
-
setData([...accumulatedChunks] as any);
|
|
681
|
-
},
|
|
682
|
-
onError: (err) => {
|
|
683
|
-
if (!mountedRef.current) return;
|
|
684
|
-
setError(err);
|
|
685
|
-
setIsError(true);
|
|
686
|
-
onError?.(err);
|
|
687
|
-
},
|
|
688
|
-
mountedRef,
|
|
689
|
-
});
|
|
690
|
-
|
|
691
|
-
if (!mountedRef.current) return;
|
|
692
|
-
|
|
693
|
-
if (success) {
|
|
694
|
-
setIsSuccess(true);
|
|
695
|
-
lastFetchTimeRef.current = Date.now();
|
|
696
|
-
|
|
697
|
-
const finalData = accumulatedChunks as any;
|
|
698
|
-
onSuccess?.(finalData);
|
|
699
|
-
}
|
|
700
|
-
return;
|
|
701
|
-
} else if (contentType.includes('application/json')) {
|
|
702
|
-
responseData = await response.json();
|
|
703
|
-
} else {
|
|
704
|
-
const text = await response.text();
|
|
705
|
-
responseData = deserializeData<any>(text);
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
if (!mountedRef.current) return;
|
|
710
|
-
|
|
711
|
-
// Use JSON memoization to prevent re-renders when data hasn't changed
|
|
712
|
-
setData((prev: any) => {
|
|
713
|
-
const newData = responseData as any;
|
|
714
|
-
if (prev !== undefined && JSON.stringify(prev) === JSON.stringify(newData)) {
|
|
715
|
-
return prev;
|
|
716
|
-
}
|
|
717
|
-
return newData;
|
|
718
|
-
});
|
|
719
|
-
setIsSuccess(true);
|
|
720
|
-
lastFetchTimeRef.current = Date.now();
|
|
721
|
-
|
|
722
|
-
onSuccess?.(responseData as any);
|
|
723
|
-
} catch (err) {
|
|
724
|
-
if (!mountedRef.current) return;
|
|
725
|
-
|
|
726
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
727
|
-
setError(error);
|
|
728
|
-
setIsError(true);
|
|
729
|
-
onError?.(error);
|
|
730
|
-
} finally {
|
|
731
|
-
if (mountedRef.current) {
|
|
732
|
-
setIsLoading(false);
|
|
733
|
-
setIsFetching(false);
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
}, [
|
|
737
|
-
context.baseUrl,
|
|
738
|
-
context.authHeader,
|
|
739
|
-
path,
|
|
740
|
-
method,
|
|
741
|
-
input,
|
|
742
|
-
query,
|
|
743
|
-
headers,
|
|
744
|
-
staleTime,
|
|
745
|
-
onSuccess,
|
|
746
|
-
onError,
|
|
747
|
-
]);
|
|
748
|
-
|
|
749
|
-
const reset = useCallback(() => {
|
|
750
|
-
setData(undefined);
|
|
751
|
-
setError(null);
|
|
752
|
-
setIsLoading(false);
|
|
753
|
-
setIsFetching(false);
|
|
754
|
-
setIsSuccess(false);
|
|
755
|
-
setIsError(false);
|
|
756
|
-
lastFetchTimeRef.current = 0;
|
|
757
|
-
}, []);
|
|
758
|
-
|
|
759
|
-
// For GET requests: auto-fetch and provide refetch
|
|
760
|
-
if (method === 'GET') {
|
|
761
|
-
// biome-ignore lint/correctness/useHookAtTopLevel: method is constant per hook usage
|
|
762
|
-
const refetch = useCallback(async () => {
|
|
763
|
-
await fetchData();
|
|
764
|
-
}, [fetchData]);
|
|
765
|
-
|
|
766
|
-
// Auto-fetch on mount if enabled (default: true for GET)
|
|
767
|
-
const shouldAutoFetch = enabled ?? true;
|
|
768
|
-
// biome-ignore lint/correctness/useHookAtTopLevel: method is constant per hook usage
|
|
769
|
-
useEffect(() => {
|
|
770
|
-
if (shouldAutoFetch) {
|
|
771
|
-
fetchData();
|
|
772
|
-
}
|
|
773
|
-
}, [shouldAutoFetch, fetchData]);
|
|
774
|
-
|
|
775
|
-
// Refetch interval
|
|
776
|
-
// biome-ignore lint/correctness/useHookAtTopLevel: method is constant per hook usage
|
|
777
|
-
useEffect(() => {
|
|
778
|
-
if (!refetchInterval || refetchInterval <= 0) return;
|
|
779
|
-
|
|
780
|
-
const interval = setInterval(() => {
|
|
781
|
-
fetchData();
|
|
782
|
-
}, refetchInterval);
|
|
783
|
-
|
|
784
|
-
return () => clearInterval(interval);
|
|
785
|
-
}, [refetchInterval, fetchData]);
|
|
786
|
-
|
|
787
|
-
return {
|
|
788
|
-
data,
|
|
789
|
-
error,
|
|
790
|
-
isLoading,
|
|
791
|
-
isSuccess,
|
|
792
|
-
isError,
|
|
793
|
-
isFetching,
|
|
794
|
-
refetch,
|
|
795
|
-
reset,
|
|
796
|
-
} as any;
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
// For POST/PUT/PATCH/DELETE: provide invoke method (manual invocation)
|
|
800
|
-
// biome-ignore lint/correctness/useHookAtTopLevel: only reached for non-GET methods which is constant per hook usage
|
|
801
|
-
const invoke = useCallback(
|
|
802
|
-
async (invokeInput?: any, invokeOptions?: { params?: Record<string, string> }) => {
|
|
803
|
-
// Use invokeInput parameter if provided
|
|
804
|
-
const effectiveInput = invokeInput !== undefined ? invokeInput : input;
|
|
805
|
-
|
|
806
|
-
// Compute path at invocation time if params provided, otherwise use hook-level path
|
|
807
|
-
const effectivePath = invokeOptions?.params
|
|
808
|
-
? substitutePathParams(basePath, invokeOptions.params)
|
|
809
|
-
: path;
|
|
810
|
-
|
|
811
|
-
setIsFetching(true);
|
|
812
|
-
setIsLoading(true);
|
|
813
|
-
setError(null);
|
|
814
|
-
setIsError(false);
|
|
815
|
-
|
|
816
|
-
try {
|
|
817
|
-
const url = buildUrl(
|
|
818
|
-
context.baseUrl || '',
|
|
819
|
-
effectivePath,
|
|
820
|
-
undefined,
|
|
821
|
-
toSearchParams(query)
|
|
822
|
-
);
|
|
823
|
-
const requestInit: RequestInit = {
|
|
824
|
-
method,
|
|
825
|
-
headers: {
|
|
826
|
-
'Content-Type': 'application/json',
|
|
827
|
-
...(context.authHeader && { Authorization: context.authHeader }),
|
|
828
|
-
...headers,
|
|
829
|
-
},
|
|
830
|
-
};
|
|
831
|
-
|
|
832
|
-
if (effectiveInput !== undefined) {
|
|
833
|
-
requestInit.body = JSON.stringify(effectiveInput);
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
const response = await fetch(url, requestInit);
|
|
837
|
-
|
|
838
|
-
if (!response.ok) {
|
|
839
|
-
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
let responseData: any;
|
|
843
|
-
|
|
844
|
-
if (response.status === 204) {
|
|
845
|
-
responseData = undefined;
|
|
846
|
-
} else {
|
|
847
|
-
const contentType = response.headers.get('Content-Type') || '';
|
|
848
|
-
|
|
849
|
-
if (
|
|
850
|
-
contentType.includes('text/event-stream') ||
|
|
851
|
-
contentType.includes('application/octet-stream')
|
|
852
|
-
) {
|
|
853
|
-
if (!response.body) {
|
|
854
|
-
throw new Error('Response body is null for streaming response');
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
setData([] as any);
|
|
858
|
-
|
|
859
|
-
// Track accumulated chunks locally to avoid stale closure
|
|
860
|
-
const accumulatedChunks: any[] = [];
|
|
861
|
-
let streamError: any = undefined;
|
|
862
|
-
|
|
863
|
-
const success = await processStream<any>(response.body, {
|
|
864
|
-
delimiter: delimiterRef.current,
|
|
865
|
-
onChunk: onChunkRef.current as any,
|
|
866
|
-
onData: (chunk) => {
|
|
867
|
-
if (!mountedRef.current) return;
|
|
868
|
-
|
|
869
|
-
accumulatedChunks.push(chunk);
|
|
870
|
-
setData([...accumulatedChunks] as any);
|
|
871
|
-
},
|
|
872
|
-
onError: (err) => {
|
|
873
|
-
if (!mountedRef.current) return;
|
|
874
|
-
streamError = err;
|
|
875
|
-
setError(err);
|
|
876
|
-
setIsError(true);
|
|
877
|
-
onError?.(err);
|
|
878
|
-
},
|
|
879
|
-
mountedRef,
|
|
880
|
-
});
|
|
881
|
-
|
|
882
|
-
if (!mountedRef.current) return accumulatedChunks as any;
|
|
883
|
-
|
|
884
|
-
if (!success && streamError) {
|
|
885
|
-
throw streamError;
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
if (success) {
|
|
889
|
-
setIsSuccess(true);
|
|
890
|
-
lastFetchTimeRef.current = Date.now();
|
|
891
|
-
|
|
892
|
-
const finalData = accumulatedChunks as any;
|
|
893
|
-
onSuccess?.(finalData);
|
|
894
|
-
return finalData;
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
return accumulatedChunks as any;
|
|
898
|
-
} else if (contentType.includes('application/json')) {
|
|
899
|
-
responseData = await response.json();
|
|
900
|
-
} else {
|
|
901
|
-
const text = await response.text();
|
|
902
|
-
responseData = deserializeData<any>(text);
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
if (!mountedRef.current) return responseData;
|
|
907
|
-
|
|
908
|
-
setData(responseData as any);
|
|
909
|
-
setIsSuccess(true);
|
|
910
|
-
lastFetchTimeRef.current = Date.now();
|
|
911
|
-
|
|
912
|
-
onSuccess?.(responseData as any);
|
|
913
|
-
|
|
914
|
-
return responseData as any;
|
|
915
|
-
} catch (err) {
|
|
916
|
-
if (!mountedRef.current) throw err;
|
|
917
|
-
|
|
918
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
919
|
-
setError(error);
|
|
920
|
-
setIsError(true);
|
|
921
|
-
onError?.(error);
|
|
922
|
-
throw error;
|
|
923
|
-
} finally {
|
|
924
|
-
if (mountedRef.current) {
|
|
925
|
-
setIsLoading(false);
|
|
926
|
-
setIsFetching(false);
|
|
927
|
-
}
|
|
928
|
-
}
|
|
929
|
-
},
|
|
930
|
-
[
|
|
931
|
-
context.baseUrl,
|
|
932
|
-
context.authHeader,
|
|
933
|
-
basePath,
|
|
934
|
-
path,
|
|
935
|
-
method,
|
|
936
|
-
query,
|
|
937
|
-
headers,
|
|
938
|
-
input,
|
|
939
|
-
onSuccess,
|
|
940
|
-
onError,
|
|
941
|
-
]
|
|
942
|
-
);
|
|
943
|
-
|
|
944
|
-
return {
|
|
945
|
-
data,
|
|
946
|
-
error,
|
|
947
|
-
isLoading,
|
|
948
|
-
isSuccess,
|
|
949
|
-
isError,
|
|
950
|
-
isFetching,
|
|
951
|
-
invoke,
|
|
952
|
-
reset,
|
|
953
|
-
} as any;
|
|
954
|
-
}
|