@veams/status-quo 1.1.0 → 1.2.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 (62) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +187 -16
  3. package/dist/hooks/__tests__/state-selector.spec.d.ts +4 -0
  4. package/dist/hooks/__tests__/state-selector.spec.js +384 -0
  5. package/dist/hooks/__tests__/state-selector.spec.js.map +1 -0
  6. package/dist/hooks/__tests__/state-singleton.spec.d.ts +4 -0
  7. package/dist/hooks/__tests__/state-singleton.spec.js +97 -0
  8. package/dist/hooks/__tests__/state-singleton.spec.js.map +1 -0
  9. package/dist/hooks/index.d.ts +3 -0
  10. package/dist/hooks/index.js +3 -0
  11. package/dist/hooks/index.js.map +1 -1
  12. package/dist/hooks/state-actions.d.ts +2 -0
  13. package/dist/hooks/state-actions.js +5 -0
  14. package/dist/hooks/state-actions.js.map +1 -0
  15. package/dist/hooks/state-factory.d.ts +5 -0
  16. package/dist/hooks/state-factory.js +10 -6
  17. package/dist/hooks/state-factory.js.map +1 -1
  18. package/dist/hooks/state-handler.d.ts +2 -0
  19. package/dist/hooks/state-handler.js +9 -0
  20. package/dist/hooks/state-handler.js.map +1 -0
  21. package/dist/hooks/state-singleton.d.ts +4 -0
  22. package/dist/hooks/state-singleton.js +3 -5
  23. package/dist/hooks/state-singleton.js.map +1 -1
  24. package/dist/hooks/state-subscription-selector.d.ts +5 -0
  25. package/dist/hooks/state-subscription-selector.js +29 -0
  26. package/dist/hooks/state-subscription-selector.js.map +1 -0
  27. package/dist/hooks/state-subscription.d.ts +8 -1
  28. package/dist/hooks/state-subscription.js +49 -10
  29. package/dist/hooks/state-subscription.js.map +1 -1
  30. package/dist/index.d.ts +4 -4
  31. package/dist/index.js +2 -2
  32. package/dist/index.js.map +1 -1
  33. package/dist/store/__tests__/observable-state-handler.spec.js +4 -0
  34. package/dist/store/__tests__/observable-state-handler.spec.js.map +1 -1
  35. package/dist/store/index.d.ts +1 -1
  36. package/dist/store/observable-state-handler.d.ts +2 -0
  37. package/dist/store/observable-state-handler.js +5 -1
  38. package/dist/store/observable-state-handler.js.map +1 -1
  39. package/dist/store/state-singleton.d.ts +4 -1
  40. package/dist/store/state-singleton.js +11 -2
  41. package/dist/store/state-singleton.js.map +1 -1
  42. package/docs/assets/index-Ci4A1zSh.js +187 -0
  43. package/docs/assets/index-WFTLEHd3.css +1 -0
  44. package/docs/assets/statusquo-logo-8GVRbxpc.png +0 -0
  45. package/docs/index.html +13 -0
  46. package/package.json +1 -1
  47. package/playground/src/App.tsx +249 -48
  48. package/playground/src/styles.css +127 -0
  49. package/src/hooks/__tests__/state-selector.spec.tsx +607 -0
  50. package/src/hooks/__tests__/state-singleton.spec.tsx +151 -0
  51. package/src/hooks/index.ts +3 -0
  52. package/src/hooks/state-actions.tsx +7 -0
  53. package/src/hooks/state-factory.tsx +32 -6
  54. package/src/hooks/state-handler.tsx +16 -0
  55. package/src/hooks/state-singleton.tsx +17 -7
  56. package/src/hooks/state-subscription-selector.tsx +70 -0
  57. package/src/hooks/state-subscription.tsx +98 -21
  58. package/src/index.ts +12 -3
  59. package/src/store/__tests__/observable-state-handler.spec.ts +6 -0
  60. package/src/store/index.ts +1 -1
  61. package/src/store/observable-state-handler.ts +6 -1
  62. package/src/store/state-singleton.ts +21 -3
