@alwatr/signal 6.2.0 → 9.1.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 (42) hide show
  1. package/dist/core/computed-signal.d.ts +2 -1
  2. package/dist/core/computed-signal.d.ts.map +1 -1
  3. package/dist/core/effect-signal.d.ts +2 -1
  4. package/dist/core/effect-signal.d.ts.map +1 -1
  5. package/dist/core/event-signal.d.ts +2 -1
  6. package/dist/core/event-signal.d.ts.map +1 -1
  7. package/dist/core/signal-base.d.ts.map +1 -1
  8. package/dist/core/state-signal.d.ts +2 -1
  9. package/dist/core/state-signal.d.ts.map +1 -1
  10. package/dist/main.js +5 -0
  11. package/dist/main.js.map +25 -0
  12. package/dist/operators/debounce.d.ts.map +1 -1
  13. package/dist/type.d.ts.map +1 -1
  14. package/package.json +48 -53
  15. package/src/core/computed-signal.ts +216 -0
  16. package/src/core/effect-signal.ts +176 -0
  17. package/src/core/event-signal.ts +61 -0
  18. package/src/core/persistent-state-signal.ts +98 -0
  19. package/src/core/session-state-signal.ts +145 -0
  20. package/src/core/signal-base.ts +191 -0
  21. package/src/core/state-signal.ts +178 -0
  22. package/src/creators/computed.ts +37 -0
  23. package/src/creators/effect.ts +46 -0
  24. package/src/creators/event.ts +31 -0
  25. package/src/creators/persistent-state.ts +33 -0
  26. package/src/creators/session-state.ts +42 -0
  27. package/src/creators/state.ts +28 -0
  28. package/src/main.ts +20 -0
  29. package/src/operators/debounce.ts +91 -0
  30. package/src/operators/filter.ts +78 -0
  31. package/src/operators/map.ts +48 -0
  32. package/src/type.ts +357 -0
  33. package/CHANGELOG.md +0 -908
  34. package/dist/main.cjs +0 -4
  35. package/dist/main.cjs.map +0 -7
  36. package/dist/main.mjs +0 -4
  37. package/dist/main.mjs.map +0 -7
  38. package/src/core/computed-signal.test.js +0 -166
  39. package/src/core/effect-signal.test.js +0 -150
  40. package/src/core/event-signal.test.js +0 -210
  41. package/src/core/state-signal.test.js +0 -251
  42. package/src/operators/debounce.test.js +0 -206
