quidproquo-web-react 0.1.12 → 0.1.13
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/lib/commonjs/hooks/index.d.ts +0 -1
- package/lib/commonjs/hooks/index.js +0 -1
- package/lib/commonjs/hooks/index.js.map +1 -1
- package/lib/commonjs/hooks/useQpq.js +2 -2
- package/lib/commonjs/hooks/useQpq.js.map +1 -1
- package/lib/commonjs/index.d.ts +3 -0
- package/lib/commonjs/index.js +3 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/{hooks/asmj → qpqContext}/QpqContextProvider.d.ts +3 -5
- package/lib/commonjs/qpqContext/QpqContextProvider.js +16 -0
- package/lib/commonjs/qpqContext/QpqContextProvider.js.map +1 -0
- package/lib/commonjs/qpqContext/index.d.ts +3 -0
- package/lib/commonjs/qpqContext/index.js +20 -0
- package/lib/commonjs/qpqContext/index.js.map +1 -0
- package/lib/commonjs/qpqContext/qpqReactContext.d.ts +2 -0
- package/lib/commonjs/qpqContext/qpqReactContext.js +6 -0
- package/lib/commonjs/qpqContext/qpqReactContext.js.map +1 -0
- package/lib/commonjs/qpqContext/useQpqContextValue.d.ts +2 -0
- package/lib/commonjs/qpqContext/useQpqContextValue.js +10 -0
- package/lib/commonjs/qpqContext/useQpqContextValue.js.map +1 -0
- package/lib/commonjs/qpqContext/useQpqContextValues.d.ts +2 -0
- package/lib/commonjs/qpqContext/useQpqContextValues.js +10 -0
- package/lib/commonjs/qpqContext/useQpqContextValues.js.map +1 -0
- package/lib/commonjs/runtime/ActionProcessorListResolverFactory.d.ts +3 -0
- package/lib/commonjs/runtime/ActionProcessorListResolverFactory.js +3 -0
- package/lib/commonjs/runtime/ActionProcessorListResolverFactory.js.map +1 -0
- package/lib/commonjs/runtime/BubbleReducerDispatchContext.d.ts +1 -0
- package/lib/commonjs/runtime/BubbleReducerDispatchContext.js +9 -0
- package/lib/commonjs/runtime/BubbleReducerDispatchContext.js.map +1 -0
- package/lib/commonjs/runtime/QpqBubbleReducer.d.ts +1 -0
- package/lib/commonjs/runtime/QpqBubbleReducer.js +3 -0
- package/lib/commonjs/runtime/QpqBubbleReducer.js.map +1 -0
- package/lib/commonjs/runtime/QpqMappedApi.js.map +1 -0
- package/lib/commonjs/runtime/QpqRuntimeEffectCatcher.d.ts +9 -0
- package/lib/commonjs/runtime/QpqRuntimeEffectCatcher.js +13 -0
- package/lib/commonjs/runtime/QpqRuntimeEffectCatcher.js.map +1 -0
- package/lib/commonjs/runtime/actionProcessor/getStateDispatchActionProcessor.js.map +1 -0
- package/lib/commonjs/runtime/actionProcessor/getStateReadActionProcessor.js.map +1 -0
- package/lib/commonjs/runtime/actionProcessor/index.js.map +1 -0
- package/lib/commonjs/runtime/createQpqRuntimeComputed.d.ts +7 -0
- package/lib/commonjs/runtime/createQpqRuntimeComputed.js +7 -0
- package/lib/commonjs/runtime/createQpqRuntimeComputed.js.map +1 -0
- package/lib/commonjs/runtime/createQpqRuntimeDefinition.d.ts +16 -0
- package/lib/commonjs/runtime/createQpqRuntimeDefinition.js +10 -0
- package/lib/commonjs/runtime/createQpqRuntimeDefinition.js.map +1 -0
- package/lib/commonjs/runtime/getQpqRuntimeAreaKey.d.ts +3 -0
- package/lib/commonjs/runtime/getQpqRuntimeAreaKey.js +6 -0
- package/lib/commonjs/runtime/getQpqRuntimeAreaKey.js.map +1 -0
- package/lib/commonjs/runtime/index.d.ts +12 -0
- package/lib/commonjs/{hooks/asmj → runtime}/index.js +6 -2
- package/lib/commonjs/runtime/index.js.map +1 -0
- package/lib/commonjs/{hooks/asmj → runtime}/useBubbleReducer.d.ts +1 -1
- package/lib/commonjs/{hooks/asmj → runtime}/useBubbleReducer.js +2 -2
- package/lib/commonjs/runtime/useBubbleReducer.js.map +1 -0
- package/lib/commonjs/runtime/useQpqAreaBinding.d.ts +11 -0
- package/lib/commonjs/runtime/useQpqAreaBinding.js +81 -0
- package/lib/commonjs/runtime/useQpqAreaBinding.js.map +1 -0
- package/lib/commonjs/runtime/useQpqRuntime.d.ts +4 -0
- package/lib/commonjs/runtime/useQpqRuntime.js +28 -0
- package/lib/commonjs/runtime/useQpqRuntime.js.map +1 -0
- package/lib/commonjs/runtime/useQpqRuntimeComputed.d.ts +2 -0
- package/lib/commonjs/runtime/useQpqRuntimeComputed.js +26 -0
- package/lib/commonjs/runtime/useQpqRuntimeComputed.js.map +1 -0
- package/lib/commonjs/store/QpqStoreProvider.d.ts +8 -0
- package/lib/commonjs/store/QpqStoreProvider.js +13 -0
- package/lib/commonjs/store/QpqStoreProvider.js.map +1 -0
- package/lib/commonjs/store/connectQpqStoreToReduxDevTools.d.ts +2 -0
- package/lib/commonjs/store/connectQpqStoreToReduxDevTools.js +144 -0
- package/lib/commonjs/store/connectQpqStoreToReduxDevTools.js.map +1 -0
- package/lib/commonjs/store/createQpqStore.d.ts +22 -0
- package/lib/commonjs/store/createQpqStore.js +160 -0
- package/lib/commonjs/store/createQpqStore.js.map +1 -0
- package/lib/commonjs/store/index.d.ts +5 -0
- package/lib/commonjs/store/index.js +22 -0
- package/lib/commonjs/store/index.js.map +1 -0
- package/lib/commonjs/store/qpqStoreContext.d.ts +3 -0
- package/lib/commonjs/store/qpqStoreContext.js +6 -0
- package/lib/commonjs/store/qpqStoreContext.js.map +1 -0
- package/lib/commonjs/store/useQpqStore.d.ts +2 -0
- package/lib/commonjs/store/useQpqStore.js +14 -0
- package/lib/commonjs/store/useQpqStore.js.map +1 -0
- package/lib/commonjs/useFieldBinding/useFieldBinding.d.ts +1 -1
- package/lib/commonjs/useFieldBinding/useFieldBinding.js +2 -2
- package/lib/commonjs/useFieldBinding/useFieldBinding.js.map +1 -1
- package/lib/commonjs/useSharedQueryParams/sharedQueryParamsRuntime.d.ts +1 -1
- package/lib/commonjs/useSharedQueryParams/sharedQueryParamsRuntime.js +7 -2
- package/lib/commonjs/useSharedQueryParams/sharedQueryParamsRuntime.js.map +1 -1
- package/lib/commonjs/webSocketQueue/hooks/useQpqWebsocketQueueRuntime.d.ts +4 -5
- package/lib/commonjs/webSocketQueue/hooks/useQpqWebsocketQueueRuntime.js +3 -3
- package/lib/commonjs/webSocketQueue/hooks/useQpqWebsocketQueueRuntime.js.map +1 -1
- package/lib/commonjs/webSocketQueue/hooks/useWebsocketQueueSendEvent.js +8 -1
- package/lib/commonjs/webSocketQueue/hooks/useWebsocketQueueSendEvent.js.map +1 -1
- package/lib/esm/hooks/index.d.ts +0 -1
- package/lib/esm/hooks/index.js +0 -1
- package/lib/esm/hooks/index.js.map +1 -1
- package/lib/esm/hooks/useQpq.js +1 -1
- package/lib/esm/hooks/useQpq.js.map +1 -1
- package/lib/esm/index.d.ts +3 -0
- package/lib/esm/index.js +3 -0
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/{hooks/asmj → qpqContext}/QpqContextProvider.d.ts +3 -5
- package/lib/esm/{hooks/asmj → qpqContext}/QpqContextProvider.js +2 -10
- package/lib/esm/qpqContext/QpqContextProvider.js.map +1 -0
- package/lib/esm/qpqContext/index.d.ts +3 -0
- package/lib/esm/qpqContext/index.js +4 -0
- package/lib/esm/qpqContext/index.js.map +1 -0
- package/lib/esm/qpqContext/qpqReactContext.d.ts +2 -0
- package/lib/esm/qpqContext/qpqReactContext.js +3 -0
- package/lib/esm/qpqContext/qpqReactContext.js.map +1 -0
- package/lib/esm/qpqContext/useQpqContextValue.d.ts +2 -0
- package/lib/esm/qpqContext/useQpqContextValue.js +6 -0
- package/lib/esm/qpqContext/useQpqContextValue.js.map +1 -0
- package/lib/esm/qpqContext/useQpqContextValues.d.ts +2 -0
- package/lib/esm/qpqContext/useQpqContextValues.js +6 -0
- package/lib/esm/qpqContext/useQpqContextValues.js.map +1 -0
- package/lib/esm/runtime/ActionProcessorListResolverFactory.d.ts +3 -0
- package/lib/esm/runtime/ActionProcessorListResolverFactory.js +2 -0
- package/lib/esm/runtime/ActionProcessorListResolverFactory.js.map +1 -0
- package/lib/esm/runtime/BubbleReducerDispatchContext.d.ts +1 -0
- package/lib/esm/runtime/BubbleReducerDispatchContext.js +6 -0
- package/lib/esm/runtime/BubbleReducerDispatchContext.js.map +1 -0
- package/lib/esm/runtime/QpqBubbleReducer.d.ts +1 -0
- package/lib/esm/runtime/QpqBubbleReducer.js +2 -0
- package/lib/esm/runtime/QpqBubbleReducer.js.map +1 -0
- package/lib/esm/runtime/QpqMappedApi.js.map +1 -0
- package/lib/esm/runtime/QpqRuntimeEffectCatcher.d.ts +9 -0
- package/lib/esm/{hooks/asmj → runtime}/QpqRuntimeEffectCatcher.js +3 -3
- package/lib/esm/runtime/QpqRuntimeEffectCatcher.js.map +1 -0
- package/lib/esm/runtime/actionProcessor/getStateDispatchActionProcessor.js.map +1 -0
- package/lib/esm/runtime/actionProcessor/getStateReadActionProcessor.js.map +1 -0
- package/lib/esm/runtime/actionProcessor/index.js.map +1 -0
- package/lib/esm/runtime/createQpqRuntimeComputed.d.ts +7 -0
- package/lib/esm/runtime/createQpqRuntimeComputed.js +4 -0
- package/lib/esm/runtime/createQpqRuntimeComputed.js.map +1 -0
- package/lib/esm/runtime/createQpqRuntimeDefinition.d.ts +16 -0
- package/lib/esm/runtime/createQpqRuntimeDefinition.js +10 -0
- package/lib/esm/runtime/createQpqRuntimeDefinition.js.map +1 -0
- package/lib/esm/runtime/getQpqRuntimeAreaKey.d.ts +3 -0
- package/lib/esm/runtime/getQpqRuntimeAreaKey.js +2 -0
- package/lib/esm/runtime/getQpqRuntimeAreaKey.js.map +1 -0
- package/lib/esm/runtime/index.d.ts +12 -0
- package/lib/esm/runtime/index.js +13 -0
- package/lib/esm/runtime/index.js.map +1 -0
- package/lib/esm/{hooks/asmj → runtime}/useBubbleReducer.d.ts +1 -1
- package/lib/esm/{hooks/asmj → runtime}/useBubbleReducer.js +1 -1
- package/lib/esm/runtime/useBubbleReducer.js.map +1 -0
- package/lib/esm/runtime/useQpqAreaBinding.d.ts +11 -0
- package/lib/esm/runtime/useQpqAreaBinding.js +68 -0
- package/lib/esm/runtime/useQpqAreaBinding.js.map +1 -0
- package/lib/esm/runtime/useQpqRuntime.d.ts +4 -0
- package/lib/esm/runtime/useQpqRuntime.js +25 -0
- package/lib/esm/runtime/useQpqRuntime.js.map +1 -0
- package/lib/esm/runtime/useQpqRuntimeComputed.d.ts +2 -0
- package/lib/esm/runtime/useQpqRuntimeComputed.js +23 -0
- package/lib/esm/runtime/useQpqRuntimeComputed.js.map +1 -0
- package/lib/esm/store/QpqStoreProvider.d.ts +8 -0
- package/lib/esm/store/QpqStoreProvider.js +9 -0
- package/lib/esm/store/QpqStoreProvider.js.map +1 -0
- package/lib/esm/store/connectQpqStoreToReduxDevTools.d.ts +2 -0
- package/lib/esm/store/connectQpqStoreToReduxDevTools.js +137 -0
- package/lib/esm/store/connectQpqStoreToReduxDevTools.js.map +1 -0
- package/lib/esm/store/createQpqStore.d.ts +22 -0
- package/lib/esm/store/createQpqStore.js +156 -0
- package/lib/esm/store/createQpqStore.js.map +1 -0
- package/lib/esm/store/index.d.ts +5 -0
- package/lib/esm/store/index.js +6 -0
- package/lib/esm/store/index.js.map +1 -0
- package/lib/esm/store/qpqStoreContext.d.ts +3 -0
- package/lib/esm/store/qpqStoreContext.js +3 -0
- package/lib/esm/store/qpqStoreContext.js.map +1 -0
- package/lib/esm/store/useQpqStore.d.ts +2 -0
- package/lib/esm/store/useQpqStore.js +10 -0
- package/lib/esm/store/useQpqStore.js.map +1 -0
- package/lib/esm/useFieldBinding/useFieldBinding.d.ts +1 -1
- package/lib/esm/useFieldBinding/useFieldBinding.js +1 -1
- package/lib/esm/useFieldBinding/useFieldBinding.js.map +1 -1
- package/lib/esm/useSharedQueryParams/sharedQueryParamsRuntime.d.ts +1 -1
- package/lib/esm/useSharedQueryParams/sharedQueryParamsRuntime.js +7 -2
- package/lib/esm/useSharedQueryParams/sharedQueryParamsRuntime.js.map +1 -1
- package/lib/esm/webSocketQueue/hooks/useQpqWebsocketQueueRuntime.d.ts +4 -5
- package/lib/esm/webSocketQueue/hooks/useQpqWebsocketQueueRuntime.js +3 -3
- package/lib/esm/webSocketQueue/hooks/useQpqWebsocketQueueRuntime.js.map +1 -1
- package/lib/esm/webSocketQueue/hooks/useWebsocketQueueSendEvent.js +8 -1
- package/lib/esm/webSocketQueue/hooks/useWebsocketQueueSendEvent.js.map +1 -1
- package/package.json +8 -9
- package/lib/commonjs/hooks/asmj/QpqContextProvider.js +0 -26
- package/lib/commonjs/hooks/asmj/QpqContextProvider.js.map +0 -1
- package/lib/commonjs/hooks/asmj/QpqMappedApi.js.map +0 -1
- package/lib/commonjs/hooks/asmj/QpqRuntimeEffectCatcher.d.ts +0 -14
- package/lib/commonjs/hooks/asmj/QpqRuntimeEffectCatcher.js +0 -14
- package/lib/commonjs/hooks/asmj/QpqRuntimeEffectCatcher.js.map +0 -1
- package/lib/commonjs/hooks/asmj/actionProcessor/getStateDispatchActionProcessor.js.map +0 -1
- package/lib/commonjs/hooks/asmj/actionProcessor/getStateReadActionProcessor.js.map +0 -1
- package/lib/commonjs/hooks/asmj/actionProcessor/index.js.map +0 -1
- package/lib/commonjs/hooks/asmj/bubbleReducer.d.ts +0 -5
- package/lib/commonjs/hooks/asmj/bubbleReducer.js +0 -29
- package/lib/commonjs/hooks/asmj/bubbleReducer.js.map +0 -1
- package/lib/commonjs/hooks/asmj/createQpqRuntimeDefinition.d.ts +0 -22
- package/lib/commonjs/hooks/asmj/createQpqRuntimeDefinition.js +0 -53
- package/lib/commonjs/hooks/asmj/createQpqRuntimeDefinition.js.map +0 -1
- package/lib/commonjs/hooks/asmj/index.d.ts +0 -8
- package/lib/commonjs/hooks/asmj/index.js.map +0 -1
- package/lib/commonjs/hooks/asmj/useBubbleReducer.js.map +0 -1
- package/lib/commonjs/hooks/asmj/useQpqRuntime.d.ts +0 -6
- package/lib/commonjs/hooks/asmj/useQpqRuntime.js +0 -49
- package/lib/commonjs/hooks/asmj/useQpqRuntime.js.map +0 -1
- package/lib/esm/hooks/asmj/QpqContextProvider.js.map +0 -1
- package/lib/esm/hooks/asmj/QpqMappedApi.js.map +0 -1
- package/lib/esm/hooks/asmj/QpqRuntimeEffectCatcher.d.ts +0 -14
- package/lib/esm/hooks/asmj/QpqRuntimeEffectCatcher.js.map +0 -1
- package/lib/esm/hooks/asmj/actionProcessor/getStateDispatchActionProcessor.js.map +0 -1
- package/lib/esm/hooks/asmj/actionProcessor/getStateReadActionProcessor.js.map +0 -1
- package/lib/esm/hooks/asmj/actionProcessor/index.js.map +0 -1
- package/lib/esm/hooks/asmj/bubbleReducer.d.ts +0 -5
- package/lib/esm/hooks/asmj/bubbleReducer.js +0 -25
- package/lib/esm/hooks/asmj/bubbleReducer.js.map +0 -1
- package/lib/esm/hooks/asmj/createQpqRuntimeDefinition.d.ts +0 -22
- package/lib/esm/hooks/asmj/createQpqRuntimeDefinition.js +0 -47
- package/lib/esm/hooks/asmj/createQpqRuntimeDefinition.js.map +0 -1
- package/lib/esm/hooks/asmj/index.d.ts +0 -8
- package/lib/esm/hooks/asmj/index.js +0 -9
- package/lib/esm/hooks/asmj/index.js.map +0 -1
- package/lib/esm/hooks/asmj/useBubbleReducer.js.map +0 -1
- package/lib/esm/hooks/asmj/useQpqRuntime.d.ts +0 -6
- package/lib/esm/hooks/asmj/useQpqRuntime.js +0 -38
- package/lib/esm/hooks/asmj/useQpqRuntime.js.map +0 -1
- /package/lib/commonjs/{hooks/asmj → runtime}/QpqMappedApi.d.ts +0 -0
- /package/lib/commonjs/{hooks/asmj → runtime}/QpqMappedApi.js +0 -0
- /package/lib/commonjs/{hooks/asmj → runtime}/actionProcessor/getStateDispatchActionProcessor.d.ts +0 -0
- /package/lib/commonjs/{hooks/asmj → runtime}/actionProcessor/getStateDispatchActionProcessor.js +0 -0
- /package/lib/commonjs/{hooks/asmj → runtime}/actionProcessor/getStateReadActionProcessor.d.ts +0 -0
- /package/lib/commonjs/{hooks/asmj → runtime}/actionProcessor/getStateReadActionProcessor.js +0 -0
- /package/lib/commonjs/{hooks/asmj → runtime}/actionProcessor/index.d.ts +0 -0
- /package/lib/commonjs/{hooks/asmj → runtime}/actionProcessor/index.js +0 -0
- /package/lib/esm/{hooks/asmj → runtime}/QpqMappedApi.d.ts +0 -0
- /package/lib/esm/{hooks/asmj → runtime}/QpqMappedApi.js +0 -0
- /package/lib/esm/{hooks/asmj → runtime}/actionProcessor/getStateDispatchActionProcessor.d.ts +0 -0
- /package/lib/esm/{hooks/asmj → runtime}/actionProcessor/getStateDispatchActionProcessor.js +0 -0
- /package/lib/esm/{hooks/asmj → runtime}/actionProcessor/getStateReadActionProcessor.d.ts +0 -0
- /package/lib/esm/{hooks/asmj → runtime}/actionProcessor/getStateReadActionProcessor.js +0 -0
- /package/lib/esm/{hooks/asmj → runtime}/actionProcessor/index.d.ts +0 -0
- /package/lib/esm/{hooks/asmj → runtime}/actionProcessor/index.js +0 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { useCallback, useContext, useEffect } from 'react';
|
|
2
|
+
import { useEffectCallback } from '../hooks/useEffectCallback';
|
|
3
|
+
import { useQpq } from '../hooks/useQpq';
|
|
4
|
+
import { useQpqStore } from '../store/useQpqStore';
|
|
5
|
+
import { getStateActionProcessor } from './actionProcessor';
|
|
6
|
+
import { BubbleReducerDispatchContext } from './BubbleReducerDispatchContext';
|
|
7
|
+
import { getQpqRuntimeAreaKey } from './getQpqRuntimeAreaKey';
|
|
8
|
+
// Shared by every hook that reads an area (full runtime and selectors alike):
|
|
9
|
+
// all readers refcount the area and any of them can be the first bind that
|
|
10
|
+
// seeds initial state and runs onInit.
|
|
11
|
+
export function useQpqAreaBinding(definition, instanceName, getActionProcessors) {
|
|
12
|
+
const store = useQpqStore();
|
|
13
|
+
const areaKey = getQpqRuntimeAreaKey(definition, instanceName);
|
|
14
|
+
// Before the bind effect fires (or after a late unbind) the area does not
|
|
15
|
+
// exist yet; render against the definition's initial state.
|
|
16
|
+
const getCurrentState = useEffectCallback(() => store.hasArea(areaKey) ? store.getAreaState(areaKey) : definition.initialState);
|
|
17
|
+
const parentDispatch = useContext(BubbleReducerDispatchContext);
|
|
18
|
+
// Reduce with the module's reducer; handled effects publish the new state to
|
|
19
|
+
// the store, unhandled ones bubble up the component tree.
|
|
20
|
+
const dispatchThroughReducer = (action) => {
|
|
21
|
+
const [newState, handled] = definition.reducer(getCurrentState(), action);
|
|
22
|
+
if (handled) {
|
|
23
|
+
store.setAreaState(areaKey, newState);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
parentDispatch(action);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
const dispatch = useEffectCallback(dispatchThroughReducer);
|
|
30
|
+
// State processors always; then the definition's processors (needed by
|
|
31
|
+
// onInit regardless of which binder runs it); then per-binding extras.
|
|
32
|
+
const mergedProcessors = async (qpqConfig, dynamicModuleLoader) => {
|
|
33
|
+
const resolvers = [
|
|
34
|
+
getStateActionProcessor(dispatch, getCurrentState),
|
|
35
|
+
definition.getActionProcessors?.(dispatch, getCurrentState),
|
|
36
|
+
getActionProcessors?.(dispatch, getCurrentState),
|
|
37
|
+
];
|
|
38
|
+
let processors = {};
|
|
39
|
+
for (const resolver of resolvers) {
|
|
40
|
+
if (resolver) {
|
|
41
|
+
processors = { ...processors, ...(await resolver(qpqConfig, dynamicModuleLoader)) };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return processors;
|
|
45
|
+
};
|
|
46
|
+
const resolver = useQpq(mergedProcessors);
|
|
47
|
+
const runInitStory = useEffectCallback(() => {
|
|
48
|
+
if (definition.onInit) {
|
|
49
|
+
resolver(definition.onInit)();
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
// Identity must change with the area so useSyncExternalStore resubscribes.
|
|
53
|
+
const subscribe = useCallback((listener) => store.subscribe(areaKey, listener), [store, areaKey]);
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
const isFirstBind = store.bindArea(areaKey, definition.initialState);
|
|
56
|
+
// A frozen store means devtools time travel: components popping in while
|
|
57
|
+
// scrubbing must render the restored moment, not fetch the present.
|
|
58
|
+
if (isFirstBind && !store.isFrozen()) {
|
|
59
|
+
runInitStory();
|
|
60
|
+
}
|
|
61
|
+
return () => store.unbindArea(areaKey);
|
|
62
|
+
// Rebind only when the target area changes; resolver or definition
|
|
63
|
+
// identity churn must not unbind (and wipe) live state.
|
|
64
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
65
|
+
}, [store, areaKey]);
|
|
66
|
+
return { subscribe, getCurrentState, dispatch, resolver };
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=useQpqAreaBinding.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useQpqAreaBinding.js","sourceRoot":"","sources":["../../../src/runtime/useQpqAreaBinding.ts"],"names":[],"mappings":"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","sourcesContent":["import { ActionProcessorList, ActionProcessorListResolver, StoryResolver } from 'quidproquo-core';\n\nimport { useCallback, useContext, useEffect } from 'react';\n\nimport { useEffectCallback } from '../hooks/useEffectCallback';\nimport { useQpq } from '../hooks/useQpq';\nimport { useQpqStore } from '../store/useQpqStore';\nimport { getStateActionProcessor } from './actionProcessor';\nimport { ActionProcessorListResolverFactory } from './ActionProcessorListResolverFactory';\nimport { BubbleReducerDispatchContext } from './BubbleReducerDispatchContext';\nimport { QpqRuntimeDefinition } from './createQpqRuntimeDefinition';\nimport { getQpqRuntimeAreaKey } from './getQpqRuntimeAreaKey';\nimport { QpqApi } from './QpqMappedApi';\n\nexport type QpqAreaBinding<TState, TAction> = {\n subscribe: (listener: () => void) => () => void;\n getCurrentState: () => TState;\n dispatch: (action: TAction) => void;\n resolver: StoryResolver;\n};\n\n// Shared by every hook that reads an area (full runtime and selectors alike):\n// all readers refcount the area and any of them can be the first bind that\n// seeds initial state and runs onInit.\nexport function useQpqAreaBinding<TState, TAction, TApi extends QpqApi>(\n definition: QpqRuntimeDefinition<TState, TAction, TApi>,\n instanceName?: string,\n getActionProcessors?: ActionProcessorListResolverFactory<TState>,\n): QpqAreaBinding<TState, TAction> {\n const store = useQpqStore();\n const areaKey = getQpqRuntimeAreaKey(definition, instanceName);\n\n // Before the bind effect fires (or after a late unbind) the area does not\n // exist yet; render against the definition's initial state.\n const getCurrentState = useEffectCallback((): TState =>\n store.hasArea(areaKey) ? (store.getAreaState(areaKey) as TState) : definition.initialState,\n );\n\n const parentDispatch = useContext(BubbleReducerDispatchContext);\n\n // Reduce with the module's reducer; handled effects publish the new state to\n // the store, unhandled ones bubble up the component tree.\n const dispatchThroughReducer = (action: TAction): void => {\n const [newState, handled] = definition.reducer(getCurrentState(), action);\n\n if (handled) {\n store.setAreaState(areaKey, newState);\n } else {\n parentDispatch(action);\n }\n };\n const dispatch = useEffectCallback(dispatchThroughReducer);\n\n // State processors always; then the definition's processors (needed by\n // onInit regardless of which binder runs it); then per-binding extras.\n const mergedProcessors: ActionProcessorListResolver = async (qpqConfig, dynamicModuleLoader) => {\n const resolvers = [\n getStateActionProcessor(dispatch, getCurrentState),\n definition.getActionProcessors?.(dispatch, getCurrentState),\n getActionProcessors?.(dispatch, getCurrentState),\n ];\n\n let processors: ActionProcessorList = {};\n for (const resolver of resolvers) {\n if (resolver) {\n processors = { ...processors, ...(await resolver(qpqConfig, dynamicModuleLoader)) };\n }\n }\n\n return processors;\n };\n\n const resolver = useQpq(mergedProcessors);\n\n const runInitStory = useEffectCallback(() => {\n if (definition.onInit) {\n resolver(definition.onInit)();\n }\n });\n\n // Identity must change with the area so useSyncExternalStore resubscribes.\n const subscribe = useCallback((listener: () => void) => store.subscribe(areaKey, listener), [store, areaKey]);\n\n useEffect(() => {\n const isFirstBind = store.bindArea(areaKey, definition.initialState);\n\n // A frozen store means devtools time travel: components popping in while\n // scrubbing must render the restored moment, not fetch the present.\n if (isFirstBind && !store.isFrozen()) {\n runInitStory();\n }\n\n return () => store.unbindArea(areaKey);\n // Rebind only when the target area changes; resolver or definition\n // identity churn must not unbind (and wipe) live state.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [store, areaKey]);\n\n return { subscribe, getCurrentState, dispatch, resolver };\n}\n"]}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { ActionProcessorListResolverFactory } from './ActionProcessorListResolverFactory';
|
|
2
|
+
import { QpqRuntimeDefinition } from './createQpqRuntimeDefinition';
|
|
3
|
+
import { QpqApi, QpqMappedApi } from './QpqMappedApi';
|
|
4
|
+
export declare function useQpqRuntime<TState, TAction, TApi extends QpqApi>(definition: QpqRuntimeDefinition<TState, TAction, TApi>, instanceName?: string, getActionProcessors?: ActionProcessorListResolverFactory<TState>): [QpqMappedApi<TApi>, TState, (action: TAction) => void];
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { useMemo, useState, useSyncExternalStore } from 'react';
|
|
2
|
+
import { useQpqAreaBinding } from './useQpqAreaBinding';
|
|
3
|
+
export function useQpqRuntime(definition, instanceName, getActionProcessors) {
|
|
4
|
+
const { subscribe, getCurrentState, dispatch, resolver } = useQpqAreaBinding(definition, instanceName, getActionProcessors);
|
|
5
|
+
const state = useSyncExternalStore(subscribe, getCurrentState, getCurrentState);
|
|
6
|
+
// Api generators are memoized to prevent unnecessary re-renders.
|
|
7
|
+
const [memoedApiGenerators] = useState(() => definition.api);
|
|
8
|
+
// Wrap and remap each API generator using the resolver.
|
|
9
|
+
const api = useMemo(() => {
|
|
10
|
+
const wrapped = {};
|
|
11
|
+
for (const key in memoedApiGenerators) {
|
|
12
|
+
if (Object.prototype.hasOwnProperty.call(memoedApiGenerators, key)) {
|
|
13
|
+
// Remove the 'ask' prefix and lower-case the first character.
|
|
14
|
+
const withoutAsk = key.slice(3); // e.g., "FetchTodos"
|
|
15
|
+
const newKey = withoutAsk.charAt(0).toLowerCase() + withoutAsk.slice(1); // "fetchTodos"
|
|
16
|
+
// We know newKey matches our mapped type so we can assign.
|
|
17
|
+
// (A type assertion is used here to quiet the compiler.)
|
|
18
|
+
wrapped[newKey] = resolver(memoedApiGenerators[key]);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return wrapped;
|
|
22
|
+
}, [resolver, memoedApiGenerators]);
|
|
23
|
+
return [api, state, dispatch];
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=useQpqRuntime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useQpqRuntime.js","sourceRoot":"","sources":["../../../src/runtime/useQpqRuntime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAKhE,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAExD,MAAM,UAAU,aAAa,CAC3B,UAAuD,EACvD,YAAqB,EACrB,mBAAgE;IAEhE,MAAM,EAAE,SAAS,EAAE,eAAe,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,iBAAiB,CAAC,UAAU,EAAE,YAAY,EAAE,mBAAmB,CAAC,CAAC;IAE5H,MAAM,KAAK,GAAG,oBAAoB,CAAC,SAAS,EAAE,eAAe,EAAE,eAAe,CAAC,CAAC;IAEhF,iEAAiE;IACjE,MAAM,CAAC,mBAAmB,CAAC,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IAE7D,wDAAwD;IACxD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE;QACvB,MAAM,OAAO,GAAwB,EAAE,CAAC;QACxC,KAAK,MAAM,GAAG,IAAI,mBAAmB,EAAE,CAAC;YACtC,IAAI,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,mBAAmB,EAAE,GAAG,CAAC,EAAE,CAAC;gBACnE,8DAA8D;gBAC9D,MAAM,UAAU,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,qBAAqB;gBACtD,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,eAAe;gBACxF,2DAA2D;gBAC3D,yDAAyD;gBACzD,OAAO,CAAC,MAAM,CAAC,GAAG,QAAQ,CAAC,mBAAmB,CAAC,GAAU,CAAC,CAAQ,CAAC;YACrE,CAAC;QACH,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC,EAAE,CAAC,QAAQ,EAAE,mBAAmB,CAAC,CAAC,CAAC;IAEpC,OAAO,CAAC,GAAyB,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;AACtD,CAAC","sourcesContent":["import { useMemo, useState, useSyncExternalStore } from 'react';\n\nimport { ActionProcessorListResolverFactory } from './ActionProcessorListResolverFactory';\nimport { QpqRuntimeDefinition } from './createQpqRuntimeDefinition';\nimport { QpqApi, QpqMappedApi } from './QpqMappedApi';\nimport { useQpqAreaBinding } from './useQpqAreaBinding';\n\nexport function useQpqRuntime<TState, TAction, TApi extends QpqApi>(\n definition: QpqRuntimeDefinition<TState, TAction, TApi>,\n instanceName?: string,\n getActionProcessors?: ActionProcessorListResolverFactory<TState>,\n): [QpqMappedApi<TApi>, TState, (action: TAction) => void] {\n const { subscribe, getCurrentState, dispatch, resolver } = useQpqAreaBinding(definition, instanceName, getActionProcessors);\n\n const state = useSyncExternalStore(subscribe, getCurrentState, getCurrentState);\n\n // Api generators are memoized to prevent unnecessary re-renders.\n const [memoedApiGenerators] = useState(() => definition.api);\n\n // Wrap and remap each API generator using the resolver.\n const api = useMemo(() => {\n const wrapped: Record<string, any> = {};\n for (const key in memoedApiGenerators) {\n if (Object.prototype.hasOwnProperty.call(memoedApiGenerators, key)) {\n // Remove the 'ask' prefix and lower-case the first character.\n const withoutAsk = key.slice(3); // e.g., \"FetchTodos\"\n const newKey = withoutAsk.charAt(0).toLowerCase() + withoutAsk.slice(1); // \"fetchTodos\"\n // We know newKey matches our mapped type so we can assign.\n // (A type assertion is used here to quiet the compiler.)\n wrapped[newKey] = resolver(memoedApiGenerators[key as any]) as any;\n }\n }\n return wrapped;\n }, [resolver, memoedApiGenerators]);\n\n return [api as QpqMappedApi<TApi>, state, dispatch];\n}\n"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { useRef, useSyncExternalStore } from 'react';
|
|
2
|
+
import { useEffectCallback } from '../hooks/useEffectCallback';
|
|
3
|
+
import { useQpqAreaBinding } from './useQpqAreaBinding';
|
|
4
|
+
export function useQpqRuntimeComputed(computed, instanceName) {
|
|
5
|
+
const { subscribe, getCurrentState } = useQpqAreaBinding(computed.definition, instanceName);
|
|
6
|
+
const sliceCache = useRef(null);
|
|
7
|
+
// The snapshot returns a referentially stable slice so the component only
|
|
8
|
+
// re-renders when its slice actually changes, not on every area update.
|
|
9
|
+
const computeStableSlice = () => {
|
|
10
|
+
const state = getCurrentState();
|
|
11
|
+
const cached = sliceCache.current;
|
|
12
|
+
if (cached && Object.is(cached.state, state)) {
|
|
13
|
+
return cached.slice;
|
|
14
|
+
}
|
|
15
|
+
const slice = computed.compute(state);
|
|
16
|
+
const stableSlice = cached && Object.is(cached.slice, slice) ? cached.slice : slice;
|
|
17
|
+
sliceCache.current = { state, slice: stableSlice };
|
|
18
|
+
return stableSlice;
|
|
19
|
+
};
|
|
20
|
+
const getSlice = useEffectCallback(computeStableSlice);
|
|
21
|
+
return useSyncExternalStore(subscribe, getSlice, getSlice);
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=useQpqRuntimeComputed.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useQpqRuntimeComputed.js","sourceRoot":"","sources":["../../../src/runtime/useQpqRuntimeComputed.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAErD,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAE/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAOxD,MAAM,UAAU,qBAAqB,CAAiB,QAA4C,EAAE,YAAqB;IACvH,MAAM,EAAE,SAAS,EAAE,eAAe,EAAE,GAAG,iBAAiB,CAAC,QAAQ,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;IAE5F,MAAM,UAAU,GAAG,MAAM,CAA4C,IAAI,CAAC,CAAC;IAE3E,0EAA0E;IAC1E,wEAAwE;IACxE,MAAM,kBAAkB,GAAG,GAAW,EAAE;QACtC,MAAM,KAAK,GAAG,eAAe,EAAE,CAAC;QAChC,MAAM,MAAM,GAAG,UAAU,CAAC,OAAO,CAAC;QAElC,IAAI,MAAM,IAAI,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC;YAC7C,OAAO,MAAM,CAAC,KAAK,CAAC;QACtB,CAAC;QAED,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QACtC,MAAM,WAAW,GAAG,MAAM,IAAI,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC;QAEpF,UAAU,CAAC,OAAO,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC;QACnD,OAAO,WAAW,CAAC;IACrB,CAAC,CAAC;IACF,MAAM,QAAQ,GAAG,iBAAiB,CAAC,kBAAkB,CAAC,CAAC;IAEvD,OAAO,oBAAoB,CAAC,SAAS,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;AAC7D,CAAC","sourcesContent":["import { Nullable } from 'quidproquo-core';\n\nimport { useRef, useSyncExternalStore } from 'react';\n\nimport { useEffectCallback } from '../hooks/useEffectCallback';\nimport { QpqRuntimeComputed } from './createQpqRuntimeComputed';\nimport { useQpqAreaBinding } from './useQpqAreaBinding';\n\ntype SliceCacheEntry<TState, TSlice> = {\n state: TState;\n slice: TSlice;\n};\n\nexport function useQpqRuntimeComputed<TState, TSlice>(computed: QpqRuntimeComputed<TState, TSlice>, instanceName?: string): TSlice {\n const { subscribe, getCurrentState } = useQpqAreaBinding(computed.definition, instanceName);\n\n const sliceCache = useRef<Nullable<SliceCacheEntry<TState, TSlice>>>(null);\n\n // The snapshot returns a referentially stable slice so the component only\n // re-renders when its slice actually changes, not on every area update.\n const computeStableSlice = (): TSlice => {\n const state = getCurrentState();\n const cached = sliceCache.current;\n\n if (cached && Object.is(cached.state, state)) {\n return cached.slice;\n }\n\n const slice = computed.compute(state);\n const stableSlice = cached && Object.is(cached.slice, slice) ? cached.slice : slice;\n\n sliceCache.current = { state, slice: stableSlice };\n return stableSlice;\n };\n const getSlice = useEffectCallback(computeStableSlice);\n\n return useSyncExternalStore(subscribe, getSlice, getSlice);\n}\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { QpqStore } from './createQpqStore';
|
|
3
|
+
type QpqStoreProviderProps = {
|
|
4
|
+
store?: QpqStore;
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
};
|
|
7
|
+
export declare const QpqStoreProvider: ({ store, children }: QpqStoreProviderProps) => import("react").JSX.Element;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { createQpqStore } from './createQpqStore';
|
|
4
|
+
import { qpqStoreContext } from './qpqStoreContext';
|
|
5
|
+
export const QpqStoreProvider = ({ store, children }) => {
|
|
6
|
+
const [ownStore] = useState(createQpqStore);
|
|
7
|
+
return _jsx(qpqStoreContext.Provider, { value: store ?? ownStore, children: children });
|
|
8
|
+
};
|
|
9
|
+
//# sourceMappingURL=QpqStoreProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"QpqStoreProvider.js","sourceRoot":"","sources":["../../../src/store/QpqStoreProvider.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAa,QAAQ,EAAE,MAAM,OAAO,CAAC;AAE5C,OAAO,EAAE,cAAc,EAAY,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AASpD,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAyB,EAAE,EAAE;IAC7E,MAAM,CAAC,QAAQ,CAAC,GAAG,QAAQ,CAAC,cAAc,CAAC,CAAC;IAE5C,OAAO,KAAC,eAAe,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,IAAI,QAAQ,YAAG,QAAQ,GAA4B,CAAC;AACnG,CAAC,CAAC","sourcesContent":["import { ReactNode, useState } from 'react';\n\nimport { createQpqStore, QpqStore } from './createQpqStore';\nimport { qpqStoreContext } from './qpqStoreContext';\n\ntype QpqStoreProviderProps = {\n // Pass a store the app created itself when imperative (non-react) code also\n // needs to read it; omitted, the provider creates and owns one.\n store?: QpqStore;\n children: ReactNode;\n};\n\nexport const QpqStoreProvider = ({ store, children }: QpqStoreProviderProps) => {\n const [ownStore] = useState(createQpqStore);\n\n return <qpqStoreContext.Provider value={store ?? ownStore}>{children}</qpqStoreContext.Provider>;\n};\n"]}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// The buttons we can honour. Skip/reorder stay off: they need replaying the
|
|
2
|
+
// effect log through the binders' reducers, which live outside the store.
|
|
3
|
+
const DEVTOOLS_FEATURES = {
|
|
4
|
+
pause: false,
|
|
5
|
+
lock: true,
|
|
6
|
+
persist: false,
|
|
7
|
+
export: true,
|
|
8
|
+
import: false,
|
|
9
|
+
jump: true,
|
|
10
|
+
skip: false,
|
|
11
|
+
reorder: false,
|
|
12
|
+
dispatch: false,
|
|
13
|
+
test: false,
|
|
14
|
+
};
|
|
15
|
+
// Wires a qpq store to the Redux DevTools browser extension: the devtools
|
|
16
|
+
// state pane shows the ENTIRE app state (one key per bound area), every
|
|
17
|
+
// seed / set_state / release lands as a named action with diffs, and the
|
|
18
|
+
// jump/slider controls time travel the store.
|
|
19
|
+
//
|
|
20
|
+
// Time travel freezes the world automatically: any jump away from the newest
|
|
21
|
+
// action freezes the store (live writes dropped, releases deferred, onInit
|
|
22
|
+
// skipped, sends muted), and jumping back to the newest action thaws it.
|
|
23
|
+
// While frozen, restores can resurrect released areas at count 0, so
|
|
24
|
+
// components popping in render the historical state instead of fetching.
|
|
25
|
+
// The extension's lock button is a manual freeze with the same semantics,
|
|
26
|
+
// and COMMIT adopts whatever is on screen as the new present.
|
|
27
|
+
//
|
|
28
|
+
// A noop returning a noop when the extension is not installed, so call it
|
|
29
|
+
// unconditionally right after creating the store.
|
|
30
|
+
export const connectQpqStoreToReduxDevTools = (store, name = 'qpq-store') => {
|
|
31
|
+
const extension = globalThis.__REDUX_DEVTOOLS_EXTENSION__;
|
|
32
|
+
if (!extension) {
|
|
33
|
+
return () => { };
|
|
34
|
+
}
|
|
35
|
+
const connection = extension.connect({ name, features: DEVTOOLS_FEATURES });
|
|
36
|
+
// Mirrors the extension's action ids (@@INIT is 0, each send increments,
|
|
37
|
+
// init resets) so a jump back onto the newest action is recognisable as
|
|
38
|
+
// "back at head". If a jump arrives without an actionId the store stays
|
|
39
|
+
// frozen; lock-off or COMMIT are the manual ways out.
|
|
40
|
+
let latestActionId = 0;
|
|
41
|
+
let isJumpedAwayFromHead = false;
|
|
42
|
+
let isLockedByUser = false;
|
|
43
|
+
const syncFrozen = () => {
|
|
44
|
+
if (isJumpedAwayFromHead || isLockedByUser) {
|
|
45
|
+
store.freeze();
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
store.thaw();
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
const restoreSnapshot = (snapshot) => {
|
|
52
|
+
for (const [areaKey, areaState] of Object.entries(snapshot)) {
|
|
53
|
+
store.restoreAreaState(areaKey, areaState);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
const parseSerializedState = (serializedState) => {
|
|
57
|
+
try {
|
|
58
|
+
return JSON.parse(serializedState);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
console.warn('qpq store devtools: could not parse time-travel state, ignoring jump');
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
// RESET returns to this baseline; COMMIT moves it to the present.
|
|
66
|
+
let committedSnapshot = store.getSnapshot();
|
|
67
|
+
connection.init(committedSnapshot);
|
|
68
|
+
const handleJump = (message) => {
|
|
69
|
+
const snapshot = message.state ? parseSerializedState(message.state) : undefined;
|
|
70
|
+
if (!snapshot) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
isJumpedAwayFromHead = message.payload?.actionId !== latestActionId;
|
|
74
|
+
// Freeze before restoring so a jump into the past can resurrect released
|
|
75
|
+
// areas; on the jump back to head, restore first and thaw after so the
|
|
76
|
+
// sweep sees final bind counts.
|
|
77
|
+
if (isJumpedAwayFromHead) {
|
|
78
|
+
syncFrozen();
|
|
79
|
+
restoreSnapshot(snapshot);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
restoreSnapshot(snapshot);
|
|
83
|
+
syncFrozen();
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
const handleDevToolsMessage = (message) => {
|
|
87
|
+
if (message.type !== 'DISPATCH') {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const payloadType = message.payload?.type;
|
|
91
|
+
if (payloadType === 'JUMP_TO_ACTION' || payloadType === 'JUMP_TO_STATE') {
|
|
92
|
+
handleJump(message);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (payloadType === 'LOCK_CHANGES') {
|
|
96
|
+
isLockedByUser = message.payload?.status === true;
|
|
97
|
+
syncFrozen();
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
// Adopt whatever is on screen (possibly a jumped-to past) as the new
|
|
101
|
+
// present: thaw first so the sweep runs, then rebase the baseline.
|
|
102
|
+
if (payloadType === 'COMMIT') {
|
|
103
|
+
isJumpedAwayFromHead = false;
|
|
104
|
+
syncFrozen();
|
|
105
|
+
committedSnapshot = store.getSnapshot();
|
|
106
|
+
connection.init(committedSnapshot);
|
|
107
|
+
latestActionId = 0;
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (payloadType === 'ROLLBACK' || payloadType === 'RESET') {
|
|
111
|
+
const snapshot = payloadType === 'ROLLBACK' && message.state ? parseSerializedState(message.state) : committedSnapshot;
|
|
112
|
+
if (!snapshot) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
restoreSnapshot(snapshot);
|
|
116
|
+
isJumpedAwayFromHead = false;
|
|
117
|
+
syncFrozen();
|
|
118
|
+
connection.init(store.getSnapshot());
|
|
119
|
+
latestActionId = 0;
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
const unsubscribeDevTools = connection.subscribe?.(handleDevToolsMessage);
|
|
123
|
+
const unsubscribeStore = store.subscribeToStore((event) => {
|
|
124
|
+
// Frozen means the world is on pause: seeds from pop-ins mid-scrub are
|
|
125
|
+
// history echoes, not new activity, and must not grow the action log.
|
|
126
|
+
if (store.isFrozen()) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
latestActionId += 1;
|
|
130
|
+
connection.send({ type: `${event.type}: ${event.areaKey}` }, store.getSnapshot());
|
|
131
|
+
});
|
|
132
|
+
return () => {
|
|
133
|
+
unsubscribeStore();
|
|
134
|
+
unsubscribeDevTools?.();
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
//# sourceMappingURL=connectQpqStoreToReduxDevTools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connectQpqStoreToReduxDevTools.js","sourceRoot":"","sources":["../../../src/store/connectQpqStoreToReduxDevTools.ts"],"names":[],"mappings":"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","sourcesContent":["// Type-only: createQpqStore imports this module at runtime (to honour\n// devToolsName), so the back-edge must stay erasable.\nimport type { QpqStore } from './createQpqStore';\n\ntype ReduxDevToolsMessage = {\n type: string;\n payload?: { type?: string; actionId?: number; status?: boolean };\n state?: string;\n};\n\ntype ReduxDevToolsConnection = {\n init(state: unknown): void;\n send(action: { type: string }, state: unknown): void;\n subscribe?(listener: (message: ReduxDevToolsMessage) => void): (() => void) | undefined;\n};\n\ntype ReduxDevToolsExtension = {\n connect(options: { name: string; features?: Record<string, boolean> }): ReduxDevToolsConnection;\n};\n\n// The buttons we can honour. Skip/reorder stay off: they need replaying the\n// effect log through the binders' reducers, which live outside the store.\nconst DEVTOOLS_FEATURES: Record<string, boolean> = {\n pause: false,\n lock: true,\n persist: false,\n export: true,\n import: false,\n jump: true,\n skip: false,\n reorder: false,\n dispatch: false,\n test: false,\n};\n\n// Wires a qpq store to the Redux DevTools browser extension: the devtools\n// state pane shows the ENTIRE app state (one key per bound area), every\n// seed / set_state / release lands as a named action with diffs, and the\n// jump/slider controls time travel the store.\n//\n// Time travel freezes the world automatically: any jump away from the newest\n// action freezes the store (live writes dropped, releases deferred, onInit\n// skipped, sends muted), and jumping back to the newest action thaws it.\n// While frozen, restores can resurrect released areas at count 0, so\n// components popping in render the historical state instead of fetching.\n// The extension's lock button is a manual freeze with the same semantics,\n// and COMMIT adopts whatever is on screen as the new present.\n//\n// A noop returning a noop when the extension is not installed, so call it\n// unconditionally right after creating the store.\nexport const connectQpqStoreToReduxDevTools = (store: QpqStore, name: string = 'qpq-store'): (() => void) => {\n const extension = (globalThis as { __REDUX_DEVTOOLS_EXTENSION__?: ReduxDevToolsExtension }).__REDUX_DEVTOOLS_EXTENSION__;\n\n if (!extension) {\n return () => {};\n }\n\n const connection = extension.connect({ name, features: DEVTOOLS_FEATURES });\n\n // Mirrors the extension's action ids (@@INIT is 0, each send increments,\n // init resets) so a jump back onto the newest action is recognisable as\n // \"back at head\". If a jump arrives without an actionId the store stays\n // frozen; lock-off or COMMIT are the manual ways out.\n let latestActionId = 0;\n let isJumpedAwayFromHead = false;\n let isLockedByUser = false;\n\n const syncFrozen = (): void => {\n if (isJumpedAwayFromHead || isLockedByUser) {\n store.freeze();\n } else {\n store.thaw();\n }\n };\n\n const restoreSnapshot = (snapshot: Record<string, unknown>): void => {\n for (const [areaKey, areaState] of Object.entries(snapshot)) {\n store.restoreAreaState(areaKey, areaState);\n }\n };\n\n const parseSerializedState = (serializedState: string): Record<string, unknown> | undefined => {\n try {\n return JSON.parse(serializedState) as Record<string, unknown>;\n } catch {\n console.warn('qpq store devtools: could not parse time-travel state, ignoring jump');\n return undefined;\n }\n };\n\n // RESET returns to this baseline; COMMIT moves it to the present.\n let committedSnapshot = store.getSnapshot();\n connection.init(committedSnapshot);\n\n const handleJump = (message: ReduxDevToolsMessage): void => {\n const snapshot = message.state ? parseSerializedState(message.state) : undefined;\n if (!snapshot) {\n return;\n }\n\n isJumpedAwayFromHead = message.payload?.actionId !== latestActionId;\n\n // Freeze before restoring so a jump into the past can resurrect released\n // areas; on the jump back to head, restore first and thaw after so the\n // sweep sees final bind counts.\n if (isJumpedAwayFromHead) {\n syncFrozen();\n restoreSnapshot(snapshot);\n } else {\n restoreSnapshot(snapshot);\n syncFrozen();\n }\n };\n\n const handleDevToolsMessage = (message: ReduxDevToolsMessage): void => {\n if (message.type !== 'DISPATCH') {\n return;\n }\n\n const payloadType = message.payload?.type;\n\n if (payloadType === 'JUMP_TO_ACTION' || payloadType === 'JUMP_TO_STATE') {\n handleJump(message);\n return;\n }\n\n if (payloadType === 'LOCK_CHANGES') {\n isLockedByUser = message.payload?.status === true;\n syncFrozen();\n return;\n }\n\n // Adopt whatever is on screen (possibly a jumped-to past) as the new\n // present: thaw first so the sweep runs, then rebase the baseline.\n if (payloadType === 'COMMIT') {\n isJumpedAwayFromHead = false;\n syncFrozen();\n committedSnapshot = store.getSnapshot();\n connection.init(committedSnapshot);\n latestActionId = 0;\n return;\n }\n\n if (payloadType === 'ROLLBACK' || payloadType === 'RESET') {\n const snapshot = payloadType === 'ROLLBACK' && message.state ? parseSerializedState(message.state) : committedSnapshot;\n if (!snapshot) {\n return;\n }\n\n restoreSnapshot(snapshot);\n isJumpedAwayFromHead = false;\n syncFrozen();\n connection.init(store.getSnapshot());\n latestActionId = 0;\n }\n };\n const unsubscribeDevTools = connection.subscribe?.(handleDevToolsMessage);\n\n const unsubscribeStore = store.subscribeToStore((event) => {\n // Frozen means the world is on pause: seeds from pop-ins mid-scrub are\n // history echoes, not new activity, and must not grow the action log.\n if (store.isFrozen()) {\n return;\n }\n\n latestActionId += 1;\n connection.send({ type: `${event.type}: ${event.areaKey}` }, store.getSnapshot());\n });\n\n return () => {\n unsubscribeStore();\n unsubscribeDevTools?.();\n };\n};\n"]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type QpqStoreEvent = {
|
|
2
|
+
type: 'seed' | 'set_state' | 'release';
|
|
3
|
+
areaKey: string;
|
|
4
|
+
};
|
|
5
|
+
export type QpqStoreOptions = {
|
|
6
|
+
devToolsName?: string;
|
|
7
|
+
};
|
|
8
|
+
export type QpqStore = {
|
|
9
|
+
hasArea(areaKey: string): boolean;
|
|
10
|
+
getAreaState(areaKey: string): unknown;
|
|
11
|
+
setAreaState(areaKey: string, state: unknown): void;
|
|
12
|
+
restoreAreaState(areaKey: string, state: unknown): void;
|
|
13
|
+
bindArea(areaKey: string, initialState: unknown): boolean;
|
|
14
|
+
unbindArea(areaKey: string): void;
|
|
15
|
+
subscribe(areaKey: string, listener: () => void): () => void;
|
|
16
|
+
subscribeToStore(listener: (event: QpqStoreEvent) => void): () => void;
|
|
17
|
+
getSnapshot(): Record<string, unknown>;
|
|
18
|
+
freeze(): void;
|
|
19
|
+
thaw(): void;
|
|
20
|
+
isFrozen(): boolean;
|
|
21
|
+
};
|
|
22
|
+
export declare function createQpqStore(options?: QpqStoreOptions): QpqStore;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { connectQpqStoreToReduxDevTools } from './connectQpqStoreToReduxDevTools';
|
|
2
|
+
// process.env is only defined when a bundler injects it, so reach through
|
|
3
|
+
// globalThis rather than referencing process directly.
|
|
4
|
+
const isDevEnvironment = () => {
|
|
5
|
+
const nodeEnv = globalThis.process?.env?.NODE_ENV;
|
|
6
|
+
return nodeEnv !== 'production';
|
|
7
|
+
};
|
|
8
|
+
export function createQpqStore(options) {
|
|
9
|
+
const areas = new Map();
|
|
10
|
+
// Listener lifetime is independent of area lifetime: React subscribes and
|
|
11
|
+
// binds in separate effects, and an area can be deleted and re-seeded while
|
|
12
|
+
// a subscriber from the same commit is still attached.
|
|
13
|
+
const listenersByArea = new Map();
|
|
14
|
+
// Store-wide observers (devtools, logging) hear every mutation with what
|
|
15
|
+
// happened; per-area subscribers just hear that their area changed.
|
|
16
|
+
const storeListeners = new Set();
|
|
17
|
+
let frozen = false;
|
|
18
|
+
const notifyArea = (areaKey) => {
|
|
19
|
+
const listeners = listenersByArea.get(areaKey);
|
|
20
|
+
if (!listeners) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
for (const listener of [...listeners]) {
|
|
24
|
+
listener();
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
const notifyStore = (event) => {
|
|
28
|
+
for (const listener of [...storeListeners]) {
|
|
29
|
+
listener(event);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
const store = {
|
|
33
|
+
hasArea: (areaKey) => areas.has(areaKey),
|
|
34
|
+
getAreaState: (areaKey) => areas.get(areaKey)?.state,
|
|
35
|
+
setAreaState: (areaKey, state) => {
|
|
36
|
+
// Frozen: the app is displaying a devtools-restored moment; live writes
|
|
37
|
+
// (in-flight stories, late fetches, websocket pushes) lose, silently,
|
|
38
|
+
// so they cannot repaint the past.
|
|
39
|
+
if (frozen) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const area = areas.get(areaKey);
|
|
43
|
+
// Smart pointer semantics: unbound state does not exist, late writers
|
|
44
|
+
// (in-flight stories, websocket pushes) lose.
|
|
45
|
+
if (!area) {
|
|
46
|
+
if (isDevEnvironment()) {
|
|
47
|
+
console.warn(`qpq store: dropped set_state for unbound area "${areaKey}"`);
|
|
48
|
+
}
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
area.state = state;
|
|
52
|
+
notifyArea(areaKey);
|
|
53
|
+
notifyStore({ type: 'set_state', areaKey });
|
|
54
|
+
},
|
|
55
|
+
restoreAreaState: (areaKey, state) => {
|
|
56
|
+
const area = areas.get(areaKey);
|
|
57
|
+
if (area) {
|
|
58
|
+
area.state = state;
|
|
59
|
+
notifyArea(areaKey);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
// While frozen a restore may resurrect an area that was released before
|
|
63
|
+
// the freeze: it sits at count 0 so a component popping in binds it
|
|
64
|
+
// (count 0 -> 1, NOT a first bind) and renders the historical state
|
|
65
|
+
// instead of fetching. Thaw sweeps whatever is still unbound.
|
|
66
|
+
if (frozen) {
|
|
67
|
+
areas.set(areaKey, { count: 0, state });
|
|
68
|
+
notifyArea(areaKey);
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
bindArea: (areaKey, initialState) => {
|
|
72
|
+
const area = areas.get(areaKey);
|
|
73
|
+
if (area) {
|
|
74
|
+
area.count += 1;
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
areas.set(areaKey, { count: 1, state: initialState });
|
|
78
|
+
// An unmount can delete the area in the same commit a mount re-seeds
|
|
79
|
+
// it; subscribers must re-read or they keep rendering the deleted state.
|
|
80
|
+
notifyArea(areaKey);
|
|
81
|
+
notifyStore({ type: 'seed', areaKey });
|
|
82
|
+
return true;
|
|
83
|
+
},
|
|
84
|
+
unbindArea: (areaKey) => {
|
|
85
|
+
const area = areas.get(areaKey);
|
|
86
|
+
if (!area) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
area.count -= 1;
|
|
90
|
+
if (area.count > 0) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
// Frozen: defer the delete (entry stays at count 0) so scrubbing
|
|
94
|
+
// forward can rebind without losing state; thaw does the sweep.
|
|
95
|
+
if (frozen) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
areas.delete(areaKey);
|
|
99
|
+
notifyStore({ type: 'release', areaKey });
|
|
100
|
+
},
|
|
101
|
+
subscribe: (areaKey, listener) => {
|
|
102
|
+
let listeners = listenersByArea.get(areaKey);
|
|
103
|
+
if (!listeners) {
|
|
104
|
+
listeners = new Set();
|
|
105
|
+
listenersByArea.set(areaKey, listeners);
|
|
106
|
+
}
|
|
107
|
+
listeners.add(listener);
|
|
108
|
+
const capturedListeners = listeners;
|
|
109
|
+
return () => {
|
|
110
|
+
capturedListeners.delete(listener);
|
|
111
|
+
// Only drop the map entry if it still holds this set; another
|
|
112
|
+
// subscriber may have recreated it after we emptied out.
|
|
113
|
+
if (capturedListeners.size === 0 && listenersByArea.get(areaKey) === capturedListeners) {
|
|
114
|
+
listenersByArea.delete(areaKey);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
},
|
|
118
|
+
subscribeToStore: (listener) => {
|
|
119
|
+
storeListeners.add(listener);
|
|
120
|
+
return () => {
|
|
121
|
+
storeListeners.delete(listener);
|
|
122
|
+
};
|
|
123
|
+
},
|
|
124
|
+
// The entire live app state as one plain object, one key per bound area.
|
|
125
|
+
getSnapshot: () => {
|
|
126
|
+
const snapshot = {};
|
|
127
|
+
for (const [areaKey, area] of areas) {
|
|
128
|
+
snapshot[areaKey] = area.state;
|
|
129
|
+
}
|
|
130
|
+
return snapshot;
|
|
131
|
+
},
|
|
132
|
+
freeze: () => {
|
|
133
|
+
frozen = true;
|
|
134
|
+
},
|
|
135
|
+
thaw: () => {
|
|
136
|
+
if (!frozen) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
frozen = false;
|
|
140
|
+
// Sweep the deferred releases and time-travel resurrections nothing is
|
|
141
|
+
// bound to any more.
|
|
142
|
+
for (const [areaKey, area] of [...areas]) {
|
|
143
|
+
if (area.count <= 0) {
|
|
144
|
+
areas.delete(areaKey);
|
|
145
|
+
notifyStore({ type: 'release', areaKey });
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
isFrozen: () => frozen,
|
|
150
|
+
};
|
|
151
|
+
if (options?.devToolsName) {
|
|
152
|
+
connectQpqStoreToReduxDevTools(store, options.devToolsName);
|
|
153
|
+
}
|
|
154
|
+
return store;
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=createQpqStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createQpqStore.js","sourceRoot":"","sources":["../../../src/store/createQpqStore.ts"],"names":[],"mappings":"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","sourcesContent":["import { connectQpqStoreToReduxDevTools } from './connectQpqStoreToReduxDevTools';\n\nexport type QpqStoreEvent = {\n type: 'seed' | 'set_state' | 'release';\n areaKey: string;\n};\n\nexport type QpqStoreOptions = {\n // Connect to the Redux DevTools browser extension under this instance name.\n // Opt-in so anonymous stores (tests, provider-owned) stay unconnected.\n devToolsName?: string;\n};\n\nexport type QpqStore = {\n hasArea(areaKey: string): boolean;\n getAreaState(areaKey: string): unknown;\n setAreaState(areaKey: string, state: unknown): void;\n // Time-travel/hydration escape hatch: writes even while frozen, emits no\n // store event, and while frozen may resurrect an unbound area at count 0.\n restoreAreaState(areaKey: string, state: unknown): void;\n bindArea(areaKey: string, initialState: unknown): boolean;\n unbindArea(areaKey: string): void;\n subscribe(areaKey: string, listener: () => void): () => void;\n subscribeToStore(listener: (event: QpqStoreEvent) => void): () => void;\n getSnapshot(): Record<string, unknown>;\n // Frozen = the world is on pause (devtools time travel): live writes are\n // dropped, releases are deferred, and binders skip onInit. Thaw resumes\n // normal behaviour and sweeps areas nothing is bound to any more.\n freeze(): void;\n thaw(): void;\n isFrozen(): boolean;\n};\n\ntype QpqStoreAreaEntry = {\n count: number;\n state: unknown;\n};\n\n// process.env is only defined when a bundler injects it, so reach through\n// globalThis rather than referencing process directly.\nconst isDevEnvironment = (): boolean => {\n const nodeEnv = (globalThis as { process?: { env?: { NODE_ENV?: string } } }).process?.env?.NODE_ENV;\n return nodeEnv !== 'production';\n};\n\nexport function createQpqStore(options?: QpqStoreOptions): QpqStore {\n const areas = new Map<string, QpqStoreAreaEntry>();\n\n // Listener lifetime is independent of area lifetime: React subscribes and\n // binds in separate effects, and an area can be deleted and re-seeded while\n // a subscriber from the same commit is still attached.\n const listenersByArea = new Map<string, Set<() => void>>();\n\n // Store-wide observers (devtools, logging) hear every mutation with what\n // happened; per-area subscribers just hear that their area changed.\n const storeListeners = new Set<(event: QpqStoreEvent) => void>();\n\n let frozen = false;\n\n const notifyArea = (areaKey: string): void => {\n const listeners = listenersByArea.get(areaKey);\n if (!listeners) {\n return;\n }\n\n for (const listener of [...listeners]) {\n listener();\n }\n };\n\n const notifyStore = (event: QpqStoreEvent): void => {\n for (const listener of [...storeListeners]) {\n listener(event);\n }\n };\n\n const store: QpqStore = {\n hasArea: (areaKey: string): boolean => areas.has(areaKey),\n\n getAreaState: (areaKey: string): unknown => areas.get(areaKey)?.state,\n\n setAreaState: (areaKey: string, state: unknown): void => {\n // Frozen: the app is displaying a devtools-restored moment; live writes\n // (in-flight stories, late fetches, websocket pushes) lose, silently,\n // so they cannot repaint the past.\n if (frozen) {\n return;\n }\n\n const area = areas.get(areaKey);\n\n // Smart pointer semantics: unbound state does not exist, late writers\n // (in-flight stories, websocket pushes) lose.\n if (!area) {\n if (isDevEnvironment()) {\n console.warn(`qpq store: dropped set_state for unbound area \"${areaKey}\"`);\n }\n return;\n }\n\n area.state = state;\n notifyArea(areaKey);\n notifyStore({ type: 'set_state', areaKey });\n },\n\n restoreAreaState: (areaKey: string, state: unknown): void => {\n const area = areas.get(areaKey);\n if (area) {\n area.state = state;\n notifyArea(areaKey);\n return;\n }\n\n // While frozen a restore may resurrect an area that was released before\n // the freeze: it sits at count 0 so a component popping in binds it\n // (count 0 -> 1, NOT a first bind) and renders the historical state\n // instead of fetching. Thaw sweeps whatever is still unbound.\n if (frozen) {\n areas.set(areaKey, { count: 0, state });\n notifyArea(areaKey);\n }\n },\n\n bindArea: (areaKey: string, initialState: unknown): boolean => {\n const area = areas.get(areaKey);\n if (area) {\n area.count += 1;\n return false;\n }\n\n areas.set(areaKey, { count: 1, state: initialState });\n\n // An unmount can delete the area in the same commit a mount re-seeds\n // it; subscribers must re-read or they keep rendering the deleted state.\n notifyArea(areaKey);\n notifyStore({ type: 'seed', areaKey });\n\n return true;\n },\n\n unbindArea: (areaKey: string): void => {\n const area = areas.get(areaKey);\n if (!area) {\n return;\n }\n\n area.count -= 1;\n if (area.count > 0) {\n return;\n }\n\n // Frozen: defer the delete (entry stays at count 0) so scrubbing\n // forward can rebind without losing state; thaw does the sweep.\n if (frozen) {\n return;\n }\n\n areas.delete(areaKey);\n notifyStore({ type: 'release', areaKey });\n },\n\n subscribe: (areaKey: string, listener: () => void): (() => void) => {\n let listeners = listenersByArea.get(areaKey);\n if (!listeners) {\n listeners = new Set();\n listenersByArea.set(areaKey, listeners);\n }\n\n listeners.add(listener);\n\n const capturedListeners = listeners;\n return () => {\n capturedListeners.delete(listener);\n\n // Only drop the map entry if it still holds this set; another\n // subscriber may have recreated it after we emptied out.\n if (capturedListeners.size === 0 && listenersByArea.get(areaKey) === capturedListeners) {\n listenersByArea.delete(areaKey);\n }\n };\n },\n\n subscribeToStore: (listener: (event: QpqStoreEvent) => void): (() => void) => {\n storeListeners.add(listener);\n return () => {\n storeListeners.delete(listener);\n };\n },\n\n // The entire live app state as one plain object, one key per bound area.\n getSnapshot: (): Record<string, unknown> => {\n const snapshot: Record<string, unknown> = {};\n for (const [areaKey, area] of areas) {\n snapshot[areaKey] = area.state;\n }\n return snapshot;\n },\n\n freeze: (): void => {\n frozen = true;\n },\n\n thaw: (): void => {\n if (!frozen) {\n return;\n }\n\n frozen = false;\n\n // Sweep the deferred releases and time-travel resurrections nothing is\n // bound to any more.\n for (const [areaKey, area] of [...areas]) {\n if (area.count <= 0) {\n areas.delete(areaKey);\n notifyStore({ type: 'release', areaKey });\n }\n }\n },\n\n isFrozen: (): boolean => frozen,\n };\n\n if (options?.devToolsName) {\n connectQpqStoreToReduxDevTools(store, options.devToolsName);\n }\n\n return store;\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/store/index.ts"],"names":[],"mappings":"AAAA,cAAc,kCAAkC,CAAC;AACjD,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC","sourcesContent":["export * from './connectQpqStoreToReduxDevTools';\nexport * from './createQpqStore';\nexport * from './qpqStoreContext';\nexport * from './QpqStoreProvider';\nexport * from './useQpqStore';\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qpqStoreContext.js","sourceRoot":"","sources":["../../../src/store/qpqStoreContext.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAItC,MAAM,CAAC,MAAM,eAAe,GAAG,aAAa,CAAqB,IAAI,CAAC,CAAC","sourcesContent":["import { Nullable } from 'quidproquo-core';\n\nimport { createContext } from 'react';\n\nimport { QpqStore } from './createQpqStore';\n\nexport const qpqStoreContext = createContext<Nullable<QpqStore>>(null);\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { useContext } from 'react';
|
|
2
|
+
import { qpqStoreContext } from './qpqStoreContext';
|
|
3
|
+
export const useQpqStore = () => {
|
|
4
|
+
const store = useContext(qpqStoreContext);
|
|
5
|
+
if (!store) {
|
|
6
|
+
throw new Error('useQpqStore must be used inside a QpqStoreProvider');
|
|
7
|
+
}
|
|
8
|
+
return store;
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=useQpqStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useQpqStore.js","sourceRoot":"","sources":["../../../src/store/useQpqStore.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAGnC,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,MAAM,CAAC,MAAM,WAAW,GAAG,GAAa,EAAE;IACxC,MAAM,KAAK,GAAG,UAAU,CAAC,eAAe,CAAC,CAAC;IAE1C,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;IACxE,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC,CAAC","sourcesContent":["import { useContext } from 'react';\n\nimport { QpqStore } from './createQpqStore';\nimport { qpqStoreContext } from './qpqStoreContext';\n\nexport const useQpqStore = (): QpqStore => {\n const store = useContext(qpqStoreContext);\n\n if (!store) {\n throw new Error('useQpqStore must be used inside a QpqStoreProvider');\n }\n\n return store;\n};\n"]}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { QpqRuntimeComputed } from '../
|
|
1
|
+
import { QpqRuntimeComputed } from '../runtime/createQpqRuntimeComputed';
|
|
2
2
|
export declare const useFieldBinding: <TState, V, E = any>(computedAtom: QpqRuntimeComputed<TState, V>, someSetter: (value: V) => Promise<void> | void, valueExtractor?: (event: E, ...args: any[]) => V) => [V, (event: E, ...args: any[]) => void];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useEffect, useState } from 'react';
|
|
2
|
-
import { useQpqRuntimeComputed } from '../
|
|
2
|
+
import { useQpqRuntimeComputed } from '../runtime/useQpqRuntimeComputed';
|
|
3
3
|
export const useFieldBinding = (computedAtom, someSetter, valueExtractor) => {
|
|
4
4
|
const computedValue = useQpqRuntimeComputed(computedAtom);
|
|
5
5
|
const [value, setValue] = useState(computedValue);
|