@veams/status-quo 1.9.0 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +14 -12
- package/README.md +33 -4
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -6
- package/dist/index.js.map +1 -1
- package/dist/observable/index.d.ts +5 -0
- package/dist/observable/index.js +6 -0
- package/dist/observable/index.js.map +1 -0
- package/dist/signals/index.d.ts +5 -0
- package/dist/signals/index.js +6 -0
- package/dist/signals/index.js.map +1 -0
- package/dist/store/__tests__/native-state-handler.spec.js +69 -0
- package/dist/store/__tests__/native-state-handler.spec.js.map +1 -1
- package/dist/store/__tests__/observable-state-handler.spec.js +6 -0
- package/dist/store/__tests__/observable-state-handler.spec.js.map +1 -1
- package/dist/store/__tests__/signal-state-handler.spec.js +69 -0
- package/dist/store/__tests__/signal-state-handler.spec.js.map +1 -1
- package/dist/store/base-state-handler.d.ts +7 -3
- package/dist/store/base-state-handler.js +27 -6
- package/dist/store/base-state-handler.js.map +1 -1
- package/dist/store/index.d.ts +0 -2
- package/dist/store/index.js +0 -4
- package/dist/store/index.js.map +1 -1
- package/package.json +15 -1
- package/src/index.ts +0 -6
- package/src/observable/index.ts +5 -0
- package/src/signals/index.ts +5 -0
- package/src/store/__tests__/native-state-handler.spec.ts +92 -0
- package/src/store/__tests__/observable-state-handler.spec.ts +10 -0
- package/src/store/__tests__/signal-state-handler.spec.ts +92 -0
- package/src/store/base-state-handler.ts +59 -9
- package/src/store/index.ts +0 -4
- package/dist/hooks/__tests__/state-provider.spec.d.ts +0 -4
- package/dist/hooks/__tests__/state-provider.spec.js +0 -179
- package/dist/hooks/__tests__/state-provider.spec.js.map +0 -1
- package/dist/hooks/__tests__/state-selector.spec.d.ts +0 -4
- package/dist/hooks/__tests__/state-selector.spec.js +0 -499
- package/dist/hooks/__tests__/state-selector.spec.js.map +0 -1
- package/dist/hooks/__tests__/state-singleton.spec.d.ts +0 -4
- package/dist/hooks/__tests__/state-singleton.spec.js +0 -96
- package/dist/hooks/__tests__/state-singleton.spec.js.map +0 -1
- package/dist/hooks/index.d.ts +0 -6
- package/dist/hooks/index.js +0 -7
- package/dist/hooks/index.js.map +0 -1
- package/dist/hooks/state-actions.d.ts +0 -2
- package/dist/hooks/state-actions.js +0 -5
- package/dist/hooks/state-actions.js.map +0 -1
- package/dist/hooks/state-factory.d.ts +0 -7
- package/dist/hooks/state-factory.js +0 -13
- package/dist/hooks/state-factory.js.map +0 -1
- package/dist/hooks/state-handler.d.ts +0 -2
- package/dist/hooks/state-handler.js +0 -9
- package/dist/hooks/state-handler.js.map +0 -1
- package/dist/hooks/state-provider.d.ts +0 -14
- package/dist/hooks/state-provider.js +0 -24
- package/dist/hooks/state-provider.js.map +0 -1
- package/dist/hooks/state-singleton.d.ts +0 -6
- package/dist/hooks/state-singleton.js +0 -7
- package/dist/hooks/state-singleton.js.map +0 -1
- package/dist/hooks/state-subscription-selector.d.ts +0 -3
- package/dist/hooks/state-subscription-selector.js +0 -63
- package/dist/hooks/state-subscription-selector.js.map +0 -1
- package/dist/hooks/state-subscription.d.ts +0 -9
- package/dist/hooks/state-subscription.js +0 -53
- package/dist/hooks/state-subscription.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-state-handler.js","sourceRoot":"","sources":["../../src/store/base-state-handler.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAClF,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"base-state-handler.js","sourceRoot":"","sources":["../../src/store/base-state-handler.ts"],"names":[],"mappings":"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"}
|
package/dist/store/index.d.ts
CHANGED
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export { BaseStateHandler } from './base-state-handler.js';
|
|
5
5
|
export { NativeStateHandler } from './native-state-handler.js';
|
|
6
|
-
export { ObservableStateHandler } from './observable-state-handler.js';
|
|
7
|
-
export { SignalStateHandler } from './signal-state-handler.js';
|
|
8
6
|
/**
|
|
9
7
|
* Export singleton related types and factory function.
|
|
10
8
|
*/
|
package/dist/store/index.js
CHANGED
|
@@ -5,10 +5,6 @@
|
|
|
5
5
|
export { BaseStateHandler } from './base-state-handler.js';
|
|
6
6
|
// Export the lightweight state handler using plain JavaScript.
|
|
7
7
|
export { NativeStateHandler } from './native-state-handler.js';
|
|
8
|
-
// Export the state handler powered by RxJS BehaviorSubjects.
|
|
9
|
-
export { ObservableStateHandler } from './observable-state-handler.js';
|
|
10
|
-
// Export the state handler powered by Preact Signals.
|
|
11
|
-
export { SignalStateHandler } from './signal-state-handler.js';
|
|
12
8
|
// Export the factory function to create singleton state handler instances.
|
|
13
9
|
export { makeStateSingleton } from './state-singleton.js';
|
|
14
10
|
//# sourceMappingURL=index.js.map
|
package/dist/store/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/store/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,yDAAyD;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,+DAA+D;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/store/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,yDAAyD;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,+DAA+D;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAQ/D,2EAA2E;AAC3E,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@veams/status-quo",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.0",
|
|
4
4
|
"description": "The manager to rule states in frontend.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -25,6 +25,20 @@
|
|
|
25
25
|
"default": "./dist/store/index.js"
|
|
26
26
|
},
|
|
27
27
|
"require": "./dist/store/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./observable": {
|
|
30
|
+
"import": {
|
|
31
|
+
"types": "./dist/observable/index.d.ts",
|
|
32
|
+
"default": "./dist/observable/index.js"
|
|
33
|
+
},
|
|
34
|
+
"require": "./dist/observable/index.js"
|
|
35
|
+
},
|
|
36
|
+
"./signals": {
|
|
37
|
+
"import": {
|
|
38
|
+
"types": "./dist/signals/index.d.ts",
|
|
39
|
+
"default": "./dist/signals/index.js"
|
|
40
|
+
},
|
|
41
|
+
"require": "./dist/signals/index.js"
|
|
28
42
|
}
|
|
29
43
|
},
|
|
30
44
|
"types": "dist/index.d.ts",
|
package/src/index.ts
CHANGED
|
@@ -9,8 +9,6 @@ import {
|
|
|
9
9
|
BaseStateHandler,
|
|
10
10
|
makeStateSingleton,
|
|
11
11
|
NativeStateHandler,
|
|
12
|
-
ObservableStateHandler,
|
|
13
|
-
SignalStateHandler,
|
|
14
12
|
} from './store/index.js';
|
|
15
13
|
|
|
16
14
|
// Import necessary types for external use.
|
|
@@ -34,12 +32,8 @@ export {
|
|
|
34
32
|
makeStateSingleton,
|
|
35
33
|
// Lightweight state handler using plain JavaScript.
|
|
36
34
|
NativeStateHandler,
|
|
37
|
-
// State handler powered by RxJS BehaviorSubjects.
|
|
38
|
-
ObservableStateHandler,
|
|
39
35
|
// Global configuration function for Status Quo.
|
|
40
36
|
setupStatusQuo,
|
|
41
|
-
// State handler powered by Preact Signals.
|
|
42
|
-
SignalStateHandler,
|
|
43
37
|
};
|
|
44
38
|
|
|
45
39
|
/**
|
|
@@ -51,6 +51,10 @@ type CounterBucketSelection = { bucket: number };
|
|
|
51
51
|
type CounterBucketState = { bucket: number };
|
|
52
52
|
type SetState = { openItems: Set<string> };
|
|
53
53
|
type SetActions = { toggle: (id: string) => void };
|
|
54
|
+
type CounterSubscribable = {
|
|
55
|
+
subscribe: (listener: (value: CounterState) => void) => () => void;
|
|
56
|
+
getSnapshot: () => CounterState;
|
|
57
|
+
};
|
|
54
58
|
|
|
55
59
|
class CounterNativeStateHandler extends NativeStateHandler<CounterState, CounterActions> {
|
|
56
60
|
constructor(initialCount = 0) {
|
|
@@ -139,6 +143,37 @@ class CounterNativeBucketBridgeStateHandler extends NativeStateHandler<
|
|
|
139
143
|
}
|
|
140
144
|
}
|
|
141
145
|
|
|
146
|
+
class CounterNamedNativeBridgeStateHandler extends NativeStateHandler<
|
|
147
|
+
CounterState,
|
|
148
|
+
{ noop: () => void }
|
|
149
|
+
> {
|
|
150
|
+
constructor(private readonly onCounterSync: (counterState: CounterState) => void) {
|
|
151
|
+
super({
|
|
152
|
+
initialState: {
|
|
153
|
+
count: -1,
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
bindNamedCounter(service: CounterSubscribable, subscriptionName = 'namedSubscription') {
|
|
159
|
+
this.bindSubscribable<CounterState, CounterState>(
|
|
160
|
+
subscriptionName,
|
|
161
|
+
service,
|
|
162
|
+
(nextCounterState) => {
|
|
163
|
+
this.onCounterSync(nextCounterState);
|
|
164
|
+
this.setState({ count: nextCounterState.count }, 'sync-counter');
|
|
165
|
+
},
|
|
166
|
+
(counterState) => counterState
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
getActions(): { noop: () => void } {
|
|
171
|
+
return {
|
|
172
|
+
noop: () => undefined,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
142
177
|
class SetNativeStateHandler extends NativeStateHandler<SetState, SetActions> {
|
|
143
178
|
constructor() {
|
|
144
179
|
super({
|
|
@@ -165,6 +200,27 @@ class SetNativeStateHandler extends NativeStateHandler<SetState, SetActions> {
|
|
|
165
200
|
}
|
|
166
201
|
}
|
|
167
202
|
|
|
203
|
+
function createCounterSubscribable(initialCount: number) {
|
|
204
|
+
let listener: ((value: CounterState) => void) | null = null;
|
|
205
|
+
const unsubscribe = jest.fn(() => {
|
|
206
|
+
listener = null;
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
emit(value: CounterState) {
|
|
211
|
+
listener?.(value);
|
|
212
|
+
},
|
|
213
|
+
service: {
|
|
214
|
+
getSnapshot: () => ({ count: initialCount }),
|
|
215
|
+
subscribe: (nextListener: (value: CounterState) => void) => {
|
|
216
|
+
listener = nextListener;
|
|
217
|
+
return unsubscribe;
|
|
218
|
+
},
|
|
219
|
+
} satisfies CounterSubscribable,
|
|
220
|
+
unsubscribe,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
168
224
|
describe('Native State Handler', () => {
|
|
169
225
|
let stateHandler: TestNativeStateHandler;
|
|
170
226
|
|
|
@@ -329,4 +385,40 @@ describe('Native State Handler', () => {
|
|
|
329
385
|
|
|
330
386
|
bridgeStateHandler.destroy();
|
|
331
387
|
});
|
|
388
|
+
|
|
389
|
+
it('should replace named bindSubscribable subscriptions when bound again with the same key', () => {
|
|
390
|
+
const syncSpy = jest.fn();
|
|
391
|
+
const bridgeStateHandler = new CounterNamedNativeBridgeStateHandler(syncSpy);
|
|
392
|
+
const firstCounter = createCounterSubscribable(1);
|
|
393
|
+
const secondCounter = createCounterSubscribable(5);
|
|
394
|
+
|
|
395
|
+
bridgeStateHandler.bindNamedCounter(firstCounter.service);
|
|
396
|
+
bridgeStateHandler.bindNamedCounter(secondCounter.service);
|
|
397
|
+
|
|
398
|
+
firstCounter.emit({ count: 2 });
|
|
399
|
+
secondCounter.emit({ count: 6 });
|
|
400
|
+
|
|
401
|
+
expect(firstCounter.unsubscribe).toHaveBeenCalledTimes(1);
|
|
402
|
+
expect(secondCounter.unsubscribe).toHaveBeenCalledTimes(0);
|
|
403
|
+
expect(syncSpy).toHaveBeenCalledTimes(3);
|
|
404
|
+
expect(syncSpy).toHaveBeenNthCalledWith(1, { count: 1 });
|
|
405
|
+
expect(syncSpy).toHaveBeenNthCalledWith(2, { count: 5 });
|
|
406
|
+
expect(syncSpy).toHaveBeenNthCalledWith(3, { count: 6 });
|
|
407
|
+
expect(bridgeStateHandler.getState()).toStrictEqual({ count: 6 });
|
|
408
|
+
|
|
409
|
+
bridgeStateHandler.destroy();
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
it('should unsubscribe named bindSubscribable subscriptions on destroy', () => {
|
|
413
|
+
const syncSpy = jest.fn();
|
|
414
|
+
const bridgeStateHandler = new CounterNamedNativeBridgeStateHandler(syncSpy);
|
|
415
|
+
const counter = createCounterSubscribable(3);
|
|
416
|
+
|
|
417
|
+
bridgeStateHandler.bindNamedCounter(counter.service);
|
|
418
|
+
bridgeStateHandler.destroy();
|
|
419
|
+
counter.emit({ count: 4 });
|
|
420
|
+
|
|
421
|
+
expect(counter.unsubscribe).toHaveBeenCalledTimes(1);
|
|
422
|
+
expect(syncSpy).toHaveBeenCalledTimes(1);
|
|
423
|
+
});
|
|
332
424
|
});
|
|
@@ -124,6 +124,16 @@ describe('Observable State Handler', () => {
|
|
|
124
124
|
expect(spy).toHaveBeenCalledTimes(1);
|
|
125
125
|
});
|
|
126
126
|
|
|
127
|
+
it('should destroy named subscriptions as well', () => {
|
|
128
|
+
const spy = jest.fn();
|
|
129
|
+
|
|
130
|
+
stateHandler.namedSubscriptions.set('namedSubscription', { unsubscribe: spy });
|
|
131
|
+
|
|
132
|
+
stateHandler.destroy();
|
|
133
|
+
|
|
134
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
135
|
+
});
|
|
136
|
+
|
|
127
137
|
it('should expose state item observable via getObservableItem', async () => {
|
|
128
138
|
const observableValue = await lastValueFrom(stateHandler.getObservableItem('test').pipe(take(1)));
|
|
129
139
|
|
|
@@ -49,6 +49,10 @@ type CounterState = { count: number };
|
|
|
49
49
|
type CounterActions = { increase: () => void };
|
|
50
50
|
type CounterBucketSelection = { bucket: number };
|
|
51
51
|
type CounterBucketState = { bucket: number };
|
|
52
|
+
type CounterSubscribable = {
|
|
53
|
+
subscribe: (listener: (value: CounterState) => void) => () => void;
|
|
54
|
+
getSnapshot: () => CounterState;
|
|
55
|
+
};
|
|
52
56
|
|
|
53
57
|
class CounterSignalStateHandler extends SignalStateHandler<CounterState, CounterActions> {
|
|
54
58
|
constructor(initialCount = 0) {
|
|
@@ -137,6 +141,58 @@ class CounterSignalBucketBridgeStateHandler extends SignalStateHandler<
|
|
|
137
141
|
}
|
|
138
142
|
}
|
|
139
143
|
|
|
144
|
+
class CounterNamedSignalBridgeStateHandler extends SignalStateHandler<
|
|
145
|
+
CounterState,
|
|
146
|
+
{ noop: () => void }
|
|
147
|
+
> {
|
|
148
|
+
constructor(private readonly onCounterSync: (counterState: CounterState) => void) {
|
|
149
|
+
super({
|
|
150
|
+
initialState: {
|
|
151
|
+
count: -1,
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
bindNamedCounter(service: CounterSubscribable, subscriptionName = 'namedSubscription') {
|
|
157
|
+
this.bindSubscribable<CounterState, CounterState>(
|
|
158
|
+
subscriptionName,
|
|
159
|
+
service,
|
|
160
|
+
(nextCounterState) => {
|
|
161
|
+
this.onCounterSync(nextCounterState);
|
|
162
|
+
this.setState({ count: nextCounterState.count }, 'sync-counter');
|
|
163
|
+
},
|
|
164
|
+
(counterState) => counterState
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
getActions(): { noop: () => void } {
|
|
169
|
+
return {
|
|
170
|
+
noop: () => undefined,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function createCounterSubscribable(initialCount: number) {
|
|
176
|
+
let listener: ((value: CounterState) => void) | null = null;
|
|
177
|
+
const unsubscribe = jest.fn(() => {
|
|
178
|
+
listener = null;
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
emit(value: CounterState) {
|
|
183
|
+
listener?.(value);
|
|
184
|
+
},
|
|
185
|
+
service: {
|
|
186
|
+
getSnapshot: () => ({ count: initialCount }),
|
|
187
|
+
subscribe: (nextListener: (value: CounterState) => void) => {
|
|
188
|
+
listener = nextListener;
|
|
189
|
+
return unsubscribe;
|
|
190
|
+
},
|
|
191
|
+
} satisfies CounterSubscribable,
|
|
192
|
+
unsubscribe,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
140
196
|
describe('Signal State Handler', () => {
|
|
141
197
|
let stateHandler: TestSignalStateHandler;
|
|
142
198
|
|
|
@@ -311,4 +367,40 @@ describe('Signal State Handler', () => {
|
|
|
311
367
|
|
|
312
368
|
bridgeStateHandler.destroy();
|
|
313
369
|
});
|
|
370
|
+
|
|
371
|
+
it('should replace named bindSubscribable subscriptions when bound again with the same key', () => {
|
|
372
|
+
const syncSpy = jest.fn();
|
|
373
|
+
const bridgeStateHandler = new CounterNamedSignalBridgeStateHandler(syncSpy);
|
|
374
|
+
const firstCounter = createCounterSubscribable(1);
|
|
375
|
+
const secondCounter = createCounterSubscribable(5);
|
|
376
|
+
|
|
377
|
+
bridgeStateHandler.bindNamedCounter(firstCounter.service);
|
|
378
|
+
bridgeStateHandler.bindNamedCounter(secondCounter.service);
|
|
379
|
+
|
|
380
|
+
firstCounter.emit({ count: 2 });
|
|
381
|
+
secondCounter.emit({ count: 6 });
|
|
382
|
+
|
|
383
|
+
expect(firstCounter.unsubscribe).toHaveBeenCalledTimes(1);
|
|
384
|
+
expect(secondCounter.unsubscribe).toHaveBeenCalledTimes(0);
|
|
385
|
+
expect(syncSpy).toHaveBeenCalledTimes(3);
|
|
386
|
+
expect(syncSpy).toHaveBeenNthCalledWith(1, { count: 1 });
|
|
387
|
+
expect(syncSpy).toHaveBeenNthCalledWith(2, { count: 5 });
|
|
388
|
+
expect(syncSpy).toHaveBeenNthCalledWith(3, { count: 6 });
|
|
389
|
+
expect(bridgeStateHandler.getState()).toStrictEqual({ count: 6 });
|
|
390
|
+
|
|
391
|
+
bridgeStateHandler.destroy();
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
it('should unsubscribe named bindSubscribable subscriptions on destroy', () => {
|
|
395
|
+
const syncSpy = jest.fn();
|
|
396
|
+
const bridgeStateHandler = new CounterNamedSignalBridgeStateHandler(syncSpy);
|
|
397
|
+
const counter = createCounterSubscribable(3);
|
|
398
|
+
|
|
399
|
+
bridgeStateHandler.bindNamedCounter(counter.service);
|
|
400
|
+
bridgeStateHandler.destroy();
|
|
401
|
+
counter.emit({ count: 4 });
|
|
402
|
+
|
|
403
|
+
expect(counter.unsubscribe).toHaveBeenCalledTimes(1);
|
|
404
|
+
expect(syncSpy).toHaveBeenCalledTimes(1);
|
|
405
|
+
});
|
|
314
406
|
});
|
|
@@ -23,6 +23,10 @@ type Subscribable<T> = {
|
|
|
23
23
|
getSnapshot?: () => T;
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
+
type ManagedSubscription = {
|
|
27
|
+
unsubscribe: () => void;
|
|
28
|
+
};
|
|
29
|
+
|
|
26
30
|
/**
|
|
27
31
|
* Configuration for Redux DevTools features enabled in this handler.
|
|
28
32
|
*/
|
|
@@ -50,7 +54,9 @@ export abstract class BaseStateHandler<S, A> implements StateSubscriptionHandler
|
|
|
50
54
|
protected devTools: DevTools | null = null;
|
|
51
55
|
|
|
52
56
|
// Keeps track of active subscriptions to allow for cleanup.
|
|
53
|
-
subscriptions:
|
|
57
|
+
subscriptions: ManagedSubscription[] = [];
|
|
58
|
+
// Tracks keyed subscriptions so handlers can replace them by name.
|
|
59
|
+
namedSubscriptions = new Map<string, ManagedSubscription>();
|
|
54
60
|
|
|
55
61
|
/**
|
|
56
62
|
* Initializes the handler with the given initial state.
|
|
@@ -125,8 +131,15 @@ export abstract class BaseStateHandler<S, A> implements StateSubscriptionHandler
|
|
|
125
131
|
* Cleans up all active subscriptions when the handler is destroyed.
|
|
126
132
|
*/
|
|
127
133
|
destroy(): void {
|
|
134
|
+
const subscriptions = [...this.subscriptions];
|
|
135
|
+
const namedSubscriptions = [...this.namedSubscriptions.values()];
|
|
136
|
+
|
|
137
|
+
this.subscriptions = [];
|
|
138
|
+
this.namedSubscriptions.clear();
|
|
139
|
+
|
|
128
140
|
// Execute the unsubscribe function for each tracked subscription.
|
|
129
|
-
|
|
141
|
+
subscriptions.forEach((subscription) => subscription.unsubscribe());
|
|
142
|
+
namedSubscriptions.forEach((subscription) => subscription.unsubscribe());
|
|
130
143
|
}
|
|
131
144
|
|
|
132
145
|
// Abstract methods to be implemented by concrete handlers for specific state engines (RxJS, Signals, etc.).
|
|
@@ -145,18 +158,46 @@ export abstract class BaseStateHandler<S, A> implements StateSubscriptionHandler
|
|
|
145
158
|
* Useful for bridging different state systems.
|
|
146
159
|
*/
|
|
147
160
|
protected bindSubscribable<T, Sel>(
|
|
161
|
+
subscriptionName: string,
|
|
148
162
|
service: Subscribable<T>,
|
|
149
163
|
onChange: (value: Sel) => void,
|
|
150
164
|
selector: Selector<T, Sel>,
|
|
151
165
|
isEqual?: EqualityFn<Sel>
|
|
152
166
|
): void;
|
|
153
|
-
protected bindSubscribable<T>(
|
|
154
|
-
|
|
167
|
+
protected bindSubscribable<T>(
|
|
168
|
+
subscriptionName: string,
|
|
169
|
+
service: Subscribable<T>,
|
|
170
|
+
onChange: (value: T) => void
|
|
171
|
+
): void;
|
|
172
|
+
protected bindSubscribable<T, Sel>(
|
|
155
173
|
service: Subscribable<T>,
|
|
156
174
|
onChange: (value: Sel) => void,
|
|
157
|
-
selector
|
|
175
|
+
selector: Selector<T, Sel>,
|
|
176
|
+
isEqual?: EqualityFn<Sel>
|
|
177
|
+
): void;
|
|
178
|
+
protected bindSubscribable<T>(service: Subscribable<T>, onChange: (value: T) => void): void;
|
|
179
|
+
protected bindSubscribable<T, Sel = T>(
|
|
180
|
+
subscriptionNameOrService: string | Subscribable<T>,
|
|
181
|
+
serviceOrOnChange: Subscribable<T> | ((value: Sel) => void),
|
|
182
|
+
onChangeOrSelector?: ((value: Sel) => void) | Selector<T, Sel>,
|
|
183
|
+
selectorOrIsEqual?: Selector<T, Sel> | EqualityFn<Sel>,
|
|
158
184
|
isEqual: EqualityFn<Sel> = Object.is
|
|
159
185
|
) {
|
|
186
|
+
const hasSubscriptionName = typeof subscriptionNameOrService === 'string';
|
|
187
|
+
const subscriptionName = hasSubscriptionName ? subscriptionNameOrService : null;
|
|
188
|
+
const service = (
|
|
189
|
+
hasSubscriptionName ? serviceOrOnChange : subscriptionNameOrService
|
|
190
|
+
) as Subscribable<T>;
|
|
191
|
+
const onChange = (
|
|
192
|
+
hasSubscriptionName ? onChangeOrSelector : serviceOrOnChange
|
|
193
|
+
) as (value: Sel) => void;
|
|
194
|
+
const selector = (
|
|
195
|
+
hasSubscriptionName ? selectorOrIsEqual : onChangeOrSelector
|
|
196
|
+
) as Selector<T, Sel> | undefined;
|
|
197
|
+
const equalityFn = (
|
|
198
|
+
hasSubscriptionName ? isEqual : (selectorOrIsEqual ?? Object.is)
|
|
199
|
+
) as EqualityFn<Sel>;
|
|
200
|
+
|
|
160
201
|
// Default to identity selector if none is provided.
|
|
161
202
|
const selectorFn = (selector ?? ((value: T) => value as unknown as Sel));
|
|
162
203
|
// Create a cache for selector results to avoid unnecessary updates.
|
|
@@ -172,7 +213,7 @@ export abstract class BaseStateHandler<S, A> implements StateSubscriptionHandler
|
|
|
172
213
|
selectorCache,
|
|
173
214
|
value,
|
|
174
215
|
selectorFn,
|
|
175
|
-
|
|
216
|
+
equalityFn
|
|
176
217
|
);
|
|
177
218
|
|
|
178
219
|
// Only trigger the callback if the selected value changed.
|
|
@@ -183,10 +224,19 @@ export abstract class BaseStateHandler<S, A> implements StateSubscriptionHandler
|
|
|
183
224
|
onChange(nextSelection);
|
|
184
225
|
};
|
|
185
226
|
|
|
227
|
+
if (subscriptionName) {
|
|
228
|
+
this.namedSubscriptions.get(subscriptionName)?.unsubscribe();
|
|
229
|
+
}
|
|
230
|
+
|
|
186
231
|
// Subscribe to the external source.
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
232
|
+
const subscription = { unsubscribe: service.subscribe(notifySelectedValue) };
|
|
233
|
+
|
|
234
|
+
if (subscriptionName) {
|
|
235
|
+
this.namedSubscriptions.set(subscriptionName, subscription);
|
|
236
|
+
} else {
|
|
237
|
+
// Track the subscription for later cleanup.
|
|
238
|
+
this.subscriptions = [...(this.subscriptions ?? []), subscription];
|
|
239
|
+
}
|
|
190
240
|
|
|
191
241
|
// If the source has a getSnapshot method and we haven't received a value yet, pull it manually.
|
|
192
242
|
if (service.getSnapshot && !receivedSyncValue) notifySelectedValue(service.getSnapshot());
|
package/src/store/index.ts
CHANGED
|
@@ -6,10 +6,6 @@
|
|
|
6
6
|
export { BaseStateHandler } from './base-state-handler.js';
|
|
7
7
|
// Export the lightweight state handler using plain JavaScript.
|
|
8
8
|
export { NativeStateHandler } from './native-state-handler.js';
|
|
9
|
-
// Export the state handler powered by RxJS BehaviorSubjects.
|
|
10
|
-
export { ObservableStateHandler } from './observable-state-handler.js';
|
|
11
|
-
// Export the state handler powered by Preact Signals.
|
|
12
|
-
export { SignalStateHandler } from './signal-state-handler.js';
|
|
13
9
|
|
|
14
10
|
/**
|
|
15
11
|
* Export singleton related types and factory function.
|
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
import React, { act } from 'react';
|
|
2
|
-
import { createRoot } from 'react-dom/client';
|
|
3
|
-
import { StateProvider, useProvidedStateActions, useProvidedStateHandler, useProvidedStateSubscription, } from '../state-provider.js';
|
|
4
|
-
class TestStateHandler {
|
|
5
|
-
initialState;
|
|
6
|
-
state;
|
|
7
|
-
listeners = new Set();
|
|
8
|
-
destroy = jest.fn();
|
|
9
|
-
constructor(initialState) {
|
|
10
|
-
this.initialState = initialState;
|
|
11
|
-
this.state = initialState;
|
|
12
|
-
}
|
|
13
|
-
subscribe(listener) {
|
|
14
|
-
const typedListener = listener;
|
|
15
|
-
this.listeners.add(typedListener);
|
|
16
|
-
return () => {
|
|
17
|
-
this.listeners.delete(typedListener);
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
getSnapshot = () => {
|
|
21
|
-
return this.state;
|
|
22
|
-
};
|
|
23
|
-
getInitialState = () => {
|
|
24
|
-
return this.initialState;
|
|
25
|
-
};
|
|
26
|
-
getActions = () => {
|
|
27
|
-
return {
|
|
28
|
-
increment: () => {
|
|
29
|
-
this.state = {
|
|
30
|
-
...this.state,
|
|
31
|
-
count: this.state.count + 1,
|
|
32
|
-
};
|
|
33
|
-
this.emitStateChange();
|
|
34
|
-
},
|
|
35
|
-
rename: (label) => {
|
|
36
|
-
this.state = {
|
|
37
|
-
...this.state,
|
|
38
|
-
label,
|
|
39
|
-
};
|
|
40
|
-
this.emitStateChange();
|
|
41
|
-
},
|
|
42
|
-
};
|
|
43
|
-
};
|
|
44
|
-
emitStateChange() {
|
|
45
|
-
const nextState = this.state;
|
|
46
|
-
this.listeners.forEach((listener) => listener(nextState));
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
function CountConsumer({ onRender }) {
|
|
50
|
-
const [count] = useProvidedStateSubscription((state) => state.count);
|
|
51
|
-
onRender(count);
|
|
52
|
-
return React.createElement("span", null, count);
|
|
53
|
-
}
|
|
54
|
-
function FullStateConsumer({ onActionsReady, onRender, }) {
|
|
55
|
-
const [state, actions] = useProvidedStateSubscription();
|
|
56
|
-
onRender(state);
|
|
57
|
-
onActionsReady(actions);
|
|
58
|
-
return React.createElement("span", null, state.label);
|
|
59
|
-
}
|
|
60
|
-
function ActionsOnlyConsumer({ onActionsReady, onRender, }) {
|
|
61
|
-
const actions = useProvidedStateActions();
|
|
62
|
-
onRender();
|
|
63
|
-
onActionsReady(actions);
|
|
64
|
-
return React.createElement("span", null, "actions-only");
|
|
65
|
-
}
|
|
66
|
-
function HandlerConsumer({ onHandlerReady, }) {
|
|
67
|
-
const handler = useProvidedStateHandler();
|
|
68
|
-
onHandlerReady(handler);
|
|
69
|
-
return React.createElement("span", null, "handler");
|
|
70
|
-
}
|
|
71
|
-
function MissingProviderConsumer() {
|
|
72
|
-
useProvidedStateActions();
|
|
73
|
-
return null;
|
|
74
|
-
}
|
|
75
|
-
describe('StateProvider', () => {
|
|
76
|
-
let container;
|
|
77
|
-
let root;
|
|
78
|
-
beforeAll(() => {
|
|
79
|
-
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
|
|
80
|
-
});
|
|
81
|
-
beforeEach(() => {
|
|
82
|
-
container = document.createElement('div');
|
|
83
|
-
document.body.appendChild(container);
|
|
84
|
-
root = createRoot(container);
|
|
85
|
-
});
|
|
86
|
-
afterEach(() => {
|
|
87
|
-
act(() => {
|
|
88
|
-
root.unmount();
|
|
89
|
-
});
|
|
90
|
-
container.remove();
|
|
91
|
-
});
|
|
92
|
-
afterAll(() => {
|
|
93
|
-
globalThis.IS_REACT_ACT_ENVIRONMENT = false;
|
|
94
|
-
});
|
|
95
|
-
it('should share one handler instance across the provider subtree', () => {
|
|
96
|
-
const stateHandler = new TestStateHandler({
|
|
97
|
-
count: 0,
|
|
98
|
-
label: 'Counter',
|
|
99
|
-
});
|
|
100
|
-
const countRenderSpy = jest.fn();
|
|
101
|
-
const actionsRenderSpy = jest.fn();
|
|
102
|
-
const actionsReadySpy = jest.fn();
|
|
103
|
-
const handlerReadySpy = jest.fn();
|
|
104
|
-
act(() => {
|
|
105
|
-
root.render(React.createElement(StateProvider, { instance: stateHandler },
|
|
106
|
-
React.createElement(CountConsumer, { onRender: countRenderSpy }),
|
|
107
|
-
React.createElement(ActionsOnlyConsumer, { onActionsReady: actionsReadySpy, onRender: actionsRenderSpy }),
|
|
108
|
-
React.createElement(HandlerConsumer, { onHandlerReady: handlerReadySpy })));
|
|
109
|
-
});
|
|
110
|
-
expect(countRenderSpy).toHaveBeenCalledTimes(1);
|
|
111
|
-
expect(countRenderSpy).toHaveBeenLastCalledWith(0);
|
|
112
|
-
expect(actionsRenderSpy).toHaveBeenCalledTimes(1);
|
|
113
|
-
expect(handlerReadySpy).toHaveBeenCalledWith(stateHandler);
|
|
114
|
-
const [[actions]] = actionsReadySpy.mock.calls;
|
|
115
|
-
act(() => {
|
|
116
|
-
actions.rename('Renamed');
|
|
117
|
-
});
|
|
118
|
-
expect(countRenderSpy).toHaveBeenCalledTimes(1);
|
|
119
|
-
expect(actionsRenderSpy).toHaveBeenCalledTimes(1);
|
|
120
|
-
act(() => {
|
|
121
|
-
actions.increment();
|
|
122
|
-
});
|
|
123
|
-
expect(countRenderSpy).toHaveBeenCalledTimes(2);
|
|
124
|
-
expect(countRenderSpy).toHaveBeenLastCalledWith(1);
|
|
125
|
-
expect(actionsRenderSpy).toHaveBeenCalledTimes(1);
|
|
126
|
-
});
|
|
127
|
-
it('should return the full snapshot when no selector is provided', () => {
|
|
128
|
-
const stateHandler = new TestStateHandler({
|
|
129
|
-
count: 2,
|
|
130
|
-
label: 'Counter',
|
|
131
|
-
});
|
|
132
|
-
const renderSpy = jest.fn();
|
|
133
|
-
const actionsReadySpy = jest.fn();
|
|
134
|
-
act(() => {
|
|
135
|
-
root.render(React.createElement(StateProvider, { instance: stateHandler },
|
|
136
|
-
React.createElement(FullStateConsumer, { onActionsReady: actionsReadySpy, onRender: renderSpy })));
|
|
137
|
-
});
|
|
138
|
-
expect(renderSpy).toHaveBeenCalledTimes(1);
|
|
139
|
-
expect(renderSpy).toHaveBeenLastCalledWith({ count: 2, label: 'Counter' });
|
|
140
|
-
const [[actions]] = actionsReadySpy.mock.calls;
|
|
141
|
-
act(() => {
|
|
142
|
-
actions.increment();
|
|
143
|
-
});
|
|
144
|
-
expect(renderSpy).toHaveBeenCalledTimes(2);
|
|
145
|
-
expect(renderSpy).toHaveBeenLastCalledWith({ count: 3, label: 'Counter' });
|
|
146
|
-
});
|
|
147
|
-
it('should follow a new instance when the provider instance changes', () => {
|
|
148
|
-
const firstHandler = new TestStateHandler({
|
|
149
|
-
count: 1,
|
|
150
|
-
label: 'First',
|
|
151
|
-
});
|
|
152
|
-
const secondHandler = new TestStateHandler({
|
|
153
|
-
count: 8,
|
|
154
|
-
label: 'Second',
|
|
155
|
-
});
|
|
156
|
-
const renderSpy = jest.fn();
|
|
157
|
-
act(() => {
|
|
158
|
-
root.render(React.createElement(StateProvider, { instance: firstHandler },
|
|
159
|
-
React.createElement(CountConsumer, { onRender: renderSpy })));
|
|
160
|
-
});
|
|
161
|
-
act(() => {
|
|
162
|
-
root.render(React.createElement(StateProvider, { instance: secondHandler },
|
|
163
|
-
React.createElement(CountConsumer, { onRender: renderSpy })));
|
|
164
|
-
});
|
|
165
|
-
expect(renderSpy).toHaveBeenCalledTimes(2);
|
|
166
|
-
expect(renderSpy).toHaveBeenNthCalledWith(1, 1);
|
|
167
|
-
expect(renderSpy).toHaveBeenNthCalledWith(2, 8);
|
|
168
|
-
});
|
|
169
|
-
it('should throw when provider hooks are used outside StateProvider', () => {
|
|
170
|
-
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => undefined);
|
|
171
|
-
expect(() => {
|
|
172
|
-
act(() => {
|
|
173
|
-
root.render(React.createElement(MissingProviderConsumer, null));
|
|
174
|
-
});
|
|
175
|
-
}).toThrow('No StateProvider instance found in the current React tree.');
|
|
176
|
-
consoleErrorSpy.mockRestore();
|
|
177
|
-
});
|
|
178
|
-
});
|
|
179
|
-
//# sourceMappingURL=state-provider.spec.js.map
|