@veams/status-quo 1.5.0 → 1.6.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 (126) hide show
  1. package/.turbo/turbo-build.log +12 -0
  2. package/.turbo/turbo-check$colon$types.log +4 -0
  3. package/.turbo/turbo-docs$colon$build.log +14 -0
  4. package/.turbo/turbo-lint.log +8 -0
  5. package/.turbo/turbo-test.log +15 -0
  6. package/CHANGELOG.md +24 -3
  7. package/README.md +217 -41
  8. package/dist/config/status-quo-config.d.ts +13 -0
  9. package/dist/config/status-quo-config.js +14 -0
  10. package/dist/config/status-quo-config.js.map +1 -1
  11. package/dist/hooks/__tests__/state-provider.spec.d.ts +4 -0
  12. package/dist/hooks/__tests__/state-provider.spec.js +179 -0
  13. package/dist/hooks/__tests__/state-provider.spec.js.map +1 -0
  14. package/dist/hooks/__tests__/state-selector.spec.js +11 -12
  15. package/dist/hooks/__tests__/state-selector.spec.js.map +1 -1
  16. package/dist/hooks/__tests__/state-singleton.spec.js +10 -11
  17. package/dist/hooks/__tests__/state-singleton.spec.js.map +1 -1
  18. package/dist/hooks/index.d.ts +1 -0
  19. package/dist/hooks/index.js +1 -0
  20. package/dist/hooks/index.js.map +1 -1
  21. package/dist/hooks/state-factory.js.map +1 -1
  22. package/dist/hooks/state-provider.d.ts +14 -0
  23. package/dist/hooks/state-provider.js +24 -0
  24. package/dist/hooks/state-provider.js.map +1 -0
  25. package/dist/hooks/state-subscription-selector.js +6 -2
  26. package/dist/hooks/state-subscription-selector.js.map +1 -1
  27. package/dist/hooks/state-subscription.js +1 -1
  28. package/dist/hooks/state-subscription.js.map +1 -1
  29. package/dist/index.d.ts +4 -5
  30. package/dist/index.js +2 -3
  31. package/dist/index.js.map +1 -1
  32. package/dist/react/hooks/__tests__/state-provider.spec.d.ts +4 -0
  33. package/dist/react/hooks/__tests__/state-provider.spec.js +179 -0
  34. package/dist/react/hooks/__tests__/state-provider.spec.js.map +1 -0
  35. package/dist/react/hooks/__tests__/state-selector.spec.d.ts +4 -0
  36. package/dist/react/hooks/__tests__/state-selector.spec.js +499 -0
  37. package/dist/react/hooks/__tests__/state-selector.spec.js.map +1 -0
  38. package/dist/react/hooks/__tests__/state-singleton.spec.d.ts +4 -0
  39. package/dist/react/hooks/__tests__/state-singleton.spec.js +96 -0
  40. package/dist/react/hooks/__tests__/state-singleton.spec.js.map +1 -0
  41. package/{src/hooks/index.ts → dist/react/hooks/index.d.ts} +1 -0
  42. package/dist/react/hooks/index.js +7 -0
  43. package/dist/react/hooks/index.js.map +1 -0
  44. package/dist/react/hooks/state-actions.d.ts +2 -0
  45. package/dist/react/hooks/state-actions.js +5 -0
  46. package/dist/react/hooks/state-actions.js.map +1 -0
  47. package/dist/react/hooks/state-factory.d.ts +7 -0
  48. package/dist/react/hooks/state-factory.js +13 -0
  49. package/dist/react/hooks/state-factory.js.map +1 -0
  50. package/dist/react/hooks/state-handler.d.ts +2 -0
  51. package/dist/react/hooks/state-handler.js +9 -0
  52. package/dist/react/hooks/state-handler.js.map +1 -0
  53. package/dist/react/hooks/state-provider.d.ts +14 -0
  54. package/dist/react/hooks/state-provider.js +24 -0
  55. package/dist/react/hooks/state-provider.js.map +1 -0
  56. package/dist/react/hooks/state-singleton.d.ts +6 -0
  57. package/dist/react/hooks/state-singleton.js +7 -0
  58. package/dist/react/hooks/state-singleton.js.map +1 -0
  59. package/dist/react/hooks/state-subscription-selector.d.ts +3 -0
  60. package/dist/react/hooks/state-subscription-selector.js +63 -0
  61. package/dist/react/hooks/state-subscription-selector.js.map +1 -0
  62. package/dist/react/hooks/state-subscription.d.ts +9 -0
  63. package/dist/react/hooks/state-subscription.js +53 -0
  64. package/dist/react/hooks/state-subscription.js.map +1 -0
  65. package/dist/react/index.d.ts +1 -0
  66. package/dist/react/index.js +2 -0
  67. package/dist/react/index.js.map +1 -0
  68. package/dist/store/__tests__/observable-state-handler.spec.js +66 -11
  69. package/dist/store/__tests__/observable-state-handler.spec.js.map +1 -1
  70. package/dist/store/__tests__/signal-state-handler.spec.js.map +1 -1
  71. package/dist/store/base-state-handler.d.ts +4 -6
  72. package/dist/store/base-state-handler.js +10 -9
  73. package/dist/store/base-state-handler.js.map +1 -1
  74. package/dist/store/dev-tools.js +0 -3
  75. package/dist/store/dev-tools.js.map +1 -1
  76. package/dist/store/observable-state-handler.d.ts +4 -10
  77. package/dist/store/observable-state-handler.js +4 -11
  78. package/dist/store/observable-state-handler.js.map +1 -1
  79. package/dist/store/signal-state-handler.d.ts +2 -5
  80. package/dist/store/signal-state-handler.js +3 -2
  81. package/dist/store/signal-state-handler.js.map +1 -1
  82. package/dist/store/state-singleton.js +1 -1
  83. package/dist/store/state-singleton.js.map +1 -1
  84. package/eslint.config.mjs +75 -0
  85. package/package.json +18 -18
  86. package/src/config/status-quo-config.ts +31 -1
  87. package/src/index.ts +11 -15
  88. package/src/react/hooks/__tests__/state-provider.spec.tsx +286 -0
  89. package/src/{hooks → react/hooks}/__tests__/state-selector.spec.tsx +52 -44
  90. package/src/{hooks → react/hooks}/__tests__/state-singleton.spec.tsx +21 -20
  91. package/src/react/hooks/index.ts +11 -0
  92. package/src/{hooks → react/hooks}/state-actions.tsx +1 -1
  93. package/src/{hooks → react/hooks}/state-factory.tsx +2 -2
  94. package/src/{hooks → react/hooks}/state-handler.tsx +1 -1
  95. package/src/react/hooks/state-provider.tsx +56 -0
  96. package/src/{hooks → react/hooks}/state-singleton.tsx +1 -1
  97. package/src/{hooks → react/hooks}/state-subscription-selector.tsx +15 -6
  98. package/src/{hooks → react/hooks}/state-subscription.tsx +5 -9
  99. package/src/react/index.ts +1 -0
  100. package/src/store/__tests__/observable-state-handler.spec.ts +92 -13
  101. package/src/store/__tests__/signal-state-handler.spec.ts +5 -1
  102. package/src/store/base-state-handler.ts +17 -24
  103. package/src/store/dev-tools.ts +3 -3
  104. package/src/store/observable-state-handler.ts +12 -22
  105. package/src/store/signal-state-handler.ts +11 -8
  106. package/src/store/state-singleton.ts +1 -1
  107. package/tsconfig.json +2 -3
  108. package/.eslintrc.cjs +0 -132
  109. package/.github/workflows/pages.yml +0 -46
  110. package/.github/workflows/release.yml +0 -33
  111. package/.nvmrc +0 -1
  112. package/.prettierrc +0 -7
  113. package/docs/assets/index-BBmpszOW.css +0 -1
  114. package/docs/assets/index-Cf8El_RO.js +0 -194
  115. package/docs/assets/statusquo-logo-8GVRbxpc.png +0 -0
  116. package/docs/index.html +0 -13
  117. package/playground/index.html +0 -12
  118. package/playground/src/App.tsx +0 -699
  119. package/playground/src/assets/philosophy-agnostic.svg +0 -18
  120. package/playground/src/assets/philosophy-separation.svg +0 -13
  121. package/playground/src/assets/philosophy-swap.svg +0 -17
  122. package/playground/src/assets/statusquo-logo.png +0 -0
  123. package/playground/src/main.tsx +0 -19
  124. package/playground/src/styles.css +0 -534
  125. package/playground/tsconfig.json +0 -12
  126. package/playground/vite.config.ts +0 -18
