@spoosh/react 0.10.1 → 0.12.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/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ReadClient, TagMode, SpooshResponse, SpooshPlugin, PluginTypeConfig, MergePluginResults, WriteSelectorClient, SpooshBody, StateManager, EventEmitter, PluginExecutor, PluginArray, ResolveTypes, MergePluginOptions, ResolverContext, ResolveResultTypes, MergePluginInstanceApi, SpooshOptions } from '@spoosh/core';
1
+ import { ReadClient, TagMode, SpooshResponse, ExtractTriggerQuery as ExtractTriggerQuery$2, ExtractTriggerBody as ExtractTriggerBody$2, ExtractTriggerParams as ExtractTriggerParams$2, SpooshPlugin, PluginTypeConfig, MergePluginResults, WriteSelectorClient, SpooshBody, QueueSelectorClient, QueueItem, QueueStats, InfiniteRequestOptions, InfiniteNextContext, InfinitePage, InfinitePrevContext, StateManager, EventEmitter, PluginExecutor, PluginArray, ResolveTypes, MergePluginOptions, ResolverContext, ResolveResultTypes, MergePluginInstanceApi, SpooshOptions } from '@spoosh/core';
2
2
 
3
3
  type SuccessResponse<T> = Extract<T, {
4
4
  data: unknown;
@@ -81,28 +81,13 @@ type ResponseInputFields<TQuery, TBody, TParamNames extends string> = [
81
81
  ] extends [never, never, never] ? object : {
82
82
  input: ReadInputFields<TQuery, TBody, TParamNames>;
83
83
  };
84
- type TriggerAwaitedReturn$1<T> = T extends (...args: never[]) => infer R ? Awaited<R> : never;
85
- type ExtractInputFromResponse$1<T> = T extends {
84
+ type TriggerAwaitedReturn$3<T> = T extends (...args: never[]) => infer R ? Awaited<R> : never;
85
+ type ExtractInputFromResponse$3<T> = T extends {
86
86
  input: infer I;
87
87
  } ? I : never;
88
- type ExtractTriggerQuery$1<I> = I extends {
89
- query: infer Q;
90
- } ? {
91
- query?: Q;
92
- } : unknown;
93
- type ExtractTriggerBody$1<I> = I extends {
94
- body: infer B;
95
- } ? {
96
- body?: B;
97
- } : unknown;
98
- type ExtractTriggerParams$1<I> = I extends {
99
- params: infer P;
100
- } ? {
101
- params?: P;
102
- } : unknown;
103
- type TriggerOptions<T> = ExtractInputFromResponse$1<TriggerAwaitedReturn$1<T>> extends infer I ? [I] extends [never] ? {
88
+ type TriggerOptions<T> = ExtractInputFromResponse$3<TriggerAwaitedReturn$3<T>> extends infer I ? [I] extends [never] ? {
104
89
  force?: boolean;
105
- } : ExtractTriggerQuery$1<I> & ExtractTriggerBody$1<I> & ExtractTriggerParams$1<I> & {
90
+ } : ExtractTriggerQuery$2<I> & ExtractTriggerBody$2<I> & ExtractTriggerParams$2<I> & {
106
91
  /** Force refetch even if data is cached */
107
92
  force?: boolean;
108
93
  } : {
@@ -160,30 +145,30 @@ type InputFields<TQuery, TBody, TParamNames extends string> = OptionalQueryField
160
145
  type WriteResponseInputFields<TQuery, TBody, TParamNames extends string> = [TQuery, TBody, TParamNames] extends [never, never, never] ? object : {
161
146
  input: InputFields<TQuery, TBody, TParamNames> | undefined;
162
147
  };
163
- type TriggerAwaitedReturn<T> = T extends (...args: any[]) => infer R ? Awaited<R> : never;
164
- type ExtractInputFromResponse<T> = T extends {
148
+ type TriggerAwaitedReturn$2<T> = T extends (...args: any[]) => infer R ? Awaited<R> : never;
149
+ type ExtractInputFromResponse$2<T> = T extends {
165
150
  input: infer I;
166
151
  } ? I : never;
167
- type ExtractTriggerQuery<I> = I extends {
152
+ type ExtractTriggerQuery$1<I> = I extends {
168
153
  query: infer Q;
169
154
  } ? undefined extends Q ? {
170
155
  query?: Exclude<Q, undefined>;
171
156
  } : {
172
157
  query: Q;
173
158
  } : unknown;
174
- type ExtractTriggerBody<I> = I extends {
159
+ type ExtractTriggerBody$1<I> = I extends {
175
160
  body: infer B;
176
161
  } ? undefined extends B ? {
177
162
  body?: Exclude<B, undefined> | SpooshBody<Exclude<B, undefined>>;
178
163
  } : {
179
164
  body: B | SpooshBody<B>;
180
165
  } : unknown;
181
- type ExtractTriggerParams<I> = I extends {
166
+ type ExtractTriggerParams$1<I> = I extends {
182
167
  params: infer P;
183
168
  } ? {
184
169
  params: P;
185
170
  } : unknown;
186
- type WriteTriggerInput<T> = ExtractInputFromResponse<TriggerAwaitedReturn<T>> extends infer I ? [I] extends [never] ? object : ExtractTriggerQuery<I> & ExtractTriggerBody<I> & ExtractTriggerParams<I> : object;
171
+ type WriteTriggerInput<T> = ExtractInputFromResponse$2<TriggerAwaitedReturn$2<T>> extends infer I ? [I] extends [never] ? object : ExtractTriggerQuery$1<I> & ExtractTriggerBody$1<I> & ExtractTriggerParams$1<I> : object;
187
172
  /**
188
173
  * Result returned by `useWrite` hook.
189
174
  *
@@ -209,42 +194,93 @@ type BaseWriteResult<TData, TError, TOptions, TMeta = Record<string, unknown>> =
209
194
  type UseWriteResult<TData, TError, TOptions, TMeta, TPlugins extends readonly SpooshPlugin<PluginTypeConfig>[]> = BaseWriteResult<TData, TError, TOptions, TMeta> & MergePluginResults<TPlugins>["write"];
210
195
  type WriteApiClient<TSchema, TDefaultError> = WriteSelectorClient<TSchema, TDefaultError>;
211
196
 
212
- type TagModeInArray = "all" | "self";
213
- type AnyInfiniteRequestOptions = {
214
- query?: Record<string, unknown>;
215
- params?: Record<string, string | number>;
216
- body?: unknown;
197
+ type TriggerAwaitedReturn$1<T> = T extends (...args: any[]) => infer R ? Awaited<R> : never;
198
+ type ExtractInputFromResponse$1<T> = T extends {
199
+ input: infer I;
200
+ } ? I : never;
201
+ type ExtractTriggerQuery<I> = I extends {
202
+ query: infer Q;
203
+ } ? undefined extends Q ? {
204
+ query?: Exclude<Q, undefined>;
205
+ } : {
206
+ query: Q;
207
+ } : unknown;
208
+ type ExtractTriggerBody<I> = I extends {
209
+ body: infer B;
210
+ } ? undefined extends B ? {
211
+ body?: Exclude<B, undefined> | SpooshBody<Exclude<B, undefined>>;
212
+ } : {
213
+ body: B | SpooshBody<B>;
214
+ } : unknown;
215
+ type ExtractTriggerParams<I> = I extends {
216
+ params: infer P;
217
+ } ? {
218
+ params: P;
219
+ } : unknown;
220
+ type QueueTriggerBase = {
221
+ /** Custom ID for this queue item. If not provided, one will be auto-generated. */
222
+ id?: string;
217
223
  };
224
+ type QueueTriggerInput<T> = ExtractInputFromResponse$1<TriggerAwaitedReturn$1<T>> extends infer I ? [I] extends [never] ? QueueTriggerBase : QueueTriggerBase & ExtractTriggerQuery<I> & ExtractTriggerBody<I> & ExtractTriggerParams<I> : QueueTriggerBase;
218
225
  /**
219
- * Context passed to `canFetchNext` and `nextPageRequest` callbacks.
226
+ * Options for useQueue hook.
220
227
  */
221
- type InfiniteNextContext<TData, TRequest> = {
222
- /** The latest fetched response data */
223
- response: TData | undefined;
224
- /** All responses fetched so far */
225
- allResponses: TData[];
226
- /** The current request options (query, params, body) */
227
- request: TRequest;
228
+ interface UseQueueOptions {
229
+ /** Maximum concurrent operations. Defaults to 3. */
230
+ concurrency?: number;
231
+ }
232
+ /**
233
+ * Result returned by useQueue hook.
234
+ *
235
+ * @template TData - The response data type
236
+ * @template TError - The error type
237
+ * @template TTriggerInput - The trigger input type
238
+ * @template TMeta - Plugin-contributed metadata on queue items
239
+ */
240
+ type UseQueueResult<TData, TError, TTriggerInput, TMeta = object> = {
241
+ /** Add item to queue and execute. Returns promise for this item. */
242
+ trigger: (input?: TTriggerInput) => Promise<SpooshResponse<TData, TError>>;
243
+ /** All tasks in queue with their current status */
244
+ tasks: QueueItem<TData, TError, TMeta>[];
245
+ /** Queue statistics (pending/loading/settled/success/failed/total/percentage) */
246
+ stats: QueueStats;
247
+ /** Abort task by ID, or all tasks if no ID */
248
+ abort: (id?: string) => void;
249
+ /** Retry failed task by ID, or all failed if no ID */
250
+ retry: (id?: string) => Promise<void>;
251
+ /** Remove specific task by ID (aborts if active) */
252
+ remove: (id: string) => void;
253
+ /** Remove all settled tasks (success, error, aborted). Keeps pending/running. */
254
+ removeSettled: () => void;
255
+ /** Abort all and clear queue */
256
+ clear: () => void;
228
257
  };
229
258
  /**
230
- * Context passed to `canFetchPrev` and `prevPageRequest` callbacks.
259
+ * API client type for queue selector.
260
+ * Supports all HTTP methods (GET, POST, PUT, PATCH, DELETE).
231
261
  */
232
- type InfinitePrevContext<TData, TRequest> = {
233
- /** The latest fetched response data */
234
- response: TData | undefined;
235
- /** All responses fetched so far */
236
- allResponses: TData[];
237
- /** The current request options (query, params, body) */
238
- request: TRequest;
262
+ type QueueApiClient<TSchema, TDefaultError> = QueueSelectorClient<TSchema, TDefaultError>;
263
+
264
+ type TagModeInArray = "all" | "self";
265
+ type TriggerAwaitedReturn<T> = T extends (...args: never[]) => infer R ? Awaited<R> : never;
266
+ type ExtractInputFromResponse<T> = T extends {
267
+ input: infer I;
268
+ } ? I : never;
269
+ type BaseTriggerOptions = {
270
+ /** Bypass cache and force refetch. Default: true */
271
+ force?: boolean;
239
272
  };
273
+ type PagesTriggerOptions<TReadFn> = ExtractInputFromResponse<TriggerAwaitedReturn<TReadFn>> extends infer I ? [I] extends [never] ? BaseTriggerOptions : ExtractTriggerQuery$2<I> & ExtractTriggerBody$2<I> & ExtractTriggerParams$2<I> & BaseTriggerOptions : BaseTriggerOptions;
240
274
  /**
241
- * Options for `useInfiniteRead` hook.
275
+ * Options for `usePages` hook.
242
276
  *
243
277
  * @template TData - The response data type for each page
244
278
  * @template TItem - The item type after merging all responses
279
+ * @template TError - The error type
245
280
  * @template TRequest - The request options type (query, params, body)
281
+ * @template TMeta - Plugin metadata type
246
282
  */
247
- type BaseInfiniteReadOptions<TData, TItem, TRequest = AnyInfiniteRequestOptions> = {
283
+ type BasePagesOptions<TData, TItem, TError = unknown, TRequest = InfiniteRequestOptions, TMeta = Record<string, unknown>> = {
248
284
  /** Whether to fetch automatically on mount. Default: true */
249
285
  enabled?: boolean;
250
286
  /**
@@ -255,30 +291,66 @@ type BaseInfiniteReadOptions<TData, TItem, TRequest = AnyInfiniteRequestOptions>
255
291
  * - 'none' should only be used as string (use `tags: 'none'` not in array)
256
292
  */
257
293
  tags?: TagMode | (TagModeInArray | (string & {}))[];
258
- /** Callback to determine if there's a next page to fetch */
259
- canFetchNext: (ctx: InfiniteNextContext<TData, TRequest>) => boolean;
260
- /** Callback to build the request options for the next page */
261
- nextPageRequest: (ctx: InfiniteNextContext<TData, TRequest>) => Partial<TRequest>;
262
- /** Callback to merge all responses into a single array of items */
263
- merger: (allResponses: TData[]) => TItem[];
264
- /** Callback to determine if there's a previous page to fetch */
265
- canFetchPrev?: (ctx: InfinitePrevContext<TData, TRequest>) => boolean;
266
- /** Callback to build the request options for the previous page */
267
- prevPageRequest?: (ctx: InfinitePrevContext<TData, TRequest>) => Partial<TRequest>;
294
+ /**
295
+ * Callback to determine if there's a next page to fetch.
296
+ * Receives the last page to check for pagination indicators.
297
+ * Default: `() => false` (no next page fetching)
298
+ *
299
+ * @example
300
+ * ```ts
301
+ * canFetchNext: ({ lastPage }) => lastPage?.data?.nextCursor != null
302
+ * ```
303
+ */
304
+ canFetchNext?: (ctx: InfiniteNextContext<TData, TError, TRequest, TMeta>) => boolean;
305
+ /**
306
+ * Callback to build the request options for the next page.
307
+ * Return only the fields that change - they will be **merged** with the initial request.
308
+ * Default: `() => ({})` (no changes to request)
309
+ *
310
+ * @example
311
+ * ```ts
312
+ * // Initial: { query: { cursor: 0, limit: 10 } }
313
+ * // Only return cursor - limit is preserved automatically
314
+ * nextPageRequest: ({ lastPage }) => ({
315
+ * query: { cursor: lastPage?.data?.nextCursor }
316
+ * })
317
+ * ```
318
+ */
319
+ nextPageRequest?: (ctx: InfiniteNextContext<TData, TError, TRequest, TMeta>) => Partial<TRequest>;
320
+ /**
321
+ * Callback to merge all pages into a single array of items.
322
+ *
323
+ * @example
324
+ * ```ts
325
+ * merger: (pages) => pages.flatMap(p => p.data?.items ?? [])
326
+ * ```
327
+ */
328
+ merger: (pages: InfinitePage<TData, TError, TMeta>[]) => TItem[];
329
+ /**
330
+ * Callback to determine if there's a previous page to fetch.
331
+ * Receives the first page to check for pagination indicators.
332
+ */
333
+ canFetchPrev?: (ctx: InfinitePrevContext<TData, TError, TRequest, TMeta>) => boolean;
334
+ /**
335
+ * Callback to build the request options for the previous page.
336
+ * Return only the fields that change - they will be **merged** with the initial request.
337
+ */
338
+ prevPageRequest?: (ctx: InfinitePrevContext<TData, TError, TRequest, TMeta>) => Partial<TRequest>;
268
339
  };
269
340
  /**
270
- * Result returned by `useInfiniteRead` hook.
341
+ * Result returned by `usePages` hook.
271
342
  *
272
343
  * @template TData - The response data type for each page
273
344
  * @template TError - The error type
274
345
  * @template TItem - The item type after merging all responses
275
346
  * @template TPluginResult - Plugin-provided result fields
347
+ * @template TTriggerOptions - Options that can be passed to trigger()
276
348
  */
277
- type BaseInfiniteReadResult<TData, TError, TItem, TPluginResult = Record<string, unknown>> = {
278
- /** Merged items from all fetched responses */
349
+ type BasePagesResult<TData, TError, TItem, TPluginResult = Record<string, unknown>, TTriggerOptions = object> = {
350
+ /** Merged items from all fetched pages */
279
351
  data: TItem[] | undefined;
280
- /** Array of all raw response data */
281
- allResponses: TData[] | undefined;
352
+ /** Array of all pages with status, data, error, and meta per page */
353
+ pages: InfinitePage<TData, TError, TPluginResult>[];
282
354
  /** True during the initial load (no data yet) */
283
355
  loading: boolean;
284
356
  /** True during any fetch operation */
@@ -291,26 +363,27 @@ type BaseInfiniteReadResult<TData, TError, TItem, TPluginResult = Record<string,
291
363
  canFetchNext: boolean;
292
364
  /** Whether there's a previous page available to fetch */
293
365
  canFetchPrev: boolean;
294
- /** Plugin-provided metadata */
295
- meta: TPluginResult;
296
366
  /** Fetch the next page */
297
367
  fetchNext: () => Promise<void>;
298
368
  /** Fetch the previous page */
299
369
  fetchPrev: () => Promise<void>;
300
- /** Trigger refetch of all pages from the beginning */
301
- trigger: () => Promise<void>;
370
+ /** Trigger refetch of all pages from the beginning, optionally with new request options */
371
+ trigger: (options?: TTriggerOptions) => Promise<void>;
302
372
  /** Abort the current fetch operation */
303
373
  abort: () => void;
304
374
  /** Error from the last failed request */
305
375
  error: TError | undefined;
306
376
  };
307
- type UseInfiniteReadResult<TData, TError, TItem, TPlugins extends readonly SpooshPlugin<PluginTypeConfig>[]> = BaseInfiniteReadResult<TData, TError, TItem> & MergePluginResults<TPlugins>["read"];
308
- type InfiniteReadApiClient<TSchema, TDefaultError> = ReadClient<TSchema, TDefaultError>;
377
+ type UsePagesResult<TData, TError, TItem, TPlugins extends readonly SpooshPlugin<PluginTypeConfig>[], TTriggerOptions = object> = BasePagesResult<TData, TError, TItem, MergePluginResults<TPlugins>["read"], TTriggerOptions>;
378
+ type PagesApiClient<TSchema, TDefaultError> = ReadClient<TSchema, TDefaultError>;
309
379
 
310
380
  type InferError<T, TDefaultError> = [T] extends [unknown] ? TDefaultError : T;
311
381
  type WriteResolverContext<TSchema, TMethod, TDefaultError> = ResolverContext<TSchema, ExtractMethodData<TMethod>, InferError<ExtractMethodError<TMethod>, TDefaultError>, ExtractMethodQuery<TMethod>, ExtractMethodBody<TMethod>, ExtractResponseParamNames<TMethod> extends never ? never : Record<ExtractResponseParamNames<TMethod>, string | number>>;
312
382
  type ResolvedWriteOptions<TSchema, TPlugins extends PluginArray, TMethod, TDefaultError> = ResolveTypes<MergePluginOptions<TPlugins>["write"], WriteResolverContext<TSchema, TMethod, TDefaultError>>;
313
383
  type ResolvedWriteTriggerOptions<TSchema, TPlugins extends PluginArray, TMethod, TDefaultError> = ResolveTypes<MergePluginOptions<TPlugins>["writeTrigger"], WriteResolverContext<TSchema, TMethod, TDefaultError>>;
384
+ type QueueResolverContext<TSchema, TMethod, TDefaultError> = ResolverContext<TSchema, ExtractMethodData<TMethod>, InferError<ExtractMethodError<TMethod>, TDefaultError>, ExtractMethodQuery<TMethod>, ExtractMethodBody<TMethod>, ExtractResponseParamNames<TMethod> extends never ? never : Record<ExtractResponseParamNames<TMethod>, string | number>>;
385
+ type ResolvedQueueOptions<TSchema, TPlugins extends PluginArray, TMethod, TDefaultError> = ResolveTypes<MergePluginOptions<TPlugins>["queue"], QueueResolverContext<TSchema, TMethod, TDefaultError>>;
386
+ type ResolvedQueueTriggerOptions<TSchema, TPlugins extends PluginArray, TMethod, TDefaultError> = ResolveTypes<MergePluginOptions<TPlugins>["queueTrigger"], QueueResolverContext<TSchema, TMethod, TDefaultError>>;
314
387
  type UseReadFn<TDefaultError, TSchema, TPlugins extends PluginArray> = {
315
388
  <TReadFn extends (api: ReadApiClient<TSchema, TDefaultError>) => Promise<SpooshResponse<unknown, unknown>>, TReadOpts>(readFn: TReadFn, readOptions: TReadOpts & BaseReadOptions & ResolveTypes<MergePluginOptions<TPlugins>["read"], ResolverContext<TSchema, ExtractMethodData<TReadFn>, InferError<ExtractMethodError<TReadFn>, TDefaultError>, ExtractResponseQuery<TReadFn>, ExtractResponseBody<TReadFn>, ExtractResponseParamNames<TReadFn> extends never ? never : Record<ExtractResponseParamNames<TReadFn>, string | number>>>): BaseReadResult<ExtractMethodData<TReadFn>, InferError<ExtractMethodError<TReadFn>, TDefaultError>, ResolveResultTypes<MergePluginResults<TPlugins>["read"], TReadOpts>, TriggerOptions<TReadFn>> & ResponseInputFields<ExtractResponseQuery<TReadFn>, ExtractResponseBody<TReadFn>, ExtractResponseParamNames<TReadFn>>;
316
389
  <TReadFn extends (api: ReadApiClient<TSchema, TDefaultError>) => Promise<SpooshResponse<unknown, unknown>>>(readFn: TReadFn): BaseReadResult<ExtractMethodData<TReadFn>, InferError<ExtractMethodError<TReadFn>, TDefaultError>, MergePluginResults<TPlugins>["read"], TriggerOptions<TReadFn>> & ResponseInputFields<ExtractResponseQuery<TReadFn>, ExtractResponseBody<TReadFn>, ExtractResponseParamNames<TReadFn>>;
@@ -318,17 +391,12 @@ type UseReadFn<TDefaultError, TSchema, TPlugins extends PluginArray> = {
318
391
  type UseWriteFn<TDefaultError, TSchema, TPlugins extends PluginArray> = {
319
392
  <TWriteFn extends (api: WriteApiClient<TSchema, TDefaultError>) => Promise<SpooshResponse<unknown, unknown>>, TWriteOpts extends ResolvedWriteOptions<TSchema, TPlugins, TWriteFn, TDefaultError> = ResolvedWriteOptions<TSchema, TPlugins, TWriteFn, TDefaultError>>(writeFn: TWriteFn, writeOptions?: TWriteOpts): BaseWriteResult<ExtractMethodData<TWriteFn>, InferError<ExtractMethodError<TWriteFn>, TDefaultError>, WriteTriggerInput<TWriteFn> & ResolvedWriteTriggerOptions<TSchema, TPlugins, TWriteFn, TDefaultError>, ResolveResultTypes<MergePluginResults<TPlugins>["write"], TWriteOpts>> & WriteResponseInputFields<ExtractMethodQuery<TWriteFn>, ExtractMethodBody<TWriteFn>, ExtractResponseParamNames<TWriteFn>>;
320
393
  };
321
- type InfiniteReadResolverContext<TSchema, TData, TError, TRequest> = ResolverContext<TSchema, TData, TError, TRequest extends {
322
- query: infer Q;
323
- } ? Q : never, TRequest extends {
324
- body: infer B;
325
- } ? B : never, TRequest extends {
326
- params: infer P;
327
- } ? P : never>;
328
- type ResolvedInfiniteReadOptions<TSchema, TPlugins extends PluginArray, TData, TError, TRequest> = ResolveTypes<MergePluginOptions<TPlugins>["infiniteRead"], InfiniteReadResolverContext<TSchema, TData, TError, TRequest>>;
329
- type UseInfiniteReadFn<TDefaultError, TSchema, TPlugins extends PluginArray> = <TData, TItem, TError = TDefaultError, TRequest extends AnyInfiniteRequestOptions = AnyInfiniteRequestOptions>(readFn: (api: InfiniteReadApiClient<TSchema, TDefaultError>) => Promise<SpooshResponse<TData, TError>>, readOptions: BaseInfiniteReadOptions<TData, TItem, TRequest> & ResolvedInfiniteReadOptions<TSchema, TPlugins, TData, TError, TRequest>) => BaseInfiniteReadResult<TData, TError, TItem, MergePluginResults<TPlugins>["read"]>;
394
+ type UseQueueFn<TDefaultError, TSchema, TPlugins extends PluginArray> = {
395
+ <TQueueFn extends (api: QueueApiClient<TSchema, TDefaultError>) => Promise<SpooshResponse<unknown, unknown>>>(queueFn: TQueueFn, queueOptions?: ResolvedQueueOptions<TSchema, TPlugins, TQueueFn, TDefaultError> & UseQueueOptions): UseQueueResult<ExtractMethodData<TQueueFn>, InferError<ExtractMethodError<TQueueFn>, TDefaultError>, QueueTriggerInput<TQueueFn> & ResolvedQueueTriggerOptions<TSchema, TPlugins, TQueueFn, TDefaultError>, ResolveResultTypes<MergePluginResults<TPlugins>["queue"], ResolvedQueueOptions<TSchema, TPlugins, TQueueFn, TDefaultError> & UseQueueOptions>>;
396
+ };
397
+ type UsePagesFn<TDefaultError, TSchema, TPlugins extends PluginArray> = <TReadFn extends (api: PagesApiClient<TSchema, TDefaultError>) => Promise<SpooshResponse<unknown, unknown>>, TRequest extends InfiniteRequestOptions = InfiniteRequestOptions, TItem = unknown>(readFn: TReadFn, readOptions: BasePagesOptions<ExtractMethodData<TReadFn>, TItem, InferError<ExtractMethodError<TReadFn>, TDefaultError>, TRequest, MergePluginResults<TPlugins>["read"]> & ResolveTypes<MergePluginOptions<TPlugins>["pages"], ResolverContext<TSchema, ExtractMethodData<TReadFn>, InferError<ExtractMethodError<TReadFn>, TDefaultError>>>) => BasePagesResult<ExtractMethodData<TReadFn>, InferError<ExtractMethodError<TReadFn>, TDefaultError>, TItem, MergePluginResults<TPlugins>["read"], PagesTriggerOptions<TReadFn>>;
330
398
  /**
331
- * Spoosh React hooks interface containing useRead, useWrite, and useInfiniteRead.
399
+ * Spoosh React hooks interface containing useRead, useWrite, and usePages.
332
400
  *
333
401
  * @template TDefaultError - The default error type
334
402
  * @template TSchema - The API schema type
@@ -375,21 +443,41 @@ type SpooshReactHooks<TDefaultError, TSchema, TPlugins extends PluginArray> = {
375
443
  *
376
444
  * @param readFn - Function that selects the API endpoint to call
377
445
  * @param readOptions - Configuration including `canFetchNext`, `nextPageRequest`, `merger`, and optional `canFetchPrev`/`prevPageRequest`
378
- * @returns Object containing `data`, `allResponses`, `fetchNext`, `fetchPrev`, `canFetchNext`, `canFetchPrev`, `loading`, `fetching`, and pagination states
446
+ * @returns Object containing `data`, `pages`, `fetchNext`, `fetchPrev`, `canFetchNext`, `canFetchPrev`, `loading`, `fetching`, and pagination states
379
447
  *
380
448
  * @example
381
449
  * ```tsx
382
- * const { data, fetchNext, canFetchNext, loading } = useInfiniteRead(
450
+ * const { data, fetchNext, canFetchNext, loading } = usePages(
383
451
  * (api) => api("posts").GET(),
384
452
  * {
385
- * canFetchNext: ({ response }) => !!response?.nextCursor,
386
- * nextPageRequest: ({ response }) => ({ query: { cursor: response?.nextCursor } }),
387
- * merger: (responses) => responses.flatMap(r => r.items)
453
+ * canFetchNext: ({ lastPage }) => !!lastPage?.data?.nextCursor,
454
+ * nextPageRequest: ({ lastPage }) => ({ query: { cursor: lastPage?.data?.nextCursor } }),
455
+ * merger: (pages) => pages.flatMap(p => p.data?.items ?? [])
388
456
  * }
389
457
  * );
390
458
  * ```
391
459
  */
392
- useInfiniteRead: UseInfiniteReadFn<TDefaultError, TSchema, TPlugins>;
460
+ usePages: UsePagesFn<TDefaultError, TSchema, TPlugins>;
461
+ /**
462
+ * React hook for queued operations with concurrency control.
463
+ *
464
+ * @param queueFn - Function that selects the API endpoint
465
+ * @param queueOptions - Optional configuration including `concurrency`
466
+ * @returns Object containing `trigger`, `queue`, `progress`, `abort`, `retry`, `remove`, `clear`
467
+ *
468
+ * @example
469
+ * ```tsx
470
+ * const { trigger, queue, progress } = useQueue(
471
+ * (api) => api("uploads").POST(),
472
+ * { concurrency: 2 }
473
+ * );
474
+ *
475
+ * for (const file of files) {
476
+ * trigger({ body: form({ file }) });
477
+ * }
478
+ * ```
479
+ */
480
+ useQueue: UseQueueFn<TDefaultError, TSchema, TPlugins>;
393
481
  } & MergePluginInstanceApi<TPlugins, TSchema>;
394
482
  /**
395
483
  * Shape of a Spoosh instance required for creating React hooks.
@@ -412,18 +500,18 @@ type SpooshInstanceShape<TApi, TSchema, TDefaultError, TPlugins> = {
412
500
  };
413
501
 
414
502
  /**
415
- * Creates React hooks (useRead, useWrite, useInfiniteRead) from a Spoosh instance.
503
+ * Creates React hooks (useRead, useWrite, usePages) from a Spoosh instance.
416
504
  *
417
505
  * @template TSchema - The API schema type
418
506
  * @template TDefaultError - The default error type
419
507
  * @template TPlugins - The plugins array type
420
508
  * @template TApi - The API type
421
509
  * @param instance - The Spoosh instance containing api, stateManager, eventEmitter, and pluginExecutor
422
- * @returns An object containing useRead, useWrite, useInfiniteRead hooks and plugin instance APIs
510
+ * @returns An object containing useRead, useWrite, usePages hooks and plugin instance APIs
423
511
  *
424
512
  * @example
425
513
  * ```ts
426
- * const { useRead, useWrite, useInfiniteRead } = create(spooshInstance);
514
+ * const { useRead, useWrite, usePages } = create(spooshInstance);
427
515
  *
428
516
  * function MyComponent() {
429
517
  * const { data, loading } = useRead((api) => api("posts").GET());
@@ -439,4 +527,4 @@ type PluginHooksConfig<TPlugins extends readonly SpooshPlugin<PluginTypeConfig>[
439
527
  plugins: TPlugins;
440
528
  };
441
529
 
442
- export { type AnyInfiniteRequestOptions, type BaseInfiniteReadOptions, type BaseInfiniteReadResult, type BaseReadOptions, type BaseReadResult, type BaseWriteResult, type ExtractCoreMethodOptions, type ExtractMethodBody, type ExtractMethodData, type ExtractMethodError, type ExtractMethodOptions, type ExtractMethodQuery, type ExtractResponseBody, type ExtractResponseParamNames, type ExtractResponseQuery, type ExtractResponseRequestOptions, type InfiniteNextContext, type InfinitePrevContext, type InfiniteReadApiClient, type PluginHooksConfig, type ReadApiClient, type ResponseInputFields, type SpooshReactHooks, type TriggerOptions, type UseInfiniteReadResult, type UseReadResult, type UseWriteResult, type WriteApiClient, type WriteResponseInputFields, create };
530
+ export { type BasePagesOptions, type BasePagesResult, type BaseReadOptions, type BaseReadResult, type BaseWriteResult, type ExtractCoreMethodOptions, type ExtractMethodBody, type ExtractMethodData, type ExtractMethodError, type ExtractMethodOptions, type ExtractMethodQuery, type ExtractResponseBody, type ExtractResponseParamNames, type ExtractResponseQuery, type ExtractResponseRequestOptions, type PagesApiClient, type PagesTriggerOptions, type PluginHooksConfig, type QueueApiClient, type QueueTriggerInput, type ReadApiClient, type ResponseInputFields, type SpooshReactHooks, type TriggerOptions, type UsePagesResult, type UseQueueOptions, type UseQueueResult, type UseReadResult, type UseWriteResult, type WriteApiClient, type WriteResponseInputFields, create };