@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.
- package/.turbo/turbo-build.log +3 -3
- package/.turbo/turbo-check$colon$types.log +1 -1
- package/.turbo/turbo-lint.log +2 -2
- package/.turbo/turbo-test.log +115 -15
- package/CHANGELOG.md +2 -0
- package/README.md +51 -7
- package/dist/config/status-quo-config.d.ts +22 -1
- package/dist/config/status-quo-config.js +46 -2
- package/dist/config/status-quo-config.js.map +1 -1
- package/dist/index.d.ts +12 -2
- package/dist/index.js +22 -2
- package/dist/index.js.map +1 -1
- package/dist/react/hooks/__tests__/state-provider.spec.js +2 -2
- package/dist/react/hooks/__tests__/state-provider.spec.js.map +1 -1
- package/dist/react/hooks/__tests__/state-selector.spec.js +48 -0
- package/dist/react/hooks/__tests__/state-selector.spec.js.map +1 -1
- package/dist/react/hooks/index.d.ts +6 -3
- package/dist/react/hooks/index.js +12 -3
- package/dist/react/hooks/index.js.map +1 -1
- package/dist/react/hooks/state-actions.d.ts +9 -1
- package/dist/react/hooks/state-actions.js +21 -2
- package/dist/react/hooks/state-actions.js.map +1 -1
- package/dist/react/hooks/state-factory.d.ts +7 -0
- package/dist/react/hooks/state-factory.js +23 -1
- package/dist/react/hooks/state-factory.js.map +1 -1
- package/dist/react/hooks/state-handler.d.ts +4 -0
- package/dist/react/hooks/state-handler.js +18 -1
- package/dist/react/hooks/state-handler.js.map +1 -1
- package/dist/react/hooks/state-provider.d.ts +18 -9
- package/dist/react/hooks/state-provider.js +25 -17
- package/dist/react/hooks/state-provider.js.map +1 -1
- package/dist/react/hooks/state-singleton.d.ts +8 -2
- package/dist/react/hooks/state-singleton.js +21 -3
- package/dist/react/hooks/state-singleton.js.map +1 -1
- package/dist/react/hooks/state-subscription-selector.d.ts +4 -0
- package/dist/react/hooks/state-subscription-selector.js +111 -4
- package/dist/react/hooks/state-subscription-selector.js.map +1 -1
- package/dist/react/hooks/state-subscription.d.ts +12 -0
- package/dist/react/hooks/state-subscription.js +49 -1
- package/dist/react/hooks/state-subscription.js.map +1 -1
- package/dist/react/index.d.ts +4 -1
- package/dist/react/index.js +5 -1
- package/dist/react/index.js.map +1 -1
- package/dist/store/__tests__/native-state-handler.spec.d.ts +1 -0
- package/dist/store/__tests__/native-state-handler.spec.js +210 -0
- package/dist/store/__tests__/native-state-handler.spec.js.map +1 -0
- package/dist/store/__tests__/observable-state-handler.spec.d.ts +7 -0
- package/dist/store/__tests__/observable-state-handler.spec.js.map +1 -1
- package/dist/store/base-state-handler.d.ts +42 -0
- package/dist/store/base-state-handler.js +73 -10
- package/dist/store/base-state-handler.js.map +1 -1
- package/dist/store/dev-tools.d.ts +42 -17
- package/dist/store/dev-tools.js +24 -8
- package/dist/store/dev-tools.js.map +1 -1
- package/dist/store/index.d.ts +7 -0
- package/dist/store/index.js +9 -0
- package/dist/store/index.js.map +1 -1
- package/dist/store/native-state-handler.d.ts +44 -0
- package/dist/store/native-state-handler.js +62 -0
- package/dist/store/native-state-handler.js.map +1 -0
- package/dist/store/observable-state-handler.d.ts +34 -0
- package/dist/store/observable-state-handler.js +45 -1
- package/dist/store/observable-state-handler.js.map +1 -1
- package/dist/store/signal-state-handler.d.ts +26 -0
- package/dist/store/signal-state-handler.js +35 -0
- package/dist/store/signal-state-handler.js.map +1 -1
- package/dist/store/state-singleton.d.ts +14 -0
- package/dist/store/state-singleton.js +20 -1
- package/dist/store/state-singleton.js.map +1 -1
- package/dist/types/types.d.ts +9 -0
- package/dist/types/types.js +3 -0
- package/dist/types/types.js.map +1 -1
- package/dist/utils/selector-cache.d.ts +17 -0
- package/dist/utils/selector-cache.js +28 -1
- package/dist/utils/selector-cache.js.map +1 -1
- package/package.json +12 -1
- package/src/config/status-quo-config.ts +64 -1
- package/src/index.ts +29 -0
- package/src/react/hooks/__tests__/state-provider.spec.tsx +2 -2
- package/src/react/hooks/__tests__/state-selector.spec.tsx +66 -0
- package/src/react/hooks/index.ts +13 -8
- package/src/react/hooks/state-actions.tsx +23 -2
- package/src/react/hooks/state-factory.tsx +34 -0
- package/src/react/hooks/state-handler.tsx +15 -0
- package/src/react/hooks/state-provider.tsx +36 -40
- package/src/react/hooks/state-singleton.tsx +37 -7
- package/src/react/hooks/state-subscription-selector.tsx +151 -3
- package/src/react/hooks/state-subscription.tsx +75 -0
- package/src/react/index.ts +16 -1
- package/src/store/__tests__/native-state-handler.spec.ts +291 -0
- package/src/store/__tests__/observable-state-handler.spec.ts +8 -0
- package/src/store/base-state-handler.ts +89 -12
- package/src/store/dev-tools.ts +72 -27
- package/src/store/index.ts +16 -0
- package/src/store/native-state-handler.ts +98 -0
- package/src/store/observable-state-handler.ts +57 -0
- package/src/store/signal-state-handler.ts +47 -1
- package/src/store/state-singleton.ts +30 -0
- package/src/types/types.ts +16 -0
- package/src/utils/selector-cache.ts +37 -0
|
@@ -1,41 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Import signal-based state primitives from @preact/signals-core.
|
|
3
|
+
*/
|
|
1
4
|
import { signal } from '@preact/signals-core';
|
|
5
|
+
/**
|
|
6
|
+
* Import internal configuration and the abstract base state handler.
|
|
7
|
+
*/
|
|
2
8
|
import { resolveDistinctOptions } from '../config/status-quo-config.js';
|
|
3
9
|
import { BaseStateHandler } from './base-state-handler.js';
|
|
10
|
+
/**
|
|
11
|
+
* SignalStateHandler: A state handler built on top of Preact Signals.
|
|
12
|
+
* Provides fine-grained reactivity and efficient state updates.
|
|
13
|
+
*/
|
|
4
14
|
export class SignalStateHandler extends BaseStateHandler {
|
|
15
|
+
// Internal Signal to manage the state with high performance.
|
|
5
16
|
state;
|
|
17
|
+
// Configuration to determine if and how state changes should trigger notifications.
|
|
6
18
|
distinctOptions;
|
|
19
|
+
/**
|
|
20
|
+
* Initializes the signal state handler with given state and configuration.
|
|
21
|
+
*/
|
|
7
22
|
constructor({ initialState, options }) {
|
|
23
|
+
// Pass the initial state to the base handler.
|
|
8
24
|
super(initialState);
|
|
25
|
+
// Initialize the internal Signal with the initial state.
|
|
9
26
|
this.state = signal(initialState);
|
|
27
|
+
// Resolve the final distinct options based on provided configuration.
|
|
10
28
|
this.distinctOptions = resolveDistinctOptions(options?.distinct, options?.useDistinctUntilChanged);
|
|
29
|
+
// Initialize Redux DevTools integration.
|
|
11
30
|
this.initDevTools(options?.devTools);
|
|
12
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Returns the underlying signal for external use (e.g., in reactive templates).
|
|
34
|
+
*/
|
|
13
35
|
getSignal() {
|
|
14
36
|
return this.state;
|
|
15
37
|
}
|
|
16
38
|
subscribe(listener) {
|
|
39
|
+
// Track whether the listener has been initialized during the first emission.
|
|
17
40
|
let initialized = false;
|
|
41
|
+
// Keep track of the previous state snapshot for comparison.
|
|
18
42
|
let previousSnapshot = this.state.value;
|
|
43
|
+
// Use the Signal's subscription method.
|
|
19
44
|
return this.state.subscribe((nextState) => {
|
|
45
|
+
// If this is the initial emission, notify the listener and mark as initialized.
|
|
20
46
|
if (!initialized) {
|
|
21
47
|
initialized = true;
|
|
22
48
|
previousSnapshot = nextState;
|
|
23
49
|
listener(nextState);
|
|
24
50
|
return;
|
|
25
51
|
}
|
|
52
|
+
// If distinct mode is enabled and the state hasn't effectively changed, stop here.
|
|
26
53
|
if (this.distinctOptions.enabled &&
|
|
27
54
|
this.distinctOptions.comparator(previousSnapshot, nextState)) {
|
|
28
55
|
previousSnapshot = nextState;
|
|
29
56
|
return;
|
|
30
57
|
}
|
|
58
|
+
// Update the previous snapshot and notify the listener.
|
|
31
59
|
previousSnapshot = nextState;
|
|
32
60
|
listener(nextState);
|
|
33
61
|
});
|
|
34
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Internal implementation of getting the state value via the Signal's value property.
|
|
65
|
+
*/
|
|
35
66
|
getStateValue() {
|
|
36
67
|
return this.state.value;
|
|
37
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Internal implementation of updating the state value via the Signal's value property.
|
|
71
|
+
*/
|
|
38
72
|
setStateValue(nextState) {
|
|
73
|
+
// Update the Signal's value, which automatically notifies its subscribers.
|
|
39
74
|
this.state.value = nextState;
|
|
40
75
|
}
|
|
41
76
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signal-state-handler.js","sourceRoot":"","sources":["../../src/store/signal-state-handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAE9C,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"signal-state-handler.js","sourceRoot":"","sources":["../../src/store/signal-state-handler.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAE9C;;GAEG;AACH,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAsB3D;;;GAGG;AACH,MAAM,OAAgB,kBAAyB,SAAQ,gBAAsB;IAC3E,6DAA6D;IAC5C,KAAK,CAAY;IAClC,oFAAoF;IACnE,eAAe,CAA+C;IAE/E;;OAEG;IACH,YAAsB,EAAE,YAAY,EAAE,OAAO,EAA8B;QACzE,8CAA8C;QAC9C,KAAK,CAAC,YAAY,CAAC,CAAC;QACpB,yDAAyD;QACzD,IAAI,CAAC,KAAK,GAAG,MAAM,CAAI,YAAY,CAAC,CAAC;QACrC,sEAAsE;QACtE,IAAI,CAAC,eAAe,GAAG,sBAAsB,CAC3C,OAAO,EAAE,QAAQ,EACjB,OAAO,EAAE,uBAAuB,CACjC,CAAC;QACF,yCAAyC;QACzC,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IACvC,CAAC;IAED;;OAEG;IACH,SAAS;QACP,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAQD,SAAS,CAAC,QAA4B;QACpC,6EAA6E;QAC7E,IAAI,WAAW,GAAG,KAAK,CAAC;QACxB,4DAA4D;QAC5D,IAAI,gBAAgB,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC;QAExC,wCAAwC;QACxC,OAAO,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,SAAS,EAAE,EAAE;YACxC,gFAAgF;YAChF,IAAI,CAAC,WAAW,EAAE,CAAC;gBACjB,WAAW,GAAG,IAAI,CAAC;gBACnB,gBAAgB,GAAG,SAAS,CAAC;gBAC7B,QAAQ,CAAC,SAAS,CAAC,CAAC;gBACpB,OAAO;YACT,CAAC;YAED,mFAAmF;YACnF,IACE,IAAI,CAAC,eAAe,CAAC,OAAO;gBAC5B,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC,gBAAgB,EAAE,SAAS,CAAC,EAC5D,CAAC;gBACD,gBAAgB,GAAG,SAAS,CAAC;gBAC7B,OAAO;YACT,CAAC;YAED,wDAAwD;YACxD,gBAAgB,GAAG,SAAS,CAAC;YAC7B,QAAQ,CAAC,SAAS,CAAC,CAAC;QACtB,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACO,aAAa;QACrB,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC;IAC1B,CAAC;IAED;;OAEG;IACO,aAAa,CAAC,SAAY;QAClC,2EAA2E;QAC3E,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,SAAS,CAAC;IAC/B,CAAC;CACF"}
|
|
@@ -1,8 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Import necessary type for state subscription management.
|
|
3
|
+
*/
|
|
1
4
|
import type { StateSubscriptionHandler } from '../types/types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Interface representing a singleton state handler.
|
|
7
|
+
* Provides a method to get the single instance of a state handler.
|
|
8
|
+
*/
|
|
2
9
|
export interface StateSingleton<V, A> {
|
|
3
10
|
getInstance: () => StateSubscriptionHandler<V, A>;
|
|
4
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Options for configuring the behavior of a state singleton.
|
|
14
|
+
*/
|
|
5
15
|
export interface StateSingletonOptions {
|
|
6
16
|
destroyOnNoConsumers?: boolean;
|
|
7
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* Factory function to create a singleton state handler.
|
|
20
|
+
* Ensures only one instance of the state handler exists throughout the application.
|
|
21
|
+
*/
|
|
8
22
|
export declare function makeStateSingleton<S, A>(stateHandlerFactory: () => StateSubscriptionHandler<S, A>, { destroyOnNoConsumers }?: StateSingletonOptions): StateSingleton<S, A>;
|
|
@@ -1,21 +1,40 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Factory function to create a singleton state handler.
|
|
3
|
+
* Ensures only one instance of the state handler exists throughout the application.
|
|
4
|
+
*/
|
|
5
|
+
export function makeStateSingleton(
|
|
6
|
+
// Function responsible for creating the state handler instance when needed.
|
|
7
|
+
stateHandlerFactory,
|
|
8
|
+
// Optional configuration for the singleton.
|
|
9
|
+
{ destroyOnNoConsumers = false } = {}) {
|
|
10
|
+
// Internal variable to store the shared instance of the state handler.
|
|
2
11
|
let instance = null;
|
|
12
|
+
// The singleton object with an added internal destroy method.
|
|
3
13
|
const singleton = {
|
|
14
|
+
// Expose whether this singleton should be destroyed when not in use.
|
|
4
15
|
destroyOnNoConsumers,
|
|
16
|
+
// Method to get or create the singleton instance.
|
|
5
17
|
getInstance() {
|
|
18
|
+
// If no instance exists, create it using the factory function.
|
|
6
19
|
if (!instance) {
|
|
7
20
|
instance = stateHandlerFactory();
|
|
8
21
|
}
|
|
22
|
+
// Return the current instance.
|
|
9
23
|
return instance;
|
|
10
24
|
},
|
|
25
|
+
// Method to manually destroy the singleton instance and its resources.
|
|
11
26
|
destroyInstance() {
|
|
27
|
+
// If no instance exists, there's nothing to destroy.
|
|
12
28
|
if (!instance) {
|
|
13
29
|
return;
|
|
14
30
|
}
|
|
31
|
+
// Call the destroy method on the instance to clean up its resources.
|
|
15
32
|
instance.destroy();
|
|
33
|
+
// Clear the internal reference to the instance.
|
|
16
34
|
instance = null;
|
|
17
35
|
},
|
|
18
36
|
};
|
|
37
|
+
// Return the created singleton object.
|
|
19
38
|
return singleton;
|
|
20
39
|
}
|
|
21
40
|
//# sourceMappingURL=state-singleton.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state-singleton.js","sourceRoot":"","sources":["../../src/store/state-singleton.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"state-singleton.js","sourceRoot":"","sources":["../../src/store/state-singleton.ts"],"names":[],"mappings":"AAsBA;;;GAGG;AACH,MAAM,UAAU,kBAAkB;AAChC,4EAA4E;AAC5E,mBAAyD;AACzD,4CAA4C;AAC5C,EAAE,oBAAoB,GAAG,KAAK,KAA4B,EAAE;IAE5D,uEAAuE;IACvE,IAAI,QAAQ,GAA0C,IAAI,CAAC;IAE3D,8DAA8D;IAC9D,MAAM,SAAS,GAGX;QACF,qEAAqE;QACrE,oBAAoB;QACpB,kDAAkD;QAClD,WAAW;YACT,+DAA+D;YAC/D,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,QAAQ,GAAG,mBAAmB,EAAE,CAAC;YACnC,CAAC;YAED,+BAA+B;YAC/B,OAAO,QAAQ,CAAC;QAClB,CAAC;QACD,uEAAuE;QACvE,eAAe;YACb,qDAAqD;YACrD,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,OAAO;YACT,CAAC;YAED,qEAAqE;YACrE,QAAQ,CAAC,OAAO,EAAE,CAAC;YACnB,gDAAgD;YAChD,QAAQ,GAAG,IAAI,CAAC;QAClB,CAAC;KACF,CAAC;IAEF,uCAAuC;IACvC,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
package/dist/types/types.d.ts
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Common types and interfaces for state management.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Interface representing a standard state handler with subscription support.
|
|
6
|
+
* Used by React hooks and other components to interact with the state store.
|
|
7
|
+
* V: The type of the state value.
|
|
8
|
+
* A: The type of the available actions.
|
|
9
|
+
*/
|
|
1
10
|
export interface StateSubscriptionHandler<V, A> {
|
|
2
11
|
subscribe(listener: () => void): () => void;
|
|
3
12
|
subscribe(listener: (value: V) => void): () => void;
|
package/dist/types/types.js
CHANGED
package/dist/types/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types/types.ts"],"names":[],"mappings":""}
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types/types.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
|
@@ -1,13 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for caching selector results to optimize state updates.
|
|
3
|
+
*/
|
|
1
4
|
export type Selector<Value, Selected> = (value: Value) => Selected;
|
|
2
5
|
export type EqualityFn<Selected> = (current: Selected, next: Selected) => boolean;
|
|
6
|
+
/**
|
|
7
|
+
* Interface for internal selector cache storage.
|
|
8
|
+
*/
|
|
3
9
|
type SelectorCache<Selected> = {
|
|
4
10
|
hasValue: boolean;
|
|
5
11
|
value: Selected | undefined;
|
|
6
12
|
};
|
|
13
|
+
/**
|
|
14
|
+
* Interface for the result of a selection operation.
|
|
15
|
+
*/
|
|
7
16
|
type SelectionResult<Selected> = {
|
|
8
17
|
value: Selected;
|
|
9
18
|
hasChanged: boolean;
|
|
10
19
|
};
|
|
20
|
+
/**
|
|
21
|
+
* Creates a new, empty selector cache.
|
|
22
|
+
* Provides a simple container to track state and values across selections.
|
|
23
|
+
*/
|
|
11
24
|
export declare function createSelectorCache<Selected>(): SelectorCache<Selected>;
|
|
25
|
+
/**
|
|
26
|
+
* Executes a selector and returns the cached value if the result is considered equal.
|
|
27
|
+
* This optimization avoids unnecessary re-renders or updates when the derived state is the same.
|
|
28
|
+
*/
|
|
12
29
|
export declare function selectWithCache<Value, Selected>(selectorCache: SelectorCache<Selected>, value: Value, selector: Selector<Value, Selected>, isEqual?: EqualityFn<Selected>): SelectionResult<Selected>;
|
|
13
30
|
export {};
|
|
@@ -1,21 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for caching selector results to optimize state updates.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Creates a new, empty selector cache.
|
|
6
|
+
* Provides a simple container to track state and values across selections.
|
|
7
|
+
*/
|
|
1
8
|
export function createSelectorCache() {
|
|
2
9
|
return {
|
|
10
|
+
// Initially, no value is stored in the cache.
|
|
3
11
|
hasValue: false,
|
|
4
12
|
value: undefined,
|
|
5
13
|
};
|
|
6
14
|
}
|
|
7
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Executes a selector and returns the cached value if the result is considered equal.
|
|
17
|
+
* This optimization avoids unnecessary re-renders or updates when the derived state is the same.
|
|
18
|
+
*/
|
|
19
|
+
export function selectWithCache(
|
|
20
|
+
// The cache object to store and compare values.
|
|
21
|
+
selectorCache,
|
|
22
|
+
// The source value from which to select.
|
|
23
|
+
value,
|
|
24
|
+
// The selector function to transform the source value.
|
|
25
|
+
selector,
|
|
26
|
+
// Optional equality function to compare selected values (defaults to Object.is).
|
|
27
|
+
isEqual = Object.is) {
|
|
28
|
+
// Execute the selector to derive the current selection value.
|
|
8
29
|
const nextSelection = selector(value);
|
|
30
|
+
// If the cache has a value and the new selection is equal to it, return the cached version.
|
|
9
31
|
if (selectorCache.hasValue && isEqual(selectorCache.value, nextSelection)) {
|
|
10
32
|
return {
|
|
33
|
+
// Return the previously cached value to maintain referential stability.
|
|
11
34
|
value: selectorCache.value,
|
|
35
|
+
// Signal that no effective change has occurred.
|
|
12
36
|
hasChanged: false,
|
|
13
37
|
};
|
|
14
38
|
}
|
|
39
|
+
// Update the cache with the newly computed selection.
|
|
15
40
|
selectorCache.hasValue = true;
|
|
16
41
|
selectorCache.value = nextSelection;
|
|
17
42
|
return {
|
|
43
|
+
// Return the fresh selection result.
|
|
18
44
|
value: nextSelection,
|
|
45
|
+
// Signal that the selected value has changed.
|
|
19
46
|
hasChanged: true,
|
|
20
47
|
};
|
|
21
48
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"selector-cache.js","sourceRoot":"","sources":["../../src/utils/selector-cache.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"selector-cache.js","sourceRoot":"","sources":["../../src/utils/selector-cache.ts"],"names":[],"mappings":"AAAA;;GAEG;AA4BH;;;GAGG;AACH,MAAM,UAAU,mBAAmB;IACjC,OAAO;QACL,8CAA8C;QAC9C,QAAQ,EAAE,KAAK;QACf,KAAK,EAAE,SAAS;KACjB,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe;AAC7B,gDAAgD;AAChD,aAAsC;AACtC,yCAAyC;AACzC,KAAY;AACZ,uDAAuD;AACvD,QAAmC;AACnC,iFAAiF;AACjF,UAAgC,MAAM,CAAC,EAAE;IAEzC,8DAA8D;IAC9D,MAAM,aAAa,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAEtC,4FAA4F;IAC5F,IAAI,aAAa,CAAC,QAAQ,IAAI,OAAO,CAAC,aAAa,CAAC,KAAiB,EAAE,aAAa,CAAC,EAAE,CAAC;QACtF,OAAO;YACL,wEAAwE;YACxE,KAAK,EAAE,aAAa,CAAC,KAAiB;YACtC,gDAAgD;YAChD,UAAU,EAAE,KAAK;SAClB,CAAC;IACJ,CAAC;IAED,sDAAsD;IACtD,aAAa,CAAC,QAAQ,GAAG,IAAI,CAAC;IAC9B,aAAa,CAAC,KAAK,GAAG,aAAa,CAAC;IAEpC,OAAO;QACL,qCAAqC;QACrC,KAAK,EAAE,aAAa;QACpB,8CAA8C;QAC9C,UAAU,EAAE,IAAI;KACjB,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@veams/status-quo",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "The manager to rule states in frontend.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -51,6 +51,17 @@
|
|
|
51
51
|
"react": ">=18.0.0",
|
|
52
52
|
"rxjs": ">=7.5.4"
|
|
53
53
|
},
|
|
54
|
+
"peerDependenciesMeta": {
|
|
55
|
+
"@preact/signals-core": {
|
|
56
|
+
"optional": true
|
|
57
|
+
},
|
|
58
|
+
"react": {
|
|
59
|
+
"optional": true
|
|
60
|
+
},
|
|
61
|
+
"rxjs": {
|
|
62
|
+
"optional": true
|
|
63
|
+
}
|
|
64
|
+
},
|
|
54
65
|
"devDependencies": {
|
|
55
66
|
"@preact/signals-core": "^1.13.0",
|
|
56
67
|
"@types/react-dom": "^19.0.0",
|
|
@@ -1,55 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global configuration and utility functions for Status Quo state management.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Function signature for comparing state objects to determine if they are distinct.
|
|
1
6
|
export type DistinctComparator<T = unknown> = (previous: T, next: T) => boolean;
|
|
2
7
|
|
|
8
|
+
// Options for configuring distinct emission behavior in state handlers.
|
|
3
9
|
export type DistinctOptions<T = unknown> = {
|
|
10
|
+
// Whether distinct updates are enabled.
|
|
4
11
|
enabled?: boolean;
|
|
12
|
+
// Custom function to compare states for equality.
|
|
5
13
|
comparator?: DistinctComparator<T>;
|
|
6
14
|
};
|
|
7
15
|
|
|
16
|
+
// Options for configuring Redux DevTools integration for a specific state handler.
|
|
8
17
|
export type DevToolsOptions = {
|
|
18
|
+
// Whether DevTools integration is enabled for this handler.
|
|
9
19
|
enabled?: boolean;
|
|
20
|
+
// Namespace for the store in the DevTools window.
|
|
10
21
|
namespace?: string;
|
|
11
22
|
};
|
|
12
23
|
|
|
24
|
+
// Global options for Redux DevTools integration across all handlers.
|
|
13
25
|
export type GlobalDevToolsOptions = {
|
|
26
|
+
// Whether DevTools integration is enabled globally.
|
|
14
27
|
enabled?: boolean;
|
|
15
28
|
};
|
|
16
29
|
|
|
30
|
+
// Main configuration object for the Status Quo system.
|
|
17
31
|
export type StatusQuoConfig<T = unknown> = {
|
|
32
|
+
// Global DevTools configuration.
|
|
18
33
|
devTools?: GlobalDevToolsOptions;
|
|
34
|
+
// Global distinct emission configuration.
|
|
19
35
|
distinct?: DistinctOptions<T>;
|
|
20
36
|
};
|
|
21
37
|
|
|
38
|
+
// Internal representation of resolved distinct options.
|
|
22
39
|
type ResolvedDistinctOptions<T = unknown> = {
|
|
40
|
+
// Final enabled status for distinct updates.
|
|
23
41
|
enabled: boolean;
|
|
42
|
+
// Final comparator function to use for equality checks.
|
|
24
43
|
comparator: DistinctComparator<T>;
|
|
25
44
|
};
|
|
26
45
|
|
|
46
|
+
// Internal representation of resolved DevTools options.
|
|
27
47
|
type ResolvedDevToolsOptions = {
|
|
48
|
+
// Final enabled status for DevTools.
|
|
28
49
|
enabled: boolean;
|
|
29
50
|
};
|
|
30
51
|
|
|
52
|
+
// Internal representation of the full resolved configuration.
|
|
31
53
|
type ResolvedStatusQuoConfig = {
|
|
54
|
+
// Resolved global DevTools configuration.
|
|
32
55
|
devTools: ResolvedDevToolsOptions;
|
|
56
|
+
// Resolved global distinct emission configuration.
|
|
33
57
|
distinct: ResolvedDistinctOptions;
|
|
34
58
|
};
|
|
35
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Default comparator function that uses referential equality (Object.is)
|
|
62
|
+
* and falls back to JSON stringification for structural equality.
|
|
63
|
+
*/
|
|
36
64
|
function distinctAsJson(previous: unknown, next: unknown) {
|
|
65
|
+
// Fast path for referential equality.
|
|
37
66
|
if (Object.is(previous, next)) {
|
|
38
67
|
return true;
|
|
39
68
|
}
|
|
40
69
|
|
|
70
|
+
// Structural comparison using JSON stringification as a fallback.
|
|
41
71
|
try {
|
|
42
72
|
return JSON.stringify(previous) === JSON.stringify(next);
|
|
43
73
|
} catch {
|
|
74
|
+
// If stringification fails, assume they are not equal.
|
|
44
75
|
return false;
|
|
45
76
|
}
|
|
46
77
|
}
|
|
47
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Creates the default Status Quo configuration.
|
|
81
|
+
*/
|
|
48
82
|
function createDefaultStatusQuoConfig(): ResolvedStatusQuoConfig {
|
|
49
83
|
return {
|
|
84
|
+
// DevTools integration is disabled by default.
|
|
50
85
|
devTools: {
|
|
51
86
|
enabled: false,
|
|
52
87
|
},
|
|
88
|
+
// Distinct emission is enabled by default with JSON-based structural equality.
|
|
53
89
|
distinct: {
|
|
54
90
|
enabled: true,
|
|
55
91
|
comparator: distinctAsJson,
|
|
@@ -57,13 +93,20 @@ function createDefaultStatusQuoConfig(): ResolvedStatusQuoConfig {
|
|
|
57
93
|
};
|
|
58
94
|
}
|
|
59
95
|
|
|
96
|
+
// Global configuration instance, initialized with defaults.
|
|
60
97
|
let statusQuoConfig = createDefaultStatusQuoConfig();
|
|
61
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Global setup function to configure Status Quo.
|
|
101
|
+
*/
|
|
62
102
|
export function setupStatusQuo<T = unknown>(config: StatusQuoConfig<T> = {}) {
|
|
103
|
+
// Merge the provided configuration with the current global config.
|
|
63
104
|
statusQuoConfig = {
|
|
105
|
+
// Update global DevTools status.
|
|
64
106
|
devTools: {
|
|
65
107
|
enabled: config.devTools?.enabled ?? false,
|
|
66
108
|
},
|
|
109
|
+
// Update global distinct emission status and comparator.
|
|
67
110
|
distinct: {
|
|
68
111
|
enabled: config.distinct?.enabled ?? true,
|
|
69
112
|
comparator: (config.distinct?.comparator ?? distinctAsJson) as DistinctComparator,
|
|
@@ -71,23 +114,39 @@ export function setupStatusQuo<T = unknown>(config: StatusQuoConfig<T> = {}) {
|
|
|
71
114
|
};
|
|
72
115
|
}
|
|
73
116
|
|
|
117
|
+
/**
|
|
118
|
+
* Resolves the final distinct options for a specific state handler.
|
|
119
|
+
* Combines global configuration with handler-specific overrides.
|
|
120
|
+
*/
|
|
74
121
|
export function resolveDistinctOptions<T>(
|
|
122
|
+
// Handler-specific distinct options.
|
|
75
123
|
options?: DistinctOptions<T>,
|
|
124
|
+
// Boolean flag often used to override or determine enabled status.
|
|
76
125
|
useDistinctUntilChanged?: boolean
|
|
77
126
|
): ResolvedDistinctOptions<T> {
|
|
78
127
|
return {
|
|
128
|
+
// Determine the enabled status for distinct emissions.
|
|
79
129
|
enabled: options?.enabled ?? useDistinctUntilChanged ?? statusQuoConfig.distinct.enabled,
|
|
130
|
+
// Determine the comparator function to use.
|
|
80
131
|
comparator: (options?.comparator ??
|
|
81
132
|
statusQuoConfig.distinct.comparator),
|
|
82
133
|
};
|
|
83
134
|
}
|
|
84
135
|
|
|
136
|
+
/**
|
|
137
|
+
* Resolves the final DevTools options for a specific state handler.
|
|
138
|
+
* Combines global configuration with handler-specific overrides.
|
|
139
|
+
*/
|
|
85
140
|
export function resolveDevToolsOptions(options?: DevToolsOptions): ResolvedDevToolsOptions {
|
|
86
141
|
return {
|
|
142
|
+
// Determine the enabled status for DevTools integration.
|
|
87
143
|
enabled: options?.enabled ?? statusQuoConfig.devTools.enabled,
|
|
88
144
|
};
|
|
89
145
|
}
|
|
90
146
|
|
|
147
|
+
/**
|
|
148
|
+
* Returns a copy of the current global configuration.
|
|
149
|
+
*/
|
|
91
150
|
export function getStatusQuoConfig() {
|
|
92
151
|
return {
|
|
93
152
|
devTools: {
|
|
@@ -100,7 +159,11 @@ export function getStatusQuoConfig() {
|
|
|
100
159
|
} as ResolvedStatusQuoConfig;
|
|
101
160
|
}
|
|
102
161
|
|
|
103
|
-
/**
|
|
162
|
+
/**
|
|
163
|
+
* Resets the Status Quo configuration to its default values.
|
|
164
|
+
* Useful for ensuring test isolation in unit tests.
|
|
165
|
+
* @internal testing helper
|
|
166
|
+
*/
|
|
104
167
|
export function resetStatusQuoForTests() {
|
|
105
168
|
statusQuoConfig = createDefaultStatusQuoConfig();
|
|
106
169
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main entry point for the Status Quo state management library.
|
|
3
|
+
* Exports core functionality for both store and React integration.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Import internal setup functions and core state handlers.
|
|
1
7
|
import { setupStatusQuo } from './config/status-quo-config.js';
|
|
2
8
|
import {
|
|
3
9
|
BaseStateHandler,
|
|
4
10
|
makeStateSingleton,
|
|
11
|
+
NativeStateHandler,
|
|
5
12
|
ObservableStateHandler,
|
|
6
13
|
SignalStateHandler,
|
|
7
14
|
} from './store';
|
|
8
15
|
|
|
16
|
+
// Import necessary types for external use.
|
|
9
17
|
import type {
|
|
10
18
|
DevToolsOptions,
|
|
11
19
|
GlobalDevToolsOptions,
|
|
@@ -16,21 +24,42 @@ import type {
|
|
|
16
24
|
import type { StateSingleton, StateSingletonOptions } from './store';
|
|
17
25
|
import type { StateSubscriptionHandler } from './types/types.js';
|
|
18
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Core state management functions and classes.
|
|
29
|
+
*/
|
|
19
30
|
export {
|
|
31
|
+
// Abstract base class for all state handlers.
|
|
20
32
|
BaseStateHandler,
|
|
33
|
+
// Factory function for creating singleton state handlers.
|
|
21
34
|
makeStateSingleton,
|
|
35
|
+
// Lightweight state handler using plain JavaScript.
|
|
36
|
+
NativeStateHandler,
|
|
37
|
+
// State handler powered by RxJS BehaviorSubjects.
|
|
22
38
|
ObservableStateHandler,
|
|
39
|
+
// Global configuration function for Status Quo.
|
|
23
40
|
setupStatusQuo,
|
|
41
|
+
// State handler powered by Preact Signals.
|
|
24
42
|
SignalStateHandler,
|
|
25
43
|
};
|
|
26
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Type definitions for public API.
|
|
47
|
+
*/
|
|
27
48
|
export type {
|
|
49
|
+
// Options for Redux DevTools integration.
|
|
28
50
|
DevToolsOptions,
|
|
51
|
+
// Global options for Redux DevTools.
|
|
29
52
|
GlobalDevToolsOptions,
|
|
53
|
+
// Function signature for state equality comparisons.
|
|
30
54
|
DistinctComparator,
|
|
55
|
+
// Options for configuring distinct updates.
|
|
31
56
|
DistinctOptions,
|
|
57
|
+
// Interface representing a singleton state handler.
|
|
32
58
|
StateSingleton,
|
|
59
|
+
// Options for configuring state singleton instances.
|
|
33
60
|
StateSingletonOptions,
|
|
61
|
+
// Interface representing a standard state subscription handler.
|
|
34
62
|
StateSubscriptionHandler,
|
|
63
|
+
// Main configuration object for the Status Quo system.
|
|
35
64
|
StatusQuoConfig,
|
|
36
65
|
};
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
useProvidedStateActions,
|
|
7
7
|
useProvidedStateHandler,
|
|
8
8
|
useProvidedStateSubscription,
|
|
9
|
-
} from '../
|
|
9
|
+
} from '../index.js';
|
|
10
10
|
|
|
11
11
|
import type { StateSubscriptionHandler } from '../../../types/types.js';
|
|
12
12
|
|
|
@@ -279,7 +279,7 @@ describe('StateProvider', () => {
|
|
|
279
279
|
act(() => {
|
|
280
280
|
root.render(<MissingProviderConsumer />);
|
|
281
281
|
});
|
|
282
|
-
}).toThrow('
|
|
282
|
+
}).toThrow('useProvidedStateHandler must be used within a StateProvider');
|
|
283
283
|
|
|
284
284
|
consoleErrorSpy.mockRestore();
|
|
285
285
|
});
|
|
@@ -331,6 +331,23 @@ const FullSubscriptionConsumer = ({
|
|
|
331
331
|
return <span>{state.user.name}</span>;
|
|
332
332
|
};
|
|
333
333
|
|
|
334
|
+
type ObjectSelectorConsumerProps = {
|
|
335
|
+
createStateHandler: () => StateSubscriptionHandler<TestState, TestActions>;
|
|
336
|
+
onRender: (value: { counter: number; userName: string }) => void;
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
const ObjectSelectorConsumer = ({ createStateHandler, onRender }: ObjectSelectorConsumerProps) => {
|
|
340
|
+
const stateHandler = useStateHandler(createStateHandler, []);
|
|
341
|
+
const [summary] = useStateSubscription(stateHandler, (state) => ({
|
|
342
|
+
counter: state.counter,
|
|
343
|
+
userName: state.user.name,
|
|
344
|
+
}));
|
|
345
|
+
|
|
346
|
+
onRender(summary);
|
|
347
|
+
|
|
348
|
+
return <span>{summary.userName}</span>;
|
|
349
|
+
};
|
|
350
|
+
|
|
334
351
|
type StrictModeMirrorFactoryConsumerProps = {
|
|
335
352
|
createStateHandler: () => StateSubscriptionHandler<CounterMirrorState, CounterMirrorActions>;
|
|
336
353
|
onRender: (count: number) => void;
|
|
@@ -569,6 +586,55 @@ describe('Selector hooks', () => {
|
|
|
569
586
|
expect(renderSpy).toHaveBeenCalledTimes(2);
|
|
570
587
|
});
|
|
571
588
|
|
|
589
|
+
it('useStateSubscription should cache object selector snapshots within one store version', () => {
|
|
590
|
+
let stateHandler: TestStateHandler | null = null;
|
|
591
|
+
const createStateHandler = jest.fn(() => {
|
|
592
|
+
if (!stateHandler) {
|
|
593
|
+
stateHandler = new TestStateHandler({
|
|
594
|
+
user: { name: 'Ada' },
|
|
595
|
+
counter: 0,
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
return stateHandler;
|
|
600
|
+
});
|
|
601
|
+
const renderSpy = jest.fn();
|
|
602
|
+
|
|
603
|
+
expect(() => {
|
|
604
|
+
act(() => {
|
|
605
|
+
root.render(
|
|
606
|
+
<ObjectSelectorConsumer createStateHandler={createStateHandler} onRender={renderSpy} />
|
|
607
|
+
);
|
|
608
|
+
});
|
|
609
|
+
}).not.toThrow();
|
|
610
|
+
|
|
611
|
+
expect(renderSpy).toHaveBeenCalledTimes(1);
|
|
612
|
+
expect(renderSpy).toHaveBeenLastCalledWith({
|
|
613
|
+
counter: 0,
|
|
614
|
+
userName: 'Ada',
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
act(() => {
|
|
618
|
+
stateHandler!.getActions().increment();
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
expect(renderSpy).toHaveBeenCalledTimes(2);
|
|
622
|
+
expect(renderSpy).toHaveBeenLastCalledWith({
|
|
623
|
+
counter: 1,
|
|
624
|
+
userName: 'Ada',
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
act(() => {
|
|
628
|
+
stateHandler!.getActions().setName('Grace');
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
expect(renderSpy).toHaveBeenCalledTimes(3);
|
|
632
|
+
expect(renderSpy).toHaveBeenLastCalledWith({
|
|
633
|
+
counter: 1,
|
|
634
|
+
userName: 'Grace',
|
|
635
|
+
});
|
|
636
|
+
});
|
|
637
|
+
|
|
572
638
|
it('useStateSubscription should return full snapshot when no selector is provided', () => {
|
|
573
639
|
let stateHandler: TestStateHandler | null = null;
|
|
574
640
|
const createStateHandler = jest.fn(() => {
|
package/src/react/hooks/index.ts
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Export available hooks for React components.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Export hook to access the actions of a state handler.
|
|
6
|
+
export { useProvidedStateActions, useStateActions } from './state-actions.js';
|
|
7
|
+
// Export hook to create and manage a state handler within a component.
|
|
2
8
|
export { useStateFactory } from './state-factory.js';
|
|
9
|
+
// Export hook to manage a state handler's lifecycle within a component's reference.
|
|
3
10
|
export { useStateHandler } from './state-handler.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
useProvidedStateHandler,
|
|
8
|
-
useProvidedStateSubscription,
|
|
9
|
-
} from './state-provider.js';
|
|
11
|
+
// Export hook and component to use a state handler through React Context.
|
|
12
|
+
export { StateProvider, useProvidedStateHandler } from './state-provider.js';
|
|
13
|
+
// Export hook to use a singleton state handler.
|
|
10
14
|
export { useStateSingleton } from './state-singleton.js';
|
|
11
|
-
|
|
15
|
+
// Export hook to subscribe to a state handler and receive its state updates.
|
|
16
|
+
export { useProvidedStateSubscription, useStateSubscription } from './state-subscription.js';
|