@@ -0,0 +1,12 @@
1
+
2
+ > @veams/status-quo@1.5.1 build
3
+ > cross-env NODE_ENV=production npm-run-all compile
4
+
5
+
6
+ > @veams/status-quo@1.5.1 compile
7
+ > npm-run-all bundle:ts
8
+
9
+
10
+ > @veams/status-quo@1.5.1 bundle:ts
11
+ > tsc --project tsconfig.json
12
+
@@ -0,0 +1,4 @@
1
+
2
+ > @veams/status-quo@1.5.1 check:types
3
+ > tsc --skipLibCheck --noEmit
4
+
@@ -0,0 +1,14 @@
1
+
2
+ > @veams/status-quo@1.5.1 docs:build
3
+ > vite build --config playground/vite.config.ts
4
+
5
+ vite v5.4.21 building for production...
6
+ transforming...
7
+ ✓ 285 modules transformed.
8
+ rendering chunks...
9
+ computing gzip size...
10
+ ../docs/index.html 0.41 kB │ gzip: 0.27 kB
11
+ ../docs/assets/statusquo-logo-8GVRbxpc.png 87.06 kB
12
+ ../docs/assets/index-BBmpszOW.css 8.73 kB │ gzip: 2.60 kB
13
+ ../docs/assets/index-BsFqA6fh.js 268.01 kB │ gzip: 83.23 kB
14
+ ✓ built in 1.18s
@@ -0,0 +1,8 @@
1
+
2
+ > @veams/status-quo@1.5.1 lint
3
+ > npm run lint:ts
4
+
5
+
6
+ > @veams/status-quo@1.5.1 lint:ts
7
+ > eslint --fix "src/**/*.{tsx,ts}"
8
+
@@ -0,0 +1,15 @@
1
+
2
+ > @veams/status-quo@1.5.1 test
3
+ > cross-env NODE_ENV=test jest --config jest.config.cjs
4
+
5
+ PASS src/react/hooks/__tests__/state-selector.spec.tsx
6
+ PASS src/react/hooks/__tests__/state-provider.spec.tsx
7
+ PASS src/react/hooks/__tests__/state-singleton.spec.tsx
8
+ PASS src/store/__tests__/observable-state-handler.spec.ts
9
+ PASS src/store/__tests__/signal-state-handler.spec.ts
10
+
11
+ Test Suites: 5 passed, 5 total
12
+ Tests: 40 passed, 40 total
13
+ Snapshots: 0 total
14
+ Time: 1.703 s
15
+ Ran all test suites.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,27 @@ 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
+ ## [Unreleased]
8
+
9
+ ### Added
10
+ - `StateProvider` for sharing one handler instance across a scoped React subtree.
11
+ - `useProvidedStateHandler()`, `useProvidedStateActions()`, and `useProvidedStateSubscription()` for provider-scoped composition.
12
+ - `setupStatusQuo({ devTools: { enabled } })` for global Redux DevTools defaults.
13
+
14
+ ### Changed
15
+ - React-specific exports now live under `@veams/status-quo/react`.
16
+ - `options.devTools.namespace` is now optional and falls back to the handler class name.
17
+ - `ObservableStateHandler#getObservable(options?)` is now the canonical API for the full state stream.
18
+ - `ObservableStateHandler#getObservableItem(key)` is now the canonical API for keyed item streams.
19
+ - `makeStateSingleton()` now keeps singleton instances alive by default unless `destroyOnNoConsumers: true` is set.
20
+
21
+ ### Removed
22
+ - React hook and provider exports from the root `@veams/status-quo` entrypoint in favor of `@veams/status-quo/react`.
23
+ - `@veams/status-quo/hooks` in favor of `@veams/status-quo/react`
24
+ - `ObservableStateHandler#getStateAsObservable(options?)`
25
+ - `ObservableStateHandler#getStateItemAsObservable(key)`
26
+ - keyed `ObservableStateHandler#getObservable(key)` in favor of `getObservableItem(key)`
27
+
7
28
  ## [1.3.0] - 2026-02-25
