@spoosh/react 0.5.1 → 0.6.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/README.md +26 -5
- package/dist/index.d.mts +49 -9
- package/dist/index.d.ts +49 -9
- package/dist/index.js +163 -28
- package/dist/index.mjs +172 -27
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @spoosh/react
|
|
2
2
|
|
|
3
|
-
React hooks for Spoosh - `useRead`, `useWrite`, and `useInfiniteRead`.
|
|
3
|
+
React hooks for Spoosh - `useRead`, `useLazyRead`, `useWrite`, and `useInfiniteRead`.
|
|
4
4
|
|
|
5
5
|
**[Documentation](https://spoosh.dev/docs/integrations/react)** · **Requirements:** TypeScript >= 5.0, React >= 18.0
|
|
6
6
|
|
|
@@ -23,7 +23,8 @@ const spoosh = new Spoosh<ApiSchema, Error>("/api").use([
|
|
|
23
23
|
cachePlugin({ staleTime: 5000 }),
|
|
24
24
|
]);
|
|
25
25
|
|
|
26
|
-
export const { useRead, useWrite, useInfiniteRead } =
|
|
26
|
+
export const { useRead, useLazyRead, useWrite, useInfiniteRead } =
|
|
27
|
+
createReactSpoosh(spoosh);
|
|
27
28
|
```
|
|
28
29
|
|
|
29
30
|
### useRead
|
|
@@ -32,7 +33,7 @@ Fetch data with automatic caching and refetching.
|
|
|
32
33
|
|
|
33
34
|
```typescript
|
|
34
35
|
function UserList() {
|
|
35
|
-
const { data, loading, error,
|
|
36
|
+
const { data, loading, error, trigger } = useRead(
|
|
36
37
|
(api) => api("users").GET()
|
|
37
38
|
);
|
|
38
39
|
|
|
@@ -62,6 +63,27 @@ const { data: user } = useRead(
|
|
|
62
63
|
);
|
|
63
64
|
```
|
|
64
65
|
|
|
66
|
+
### useLazyRead
|
|
67
|
+
|
|
68
|
+
Lazy data fetching for print/download/export scenarios. Does not auto-fetch on mount.
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
function PrintOrder() {
|
|
72
|
+
const { trigger, loading } = useLazyRead((api) => api("orders/:id").GET);
|
|
73
|
+
|
|
74
|
+
const handlePrint = async (orderId: string) => {
|
|
75
|
+
const { data } = await trigger({ params: { id: orderId } });
|
|
76
|
+
if (data) printReceipt(data);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<button onClick={() => handlePrint("123")} disabled={loading}>
|
|
81
|
+
{loading ? "Loading..." : "Print"}
|
|
82
|
+
</button>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
65
87
|
### useWrite
|
|
66
88
|
|
|
67
89
|
Trigger mutations with loading and error states.
|
|
@@ -177,7 +199,7 @@ function PostList() {
|
|
|
177
199
|
| `error` | `TError \| undefined` | Error if request failed |
|
|
178
200
|
| `loading` | `boolean` | True during initial load |
|
|
179
201
|
| `fetching` | `boolean` | True during any fetch |
|
|
180
|
-
| `
|
|
202
|
+
| `trigger` | `() => Promise` | Manually trigger fetch |
|
|
181
203
|
| `abort` | `() => void` | Abort current request |
|
|
182
204
|
|
|
183
205
|
### useWrite(writeFn)
|
|
@@ -190,7 +212,6 @@ function PostList() {
|
|
|
190
212
|
| `data` | `TData \| undefined` | Response data |
|
|
191
213
|
| `error` | `TError \| undefined` | Error if request failed |
|
|
192
214
|
| `loading` | `boolean` | True while mutation is in progress |
|
|
193
|
-
| `reset` | `() => void` | Reset state |
|
|
194
215
|
| `abort` | `() => void` | Abort current request |
|
|
195
216
|
|
|
196
217
|
### useInfiniteRead(readFn, options)
|
package/dist/index.d.mts
CHANGED
|
@@ -41,8 +41,8 @@ type BaseReadResult<TData, TError, TMeta = Record<string, unknown>> = {
|
|
|
41
41
|
meta: TMeta;
|
|
42
42
|
/** Abort the current fetch operation */
|
|
43
43
|
abort: () => void;
|
|
44
|
-
/** Manually trigger a
|
|
45
|
-
|
|
44
|
+
/** Manually trigger a fetch */
|
|
45
|
+
trigger: () => Promise<SpooshResponse<TData, TError>>;
|
|
46
46
|
};
|
|
47
47
|
/**
|
|
48
48
|
* Result returned by `useWrite` hook.
|
|
@@ -63,11 +63,28 @@ type BaseWriteResult<TData, TError, TOptions, TMeta = Record<string, unknown>> =
|
|
|
63
63
|
error: TError | undefined;
|
|
64
64
|
/** Plugin-provided metadata */
|
|
65
65
|
meta: TMeta;
|
|
66
|
-
/** Reset the state to initial values */
|
|
67
|
-
reset: () => void;
|
|
68
66
|
/** Abort the current mutation */
|
|
69
67
|
abort: () => void;
|
|
70
68
|
};
|
|
69
|
+
/**
|
|
70
|
+
* Result returned by `useLazyRead` hook.
|
|
71
|
+
*
|
|
72
|
+
* @template TData - The response data type
|
|
73
|
+
* @template TError - The error type
|
|
74
|
+
* @template TOptions - The trigger options type
|
|
75
|
+
*/
|
|
76
|
+
type BaseLazyReadResult<TData, TError, TOptions> = {
|
|
77
|
+
/** Execute the fetch with optional options */
|
|
78
|
+
trigger: (options?: TOptions) => Promise<SpooshResponse<TData, TError>>;
|
|
79
|
+
/** True while the fetch is in progress */
|
|
80
|
+
loading: boolean;
|
|
81
|
+
/** Response data from the API */
|
|
82
|
+
data: TData | undefined;
|
|
83
|
+
/** Error from the last failed request */
|
|
84
|
+
error: TError | undefined;
|
|
85
|
+
/** Abort the current fetch */
|
|
86
|
+
abort: () => void;
|
|
87
|
+
};
|
|
71
88
|
type OptionalQueryField<TQuery> = [TQuery] extends [never] ? object : undefined extends TQuery ? {
|
|
72
89
|
query?: Exclude<TQuery, undefined>;
|
|
73
90
|
} : {
|
|
@@ -106,6 +123,7 @@ type ExtractMethodError<T> = T extends (...args: never[]) => infer R ? ErrorResp
|
|
|
106
123
|
error: infer E;
|
|
107
124
|
} ? E : unknown : unknown;
|
|
108
125
|
type ExtractMethodOptions<T> = T extends (...args: infer A) => unknown ? A[0] : never;
|
|
126
|
+
type ExtractCoreMethodOptions<T> = T extends (...args: infer A) => unknown ? A[0] extends object ? Pick<A[0], Extract<keyof A[0], "query" | "params" | "body">> : object : object;
|
|
109
127
|
type ExtractMethodQuery<T> = ExtractMethodOptions<T> extends {
|
|
110
128
|
query: infer Q;
|
|
111
129
|
} ? Q : never;
|
|
@@ -237,8 +255,8 @@ type BaseInfiniteReadResult<TData, TError, TItem, TPluginResult = Record<string,
|
|
|
237
255
|
fetchNext: () => Promise<void>;
|
|
238
256
|
/** Fetch the previous page */
|
|
239
257
|
fetchPrev: () => Promise<void>;
|
|
240
|
-
/**
|
|
241
|
-
|
|
258
|
+
/** Trigger refetch of all pages from the beginning */
|
|
259
|
+
trigger: () => Promise<void>;
|
|
242
260
|
/** Abort the current fetch operation */
|
|
243
261
|
abort: () => void;
|
|
244
262
|
/** Error from the last failed request */
|
|
@@ -263,6 +281,9 @@ type UseReadFn<TDefaultError, TSchema, TPlugins extends PluginArray> = {
|
|
|
263
281
|
type UseWriteFn<TDefaultError, TSchema, TPlugins extends PluginArray> = {
|
|
264
282
|
<TMethod extends (...args: never) => Promise<SpooshResponse<unknown, unknown>>>(writeFn: (api: WriteApiClient<TSchema, TDefaultError>) => TMethod): BaseWriteResult<ExtractMethodData<TMethod>, InferError<ExtractMethodError<TMethod>, TDefaultError>, Parameters<TMethod>[0] & ResolvedWriteOptions<TSchema, TPlugins, TMethod, TDefaultError>, MergePluginResults<TPlugins>["write"]> & WriteResponseInputFields<ExtractMethodQuery<TMethod>, ExtractMethodBody<TMethod>, ExtractResponseParamNames<TMethod>>;
|
|
265
283
|
};
|
|
284
|
+
type UseLazyReadFn<TDefaultError, TSchema> = {
|
|
285
|
+
<TMethod extends (...args: never) => Promise<SpooshResponse<unknown, unknown>>>(readFn: (api: ReadApiClient<TSchema, TDefaultError>) => TMethod): BaseLazyReadResult<ExtractMethodData<TMethod>, InferError<ExtractMethodError<TMethod>, TDefaultError>, ExtractCoreMethodOptions<TMethod>> & WriteResponseInputFields<ExtractResponseQuery<TMethod>, ExtractResponseBody<TMethod>, ExtractResponseParamNames<TMethod>>;
|
|
286
|
+
};
|
|
266
287
|
type InfiniteReadResolverContext<TSchema, TData, TError, TRequest> = ResolverContext<TSchema, TData, TError, TRequest extends {
|
|
267
288
|
query: infer Q;
|
|
268
289
|
} ? Q : never, TRequest extends {
|
|
@@ -285,7 +306,7 @@ type SpooshReactHooks<TDefaultError, TSchema, TPlugins extends PluginArray> = {
|
|
|
285
306
|
*
|
|
286
307
|
* @param readFn - Function that selects the API endpoint to call (e.g., `(api) => api("posts").GET()`)
|
|
287
308
|
* @param readOptions - Optional configuration including `enabled`, `tags`, and plugin-specific options
|
|
288
|
-
* @returns Object containing `data`, `error`, `loading`, `fetching`, `
|
|
309
|
+
* @returns Object containing `data`, `error`, `loading`, `fetching`, `trigger`, and `abort`
|
|
289
310
|
*
|
|
290
311
|
* @example
|
|
291
312
|
* ```tsx
|
|
@@ -302,7 +323,7 @@ type SpooshReactHooks<TDefaultError, TSchema, TPlugins extends PluginArray> = {
|
|
|
302
323
|
* React hook for mutations (POST, PUT, PATCH, DELETE) with manual triggering.
|
|
303
324
|
*
|
|
304
325
|
* @param writeFn - Function that selects the API endpoint (e.g., `(api) => api("posts").POST`)
|
|
305
|
-
* @returns Object containing `trigger`, `data`, `error`, `loading`,
|
|
326
|
+
* @returns Object containing `trigger`, `data`, `error`, `loading`, and `abort`
|
|
306
327
|
*
|
|
307
328
|
* @example
|
|
308
329
|
* ```tsx
|
|
@@ -315,6 +336,23 @@ type SpooshReactHooks<TDefaultError, TSchema, TPlugins extends PluginArray> = {
|
|
|
315
336
|
* ```
|
|
316
337
|
*/
|
|
317
338
|
useWrite: UseWriteFn<TDefaultError, TSchema, TPlugins>;
|
|
339
|
+
/**
|
|
340
|
+
* React hook for lazy GET requests with manual triggering (does not auto-fetch on mount).
|
|
341
|
+
*
|
|
342
|
+
* @param readFn - Function that selects the API endpoint (e.g., `(api) => api("posts").GET`)
|
|
343
|
+
* @returns Object containing `trigger`, `data`, `error`, `loading`, and `abort`
|
|
344
|
+
*
|
|
345
|
+
* @example
|
|
346
|
+
* ```tsx
|
|
347
|
+
* const { trigger, loading, data } = useLazyRead((api) => api("posts/:id").GET);
|
|
348
|
+
*
|
|
349
|
+
* const handleClick = async (id) => {
|
|
350
|
+
* const { data, error } = await trigger({ params: { id } });
|
|
351
|
+
* if (data) console.log('Fetched:', data);
|
|
352
|
+
* };
|
|
353
|
+
* ```
|
|
354
|
+
*/
|
|
355
|
+
useLazyRead: UseLazyReadFn<TDefaultError, TSchema>;
|
|
318
356
|
/**
|
|
319
357
|
* React hook for infinite/paginated data fetching with automatic pagination control.
|
|
320
358
|
*
|
|
@@ -411,6 +449,8 @@ declare function createUseRead<TSchema, TDefaultError, TPlugins extends readonly
|
|
|
411
449
|
}> ? E : unknown, MergePluginResults<TPlugins>["read"]> & ResponseInputFields<ExtractResponseQuery<TReadFn>, ExtractResponseBody<TReadFn>, ExtractResponseParamNames<TReadFn>>;
|
|
412
450
|
};
|
|
413
451
|
|
|
452
|
+
declare function createUseLazyRead<TSchema, TDefaultError, TPlugins extends readonly SpooshPlugin<PluginTypeConfig>[]>(options: Omit<SpooshInstanceShape<unknown, TSchema, TDefaultError, TPlugins>, "_types">): <TMethod extends (...args: never[]) => Promise<SpooshResponse<unknown, unknown>>>(readFn: (api: ReadApiClient<TSchema, TDefaultError>) => TMethod) => BaseLazyReadResult<ExtractMethodData<TMethod>, [ExtractMethodError<TMethod>] extends [unknown] ? TDefaultError : ExtractMethodError<TMethod>, ExtractCoreMethodOptions<TMethod>> & WriteResponseInputFields<ExtractResponseQuery<TMethod>, ExtractResponseBody<TMethod>, ExtractResponseParamNames<TMethod>>;
|
|
453
|
+
|
|
414
454
|
declare function createUseWrite<TSchema, TDefaultError, TPlugins extends readonly SpooshPlugin<PluginTypeConfig>[]>(options: Omit<SpooshInstanceShape<unknown, TSchema, TDefaultError, TPlugins>, "_types">): <TMethod extends (...args: never[]) => Promise<SpooshResponse<unknown, unknown>>>(writeFn: (api: WriteApiClient<TSchema, TDefaultError>) => TMethod) => BaseWriteResult<ExtractMethodData<TMethod>, [ExtractMethodError<TMethod>] extends [unknown] ? TDefaultError : ExtractMethodError<TMethod>, ExtractMethodOptions<TMethod> & ResolveTypes<((TPlugins[number] extends infer T ? T extends TPlugins[number] ? T extends SpooshPlugin<infer Types extends PluginTypeConfig> ? Types extends {
|
|
415
455
|
writeOptions: infer W;
|
|
416
456
|
} ? W : object : object : never : never) extends infer T_1 ? T_1 extends (TPlugins[number] extends infer T_2 ? T_2 extends TPlugins[number] ? T_2 extends SpooshPlugin<infer Types extends PluginTypeConfig> ? Types extends {
|
|
@@ -427,4 +467,4 @@ declare function createUseInfiniteRead<TSchema, TDefaultError, TPlugins extends
|
|
|
427
467
|
readResult: infer R;
|
|
428
468
|
} ? R : object : object : never : never) ? T_1 extends unknown ? (x: T_1) => void : never : never : never) extends (x: infer I) => void ? I : never>;
|
|
429
469
|
|
|
430
|
-
export { type BaseInfiniteReadOptions, type BaseInfiniteReadResult, type BaseReadOptions, type BaseReadResult, type BaseWriteResult, type PluginHooksConfig, type SpooshReactHooks, type UseInfiniteReadResult, type UseReadResult, type UseWriteResult, createReactSpoosh, createUseInfiniteRead, createUseRead, createUseWrite };
|
|
470
|
+
export { type BaseInfiniteReadOptions, type BaseInfiniteReadResult, type BaseLazyReadResult, type BaseReadOptions, type BaseReadResult, type BaseWriteResult, type PluginHooksConfig, type SpooshReactHooks, type UseInfiniteReadResult, type UseReadResult, type UseWriteResult, createReactSpoosh, createUseInfiniteRead, createUseLazyRead, createUseRead, createUseWrite };
|
package/dist/index.d.ts
CHANGED
|
@@ -41,8 +41,8 @@ type BaseReadResult<TData, TError, TMeta = Record<string, unknown>> = {
|
|
|
41
41
|
meta: TMeta;
|
|
42
42
|
/** Abort the current fetch operation */
|
|
43
43
|
abort: () => void;
|
|
44
|
-
/** Manually trigger a
|
|
45
|
-
|
|
44
|
+
/** Manually trigger a fetch */
|
|
45
|
+
trigger: () => Promise<SpooshResponse<TData, TError>>;
|
|
46
46
|
};
|
|
47
47
|
/**
|
|
48
48
|
* Result returned by `useWrite` hook.
|
|
@@ -63,11 +63,28 @@ type BaseWriteResult<TData, TError, TOptions, TMeta = Record<string, unknown>> =
|
|
|
63
63
|
error: TError | undefined;
|
|
64
64
|
/** Plugin-provided metadata */
|
|
65
65
|
meta: TMeta;
|
|
66
|
-
/** Reset the state to initial values */
|
|
67
|
-
reset: () => void;
|
|
68
66
|
/** Abort the current mutation */
|
|
69
67
|
abort: () => void;
|
|
70
68
|
};
|
|
69
|
+
/**
|
|
70
|
+
* Result returned by `useLazyRead` hook.
|
|
71
|
+
*
|
|
72
|
+
* @template TData - The response data type
|
|
73
|
+
* @template TError - The error type
|
|
74
|
+
* @template TOptions - The trigger options type
|
|
75
|
+
*/
|
|
76
|
+
type BaseLazyReadResult<TData, TError, TOptions> = {
|
|
77
|
+
/** Execute the fetch with optional options */
|
|
78
|
+
trigger: (options?: TOptions) => Promise<SpooshResponse<TData, TError>>;
|
|
79
|
+
/** True while the fetch is in progress */
|
|
80
|
+
loading: boolean;
|
|
81
|
+
/** Response data from the API */
|
|
82
|
+
data: TData | undefined;
|
|
83
|
+
/** Error from the last failed request */
|
|
84
|
+
error: TError | undefined;
|
|
85
|
+
/** Abort the current fetch */
|
|
86
|
+
abort: () => void;
|
|
87
|
+
};
|
|
71
88
|
type OptionalQueryField<TQuery> = [TQuery] extends [never] ? object : undefined extends TQuery ? {
|
|
72
89
|
query?: Exclude<TQuery, undefined>;
|
|
73
90
|
} : {
|
|
@@ -106,6 +123,7 @@ type ExtractMethodError<T> = T extends (...args: never[]) => infer R ? ErrorResp
|
|
|
106
123
|
error: infer E;
|
|
107
124
|
} ? E : unknown : unknown;
|
|
108
125
|
type ExtractMethodOptions<T> = T extends (...args: infer A) => unknown ? A[0] : never;
|
|
126
|
+
type ExtractCoreMethodOptions<T> = T extends (...args: infer A) => unknown ? A[0] extends object ? Pick<A[0], Extract<keyof A[0], "query" | "params" | "body">> : object : object;
|
|
109
127
|
type ExtractMethodQuery<T> = ExtractMethodOptions<T> extends {
|
|
110
128
|
query: infer Q;
|
|
111
129
|
} ? Q : never;
|
|
@@ -237,8 +255,8 @@ type BaseInfiniteReadResult<TData, TError, TItem, TPluginResult = Record<string,
|
|
|
237
255
|
fetchNext: () => Promise<void>;
|
|
238
256
|
/** Fetch the previous page */
|
|
239
257
|
fetchPrev: () => Promise<void>;
|
|
240
|
-
/**
|
|
241
|
-
|
|
258
|
+
/** Trigger refetch of all pages from the beginning */
|
|
259
|
+
trigger: () => Promise<void>;
|
|
242
260
|
/** Abort the current fetch operation */
|
|
243
261
|
abort: () => void;
|
|
244
262
|
/** Error from the last failed request */
|
|
@@ -263,6 +281,9 @@ type UseReadFn<TDefaultError, TSchema, TPlugins extends PluginArray> = {
|
|
|
263
281
|
type UseWriteFn<TDefaultError, TSchema, TPlugins extends PluginArray> = {
|
|
264
282
|
<TMethod extends (...args: never) => Promise<SpooshResponse<unknown, unknown>>>(writeFn: (api: WriteApiClient<TSchema, TDefaultError>) => TMethod): BaseWriteResult<ExtractMethodData<TMethod>, InferError<ExtractMethodError<TMethod>, TDefaultError>, Parameters<TMethod>[0] & ResolvedWriteOptions<TSchema, TPlugins, TMethod, TDefaultError>, MergePluginResults<TPlugins>["write"]> & WriteResponseInputFields<ExtractMethodQuery<TMethod>, ExtractMethodBody<TMethod>, ExtractResponseParamNames<TMethod>>;
|
|
265
283
|
};
|
|
284
|
+
type UseLazyReadFn<TDefaultError, TSchema> = {
|
|
285
|
+
<TMethod extends (...args: never) => Promise<SpooshResponse<unknown, unknown>>>(readFn: (api: ReadApiClient<TSchema, TDefaultError>) => TMethod): BaseLazyReadResult<ExtractMethodData<TMethod>, InferError<ExtractMethodError<TMethod>, TDefaultError>, ExtractCoreMethodOptions<TMethod>> & WriteResponseInputFields<ExtractResponseQuery<TMethod>, ExtractResponseBody<TMethod>, ExtractResponseParamNames<TMethod>>;
|
|
286
|
+
};
|
|
266
287
|
type InfiniteReadResolverContext<TSchema, TData, TError, TRequest> = ResolverContext<TSchema, TData, TError, TRequest extends {
|
|
267
288
|
query: infer Q;
|
|
268
289
|
} ? Q : never, TRequest extends {
|
|
@@ -285,7 +306,7 @@ type SpooshReactHooks<TDefaultError, TSchema, TPlugins extends PluginArray> = {
|
|
|
285
306
|
*
|
|
286
307
|
* @param readFn - Function that selects the API endpoint to call (e.g., `(api) => api("posts").GET()`)
|
|
287
308
|
* @param readOptions - Optional configuration including `enabled`, `tags`, and plugin-specific options
|
|
288
|
-
* @returns Object containing `data`, `error`, `loading`, `fetching`, `
|
|
309
|
+
* @returns Object containing `data`, `error`, `loading`, `fetching`, `trigger`, and `abort`
|
|
289
310
|
*
|
|
290
311
|
* @example
|
|
291
312
|
* ```tsx
|
|
@@ -302,7 +323,7 @@ type SpooshReactHooks<TDefaultError, TSchema, TPlugins extends PluginArray> = {
|
|
|
302
323
|
* React hook for mutations (POST, PUT, PATCH, DELETE) with manual triggering.
|
|
303
324
|
*
|
|
304
325
|
* @param writeFn - Function that selects the API endpoint (e.g., `(api) => api("posts").POST`)
|
|
305
|
-
* @returns Object containing `trigger`, `data`, `error`, `loading`,
|
|
326
|
+
* @returns Object containing `trigger`, `data`, `error`, `loading`, and `abort`
|
|
306
327
|
*
|
|
307
328
|
* @example
|
|
308
329
|
* ```tsx
|
|
@@ -315,6 +336,23 @@ type SpooshReactHooks<TDefaultError, TSchema, TPlugins extends PluginArray> = {
|
|
|
315
336
|
* ```
|
|
316
337
|
*/
|
|
317
338
|
useWrite: UseWriteFn<TDefaultError, TSchema, TPlugins>;
|
|
339
|
+
/**
|
|
340
|
+
* React hook for lazy GET requests with manual triggering (does not auto-fetch on mount).
|
|
341
|
+
*
|
|
342
|
+
* @param readFn - Function that selects the API endpoint (e.g., `(api) => api("posts").GET`)
|
|
343
|
+
* @returns Object containing `trigger`, `data`, `error`, `loading`, and `abort`
|
|
344
|
+
*
|
|
345
|
+
* @example
|
|
346
|
+
* ```tsx
|
|
347
|
+
* const { trigger, loading, data } = useLazyRead((api) => api("posts/:id").GET);
|
|
348
|
+
*
|
|
349
|
+
* const handleClick = async (id) => {
|
|
350
|
+
* const { data, error } = await trigger({ params: { id } });
|
|
351
|
+
* if (data) console.log('Fetched:', data);
|
|
352
|
+
* };
|
|
353
|
+
* ```
|
|
354
|
+
*/
|
|
355
|
+
useLazyRead: UseLazyReadFn<TDefaultError, TSchema>;
|
|
318
356
|
/**
|
|
319
357
|
* React hook for infinite/paginated data fetching with automatic pagination control.
|
|
320
358
|
*
|
|
@@ -411,6 +449,8 @@ declare function createUseRead<TSchema, TDefaultError, TPlugins extends readonly
|
|
|
411
449
|
}> ? E : unknown, MergePluginResults<TPlugins>["read"]> & ResponseInputFields<ExtractResponseQuery<TReadFn>, ExtractResponseBody<TReadFn>, ExtractResponseParamNames<TReadFn>>;
|
|
412
450
|
};
|
|
413
451
|
|
|
452
|
+
declare function createUseLazyRead<TSchema, TDefaultError, TPlugins extends readonly SpooshPlugin<PluginTypeConfig>[]>(options: Omit<SpooshInstanceShape<unknown, TSchema, TDefaultError, TPlugins>, "_types">): <TMethod extends (...args: never[]) => Promise<SpooshResponse<unknown, unknown>>>(readFn: (api: ReadApiClient<TSchema, TDefaultError>) => TMethod) => BaseLazyReadResult<ExtractMethodData<TMethod>, [ExtractMethodError<TMethod>] extends [unknown] ? TDefaultError : ExtractMethodError<TMethod>, ExtractCoreMethodOptions<TMethod>> & WriteResponseInputFields<ExtractResponseQuery<TMethod>, ExtractResponseBody<TMethod>, ExtractResponseParamNames<TMethod>>;
|
|
453
|
+
|
|
414
454
|
declare function createUseWrite<TSchema, TDefaultError, TPlugins extends readonly SpooshPlugin<PluginTypeConfig>[]>(options: Omit<SpooshInstanceShape<unknown, TSchema, TDefaultError, TPlugins>, "_types">): <TMethod extends (...args: never[]) => Promise<SpooshResponse<unknown, unknown>>>(writeFn: (api: WriteApiClient<TSchema, TDefaultError>) => TMethod) => BaseWriteResult<ExtractMethodData<TMethod>, [ExtractMethodError<TMethod>] extends [unknown] ? TDefaultError : ExtractMethodError<TMethod>, ExtractMethodOptions<TMethod> & ResolveTypes<((TPlugins[number] extends infer T ? T extends TPlugins[number] ? T extends SpooshPlugin<infer Types extends PluginTypeConfig> ? Types extends {
|
|
415
455
|
writeOptions: infer W;
|
|
416
456
|
} ? W : object : object : never : never) extends infer T_1 ? T_1 extends (TPlugins[number] extends infer T_2 ? T_2 extends TPlugins[number] ? T_2 extends SpooshPlugin<infer Types extends PluginTypeConfig> ? Types extends {
|
|
@@ -427,4 +467,4 @@ declare function createUseInfiniteRead<TSchema, TDefaultError, TPlugins extends
|
|
|
427
467
|
readResult: infer R;
|
|
428
468
|
} ? R : object : object : never : never) ? T_1 extends unknown ? (x: T_1) => void : never : never : never) extends (x: infer I) => void ? I : never>;
|
|
429
469
|
|
|
430
|
-
export { type BaseInfiniteReadOptions, type BaseInfiniteReadResult, type BaseReadOptions, type BaseReadResult, type BaseWriteResult, type PluginHooksConfig, type SpooshReactHooks, type UseInfiniteReadResult, type UseReadResult, type UseWriteResult, createReactSpoosh, createUseInfiniteRead, createUseRead, createUseWrite };
|
|
470
|
+
export { type BaseInfiniteReadOptions, type BaseInfiniteReadResult, type BaseLazyReadResult, type BaseReadOptions, type BaseReadResult, type BaseWriteResult, type PluginHooksConfig, type SpooshReactHooks, type UseInfiniteReadResult, type UseReadResult, type UseWriteResult, createReactSpoosh, createUseInfiniteRead, createUseLazyRead, createUseRead, createUseWrite };
|
package/dist/index.js
CHANGED
|
@@ -24,6 +24,7 @@ var src_exports = {};
|
|
|
24
24
|
__export(src_exports, {
|
|
25
25
|
createReactSpoosh: () => createReactSpoosh,
|
|
26
26
|
createUseInfiniteRead: () => createUseInfiniteRead,
|
|
27
|
+
createUseLazyRead: () => createUseLazyRead,
|
|
27
28
|
createUseRead: () => createUseRead,
|
|
28
29
|
createUseWrite: () => createUseWrite
|
|
29
30
|
});
|
|
@@ -171,7 +172,7 @@ function createUseRead(options) {
|
|
|
171
172
|
const abort = (0, import_react.useCallback)(() => {
|
|
172
173
|
abortRef.current();
|
|
173
174
|
}, []);
|
|
174
|
-
const
|
|
175
|
+
const trigger = (0, import_react.useCallback)(() => {
|
|
175
176
|
return executeWithTracking(true);
|
|
176
177
|
}, [executeWithTracking]);
|
|
177
178
|
const entry = stateManager.getCache(queryKey);
|
|
@@ -200,7 +201,7 @@ function createUseRead(options) {
|
|
|
200
201
|
loading,
|
|
201
202
|
fetching,
|
|
202
203
|
abort,
|
|
203
|
-
|
|
204
|
+
trigger
|
|
204
205
|
};
|
|
205
206
|
}
|
|
206
207
|
return useRead;
|
|
@@ -262,10 +263,6 @@ function createUseWrite(options) {
|
|
|
262
263
|
);
|
|
263
264
|
const [lastTriggerOptions, setLastTriggerOptions] = (0, import_react2.useState)(void 0);
|
|
264
265
|
const [requestState, setRequestState] = (0, import_react2.useState)({ isPending: false, error: void 0 });
|
|
265
|
-
const reset = (0, import_react2.useCallback)(() => {
|
|
266
|
-
stateManager.deleteCache(queryKey);
|
|
267
|
-
setRequestState({ isPending: false, error: void 0 });
|
|
268
|
-
}, [queryKey]);
|
|
269
266
|
const abort = (0, import_react2.useCallback)(() => {
|
|
270
267
|
controller.abort();
|
|
271
268
|
}, []);
|
|
@@ -317,7 +314,6 @@ function createUseWrite(options) {
|
|
|
317
314
|
data: state.data,
|
|
318
315
|
error: requestState.error ?? state.error,
|
|
319
316
|
loading,
|
|
320
|
-
reset,
|
|
321
317
|
abort
|
|
322
318
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
323
319
|
};
|
|
@@ -325,9 +321,141 @@ function createUseWrite(options) {
|
|
|
325
321
|
return useWrite;
|
|
326
322
|
}
|
|
327
323
|
|
|
328
|
-
// src/
|
|
324
|
+
// src/useLazyRead/index.ts
|
|
329
325
|
var import_react3 = require("react");
|
|
330
326
|
var import_core3 = require("@spoosh/core");
|
|
327
|
+
function createUseLazyRead(options) {
|
|
328
|
+
const { api, stateManager, pluginExecutor, eventEmitter } = options;
|
|
329
|
+
function useLazyRead(readFn) {
|
|
330
|
+
const hookId = (0, import_react3.useId)();
|
|
331
|
+
const selectorResultRef = (0, import_react3.useRef)({
|
|
332
|
+
call: null,
|
|
333
|
+
selector: null
|
|
334
|
+
});
|
|
335
|
+
const selectorProxy = (0, import_core3.createSelectorProxy)((result) => {
|
|
336
|
+
selectorResultRef.current = result;
|
|
337
|
+
});
|
|
338
|
+
readFn(selectorProxy);
|
|
339
|
+
const selectedEndpoint = selectorResultRef.current.selector;
|
|
340
|
+
if (!selectedEndpoint) {
|
|
341
|
+
throw new Error(
|
|
342
|
+
'useLazyRead requires selecting an HTTP method (GET). Example: useLazyRead((api) => api("posts").GET)'
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
if (selectedEndpoint.method !== "GET") {
|
|
346
|
+
throw new Error(
|
|
347
|
+
"useLazyRead only supports GET method. Use useWrite for POST, PUT, PATCH, DELETE methods."
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
const pathSegments = selectedEndpoint.path.split("/").filter(Boolean);
|
|
351
|
+
const controllerRef = (0, import_react3.useRef)(null);
|
|
352
|
+
const emptyStateRef = (0, import_react3.useRef)({ data: void 0, error: void 0 });
|
|
353
|
+
const [currentQueryKey, setCurrentQueryKey] = (0, import_react3.useState)(null);
|
|
354
|
+
const [, forceUpdate] = (0, import_react3.useState)(0);
|
|
355
|
+
const getOrCreateController = (0, import_react3.useCallback)(
|
|
356
|
+
(triggerOptions) => {
|
|
357
|
+
const queryKey = stateManager.createQueryKey({
|
|
358
|
+
path: pathSegments,
|
|
359
|
+
method: selectedEndpoint.method,
|
|
360
|
+
options: triggerOptions
|
|
361
|
+
});
|
|
362
|
+
if (controllerRef.current?.queryKey === queryKey) {
|
|
363
|
+
return { controller: controllerRef.current.controller, queryKey };
|
|
364
|
+
}
|
|
365
|
+
const controller2 = (0, import_core3.createOperationController)({
|
|
366
|
+
operationType: "read",
|
|
367
|
+
path: pathSegments,
|
|
368
|
+
method: "GET",
|
|
369
|
+
tags: [],
|
|
370
|
+
stateManager,
|
|
371
|
+
eventEmitter,
|
|
372
|
+
pluginExecutor,
|
|
373
|
+
hookId,
|
|
374
|
+
requestOptions: triggerOptions,
|
|
375
|
+
fetchFn: async (fetchOpts) => {
|
|
376
|
+
const pathMethods = api(selectedEndpoint.path);
|
|
377
|
+
const method = pathMethods[selectedEndpoint.method];
|
|
378
|
+
return method(fetchOpts);
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
controllerRef.current = { controller: controller2, queryKey };
|
|
382
|
+
setCurrentQueryKey(queryKey);
|
|
383
|
+
forceUpdate((n) => n + 1);
|
|
384
|
+
return { controller: controller2, queryKey };
|
|
385
|
+
},
|
|
386
|
+
[pathSegments, selectedEndpoint.method, selectedEndpoint.path, hookId]
|
|
387
|
+
);
|
|
388
|
+
const controller = controllerRef.current?.controller;
|
|
389
|
+
const subscribe = (0, import_react3.useCallback)(
|
|
390
|
+
(callback) => {
|
|
391
|
+
if (!controller) return () => {
|
|
392
|
+
};
|
|
393
|
+
return controller.subscribe(callback);
|
|
394
|
+
},
|
|
395
|
+
[controller]
|
|
396
|
+
);
|
|
397
|
+
const getSnapshot = (0, import_react3.useCallback)(() => {
|
|
398
|
+
if (!controller) return emptyStateRef.current;
|
|
399
|
+
return controller.getState();
|
|
400
|
+
}, [controller]);
|
|
401
|
+
const state = (0, import_react3.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
|
|
402
|
+
const [lastTriggerOptions, setLastTriggerOptions] = (0, import_react3.useState)(void 0);
|
|
403
|
+
const [requestState, setRequestState] = (0, import_react3.useState)({ isPending: false, error: void 0 });
|
|
404
|
+
const abort = (0, import_react3.useCallback)(() => {
|
|
405
|
+
controllerRef.current?.controller.abort();
|
|
406
|
+
}, []);
|
|
407
|
+
const trigger = (0, import_react3.useCallback)(
|
|
408
|
+
async (triggerOptions) => {
|
|
409
|
+
setLastTriggerOptions(triggerOptions);
|
|
410
|
+
setRequestState((prev) => ({ ...prev, isPending: true }));
|
|
411
|
+
const params = triggerOptions?.params;
|
|
412
|
+
(0, import_core3.resolvePath)(pathSegments, params);
|
|
413
|
+
const { controller: ctrl } = getOrCreateController(triggerOptions);
|
|
414
|
+
ctrl.setPluginOptions(triggerOptions);
|
|
415
|
+
try {
|
|
416
|
+
const response = await ctrl.execute(triggerOptions);
|
|
417
|
+
if (response.error) {
|
|
418
|
+
setRequestState({ isPending: false, error: response.error });
|
|
419
|
+
} else {
|
|
420
|
+
setRequestState({ isPending: false, error: void 0 });
|
|
421
|
+
}
|
|
422
|
+
return response;
|
|
423
|
+
} catch (err) {
|
|
424
|
+
setRequestState({ isPending: false, error: err });
|
|
425
|
+
throw err;
|
|
426
|
+
}
|
|
427
|
+
},
|
|
428
|
+
[pathSegments, getOrCreateController]
|
|
429
|
+
);
|
|
430
|
+
const opts = lastTriggerOptions;
|
|
431
|
+
const inputInner = {};
|
|
432
|
+
if (opts?.query !== void 0) {
|
|
433
|
+
inputInner.query = opts.query;
|
|
434
|
+
}
|
|
435
|
+
if (opts?.body !== void 0) {
|
|
436
|
+
inputInner.body = opts.body;
|
|
437
|
+
}
|
|
438
|
+
if (opts?.params !== void 0) {
|
|
439
|
+
inputInner.params = opts.params;
|
|
440
|
+
}
|
|
441
|
+
const inputField = Object.keys(inputInner).length > 0 ? { input: inputInner } : {};
|
|
442
|
+
const loading = requestState.isPending;
|
|
443
|
+
return {
|
|
444
|
+
trigger,
|
|
445
|
+
...inputField,
|
|
446
|
+
data: state.data,
|
|
447
|
+
error: requestState.error ?? state.error,
|
|
448
|
+
loading,
|
|
449
|
+
abort
|
|
450
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
return useLazyRead;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// src/useInfiniteRead/index.ts
|
|
457
|
+
var import_react4 = require("react");
|
|
458
|
+
var import_core4 = require("@spoosh/core");
|
|
331
459
|
function createUseInfiniteRead(options) {
|
|
332
460
|
const { api, stateManager, eventEmitter, pluginExecutor } = options;
|
|
333
461
|
return function useInfiniteRead(readFn, readOptions) {
|
|
@@ -341,12 +469,12 @@ function createUseInfiniteRead(options) {
|
|
|
341
469
|
prevPageRequest,
|
|
342
470
|
...pluginOpts
|
|
343
471
|
} = readOptions;
|
|
344
|
-
const hookId = (0,
|
|
345
|
-
const selectorResultRef = (0,
|
|
472
|
+
const hookId = (0, import_react4.useId)();
|
|
473
|
+
const selectorResultRef = (0, import_react4.useRef)({
|
|
346
474
|
call: null,
|
|
347
475
|
selector: null
|
|
348
476
|
});
|
|
349
|
-
const selectorProxy = (0,
|
|
477
|
+
const selectorProxy = (0, import_core4.createSelectorProxy)((result2) => {
|
|
350
478
|
selectorResultRef.current = result2;
|
|
351
479
|
});
|
|
352
480
|
readFn(selectorProxy);
|
|
@@ -369,13 +497,13 @@ function createUseInfiniteRead(options) {
|
|
|
369
497
|
params: void 0,
|
|
370
498
|
body: void 0
|
|
371
499
|
};
|
|
372
|
-
const resolvedPath = (0,
|
|
373
|
-
const resolvedTags = (0,
|
|
374
|
-
const canFetchNextRef = (0,
|
|
375
|
-
const canFetchPrevRef = (0,
|
|
376
|
-
const nextPageRequestRef = (0,
|
|
377
|
-
const prevPageRequestRef = (0,
|
|
378
|
-
const mergerRef = (0,
|
|
500
|
+
const resolvedPath = (0, import_core4.resolvePath)(pathSegments, requestOptions?.params);
|
|
501
|
+
const resolvedTags = (0, import_core4.resolveTags)({ tags }, resolvedPath);
|
|
502
|
+
const canFetchNextRef = (0, import_react4.useRef)(canFetchNext);
|
|
503
|
+
const canFetchPrevRef = (0, import_react4.useRef)(canFetchPrev);
|
|
504
|
+
const nextPageRequestRef = (0, import_react4.useRef)(nextPageRequest);
|
|
505
|
+
const prevPageRequestRef = (0, import_react4.useRef)(prevPageRequest);
|
|
506
|
+
const mergerRef = (0, import_react4.useRef)(merger);
|
|
379
507
|
canFetchNextRef.current = canFetchNext;
|
|
380
508
|
canFetchPrevRef.current = canFetchPrev;
|
|
381
509
|
nextPageRequestRef.current = nextPageRequest;
|
|
@@ -386,10 +514,10 @@ function createUseInfiniteRead(options) {
|
|
|
386
514
|
method: capturedCall.method,
|
|
387
515
|
options: baseOptionsForKey
|
|
388
516
|
});
|
|
389
|
-
const controllerRef = (0,
|
|
517
|
+
const controllerRef = (0, import_react4.useRef)(null);
|
|
390
518
|
if (!controllerRef.current || controllerRef.current.queryKey !== queryKey) {
|
|
391
519
|
controllerRef.current = {
|
|
392
|
-
controller: (0,
|
|
520
|
+
controller: (0, import_core4.createInfiniteReadController)({
|
|
393
521
|
path: pathSegments,
|
|
394
522
|
method: capturedCall.method,
|
|
395
523
|
tags: resolvedTags,
|
|
@@ -422,12 +550,12 @@ function createUseInfiniteRead(options) {
|
|
|
422
550
|
}
|
|
423
551
|
const controller = controllerRef.current.controller;
|
|
424
552
|
controller.setPluginOptions(pluginOpts);
|
|
425
|
-
const state = (0,
|
|
553
|
+
const state = (0, import_react4.useSyncExternalStore)(
|
|
426
554
|
controller.subscribe,
|
|
427
555
|
controller.getState,
|
|
428
556
|
controller.getState
|
|
429
557
|
);
|
|
430
|
-
const [isPending, setIsPending] = (0,
|
|
558
|
+
const [isPending, setIsPending] = (0, import_react4.useState)(() => {
|
|
431
559
|
return enabled && state.data === void 0;
|
|
432
560
|
});
|
|
433
561
|
const fetchingDirection = controller.getFetchingDirection();
|
|
@@ -436,18 +564,18 @@ function createUseInfiniteRead(options) {
|
|
|
436
564
|
const fetchingPrev = fetchingDirection === "prev";
|
|
437
565
|
const hasData = state.data !== void 0;
|
|
438
566
|
const loading = (isPending || fetching) && !hasData;
|
|
439
|
-
const lifecycleRef = (0,
|
|
567
|
+
const lifecycleRef = (0, import_react4.useRef)({
|
|
440
568
|
initialized: false,
|
|
441
569
|
prevContext: null
|
|
442
570
|
});
|
|
443
571
|
const tagsKey = JSON.stringify(tags);
|
|
444
|
-
(0,
|
|
572
|
+
(0, import_react4.useEffect)(() => {
|
|
445
573
|
return () => {
|
|
446
574
|
controllerRef.current?.controller.unmount();
|
|
447
575
|
lifecycleRef.current.initialized = false;
|
|
448
576
|
};
|
|
449
577
|
}, []);
|
|
450
|
-
(0,
|
|
578
|
+
(0, import_react4.useEffect)(() => {
|
|
451
579
|
controller.mount();
|
|
452
580
|
lifecycleRef.current.initialized = true;
|
|
453
581
|
const unsubInvalidate = eventEmitter.on(
|
|
@@ -466,7 +594,7 @@ function createUseInfiniteRead(options) {
|
|
|
466
594
|
unsubInvalidate();
|
|
467
595
|
};
|
|
468
596
|
}, [tagsKey]);
|
|
469
|
-
(0,
|
|
597
|
+
(0, import_react4.useEffect)(() => {
|
|
470
598
|
if (!lifecycleRef.current.initialized) return;
|
|
471
599
|
if (enabled) {
|
|
472
600
|
const currentState = controller.getState();
|
|
@@ -477,7 +605,7 @@ function createUseInfiniteRead(options) {
|
|
|
477
605
|
}
|
|
478
606
|
}
|
|
479
607
|
}, [enabled]);
|
|
480
|
-
(0,
|
|
608
|
+
(0, import_react4.useEffect)(() => {
|
|
481
609
|
if (!enabled || !lifecycleRef.current.initialized) return;
|
|
482
610
|
const prevContext = controller.getContext();
|
|
483
611
|
controller.update(prevContext);
|
|
@@ -496,7 +624,7 @@ function createUseInfiniteRead(options) {
|
|
|
496
624
|
canFetchPrev: state.canFetchPrev,
|
|
497
625
|
fetchNext: controller.fetchNext,
|
|
498
626
|
fetchPrev: controller.fetchPrev,
|
|
499
|
-
|
|
627
|
+
trigger: controller.refetch,
|
|
500
628
|
abort: controller.abort,
|
|
501
629
|
error: state.error
|
|
502
630
|
};
|
|
@@ -519,6 +647,12 @@ function createReactSpoosh(instance) {
|
|
|
519
647
|
eventEmitter,
|
|
520
648
|
pluginExecutor
|
|
521
649
|
});
|
|
650
|
+
const useLazyRead = createUseLazyRead({
|
|
651
|
+
api,
|
|
652
|
+
stateManager,
|
|
653
|
+
eventEmitter,
|
|
654
|
+
pluginExecutor
|
|
655
|
+
});
|
|
522
656
|
const useInfiniteRead = createUseInfiniteRead({
|
|
523
657
|
api,
|
|
524
658
|
stateManager,
|
|
@@ -544,6 +678,7 @@ function createReactSpoosh(instance) {
|
|
|
544
678
|
return {
|
|
545
679
|
useRead,
|
|
546
680
|
useWrite,
|
|
681
|
+
useLazyRead,
|
|
547
682
|
useInfiniteRead,
|
|
548
683
|
...instanceApis
|
|
549
684
|
};
|
package/dist/index.mjs
CHANGED
|
@@ -155,7 +155,7 @@ function createUseRead(options) {
|
|
|
155
155
|
const abort = useCallback(() => {
|
|
156
156
|
abortRef.current();
|
|
157
157
|
}, []);
|
|
158
|
-
const
|
|
158
|
+
const trigger = useCallback(() => {
|
|
159
159
|
return executeWithTracking(true);
|
|
160
160
|
}, [executeWithTracking]);
|
|
161
161
|
const entry = stateManager.getCache(queryKey);
|
|
@@ -184,7 +184,7 @@ function createUseRead(options) {
|
|
|
184
184
|
loading,
|
|
185
185
|
fetching,
|
|
186
186
|
abort,
|
|
187
|
-
|
|
187
|
+
trigger
|
|
188
188
|
};
|
|
189
189
|
}
|
|
190
190
|
return useRead;
|
|
@@ -257,10 +257,6 @@ function createUseWrite(options) {
|
|
|
257
257
|
);
|
|
258
258
|
const [lastTriggerOptions, setLastTriggerOptions] = useState2(void 0);
|
|
259
259
|
const [requestState, setRequestState] = useState2({ isPending: false, error: void 0 });
|
|
260
|
-
const reset = useCallback2(() => {
|
|
261
|
-
stateManager.deleteCache(queryKey);
|
|
262
|
-
setRequestState({ isPending: false, error: void 0 });
|
|
263
|
-
}, [queryKey]);
|
|
264
260
|
const abort = useCallback2(() => {
|
|
265
261
|
controller.abort();
|
|
266
262
|
}, []);
|
|
@@ -312,7 +308,6 @@ function createUseWrite(options) {
|
|
|
312
308
|
data: state.data,
|
|
313
309
|
error: requestState.error ?? state.error,
|
|
314
310
|
loading,
|
|
315
|
-
reset,
|
|
316
311
|
abort
|
|
317
312
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
318
313
|
};
|
|
@@ -320,18 +315,160 @@ function createUseWrite(options) {
|
|
|
320
315
|
return useWrite;
|
|
321
316
|
}
|
|
322
317
|
|
|
323
|
-
// src/
|
|
318
|
+
// src/useLazyRead/index.ts
|
|
324
319
|
import {
|
|
320
|
+
useSyncExternalStore as useSyncExternalStore3,
|
|
325
321
|
useRef as useRef3,
|
|
322
|
+
useCallback as useCallback3,
|
|
323
|
+
useState as useState3,
|
|
324
|
+
useId as useId3
|
|
325
|
+
} from "react";
|
|
326
|
+
import {
|
|
327
|
+
createOperationController as createOperationController3,
|
|
328
|
+
createSelectorProxy as createSelectorProxy3,
|
|
329
|
+
resolvePath as resolvePath3
|
|
330
|
+
} from "@spoosh/core";
|
|
331
|
+
function createUseLazyRead(options) {
|
|
332
|
+
const { api, stateManager, pluginExecutor, eventEmitter } = options;
|
|
333
|
+
function useLazyRead(readFn) {
|
|
334
|
+
const hookId = useId3();
|
|
335
|
+
const selectorResultRef = useRef3({
|
|
336
|
+
call: null,
|
|
337
|
+
selector: null
|
|
338
|
+
});
|
|
339
|
+
const selectorProxy = createSelectorProxy3((result) => {
|
|
340
|
+
selectorResultRef.current = result;
|
|
341
|
+
});
|
|
342
|
+
readFn(selectorProxy);
|
|
343
|
+
const selectedEndpoint = selectorResultRef.current.selector;
|
|
344
|
+
if (!selectedEndpoint) {
|
|
345
|
+
throw new Error(
|
|
346
|
+
'useLazyRead requires selecting an HTTP method (GET). Example: useLazyRead((api) => api("posts").GET)'
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
if (selectedEndpoint.method !== "GET") {
|
|
350
|
+
throw new Error(
|
|
351
|
+
"useLazyRead only supports GET method. Use useWrite for POST, PUT, PATCH, DELETE methods."
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
const pathSegments = selectedEndpoint.path.split("/").filter(Boolean);
|
|
355
|
+
const controllerRef = useRef3(null);
|
|
356
|
+
const emptyStateRef = useRef3({ data: void 0, error: void 0 });
|
|
357
|
+
const [currentQueryKey, setCurrentQueryKey] = useState3(null);
|
|
358
|
+
const [, forceUpdate] = useState3(0);
|
|
359
|
+
const getOrCreateController = useCallback3(
|
|
360
|
+
(triggerOptions) => {
|
|
361
|
+
const queryKey = stateManager.createQueryKey({
|
|
362
|
+
path: pathSegments,
|
|
363
|
+
method: selectedEndpoint.method,
|
|
364
|
+
options: triggerOptions
|
|
365
|
+
});
|
|
366
|
+
if (controllerRef.current?.queryKey === queryKey) {
|
|
367
|
+
return { controller: controllerRef.current.controller, queryKey };
|
|
368
|
+
}
|
|
369
|
+
const controller2 = createOperationController3({
|
|
370
|
+
operationType: "read",
|
|
371
|
+
path: pathSegments,
|
|
372
|
+
method: "GET",
|
|
373
|
+
tags: [],
|
|
374
|
+
stateManager,
|
|
375
|
+
eventEmitter,
|
|
376
|
+
pluginExecutor,
|
|
377
|
+
hookId,
|
|
378
|
+
requestOptions: triggerOptions,
|
|
379
|
+
fetchFn: async (fetchOpts) => {
|
|
380
|
+
const pathMethods = api(selectedEndpoint.path);
|
|
381
|
+
const method = pathMethods[selectedEndpoint.method];
|
|
382
|
+
return method(fetchOpts);
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
controllerRef.current = { controller: controller2, queryKey };
|
|
386
|
+
setCurrentQueryKey(queryKey);
|
|
387
|
+
forceUpdate((n) => n + 1);
|
|
388
|
+
return { controller: controller2, queryKey };
|
|
389
|
+
},
|
|
390
|
+
[pathSegments, selectedEndpoint.method, selectedEndpoint.path, hookId]
|
|
391
|
+
);
|
|
392
|
+
const controller = controllerRef.current?.controller;
|
|
393
|
+
const subscribe = useCallback3(
|
|
394
|
+
(callback) => {
|
|
395
|
+
if (!controller) return () => {
|
|
396
|
+
};
|
|
397
|
+
return controller.subscribe(callback);
|
|
398
|
+
},
|
|
399
|
+
[controller]
|
|
400
|
+
);
|
|
401
|
+
const getSnapshot = useCallback3(() => {
|
|
402
|
+
if (!controller) return emptyStateRef.current;
|
|
403
|
+
return controller.getState();
|
|
404
|
+
}, [controller]);
|
|
405
|
+
const state = useSyncExternalStore3(subscribe, getSnapshot, getSnapshot);
|
|
406
|
+
const [lastTriggerOptions, setLastTriggerOptions] = useState3(void 0);
|
|
407
|
+
const [requestState, setRequestState] = useState3({ isPending: false, error: void 0 });
|
|
408
|
+
const abort = useCallback3(() => {
|
|
409
|
+
controllerRef.current?.controller.abort();
|
|
410
|
+
}, []);
|
|
411
|
+
const trigger = useCallback3(
|
|
412
|
+
async (triggerOptions) => {
|
|
413
|
+
setLastTriggerOptions(triggerOptions);
|
|
414
|
+
setRequestState((prev) => ({ ...prev, isPending: true }));
|
|
415
|
+
const params = triggerOptions?.params;
|
|
416
|
+
resolvePath3(pathSegments, params);
|
|
417
|
+
const { controller: ctrl } = getOrCreateController(triggerOptions);
|
|
418
|
+
ctrl.setPluginOptions(triggerOptions);
|
|
419
|
+
try {
|
|
420
|
+
const response = await ctrl.execute(triggerOptions);
|
|
421
|
+
if (response.error) {
|
|
422
|
+
setRequestState({ isPending: false, error: response.error });
|
|
423
|
+
} else {
|
|
424
|
+
setRequestState({ isPending: false, error: void 0 });
|
|
425
|
+
}
|
|
426
|
+
return response;
|
|
427
|
+
} catch (err) {
|
|
428
|
+
setRequestState({ isPending: false, error: err });
|
|
429
|
+
throw err;
|
|
430
|
+
}
|
|
431
|
+
},
|
|
432
|
+
[pathSegments, getOrCreateController]
|
|
433
|
+
);
|
|
434
|
+
const opts = lastTriggerOptions;
|
|
435
|
+
const inputInner = {};
|
|
436
|
+
if (opts?.query !== void 0) {
|
|
437
|
+
inputInner.query = opts.query;
|
|
438
|
+
}
|
|
439
|
+
if (opts?.body !== void 0) {
|
|
440
|
+
inputInner.body = opts.body;
|
|
441
|
+
}
|
|
442
|
+
if (opts?.params !== void 0) {
|
|
443
|
+
inputInner.params = opts.params;
|
|
444
|
+
}
|
|
445
|
+
const inputField = Object.keys(inputInner).length > 0 ? { input: inputInner } : {};
|
|
446
|
+
const loading = requestState.isPending;
|
|
447
|
+
return {
|
|
448
|
+
trigger,
|
|
449
|
+
...inputField,
|
|
450
|
+
data: state.data,
|
|
451
|
+
error: requestState.error ?? state.error,
|
|
452
|
+
loading,
|
|
453
|
+
abort
|
|
454
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
return useLazyRead;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// src/useInfiniteRead/index.ts
|
|
461
|
+
import {
|
|
462
|
+
useRef as useRef4,
|
|
326
463
|
useEffect as useEffect2,
|
|
327
|
-
useSyncExternalStore as
|
|
328
|
-
useId as
|
|
329
|
-
useState as
|
|
464
|
+
useSyncExternalStore as useSyncExternalStore4,
|
|
465
|
+
useId as useId4,
|
|
466
|
+
useState as useState4
|
|
330
467
|
} from "react";
|
|
331
468
|
import {
|
|
332
469
|
createInfiniteReadController,
|
|
333
|
-
createSelectorProxy as
|
|
334
|
-
resolvePath as
|
|
470
|
+
createSelectorProxy as createSelectorProxy4,
|
|
471
|
+
resolvePath as resolvePath4,
|
|
335
472
|
resolveTags as resolveTags3
|
|
336
473
|
} from "@spoosh/core";
|
|
337
474
|
function createUseInfiniteRead(options) {
|
|
@@ -347,12 +484,12 @@ function createUseInfiniteRead(options) {
|
|
|
347
484
|
prevPageRequest,
|
|
348
485
|
...pluginOpts
|
|
349
486
|
} = readOptions;
|
|
350
|
-
const hookId =
|
|
351
|
-
const selectorResultRef =
|
|
487
|
+
const hookId = useId4();
|
|
488
|
+
const selectorResultRef = useRef4({
|
|
352
489
|
call: null,
|
|
353
490
|
selector: null
|
|
354
491
|
});
|
|
355
|
-
const selectorProxy =
|
|
492
|
+
const selectorProxy = createSelectorProxy4((result2) => {
|
|
356
493
|
selectorResultRef.current = result2;
|
|
357
494
|
});
|
|
358
495
|
readFn(selectorProxy);
|
|
@@ -375,13 +512,13 @@ function createUseInfiniteRead(options) {
|
|
|
375
512
|
params: void 0,
|
|
376
513
|
body: void 0
|
|
377
514
|
};
|
|
378
|
-
const resolvedPath =
|
|
515
|
+
const resolvedPath = resolvePath4(pathSegments, requestOptions?.params);
|
|
379
516
|
const resolvedTags = resolveTags3({ tags }, resolvedPath);
|
|
380
|
-
const canFetchNextRef =
|
|
381
|
-
const canFetchPrevRef =
|
|
382
|
-
const nextPageRequestRef =
|
|
383
|
-
const prevPageRequestRef =
|
|
384
|
-
const mergerRef =
|
|
517
|
+
const canFetchNextRef = useRef4(canFetchNext);
|
|
518
|
+
const canFetchPrevRef = useRef4(canFetchPrev);
|
|
519
|
+
const nextPageRequestRef = useRef4(nextPageRequest);
|
|
520
|
+
const prevPageRequestRef = useRef4(prevPageRequest);
|
|
521
|
+
const mergerRef = useRef4(merger);
|
|
385
522
|
canFetchNextRef.current = canFetchNext;
|
|
386
523
|
canFetchPrevRef.current = canFetchPrev;
|
|
387
524
|
nextPageRequestRef.current = nextPageRequest;
|
|
@@ -392,7 +529,7 @@ function createUseInfiniteRead(options) {
|
|
|
392
529
|
method: capturedCall.method,
|
|
393
530
|
options: baseOptionsForKey
|
|
394
531
|
});
|
|
395
|
-
const controllerRef =
|
|
532
|
+
const controllerRef = useRef4(null);
|
|
396
533
|
if (!controllerRef.current || controllerRef.current.queryKey !== queryKey) {
|
|
397
534
|
controllerRef.current = {
|
|
398
535
|
controller: createInfiniteReadController({
|
|
@@ -428,12 +565,12 @@ function createUseInfiniteRead(options) {
|
|
|
428
565
|
}
|
|
429
566
|
const controller = controllerRef.current.controller;
|
|
430
567
|
controller.setPluginOptions(pluginOpts);
|
|
431
|
-
const state =
|
|
568
|
+
const state = useSyncExternalStore4(
|
|
432
569
|
controller.subscribe,
|
|
433
570
|
controller.getState,
|
|
434
571
|
controller.getState
|
|
435
572
|
);
|
|
436
|
-
const [isPending, setIsPending] =
|
|
573
|
+
const [isPending, setIsPending] = useState4(() => {
|
|
437
574
|
return enabled && state.data === void 0;
|
|
438
575
|
});
|
|
439
576
|
const fetchingDirection = controller.getFetchingDirection();
|
|
@@ -442,7 +579,7 @@ function createUseInfiniteRead(options) {
|
|
|
442
579
|
const fetchingPrev = fetchingDirection === "prev";
|
|
443
580
|
const hasData = state.data !== void 0;
|
|
444
581
|
const loading = (isPending || fetching) && !hasData;
|
|
445
|
-
const lifecycleRef =
|
|
582
|
+
const lifecycleRef = useRef4({
|
|
446
583
|
initialized: false,
|
|
447
584
|
prevContext: null
|
|
448
585
|
});
|
|
@@ -502,7 +639,7 @@ function createUseInfiniteRead(options) {
|
|
|
502
639
|
canFetchPrev: state.canFetchPrev,
|
|
503
640
|
fetchNext: controller.fetchNext,
|
|
504
641
|
fetchPrev: controller.fetchPrev,
|
|
505
|
-
|
|
642
|
+
trigger: controller.refetch,
|
|
506
643
|
abort: controller.abort,
|
|
507
644
|
error: state.error
|
|
508
645
|
};
|
|
@@ -525,6 +662,12 @@ function createReactSpoosh(instance) {
|
|
|
525
662
|
eventEmitter,
|
|
526
663
|
pluginExecutor
|
|
527
664
|
});
|
|
665
|
+
const useLazyRead = createUseLazyRead({
|
|
666
|
+
api,
|
|
667
|
+
stateManager,
|
|
668
|
+
eventEmitter,
|
|
669
|
+
pluginExecutor
|
|
670
|
+
});
|
|
528
671
|
const useInfiniteRead = createUseInfiniteRead({
|
|
529
672
|
api,
|
|
530
673
|
stateManager,
|
|
@@ -550,6 +693,7 @@ function createReactSpoosh(instance) {
|
|
|
550
693
|
return {
|
|
551
694
|
useRead,
|
|
552
695
|
useWrite,
|
|
696
|
+
useLazyRead,
|
|
553
697
|
useInfiniteRead,
|
|
554
698
|
...instanceApis
|
|
555
699
|
};
|
|
@@ -557,6 +701,7 @@ function createReactSpoosh(instance) {
|
|
|
557
701
|
export {
|
|
558
702
|
createReactSpoosh,
|
|
559
703
|
createUseInfiniteRead,
|
|
704
|
+
createUseLazyRead,
|
|
560
705
|
createUseRead,
|
|
561
706
|
createUseWrite
|
|
562
707
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spoosh/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "React hooks for Spoosh API client",
|
|
6
6
|
"keywords": [
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"react": "^18 || ^19"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@spoosh/core": "0.
|
|
41
|
+
"@spoosh/core": "0.9.0"
|
|
42
42
|
},
|
|
43
43
|
"scripts": {
|
|
44
44
|
"dev": "tsup --watch",
|