@@ -1,150 +0,0 @@
1
- import {describe, beforeEach, afterEach, it, expect, jest} from '@jest/globals';
2
- import {EffectSignal, StateSignal} from '@alwatr/signal';
3
- import {delay} from '@alwatr/delay';
4
-
5
- describe('EffectSignal', () => {
6
- /** @type {StateSignal<number>} */
7
- let depSignal;
8
- /** @type {EffectSignal} */
9
- let effectSignal;
10
-
11
- beforeEach(() => {
12
- depSignal = new StateSignal({name: 'dep', initialValue: 0});
13
- });
14
-
15
- afterEach(() => {
16
- if (effectSignal && !effectSignal.isDestroyed) {
17
- effectSignal.destroy();
18
- }
19
- depSignal.destroy();
20
- });
21
-
22
- it('should be defined', () => {
23
- const runFn = jest.fn();
24
- effectSignal = new EffectSignal({
25
- deps: [depSignal],
26
- run: runFn,
27
- });
28
- expect(EffectSignal).toBeDefined();
29
- expect(effectSignal).toBeInstanceOf(EffectSignal);
30
- });
31
-
32
- it('should run the effect immediately if runImmediately is true', async () => {
33
- const runFn = jest.fn();
34
- effectSignal = new EffectSignal({
35
- deps: [depSignal],
36
- run: runFn,
37
- runImmediately: true,
38
- });
39
- await delay.by(5);
40
- expect(runFn).toHaveBeenCalledTimes(1);
41
- });
42
-
43
- it('should not run the effect immediately if runImmediately is false or undefined', async () => {
44
- const runFn = jest.fn();
45
- effectSignal = new EffectSignal({
46
- deps: [depSignal],
47
- run: runFn,
48
- });
49
- await delay.by(5);
50
- expect(runFn).not.toHaveBeenCalled();
51
- });
52
-
53
- it('should run the effect when a dependency changes', async () => {
54
- const runFn = jest.fn();
55
- effectSignal = new EffectSignal({
56
- deps: [depSignal],
57
- run: runFn,
58
- });
59
- await delay.by(5);
60
- expect(runFn).not.toHaveBeenCalled();
61
- depSignal.set(1);
62
- await delay.by(5);
63
- expect(runFn).toHaveBeenCalledTimes(1);
64
- });
65
-
66
- it('should run the effect for each dependency change', async () => {
67
- const runFn = jest.fn();
68
- effectSignal = new EffectSignal({
69
- deps: [depSignal],
70
- run: runFn,
71
- });
72
- depSignal.set(1);
73
- await delay.by(5);
74
-
75
- depSignal.set(2);
76
- await delay.by(5);
77
-
78
- expect(runFn).toHaveBeenCalledTimes(2);
79
- });
80
-
81
- it('should handle multiple dependencies', async () => {
82
- const depSignal2 = new StateSignal({name: 'dep2', initialValue: 'a'});
83
- const runFn = jest.fn();
84
- effectSignal = new EffectSignal({
85
- deps: [depSignal, depSignal2],
86
- run: runFn,
87
- });
88
- depSignal.set(1);
89
- await delay.by(5);
90
-
91
- expect(runFn).toHaveBeenCalledTimes(1);
92
- depSignal2.set('b');
93
- await delay.by(5);
94
-
95
- expect(runFn).toHaveBeenCalledTimes(2);
96
- depSignal2.destroy();
97
- });
98
-
99
- it('should not run the effect after destroy', async () => {
100
- const runFn = jest.fn();
101
- effectSignal = new EffectSignal({
102
- deps: [depSignal],
103
- run: runFn,
104
- });
105
- effectSignal.destroy();
106
- depSignal.set(1);
107
- await delay.by(5);
108
-
109
- expect(runFn).not.toHaveBeenCalled();
110
- });
111
-
112
- it('should handle async run functions', async () => {
113
- const runFn = jest.fn().mockResolvedValue(undefined);
114
- effectSignal = new EffectSignal({
115
- deps: [depSignal],
116
- run: runFn,
117
- });
118
- depSignal.set(1);
119
- await delay.by(5);
120
-
121
- expect(runFn).toHaveBeenCalledTimes(1);
122
- });
123
-
124
- it('should not run if dependencies do not change', async () => {
125
- const runFn = jest.fn();
126
- effectSignal = new EffectSignal({
127
- deps: [depSignal],
128
- run: runFn,
129
- });
130
- depSignal.set(0); // Same value
131
- await delay.by(5);
132
-
133
- expect(runFn).not.toHaveBeenCalled();
134
- });
135
-
136
- describe('destroyed signal', () => {
137
- it('should not run effect after destroy', async () => {
138
- const runFn = jest.fn();
139
- effectSignal = new EffectSignal({
140
- deps: [depSignal],
141
- run: runFn,
142
- });
143
- effectSignal.destroy();
144
- depSignal.set(1);
145
- await delay.by(5);
146
-
147
- expect(runFn).not.toHaveBeenCalled();
148
- });
149
- });
150
- });
@@ -1,210 +0,0 @@
1
- import {describe, beforeEach, afterEach, it, expect, jest} from '@jest/globals';
2
- import {EventSignal} from '@alwatr/signal';
3
- import {delay} from '@alwatr/delay';
4
-
5
- describe('EventSignal', () => {
6
- /** @type {EventSignal<unknown>} */
7
- let signal;
8
- const name = 'test-event-signal';
9
-
10
- beforeEach(() => {
11
- signal = new EventSignal({name});
12
- });
13
-
14
- afterEach(() => {
15
- signal.destroy();
16
- });
17
-
18
- it('should be defined and have the correct name', () => {
19
- expect(EventSignal).toBeDefined();
20
- expect(signal).toBeInstanceOf(EventSignal);
21
- expect(signal.name).toBe(name);
22
- });
23
-
24
- it('should dispatch an event and notify a subscriber with the correct payload', async () => {
25
- const callback = jest.fn();
26
- const payload = {data: 'test'};
27
-
28
- signal.subscribe(callback);
29
- signal.dispatch(payload);
30
-
31
- expect(callback).not.toHaveBeenCalled(); // Should be async
32
- await delay.nextMacrotask();
33
- expect(callback).toHaveBeenCalledTimes(1);
34
- expect(callback).toHaveBeenCalledWith(payload);
35
- });
36
-
37
- it('should notify multiple subscribers', async () => {
38
- const callback1 = jest.fn();
39
- const callback2 = jest.fn();
40
- const payload = 'event-data';
41
-
42
- signal.subscribe(callback1);
43
- signal.subscribe(callback2);
44
- signal.dispatch(payload);
45
-
46
- await delay.nextMacrotask();
47
- expect(callback1).toHaveBeenCalledTimes(1);
48
- expect(callback1).toHaveBeenCalledWith(payload);
49
- expect(callback2).toHaveBeenCalledTimes(1);
50
- expect(callback2).toHaveBeenCalledWith(payload);
51
- });
52
-
53
- it('should not notify unsubscribed listeners', async () => {
54
- const callback = jest.fn();
55
- const subscription = signal.subscribe(callback);
56
-
57
- subscription.unsubscribe();
58
- signal.dispatch('some-data');
59
-
60
- await delay.nextMacrotask();
61
- expect(callback).not.toHaveBeenCalled();
62
- });
63
-
64
- it('should handle subscriptions with the "once" option', async () => {
65
- const callback = jest.fn();
66
- signal.subscribe(callback, {once: true});
67
-
68
- signal.dispatch('first-dispatch');
69
- await delay.nextMacrotask();
70
- expect(callback).toHaveBeenCalledTimes(1);
71
- expect(callback).toHaveBeenCalledWith('first-dispatch');
72
-
73
- signal.dispatch('second-dispatch');
74
- await delay.nextMacrotask();
75
- expect(callback).toHaveBeenCalledTimes(1); // Should not be called again
76
- });
77
-
78
- it('should resolve untilNext() with the next dispatched payload', async () => {
79
- const payload = {value: 42};
80
- const untilNextPromise = signal.untilNext();
81
-
82
- signal.dispatch(payload);
83
-
84
- await expect(untilNextPromise).resolves.toBe(payload);
85
- });
86
-
87
- it('should handle dispatching without a payload (void)', async () => {
88
- const voidSignal = new EventSignal({name: 'void-signal'});
89
- const callback = jest.fn();
90
- voidSignal.subscribe(callback);
91
-
92
- voidSignal.dispatch();
93
-
94
- await delay.nextMacrotask();
95
- expect(callback).toHaveBeenCalledTimes(1);
96
- expect(callback).toHaveBeenCalledWith(undefined);
97
- voidSignal.destroy();
98
- });
99
-
100
- it('should handle dispatching with undefined payload explicitly', async () => {
101
- const callback = jest.fn();
102
- signal.subscribe(callback);
103
-
104
- signal.dispatch(undefined);
105
-
106
- await delay.nextMacrotask();
107
- expect(callback).toHaveBeenCalledTimes(1);
108
- expect(callback).toHaveBeenCalledWith(undefined);
109
- });
110
-
111
- it('should notify high-priority subscribers first', async () => {
112
- const callOrder = [];
113
- const callback1 = jest.fn(() => callOrder.push('normal'));
114
- const callback2 = jest.fn(() => callOrder.push('priority'));
115
-
116
- signal.subscribe(callback1); // Normal priority
117
- signal.subscribe(callback2, {priority: true}); // High priority
118
- signal.dispatch('test');
119
-
120
- await delay.nextMacrotask();
121
- expect(callOrder).toEqual(['priority', 'normal']);
122
- });
123
-
124
- it('should handle async callbacks correctly', async () => {
125
- const callback = jest.fn(async () => {
126
- await delay.nextMacrotask();
127
- return 'done';
128
- });
129
-
130
- signal.subscribe(callback);
131
- signal.dispatch('test');
132
-
133
- // Dispatch should not be awaited, but we need to wait for the microtask queue to be processed.
134
- await delay.nextMacrotask();
135
- expect(callback).toHaveBeenCalledTimes(1);
136
- });
137
-
138
- it('should continue notifying other subscribers if one callback throws an error', async () => {
139
- const errorCallback = jest.fn(() => {
140
- throw new Error('Test error');
141
- });
142
- const normalCallback = jest.fn();
143
-
144
- signal.subscribe(errorCallback);
145
- signal.subscribe(normalCallback);
146
- signal.dispatch('test');
147
-
148
- await delay.nextMacrotask();
149
- expect(errorCallback).toHaveBeenCalledTimes(1);
150
- expect(normalCallback).toHaveBeenCalledTimes(1);
151
- });
152
-
153
- it('should resolve multiple untilNext() calls with the same dispatched payload', async () => {
154
- const firstPromise = signal.untilNext();
155
- const secondPromise = signal.untilNext();
156
-
157
- const payload = 'test-payload';
158
- signal.dispatch(payload);
159
-
160
- // Both promises should resolve with the same payload
161
- await expect(firstPromise).resolves.toBe(payload);
162
- await expect(secondPromise).resolves.toBe(payload);
163
- });
164
-
165
- it('should multiple dispatch calls notify subscribers each time', async () => {
166
- const callback = jest.fn();
167
- signal.subscribe(callback);
168
-
169
- signal.dispatch(1);
170
- signal.dispatch(2);
171
- signal.dispatch(3);
172
-
173
- await delay.nextMacrotask();
174
-
175
- expect(callback).toHaveBeenCalledTimes(3);
176
- expect(callback).toHaveBeenNthCalledWith(1, 1);
177
- expect(callback).toHaveBeenNthCalledWith(2, 2);
178
- expect(callback).toHaveBeenNthCalledWith(3, 3);
179
- });
180
-
181
- describe('destroyed signal', () => {
182
- beforeEach(() => {
183
- signal.destroy();
184
- });
185
-
186
- it('should throw an error when dispatch is called on a destroyed signal', () => {
187
- expect(() => signal.dispatch('test')).toThrow(`Cannot interact with a destroyed signal (id: ${name})`);
188
- });
189
-
190
- it('should throw an error when subscribe is called on a destroyed signal', () => {
191
- expect(() => signal.subscribe(jest.fn())).toThrow(`Cannot interact with a destroyed signal (id: ${name})`);
192
- });
193
-
194
- it('should throw an error when untilNext is called on a destroyed signal', () => {
195
- expect(() => signal.untilNext()).toThrow(`Cannot interact with a destroyed signal (id: ${name})`);
196
- });
197
-
198
- it('should not notify any listeners after being destroyed', async () => {
199
- const localSignal = new EventSignal({name: 'local'});
200
- const callback = jest.fn();
201
- localSignal.subscribe(callback);
202
-
203
- localSignal.destroy();
204
- expect(() => localSignal.dispatch()).toThrow();
205
-
206
- await delay.nextMacrotask();
207
- expect(callback).not.toHaveBeenCalled();
208
- });
209
- });
210
- });
@@ -1,251 +0,0 @@
1
- import {describe, beforeEach, afterEach, it, expect, jest} from '@jest/globals';
2
- import {StateSignal} from '@alwatr/signal';
3
- import {delay} from '@alwatr/delay';
4
-
5
- describe('StateSignal', () => {
6
- /** @type {StateSignal<number>} */
7
- let signal;
8
- const name = 'test-state-signal';
9
-
10
- beforeEach(() => {
11
- signal = new StateSignal({name, initialValue: 0});
12
- });
13
-
14
- afterEach(() => {
15
- signal.destroy();
16
- });
17
-
18
- it('should be defined and have the correct name and initial value', () => {
19
- expect(StateSignal).toBeDefined();
20
- expect(signal).toBeInstanceOf(StateSignal);
21
- expect(signal.name).toBe(name);
22
- expect(signal.get()).toBe(0);
23
- });
24
-
25
- it('should notify subscriber with receivePrevious', async () => {
26
- const callback = jest.fn();
27
- const newValue = 42;
28
-
29
- signal.subscribe(callback);
30
- await delay.nextMacrotask();
31
- expect(callback).toHaveBeenCalledTimes(1);
32
- expect(callback).toHaveBeenCalledWith(0);
33
- signal.set(newValue);
34
- await delay.nextMacrotask();
35
- expect(callback).toHaveBeenCalledTimes(2);
36
- expect(callback).toHaveBeenCalledWith(newValue);
37
- });
38
-
39
- it('should notify subscribers when value changes', async () => {
40
- const callback = jest.fn();
41
- const newValue = 42;
42
-
43
- signal.subscribe(callback, {receivePrevious: false});
44
- signal.set(newValue);
45
-
46
- expect(callback).not.toHaveBeenCalled(); // Should be async
47
- await delay.nextMacrotask();
48
- expect(callback).toHaveBeenCalledTimes(1);
49
- expect(callback).toHaveBeenCalledWith(newValue);
50
- });
51
-
52
- it('should not notify subscribers if value does not change', async () => {
53
- const callback = jest.fn();
54
-
55
- signal.subscribe(callback, {receivePrevious: false});
56
- signal.set(0); // Same as initial
57
-
58
- await delay.nextMacrotask();
59
- expect(callback).not.toHaveBeenCalled();
60
- });
61
-
62
- it('should notify multiple subscribers', async () => {
63
- const callback1 = jest.fn();
64
- const callback2 = jest.fn();
65
- const newValue = 100;
66
-
67
- signal.subscribe(callback1, {receivePrevious: false});
68
- signal.subscribe(callback2, {receivePrevious: false});
69
- signal.set(newValue);
70
-
71
- await delay.nextMacrotask();
72
- expect(callback1).toHaveBeenCalledTimes(1);
73
- expect(callback1).toHaveBeenCalledWith(newValue);
74
- expect(callback2).toHaveBeenCalledTimes(1);
75
- expect(callback2).toHaveBeenCalledWith(newValue);
76
- });
77
-
78
- it('should not notify unsubscribed listeners', async () => {
79
- const callback = jest.fn();
80
- const subscription = signal.subscribe(callback, {receivePrevious: false});
81
-
82
- subscription.unsubscribe();
83
- signal.set(50);
84
-
85
- await delay.nextMacrotask();
86
- expect(callback).not.toHaveBeenCalled();
87
- });
88
-
89
- it('should handle subscriptions with the "once" option', async () => {
90
- const callback = jest.fn();
91
- signal.subscribe(callback, {once: true});
92
-
93
- signal.set(10);
94
- await delay.nextMacrotask();
95
- expect(callback).toHaveBeenCalledTimes(1);
96
- expect(callback).toHaveBeenCalledWith(10);
97
-
98
- signal.set(20);
99
- await delay.nextMacrotask();
100
- expect(callback).toHaveBeenCalledTimes(1); // Should not be called again
101
- });
102
-
103
- it('should immediately notify new subscribers with current value by default', async () => {
104
- const callback = jest.fn();
105
-
106
- signal.set(5);
107
- await delay.nextMacrotask();
108
-
109
- signal.subscribe(callback);
110
-
111
- await delay.nextMacrotask();
112
- expect(callback).toHaveBeenCalledTimes(1);
113
- expect(callback).toHaveBeenCalledWith(5);
114
- });
115
-
116
- it('should not immediately notify new subscribers if receivePrevious is false', async () => {
117
- const callback = jest.fn();
118
-
119
- signal.set(5);
120
- await delay.nextMacrotask();
121
-
122
- signal.subscribe(callback, {receivePrevious: false});
123
-
124
- await delay.nextMacrotask();
125
- expect(callback).not.toHaveBeenCalled();
126
-
127
- signal.set(10);
128
- await delay.nextMacrotask();
129
- expect(callback).toHaveBeenCalledTimes(1);
130
- expect(callback).toHaveBeenCalledWith(10);
131
- });
132
-
133
- it('should resolve untilNext() with the next set value', async () => {
134
- const newValue = 99;
135
- const untilNextPromise = signal.untilNext();
136
-
137
- signal.set(newValue);
138
-
139
- await expect(untilNextPromise).resolves.toBe(newValue);
140
- });
141
-
142
- it('should handle setting the same value multiple times without notifying', async () => {
143
- const callback = jest.fn();
144
-
145
- signal.subscribe(callback, {receivePrevious: false});
146
- signal.set(0);
147
- signal.set(0);
148
- signal.set(0);
149
-
150
- await delay.nextMacrotask();
151
- expect(callback).not.toHaveBeenCalled();
152
- });
153
-
154
- it('should notify subscribers multiple times for object values when they change', async () => {
155
- const callback = jest.fn();
156
- const value = {a: 1};
157
- const signal = new StateSignal({name: 'object-signal', initialValue: value});
158
-
159
- signal.subscribe(callback, {receivePrevious: false});
160
-
161
- value.a++;
162
- signal.set(value);
163
-
164
- await delay.nextMacrotask();
165
- expect(callback).toHaveBeenCalledTimes(1);
166
- expect(callback).toHaveBeenCalledWith(value);
167
-
168
- signal.set(value);
169
- await delay.nextMacrotask();
170
- expect(callback).toHaveBeenCalledTimes(2);
171
- expect(callback).toHaveBeenCalledWith(value);
172
- });
173
-
174
- it('should notify high-priority subscribers first', async () => {
175
- /** @type {Array<string>} */
176
- const callOrder = [];
177
- const callback1 = jest.fn(() => callOrder.push('normal'));
178
- const callback2 = jest.fn(() => callOrder.push('priority'));
179
-
180
- signal.subscribe(callback1, {receivePrevious: false}); // Normal priority
181
- signal.subscribe(callback2, {priority: true, receivePrevious: false}); // High priority
182
- signal.set(1);
183
-
184
- await delay.nextMacrotask();
185
- expect(callOrder).toEqual(['priority', 'normal']);
186
- });
187
-
188
- it('should handle async callbacks correctly', async () => {
189
- const callback = jest.fn(async () => {
190
- await delay.nextMacrotask();
191
- return 'done';
192
- });
193
-
194
- signal.subscribe(callback);
195
- signal.set(1);
196
-
197
- // Set should not be awaited, but we need to wait for the microtask queue to be processed.
198
- await delay.nextMacrotask();
199
- expect(callback).toHaveBeenCalledTimes(2);
200
- });
201
-
202
- it('should continue notifying other subscribers if one callback throws an error', async () => {
203
- const errorCallback = jest.fn(() => {
204
- throw new Error('Test error');
205
- });
206
- const normalCallback = jest.fn();
207
-
208
- signal.subscribe(errorCallback, {receivePrevious: false});
209
- signal.subscribe(normalCallback, {receivePrevious: false});
210
- signal.set(5);
211
- signal.set(10);
212
- await delay.nextMacrotask();
213
- expect(errorCallback).toHaveBeenCalledTimes(2);
214
- expect(normalCallback).toHaveBeenCalledTimes(2);
215
- expect(normalCallback).toHaveBeenCalledWith(10)
216
- });
217
-
218
- describe('destroyed signal', () => {
219
- beforeEach(() => {
220
- signal.destroy();
221
- });
222
-
223
- it('should throw an error when set is called on a destroyed signal', () => {
224
- expect(() => signal.set(1)).toThrow(`Cannot interact with a destroyed signal (id: ${name})`);
225
- });
226
-
227
- it('should throw an error when subscribe is called on a destroyed signal', () => {
228
- expect(() => signal.subscribe(jest.fn())).toThrow(`Cannot interact with a destroyed signal (id: ${name})`);
229
- });
230
-
231
- it('should throw an error when untilNext is called on a destroyed signal', () => {
232
- expect(() => signal.untilNext()).toThrow(`Cannot interact with a destroyed signal (id: ${name})`);
233
- });
234
-
235
- it('should throw an error when accessing value on a destroyed signal', () => {
236
- expect(() => signal.get()).toThrow(`Cannot interact with a destroyed signal (id: ${name})`);
237
- });
238
-
239
- it('should not notify any listeners after being destroyed', async () => {
240
- const localSignal = new StateSignal({name: 'local', initialValue: 0});
241
- const callback = jest.fn();
242
- localSignal.subscribe(callback, {receivePrevious: false});
243
-
244
- localSignal.destroy();
245
- expect(() => localSignal.set(1)).toThrow();
246
-
247
- await delay.nextMacrotask();
248
- expect(callback).not.toHaveBeenCalled();
249
- });
250
- });
251
- });