package/CHANGELOG.md CHANGED
@@ -4,6 +4,49 @@ 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.2.0] - 2026-02-25
8
+
9
+ ### Added
10
+ - New composable hook APIs:
11
+ - `useStateHandler(factory, params?)`
12
+ - `useStateActions(handler)`
13
+ - `useStateSubscription(source, selector?, isEqual?)`
14
+ - Selector + equality support across shortcut hooks:
15
+ - `useStateFactory(factory, selector?, isEqual?, params?)`
16
+ - `useStateSingleton(singleton, selector?, isEqual?)`
17
+ - `StateSingletonOptions` with `destroyOnNoConsumers?: boolean` (default: `true`).
18
+ - Ref-counted singleton lifecycle handling so shared singleton instances are only destroyed when the last consumer unmounts.
19
+ - New hook test coverage for:
20
+ - composed API usage (`useStateHandler + useStateActions + useStateSubscription`)
21
+ - selector subscriptions with and without custom equality
22
+ - singleton subscription behavior and `destroyOnNoConsumers: false`
23
+ - full-snapshot subscription behavior.
24
+
25
+ ### Changed
26
+ - `useStateFactory` now composes `useStateHandler` + `useStateSubscription` internally.
27
+ - `useStateSubscription` now:
28
+ - accepts either `StateSubscriptionHandler` or `StateSingleton`
29
+ - returns `[selectedState, actions]`
30
+ - supports selector/equality without requiring separate selector-specific hook APIs.
31
+ - `useStateSingleton` is now a shortcut over `useStateSubscription(singleton, selector?, isEqual?)`.
32
+ - `makeStateSingleton` now accepts options and manages instance destruction through explicit lifecycle controls.
33
+ - Public exports extended:
34
+ - Added `useStateHandler`, `useStateActions`, `useStateSubscription`
35
+ - Added exported `StateSingletonOptions` type.
36
+ - Observable handler naming aligned with signal convention:
37
+ - Added `getObservable(key)` as the canonical API.
38
+
39
+ ### Deprecated
40
+ - `ObservableStateHandler#getObservableItem(key)` is now deprecated in favor of `getObservable(key)`.
41
+
42
+ ### Documentation
43
+ - README rewritten with a dedicated API guide that documents base composition, shortcut composition, singleton lifecycle options, and usage examples.
44
+ - Playground documentation significantly expanded:
45
+ - dedicated API section grouped by base composition, shortcut composition, and helper functions
46
+ - clearer singleton lifecycle explanation and examples
47
+ - improved card hierarchy and spacing for readability
48
+ - responsive/toggleable navigation for better mobile usage.
49
+
7
50
  ## [1.0.0] - 2026-02-17
8
51
 
9
52
  ### Added
@@ -27,4 +70,5 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
27
70
  - From: `super({ initialState, devTools: { ... } })`
28
71
  - To: `super({ initialState, options: { devTools: { ... } } })`
29
72
 