8
29
 
9
30
  ### Added
@@ -78,6 +99,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
78
99
  - From: `super({ initialState, devTools: { ... } })`
79
100
  - To: `super({ initialState, options: { devTools: { ... } } })`
80
101
 
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
83
- [1.0.0]: https://github.com/Veams/status-quo/releases/tag/v1.0.0
102
+ [1.3.0]: https://github.com/Veams/veams/compare/v1.2.0...v1.3.0
103
+ [1.2.0]: https://github.com/Veams/veams/compare/v1.0.0...v1.2.0
104
+ [1.0.0]: https://github.com/Veams/veams/releases/tag/v1.0.0
package/README.md CHANGED
@@ -7,23 +7,24 @@
7
7
 
8
8
  The manager to rule your state.
9
9
 
10
- This page mirrors the demo content and adds a full API reference.
10
+ This README summarizes the package API. The full routed documentation lives in VEAMS Documentation.
11
11
 
12
12
  ## Table of Contents
13
13
 
14
14
  1. [Overview](#overview)
15
15
  2. [Philosophy](#philosophy)
16
- 3. [Demo](#demo)
16
+ 3. [Docs](#docs)
17
17
  4. [Quickstart](#quickstart)
18
18
  5. [Handlers](#handlers)
19
19
  6. [Hooks](#hooks)
20
- 7. [Singletons](#singletons)
21
- 8. [Composition](#composition)
22
- 9. [API Guide](#api-guide)
23
- 10. [Devtools](#devtools)
24
- 11. [Cleanup](#cleanup)
25
- 12. [API Reference](#api-reference)
26
- 13. [Migration](#migration)
20
+ 7. [Providers](#providers)
21
+ 8. [Singletons](#singletons)
22
+ 9. [Composition](#composition)
23
+ 10. [API Guide](#api-guide)
24
+ 11. [Devtools](#devtools)
25
+ 12. [Cleanup](#cleanup)
26
+ 13. [API Reference](#api-reference)
27
+ 14. [Migration](#migration)
27
28
 
28
29
  ## Overview
29
30
 
@@ -35,11 +36,11 @@ StatusQuo is a small, framework-agnostic state layer that focuses on explicit li
35
36
  - Separate view and state. Handlers own transitions and expose actions; views subscribe to snapshots.
36
37
  - Framework-agnostic core. Business logic lives outside the UI library; hooks provide the glue.
37
38
 
38
- ## Demo
39
+ ## Docs
39
40
 
40
- Live docs and demo:
41
+ Live docs:
41
42
 
42
- [https://veams.github.io/status-quo/](https://veams.github.io/status-quo/)
43
+ [https://veams.github.io/status-quo/packages/status-quo/getting-started](https://veams.github.io/status-quo/packages/status-quo/getting-started)
43
44
 
44
45
  ## Quickstart
45
46
 
@@ -52,7 +53,8 @@ npm install @veams/status-quo rxjs @preact/signals-core
52
53
  Create a store and use it in a component:
53
54
 
54
55
  ```ts
55
- import { ObservableStateHandler, useStateFactory } from '@veams/status-quo';
56
+ import { ObservableStateHandler } from '@veams/status-quo';
57
+ import { useStateFactory } from '@veams/status-quo/react';
56
58
 
57
59
  type CounterState = { count: number };
58
60
 
@@ -77,13 +79,16 @@ class CounterStore extends ObservableStateHandler<CounterState, CounterActions>
77
79
  const [state, actions] = useStateFactory(() => new CounterStore(), []);
78
80
  ```
79
81
 
80
- Optional global setup (e.g. with a custom deep-equality comparator):
82
+ Optional global setup for shared defaults such as distinct behavior and Redux DevTools:
81
83
 
82
84
  ```ts
83
85
  import equal from 'fast-deep-equal';
84
86
  import { setupStatusQuo } from '@veams/status-quo';
85
87
 
86
88
  setupStatusQuo({
89
+ devTools: {
90
+ enabled: true,
91
+ },
87
92
  distinct: {
88
93
  comparator: equal,
89
94
  },
@@ -101,12 +106,22 @@ Both are built on `BaseStateHandler`, which provides the shared lifecycle and de
101
106
 
102
107
  ## Hooks
103
108
 
109
+ The React layer lives under `@veams/status-quo/react`.
110
+
104
111
  Use `useStateHandler + useStateActions + useStateSubscription` as the base composition.
105
112
  `useStateFactory` and `useStateSingleton` are shortcut APIs over that composition.
106
113
  For full signatures and practical examples, see [API Guide](#api-guide).
107
114
 
108
115
  - `useStateHandler(factory, params)`
109
116
  - Creates and memoizes one handler instance per component.
117
+ - `StateProvider({ instance })`
118
+ - Shares one handler instance with a subtree through React context.
119
+ - `useProvidedStateHandler()`
120
+ - Reads the nearest provider-scoped handler instance.
121
+ - `useProvidedStateActions()`
122
+ - Returns provider-scoped actions without subscribing to state.
123
+ - `useProvidedStateSubscription(selector?, isEqual?)`
124
+ - Subscribes to provider-scoped state and returns `[state, actions]`.
110
125
  - `useStateActions(handler)`
111
126
  - Returns actions without subscribing to state.
112
127
  - `useStateSubscription(handlerOrSingleton, selector?, isEqual?)`
@@ -119,6 +134,12 @@ For full signatures and practical examples, see [API Guide](#api-guide).
119
134
  Recommended composition:
120
135
 
121
136
  ```ts
137
+ import {
138
+ useStateActions,
139
+ useStateHandler,
140
+ useStateSubscription,
141
+ } from '@veams/status-quo/react';
142
+
122
143
  const handler = useStateHandler(createUserStore, []);
123
144
  const actions = useStateActions(handler);
124
145
  const [name] = useStateSubscription(handler, (state) => state.user.name);
@@ -126,28 +147,72 @@ const [name] = useStateSubscription(handler, (state) => state.user.name);
126
147
  const [singletonName] = useStateSubscription(UserSingleton, (state) => state.user.name);
127
148
  ```
128
149
 
150
+ ## Providers
151
+
152
+ Use provider scope when a parent should own one local handler instance and several descendants need access to that same instance.
153
+
154
+ ```tsx
155
+ import {
156
+ StateProvider,
157
+ useProvidedStateActions,
158
+ useProvidedStateSubscription,
159
+ useStateHandler,
160
+ } from '@veams/status-quo/react';
161
+
162
+ function CounterScope() {
163
+ const handler = useStateHandler(createCounterStore, []);
164
+
165
+ return (
166
+ <StateProvider instance={handler}>
167
+ <CounterValue />
168
+ <CounterButtons />
169
+ </StateProvider>
170
+ );
171
+ }
172
+
173
+ function CounterValue() {
174
+ const [count] = useProvidedStateSubscription((state: CounterState) => state.count);
175
+
176
+ return <strong>{count}</strong>;
177
+ }
178
+
179
+ function CounterButtons() {
180
+ const actions = useProvidedStateActions<CounterState, CounterActions>();
181
+
182
+ return <button onClick={actions.increase}>Increase</button>;
183
+ }
184
+ ```
185
+
186
+ This is the scoped-sharing option between a local handler and a singleton:
187
+
188
+ - parent owns lifecycle once
189
+ - children choose whether they need state, actions, or the raw handler
190
+ - action-only components stay out of rerender fanout
191
+ - no prop drilling just to move a handler through the tree
192
+
129
193
  ## Singletons
130
194
 
131
195
  Use singletons for shared state across multiple components.
132
196
 
133
197
  ```ts
134
- import { makeStateSingleton, useStateSingleton } from '@veams/status-quo';
198
+ import { makeStateSingleton } from '@veams/status-quo';
199
+ import { useStateSingleton } from '@veams/status-quo/react';
135
200
 
136
- // Default behavior: singleton is destroyed when the last consumer unmounts.
201
+ // Default behavior: singleton stays alive across unmounts.
137
202
  const CounterSingleton = makeStateSingleton(() => new CounterStore());
138
203
 
139
204
  const [state, actions] = useStateSingleton(CounterSingleton);
140
205
  ```
141
206
 
142
- Keep a singleton instance alive across unmounts:
207
+ Destroy a singleton instance when the last consumer unmounts:
143
208
 
144
209
  ```ts
145
- const PersistentCounterSingleton = makeStateSingleton(() => new CounterStore(), {
146
- destroyOnNoConsumers: false,
210
+ const RouteScopedCounterSingleton = makeStateSingleton(() => new CounterStore(), {
211
+ destroyOnNoConsumers: true,
147
212
  });
148
213
  ```
149
214
 
150
- Use this for app-level stores that should survive route/component unmounts. Keep the default for stores that should release resources when unused.
215
+ Keep the default for app-level shared state that should survive route/component unmounts. Use `destroyOnNoConsumers: true` when the shared handler should behave more like a mounted resource.
151
216
 
152
217
  ## Composition
153
218
 
@@ -158,7 +223,7 @@ import { combineLatest } from 'rxjs';
158
223
 
159
224
  // RxJS: combine handler streams (RxJS shines here)
160
225
  class AppSignalStore extends SignalStateHandler<AppState, AppActions> {
161
- private counter$ = CounterObservableStore.getInstance().getStateAsObservable();
226
+ private counter$ = CounterObservableStore.getInstance().getObservable();
162
227
  private card$ = new CardObservableHandler();
163
228
 
164
229
  constructor() {
@@ -207,11 +272,14 @@ This section documents the primary public API with behavior notes and usage exam
207
272
 
208
273
  ### `setupStatusQuo(config?)`
209
274
 
210
- Sets global runtime defaults for distinct update behavior.
275
+ Sets global runtime defaults for distinct update behavior and Redux DevTools enablement.
211
276
  Per-handler options still override the global setup.
212
277
 
213
278
  ```ts
214
279
  type StatusQuoConfig = {
280
+ devTools?: {
281
+ enabled?: boolean; // default: false
282
+ };
215
283
  distinct?: {
216
284
  enabled?: boolean; // default: true
217
285
  comparator?: (previous: unknown, next: unknown) => boolean; // default: JSON compare
@@ -224,6 +292,9 @@ import equal from 'fast-deep-equal';
224
292
  import { setupStatusQuo } from '@veams/status-quo';
225
293
 
226
294
  setupStatusQuo({
295
+ devTools: {
296
+ enabled: true,
297
+ },
227
298
  distinct: {
228
299
  comparator: equal,
229
300
  },
@@ -239,19 +310,58 @@ Creates one handler instance per component mount and returns it.
239
310
  - lifecycle note: params are applied when the handler instance is created for that mount
240
311
 
241
312
  ```ts
313
+ import { useStateHandler } from '@veams/status-quo/react';
314
+
242
315
  const handler = useStateHandler(createUserStore, []);
243
316
  ```
244
317
 
318
+ ### `StateProvider`
319
+
320
+ Shares one existing handler instance with a subtree through React context.
321
+ Use this when a parent owns lifecycle and descendants should consume the same local instance without prop drilling.
322
+
323
+ ```tsx
324
+ import { StateProvider, useStateHandler } from '@veams/status-quo/react';
325
+
326
+ const handler = useStateHandler(createUserStore, []);
327
+
328
+ return <StateProvider instance={handler}>{children}</StateProvider>;
329
+ ```
330
+
331
+ ### `useProvidedStateHandler()`
332
+
333
+ Reads the nearest provider-scoped handler instance.
334
+ Use this as the low-level entry point when a descendant needs the raw handler for manual composition.
335
+
336
+ ```ts
337
+ import { useProvidedStateHandler } from '@veams/status-quo/react';
338
+
339
+ const handler = useProvidedStateHandler<UserState, UserActions>();
340
+ ```
341
+
245
342
  ### `useStateActions(handler)`
246
343
 
247
344
  Returns actions from a handler without subscribing to state changes.
248
345
  Use this in action-only components to avoid rerenders from state updates.
249
346
 
250
347
  ```ts
348
+ import { useStateActions, useStateHandler } from '@veams/status-quo/react';
349
+
251
350
  const handler = useStateHandler(createUserStore, []);
252
351
  const actions = useStateActions(handler);
253
352
  ```
254
353
 
354
+ ### `useProvidedStateActions()`
355
+
356
+ Returns actions from the nearest `StateProvider` without subscribing to state.
357
+ Use this for command-only components inside a provider scope.
358
+
359
+ ```ts
360
+ import { useProvidedStateActions } from '@veams/status-quo/react';
361
+
362
+ const actions = useProvidedStateActions<UserState, UserActions>();
363
+ ```
364
+
255
365
  ### `useStateSubscription(source, selector?, isEqual?)`
256
366
 
257
367
  Subscribes to either a handler instance or a singleton and returns `[selectedState, actions]`.
@@ -263,6 +373,8 @@ Subscribes to either a handler instance or a singleton and returns `[selectedSta
263
373
  Full snapshot subscription:
264
374
 
265
375
  ```ts
376
+ import { useStateHandler, useStateSubscription } from '@veams/status-quo/react';
377
+
266
378
  const handler = useStateHandler(createUserStore, []);
267
379
  const [state, actions] = useStateSubscription(handler);
268
380
  ```
@@ -270,6 +382,8 @@ const [state, actions] = useStateSubscription(handler);
270
382
  Selector subscription:
271
383
 
272
384
  ```ts
385
+ import { useStateSubscription } from '@veams/status-quo/react';
386
+
273
387
  const [name, actions] = useStateSubscription(
274
388
  handler,
275
389
  (state) => state.user.name
@@ -279,6 +393,8 @@ const [name, actions] = useStateSubscription(
279
393
  Selector with custom equality:
280
394
 
281
395
  ```ts
396
+ import { useStateSubscription } from '@veams/status-quo/react';
397
+
282
398
  const [profile] = useStateSubscription(
283
399
  handler,
284
400
  (state) => state.user.profile,
@@ -289,12 +405,26 @@ const [profile] = useStateSubscription(
289
405
  Singleton source:
290
406
 
291
407
  ```ts
408
+ import { useStateSubscription } from '@veams/status-quo/react';
409
+
292
410
  const [session, actions] = useStateSubscription(SessionSingleton);
293
411
  ```
294
412
 
295
413
  Lifecycle note for singleton sources:
296
414
  - Consumers are ref-counted.
297
- - The singleton instance is only destroyed when the last consumer unmounts and `destroyOnNoConsumers !== false`.
415
+ - The singleton instance is only destroyed when the last consumer unmounts and `destroyOnNoConsumers === true`.
416
+
417
+ ### `useProvidedStateSubscription(selector?, isEqual?)`
418
+
419
+ Subscribes to the nearest `StateProvider` instead of taking a source argument.
420
+ It supports full snapshots, selectors, and custom equality the same way `useStateSubscription` does.
421
+
422
+ ```ts
423
+ import { useProvidedStateSubscription } from '@veams/status-quo/react';
424
+
425
+ const [state, actions] = useProvidedStateSubscription<UserState, UserActions>();
426
+ const [name] = useProvidedStateSubscription((state: UserState) => state.user.name);
427
+ ```
298
428
 
299
429
  ### `useStateFactory(factory, selector?, isEqual?, params?)`
300
430
 
@@ -305,6 +435,8 @@ Shortcut API for `useStateHandler + useStateSubscription`.
305
435
  - `useStateFactory(factory, selector, isEqual, params)`
306
436
 
307
437
  ```ts
438
+ import { useStateFactory } from '@veams/status-quo/react';
439
+
308
440
  const [state, actions] = useStateFactory(createUserStore, []);
309
441
  const [name] = useStateFactory(createUserStore, (state) => state.user.name, []);
310
442
  const [profile] = useStateFactory(
@@ -320,6 +452,8 @@ const [profile] = useStateFactory(
320
452
  Creates a shared singleton provider for a handler instance.
321
453
 
322
454
  ```ts
455
+ import { makeStateSingleton } from '@veams/status-quo';
456
+
323
457
  const UserSingleton = makeStateSingleton(() => new UserStore());
324
458
  ```
325
459
 
@@ -327,16 +461,18 @@ Options:
327
461
 
328
462
  ```ts
329
463
  type StateSingletonOptions = {
330
- destroyOnNoConsumers?: boolean; // default: true
464
+ destroyOnNoConsumers?: boolean; // default: false
331
465
  };
332
466
  ```
333
467
 
334
- - `true` (default): destroy instance after last consumer unmounts
335
- - `false`: keep instance alive across periods with zero consumers
468
+ - `false` (default): keep instance alive across periods with zero consumers
469
+ - `true`: destroy instance after last consumer unmounts
336
470
 
337
471
  ```ts
338
- const PersistentUserSingleton = makeStateSingleton(() => new UserStore(), {
339
- destroyOnNoConsumers: false,
472
+ import { makeStateSingleton } from '@veams/status-quo';
473
+
474
+ const RouteScopedUserSingleton = makeStateSingleton(() => new UserStore(), {
475
+ destroyOnNoConsumers: true,
340
476
  });
341
477
  ```
342
478
 
@@ -345,25 +481,50 @@ const PersistentUserSingleton = makeStateSingleton(() => new UserStore(), {
345
481
  Shortcut API for `useStateSubscription(singleton, selector?, isEqual?)`.
346
482
 
347
483
  ```ts
484
+ import { useStateSingleton } from '@veams/status-quo/react';
485
+
348
486
  const [state, actions] = useStateSingleton(UserSingleton);
349
487
  const [name] = useStateSingleton(UserSingleton, (state) => state.user.name);
350
488
  ```
351
489
 
352
490
  ## Devtools
353
491
 
354
- Enable Redux Devtools integration with `options.devTools`:
492
+ Status Quo supports the Redux DevTools browser extension on both `ObservableStateHandler` and `SignalStateHandler`.
493
+
494
+ Turn it on globally:
495
+
496
+ ```ts
497
+ import { setupStatusQuo } from '@veams/status-quo';
498
+
499
+ setupStatusQuo({
500
+ devTools: {
501
+ enabled: true,
502
+ },
503
+ });
504
+ ```
505
+
506
+ Override it per handler when needed:
355
507
 
356
508
  ```ts
357
509
  class CounterStore extends ObservableStateHandler<CounterState, CounterActions> {
358
510
  constructor() {
359
511
  super({
360
512
  initialState: { count: 0 },
361
- options: { devTools: { enabled: true, namespace: 'Counter' } },
513
+ options: { devTools: { namespace: 'Counter' } },
362
514
  });
363
515
  }
364
516
  }
365
517
  ```
366
518
 
519
+ Notes:
520
+
521
+ - `setupStatusQuo({ devTools: { enabled: true } })` enables Redux DevTools by default for handlers.
522
+ - `options.devTools` still overrides the global default per handler.
523
+ - `namespace` is optional. When omitted, Status Quo uses the handler class name.
524
+ - `setState(nextState, actionName)` sends the `actionName` to the Redux DevTools timeline.
525
+ - The current integration supports reset, commit, jump to state, and jump to action from the extension UI.
526
+ - If the extension is not installed, Status Quo logs that and continues without a devtools connection.
527
+
367
528
  ## Cleanup
368
529
 
369
530
  Handlers expose `subscribe`, `getSnapshot`, and `destroy` for custom integrations:
@@ -419,7 +580,7 @@ Protected helpers:
419
580
 
420
581
  - `getStateValue(): S` (abstract)
421
582
  - `setStateValue(next: S): void` (abstract)
422
- - `initDevTools(options?: { enabled?: boolean; namespace: string }): void`
583
+ - `initDevTools(options?: { enabled?: boolean; namespace?: string }): void`
423
584
  - `bindSubscribable<T>(service: { subscribe: (listener: (value: T) => void) => () => void; getSnapshot?: () => T }, onChange: (value: T) => void, selector?: (value: T) => T, isEqual?: (current: T, next: T) => boolean): void`
424
585
  - `bindSubscribable<T, Sel>(service: { subscribe: (listener: (value: T) => void) => () => void; getSnapshot?: () => T }, onChange: (value: Sel) => void, selector: (value: T) => Sel, isEqual?: (current: Sel, next: Sel) => boolean): void`
425
586
  - Registers the subscription on `this.subscriptions` and invokes `onChange` with the current snapshot when available.
@@ -439,7 +600,7 @@ protected constructor({
439
600
  }: {
440
601
  initialState: S;
441
602
  options?: {
442
- devTools?: { enabled?: boolean; namespace: string };
603
+ devTools?: { enabled?: boolean; namespace?: string };
443
604
  distinct?: {
444
605
  enabled?: boolean;
445
606
  comparator?: (previous: S, next: S) => boolean;
@@ -451,15 +612,15 @@ protected constructor({
451
612
 
452
613
  Public methods:
453
614
 
454
- - `getStateAsObservable(options?: { useDistinctUntilChanged?: boolean }): Observable<S>`
455
- - `getStateItemAsObservable(key: keyof S): Observable<S[keyof S]>`
456
- - `getObservable(key: keyof S): Observable<S[keyof S]>`
615
+ - `getObservable(options?: { useDistinctUntilChanged?: boolean }): Observable<S>`
616
+ - `getObservableItem(key: keyof S): Observable<S[keyof S]>`
457
617
  - `subscribe(listener: () => void): () => void`
458
618
  - `subscribe(listener: (value: S) => void): () => void`
459
619
 
460
620
  Notes:
461
621
  - The observable stream uses `distinctUntilChanged` by default.
462
622
  - Distinct behavior can be configured globally via `setupStatusQuo` or per handler via `options.distinct`.
623
+ - Devtools can be enabled globally via `setupStatusQuo({ devTools: { enabled: true } })` or overridden per handler via `options.devTools`.
463
624
  - `subscribe` fires immediately with the current snapshot and then on subsequent changes.
464
625
  - Subscribers receive the next state snapshot as a callback argument.
465
626
 
@@ -476,7 +637,7 @@ protected constructor({
476
637
  }: {
477
638
  initialState: S;
478
639
  options?: {
479
- devTools?: { enabled?: boolean; namespace: string };
640
+ devTools?: { enabled?: boolean; namespace?: string };
480
641
  distinct?: {
481
642
  enabled?: boolean;
482
643
  comparator?: (previous: S, next: S) => boolean;
@@ -495,12 +656,16 @@ Public methods:
495
656
  Notes:
496
657
  - Distinct behavior defaults to enabled.
497
658
  - Configure it globally via `setupStatusQuo` or per handler via `options.distinct`.
659
+ - Devtools can be enabled globally via `setupStatusQuo({ devTools: { enabled: true } })` or overridden per handler via `options.devTools`.
498
660
  - `useDistinctUntilChanged` remains available as a shorthand enable/disable override.
499
661
 
500
662
  ### `setupStatusQuo`
501
663
 
502
664
  ```ts
503
665
  type StatusQuoConfig = {
666
+ devTools?: {
667
+ enabled?: boolean;
668
+ };
504
669
  distinct?: {
505
670
  enabled?: boolean;
506
671
  comparator?: (previous: unknown, next: unknown) => boolean;
@@ -514,7 +679,7 @@ function setupStatusQuo(config?: StatusQuoConfig): void
514
679
 
515
680
  ```ts
516
681
  type StateSingletonOptions = {
517
- destroyOnNoConsumers?: boolean; // default: true
682
+ destroyOnNoConsumers?: boolean; // default: false
518
683
  };
519
684
 
520
685
  function makeStateSingleton<S, A>(
@@ -526,13 +691,22 @@ function makeStateSingleton<S, A>(
526
691
  ```
527
692
 
528
693
  Lifecycle behavior:
529
- - `destroyOnNoConsumers: true` (default): destroy and recreate singleton instances with mount lifecycle.
530
- - `destroyOnNoConsumers: false`: keep the same singleton instance alive when no component is subscribed.
694
+ - `destroyOnNoConsumers: false` (default): keep the same singleton instance alive when no component is subscribed.
695
+ - `destroyOnNoConsumers: true`: destroy and recreate singleton instances with mount lifecycle.
531
696
 
532
697
  ### Hooks
533
698
 
699
+ - React entrypoint: `@veams/status-quo/react`
534
700
  - `useStateHandler<V, A, P extends unknown[]>(factory: (...args: P) => StateSubscriptionHandler<V, A>, params?: P)`
535
701
  - Returns `StateSubscriptionHandler<V, A>`.
702
+ - `StateProvider<V, A>({ instance }: { instance: StateSubscriptionHandler<V, A> })`
703
+ - Shares a handler instance with a subtree.
704
+ - `useProvidedStateHandler<V, A>()`
705
+ - Returns `StateSubscriptionHandler<V, A>`.
706
+ - `useProvidedStateActions<V, A>()`
707
+ - Returns `A`.
708
+ - `useProvidedStateSubscription<V, A, Sel = V>(selector?: (state: V) => Sel, isEqual?: (current: Sel, next: Sel) => boolean)`
709
+ - Returns `[state, actions]`.
536
710
  - `useStateActions<V, A>(handler: StateSubscriptionHandler<V, A>)`
537
711
  - Returns `A`.
538
712
  - `useStateSubscription<V, A, Sel = V>(source: StateSubscriptionHandler<V, A> | StateSingleton<V, A>, selector?: (state: V) => Sel, isEqual?: (current: Sel, next: Sel) => boolean)`
@@ -548,7 +722,9 @@ From pre-1.0 releases:
548
722
 
549
723
  1. Rename `StateHandler` -> `ObservableStateHandler`.
550
724
  2. Implement `subscribe()` and `getSnapshot()` on custom handlers.
551
- 3. Replace `getObservable()` usage with `subscribe()` in custom integrations.
552
- 4. Update devtools config:
725
+ 3. Replace `getStateAsObservable()` with `getObservable()`.
726
+ 4. Replace `getStateItemAsObservable()` and keyed `getObservable(key)` with `getObservableItem(key)`.
727
+ 5. Move React hooks and `StateProvider` imports from `@veams/status-quo` or `@veams/status-quo/hooks` to `@veams/status-quo/react`.
728
+ 6. Update devtools config:
553
729
  - From: `super({ initialState, devTools: { ... } })`
554
730
  - To: `super({ initialState, options: { devTools: { ... } } })`
@@ -3,18 +3,31 @@ export type DistinctOptions<T = unknown> = {
3
3
  enabled?: boolean;
4
4
  comparator?: DistinctComparator<T>;
5
5
  };
6
+ export type DevToolsOptions = {
7
+ enabled?: boolean;
8
+ namespace?: string;
9
+ };
10
+ export type GlobalDevToolsOptions = {
11
+ enabled?: boolean;
12
+ };
6
13
  export type StatusQuoConfig<T = unknown> = {
14
+ devTools?: GlobalDevToolsOptions;
7
15
  distinct?: DistinctOptions<T>;
8
16
  };
9
17
  type ResolvedDistinctOptions<T = unknown> = {
10
18
  enabled: boolean;
11
19
  comparator: DistinctComparator<T>;
12
20
  };
21
+ type ResolvedDevToolsOptions = {
22
+ enabled: boolean;
23
+ };
13
24
  type ResolvedStatusQuoConfig = {
25
+ devTools: ResolvedDevToolsOptions;
14
26
  distinct: ResolvedDistinctOptions;
15
27
  };
16
28
  export declare function setupStatusQuo<T = unknown>(config?: StatusQuoConfig<T>): void;
17
29
  export declare function resolveDistinctOptions<T>(options?: DistinctOptions<T>, useDistinctUntilChanged?: boolean): ResolvedDistinctOptions<T>;
30
+ export declare function resolveDevToolsOptions(options?: DevToolsOptions): ResolvedDevToolsOptions;
18
31
  export declare function getStatusQuoConfig(): ResolvedStatusQuoConfig;
19
32
  /** @internal testing helper */
20
33
  export declare function resetStatusQuoForTests(): void;