@veams/status-quo 1.1.0 → 1.3.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/CHANGELOG.md +53 -0
- package/README.md +252 -18
- package/dist/config/status-quo-config.d.ts +21 -0
- package/dist/config/status-quo-config.js +48 -0
- package/dist/config/status-quo-config.js.map +1 -0
- package/dist/hooks/__tests__/state-selector.spec.d.ts +4 -0
- package/dist/hooks/__tests__/state-selector.spec.js +384 -0
- package/dist/hooks/__tests__/state-selector.spec.js.map +1 -0
- package/dist/hooks/__tests__/state-singleton.spec.d.ts +4 -0
- package/dist/hooks/__tests__/state-singleton.spec.js +97 -0
- package/dist/hooks/__tests__/state-singleton.spec.js.map +1 -0
- package/dist/hooks/index.d.ts +3 -0
- package/dist/hooks/index.js +3 -0
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/state-actions.d.ts +2 -0
- package/dist/hooks/state-actions.js +5 -0
- package/dist/hooks/state-actions.js.map +1 -0
- package/dist/hooks/state-factory.d.ts +5 -0
- package/dist/hooks/state-factory.js +10 -6
- package/dist/hooks/state-factory.js.map +1 -1
- package/dist/hooks/state-handler.d.ts +2 -0
- package/dist/hooks/state-handler.js +9 -0
- package/dist/hooks/state-handler.js.map +1 -0
- package/dist/hooks/state-singleton.d.ts +4 -0
- package/dist/hooks/state-singleton.js +3 -5
- package/dist/hooks/state-singleton.js.map +1 -1
- package/dist/hooks/state-subscription-selector.d.ts +5 -0
- package/dist/hooks/state-subscription-selector.js +29 -0
- package/dist/hooks/state-subscription-selector.js.map +1 -0
- package/dist/hooks/state-subscription.d.ts +8 -1
- package/dist/hooks/state-subscription.js +49 -10
- package/dist/hooks/state-subscription.js.map +1 -1
- package/dist/index.d.ts +7 -5
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/store/__tests__/observable-state-handler.spec.js +68 -5
- package/dist/store/__tests__/observable-state-handler.spec.js.map +1 -1
- package/dist/store/__tests__/signal-state-handler.spec.js +64 -5
- package/dist/store/__tests__/signal-state-handler.spec.js.map +1 -1
- package/dist/store/index.d.ts +1 -1
- package/dist/store/observable-state-handler.d.ts +7 -2
- package/dist/store/observable-state-handler.js +17 -22
- package/dist/store/observable-state-handler.js.map +1 -1
- package/dist/store/signal-state-handler.d.ts +3 -1
- package/dist/store/signal-state-handler.js +5 -14
- package/dist/store/signal-state-handler.js.map +1 -1
- package/dist/store/state-singleton.d.ts +4 -1
- package/dist/store/state-singleton.js +11 -2
- package/dist/store/state-singleton.js.map +1 -1
- package/docs/assets/index-BBmpszOW.css +1 -0
- package/docs/assets/index-Cf8El_RO.js +194 -0
- package/docs/assets/statusquo-logo-8GVRbxpc.png +0 -0
- package/docs/index.html +13 -0
- package/package.json +1 -1
- package/playground/src/App.tsx +269 -48
- package/playground/src/styles.css +123 -0
- package/src/config/status-quo-config.ts +76 -0
- package/src/hooks/__tests__/state-selector.spec.tsx +607 -0
- package/src/hooks/__tests__/state-singleton.spec.tsx +151 -0
- package/src/hooks/index.ts +3 -0
- package/src/hooks/state-actions.tsx +7 -0
- package/src/hooks/state-factory.tsx +32 -6
- package/src/hooks/state-handler.tsx +16 -0
- package/src/hooks/state-singleton.tsx +17 -7
- package/src/hooks/state-subscription-selector.tsx +70 -0
- package/src/hooks/state-subscription.tsx +98 -21
- package/src/index.ts +23 -4
- package/src/store/__tests__/observable-state-handler.spec.ts +98 -11
- package/src/store/__tests__/signal-state-handler.spec.ts +92 -11
- package/src/store/index.ts +1 -1
- package/src/store/observable-state-handler.ts +25 -27
- package/src/store/signal-state-handler.ts +7 -16
- package/src/store/state-singleton.ts +21 -3
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,57 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
6
6
|
|
|
7
|
+
## [1.3.0] - 2026-02-25
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- `setupStatusQuo(config)` runtime setup for global distinct update behavior.
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- Handler distinct comparison now supports global defaults (`setupStatusQuo`) with per-handler override precedence.
|
|
14
|
+
|
|
15
|
+
## [1.2.0] - 2026-02-25
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- New composable hook APIs:
|
|
19
|
+
- `useStateHandler(factory, params?)`
|
|
20
|
+
- `useStateActions(handler)`
|
|
21
|
+
- `useStateSubscription(source, selector?, isEqual?)`
|
|
22
|
+
- Selector + equality support across shortcut hooks:
|
|
23
|
+
- `useStateFactory(factory, selector?, isEqual?, params?)`
|
|
24
|
+
- `useStateSingleton(singleton, selector?, isEqual?)`
|
|
25
|
+
- `StateSingletonOptions` with `destroyOnNoConsumers?: boolean` (default: `true`).
|
|
26
|
+
- Ref-counted singleton lifecycle handling so shared singleton instances are only destroyed when the last consumer unmounts.
|
|
27
|
+
- New hook test coverage for:
|
|
28
|
+
- composed API usage (`useStateHandler + useStateActions + useStateSubscription`)
|
|
29
|
+
- selector subscriptions with and without custom equality
|
|
30
|
+
- singleton subscription behavior and `destroyOnNoConsumers: false`
|
|
31
|
+
- full-snapshot subscription behavior.
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
- `useStateFactory` now composes `useStateHandler` + `useStateSubscription` internally.
|
|
35
|
+
- `useStateSubscription` now:
|
|
36
|
+
- accepts either `StateSubscriptionHandler` or `StateSingleton`
|
|
37
|
+
- returns `[selectedState, actions]`
|
|
38
|
+
- supports selector/equality without requiring separate selector-specific hook APIs.
|
|
39
|
+
- `useStateSingleton` is now a shortcut over `useStateSubscription(singleton, selector?, isEqual?)`.
|
|
40
|
+
- `makeStateSingleton` now accepts options and manages instance destruction through explicit lifecycle controls.
|
|
41
|
+
- Public exports extended:
|
|
42
|
+
- Added `useStateHandler`, `useStateActions`, `useStateSubscription`
|
|
43
|
+
- Added exported `StateSingletonOptions` type.
|
|
44
|
+
- Observable handler naming aligned with signal convention:
|
|
45
|
+
- Added `getObservable(key)` as the canonical API.
|
|
46
|
+
|
|
47
|
+
### Deprecated
|
|
48
|
+
- `ObservableStateHandler#getObservableItem(key)` is now deprecated in favor of `getObservable(key)`.
|
|
49
|
+
|
|
50
|
+
### Documentation
|
|
51
|
+
- README rewritten with a dedicated API guide that documents base composition, shortcut composition, singleton lifecycle options, and usage examples.
|
|
52
|
+
- Playground documentation significantly expanded:
|
|
53
|
+
- dedicated API section grouped by base composition, shortcut composition, and helper functions
|
|
54
|
+
- clearer singleton lifecycle explanation and examples
|
|
55
|
+
- improved card hierarchy and spacing for readability
|
|
56
|
+
- responsive/toggleable navigation for better mobile usage.
|
|
57
|
+
|
|
7
58
|
## [1.0.0] - 2026-02-17
|
|
8
59
|
|
|
9
60
|
### Added
|
|
@@ -27,4 +78,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
27
78
|
- From: `super({ initialState, devTools: { ... } })`
|
|
28
79
|
- To: `super({ initialState, options: { devTools: { ... } } })`
|
|
29
80
|
|
|
81
|
+
[1.3.0]: https://github.com/Veams/status-quo/compare/v1.2.0...v1.3.0
|
|
82
|
+
[1.2.0]: https://github.com/Veams/status-quo/compare/v1.0.0...v1.2.0
|
|
30
83
|
[1.0.0]: https://github.com/Veams/status-quo/releases/tag/v1.0.0
|
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
# @veams/status-quo
|
|
2
|
-
[](https://www.npmjs.com/package/@veams/status-quo)
|
|
3
|
-
|
|
4
1
|
<center>
|
|
5
2
|
<img src="assets/statusquo-logo.png" width="200" alt="StatusQuo Logo" style="margin: 0 auto;">
|
|
6
3
|
</center>
|
|
7
4
|
|
|
5
|
+
# @veams/status-quo
|
|
6
|
+
[](https://www.npmjs.com/package/@veams/status-quo)
|
|
7
|
+
|
|
8
8
|
The manager to rule your state.
|
|
9
9
|
|
|
10
10
|
This page mirrors the demo content and adds a full API reference.
|
|
@@ -19,10 +19,11 @@ This page mirrors the demo content and adds a full API reference.
|
|
|
19
19
|
6. [Hooks](#hooks)
|
|
20
20
|
7. [Singletons](#singletons)
|
|
21
21
|
8. [Composition](#composition)
|
|
22
|
-
9. [
|
|
23
|
-
10. [
|
|
24
|
-
11. [
|
|
25
|
-
12. [
|
|
22
|
+
9. [API Guide](#api-guide)
|
|
23
|
+
10. [Devtools](#devtools)
|
|
24
|
+
11. [Cleanup](#cleanup)
|
|
25
|
+
12. [API Reference](#api-reference)
|
|
26
|
+
13. [Migration](#migration)
|
|
26
27
|
|
|
27
28
|
## Overview
|
|
28
29
|
|
|
@@ -76,6 +77,19 @@ class CounterStore extends ObservableStateHandler<CounterState, CounterActions>
|
|
|
76
77
|
const [state, actions] = useStateFactory(() => new CounterStore(), []);
|
|
77
78
|
```
|
|
78
79
|
|
|
80
|
+
Optional global setup (e.g. with a custom deep-equality comparator):
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
import equal from 'fast-deep-equal';
|
|
84
|
+
import { setupStatusQuo } from '@veams/status-quo';
|
|
85
|
+
|
|
86
|
+
setupStatusQuo({
|
|
87
|
+
distinct: {
|
|
88
|
+
comparator: equal,
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
79
93
|
## Handlers
|
|
80
94
|
|
|
81
95
|
StatusQuo provides two handler implementations with the same public interface:
|
|
@@ -87,22 +101,54 @@ Both are built on `BaseStateHandler`, which provides the shared lifecycle and de
|
|
|
87
101
|
|
|
88
102
|
## Hooks
|
|
89
103
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
104
|
+
Use `useStateHandler + useStateActions + useStateSubscription` as the base composition.
|
|
105
|
+
`useStateFactory` and `useStateSingleton` are shortcut APIs over that composition.
|
|
106
|
+
For full signatures and practical examples, see [API Guide](#api-guide).
|
|
107
|
+
|
|
108
|
+
- `useStateHandler(factory, params)`
|
|
109
|
+
- Creates and memoizes one handler instance per component.
|
|
110
|
+
- `useStateActions(handler)`
|
|
111
|
+
- Returns actions without subscribing to state.
|
|
112
|
+
- `useStateSubscription(handlerOrSingleton, selector?, isEqual?)`
|
|
113
|
+
- Subscribes to full state or a selected slice and returns `[state, actions]`.
|
|
114
|
+
- `useStateFactory(factory, selector?, isEqual?, params?)`
|
|
115
|
+
- Shortcut for `useStateHandler + useStateSubscription`.
|
|
116
|
+
- `useStateSingleton(singleton, selector?, isEqual?)`
|
|
117
|
+
- Shortcut for `useStateSubscription(singleton, selector?, isEqual?)`.
|
|
118
|
+
|
|
119
|
+
Recommended composition:
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
const handler = useStateHandler(createUserStore, []);
|
|
123
|
+
const actions = useStateActions(handler);
|
|
124
|
+
const [name] = useStateSubscription(handler, (state) => state.user.name);
|
|
125
|
+
|
|
126
|
+
const [singletonName] = useStateSubscription(UserSingleton, (state) => state.user.name);
|
|
127
|
+
```
|
|
95
128
|
|
|
96
129
|
## Singletons
|
|
97
130
|
|
|
131
|
+
Use singletons for shared state across multiple components.
|
|
132
|
+
|
|
98
133
|
```ts
|
|
99
134
|
import { makeStateSingleton, useStateSingleton } from '@veams/status-quo';
|
|
100
135
|
|
|
136
|
+
// Default behavior: singleton is destroyed when the last consumer unmounts.
|
|
101
137
|
const CounterSingleton = makeStateSingleton(() => new CounterStore());
|
|
102
138
|
|
|
103
139
|
const [state, actions] = useStateSingleton(CounterSingleton);
|
|
104
140
|
```
|
|
105
141
|
|
|
142
|
+
Keep a singleton instance alive across unmounts:
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
const PersistentCounterSingleton = makeStateSingleton(() => new CounterStore(), {
|
|
146
|
+
destroyOnNoConsumers: false,
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Use this for app-level stores that should survive route/component unmounts. Keep the default for stores that should release resources when unused.
|
|
151
|
+
|
|
106
152
|
## Composition
|
|
107
153
|
|
|
108
154
|
Use only the slice you need. RxJS makes multi-source composition powerful and declarative with operators like `combineLatest`, `switchMap`, or `debounceTime`. Signals can derive values with `computed` and wire them into a parent store via `bindSubscribable`.
|
|
@@ -155,6 +201,154 @@ class AppSignalStore extends SignalStateHandler<AppState, AppActions> {
|
|
|
155
201
|
}
|
|
156
202
|
```
|
|
157
203
|
|
|
204
|
+
## API Guide
|
|
205
|
+
|
|
206
|
+
This section documents the primary public API with behavior notes and usage examples.
|
|
207
|
+
|
|
208
|
+
### `setupStatusQuo(config?)`
|
|
209
|
+
|
|
210
|
+
Sets global runtime defaults for distinct update behavior.
|
|
211
|
+
Per-handler options still override the global setup.
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
type StatusQuoConfig = {
|
|
215
|
+
distinct?: {
|
|
216
|
+
enabled?: boolean; // default: true
|
|
217
|
+
comparator?: (previous: unknown, next: unknown) => boolean; // default: JSON compare
|
|
218
|
+
};
|
|
219
|
+
};
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
import equal from 'fast-deep-equal';
|
|
224
|
+
import { setupStatusQuo } from '@veams/status-quo';
|
|
225
|
+
|
|
226
|
+
setupStatusQuo({
|
|
227
|
+
distinct: {
|
|
228
|
+
comparator: equal,
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### `useStateHandler(factory, params?)`
|
|
234
|
+
|
|
235
|
+
Creates one handler instance per component mount and returns it.
|
|
236
|
+
|
|
237
|
+
- `factory`: function returning a `StateSubscriptionHandler`
|
|
238
|
+
- `params`: optional factory params tuple
|
|
239
|
+
- lifecycle note: params are applied when the handler instance is created for that mount
|
|
240
|
+
|
|
241
|
+
```ts
|
|
242
|
+
const handler = useStateHandler(createUserStore, []);
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### `useStateActions(handler)`
|
|
246
|
+
|
|
247
|
+
Returns actions from a handler without subscribing to state changes.
|
|
248
|
+
Use this in action-only components to avoid rerenders from state updates.
|
|
249
|
+
|
|
250
|
+
```ts
|
|
251
|
+
const handler = useStateHandler(createUserStore, []);
|
|
252
|
+
const actions = useStateActions(handler);
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### `useStateSubscription(source, selector?, isEqual?)`
|
|
256
|
+
|
|
257
|
+
Subscribes to either a handler instance or a singleton and returns `[selectedState, actions]`.
|
|
258
|
+
|
|
259
|
+
- `source`: `StateSubscriptionHandler` or `StateSingleton`
|
|
260
|
+
- `selector`: optional projection function; defaults to identity
|
|
261
|
+
- `isEqual`: optional equality function; defaults to `Object.is`
|
|
262
|
+
|
|
263
|
+
Full snapshot subscription:
|
|
264
|
+
|
|
265
|
+
```ts
|
|
266
|
+
const handler = useStateHandler(createUserStore, []);
|
|
267
|
+
const [state, actions] = useStateSubscription(handler);
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Selector subscription:
|
|
271
|
+
|
|
272
|
+
```ts
|
|
273
|
+
const [name, actions] = useStateSubscription(
|
|
274
|
+
handler,
|
|
275
|
+
(state) => state.user.name
|
|
276
|
+
);
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Selector with custom equality:
|
|
280
|
+
|
|
281
|
+
```ts
|
|
282
|
+
const [profile] = useStateSubscription(
|
|
283
|
+
handler,
|
|
284
|
+
(state) => state.user.profile,
|
|
285
|
+
(current, next) => current.id === next.id && current.role === next.role
|
|
286
|
+
);
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
Singleton source:
|
|
290
|
+
|
|
291
|
+
```ts
|
|
292
|
+
const [session, actions] = useStateSubscription(SessionSingleton);
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Lifecycle note for singleton sources:
|
|
296
|
+
- Consumers are ref-counted.
|
|
297
|
+
- The singleton instance is only destroyed when the last consumer unmounts and `destroyOnNoConsumers !== false`.
|
|
298
|
+
|
|
299
|
+
### `useStateFactory(factory, selector?, isEqual?, params?)`
|
|
300
|
+
|
|
301
|
+
Shortcut API for `useStateHandler + useStateSubscription`.
|
|
302
|
+
|
|
303
|
+
- `useStateFactory(factory, params)`
|
|
304
|
+
- `useStateFactory(factory, selector, params)`
|
|
305
|
+
- `useStateFactory(factory, selector, isEqual, params)`
|
|
306
|
+
|
|
307
|
+
```ts
|
|
308
|
+
const [state, actions] = useStateFactory(createUserStore, []);
|
|
309
|
+
const [name] = useStateFactory(createUserStore, (state) => state.user.name, []);
|
|
310
|
+
const [profile] = useStateFactory(
|
|
311
|
+
createUserStore,
|
|
312
|
+
(state) => state.user.profile,
|
|
313
|
+
(current, next) => current.id === next.id,
|
|
314
|
+
[]
|
|
315
|
+
);
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### `makeStateSingleton(factory, options?)`
|
|
319
|
+
|
|
320
|
+
Creates a shared singleton provider for a handler instance.
|
|
321
|
+
|
|
322
|
+
```ts
|
|
323
|
+
const UserSingleton = makeStateSingleton(() => new UserStore());
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Options:
|
|
327
|
+
|
|
328
|
+
```ts
|
|
329
|
+
type StateSingletonOptions = {
|
|
330
|
+
destroyOnNoConsumers?: boolean; // default: true
|
|
331
|
+
};
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
- `true` (default): destroy instance after last consumer unmounts
|
|
335
|
+
- `false`: keep instance alive across periods with zero consumers
|
|
336
|
+
|
|
337
|
+
```ts
|
|
338
|
+
const PersistentUserSingleton = makeStateSingleton(() => new UserStore(), {
|
|
339
|
+
destroyOnNoConsumers: false,
|
|
340
|
+
});
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### `useStateSingleton(singleton, selector?, isEqual?)`
|
|
344
|
+
|
|
345
|
+
Shortcut API for `useStateSubscription(singleton, selector?, isEqual?)`.
|
|
346
|
+
|
|
347
|
+
```ts
|
|
348
|
+
const [state, actions] = useStateSingleton(UserSingleton);
|
|
349
|
+
const [name] = useStateSingleton(UserSingleton, (state) => state.user.name);
|
|
350
|
+
```
|
|
351
|
+
|
|
158
352
|
## Devtools
|
|
159
353
|
|
|
160
354
|
Enable Redux Devtools integration with `options.devTools`:
|
|
@@ -241,6 +435,11 @@ protected constructor({
|
|
|
241
435
|
initialState: S;
|
|
242
436
|
options?: {
|
|
243
437
|
devTools?: { enabled?: boolean; namespace: string };
|
|
438
|
+
distinct?: {
|
|
439
|
+
enabled?: boolean;
|
|
440
|
+
comparator?: (previous: S, next: S) => boolean;
|
|
441
|
+
};
|
|
442
|
+
useDistinctUntilChanged?: boolean; // optional override
|
|
244
443
|
};
|
|
245
444
|
})
|
|
246
445
|
```
|
|
@@ -249,11 +448,12 @@ Public methods:
|
|
|
249
448
|
|
|
250
449
|
- `getStateAsObservable(options?: { useDistinctUntilChanged?: boolean }): Observable<S>`
|
|
251
450
|
- `getStateItemAsObservable(key: keyof S): Observable<S[keyof S]>`
|
|
252
|
-
- `
|
|
451
|
+
- `getObservable(key: keyof S): Observable<S[keyof S]>`
|
|
253
452
|
- `subscribe(listener: () => void): () => void`
|
|
254
453
|
|
|
255
454
|
Notes:
|
|
256
|
-
- The observable stream uses `distinctUntilChanged` by default
|
|
455
|
+
- The observable stream uses `distinctUntilChanged` by default.
|
|
456
|
+
- Distinct behavior can be configured globally via `setupStatusQuo` or per handler via `options.distinct`.
|
|
257
457
|
- `subscribe` does not fire for the initial value; it only fires on subsequent changes.
|
|
258
458
|
|
|
259
459
|
### `SignalStateHandler<S, A>`
|
|
@@ -270,6 +470,10 @@ protected constructor({
|
|
|
270
470
|
initialState: S;
|
|
271
471
|
options?: {
|
|
272
472
|
devTools?: { enabled?: boolean; namespace: string };
|
|
473
|
+
distinct?: {
|
|
474
|
+
enabled?: boolean;
|
|
475
|
+
comparator?: (previous: S, next: S) => boolean;
|
|
476
|
+
};
|
|
273
477
|
useDistinctUntilChanged?: boolean;
|
|
274
478
|
};
|
|
275
479
|
})
|
|
@@ -281,23 +485,53 @@ Public methods:
|
|
|
281
485
|
- `subscribe(listener: () => void): () => void`
|
|
282
486
|
|
|
283
487
|
Notes:
|
|
284
|
-
-
|
|
488
|
+
- Distinct behavior defaults to enabled.
|
|
489
|
+
- Configure it globally via `setupStatusQuo` or per handler via `options.distinct`.
|
|
490
|
+
- `useDistinctUntilChanged` remains available as a shorthand enable/disable override.
|
|
491
|
+
|
|
492
|
+
### `setupStatusQuo`
|
|
493
|
+
|
|
494
|
+
```ts
|
|
495
|
+
type StatusQuoConfig = {
|
|
496
|
+
distinct?: {
|
|
497
|
+
enabled?: boolean;
|
|
498
|
+
comparator?: (previous: unknown, next: unknown) => boolean;
|
|
499
|
+
};
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
function setupStatusQuo(config?: StatusQuoConfig): void
|
|
503
|
+
```
|
|
285
504
|
|
|
286
505
|
### `makeStateSingleton`
|
|
287
506
|
|
|
288
507
|
```ts
|
|
508
|
+
type StateSingletonOptions = {
|
|
509
|
+
destroyOnNoConsumers?: boolean; // default: true
|
|
510
|
+
};
|
|
511
|
+
|
|
289
512
|
function makeStateSingleton<S, A>(
|
|
290
|
-
factory: () => StateSubscriptionHandler<S, A
|
|
513
|
+
factory: () => StateSubscriptionHandler<S, A>,
|
|
514
|
+
options?: StateSingletonOptions
|
|
291
515
|
): {
|
|
292
516
|
getInstance: () => StateSubscriptionHandler<S, A>;
|
|
293
517
|
}
|
|
294
518
|
```
|
|
295
519
|
|
|
520
|
+
Lifecycle behavior:
|
|
521
|
+
- `destroyOnNoConsumers: true` (default): destroy and recreate singleton instances with mount lifecycle.
|
|
522
|
+
- `destroyOnNoConsumers: false`: keep the same singleton instance alive when no component is subscribed.
|
|
523
|
+
|
|
296
524
|
### Hooks
|
|
297
525
|
|
|
298
|
-
- `
|
|
526
|
+
- `useStateHandler<V, A, P extends unknown[]>(factory: (...args: P) => StateSubscriptionHandler<V, A>, params?: P)`
|
|
527
|
+
- Returns `StateSubscriptionHandler<V, A>`.
|
|
528
|
+
- `useStateActions<V, A>(handler: StateSubscriptionHandler<V, A>)`
|
|
529
|
+
- Returns `A`.
|
|
530
|
+
- `useStateSubscription<V, A, Sel = V>(source: StateSubscriptionHandler<V, A> | StateSingleton<V, A>, selector?: (state: V) => Sel, isEqual?: (current: Sel, next: Sel) => boolean)`
|
|
531
|
+
- Returns `[state, actions]`.
|
|
532
|
+
- `useStateFactory<V, A, P extends unknown[], Sel = V>(factory: (...args: P) => StateSubscriptionHandler<V, A>, selector?: (state: V) => Sel, isEqual?: (current: Sel, next: Sel) => boolean, params?: P)`
|
|
299
533
|
- Returns `[state, actions]`.
|
|
300
|
-
- `useStateSingleton<V, A>(singleton: StateSingleton<V, A
|
|
534
|
+
- `useStateSingleton<V, A, Sel = V>(singleton: StateSingleton<V, A>, selector?: (state: V) => Sel, isEqual?: (current: Sel, next: Sel) => boolean)`
|
|
301
535
|
- Returns `[state, actions]`.
|
|
302
536
|
|
|
303
537
|
## Migration
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type DistinctComparator<T = unknown> = (previous: T, next: T) => boolean;
|
|
2
|
+
export type DistinctOptions<T = unknown> = {
|
|
3
|
+
enabled?: boolean;
|
|
4
|
+
comparator?: DistinctComparator<T>;
|
|
5
|
+
};
|
|
6
|
+
export type StatusQuoConfig<T = unknown> = {
|
|
7
|
+
distinct?: DistinctOptions<T>;
|
|
8
|
+
};
|
|
9
|
+
type ResolvedDistinctOptions<T = unknown> = {
|
|
10
|
+
enabled: boolean;
|
|
11
|
+
comparator: DistinctComparator<T>;
|
|
12
|
+
};
|
|
13
|
+
type ResolvedStatusQuoConfig = {
|
|
14
|
+
distinct: ResolvedDistinctOptions;
|
|
15
|
+
};
|
|
16
|
+
export declare function setupStatusQuo<T = unknown>(config?: StatusQuoConfig<T>): void;
|
|
17
|
+
export declare function resolveDistinctOptions<T>(options?: DistinctOptions<T>, useDistinctUntilChanged?: boolean): ResolvedDistinctOptions<T>;
|
|
18
|
+
export declare function getStatusQuoConfig(): ResolvedStatusQuoConfig;
|
|
19
|
+
/** @internal testing helper */
|
|
20
|
+
export declare function resetStatusQuoForTests(): void;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
function distinctAsJson(previous, next) {
|
|
2
|
+
if (Object.is(previous, next)) {
|
|
3
|
+
return true;
|
|
4
|
+
}
|
|
5
|
+
try {
|
|
6
|
+
return JSON.stringify(previous) === JSON.stringify(next);
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function createDefaultStatusQuoConfig() {
|
|
13
|
+
return {
|
|
14
|
+
distinct: {
|
|
15
|
+
enabled: true,
|
|
16
|
+
comparator: distinctAsJson,
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
let statusQuoConfig = createDefaultStatusQuoConfig();
|
|
21
|
+
export function setupStatusQuo(config = {}) {
|
|
22
|
+
statusQuoConfig = {
|
|
23
|
+
distinct: {
|
|
24
|
+
enabled: config.distinct?.enabled ?? true,
|
|
25
|
+
comparator: (config.distinct?.comparator ?? distinctAsJson),
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export function resolveDistinctOptions(options, useDistinctUntilChanged) {
|
|
30
|
+
return {
|
|
31
|
+
enabled: options?.enabled ?? useDistinctUntilChanged ?? statusQuoConfig.distinct.enabled,
|
|
32
|
+
comparator: (options?.comparator ??
|
|
33
|
+
statusQuoConfig.distinct.comparator),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export function getStatusQuoConfig() {
|
|
37
|
+
return {
|
|
38
|
+
distinct: {
|
|
39
|
+
enabled: statusQuoConfig.distinct.enabled,
|
|
40
|
+
comparator: statusQuoConfig.distinct.comparator,
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/** @internal testing helper */
|
|
45
|
+
export function resetStatusQuoForTests() {
|
|
46
|
+
statusQuoConfig = createDefaultStatusQuoConfig();
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=status-quo-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status-quo-config.js","sourceRoot":"","sources":["../../src/config/status-quo-config.ts"],"names":[],"mappings":"AAoBA,SAAS,cAAc,CAAC,QAAiB,EAAE,IAAa;IACtD,IAAI,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,CAAC;QAC9B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IAC3D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,SAAS,4BAA4B;IACnC,OAAO;QACL,QAAQ,EAAE;YACR,OAAO,EAAE,IAAI;YACb,UAAU,EAAE,cAAc;SAC3B;KACF,CAAC;AACJ,CAAC;AAED,IAAI,eAAe,GAAG,4BAA4B,EAAE,CAAC;AAErD,MAAM,UAAU,cAAc,CAAc,SAA6B,EAAE;IACzE,eAAe,GAAG;QAChB,QAAQ,EAAE;YACR,OAAO,EAAE,MAAM,CAAC,QAAQ,EAAE,OAAO,IAAI,IAAI;YACzC,UAAU,EAAE,CAAC,MAAM,CAAC,QAAQ,EAAE,UAAU,IAAI,cAAc,CAAuB;SAClF;KACF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,sBAAsB,CACpC,OAA4B,EAC5B,uBAAiC;IAEjC,OAAO;QACL,OAAO,EAAE,OAAO,EAAE,OAAO,IAAI,uBAAuB,IAAI,eAAe,CAAC,QAAQ,CAAC,OAAO;QACxF,UAAU,EAAE,CAAC,OAAO,EAAE,UAAU;YAC9B,eAAe,CAAC,QAAQ,CAAC,UAAU,CAA0B;KAChE,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,kBAAkB;IAChC,OAAO;QACL,QAAQ,EAAE;YACR,OAAO,EAAE,eAAe,CAAC,QAAQ,CAAC,OAAO;YACzC,UAAU,EAAE,eAAe,CAAC,QAAQ,CAAC,UAAU;SAChD;KACyB,CAAC;AAC/B,CAAC;AAED,+BAA+B;AAC/B,MAAM,UAAU,sBAAsB;IACpC,eAAe,GAAG,4BAA4B,EAAE,CAAC;AACnD,CAAC"}
|