73
+ [1.2.0]: https://github.com/Veams/status-quo/compare/v1.0.0...v1.2.0
30
74
  [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
- [![npm version](https://img.shields.io/npm/v/@veams/status-quo)](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
+ [![npm version](https://img.shields.io/npm/v/@veams/status-quo)](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. [Devtools](#devtools)
23
- 10. [Cleanup](#cleanup)
24
- 11. [API Reference](#api-reference)
25
- 12. [Migration](#migration)
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
 
@@ -87,22 +88,54 @@ Both are built on `BaseStateHandler`, which provides the shared lifecycle and de
87
88
 
88
89
  ## Hooks
89
90
 
90
- - `useStateFactory(factory, deps)`
91
- - Creates a handler instance per component and subscribes to its snapshot.
92
- - Suitable for per-component or per-instance state.
93
- - `useStateSingleton(singleton)`
94
- - Uses a shared singleton handler across components.
91
+ Use `useStateHandler + useStateActions + useStateSubscription` as the base composition.
92
+ `useStateFactory` and `useStateSingleton` are shortcut APIs over that composition.
93
+ For full signatures and practical examples, see [API Guide](#api-guide).
94
+
95
+ - `useStateHandler(factory, params)`
96
+ - Creates and memoizes one handler instance per component.
97
+ - `useStateActions(handler)`
98
+ - Returns actions without subscribing to state.
99
+ - `useStateSubscription(handlerOrSingleton, selector?, isEqual?)`
100
+ - Subscribes to full state or a selected slice and returns `[state, actions]`.
101
+ - `useStateFactory(factory, selector?, isEqual?, params?)`
102
+ - Shortcut for `useStateHandler + useStateSubscription`.
103
+ - `useStateSingleton(singleton, selector?, isEqual?)`
104
+ - Shortcut for `useStateSubscription(singleton, selector?, isEqual?)`.
105
+
106
+ Recommended composition:
107
+
108
+ ```ts
109
+ const handler = useStateHandler(createUserStore, []);
110
+ const actions = useStateActions(handler);
111
+ const [name] = useStateSubscription(handler, (state) => state.user.name);
112
+
113
+ const [singletonName] = useStateSubscription(UserSingleton, (state) => state.user.name);
114
+ ```
95
115
 
96
116
  ## Singletons
97
117
 
118
+ Use singletons for shared state across multiple components.
119
+
98
120
  ```ts
99
121
  import { makeStateSingleton, useStateSingleton } from '@veams/status-quo';
100
122
 
123
+ // Default behavior: singleton is destroyed when the last consumer unmounts.
101
124
  const CounterSingleton = makeStateSingleton(() => new CounterStore());
102
125
 
103
126
  const [state, actions] = useStateSingleton(CounterSingleton);
104
127
  ```
105
128
 
129
+ Keep a singleton instance alive across unmounts:
130
+
131
+ ```ts
132
+ const PersistentCounterSingleton = makeStateSingleton(() => new CounterStore(), {
133
+ destroyOnNoConsumers: false,
134
+ });
135
+ ```
136
+
137
+ Use this for app-level stores that should survive route/component unmounts. Keep the default for stores that should release resources when unused.
138
+
106
139
  ## Composition
107
140
 
108
141
  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 +188,129 @@ class AppSignalStore extends SignalStateHandler<AppState, AppActions> {
155
188
  }
156
189
  ```
157
190
 
191
+ ## API Guide
192
+
193
+ This section documents the primary public API with behavior notes and usage examples.
194
+
195
+ ### `useStateHandler(factory, params?)`
196
+
197
+ Creates one handler instance per component mount and returns it.
198
+
199
+ - `factory`: function returning a `StateSubscriptionHandler`
200
+ - `params`: optional factory params tuple
201
+ - lifecycle note: params are applied when the handler instance is created for that mount
202
+
203
+ ```ts
204
+ const handler = useStateHandler(createUserStore, []);
205
+ ```
206
+
207
+ ### `useStateActions(handler)`
208
+
209
+ Returns actions from a handler without subscribing to state changes.
210
+ Use this in action-only components to avoid rerenders from state updates.
211
+
212
+ ```ts
213
+ const handler = useStateHandler(createUserStore, []);
214
+ const actions = useStateActions(handler);
215
+ ```
216
+
217
+ ### `useStateSubscription(source, selector?, isEqual?)`
218
+
219
+ Subscribes to either a handler instance or a singleton and returns `[selectedState, actions]`.
220
+
221
+ - `source`: `StateSubscriptionHandler` or `StateSingleton`
222
+ - `selector`: optional projection function; defaults to identity
223
+ - `isEqual`: optional equality function; defaults to `Object.is`
224
+
225
+ Full snapshot subscription:
226
+
227
+ ```ts
228
+ const handler = useStateHandler(createUserStore, []);
229
+ const [state, actions] = useStateSubscription(handler);
230
+ ```
231
+
232
+ Selector subscription:
233
+
234
+ ```ts
235
+ const [name, actions] = useStateSubscription(
236
+ handler,
237
+ (state) => state.user.name
238
+ );
239
+ ```
240
+
241
+ Selector with custom equality:
242
+
243
+ ```ts
244
+ const [profile] = useStateSubscription(
245
+ handler,
246
+ (state) => state.user.profile,
247
+ (current, next) => current.id === next.id && current.role === next.role
248
+ );
249
+ ```
250
+
251
+ Singleton source:
252
+
253
+ ```ts
254
+ const [session, actions] = useStateSubscription(SessionSingleton);
255
+ ```
256
+
257
+ Lifecycle note for singleton sources:
258
+ - Consumers are ref-counted.
259
+ - The singleton instance is only destroyed when the last consumer unmounts and `destroyOnNoConsumers !== false`.
260
+
261
+ ### `useStateFactory(factory, selector?, isEqual?, params?)`
262
+
263
+ Shortcut API for `useStateHandler + useStateSubscription`.
264
+
265
+ - `useStateFactory(factory, params)`
266
+ - `useStateFactory(factory, selector, params)`
267
+ - `useStateFactory(factory, selector, isEqual, params)`
268
+
269
+ ```ts
270
+ const [state, actions] = useStateFactory(createUserStore, []);
271
+ const [name] = useStateFactory(createUserStore, (state) => state.user.name, []);
272
+ const [profile] = useStateFactory(
273
+ createUserStore,
274
+ (state) => state.user.profile,
275
+ (current, next) => current.id === next.id,
276
+ []
277
+ );
278
+ ```
279
+
280
+ ### `makeStateSingleton(factory, options?)`
281
+
282
+ Creates a shared singleton provider for a handler instance.
283
+
284
+ ```ts
285
+ const UserSingleton = makeStateSingleton(() => new UserStore());
286
+ ```
287
+
288
+ Options:
289
+
290
+ ```ts
291
+ type StateSingletonOptions = {
292
+ destroyOnNoConsumers?: boolean; // default: true
293
+ };
294
+ ```
295
+
296
+ - `true` (default): destroy instance after last consumer unmounts
297
+ - `false`: keep instance alive across periods with zero consumers
298
+
299
+ ```ts
300
+ const PersistentUserSingleton = makeStateSingleton(() => new UserStore(), {
301
+ destroyOnNoConsumers: false,
302
+ });
303
+ ```
304
+
305
+ ### `useStateSingleton(singleton, selector?, isEqual?)`
306
+
307
+ Shortcut API for `useStateSubscription(singleton, selector?, isEqual?)`.
308
+
309
+ ```ts
310
+ const [state, actions] = useStateSingleton(UserSingleton);
311
+ const [name] = useStateSingleton(UserSingleton, (state) => state.user.name);
312
+ ```
313
+
158
314
  ## Devtools
159
315
 
160
316
  Enable Redux Devtools integration with `options.devTools`:
@@ -249,7 +405,7 @@ Public methods:
249
405
 
250
406
  - `getStateAsObservable(options?: { useDistinctUntilChanged?: boolean }): Observable<S>`
251
407
  - `getStateItemAsObservable(key: keyof S): Observable<S[keyof S]>`
252
- - `getObservableItem(key: keyof S): Observable<S[keyof S]>`
408
+ - `getObservable(key: keyof S): Observable<S[keyof S]>`
253
409
  - `subscribe(listener: () => void): () => void`
254
410
 
255
411
  Notes:
@@ -286,18 +442,33 @@ Notes:
286
442
  ### `makeStateSingleton`
287
443
 
288
444
  ```ts
445
+ type StateSingletonOptions = {
446
+ destroyOnNoConsumers?: boolean; // default: true
447
+ };
448
+
289
449
  function makeStateSingleton<S, A>(
290
- factory: () => StateSubscriptionHandler<S, A>
450
+ factory: () => StateSubscriptionHandler<S, A>,
451
+ options?: StateSingletonOptions
291
452
  ): {
292
453
  getInstance: () => StateSubscriptionHandler<S, A>;
293
454
  }
294
455
  ```
295
456
 
457
+ Lifecycle behavior:
458
+ - `destroyOnNoConsumers: true` (default): destroy and recreate singleton instances with mount lifecycle.
459
+ - `destroyOnNoConsumers: false`: keep the same singleton instance alive when no component is subscribed.
460
+
296
461
  ### Hooks
297
462
 
298
- - `useStateFactory<V, A, P extends unknown[]>(factory: (...args: P) => StateSubscriptionHandler<V, A>, params?: P)`
463
+ - `useStateHandler<V, A, P extends unknown[]>(factory: (...args: P) => StateSubscriptionHandler<V, A>, params?: P)`
464
+ - Returns `StateSubscriptionHandler<V, A>`.
465
+ - `useStateActions<V, A>(handler: StateSubscriptionHandler<V, A>)`
466
+ - Returns `A`.
467
+ - `useStateSubscription<V, A, Sel = V>(source: StateSubscriptionHandler<V, A> | StateSingleton<V, A>, selector?: (state: V) => Sel, isEqual?: (current: Sel, next: Sel) => boolean)`
468
+ - Returns `[state, actions]`.
469
+ - `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
470
  - Returns `[state, actions]`.
300
- - `useStateSingleton<V, A>(singleton: StateSingleton<V, A>)`
471
+ - `useStateSingleton<V, A, Sel = V>(singleton: StateSingleton<V, A>, selector?: (state: V) => Sel, isEqual?: (current: Sel, next: Sel) => boolean)`
301
472
  - Returns `[state, actions]`.
302
473
 
303
474
  ## Migration
@@ -0,0 +1,4 @@
1
+ declare global {
2
+ var IS_REACT_ACT_ENVIRONMENT: boolean;
3
+ }
4
+ export {};