@spoosh/react 0.11.0 → 0.13.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 +143 -41
- package/dist/index.d.mts +306 -109
- package/dist/index.d.ts +306 -109
- package/dist/index.js +379 -56
- package/dist/index.mjs +390 -57
- package/package.json +11 -4
package/dist/index.mjs
CHANGED
|
@@ -402,13 +402,14 @@ function createUseWrite(options) {
|
|
|
402
402
|
return useWrite;
|
|
403
403
|
}
|
|
404
404
|
|
|
405
|
-
// src/
|
|
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
|
|
420
|
+
function createUsePages(options) {
|
|
420
421
|
const { api, stateManager, eventEmitter, pluginExecutor } = options;
|
|
421
|
-
return function
|
|
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
|
-
'
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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: (
|
|
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
|
-
|
|
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
|
|
514
|
-
controller.subscribe,
|
|
515
|
-
controller
|
|
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
|
-
|
|
540
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
}, [
|
|
578
|
-
const
|
|
579
|
-
|
|
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
|
-
|
|
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
|
|
601
|
+
trigger,
|
|
593
602
|
abort: controller.abort,
|
|
594
603
|
error: state.error
|
|
595
604
|
};
|
|
@@ -666,32 +675,354 @@ function createUseQueue(options) {
|
|
|
666
675
|
return useQueue;
|
|
667
676
|
}
|
|
668
677
|
|
|
678
|
+
// src/useSubscription/index.ts
|
|
679
|
+
import {
|
|
680
|
+
useSyncExternalStore as useSyncExternalStore5,
|
|
681
|
+
useRef as useRef5,
|
|
682
|
+
useEffect as useEffect4,
|
|
683
|
+
useCallback as useCallback4,
|
|
684
|
+
useState as useState4
|
|
685
|
+
} from "react";
|
|
686
|
+
import {
|
|
687
|
+
createSelectorProxy as createSelectorProxy5,
|
|
688
|
+
createSubscriptionController
|
|
689
|
+
} from "@spoosh/core";
|
|
690
|
+
function createUseSubscription(options) {
|
|
691
|
+
const { stateManager, eventEmitter, pluginExecutor } = options;
|
|
692
|
+
function useSubscription(subFn, subOptions) {
|
|
693
|
+
const { enabled = true, adapter, operationType } = subOptions;
|
|
694
|
+
const selectorResultRef = useRef5({
|
|
695
|
+
call: null,
|
|
696
|
+
selector: null
|
|
697
|
+
});
|
|
698
|
+
const selectorProxy = createSelectorProxy5((result) => {
|
|
699
|
+
selectorResultRef.current = result;
|
|
700
|
+
});
|
|
701
|
+
subFn(selectorProxy);
|
|
702
|
+
const capturedCall = selectorResultRef.current.call;
|
|
703
|
+
if (!capturedCall) {
|
|
704
|
+
throw new Error("useSubscription requires calling a method");
|
|
705
|
+
}
|
|
706
|
+
const queryKey = stateManager.createQueryKey({
|
|
707
|
+
path: capturedCall.path,
|
|
708
|
+
method: capturedCall.method,
|
|
709
|
+
options: capturedCall.options
|
|
710
|
+
});
|
|
711
|
+
const controllerRef = useRef5(null);
|
|
712
|
+
const subscriptionVersionRef = useRef5(0);
|
|
713
|
+
const getOrCreateController = useCallback4(() => {
|
|
714
|
+
if (controllerRef.current) {
|
|
715
|
+
return controllerRef.current;
|
|
716
|
+
}
|
|
717
|
+
const controller = createSubscriptionController({
|
|
718
|
+
channel: capturedCall.path,
|
|
719
|
+
baseAdapter: adapter,
|
|
720
|
+
stateManager,
|
|
721
|
+
eventEmitter,
|
|
722
|
+
pluginExecutor,
|
|
723
|
+
queryKey,
|
|
724
|
+
operationType,
|
|
725
|
+
path: capturedCall.path,
|
|
726
|
+
method: capturedCall.method
|
|
727
|
+
});
|
|
728
|
+
controllerRef.current = controller;
|
|
729
|
+
return controller;
|
|
730
|
+
}, [
|
|
731
|
+
queryKey,
|
|
732
|
+
adapter,
|
|
733
|
+
operationType,
|
|
734
|
+
capturedCall.path,
|
|
735
|
+
capturedCall.method
|
|
736
|
+
]);
|
|
737
|
+
const subscribe = useCallback4(
|
|
738
|
+
(callback) => {
|
|
739
|
+
const controller = getOrCreateController();
|
|
740
|
+
return controller.subscribe(callback);
|
|
741
|
+
},
|
|
742
|
+
[getOrCreateController]
|
|
743
|
+
);
|
|
744
|
+
const emptyStateRef = useRef5({
|
|
745
|
+
data: void 0,
|
|
746
|
+
error: void 0,
|
|
747
|
+
isConnected: false
|
|
748
|
+
});
|
|
749
|
+
const getSnapshot = useCallback4(() => {
|
|
750
|
+
if (!controllerRef.current) {
|
|
751
|
+
return emptyStateRef.current;
|
|
752
|
+
}
|
|
753
|
+
return controllerRef.current.getState();
|
|
754
|
+
}, []);
|
|
755
|
+
const state = useSyncExternalStore5(subscribe, getSnapshot, getSnapshot);
|
|
756
|
+
const [isPending, setIsPending] = useState4(enabled);
|
|
757
|
+
useEffect4(() => {
|
|
758
|
+
if (!enabled) {
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
setIsPending(true);
|
|
762
|
+
const controller = getOrCreateController();
|
|
763
|
+
controller.mount();
|
|
764
|
+
controller.subscribe();
|
|
765
|
+
return () => {
|
|
766
|
+
subscriptionVersionRef.current++;
|
|
767
|
+
controller.unsubscribe();
|
|
768
|
+
};
|
|
769
|
+
}, [queryKey, enabled, getOrCreateController]);
|
|
770
|
+
useEffect4(() => {
|
|
771
|
+
if (state.isConnected || state.data !== void 0 || state.error !== void 0) {
|
|
772
|
+
setIsPending(false);
|
|
773
|
+
}
|
|
774
|
+
}, [state.isConnected, state.data, state.error]);
|
|
775
|
+
const disconnect = useCallback4(() => {
|
|
776
|
+
subscriptionVersionRef.current++;
|
|
777
|
+
if (controllerRef.current) {
|
|
778
|
+
controllerRef.current.unsubscribe();
|
|
779
|
+
}
|
|
780
|
+
}, []);
|
|
781
|
+
const trigger = useCallback4(async () => {
|
|
782
|
+
setIsPending(true);
|
|
783
|
+
subscriptionVersionRef.current++;
|
|
784
|
+
const controller = getOrCreateController();
|
|
785
|
+
controller.unsubscribe();
|
|
786
|
+
controller.mount();
|
|
787
|
+
await controller.subscribe();
|
|
788
|
+
}, [getOrCreateController]);
|
|
789
|
+
const loading = isPending;
|
|
790
|
+
return {
|
|
791
|
+
meta: {},
|
|
792
|
+
data: state.data,
|
|
793
|
+
error: state.error,
|
|
794
|
+
loading,
|
|
795
|
+
isConnected: state.isConnected,
|
|
796
|
+
_queryKey: queryKey,
|
|
797
|
+
_subscriptionVersion: subscriptionVersionRef.current,
|
|
798
|
+
trigger,
|
|
799
|
+
disconnect
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
return useSubscription;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// src/useSSE/index.ts
|
|
806
|
+
import { useState as useState5, useRef as useRef6, useEffect as useEffect5, useCallback as useCallback5, useMemo } from "react";
|
|
807
|
+
import { createSelectorProxy as createSelectorProxy6 } from "@spoosh/core";
|
|
808
|
+
import { resolveParser, resolveAccumulator } from "@spoosh/transport-sse";
|
|
809
|
+
function isSSETransport(transport) {
|
|
810
|
+
return "createSubscriptionAdapter" in transport && typeof transport.createSubscriptionAdapter === "function";
|
|
811
|
+
}
|
|
812
|
+
function createUseSSE(options) {
|
|
813
|
+
const { eventEmitter, transports, config } = options;
|
|
814
|
+
const useSubscription = createUseSubscription(options);
|
|
815
|
+
function useSSE(subFn, sseOptions) {
|
|
816
|
+
const {
|
|
817
|
+
enabled = true,
|
|
818
|
+
events,
|
|
819
|
+
parse = "auto",
|
|
820
|
+
accumulate = "replace",
|
|
821
|
+
maxRetries,
|
|
822
|
+
retryDelay
|
|
823
|
+
} = sseOptions ?? {};
|
|
824
|
+
const transport = transports.get("sse");
|
|
825
|
+
if (!transport) {
|
|
826
|
+
throw new Error(
|
|
827
|
+
"SSE transport not registered. Make sure to register an SSE transport before using useSSE."
|
|
828
|
+
);
|
|
829
|
+
}
|
|
830
|
+
if (!isSSETransport(transport)) {
|
|
831
|
+
throw new Error(
|
|
832
|
+
"SSE transport does not implement createSubscriptionAdapter."
|
|
833
|
+
);
|
|
834
|
+
}
|
|
835
|
+
const selectorResultRef = useRef6({
|
|
836
|
+
call: null,
|
|
837
|
+
selector: null
|
|
838
|
+
});
|
|
839
|
+
const selectorProxy = createSelectorProxy6((result) => {
|
|
840
|
+
selectorResultRef.current = result;
|
|
841
|
+
});
|
|
842
|
+
subFn(selectorProxy);
|
|
843
|
+
const capturedCall = selectorResultRef.current.call;
|
|
844
|
+
if (!capturedCall) {
|
|
845
|
+
throw new Error("useSSE requires calling a method");
|
|
846
|
+
}
|
|
847
|
+
const currentOptionsRef = useRef6(
|
|
848
|
+
capturedCall.options
|
|
849
|
+
);
|
|
850
|
+
const adapter = useMemo(
|
|
851
|
+
() => transport.createSubscriptionAdapter({
|
|
852
|
+
channel: capturedCall.path,
|
|
853
|
+
method: capturedCall.method,
|
|
854
|
+
baseUrl: config.baseUrl,
|
|
855
|
+
globalHeaders: config.defaultOptions.headers,
|
|
856
|
+
getRequestOptions: () => currentOptionsRef.current,
|
|
857
|
+
eventEmitter,
|
|
858
|
+
devtoolMeta: events ? { listenedEvents: events } : void 0
|
|
859
|
+
}),
|
|
860
|
+
[capturedCall.path, capturedCall.method]
|
|
861
|
+
);
|
|
862
|
+
const [accumulatedData, setAccumulatedData] = useState5({});
|
|
863
|
+
const eventSet = useMemo(
|
|
864
|
+
() => events ? new Set(events) : null,
|
|
865
|
+
[events?.join(",")]
|
|
866
|
+
);
|
|
867
|
+
const parseRef = useRef6(parse);
|
|
868
|
+
const accumulateRef = useRef6(accumulate);
|
|
869
|
+
parseRef.current = parse;
|
|
870
|
+
accumulateRef.current = accumulate;
|
|
871
|
+
const optionsRef = useRef6({
|
|
872
|
+
maxRetries,
|
|
873
|
+
retryDelay
|
|
874
|
+
});
|
|
875
|
+
optionsRef.current = { maxRetries, retryDelay };
|
|
876
|
+
const subscription = useSubscription(subFn, {
|
|
877
|
+
enabled,
|
|
878
|
+
adapter,
|
|
879
|
+
operationType: transport.operationType
|
|
880
|
+
});
|
|
881
|
+
const prevVersionRef = useRef6(subscription._subscriptionVersion);
|
|
882
|
+
const lastMessageIndexRef = useRef6({});
|
|
883
|
+
useEffect5(() => {
|
|
884
|
+
if (subscription._subscriptionVersion !== prevVersionRef.current) {
|
|
885
|
+
setAccumulatedData({});
|
|
886
|
+
lastMessageIndexRef.current = {};
|
|
887
|
+
}
|
|
888
|
+
prevVersionRef.current = subscription._subscriptionVersion;
|
|
889
|
+
}, [subscription._subscriptionVersion]);
|
|
890
|
+
useEffect5(() => {
|
|
891
|
+
const data = subscription.data;
|
|
892
|
+
if (!data) {
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
if (eventSet && !eventSet.has(data.event)) {
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
const parser = resolveParser(parseRef.current, data.event);
|
|
899
|
+
let parsed;
|
|
900
|
+
try {
|
|
901
|
+
parsed = parser(data.data);
|
|
902
|
+
} catch {
|
|
903
|
+
parsed = data.data;
|
|
904
|
+
}
|
|
905
|
+
if (parsed === void 0) {
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
const accumulator = resolveAccumulator(accumulateRef.current, data.event);
|
|
909
|
+
const parsedObj = parsed;
|
|
910
|
+
const messageIndex = typeof parsedObj?.index === "number" ? parsedObj.index : void 0;
|
|
911
|
+
if (messageIndex !== void 0) {
|
|
912
|
+
const lastIndex = lastMessageIndexRef.current[data.event];
|
|
913
|
+
if (lastIndex !== void 0 && messageIndex < lastIndex) {
|
|
914
|
+
setAccumulatedData({});
|
|
915
|
+
lastMessageIndexRef.current = {};
|
|
916
|
+
}
|
|
917
|
+
lastMessageIndexRef.current[data.event] = messageIndex;
|
|
918
|
+
}
|
|
919
|
+
setAccumulatedData((prev) => {
|
|
920
|
+
const previousEventData = prev[data.event];
|
|
921
|
+
let newEventData;
|
|
922
|
+
try {
|
|
923
|
+
newEventData = accumulator(previousEventData, parsed);
|
|
924
|
+
} catch {
|
|
925
|
+
newEventData = parsed;
|
|
926
|
+
}
|
|
927
|
+
const newAccumulated = {
|
|
928
|
+
...prev,
|
|
929
|
+
[data.event]: newEventData
|
|
930
|
+
};
|
|
931
|
+
eventEmitter?.emit(
|
|
932
|
+
"spoosh:subscription:accumulate",
|
|
933
|
+
{
|
|
934
|
+
queryKey: subscription._queryKey,
|
|
935
|
+
eventType: data.event,
|
|
936
|
+
accumulatedData: newAccumulated,
|
|
937
|
+
timestamp: Date.now()
|
|
938
|
+
}
|
|
939
|
+
);
|
|
940
|
+
return newAccumulated;
|
|
941
|
+
});
|
|
942
|
+
}, [subscription.data, subscription._queryKey, eventSet]);
|
|
943
|
+
const reset = useCallback5(() => {
|
|
944
|
+
setAccumulatedData({});
|
|
945
|
+
}, []);
|
|
946
|
+
const trigger = useCallback5(
|
|
947
|
+
async (opts) => {
|
|
948
|
+
reset();
|
|
949
|
+
const triggerOpts = {
|
|
950
|
+
...opts ?? {},
|
|
951
|
+
maxRetries: optionsRef.current.maxRetries,
|
|
952
|
+
retryDelay: optionsRef.current.retryDelay
|
|
953
|
+
};
|
|
954
|
+
currentOptionsRef.current = {
|
|
955
|
+
...currentOptionsRef.current,
|
|
956
|
+
...triggerOpts
|
|
957
|
+
};
|
|
958
|
+
await subscription.trigger(triggerOpts);
|
|
959
|
+
},
|
|
960
|
+
[subscription.trigger, reset]
|
|
961
|
+
);
|
|
962
|
+
return {
|
|
963
|
+
data: Object.keys(accumulatedData).length ? accumulatedData : void 0,
|
|
964
|
+
error: subscription.error,
|
|
965
|
+
isConnected: subscription.isConnected,
|
|
966
|
+
loading: subscription.loading,
|
|
967
|
+
meta: {},
|
|
968
|
+
trigger,
|
|
969
|
+
disconnect: subscription.disconnect,
|
|
970
|
+
reset
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
return useSSE;
|
|
974
|
+
}
|
|
975
|
+
|
|
669
976
|
// src/create/index.ts
|
|
670
977
|
function create(instance) {
|
|
671
|
-
const { api, stateManager, eventEmitter, pluginExecutor } = instance;
|
|
978
|
+
const { api, stateManager, eventEmitter, pluginExecutor, transports } = instance;
|
|
672
979
|
const useRead = createUseRead({
|
|
673
980
|
api,
|
|
674
981
|
stateManager,
|
|
675
982
|
eventEmitter,
|
|
676
|
-
pluginExecutor
|
|
983
|
+
pluginExecutor,
|
|
984
|
+
transports,
|
|
985
|
+
config: instance.config
|
|
677
986
|
});
|
|
678
987
|
const useWrite = createUseWrite({
|
|
679
988
|
api,
|
|
680
989
|
stateManager,
|
|
681
990
|
eventEmitter,
|
|
682
|
-
pluginExecutor
|
|
991
|
+
pluginExecutor,
|
|
992
|
+
transports,
|
|
993
|
+
config: instance.config
|
|
683
994
|
});
|
|
684
|
-
const
|
|
995
|
+
const usePages = createUsePages({
|
|
685
996
|
api,
|
|
686
997
|
stateManager,
|
|
687
998
|
eventEmitter,
|
|
688
|
-
pluginExecutor
|
|
999
|
+
pluginExecutor,
|
|
1000
|
+
transports,
|
|
1001
|
+
config: instance.config
|
|
689
1002
|
});
|
|
690
1003
|
const useQueue = createUseQueue({
|
|
691
1004
|
api,
|
|
692
1005
|
stateManager,
|
|
693
1006
|
eventEmitter,
|
|
694
|
-
pluginExecutor
|
|
1007
|
+
pluginExecutor,
|
|
1008
|
+
transports,
|
|
1009
|
+
config: instance.config
|
|
1010
|
+
});
|
|
1011
|
+
const useSubscription = createUseSubscription({
|
|
1012
|
+
api,
|
|
1013
|
+
stateManager,
|
|
1014
|
+
eventEmitter,
|
|
1015
|
+
pluginExecutor,
|
|
1016
|
+
transports,
|
|
1017
|
+
config: instance.config
|
|
1018
|
+
});
|
|
1019
|
+
const useSSE = createUseSSE({
|
|
1020
|
+
api,
|
|
1021
|
+
stateManager,
|
|
1022
|
+
eventEmitter,
|
|
1023
|
+
pluginExecutor,
|
|
1024
|
+
transports,
|
|
1025
|
+
config: instance.config
|
|
695
1026
|
});
|
|
696
1027
|
const plugins = pluginExecutor.getPlugins();
|
|
697
1028
|
const setupContext = {
|
|
@@ -720,8 +1051,10 @@ function create(instance) {
|
|
|
720
1051
|
return {
|
|
721
1052
|
useRead,
|
|
722
1053
|
useWrite,
|
|
723
|
-
|
|
1054
|
+
usePages,
|
|
724
1055
|
useQueue,
|
|
1056
|
+
useSubscription,
|
|
1057
|
+
useSSE,
|
|
725
1058
|
...instanceApis
|
|
726
1059
|
};
|
|
727
1060
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spoosh/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "React hooks for Spoosh API toolkit",
|
|
6
6
|
"keywords": [
|
|
@@ -34,14 +34,21 @@
|
|
|
34
34
|
}
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"@spoosh/core": ">=0.
|
|
37
|
+
"@spoosh/core": ">=0.15.0",
|
|
38
|
+
"@spoosh/transport-sse": ">=0.1.0",
|
|
38
39
|
"react": "^18 || ^19"
|
|
39
40
|
},
|
|
41
|
+
"peerDependenciesMeta": {
|
|
42
|
+
"@spoosh/transport-sse": {
|
|
43
|
+
"optional": true
|
|
44
|
+
}
|
|
45
|
+
},
|
|
40
46
|
"devDependencies": {
|
|
41
47
|
"@testing-library/react": "^16.0.0",
|
|
42
48
|
"jsdom": "^26.0.0",
|
|
43
|
-
"@spoosh/core": "0.
|
|
44
|
-
"@spoosh/
|
|
49
|
+
"@spoosh/core": "0.16.0",
|
|
50
|
+
"@spoosh/transport-sse": "0.1.0",
|
|
51
|
+
"@spoosh/test-utils": "0.3.0"
|
|
45
52
|
},
|
|
46
53
|
"scripts": {
|
|
47
54
|
"dev": "tsup --watch",
|