@veams/status-quo 1.6.0 → 1.8.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.
Files changed (100) hide show
  1. package/.turbo/turbo-build.log +3 -3
  2. package/.turbo/turbo-check$colon$types.log +1 -1
  3. package/.turbo/turbo-lint.log +2 -2
  4. package/.turbo/turbo-test.log +115 -15
  5. package/CHANGELOG.md +2 -0
  6. package/README.md +51 -7
  7. package/dist/config/status-quo-config.d.ts +22 -1
  8. package/dist/config/status-quo-config.js +46 -2
  9. package/dist/config/status-quo-config.js.map +1 -1
  10. package/dist/index.d.ts +12 -2
  11. package/dist/index.js +22 -2
  12. package/dist/index.js.map +1 -1
  13. package/dist/react/hooks/__tests__/state-provider.spec.js +2 -2
  14. package/dist/react/hooks/__tests__/state-provider.spec.js.map +1 -1
  15. package/dist/react/hooks/__tests__/state-selector.spec.js +48 -0
  16. package/dist/react/hooks/__tests__/state-selector.spec.js.map +1 -1
  17. package/dist/react/hooks/index.d.ts +6 -3
  18. package/dist/react/hooks/index.js +12 -3
  19. package/dist/react/hooks/index.js.map +1 -1
  20. package/dist/react/hooks/state-actions.d.ts +9 -1
  21. package/dist/react/hooks/state-actions.js +21 -2
  22. package/dist/react/hooks/state-actions.js.map +1 -1
  23. package/dist/react/hooks/state-factory.d.ts +7 -0
  24. package/dist/react/hooks/state-factory.js +23 -1
  25. package/dist/react/hooks/state-factory.js.map +1 -1
  26. package/dist/react/hooks/state-handler.d.ts +4 -0
  27. package/dist/react/hooks/state-handler.js +18 -1
  28. package/dist/react/hooks/state-handler.js.map +1 -1
  29. package/dist/react/hooks/state-provider.d.ts +18 -9
  30. package/dist/react/hooks/state-provider.js +25 -17
  31. package/dist/react/hooks/state-provider.js.map +1 -1
  32. package/dist/react/hooks/state-singleton.d.ts +8 -2
  33. package/dist/react/hooks/state-singleton.js +21 -3
  34. package/dist/react/hooks/state-singleton.js.map +1 -1
  35. package/dist/react/hooks/state-subscription-selector.d.ts +4 -0
  36. package/dist/react/hooks/state-subscription-selector.js +111 -4
  37. package/dist/react/hooks/state-subscription-selector.js.map +1 -1
  38. package/dist/react/hooks/state-subscription.d.ts +12 -0
  39. package/dist/react/hooks/state-subscription.js +49 -1
  40. package/dist/react/hooks/state-subscription.js.map +1 -1
  41. package/dist/react/index.d.ts +4 -1
  42. package/dist/react/index.js +5 -1
  43. package/dist/react/index.js.map +1 -1
  44. package/dist/store/__tests__/native-state-handler.spec.d.ts +1 -0
  45. package/dist/store/__tests__/native-state-handler.spec.js +210 -0
  46. package/dist/store/__tests__/native-state-handler.spec.js.map +1 -0
  47. package/dist/store/__tests__/observable-state-handler.spec.d.ts +7 -0
  48. package/dist/store/__tests__/observable-state-handler.spec.js.map +1 -1
  49. package/dist/store/base-state-handler.d.ts +42 -0
  50. package/dist/store/base-state-handler.js +73 -10
  51. package/dist/store/base-state-handler.js.map +1 -1
  52. package/dist/store/dev-tools.d.ts +42 -17
  53. package/dist/store/dev-tools.js +24 -8
  54. package/dist/store/dev-tools.js.map +1 -1
  55. package/dist/store/index.d.ts +7 -0
  56. package/dist/store/index.js +9 -0
  57. package/dist/store/index.js.map +1 -1
  58. package/dist/store/native-state-handler.d.ts +44 -0
  59. package/dist/store/native-state-handler.js +62 -0
  60. package/dist/store/native-state-handler.js.map +1 -0
  61. package/dist/store/observable-state-handler.d.ts +34 -0
  62. package/dist/store/observable-state-handler.js +45 -1
  63. package/dist/store/observable-state-handler.js.map +1 -1
  64. package/dist/store/signal-state-handler.d.ts +26 -0
  65. package/dist/store/signal-state-handler.js +35 -0
  66. package/dist/store/signal-state-handler.js.map +1 -1
  67. package/dist/store/state-singleton.d.ts +14 -0
  68. package/dist/store/state-singleton.js +20 -1
  69. package/dist/store/state-singleton.js.map +1 -1
  70. package/dist/types/types.d.ts +9 -0
  71. package/dist/types/types.js +3 -0
  72. package/dist/types/types.js.map +1 -1
  73. package/dist/utils/selector-cache.d.ts +17 -0
  74. package/dist/utils/selector-cache.js +28 -1
  75. package/dist/utils/selector-cache.js.map +1 -1
  76. package/package.json +12 -1
  77. package/src/config/status-quo-config.ts +64 -1
  78. package/src/index.ts +29 -0
  79. package/src/react/hooks/__tests__/state-provider.spec.tsx +2 -2
  80. package/src/react/hooks/__tests__/state-selector.spec.tsx +66 -0
  81. package/src/react/hooks/index.ts +13 -8
  82. package/src/react/hooks/state-actions.tsx +23 -2
  83. package/src/react/hooks/state-factory.tsx +34 -0
  84. package/src/react/hooks/state-handler.tsx +15 -0
  85. package/src/react/hooks/state-provider.tsx +36 -40
  86. package/src/react/hooks/state-singleton.tsx +37 -7
  87. package/src/react/hooks/state-subscription-selector.tsx +151 -3
  88. package/src/react/hooks/state-subscription.tsx +75 -0
  89. package/src/react/index.ts +16 -1
  90. package/src/store/__tests__/native-state-handler.spec.ts +291 -0
  91. package/src/store/__tests__/observable-state-handler.spec.ts +8 -0
  92. package/src/store/base-state-handler.ts +89 -12
  93. package/src/store/dev-tools.ts +72 -27
  94. package/src/store/index.ts +16 -0
  95. package/src/store/native-state-handler.ts +98 -0
  96. package/src/store/observable-state-handler.ts +57 -0
  97. package/src/store/signal-state-handler.ts +47 -1
  98. package/src/store/state-singleton.ts +30 -0
  99. package/src/types/types.ts +16 -0
  100. package/src/utils/selector-cache.ts +37 -0
