@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.js CHANGED
@@ -404,12 +404,12 @@ function createUseWrite(options) {
404
404
  return useWrite;
405
405
  }
406
406
 
407
- // src/useInfiniteRead/index.ts
407
+ // src/usePages/index.ts
408
408
  var import_react3 = require("react");
409
409
  var import_core3 = require("@spoosh/core");
410
- function createUseInfiniteRead(options) {
410
+ function createUsePages(options) {
411
411
  const { api, stateManager, eventEmitter, pluginExecutor } = options;
412
- return function useInfiniteRead(readFn, readOptions) {
412
+ return function usePages(readFn, readOptions) {
413
413
  const {
414
414
  enabled = true,
415
415
  tags,
@@ -432,7 +432,7 @@ function createUseInfiniteRead(options) {
432
432
  const capturedCall = selectorResultRef.current.call;
433
433
  if (!capturedCall) {
434
434
  throw new Error(
435
- 'useInfiniteRead requires calling an HTTP method (GET). Example: useInfiniteRead((api) => api("posts").GET())'
435
+ 'usePages requires calling an HTTP method (GET). Example: usePages((api) => api("posts").GET())'
436
436
  );
437
437
  }
438
438
  const requestOptions = capturedCall.options;
@@ -442,12 +442,6 @@ function createUseInfiniteRead(options) {
442
442
  params: requestOptions?.params,
443
443
  body: requestOptions?.body
444
444
  };
445
- const baseOptionsForKey = {
446
- ...capturedCall.options,
447
- query: void 0,
448
- params: void 0,
449
- body: void 0
450
- };
451
445
  const resolvedPath = (0, import_core3.resolvePath)(pathSegments, requestOptions?.params);
452
446
  const resolvedTags = (0, import_core3.resolveTags)({ tags }, resolvedPath);
453
447
  const canFetchNextRef = (0, import_react3.useRef)(canFetchNext);
@@ -463,22 +457,31 @@ function createUseInfiniteRead(options) {
463
457
  const queryKey = stateManager.createQueryKey({
464
458
  path: capturedCall.path,
465
459
  method: capturedCall.method,
466
- options: baseOptionsForKey
460
+ options: capturedCall.options
461
+ });
462
+ const lifecycleRef = (0, import_react3.useRef)({
463
+ initialized: false,
464
+ prevContext: null,
465
+ lastQueryKey: null
467
466
  });
468
467
  const controllerRef = (0, import_react3.useRef)(null);
469
- if (!controllerRef.current || controllerRef.current.queryKey !== queryKey) {
468
+ const queryKeyChanged = controllerRef.current !== null && controllerRef.current.queryKey !== queryKey;
469
+ if (queryKeyChanged) {
470
+ lifecycleRef.current.prevContext = controllerRef.current.controller.getContext();
471
+ lifecycleRef.current.initialized = false;
472
+ }
473
+ if (!controllerRef.current || queryKeyChanged) {
470
474
  controllerRef.current = {
471
475
  controller: (0, import_core3.createInfiniteReadController)({
472
476
  path: capturedCall.path,
473
477
  method: capturedCall.method,
474
478
  tags: resolvedTags,
475
479
  initialRequest,
476
- baseOptionsForKey,
477
- canFetchNext: (ctx) => canFetchNextRef.current(ctx),
480
+ canFetchNext: canFetchNext ? (ctx) => canFetchNextRef.current?.(ctx) ?? false : void 0,
478
481
  canFetchPrev: canFetchPrev ? (ctx) => canFetchPrevRef.current?.(ctx) ?? false : void 0,
479
- nextPageRequest: (ctx) => nextPageRequestRef.current(ctx),
482
+ nextPageRequest: nextPageRequest ? (ctx) => nextPageRequestRef.current?.(ctx) ?? {} : void 0,
480
483
  prevPageRequest: prevPageRequest ? (ctx) => prevPageRequestRef.current?.(ctx) ?? {} : void 0,
481
- merger: (responses) => mergerRef.current(responses),
484
+ merger: (pages) => mergerRef.current(pages),
482
485
  stateManager,
483
486
  eventEmitter,
484
487
  pluginExecutor,
@@ -488,9 +491,7 @@ function createUseInfiniteRead(options) {
488
491
  const method = pathMethods[capturedCall.method];
489
492
  const fetchOptions = {
490
493
  ...capturedCall.options,
491
- query: opts.query,
492
- params: opts.params,
493
- body: opts.body,
494
+ ...opts,
494
495
  signal
495
496
  };
496
497
  return method(fetchOptions);
@@ -501,11 +502,12 @@ function createUseInfiniteRead(options) {
501
502
  }
502
503
  const controller = controllerRef.current.controller;
503
504
  controller.setPluginOptions(pluginOpts);
504
- const state = (0, import_react3.useSyncExternalStore)(
505
- controller.subscribe,
506
- controller.getState,
507
- controller.getState
505
+ const subscribe = (0, import_react3.useCallback)(
506
+ (callback) => controller.subscribe(callback),
507
+ [controller]
508
508
  );
509
+ const getSnapshot = (0, import_react3.useCallback)(() => controller.getState(), [controller]);
510
+ const state = (0, import_react3.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
509
511
  const [isPending, setIsPending] = (0, import_react3.useState)(() => {
510
512
  return enabled && state.data === void 0;
511
513
  });
@@ -515,10 +517,6 @@ function createUseInfiniteRead(options) {
515
517
  const fetchingPrev = fetchingDirection === "prev";
516
518
  const hasData = state.data !== void 0;
517
519
  const loading = (isPending || fetching) && !hasData;
518
- const lifecycleRef = (0, import_react3.useRef)({
519
- initialized: false,
520
- prevContext: null
521
- });
522
520
  const tagsKey = JSON.stringify(tags);
523
521
  (0, import_react3.useEffect)(() => {
524
522
  return () => {
@@ -527,8 +525,27 @@ function createUseInfiniteRead(options) {
527
525
  };
528
526
  }, []);
529
527
  (0, import_react3.useEffect)(() => {
530
- controller.mount();
531
- lifecycleRef.current.initialized = true;
528
+ if (!enabled) return;
529
+ const { initialized, prevContext, lastQueryKey } = lifecycleRef.current;
530
+ const isQueryKeyChange = lastQueryKey !== null && lastQueryKey !== queryKey;
531
+ if (!initialized) {
532
+ controller.mount();
533
+ lifecycleRef.current.initialized = true;
534
+ if (prevContext) {
535
+ controller.update(prevContext);
536
+ lifecycleRef.current.prevContext = null;
537
+ }
538
+ }
539
+ lifecycleRef.current.lastQueryKey = queryKey;
540
+ const currentState = controller.getState();
541
+ const isFetching = controller.getFetchingDirection() !== null;
542
+ if (isQueryKeyChange) {
543
+ setIsPending(true);
544
+ controller.trigger({ force: false }).finally(() => setIsPending(false));
545
+ } else if (currentState.data === void 0 && !isFetching) {
546
+ setIsPending(true);
547
+ controller.fetchNext().finally(() => setIsPending(false));
548
+ }
532
549
  const unsubInvalidate = eventEmitter.on(
533
550
  "invalidate",
534
551
  (invalidatedTags) => {
@@ -537,41 +554,32 @@ function createUseInfiniteRead(options) {
537
554
  );
538
555
  if (hasMatch) {
539
556
  setIsPending(true);
540
- controller.refetch().finally(() => setIsPending(false));
557
+ controller.trigger().finally(() => setIsPending(false));
541
558
  }
542
559
  }
543
560
  );
544
561
  const unsubRefetchAll = eventEmitter.on("refetchAll", () => {
545
562
  setIsPending(true);
546
- controller.refetch().finally(() => setIsPending(false));
563
+ controller.trigger().finally(() => setIsPending(false));
547
564
  });
548
565
  return () => {
566
+ controller.unmount();
549
567
  unsubInvalidate();
550
568
  unsubRefetchAll();
551
569
  };
552
- }, [tagsKey]);
553
- (0, import_react3.useEffect)(() => {
554
- if (!lifecycleRef.current.initialized) return;
555
- if (enabled) {
556
- const currentState = controller.getState();
557
- const isFetching = controller.getFetchingDirection() !== null;
558
- if (currentState.data === void 0 && !isFetching) {
559
- setIsPending(true);
560
- controller.fetchNext().finally(() => setIsPending(false));
561
- }
562
- }
563
- }, [enabled]);
570
+ }, [queryKey, enabled, tagsKey]);
571
+ const pluginOptsKey = JSON.stringify(pluginOpts);
564
572
  (0, import_react3.useEffect)(() => {
565
573
  if (!enabled || !lifecycleRef.current.initialized) return;
566
574
  const prevContext = controller.getContext();
567
575
  controller.update(prevContext);
568
- }, [JSON.stringify(pluginOpts)]);
569
- const entry = stateManager.getCache(queryKey);
570
- const pluginResultData = entry?.meta ? Object.fromEntries(entry.meta) : {};
576
+ }, [pluginOptsKey]);
577
+ const trigger = async (options2) => {
578
+ await controller.trigger(options2);
579
+ };
571
580
  const result = {
572
- meta: pluginResultData,
573
581
  data: state.data,
574
- allResponses: state.allResponses,
582
+ pages: state.pages,
575
583
  loading,
576
584
  fetching,
577
585
  fetchingNext,
@@ -580,7 +588,7 @@ function createUseInfiniteRead(options) {
580
588
  canFetchPrev: state.canFetchPrev,
581
589
  fetchNext: controller.fetchNext,
582
590
  fetchPrev: controller.fetchPrev,
583
- trigger: controller.refetch,
591
+ trigger,
584
592
  abort: controller.abort,
585
593
  error: state.error
586
594
  };
@@ -588,6 +596,72 @@ function createUseInfiniteRead(options) {
588
596
  };
589
597
  }
590
598
 
599
+ // src/useQueue/index.ts
600
+ var import_react4 = require("react");
601
+ var import_core4 = require("@spoosh/core");
602
+ function createUseQueue(options) {
603
+ const { api, stateManager, pluginExecutor, eventEmitter } = options;
604
+ function useQueue(queueFn, queueOptions) {
605
+ (0, import_react4.useId)();
606
+ const selectorResultRef = (0, import_react4.useRef)({
607
+ call: null,
608
+ selector: null
609
+ });
610
+ const selectorProxy = (0, import_core4.createSelectorProxy)((result) => {
611
+ selectorResultRef.current = result;
612
+ });
613
+ queueFn(selectorProxy);
614
+ const capturedCall = selectorResultRef.current.call;
615
+ const capturedSelector = selectorResultRef.current.selector;
616
+ const captured = capturedCall ?? capturedSelector;
617
+ if (!captured) {
618
+ throw new Error(
619
+ 'useQueue requires selecting an HTTP method. Example: useQueue((api) => api("uploads").POST())'
620
+ );
621
+ }
622
+ const { concurrency, ...hookOptions } = queueOptions ?? {};
623
+ const controllerRef = (0, import_react4.useRef)(null);
624
+ if (!controllerRef.current) {
625
+ const config = {
626
+ path: captured.path,
627
+ method: captured.method,
628
+ concurrency,
629
+ operationType: "queue",
630
+ hookOptions
631
+ };
632
+ controllerRef.current = (0, import_core4.createQueueController)(config, {
633
+ api,
634
+ stateManager,
635
+ eventEmitter,
636
+ pluginExecutor
637
+ });
638
+ }
639
+ const controller = controllerRef.current;
640
+ (0, import_react4.useEffect)(() => {
641
+ if (concurrency !== void 0) {
642
+ controller.setConcurrency(concurrency);
643
+ }
644
+ }, [concurrency, controller]);
645
+ const tasks = (0, import_react4.useSyncExternalStore)(
646
+ controller.subscribe,
647
+ controller.getQueue,
648
+ controller.getQueue
649
+ );
650
+ return {
651
+ trigger: (input) => controller.trigger(input ?? {}),
652
+ tasks,
653
+ stats: controller.getStats(),
654
+ abort: controller.abort,
655
+ retry: controller.retry,
656
+ remove: controller.remove,
657
+ removeSettled: controller.removeSettled,
658
+ clear: controller.clear
659
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
660
+ };
661
+ }
662
+ return useQueue;
663
+ }
664
+
591
665
  // src/create/index.ts
592
666
  function create(instance) {
593
667
  const { api, stateManager, eventEmitter, pluginExecutor } = instance;
@@ -603,7 +677,13 @@ function create(instance) {
603
677
  eventEmitter,
604
678
  pluginExecutor
605
679
  });
606
- const useInfiniteRead = createUseInfiniteRead({
680
+ const usePages = createUsePages({
681
+ api,
682
+ stateManager,
683
+ eventEmitter,
684
+ pluginExecutor
685
+ });
686
+ const useQueue = createUseQueue({
607
687
  api,
608
688
  stateManager,
609
689
  eventEmitter,
@@ -636,7 +716,8 @@ function create(instance) {
636
716
  return {
637
717
  useRead,
638
718
  useWrite,
639
- useInfiniteRead,
719
+ usePages,
720
+ useQueue,
640
721
  ...instanceApis
641
722
  };
642
723
  }
package/dist/index.mjs CHANGED
@@ -402,13 +402,14 @@ function createUseWrite(options) {
402
402
  return useWrite;
403
403
  }
404
404
 
405
- // src/useInfiniteRead/index.ts
405
+ // src/usePages/index.ts
406
406
  import {
407
407
  useRef as useRef3,
408
408
  useEffect as useEffect2,
409
409
  useSyncExternalStore as useSyncExternalStore3,
410
410
  useId as useId3,
411
- useState as useState3
411
+ useState as useState3,
412
+ useCallback as useCallback3
412
413
  } from "react";
413
414
  import {
414
415
  createInfiniteReadController,
@@ -416,9 +417,9 @@ import {
416
417
  resolvePath as resolvePath3,
417
418
  resolveTags as resolveTags3
418
419
  } from "@spoosh/core";
419
- function createUseInfiniteRead(options) {
420
+ function createUsePages(options) {
420
421
  const { api, stateManager, eventEmitter, pluginExecutor } = options;
421
- return function useInfiniteRead(readFn, readOptions) {
422
+ return function usePages(readFn, readOptions) {
422
423
  const {
423
424
  enabled = true,
424
425
  tags,
@@ -441,7 +442,7 @@ function createUseInfiniteRead(options) {
441
442
  const capturedCall = selectorResultRef.current.call;
442
443
  if (!capturedCall) {
443
444
  throw new Error(
444
- 'useInfiniteRead requires calling an HTTP method (GET). Example: useInfiniteRead((api) => api("posts").GET())'
445
+ 'usePages requires calling an HTTP method (GET). Example: usePages((api) => api("posts").GET())'
445
446
  );
446
447
  }
447
448
  const requestOptions = capturedCall.options;
@@ -451,12 +452,6 @@ function createUseInfiniteRead(options) {
451
452
  params: requestOptions?.params,
452
453
  body: requestOptions?.body
453
454
  };
454
- const baseOptionsForKey = {
455
- ...capturedCall.options,
456
- query: void 0,
457
- params: void 0,
458
- body: void 0
459
- };
460
455
  const resolvedPath = resolvePath3(pathSegments, requestOptions?.params);
461
456
  const resolvedTags = resolveTags3({ tags }, resolvedPath);
462
457
  const canFetchNextRef = useRef3(canFetchNext);
@@ -472,22 +467,31 @@ function createUseInfiniteRead(options) {
472
467
  const queryKey = stateManager.createQueryKey({
473
468
  path: capturedCall.path,
474
469
  method: capturedCall.method,
475
- options: baseOptionsForKey
470
+ options: capturedCall.options
471
+ });
472
+ const lifecycleRef = useRef3({
473
+ initialized: false,
474
+ prevContext: null,
475
+ lastQueryKey: null
476
476
  });
477
477
  const controllerRef = useRef3(null);
478
- if (!controllerRef.current || controllerRef.current.queryKey !== queryKey) {
478
+ const queryKeyChanged = controllerRef.current !== null && controllerRef.current.queryKey !== queryKey;
479
+ if (queryKeyChanged) {
480
+ lifecycleRef.current.prevContext = controllerRef.current.controller.getContext();
481
+ lifecycleRef.current.initialized = false;
482
+ }
483
+ if (!controllerRef.current || queryKeyChanged) {
479
484
  controllerRef.current = {
480
485
  controller: createInfiniteReadController({
481
486
  path: capturedCall.path,
482
487
  method: capturedCall.method,
483
488
  tags: resolvedTags,
484
489
  initialRequest,
485
- baseOptionsForKey,
486
- canFetchNext: (ctx) => canFetchNextRef.current(ctx),
490
+ canFetchNext: canFetchNext ? (ctx) => canFetchNextRef.current?.(ctx) ?? false : void 0,
487
491
  canFetchPrev: canFetchPrev ? (ctx) => canFetchPrevRef.current?.(ctx) ?? false : void 0,
488
- nextPageRequest: (ctx) => nextPageRequestRef.current(ctx),
492
+ nextPageRequest: nextPageRequest ? (ctx) => nextPageRequestRef.current?.(ctx) ?? {} : void 0,
489
493
  prevPageRequest: prevPageRequest ? (ctx) => prevPageRequestRef.current?.(ctx) ?? {} : void 0,
490
- merger: (responses) => mergerRef.current(responses),
494
+ merger: (pages) => mergerRef.current(pages),
491
495
  stateManager,
492
496
  eventEmitter,
493
497
  pluginExecutor,
@@ -497,9 +501,7 @@ function createUseInfiniteRead(options) {
497
501
  const method = pathMethods[capturedCall.method];
498
502
  const fetchOptions = {
499
503
  ...capturedCall.options,
500
- query: opts.query,
501
- params: opts.params,
502
- body: opts.body,
504
+ ...opts,
503
505
  signal
504
506
  };
505
507
  return method(fetchOptions);
@@ -510,11 +512,12 @@ function createUseInfiniteRead(options) {
510
512
  }
511
513
  const controller = controllerRef.current.controller;
512
514
  controller.setPluginOptions(pluginOpts);
513
- const state = useSyncExternalStore3(
514
- controller.subscribe,
515
- controller.getState,
516
- controller.getState
515
+ const subscribe = useCallback3(
516
+ (callback) => controller.subscribe(callback),
517
+ [controller]
517
518
  );
519
+ const getSnapshot = useCallback3(() => controller.getState(), [controller]);
520
+ const state = useSyncExternalStore3(subscribe, getSnapshot, getSnapshot);
518
521
  const [isPending, setIsPending] = useState3(() => {
519
522
  return enabled && state.data === void 0;
520
523
  });
@@ -524,10 +527,6 @@ function createUseInfiniteRead(options) {
524
527
  const fetchingPrev = fetchingDirection === "prev";
525
528
  const hasData = state.data !== void 0;
526
529
  const loading = (isPending || fetching) && !hasData;
527
- const lifecycleRef = useRef3({
528
- initialized: false,
529
- prevContext: null
530
- });
531
530
  const tagsKey = JSON.stringify(tags);
532
531
  useEffect2(() => {
533
532
  return () => {
@@ -536,8 +535,27 @@ function createUseInfiniteRead(options) {
536
535
  };
537
536
  }, []);
538
537
  useEffect2(() => {
539
- controller.mount();
540
- lifecycleRef.current.initialized = true;
538
+ if (!enabled) return;
539
+ const { initialized, prevContext, lastQueryKey } = lifecycleRef.current;
540
+ const isQueryKeyChange = lastQueryKey !== null && lastQueryKey !== queryKey;
541
+ if (!initialized) {
542
+ controller.mount();
543
+ lifecycleRef.current.initialized = true;
544
+ if (prevContext) {
545
+ controller.update(prevContext);
546
+ lifecycleRef.current.prevContext = null;
547
+ }
548
+ }
549
+ lifecycleRef.current.lastQueryKey = queryKey;
550
+ const currentState = controller.getState();
551
+ const isFetching = controller.getFetchingDirection() !== null;
552
+ if (isQueryKeyChange) {
553
+ setIsPending(true);
554
+ controller.trigger({ force: false }).finally(() => setIsPending(false));
555
+ } else if (currentState.data === void 0 && !isFetching) {
556
+ setIsPending(true);
557
+ controller.fetchNext().finally(() => setIsPending(false));
558
+ }
541
559
  const unsubInvalidate = eventEmitter.on(
542
560
  "invalidate",
543
561
  (invalidatedTags) => {
@@ -546,41 +564,32 @@ function createUseInfiniteRead(options) {
546
564
  );
547
565
  if (hasMatch) {
548
566
  setIsPending(true);
549
- controller.refetch().finally(() => setIsPending(false));
567
+ controller.trigger().finally(() => setIsPending(false));
550
568
  }
551
569
  }
552
570
  );
553
571
  const unsubRefetchAll = eventEmitter.on("refetchAll", () => {
554
572
  setIsPending(true);
555
- controller.refetch().finally(() => setIsPending(false));
573
+ controller.trigger().finally(() => setIsPending(false));
556
574
  });
557
575
  return () => {
576
+ controller.unmount();
558
577
  unsubInvalidate();
559
578
  unsubRefetchAll();
560
579
  };
561
- }, [tagsKey]);
562
- useEffect2(() => {
563
- if (!lifecycleRef.current.initialized) return;
564
- if (enabled) {
565
- const currentState = controller.getState();
566
- const isFetching = controller.getFetchingDirection() !== null;
567
- if (currentState.data === void 0 && !isFetching) {
568
- setIsPending(true);
569
- controller.fetchNext().finally(() => setIsPending(false));
570
- }
571
- }
572
- }, [enabled]);
580
+ }, [queryKey, enabled, tagsKey]);
581
+ const pluginOptsKey = JSON.stringify(pluginOpts);
573
582
  useEffect2(() => {
574
583
  if (!enabled || !lifecycleRef.current.initialized) return;
575
584
  const prevContext = controller.getContext();
576
585
  controller.update(prevContext);
577
- }, [JSON.stringify(pluginOpts)]);
578
- const entry = stateManager.getCache(queryKey);
579
- const pluginResultData = entry?.meta ? Object.fromEntries(entry.meta) : {};
586
+ }, [pluginOptsKey]);
587
+ const trigger = async (options2) => {
588
+ await controller.trigger(options2);
589
+ };
580
590
  const result = {
581
- meta: pluginResultData,
582
591
  data: state.data,
583
- allResponses: state.allResponses,
592
+ pages: state.pages,
584
593
  loading,
585
594
  fetching,
586
595
  fetchingNext,
@@ -589,7 +598,7 @@ function createUseInfiniteRead(options) {
589
598
  canFetchPrev: state.canFetchPrev,
590
599
  fetchNext: controller.fetchNext,
591
600
  fetchPrev: controller.fetchPrev,
592
- trigger: controller.refetch,
601
+ trigger,
593
602
  abort: controller.abort,
594
603
  error: state.error
595
604
  };
@@ -597,6 +606,75 @@ function createUseInfiniteRead(options) {
597
606
  };
598
607
  }
599
608
 
609
+ // src/useQueue/index.ts
610
+ import { useSyncExternalStore as useSyncExternalStore4, useRef as useRef4, useId as useId4, useEffect as useEffect3 } from "react";
611
+ import {
612
+ createSelectorProxy as createSelectorProxy4,
613
+ createQueueController
614
+ } from "@spoosh/core";
615
+ function createUseQueue(options) {
616
+ const { api, stateManager, pluginExecutor, eventEmitter } = options;
617
+ function useQueue(queueFn, queueOptions) {
618
+ useId4();
619
+ const selectorResultRef = useRef4({
620
+ call: null,
621
+ selector: null
622
+ });
623
+ const selectorProxy = createSelectorProxy4((result) => {
624
+ selectorResultRef.current = result;
625
+ });
626
+ queueFn(selectorProxy);
627
+ const capturedCall = selectorResultRef.current.call;
628
+ const capturedSelector = selectorResultRef.current.selector;
629
+ const captured = capturedCall ?? capturedSelector;
630
+ if (!captured) {
631
+ throw new Error(
632
+ 'useQueue requires selecting an HTTP method. Example: useQueue((api) => api("uploads").POST())'
633
+ );
634
+ }
635
+ const { concurrency, ...hookOptions } = queueOptions ?? {};
636
+ const controllerRef = useRef4(null);
637
+ if (!controllerRef.current) {
638
+ const config = {
639
+ path: captured.path,
640
+ method: captured.method,
641
+ concurrency,
642
+ operationType: "queue",
643
+ hookOptions
644
+ };
645
+ controllerRef.current = createQueueController(config, {
646
+ api,
647
+ stateManager,
648
+ eventEmitter,
649
+ pluginExecutor
650
+ });
651
+ }
652
+ const controller = controllerRef.current;
653
+ useEffect3(() => {
654
+ if (concurrency !== void 0) {
655
+ controller.setConcurrency(concurrency);
656
+ }
657
+ }, [concurrency, controller]);
658
+ const tasks = useSyncExternalStore4(
659
+ controller.subscribe,
660
+ controller.getQueue,
661
+ controller.getQueue
662
+ );
663
+ return {
664
+ trigger: (input) => controller.trigger(input ?? {}),
665
+ tasks,
666
+ stats: controller.getStats(),
667
+ abort: controller.abort,
668
+ retry: controller.retry,
669
+ remove: controller.remove,
670
+ removeSettled: controller.removeSettled,
671
+ clear: controller.clear
672
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
673
+ };
674
+ }
675
+ return useQueue;
676
+ }
677
+
600
678
  // src/create/index.ts
601
679
  function create(instance) {
602
680
  const { api, stateManager, eventEmitter, pluginExecutor } = instance;
@@ -612,7 +690,13 @@ function create(instance) {
612
690
  eventEmitter,
613
691
  pluginExecutor
614
692
  });
615
- const useInfiniteRead = createUseInfiniteRead({
693
+ const usePages = createUsePages({
694
+ api,
695
+ stateManager,
696
+ eventEmitter,
697
+ pluginExecutor
698
+ });
699
+ const useQueue = createUseQueue({
616
700
  api,
617
701
  stateManager,
618
702
  eventEmitter,
@@ -645,7 +729,8 @@ function create(instance) {
645
729
  return {
646
730
  useRead,
647
731
  useWrite,
648
- useInfiniteRead,
732
+ usePages,
733
+ useQueue,
649
734
  ...instanceApis
650
735
  };
651
736
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spoosh/react",
3
- "version": "0.10.1",
3
+ "version": "0.12.0",
4
4
  "license": "MIT",
5
5
  "description": "React hooks for Spoosh API toolkit",
6
6
  "keywords": [
@@ -34,14 +34,14 @@
34
34
  }
35
35
  },
36
36
  "peerDependencies": {
37
- "@spoosh/core": ">=0.13.2",
37
+ "@spoosh/core": ">=0.15.0",
38
38
  "react": "^18 || ^19"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@testing-library/react": "^16.0.0",
42
42
  "jsdom": "^26.0.0",
43
- "@spoosh/core": "0.13.2",
44
- "@spoosh/test-utils": "0.2.0"
43
+ "@spoosh/core": "0.15.0",
44
+ "@spoosh/test-utils": "0.3.0"
45
45
  },
46
46
  "scripts": {
47
47
  "dev": "tsup --watch",