@spoosh/react 0.12.0 → 0.13.1-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +86 -1
- package/dist/index.d.mts +223 -21
- package/dist/index.d.ts +223 -21
- package/dist/index.js +326 -5
- package/dist/index.mjs +335 -5
- package/package.json +9 -2
package/dist/index.js
CHANGED
|
@@ -662,32 +662,351 @@ function createUseQueue(options) {
|
|
|
662
662
|
return useQueue;
|
|
663
663
|
}
|
|
664
664
|
|
|
665
|
+
// src/useSubscription/index.ts
|
|
666
|
+
var import_react5 = require("react");
|
|
667
|
+
var import_core5 = require("@spoosh/core");
|
|
668
|
+
function createUseSubscription(options) {
|
|
669
|
+
const { stateManager, eventEmitter, pluginExecutor } = options;
|
|
670
|
+
function useSubscription(subFn, subOptions) {
|
|
671
|
+
const { enabled = true, adapter, operationType } = subOptions;
|
|
672
|
+
const selectorResultRef = (0, import_react5.useRef)({
|
|
673
|
+
call: null,
|
|
674
|
+
selector: null
|
|
675
|
+
});
|
|
676
|
+
const selectorProxy = (0, import_core5.createSelectorProxy)((result) => {
|
|
677
|
+
selectorResultRef.current = result;
|
|
678
|
+
});
|
|
679
|
+
subFn(selectorProxy);
|
|
680
|
+
const capturedCall = selectorResultRef.current.call;
|
|
681
|
+
if (!capturedCall) {
|
|
682
|
+
throw new Error("useSubscription requires calling a method");
|
|
683
|
+
}
|
|
684
|
+
const queryKey = stateManager.createQueryKey({
|
|
685
|
+
path: capturedCall.path,
|
|
686
|
+
method: capturedCall.method,
|
|
687
|
+
options: capturedCall.options
|
|
688
|
+
});
|
|
689
|
+
const controllerRef = (0, import_react5.useRef)(null);
|
|
690
|
+
const subscriptionVersionRef = (0, import_react5.useRef)(0);
|
|
691
|
+
const getOrCreateController = (0, import_react5.useCallback)(() => {
|
|
692
|
+
if (controllerRef.current) {
|
|
693
|
+
return controllerRef.current;
|
|
694
|
+
}
|
|
695
|
+
const controller = (0, import_core5.createSubscriptionController)({
|
|
696
|
+
channel: capturedCall.path,
|
|
697
|
+
baseAdapter: adapter,
|
|
698
|
+
stateManager,
|
|
699
|
+
eventEmitter,
|
|
700
|
+
pluginExecutor,
|
|
701
|
+
queryKey,
|
|
702
|
+
operationType,
|
|
703
|
+
path: capturedCall.path,
|
|
704
|
+
method: capturedCall.method
|
|
705
|
+
});
|
|
706
|
+
controllerRef.current = controller;
|
|
707
|
+
return controller;
|
|
708
|
+
}, [
|
|
709
|
+
queryKey,
|
|
710
|
+
adapter,
|
|
711
|
+
operationType,
|
|
712
|
+
capturedCall.path,
|
|
713
|
+
capturedCall.method
|
|
714
|
+
]);
|
|
715
|
+
const subscribe = (0, import_react5.useCallback)(
|
|
716
|
+
(callback) => {
|
|
717
|
+
const controller = getOrCreateController();
|
|
718
|
+
return controller.subscribe(callback);
|
|
719
|
+
},
|
|
720
|
+
[getOrCreateController]
|
|
721
|
+
);
|
|
722
|
+
const emptyStateRef = (0, import_react5.useRef)({
|
|
723
|
+
data: void 0,
|
|
724
|
+
error: void 0,
|
|
725
|
+
isConnected: false
|
|
726
|
+
});
|
|
727
|
+
const getSnapshot = (0, import_react5.useCallback)(() => {
|
|
728
|
+
if (!controllerRef.current) {
|
|
729
|
+
return emptyStateRef.current;
|
|
730
|
+
}
|
|
731
|
+
return controllerRef.current.getState();
|
|
732
|
+
}, []);
|
|
733
|
+
const state = (0, import_react5.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
|
|
734
|
+
const [isPending, setIsPending] = (0, import_react5.useState)(enabled);
|
|
735
|
+
(0, import_react5.useEffect)(() => {
|
|
736
|
+
if (!enabled) {
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
setIsPending(true);
|
|
740
|
+
const controller = getOrCreateController();
|
|
741
|
+
controller.mount();
|
|
742
|
+
controller.subscribe();
|
|
743
|
+
return () => {
|
|
744
|
+
subscriptionVersionRef.current++;
|
|
745
|
+
controller.unsubscribe();
|
|
746
|
+
};
|
|
747
|
+
}, [queryKey, enabled, getOrCreateController]);
|
|
748
|
+
(0, import_react5.useEffect)(() => {
|
|
749
|
+
if (state.isConnected || state.data !== void 0 || state.error !== void 0) {
|
|
750
|
+
setIsPending(false);
|
|
751
|
+
}
|
|
752
|
+
}, [state.isConnected, state.data, state.error]);
|
|
753
|
+
const disconnect = (0, import_react5.useCallback)(() => {
|
|
754
|
+
subscriptionVersionRef.current++;
|
|
755
|
+
if (controllerRef.current) {
|
|
756
|
+
controllerRef.current.unsubscribe();
|
|
757
|
+
}
|
|
758
|
+
}, []);
|
|
759
|
+
const trigger = (0, import_react5.useCallback)(async () => {
|
|
760
|
+
setIsPending(true);
|
|
761
|
+
subscriptionVersionRef.current++;
|
|
762
|
+
const controller = getOrCreateController();
|
|
763
|
+
controller.unsubscribe();
|
|
764
|
+
controller.mount();
|
|
765
|
+
await controller.subscribe();
|
|
766
|
+
}, [getOrCreateController]);
|
|
767
|
+
const loading = isPending;
|
|
768
|
+
return {
|
|
769
|
+
meta: {},
|
|
770
|
+
data: state.data,
|
|
771
|
+
error: state.error,
|
|
772
|
+
loading,
|
|
773
|
+
isConnected: state.isConnected,
|
|
774
|
+
_queryKey: queryKey,
|
|
775
|
+
_subscriptionVersion: subscriptionVersionRef.current,
|
|
776
|
+
trigger,
|
|
777
|
+
disconnect
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
return useSubscription;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// src/useSSE/index.ts
|
|
784
|
+
var import_react6 = require("react");
|
|
785
|
+
var import_core6 = require("@spoosh/core");
|
|
786
|
+
function isSSETransport(transport) {
|
|
787
|
+
const t = transport;
|
|
788
|
+
return typeof t.createSubscriptionAdapter === "function" && typeof t.utils?.resolveParser === "function" && typeof t.utils?.resolveAccumulator === "function";
|
|
789
|
+
}
|
|
790
|
+
function createUseSSE(options) {
|
|
791
|
+
const { eventEmitter, transports, config } = options;
|
|
792
|
+
const useSubscription = createUseSubscription(options);
|
|
793
|
+
function useSSE(subFn, sseOptions) {
|
|
794
|
+
const {
|
|
795
|
+
enabled = true,
|
|
796
|
+
events,
|
|
797
|
+
parse = "auto",
|
|
798
|
+
accumulate = "replace",
|
|
799
|
+
maxRetries,
|
|
800
|
+
retryDelay
|
|
801
|
+
} = sseOptions ?? {};
|
|
802
|
+
const transport = transports.get("sse");
|
|
803
|
+
if (!transport) {
|
|
804
|
+
throw new Error(
|
|
805
|
+
"SSE transport not registered. Make sure to register an SSE transport before using useSSE."
|
|
806
|
+
);
|
|
807
|
+
}
|
|
808
|
+
if (!isSSETransport(transport)) {
|
|
809
|
+
throw new Error(
|
|
810
|
+
"SSE transport does not implement createSubscriptionAdapter."
|
|
811
|
+
);
|
|
812
|
+
}
|
|
813
|
+
const selectorResultRef = (0, import_react6.useRef)({
|
|
814
|
+
call: null,
|
|
815
|
+
selector: null
|
|
816
|
+
});
|
|
817
|
+
const selectorProxy = (0, import_core6.createSelectorProxy)((result) => {
|
|
818
|
+
selectorResultRef.current = result;
|
|
819
|
+
});
|
|
820
|
+
subFn(selectorProxy);
|
|
821
|
+
const capturedCall = selectorResultRef.current.call;
|
|
822
|
+
if (!capturedCall) {
|
|
823
|
+
throw new Error("useSSE requires calling a method");
|
|
824
|
+
}
|
|
825
|
+
const currentOptionsRef = (0, import_react6.useRef)(
|
|
826
|
+
capturedCall.options
|
|
827
|
+
);
|
|
828
|
+
const adapter = (0, import_react6.useMemo)(
|
|
829
|
+
() => transport.createSubscriptionAdapter({
|
|
830
|
+
channel: capturedCall.path,
|
|
831
|
+
method: capturedCall.method,
|
|
832
|
+
baseUrl: config.baseUrl,
|
|
833
|
+
globalHeaders: config.defaultOptions.headers,
|
|
834
|
+
getRequestOptions: () => currentOptionsRef.current,
|
|
835
|
+
eventEmitter,
|
|
836
|
+
devtoolMeta: events ? { listenedEvents: events } : void 0
|
|
837
|
+
}),
|
|
838
|
+
[capturedCall.path, capturedCall.method]
|
|
839
|
+
);
|
|
840
|
+
const [accumulatedData, setAccumulatedData] = (0, import_react6.useState)({});
|
|
841
|
+
const eventSet = (0, import_react6.useMemo)(
|
|
842
|
+
() => events ? new Set(events) : null,
|
|
843
|
+
[events?.join(",")]
|
|
844
|
+
);
|
|
845
|
+
const parseRef = (0, import_react6.useRef)(parse);
|
|
846
|
+
const accumulateRef = (0, import_react6.useRef)(accumulate);
|
|
847
|
+
parseRef.current = parse;
|
|
848
|
+
accumulateRef.current = accumulate;
|
|
849
|
+
const optionsRef = (0, import_react6.useRef)({
|
|
850
|
+
maxRetries,
|
|
851
|
+
retryDelay
|
|
852
|
+
});
|
|
853
|
+
optionsRef.current = { maxRetries, retryDelay };
|
|
854
|
+
const subscription = useSubscription(subFn, {
|
|
855
|
+
enabled,
|
|
856
|
+
adapter,
|
|
857
|
+
operationType: transport.operationType
|
|
858
|
+
});
|
|
859
|
+
const prevVersionRef = (0, import_react6.useRef)(subscription._subscriptionVersion);
|
|
860
|
+
const lastMessageIndexRef = (0, import_react6.useRef)({});
|
|
861
|
+
(0, import_react6.useEffect)(() => {
|
|
862
|
+
if (subscription._subscriptionVersion !== prevVersionRef.current) {
|
|
863
|
+
setAccumulatedData({});
|
|
864
|
+
lastMessageIndexRef.current = {};
|
|
865
|
+
}
|
|
866
|
+
prevVersionRef.current = subscription._subscriptionVersion;
|
|
867
|
+
}, [subscription._subscriptionVersion]);
|
|
868
|
+
(0, import_react6.useEffect)(() => {
|
|
869
|
+
const data = subscription.data;
|
|
870
|
+
if (!data) {
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
if (eventSet && !eventSet.has(data.event)) {
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
const parser = transport.utils.resolveParser(
|
|
877
|
+
parseRef.current,
|
|
878
|
+
data.event
|
|
879
|
+
);
|
|
880
|
+
let parsed;
|
|
881
|
+
try {
|
|
882
|
+
parsed = parser(data.data);
|
|
883
|
+
} catch {
|
|
884
|
+
parsed = data.data;
|
|
885
|
+
}
|
|
886
|
+
if (parsed === void 0) {
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
const accumulator = transport.utils.resolveAccumulator(
|
|
890
|
+
accumulateRef.current,
|
|
891
|
+
data.event
|
|
892
|
+
);
|
|
893
|
+
const parsedObj = parsed;
|
|
894
|
+
const messageIndex = typeof parsedObj?.index === "number" ? parsedObj.index : void 0;
|
|
895
|
+
if (messageIndex !== void 0) {
|
|
896
|
+
const lastIndex = lastMessageIndexRef.current[data.event];
|
|
897
|
+
if (lastIndex !== void 0 && messageIndex < lastIndex) {
|
|
898
|
+
setAccumulatedData({});
|
|
899
|
+
lastMessageIndexRef.current = {};
|
|
900
|
+
}
|
|
901
|
+
lastMessageIndexRef.current[data.event] = messageIndex;
|
|
902
|
+
}
|
|
903
|
+
setAccumulatedData((prev) => {
|
|
904
|
+
const previousEventData = prev[data.event];
|
|
905
|
+
let newEventData;
|
|
906
|
+
try {
|
|
907
|
+
newEventData = accumulator(previousEventData, parsed);
|
|
908
|
+
} catch {
|
|
909
|
+
newEventData = parsed;
|
|
910
|
+
}
|
|
911
|
+
const newAccumulated = {
|
|
912
|
+
...prev,
|
|
913
|
+
[data.event]: newEventData
|
|
914
|
+
};
|
|
915
|
+
eventEmitter?.emit(
|
|
916
|
+
"spoosh:subscription:accumulate",
|
|
917
|
+
{
|
|
918
|
+
queryKey: subscription._queryKey,
|
|
919
|
+
eventType: data.event,
|
|
920
|
+
accumulatedData: newAccumulated,
|
|
921
|
+
timestamp: Date.now()
|
|
922
|
+
}
|
|
923
|
+
);
|
|
924
|
+
return newAccumulated;
|
|
925
|
+
});
|
|
926
|
+
}, [subscription.data, subscription._queryKey, eventSet]);
|
|
927
|
+
const reset = (0, import_react6.useCallback)(() => {
|
|
928
|
+
setAccumulatedData({});
|
|
929
|
+
}, []);
|
|
930
|
+
const trigger = (0, import_react6.useCallback)(
|
|
931
|
+
async (opts) => {
|
|
932
|
+
reset();
|
|
933
|
+
const triggerOpts = {
|
|
934
|
+
...opts ?? {},
|
|
935
|
+
maxRetries: optionsRef.current.maxRetries,
|
|
936
|
+
retryDelay: optionsRef.current.retryDelay
|
|
937
|
+
};
|
|
938
|
+
currentOptionsRef.current = {
|
|
939
|
+
...currentOptionsRef.current,
|
|
940
|
+
...triggerOpts
|
|
941
|
+
};
|
|
942
|
+
await subscription.trigger(triggerOpts);
|
|
943
|
+
},
|
|
944
|
+
[subscription.trigger, reset]
|
|
945
|
+
);
|
|
946
|
+
return {
|
|
947
|
+
data: Object.keys(accumulatedData).length ? accumulatedData : void 0,
|
|
948
|
+
error: subscription.error,
|
|
949
|
+
isConnected: subscription.isConnected,
|
|
950
|
+
loading: subscription.loading,
|
|
951
|
+
meta: {},
|
|
952
|
+
trigger,
|
|
953
|
+
disconnect: subscription.disconnect,
|
|
954
|
+
reset
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
return useSSE;
|
|
958
|
+
}
|
|
959
|
+
|
|
665
960
|
// src/create/index.ts
|
|
666
961
|
function create(instance) {
|
|
667
|
-
const { api, stateManager, eventEmitter, pluginExecutor } = instance;
|
|
962
|
+
const { api, stateManager, eventEmitter, pluginExecutor, transports } = instance;
|
|
668
963
|
const useRead = createUseRead({
|
|
669
964
|
api,
|
|
670
965
|
stateManager,
|
|
671
966
|
eventEmitter,
|
|
672
|
-
pluginExecutor
|
|
967
|
+
pluginExecutor,
|
|
968
|
+
transports,
|
|
969
|
+
config: instance.config
|
|
673
970
|
});
|
|
674
971
|
const useWrite = createUseWrite({
|
|
675
972
|
api,
|
|
676
973
|
stateManager,
|
|
677
974
|
eventEmitter,
|
|
678
|
-
pluginExecutor
|
|
975
|
+
pluginExecutor,
|
|
976
|
+
transports,
|
|
977
|
+
config: instance.config
|
|
679
978
|
});
|
|
680
979
|
const usePages = createUsePages({
|
|
681
980
|
api,
|
|
682
981
|
stateManager,
|
|
683
982
|
eventEmitter,
|
|
684
|
-
pluginExecutor
|
|
983
|
+
pluginExecutor,
|
|
984
|
+
transports,
|
|
985
|
+
config: instance.config
|
|
685
986
|
});
|
|
686
987
|
const useQueue = createUseQueue({
|
|
687
988
|
api,
|
|
688
989
|
stateManager,
|
|
689
990
|
eventEmitter,
|
|
690
|
-
pluginExecutor
|
|
991
|
+
pluginExecutor,
|
|
992
|
+
transports,
|
|
993
|
+
config: instance.config
|
|
994
|
+
});
|
|
995
|
+
const useSubscription = createUseSubscription({
|
|
996
|
+
api,
|
|
997
|
+
stateManager,
|
|
998
|
+
eventEmitter,
|
|
999
|
+
pluginExecutor,
|
|
1000
|
+
transports,
|
|
1001
|
+
config: instance.config
|
|
1002
|
+
});
|
|
1003
|
+
const useSSE = createUseSSE({
|
|
1004
|
+
api,
|
|
1005
|
+
stateManager,
|
|
1006
|
+
eventEmitter,
|
|
1007
|
+
pluginExecutor,
|
|
1008
|
+
transports,
|
|
1009
|
+
config: instance.config
|
|
691
1010
|
});
|
|
692
1011
|
const plugins = pluginExecutor.getPlugins();
|
|
693
1012
|
const setupContext = {
|
|
@@ -718,6 +1037,8 @@ function create(instance) {
|
|
|
718
1037
|
useWrite,
|
|
719
1038
|
usePages,
|
|
720
1039
|
useQueue,
|
|
1040
|
+
useSubscription,
|
|
1041
|
+
useSSE,
|
|
721
1042
|
...instanceApis
|
|
722
1043
|
};
|
|
723
1044
|
}
|