@@ -1,7 +1,28 @@
1
+ /**
2
+ * Utility hook for accessing actions from a state handler instance.
3
+ */
1
4
  import { useMemo } from 'react';
2
5
 
3
6
  import type { StateSubscriptionHandler } from '../../types/types.js';
7
+ import { useProvidedStateHandler } from './state-provider.js';
4
8
 
5
- export function useStateActions<V, A>(stateSubscriptionHandler: StateSubscriptionHandler<V, A>) {
6
- return useMemo(() => stateSubscriptionHandler.getActions(), [stateSubscriptionHandler]);
9
+ /**
10
+ * Returns the actions of a state handler instance.
11
+ * memoized based on the state handler instance itself.
12
+ */
13
+ export function useStateActions<V, A>(stateHandler: StateSubscriptionHandler<V, A>): A {
14
+ // Access and memoize the actions from the state handler.
15
+ // This ensures the action object remains referentially stable as long as the state handler is the same.
16
+ const actions = useMemo(() => stateHandler.getActions(), [stateHandler]);
17
+
18
+ // Return the set of actions.
19
+ return actions;
20
+ }
21
+
22
+ /**
23
+ * Returns the actions of the state handler provided by the nearest StateProvider.
24
+ */
25
+ export function useProvidedStateActions<V, A>(): A {
26
+ const stateHandler = useProvidedStateHandler<V, A>();
27
+ return useStateActions(stateHandler);
7
28
  }
@@ -1,42 +1,76 @@
1
+ /**
2
+ * Utility hook for creating and subscribing to a state handler within a component.
3
+ * Combines creation and subscription logic in a single call.
4
+ */
1
5
  import { useStateHandler } from './state-handler.js';
2
6
  import { useStateSubscription } from './state-subscription.js';
3
7
 
4
8
  import type { StateSubscriptionHandler } from '../../types/types.js';
5
9
 
10
+ /**
11
+ * Type signatures for selector and equality functions.
12
+ */
6
13
  type StateSelector<State, SelectedState> = (state: State) => SelectedState;
7
14
  type EqualityFn<SelectedState> = (current: SelectedState, next: SelectedState) => boolean;
8
15
 
16
+ /**
17
+ * Default identity selector returns the whole state.
18
+ */
9
19
  const identitySelector = <State,>(state: State) => state;
10
20
 
21
+ /**
22
+ * Factory hook to create and subscribe to a state handler.
23
+ * Manages the handler instance using useStateHandler and its subscription with useStateSubscription.
24
+ */
11
25
  export function useStateFactory<V, A, P extends unknown[]>(
26
+ // Function to create a new state handler instance.
12
27
  stateFactoryFunction: (...args: P) => StateSubscriptionHandler<V, A>,
28
+ // Parameters to pass to the factory function.
13
29
  params?: P
14
30
  ): [V, A];
15
31
  export function useStateFactory<V, A, P extends unknown[], Sel>(
32
+ // Function to create a new state handler instance.
16
33
  stateFactoryFunction: (...args: P) => StateSubscriptionHandler<V, A>,
34
+ // Selector function to derive a specific value from the state.
17
35
  selector: StateSelector<V, Sel>,
36
+ // Parameters to pass to the factory function.
18
37
  params?: P
19
38
  ): [Sel, A];
20
39
  export function useStateFactory<V, A, P extends unknown[], Sel>(
40
+ // Function to create a new state handler instance.
21
41
  stateFactoryFunction: (...args: P) => StateSubscriptionHandler<V, A>,
42
+ // Selector function to derive a specific value from the state.
22
43
  selector: StateSelector<V, Sel>,
44
+ // Optional equality function to compare selected values.
23
45
  isEqual?: EqualityFn<Sel>,
46
+ // Parameters to pass to the factory function.
24
47
  params?: P
25
48
  ): [Sel, A];
26
49
  export function useStateFactory<V, A, P extends unknown[], Sel = V>(
50
+ // Implementation of the overloaded useStateFactory hook.
27
51
  stateFactoryFunction: (...args: P) => StateSubscriptionHandler<V, A>,
52
+ // Mixed argument: can be a selector or params array.
28
53
  selectorOrParams: StateSelector<V, Sel> | P = [] as unknown as P,
54
+ // Mixed argument: can be an equality function or params array.
29
55
  isEqualOrParams: EqualityFn<Sel> | P = Object.is as EqualityFn<Sel>,
56
+ // Optional params array.
30
57
  params: P = [] as unknown as P
31
58
  ) {
59
+ // Determine whether a selector was provided.
32
60
  const hasSelector = typeof selectorOrParams === 'function';
61
+ // Fallback to identity selector if none is provided.
33
62
  const selector = (hasSelector ? selectorOrParams : identitySelector) as StateSelector<V, Sel>;
63
+ // Determine whether a custom equality function was provided.
34
64
  const hasCustomEquality = hasSelector && typeof isEqualOrParams === 'function';
65
+ // Fallback to default equality check (Object.is).
35
66
  const isEqual = (hasCustomEquality ? isEqualOrParams : Object.is);
67
+ // Resolve the parameters for the state handler factory.
36
68
  const stateFactoryParams = (
37
69
  hasSelector ? (hasCustomEquality ? params : isEqualOrParams) : selectorOrParams
38
70
  ) as P;
71
+ // Create and persist the state handler instance using its factory and parameters.
39
72
  const stateHandler = useStateHandler(stateFactoryFunction, stateFactoryParams);
40
73
 
74
+ // Subscribe to the state handler and return the selected state and actions.
41
75
  return useStateSubscription(stateHandler, selector, isEqual);
42
76
  }
@@ -1,16 +1,31 @@
1
+ /**
2
+ * Utility hook for managing the lifecycle of a state handler instance.
3
+ * Ensures the instance is created once and persisted across component re-renders.
4
+ */
1
5
  import { useRef } from 'react';
2
6
 
3
7
  import type { StateSubscriptionHandler } from '../../types/types.js';
4
8
 
9
+ /**
10
+ * Returns a stable state handler instance based on a factory function.
11
+ * Uses a ref to ensure the factory function is only executed during initial render.
12
+ */
5
13
  export function useStateHandler<V, A, P extends unknown[]>(
14
+ // Function to create a new state handler instance.
6
15
  stateFactoryFunction: (...args: P) => StateSubscriptionHandler<V, A>,
16
+ // Parameters to pass to the factory function.
7
17
  params: P = [] as unknown as P
8
18
  ) {
19
+ // Use a ref to store the state handler instance.
20
+ // This prevents the state handler from being recreated on every re-render.
9
21
  const stateHandlerRef = useRef<StateSubscriptionHandler<V, A> | null>(null);
10
22
 
23
+ // If the ref is currently null, we create the instance for the first time.
11
24
  if (!stateHandlerRef.current) {
25
+ // Invoke the factory function with the provided parameters.
12
26
  stateHandlerRef.current = stateFactoryFunction(...params);
13
27
  }
14
28
 
29
+ // Return the stable state handler instance.
15
30
  return stateHandlerRef.current;
16
31
  }
@@ -1,56 +1,52 @@
1
+ /**
2
+ * Utility hook for providing a state handler through React Context.
3
+ * Allows components deep in the component tree to access a common state handler instance.
4
+ */
1
5
  import React, { createContext, useContext } from 'react';
2
6
 
3
- import { useStateActions } from './state-actions.js';
4
- import { useStateSubscription } from './state-subscription.js';
5
-
6
- import type { PropsWithChildren } from 'react';
7
7
  import type { StateSubscriptionHandler } from '../../types/types.js';
8
8
 
9
- type StateSelector<State, SelectedState> = (state: State) => SelectedState;
10
- type EqualityFn<SelectedState> = (current: SelectedState, next: SelectedState) => boolean;
11
- type SharedStateHandler = StateSubscriptionHandler<unknown, unknown>;
12
-
13
- const StateProviderContext = createContext<SharedStateHandler | null>(null);
14
- const identitySelector = <State,>(state: State) => state;
15
-
16
- export type StateProviderProps<V, A> = PropsWithChildren<{
9
+ /**
10
+ * Interface for the state provider component props.
11
+ */
12
+ interface StateProviderProps<V, A> {
13
+ // Children components that will have access to the state handler.
14
+ children: React.ReactNode;
15
+ // The state handler instance to be provided to the component tree.
17
16
  instance: StateSubscriptionHandler<V, A>;
18
- }>;
19
-
20
- export function useProvidedStateHandler<V, A>() {
21
- const stateHandler = useContext(StateProviderContext);
22
-
23
- if (!stateHandler) {
24
- throw new Error('No StateProvider instance found in the current React tree.');
25
- }
26
-
27
- return stateHandler as StateSubscriptionHandler<V, A>;
28
17
  }
29
18
 
19
+ /**
20
+ * Creates a React Context for storing and providing the state handler instance.
21
+ * Initialized with null as there is no default state handler.
22
+ */
23
+ const StateContext = createContext<StateSubscriptionHandler<unknown, unknown> | null>(null);
24
+
25
+ /**
26
+ * Provides a state handler instance to its descendant components using React Context.
27
+ */
30
28
  export function StateProvider<V, A>({ children, instance }: StateProviderProps<V, A>) {
29
+ // Use a context provider to share the state handler instance.
31
30
  return (
32
- <StateProviderContext.Provider value={instance as SharedStateHandler}>
31
+ <StateContext.Provider value={instance as StateSubscriptionHandler<unknown, unknown>}>
33
32
  {children}
34
- </StateProviderContext.Provider>
33
+ </StateContext.Provider>
35
34
  );
36
35
  }
37
36
 
38
- export function useProvidedStateActions<V, A>() {
39
- const stateHandler = useProvidedStateHandler<V, A>();
40
-
41
- return useStateActions(stateHandler);
42
- }
37
+ /**
38
+ * Custom hook to access the state handler provided by the StateProvider.
39
+ * Throws an error if the hook is used outside of a StateProvider.
40
+ */
41
+ export function useProvidedStateHandler<V, A>(): StateSubscriptionHandler<V, A> {
42
+ // Retrieve the state handler from the nearest context provider.
43
+ const stateHandler = useContext(StateContext);
43
44
 
44
- export function useProvidedStateSubscription<V, A>(): [V, A];
45
- export function useProvidedStateSubscription<V, A, Sel>(
46
- selector: StateSelector<V, Sel>,
47
- isEqual?: EqualityFn<Sel>
48
- ): [Sel, A];
49
- export function useProvidedStateSubscription<V, A, Sel = V>(
50
- selector: StateSelector<V, Sel> = identitySelector as StateSelector<V, Sel>,
51
- isEqual: EqualityFn<Sel> = Object.is
52
- ) {
53
- const stateHandler = useProvidedStateHandler<V, A>();
45
+ // If no state handler is found, it means the hook is being used incorrectly.
46
+ if (!stateHandler) {
47
+ throw new Error('useProvidedStateHandler must be used within a StateProvider');
48
+ }
54
49
 
55
- return useStateSubscription(stateHandler, selector, isEqual);
50
+ // Cast and return the state handler instance.
51
+ return stateHandler as StateSubscriptionHandler<V, A>;
56
52
  }
@@ -1,24 +1,54 @@
1
+ /**
2
+ * Utility hook for subscribing to a state singleton within a component.
3
+ */
1
4
  import { useStateSubscription } from './state-subscription.js';
2
5
 
3
6
  import type { StateSingleton } from '../../store/state-singleton.js';
4
7
 
8
+ /**
9
+ * Type signatures for selector and equality functions.
10
+ */
5
11
  type StateSelector<State, SelectedState> = (state: State) => SelectedState;
6
12
  type EqualityFn<SelectedState> = (current: SelectedState, next: SelectedState) => boolean;
7
13
 
14
+ /**
15
+ * Default identity selector returns the whole state.
16
+ */
8
17
  const identitySelector = <State,>(state: State) => state;
9
18
 
10
- export function useStateSingleton<V, A>(stateSingleton: StateSingleton<V, A>): [V, A];
19
+ /**
20
+ * Singleton hook to subscribe to a state singleton.
21
+ */
22
+ export function useStateSingleton<V, A>(
23
+ // The state singleton instance to subscribe to.
24
+ singleton: StateSingleton<V, A>
25
+ ): [V, A];
11
26
  export function useStateSingleton<V, A, Sel>(
12
- stateSingleton: StateSingleton<V, A>,
27
+ // The state singleton instance to subscribe to.
28
+ singleton: StateSingleton<V, A>,
29
+ // Selector function to derive a specific value from the state.
13
30
  selector: StateSelector<V, Sel>,
31
+ // Optional equality function to compare selected values.
14
32
  isEqual?: EqualityFn<Sel>
15
33
  ): [Sel, A];
16
34
  export function useStateSingleton<V, A, Sel = V>(
17
- stateSingleton: StateSingleton<V, A>,
18
- selector: StateSelector<V, Sel> = identitySelector as StateSelector<V, Sel>,
19
- isEqual: EqualityFn<Sel> = Object.is
35
+ // Implementation of the overloaded useStateSingleton hook.
36
+ singleton: StateSingleton<V, A>,
37
+ // Mixed argument: can be a selector or equality function.
38
+ selectorOrIsEqual: StateSelector<V, Sel> | EqualityFn<Sel> = identitySelector as StateSelector<
39
+ V,
40
+ Sel
41
+ >,
42
+ // Mixed argument: can be an equality function or undefined.
43
+ isEqual: EqualityFn<Sel> = Object.is as EqualityFn<Sel>
20
44
  ) {
21
- const [state, actions] = useStateSubscription(stateSingleton, selector, isEqual);
45
+ // Determine whether a selector was provided as the first optional argument.
46
+ const hasSelector = typeof selectorOrIsEqual === 'function' && selectorOrIsEqual.length === 1;
47
+ // Fallback to identity selector if none is provided.
48
+ const selector = (hasSelector ? selectorOrIsEqual : identitySelector) as StateSelector<V, Sel>;
49
+ // Resolve the equality function to use.
50
+ const equalityFn = (hasSelector ? isEqual : selectorOrIsEqual) as EqualityFn<Sel>;
22
51
 
23
- return [state, actions] as [Sel, A];
52
+ // Subscribe to the singleton state handler and return the selected state and actions.
53
+ return useStateSubscription(singleton, selector, equalityFn);
24
54
  }
@@ -1,3 +1,7 @@
1
+ /**
2
+ * Utility hook for subscribing to a state handler and selecting a specific value from the state.
3
+ * Uses useSyncExternalStore to ensure consistent state reads and avoid unnecessary re-renders.
4
+ */
1
5
  import { useCallback, useRef, useSyncExternalStore } from 'react';
2
6
 
3
7
  import { createSelectorCache, selectWithCache } from '../../utils/selector-cache.js';
@@ -5,24 +9,67 @@ import { createSelectorCache, selectWithCache } from '../../utils/selector-cache
5
9
  import type { StateSubscriptionHandler } from '../../types/types.js';
6
10
  import type { EqualityFn, Selector } from '../../utils/selector-cache.js';
7
11
 
12
+ /**
13
+ * Type signature for listener functions.
14
+ */
8
15
  type Listener = () => void;
16
+
17
+ /**
18
+ * Represents a state subscription handler with unknown state and actions.
19
+ */
9
20
  type SharedStateSubscriptionHandler = StateSubscriptionHandler<unknown, unknown>;
21
+
22
+ /**
23
+ * Tracks the reference count and deferred destruction status of a state handler.
24
+ */
10
25
  type DeferredDestroy = {
26
+ // Number of active consumers of the state handler.
11
27
  refCount: number;
28
+ // ID of the timeout for deferred destruction.
12
29
  timeoutId: ReturnType<typeof setTimeout> | null;
13
30
  };
14
31
 
32
+ /**
33
+ * Cache entry for a selected state snapshot.
34
+ */
35
+ type SnapshotCacheEntry<Source, Selected> = {
36
+ // The selected value derived from the source snapshot.
37
+ selectedSnapshot: Selected;
38
+ // The source snapshot from which the value was selected.
39
+ sourceSnapshot: Source;
40
+ // A version number to track state changes.
41
+ version: number;
42
+ };
43
+
44
+ /**
45
+ * Cache entry for a selected server state snapshot (SSR).
46
+ */
47
+ type ServerSnapshotCacheEntry<Source, Selected> = {
48
+ // The selected value derived from the source snapshot.
49
+ selectedSnapshot: Selected;
50
+ // The source snapshot from which the value was selected.
51
+ sourceSnapshot: Source;
52
+ };
53
+
54
+ // Global map to track deferred destruction status for each state handler instance.
15
55
  const deferredDestroyMap = new WeakMap<SharedStateSubscriptionHandler, DeferredDestroy>();
16
56
 
57
+ /**
58
+ * Returns the deferred destruction status for a given state handler instance.
59
+ * Initializes the status if it does not already exist.
60
+ */
17
61
  function getDeferredDestroyState(
18
62
  stateSubscriptionHandler: SharedStateSubscriptionHandler
19
63
  ): DeferredDestroy {
64
+ // Retrieve the existing status from the map.
20
65
  const existingState = deferredDestroyMap.get(stateSubscriptionHandler);
21
66
 
67
+ // If status already exists, return it.
22
68
  if (existingState) {
23
69
  return existingState;
24
70
  }
25
71
 
72
+ // Create and store a new status for the handler.
26
73
  const nextState: DeferredDestroy = {
27
74
  refCount: 0,
28
75
  timeoutId: null,
@@ -33,65 +80,105 @@ function getDeferredDestroyState(
33
80
  return nextState;
34
81
  }
35
82
 
83
+ /**
84
+ * Custom hook to select and subscribe to a specific piece of state from a handler.
85
+ * Efficiently manages subscriptions and selector results.
86
+ */
36
87
  export function useStateSubscriptionSelector<V, A, Sel>(
88
+ // The state handler instance to subscribe to.
37
89
  stateSubscriptionHandler: StateSubscriptionHandler<V, A>,
90
+ // Selector function to derive a value from the state.
38
91
  selector: Selector<V, Sel>,
92
+ // Equality function to compare selected values for changes.
39
93
  isEqual: EqualityFn<Sel> = Object.is,
94
+ // Whether to automatically destroy the handler instance on component unmount.
40
95
  destroyOnCleanup = true
41
96
  ) {
97
+ // Cache for the selector results to ensure referential stability.
42
98
  const selectorCacheRef = useRef<ReturnType<typeof createSelectorCache<Sel>> | null>(null);
99
+ // Tracks store notifications so getSnapshot can reuse the same selected value within one store version.
100
+ const snapshotVersionRef = useRef(0);
101
+ // Client-side cache for selected snapshots per source snapshot/version pair.
102
+ const snapshotCacheRef = useRef<SnapshotCacheEntry<V, Sel> | null>(null);
103
+ // Separate cache for the server snapshot function used by SSR/hydration paths.
104
+ const serverSnapshotCacheRef = useRef<ServerSnapshotCacheEntry<V, Sel> | null>(null);
43
105
 
106
+ // Initialize the selector cache if it doesn't already exist.
44
107
  if (!selectorCacheRef.current) {
45
108
  selectorCacheRef.current = createSelectorCache<Sel>();
46
109
  }
47
110
 
48
111
  const selectorCache = selectorCacheRef.current;
49
112
 
113
+ // Subscription function to be used by useSyncExternalStore.
50
114
  const subscribe = useCallback(
51
115
  (listener: Listener) => {
116
+ // Access the deferred destruction status for this handler.
52
117
  const sharedStateSubscriptionHandler =
53
118
  stateSubscriptionHandler as unknown as SharedStateSubscriptionHandler;
54
119
  const deferredDestroyState = getDeferredDestroyState(sharedStateSubscriptionHandler);
120
+ // Increment the consumer reference count.
55
121
  deferredDestroyState.refCount += 1;
56
122
 
123
+ // If a pending destruction timeout is scheduled, cancel it.
57
124
  if (deferredDestroyState.timeoutId) {
58
125
  clearTimeout(deferredDestroyState.timeoutId);
59
126
  deferredDestroyState.timeoutId = null;
60
127
  }
61
128
 
62
- const unsubscribe = stateSubscriptionHandler.subscribe(listener);
129
+ // Subscribe to the state handler.
130
+ const unsubscribe = stateSubscriptionHandler.subscribe(() => {
131
+ // Invalidate the selected snapshot cache before notifying React of a change.
132
+ snapshotVersionRef.current += 1;
133
+ snapshotCacheRef.current = null;
134
+ // Notify React to re-trigger a getSnapshot call.
135
+ listener();
136
+ });
63
137
 
138
+ // Return an unsubscribe function to be called by React.
64
139
  return () => {
140
+ // Execute the handler's unsubscribe method.
65
141
  unsubscribe();
66
142
 
143
+ // If automatic cleanup is disabled, stop here.
67
144
  if (!destroyOnCleanup) {
68
145
  return;
69
146
  }
70
147
 
148
+ // Retrieve the current destruction status.
71
149
  const activeDeferredDestroyState = deferredDestroyMap.get(sharedStateSubscriptionHandler);
72
150
 
151
+ // If no status is found, stop here.
73
152
  if (!activeDeferredDestroyState) {
74
153
  return;
75
154
  }
76
155
 
156
+ // Decrement the consumer reference count.
77
157
  activeDeferredDestroyState.refCount -= 1;
78
158
 
159
+ // If there are still active consumers, do not destroy the handler.
79
160
  if (activeDeferredDestroyState.refCount > 0) {
80
161
  return;
81
162
  }
82
163
 
164
+ // Reset the reference count to zero.
83
165
  activeDeferredDestroyState.refCount = 0;
166
+ // Schedule deferred destruction to allow for potential immediate re-subscriptions.
84
167
  activeDeferredDestroyState.timeoutId = setTimeout(() => {
168
+ // Check if the handler still has no consumers after the timeout.
85
169
  const pendingDeferredDestroyState = deferredDestroyMap.get(
86
170
  sharedStateSubscriptionHandler
87
171
  );
88
172
 
173
+ // If consumers have reappeared, do not destroy the handler.
89
174
  if (!pendingDeferredDestroyState || pendingDeferredDestroyState.refCount > 0) {
90
175
  return;
91
176
  }
92
177
 
178
+ // Clear the pending timeout and destroy the state handler.
93
179
  pendingDeferredDestroyState.timeoutId = null;
94
180
  stateSubscriptionHandler.destroy();
181
+ // Remove the status from the global map.
95
182
  deferredDestroyMap.delete(sharedStateSubscriptionHandler);
96
183
  }, 0);
97
184
  };
@@ -99,6 +186,7 @@ export function useStateSubscriptionSelector<V, A, Sel>(
99
186
  [destroyOnCleanup, stateSubscriptionHandler]
100
187
  );
101
188
 
189
+ // Helper to execute selection using the cache.
102
190
  const selectSnapshot = useCallback(
103
191
  (snapshot: V) => {
104
192
  return selectWithCache(selectorCache, snapshot, selector, isEqual).value;
@@ -106,15 +194,75 @@ export function useStateSubscriptionSelector<V, A, Sel>(
106
194
  [isEqual, selector, selectorCache]
107
195
  );
108
196
 
197
+ // Reference to track changes in selection strategy.
198
+ const selectorCacheControlRef = useRef(selectSnapshot);
199
+
200
+ // If the selector or equality function changes, clear all caches.
201
+ if (selectorCacheControlRef.current !== selectSnapshot) {
202
+ selectorCacheControlRef.current = selectSnapshot;
203
+ snapshotVersionRef.current = 0;
204
+ snapshotCacheRef.current = null;
205
+ serverSnapshotCacheRef.current = null;
206
+ }
207
+
208
+ // Snapshot retrieval function to be used by useSyncExternalStore.
109
209
  const getSnapshot = useCallback(
110
- () => selectSnapshot(stateSubscriptionHandler.getSnapshot()),
210
+ () => {
211
+ // Retrieve the current source state from the handler.
212
+ const sourceSnapshot = stateSubscriptionHandler.getSnapshot();
213
+ // Access the current version and cached value.
214
+ const version = snapshotVersionRef.current;
215
+ const cachedSnapshot = snapshotCacheRef.current;
216
+
217
+ // If the cached selection is still valid for this version and source state, return it.
218
+ if (
219
+ cachedSnapshot &&
220
+ cachedSnapshot.version === version &&
221
+ Object.is(cachedSnapshot.sourceSnapshot, sourceSnapshot)
222
+ ) {
223
+ return cachedSnapshot.selectedSnapshot;
224
+ }
225
+
226
+ // Compute a new selection and update the cache.
227
+ const selectedSnapshot = selectSnapshot(sourceSnapshot);
228
+ snapshotCacheRef.current = {
229
+ selectedSnapshot,
230
+ sourceSnapshot,
231
+ version,
232
+ };
233
+
234
+ // Return the new selection.
235
+ return selectedSnapshot;
236
+ },
111
237
  [selectSnapshot, stateSubscriptionHandler]
112
238
  );
113
239
 
240
+ // Server snapshot retrieval function to be used for SSR/hydration.
114
241
  const getServerSnapshot = useCallback(
115
- () => selectSnapshot(stateSubscriptionHandler.getInitialState()),
242
+ () => {
243
+ // Retrieve the initial source state from the handler.
244
+ const sourceSnapshot = stateSubscriptionHandler.getInitialState();
245
+ // Access the current cached server snapshot.
246
+ const cachedSnapshot = serverSnapshotCacheRef.current;
247
+
248
+ // If the cached server selection is still valid for the initial state, return it.
249
+ if (cachedSnapshot && Object.is(cachedSnapshot.sourceSnapshot, sourceSnapshot)) {
250
+ return cachedSnapshot.selectedSnapshot;
251
+ }
252
+
253
+ // Compute a new server selection and update its cache.
254
+ const selectedSnapshot = selectSnapshot(sourceSnapshot);
255
+ serverSnapshotCacheRef.current = {
256
+ selectedSnapshot,
257
+ sourceSnapshot,
258
+ };
259
+
260
+ // Return the initial selection result.
261
+ return selectedSnapshot;
262
+ },
116
263
  [selectSnapshot, stateSubscriptionHandler]
117
264
  );
118
265
 
266
+ // Use the useSyncExternalStore hook to integrate the state with React's rendering lifecycle.
119
267
  return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
120
268
  }