@veams/status-quo 1.8.1 → 1.8.2
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/README.md +69 -1
- package/dist/config/status-quo-config.js +16 -1
- package/dist/config/status-quo-config.js.map +1 -1
- package/dist/store/__tests__/native-state-handler.spec.js +32 -0
- package/dist/store/__tests__/native-state-handler.spec.js.map +1 -1
- package/package.json +1 -1
- package/src/config/status-quo-config.ts +21 -1
- package/src/store/__tests__/native-state-handler.spec.ts +41 -0
package/README.md
CHANGED
|
@@ -133,6 +133,7 @@ For full signatures and practical examples, see [API Guide](#api-guide).
|
|
|
133
133
|
- Returns actions without subscribing to state.
|
|
134
134
|
- `useStateSubscription(handlerOrSingleton, selector?, isEqual?)`
|
|
135
135
|
- Subscribes to full state or a selected slice and returns `[state, actions]`.
|
|
136
|
+
- For local state, pass the same handler instance returned by `useStateHandler()` that you would also pass to `useStateActions()`.
|
|
136
137
|
- `useStateFactory(factory, selector?, isEqual?, params?)`
|
|
137
138
|
- Shortcut for `useStateHandler + useStateSubscription`.
|
|
138
139
|
- `useStateSingleton(singleton, selector?, isEqual?)`
|
|
@@ -273,6 +274,71 @@ class AppSignalStore extends SignalStateHandler<AppState, AppActions> {
|
|
|
273
274
|
}
|
|
274
275
|
```
|
|
275
276
|
|
|
277
|
+
## Comparator defaults
|
|
278
|
+
|
|
279
|
+
Status Quo has two comparison layers, and they solve different problems.
|
|
280
|
+
|
|
281
|
+
- Handler-level distinct comparison decides whether a state update should propagate at all.
|
|
282
|
+
- Hook-level `isEqual` decides whether one selected value should trigger a rerender.
|
|
283
|
+
- The handler is the primary place to define comparison behavior. Hook-level equality is possible, but it should stay focused on UI-specific selection boundaries.
|
|
284
|
+
|
|
285
|
+
Default behavior:
|
|
286
|
+
|
|
287
|
+
- `setupStatusQuo({ distinct })` and per-handler `options.distinct` use a comparator with an `Object.is` fast path and a JSON-based structural fallback that serializes `Map` and `Set` values through a custom replacer.
|
|
288
|
+
- `useStateSubscription()`, `useProvidedStateSubscription()`, `useStateFactory()`, and `useStateSingleton()` default `isEqual` to `Object.is`.
|
|
289
|
+
- If a selector returns a fresh object on each run, `Object.is` will treat that as changed unless you provide a custom equality function.
|
|
290
|
+
|
|
291
|
+
Set the distinct comparator globally:
|
|
292
|
+
|
|
293
|
+
```ts
|
|
294
|
+
import { setupStatusQuo } from '@veams/status-quo';
|
|
295
|
+
|
|
296
|
+
setupStatusQuo({
|
|
297
|
+
distinct: {
|
|
298
|
+
comparator: (previous, next) => previous.version === next.version,
|
|
299
|
+
},
|
|
300
|
+
});
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Or set it per handler:
|
|
304
|
+
|
|
305
|
+
```ts
|
|
306
|
+
import { NativeStateHandler } from '@veams/status-quo';
|
|
307
|
+
|
|
308
|
+
class SearchHandler extends NativeStateHandler<
|
|
309
|
+
{ version: number; resultIds: string[] },
|
|
310
|
+
{ replace: (version: number, resultIds: string[]) => void }
|
|
311
|
+
> {
|
|
312
|
+
constructor() {
|
|
313
|
+
super({
|
|
314
|
+
initialState: {
|
|
315
|
+
version: 0,
|
|
316
|
+
resultIds: [],
|
|
317
|
+
},
|
|
318
|
+
options: {
|
|
319
|
+
distinct: {
|
|
320
|
+
comparator: (previous, next) => previous.version === next.version,
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
getActions() {
|
|
327
|
+
return {
|
|
328
|
+
replace: (version: number, resultIds: string[]) => {
|
|
329
|
+
this.setState({ version, resultIds }, 'replace');
|
|
330
|
+
},
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
Practical rule:
|
|
337
|
+
|
|
338
|
+
- If several consumers need the same comparison rule, move it into the handler.
|
|
339
|
+
- Use hook-level `isEqual` only when one component is projecting a temporary view model or other UI-specific slice.
|
|
340
|
+
- Avoid making the component layer the primary home of state semantics when the handler can own that rule once.
|
|
341
|
+
|
|
276
342
|
## API Guide
|
|
277
343
|
|
|
278
344
|
This section documents the primary public API with behavior notes and usage examples.
|
|
@@ -289,7 +355,7 @@ type StatusQuoConfig = {
|
|
|
289
355
|
};
|
|
290
356
|
distinct?: {
|
|
291
357
|
enabled?: boolean; // default: true
|
|
292
|
-
comparator?: (previous: unknown, next: unknown) => boolean; // default: JSON
|
|
358
|
+
comparator?: (previous: unknown, next: unknown) => boolean; // default: Object.is fast path + JSON structural fallback with Map/Set support
|
|
293
359
|
};
|
|
294
360
|
};
|
|
295
361
|
```
|
|
@@ -374,6 +440,8 @@ const actions = useProvidedStateActions<UserState, UserActions>();
|
|
|
374
440
|
Subscribes to either a handler instance or a singleton and returns `[selectedState, actions]`.
|
|
375
441
|
|
|
376
442
|
- `source`: `StateSubscriptionHandler` or `StateSingleton`
|
|
443
|
+
- When you use local state, `source` is the handler instance returned by `useStateHandler()`.
|
|
444
|
+
- That means it is the same object you would pass to `useStateActions(handler)`.
|
|
377
445
|
- `selector`: optional projection function; defaults to identity
|
|
378
446
|
- `isEqual`: optional equality function; defaults to `Object.is`
|
|
379
447
|
|
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Global configuration and utility functions for Status Quo state management.
|
|
3
3
|
*/
|
|
4
|
+
function distinctReplacer(_key, value) {
|
|
5
|
+
if (value instanceof Set) {
|
|
6
|
+
return {
|
|
7
|
+
__statusQuoType: 'Set',
|
|
8
|
+
values: [...value],
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
if (value instanceof Map) {
|
|
12
|
+
return {
|
|
13
|
+
__statusQuoType: 'Map',
|
|
14
|
+
entries: [...value.entries()],
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
4
19
|
/**
|
|
5
20
|
* Default comparator function that uses referential equality (Object.is)
|
|
6
21
|
* and falls back to JSON stringification for structural equality.
|
|
@@ -12,7 +27,7 @@ function distinctAsJson(previous, next) {
|
|
|
12
27
|
}
|
|
13
28
|
// Structural comparison using JSON stringification as a fallback.
|
|
14
29
|
try {
|
|
15
|
-
return JSON.stringify(previous) === JSON.stringify(next);
|
|
30
|
+
return (JSON.stringify(previous, distinctReplacer) === JSON.stringify(next, distinctReplacer));
|
|
16
31
|
}
|
|
17
32
|
catch {
|
|
18
33
|
// If stringification fails, assume they are not equal.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status-quo-config.js","sourceRoot":"","sources":["../../src/config/status-quo-config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAyDH;;;GAGG;AACH,SAAS,cAAc,CAAC,QAAiB,EAAE,IAAa;IACtD,sCAAsC;IACtC,IAAI,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,CAAC;QAC9B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,kEAAkE;IAClE,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"status-quo-config.js","sourceRoot":"","sources":["../../src/config/status-quo-config.ts"],"names":[],"mappings":"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"}
|
|
@@ -88,6 +88,29 @@ class CounterNativeBucketBridgeStateHandler extends NativeStateHandler {
|
|
|
88
88
|
};
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
|
+
class SetNativeStateHandler extends NativeStateHandler {
|
|
92
|
+
constructor() {
|
|
93
|
+
super({
|
|
94
|
+
initialState: {
|
|
95
|
+
openItems: new Set(),
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
getActions() {
|
|
100
|
+
return {
|
|
101
|
+
toggle: (id) => {
|
|
102
|
+
const openItems = new Set(this.getState().openItems);
|
|
103
|
+
if (openItems.has(id)) {
|
|
104
|
+
openItems.delete(id);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
openItems.add(id);
|
|
108
|
+
}
|
|
109
|
+
this.setState({ openItems }, 'toggle');
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
91
114
|
describe('Native State Handler', () => {
|
|
92
115
|
let stateHandler;
|
|
93
116
|
beforeEach(() => {
|
|
@@ -172,6 +195,15 @@ describe('Native State Handler', () => {
|
|
|
172
195
|
unsubscribe();
|
|
173
196
|
expect(spy).toHaveBeenCalledTimes(2); // initial + one change
|
|
174
197
|
});
|
|
198
|
+
it('should notify subscribers when Set state changes', () => {
|
|
199
|
+
const handler = new SetNativeStateHandler();
|
|
200
|
+
const spy = jest.fn();
|
|
201
|
+
const unsubscribe = handler.subscribe(spy);
|
|
202
|
+
handler.getActions().toggle('item-1');
|
|
203
|
+
handler.getActions().toggle('item-1');
|
|
204
|
+
unsubscribe();
|
|
205
|
+
expect(spy).toHaveBeenCalledTimes(3); // initial + open + close
|
|
206
|
+
});
|
|
175
207
|
it('should notify another state handler for each singleton counter update', () => {
|
|
176
208
|
const counterSingleton = makeStateSingleton(() => new CounterNativeStateHandler(0), {
|
|
177
209
|
destroyOnNoConsumers: false,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native-state-handler.spec.js","sourceRoot":"","sources":["../../../src/store/__tests__/native-state-handler.spec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AAC3F,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAY3D,MAAM,sBAAuB,SAAQ,kBAA0C;IAC7E,YAAY,EAAE,YAAY,EAAE,QAAQ,EAAE,uBAAuB,KAA+B,EAAE;QAC5F,KAAK,CAAC;YACJ,YAAY,EAAE;gBACZ,IAAI,EAAE,WAAW;gBACjB,KAAK,EAAE,YAAY;aACpB;YACD,OAAO,EAAE;gBACP,GAAG,CAAC,YAAY,IAAI;oBAClB,QAAQ,EAAE;wBACR,OAAO,EAAE,IAAI;wBACb,SAAS,EAAE,wBAAwB;qBACpC;iBACF,CAAC;gBACF,GAAG,CAAC,QAAQ,IAAI;oBACd,QAAQ;iBACT,CAAC;gBACF,GAAG,CAAC,OAAO,uBAAuB,KAAK,SAAS,IAAI;oBAClD,uBAAuB;iBACxB,CAAC;aACH;SACF,CAAC,CAAC;IACL,CAAC;IAED,UAAU;QACR,OAAO;YACL,UAAU,EAAE,GAAG,EAAE;gBACf,IAAI,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC;YACtC,CAAC;SACF,CAAC;IACJ,CAAC;CACF;
|
|
1
|
+
{"version":3,"file":"native-state-handler.spec.js","sourceRoot":"","sources":["../../../src/store/__tests__/native-state-handler.spec.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -57,6 +57,24 @@ type ResolvedStatusQuoConfig = {
|
|
|
57
57
|
distinct: ResolvedDistinctOptions;
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
+
function distinctReplacer(_key: string, value: unknown) {
|
|
61
|
+
if (value instanceof Set) {
|
|
62
|
+
return {
|
|
63
|
+
__statusQuoType: 'Set',
|
|
64
|
+
values: [...value],
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (value instanceof Map) {
|
|
69
|
+
return {
|
|
70
|
+
__statusQuoType: 'Map',
|
|
71
|
+
entries: [...value.entries()],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return value;
|
|
76
|
+
}
|
|
77
|
+
|
|
60
78
|
/**
|
|
61
79
|
* Default comparator function that uses referential equality (Object.is)
|
|
62
80
|
* and falls back to JSON stringification for structural equality.
|
|
@@ -69,7 +87,9 @@ function distinctAsJson(previous: unknown, next: unknown) {
|
|
|
69
87
|
|
|
70
88
|
// Structural comparison using JSON stringification as a fallback.
|
|
71
89
|
try {
|
|
72
|
-
return
|
|
90
|
+
return (
|
|
91
|
+
JSON.stringify(previous, distinctReplacer) === JSON.stringify(next, distinctReplacer)
|
|
92
|
+
);
|
|
73
93
|
} catch {
|
|
74
94
|
// If stringification fails, assume they are not equal.
|
|
75
95
|
return false;
|
|
@@ -49,6 +49,8 @@ type CounterState = { count: number };
|
|
|
49
49
|
type CounterActions = { increase: () => void };
|
|
50
50
|
type CounterBucketSelection = { bucket: number };
|
|
51
51
|
type CounterBucketState = { bucket: number };
|
|
52
|
+
type SetState = { openItems: Set<string> };
|
|
53
|
+
type SetActions = { toggle: (id: string) => void };
|
|
52
54
|
|
|
53
55
|
class CounterNativeStateHandler extends NativeStateHandler<CounterState, CounterActions> {
|
|
54
56
|
constructor(initialCount = 0) {
|
|
@@ -137,6 +139,32 @@ class CounterNativeBucketBridgeStateHandler extends NativeStateHandler<
|
|
|
137
139
|
}
|
|
138
140
|
}
|
|
139
141
|
|
|
142
|
+
class SetNativeStateHandler extends NativeStateHandler<SetState, SetActions> {
|
|
143
|
+
constructor() {
|
|
144
|
+
super({
|
|
145
|
+
initialState: {
|
|
146
|
+
openItems: new Set(),
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
getActions(): SetActions {
|
|
152
|
+
return {
|
|
153
|
+
toggle: (id: string) => {
|
|
154
|
+
const openItems = new Set(this.getState().openItems);
|
|
155
|
+
|
|
156
|
+
if (openItems.has(id)) {
|
|
157
|
+
openItems.delete(id);
|
|
158
|
+
} else {
|
|
159
|
+
openItems.add(id);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
this.setState({ openItems }, 'toggle');
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
140
168
|
describe('Native State Handler', () => {
|
|
141
169
|
let stateHandler: TestNativeStateHandler;
|
|
142
170
|
|
|
@@ -247,6 +275,19 @@ describe('Native State Handler', () => {
|
|
|
247
275
|
expect(spy).toHaveBeenCalledTimes(2); // initial + one change
|
|
248
276
|
});
|
|
249
277
|
|
|
278
|
+
it('should notify subscribers when Set state changes', () => {
|
|
279
|
+
const handler = new SetNativeStateHandler();
|
|
280
|
+
const spy = jest.fn();
|
|
281
|
+
const unsubscribe = handler.subscribe(spy);
|
|
282
|
+
|
|
283
|
+
handler.getActions().toggle('item-1');
|
|
284
|
+
handler.getActions().toggle('item-1');
|
|
285
|
+
|
|
286
|
+
unsubscribe();
|
|
287
|
+
|
|
288
|
+
expect(spy).toHaveBeenCalledTimes(3); // initial + open + close
|
|
289
|
+
});
|
|
290
|
+
|
|
250
291
|
it('should notify another state handler for each singleton counter update', () => {
|
|
251
292
|
const counterSingleton = makeStateSingleton(() => new CounterNativeStateHandler(0), {
|
|
252
293
|
destroyOnNoConsumers: false,
|