@ersbeth/picoflow 1.0.1 → 1.1.1
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/.cursor/plans/unifier-flowresource-avec-flowderivation-c9506e24.plan.md +372 -0
- package/README.md +17 -1
- package/biome.json +4 -1
- package/dist/picoflow.js +1155 -582
- package/dist/types/flow/base/flowDisposable.d.ts +67 -0
- package/dist/types/flow/base/flowDisposable.d.ts.map +1 -0
- package/dist/types/flow/base/flowEffect.d.ts +127 -0
- package/dist/types/flow/base/flowEffect.d.ts.map +1 -0
- package/dist/types/flow/base/flowGraph.d.ts +97 -0
- package/dist/types/flow/base/flowGraph.d.ts.map +1 -0
- package/dist/types/flow/base/flowSignal.d.ts +134 -0
- package/dist/types/flow/base/flowSignal.d.ts.map +1 -0
- package/dist/types/flow/base/flowTracker.d.ts +15 -0
- package/dist/types/flow/base/flowTracker.d.ts.map +1 -0
- package/dist/types/flow/base/index.d.ts +7 -0
- package/dist/types/flow/base/index.d.ts.map +1 -0
- package/dist/types/flow/base/utils.d.ts +20 -0
- package/dist/types/flow/base/utils.d.ts.map +1 -0
- package/dist/types/{advanced/array.d.ts → flow/collections/flowArray.d.ts} +50 -12
- package/dist/types/flow/collections/flowArray.d.ts.map +1 -0
- package/dist/types/flow/collections/flowMap.d.ts +224 -0
- package/dist/types/flow/collections/flowMap.d.ts.map +1 -0
- package/dist/types/flow/collections/index.d.ts +3 -0
- package/dist/types/flow/collections/index.d.ts.map +1 -0
- package/dist/types/flow/index.d.ts +4 -0
- package/dist/types/flow/index.d.ts.map +1 -0
- package/dist/types/flow/nodes/async/flowConstantAsync.d.ts +137 -0
- package/dist/types/flow/nodes/async/flowConstantAsync.d.ts.map +1 -0
- package/dist/types/flow/nodes/async/flowDerivationAsync.d.ts +137 -0
- package/dist/types/flow/nodes/async/flowDerivationAsync.d.ts.map +1 -0
- package/dist/types/flow/nodes/async/flowNodeAsync.d.ts +343 -0
- package/dist/types/flow/nodes/async/flowNodeAsync.d.ts.map +1 -0
- package/dist/types/flow/nodes/async/flowReadonlyAsync.d.ts +81 -0
- package/dist/types/flow/nodes/async/flowReadonlyAsync.d.ts.map +1 -0
- package/dist/types/flow/nodes/async/flowStateAsync.d.ts +111 -0
- package/dist/types/flow/nodes/async/flowStateAsync.d.ts.map +1 -0
- package/dist/types/flow/nodes/async/index.d.ts +6 -0
- package/dist/types/flow/nodes/async/index.d.ts.map +1 -0
- package/dist/types/flow/nodes/index.d.ts +3 -0
- package/dist/types/flow/nodes/index.d.ts.map +1 -0
- package/dist/types/flow/nodes/sync/flowConstant.d.ts +108 -0
- package/dist/types/flow/nodes/sync/flowConstant.d.ts.map +1 -0
- package/dist/types/flow/nodes/sync/flowDerivation.d.ts +100 -0
- package/dist/types/flow/nodes/sync/flowDerivation.d.ts.map +1 -0
- package/dist/types/flow/nodes/sync/flowNode.d.ts +314 -0
- package/dist/types/flow/nodes/sync/flowNode.d.ts.map +1 -0
- package/dist/types/flow/nodes/sync/flowReadonly.d.ts +57 -0
- package/dist/types/flow/nodes/sync/flowReadonly.d.ts.map +1 -0
- package/dist/types/flow/nodes/sync/flowState.d.ts +96 -0
- package/dist/types/flow/nodes/sync/flowState.d.ts.map +1 -0
- package/dist/types/flow/nodes/sync/index.d.ts +6 -0
- package/dist/types/flow/nodes/sync/index.d.ts.map +1 -0
- package/dist/types/index.d.ts +2 -4
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/solid/converters.d.ts +34 -45
- package/dist/types/solid/converters.d.ts.map +1 -1
- package/dist/types/solid/index.d.ts +2 -2
- package/dist/types/solid/index.d.ts.map +1 -1
- package/dist/types/solid/primitives.d.ts +1 -0
- package/dist/types/solid/primitives.d.ts.map +1 -1
- package/docs/.vitepress/config.mts +1 -1
- package/docs/api/typedoc-sidebar.json +81 -1
- package/package.json +60 -58
- package/src/flow/base/flowDisposable.ts +71 -0
- package/src/flow/base/flowEffect.ts +171 -0
- package/src/flow/base/flowGraph.ts +288 -0
- package/src/flow/base/flowSignal.ts +207 -0
- package/src/flow/base/flowTracker.ts +17 -0
- package/src/flow/base/index.ts +6 -0
- package/src/flow/base/utils.ts +19 -0
- package/src/flow/collections/flowArray.ts +409 -0
- package/src/flow/collections/flowMap.ts +398 -0
- package/src/flow/collections/index.ts +2 -0
- package/src/flow/index.ts +3 -0
- package/src/flow/nodes/async/flowConstantAsync.ts +142 -0
- package/src/flow/nodes/async/flowDerivationAsync.ts +143 -0
- package/src/flow/nodes/async/flowNodeAsync.ts +474 -0
- package/src/flow/nodes/async/flowReadonlyAsync.ts +81 -0
- package/src/flow/nodes/async/flowStateAsync.ts +116 -0
- package/src/flow/nodes/async/index.ts +5 -0
- package/src/flow/nodes/await/advanced/index.ts +5 -0
- package/src/{advanced → flow/nodes/await/advanced}/resource.ts +37 -3
- package/src/{advanced → flow/nodes/await/advanced}/resourceAsync.ts +35 -3
- package/src/{advanced → flow/nodes/await/advanced}/stream.ts +40 -2
- package/src/{advanced → flow/nodes/await/advanced}/streamAsync.ts +38 -3
- package/src/flow/nodes/await/flowConstantAwait.ts +154 -0
- package/src/flow/nodes/await/flowDerivationAwait.ts +154 -0
- package/src/flow/nodes/await/flowNodeAwait.ts +508 -0
- package/src/flow/nodes/await/flowReadonlyAwait.ts +89 -0
- package/src/flow/nodes/await/flowStateAwait.ts +130 -0
- package/src/flow/nodes/await/index.ts +5 -0
- package/src/flow/nodes/index.ts +3 -0
- package/src/flow/nodes/sync/flowConstant.ts +111 -0
- package/src/flow/nodes/sync/flowDerivation.ts +105 -0
- package/src/flow/nodes/sync/flowNode.ts +439 -0
- package/src/flow/nodes/sync/flowReadonly.ts +57 -0
- package/src/flow/nodes/sync/flowState.ts +101 -0
- package/src/flow/nodes/sync/index.ts +5 -0
- package/src/index.ts +2 -47
- package/src/solid/converters.ts +60 -199
- package/src/solid/index.ts +2 -8
- package/src/solid/primitives.ts +4 -0
- package/test/base/flowEffect.test.ts +108 -0
- package/test/base/flowGraph.test.ts +485 -0
- package/test/base/flowSignal.test.ts +372 -0
- package/test/collections/flowArray.asyncStates.test.ts +1553 -0
- package/test/collections/flowArray.scalars.test.ts +1129 -0
- package/test/collections/flowArray.states.test.ts +1365 -0
- package/test/collections/flowMap.asyncStates.test.ts +1105 -0
- package/test/collections/flowMap.scalars.test.ts +877 -0
- package/test/collections/flowMap.states.test.ts +1097 -0
- package/test/nodes/async/flowConstantAsync.test.ts +860 -0
- package/test/nodes/async/flowDerivationAsync.test.ts +1517 -0
- package/test/nodes/async/flowStateAsync.test.ts +1387 -0
- package/test/{resource.test.ts → nodes/await/advanced/resource.test.ts} +21 -19
- package/test/{resourceAsync.test.ts → nodes/await/advanced/resourceAsync.test.ts} +3 -1
- package/test/{stream.test.ts → nodes/await/advanced/stream.test.ts} +30 -28
- package/test/{streamAsync.test.ts → nodes/await/advanced/streamAsync.test.ts} +16 -14
- package/test/nodes/await/flowConstantAwait.test.ts +643 -0
- package/test/nodes/await/flowDerivationAwait.test.ts +1583 -0
- package/test/nodes/await/flowStateAwait.test.ts +999 -0
- package/test/nodes/mixed/derivation.test.ts +1527 -0
- package/test/nodes/sync/flowConstant.test.ts +620 -0
- package/test/nodes/sync/flowDerivation.test.ts +1373 -0
- package/test/nodes/sync/flowState.test.ts +945 -0
- package/test/solid/converters.test.ts +721 -0
- package/test/solid/primitives.test.ts +1031 -0
- package/tsconfig.json +2 -1
- package/vitest.config.ts +7 -1
- package/IMPLEMENTATION_GUIDE.md +0 -1578
- package/dist/types/advanced/array.d.ts.map +0 -1
- package/dist/types/advanced/index.d.ts +0 -9
- package/dist/types/advanced/index.d.ts.map +0 -1
- package/dist/types/advanced/map.d.ts +0 -166
- package/dist/types/advanced/map.d.ts.map +0 -1
- package/dist/types/advanced/resource.d.ts +0 -78
- package/dist/types/advanced/resource.d.ts.map +0 -1
- package/dist/types/advanced/resourceAsync.d.ts +0 -56
- package/dist/types/advanced/resourceAsync.d.ts.map +0 -1
- package/dist/types/advanced/stream.d.ts +0 -117
- package/dist/types/advanced/stream.d.ts.map +0 -1
- package/dist/types/advanced/streamAsync.d.ts +0 -97
- package/dist/types/advanced/streamAsync.d.ts.map +0 -1
- package/dist/types/basic/constant.d.ts +0 -60
- package/dist/types/basic/constant.d.ts.map +0 -1
- package/dist/types/basic/derivation.d.ts +0 -89
- package/dist/types/basic/derivation.d.ts.map +0 -1
- package/dist/types/basic/disposable.d.ts +0 -82
- package/dist/types/basic/disposable.d.ts.map +0 -1
- package/dist/types/basic/effect.d.ts +0 -67
- package/dist/types/basic/effect.d.ts.map +0 -1
- package/dist/types/basic/index.d.ts +0 -10
- package/dist/types/basic/index.d.ts.map +0 -1
- package/dist/types/basic/observable.d.ts +0 -83
- package/dist/types/basic/observable.d.ts.map +0 -1
- package/dist/types/basic/signal.d.ts +0 -69
- package/dist/types/basic/signal.d.ts.map +0 -1
- package/dist/types/basic/state.d.ts +0 -47
- package/dist/types/basic/state.d.ts.map +0 -1
- package/dist/types/basic/trackingContext.d.ts +0 -33
- package/dist/types/basic/trackingContext.d.ts.map +0 -1
- package/dist/types/creators.d.ts +0 -340
- package/dist/types/creators.d.ts.map +0 -1
- package/src/advanced/array.ts +0 -222
- package/src/advanced/index.ts +0 -12
- package/src/advanced/map.ts +0 -193
- package/src/basic/constant.ts +0 -97
- package/src/basic/derivation.ts +0 -147
- package/src/basic/disposable.ts +0 -86
- package/src/basic/effect.ts +0 -104
- package/src/basic/index.ts +0 -9
- package/src/basic/observable.ts +0 -109
- package/src/basic/signal.ts +0 -145
- package/src/basic/state.ts +0 -60
- package/src/basic/trackingContext.ts +0 -45
- package/src/creators.ts +0 -395
- package/test/array.test.ts +0 -600
- package/test/constant.test.ts +0 -44
- package/test/derivation.test.ts +0 -539
- package/test/effect.test.ts +0 -29
- package/test/map.test.ts +0 -240
- package/test/signal.test.ts +0 -72
- package/test/state.test.ts +0 -212
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { effect, FlowGraph } from "#package";
|
|
3
|
+
|
|
4
|
+
describe("FlowGraph", () => {
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
FlowGraph.clear();
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
describe("unit", () => {
|
|
10
|
+
describe("clear", () => {
|
|
11
|
+
it("should reset all internal queues and processing state", () => {
|
|
12
|
+
// Use FlowGraph methods to populate queues
|
|
13
|
+
const notify = vi.fn();
|
|
14
|
+
FlowGraph.requestTrigger(notify);
|
|
15
|
+
FlowGraph.requestRead(() => 42);
|
|
16
|
+
|
|
17
|
+
// Clear should reset everything
|
|
18
|
+
FlowGraph.clear();
|
|
19
|
+
|
|
20
|
+
// After clear, new operations should work without interference
|
|
21
|
+
const newNotify = vi.fn();
|
|
22
|
+
const promise = FlowGraph.requestTrigger(newNotify);
|
|
23
|
+
expect(promise).toBeInstanceOf(Promise);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("should allow multiple clear calls without errors", () => {
|
|
27
|
+
expect(() => {
|
|
28
|
+
FlowGraph.clear();
|
|
29
|
+
FlowGraph.clear();
|
|
30
|
+
FlowGraph.clear();
|
|
31
|
+
}).not.toThrow();
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe("integration", () => {
|
|
37
|
+
describe("requestRead", () => {
|
|
38
|
+
it("should process read operations synchronously", async () => {
|
|
39
|
+
const readFn = vi.fn(() => 42);
|
|
40
|
+
const promise = FlowGraph.requestRead(readFn);
|
|
41
|
+
|
|
42
|
+
expect(readFn).toHaveBeenCalled();
|
|
43
|
+
const result = await promise;
|
|
44
|
+
expect(result).toBe(42);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("should process read operations asynchronously", async () => {
|
|
48
|
+
const readFn = vi.fn(async () => {
|
|
49
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
50
|
+
return 100;
|
|
51
|
+
});
|
|
52
|
+
const promise = FlowGraph.requestRead(readFn);
|
|
53
|
+
|
|
54
|
+
const result = await promise;
|
|
55
|
+
expect(result).toBe(100);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("should resolve promise with read value", async () => {
|
|
59
|
+
const value = "test-value";
|
|
60
|
+
const promise = FlowGraph.requestRead(() => value);
|
|
61
|
+
|
|
62
|
+
await expect(promise).resolves.toBe(value);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("should process read operations in order", async () => {
|
|
66
|
+
const executionOrder: number[] = [];
|
|
67
|
+
|
|
68
|
+
const promise1 = FlowGraph.requestRead(() => {
|
|
69
|
+
executionOrder.push(1);
|
|
70
|
+
return 1;
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const promise2 = FlowGraph.requestRead(() => {
|
|
74
|
+
executionOrder.push(2);
|
|
75
|
+
return 2;
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const promise3 = FlowGraph.requestRead(() => {
|
|
79
|
+
executionOrder.push(3);
|
|
80
|
+
return 3;
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
await Promise.all([promise1, promise2, promise3]);
|
|
84
|
+
|
|
85
|
+
expect(executionOrder).toEqual([1, 2, 3]);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("should stop processing after read operation", async () => {
|
|
89
|
+
const effectFn = vi.fn();
|
|
90
|
+
const $effect = effect(() => {
|
|
91
|
+
effectFn();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
FlowGraph.pushEffects([$effect]);
|
|
95
|
+
|
|
96
|
+
// Read should stop processing, so effects shouldn't execute
|
|
97
|
+
await FlowGraph.requestRead(() => 42);
|
|
98
|
+
|
|
99
|
+
// Wait a bit to ensure effects don't execute
|
|
100
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
101
|
+
|
|
102
|
+
// Effect should not have been executed by the read operation
|
|
103
|
+
expect(effectFn).toHaveBeenCalledTimes(1); // Only initial execution
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe("requestTrigger", () => {
|
|
108
|
+
it("should process trigger operations", async () => {
|
|
109
|
+
const notify = vi.fn();
|
|
110
|
+
const promise = FlowGraph.requestTrigger(notify);
|
|
111
|
+
|
|
112
|
+
await promise;
|
|
113
|
+
expect(notify).toHaveBeenCalled();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("should call notify function when processing trigger", async () => {
|
|
117
|
+
const notify = vi.fn();
|
|
118
|
+
await FlowGraph.requestTrigger(notify);
|
|
119
|
+
|
|
120
|
+
expect(notify).toHaveBeenCalledTimes(1);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("should resolve promise after trigger is processed", async () => {
|
|
124
|
+
const notify = vi.fn();
|
|
125
|
+
const promise = FlowGraph.requestTrigger(notify);
|
|
126
|
+
|
|
127
|
+
await expect(promise).resolves.toBeUndefined();
|
|
128
|
+
expect(notify).toHaveBeenCalled();
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("should process multiple triggers in order", async () => {
|
|
132
|
+
const executionOrder: string[] = [];
|
|
133
|
+
const notify1 = vi.fn(() => executionOrder.push("trigger1"));
|
|
134
|
+
const notify2 = vi.fn(() => executionOrder.push("trigger2"));
|
|
135
|
+
const notify3 = vi.fn(() => executionOrder.push("trigger3"));
|
|
136
|
+
|
|
137
|
+
const promise1 = FlowGraph.requestTrigger(notify1);
|
|
138
|
+
const promise2 = FlowGraph.requestTrigger(notify2);
|
|
139
|
+
const promise3 = FlowGraph.requestTrigger(notify3);
|
|
140
|
+
|
|
141
|
+
await Promise.all([promise1, promise2, promise3]);
|
|
142
|
+
|
|
143
|
+
expect(executionOrder).toEqual(["trigger1", "trigger2", "trigger3"]);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("should execute effects after trigger notification", async () => {
|
|
147
|
+
const notify = vi.fn();
|
|
148
|
+
const effectFn = vi.fn();
|
|
149
|
+
const $effect = effect(() => {
|
|
150
|
+
effectFn();
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
FlowGraph.pushEffects([$effect]);
|
|
154
|
+
|
|
155
|
+
await FlowGraph.requestTrigger(notify);
|
|
156
|
+
|
|
157
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(2)); // Initial + after trigger
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe("requestWrite", () => {
|
|
162
|
+
it("should process write operations with sync update", async () => {
|
|
163
|
+
const notify = vi.fn();
|
|
164
|
+
const update = vi.fn(() => true);
|
|
165
|
+
const promise = FlowGraph.requestWrite(notify, update);
|
|
166
|
+
|
|
167
|
+
await promise;
|
|
168
|
+
expect(update).toHaveBeenCalled();
|
|
169
|
+
expect(notify).toHaveBeenCalled();
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("should process write operations with async update", async () => {
|
|
173
|
+
const notify = vi.fn();
|
|
174
|
+
const update = vi.fn(async () => {
|
|
175
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
176
|
+
return true;
|
|
177
|
+
});
|
|
178
|
+
const promise = FlowGraph.requestWrite(notify, update);
|
|
179
|
+
|
|
180
|
+
await promise;
|
|
181
|
+
expect(update).toHaveBeenCalled();
|
|
182
|
+
expect(notify).toHaveBeenCalled();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("should call notify only when update returns true", async () => {
|
|
186
|
+
const notify1 = vi.fn();
|
|
187
|
+
const notify2 = vi.fn();
|
|
188
|
+
const notify3 = vi.fn();
|
|
189
|
+
|
|
190
|
+
await FlowGraph.requestWrite(notify1, () => true);
|
|
191
|
+
await FlowGraph.requestWrite(notify2, () => false);
|
|
192
|
+
await FlowGraph.requestWrite(notify3, () => true);
|
|
193
|
+
|
|
194
|
+
expect(notify1).toHaveBeenCalledTimes(1);
|
|
195
|
+
expect(notify2).not.toHaveBeenCalled();
|
|
196
|
+
expect(notify3).toHaveBeenCalledTimes(1);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("should not call notify when update returns false", async () => {
|
|
200
|
+
const notify = vi.fn();
|
|
201
|
+
await FlowGraph.requestWrite(notify, () => false);
|
|
202
|
+
|
|
203
|
+
expect(notify).not.toHaveBeenCalled();
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("should resolve promise after write is processed", async () => {
|
|
207
|
+
const notify = vi.fn();
|
|
208
|
+
const update = vi.fn(() => true);
|
|
209
|
+
const promise = FlowGraph.requestWrite(notify, update);
|
|
210
|
+
|
|
211
|
+
await expect(promise).resolves.toBeUndefined();
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("should execute effects after write notification", async () => {
|
|
215
|
+
const notify = vi.fn();
|
|
216
|
+
const effectFn = vi.fn();
|
|
217
|
+
const $effect = effect(() => {
|
|
218
|
+
effectFn();
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
FlowGraph.pushEffects([$effect]);
|
|
222
|
+
|
|
223
|
+
await FlowGraph.requestWrite(notify, () => true);
|
|
224
|
+
|
|
225
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(2)); // Initial + after write
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it("should not execute effects when update returns false", async () => {
|
|
229
|
+
const notify = vi.fn();
|
|
230
|
+
const effectFn = vi.fn();
|
|
231
|
+
const $effect = effect(() => {
|
|
232
|
+
effectFn();
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
FlowGraph.pushEffects([$effect]);
|
|
236
|
+
|
|
237
|
+
await FlowGraph.requestWrite(notify, () => false);
|
|
238
|
+
|
|
239
|
+
// Wait to ensure effects don't execute
|
|
240
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
241
|
+
|
|
242
|
+
expect(effectFn).toHaveBeenCalledTimes(1); // Only initial execution
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
describe("pushEffects", () => {
|
|
247
|
+
it("should queue effects for execution", async () => {
|
|
248
|
+
const effectFn1 = vi.fn();
|
|
249
|
+
const effectFn2 = vi.fn();
|
|
250
|
+
const $effect1 = effect(() => {
|
|
251
|
+
effectFn1();
|
|
252
|
+
});
|
|
253
|
+
const $effect2 = effect(() => {
|
|
254
|
+
effectFn2();
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
FlowGraph.pushEffects([$effect1, $effect2]);
|
|
258
|
+
|
|
259
|
+
const notify = vi.fn();
|
|
260
|
+
await FlowGraph.requestTrigger(notify);
|
|
261
|
+
|
|
262
|
+
await vi.waitFor(() => {
|
|
263
|
+
expect(effectFn1).toHaveBeenCalledTimes(2); // Initial + queued
|
|
264
|
+
expect(effectFn2).toHaveBeenCalledTimes(2); // Initial + queued
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it("should execute queued effects after current operation", async () => {
|
|
269
|
+
const effectFn = vi.fn();
|
|
270
|
+
const $effect = effect(() => {
|
|
271
|
+
effectFn();
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
FlowGraph.pushEffects([$effect]);
|
|
275
|
+
|
|
276
|
+
const notify = vi.fn();
|
|
277
|
+
await FlowGraph.requestTrigger(notify);
|
|
278
|
+
|
|
279
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(2));
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it("should execute multiple effects in order", async () => {
|
|
283
|
+
const executionOrder: string[] = [];
|
|
284
|
+
const $effect1 = effect(() => {
|
|
285
|
+
executionOrder.push("effect1");
|
|
286
|
+
});
|
|
287
|
+
const $effect2 = effect(() => {
|
|
288
|
+
executionOrder.push("effect2");
|
|
289
|
+
});
|
|
290
|
+
const $effect3 = effect(() => {
|
|
291
|
+
executionOrder.push("effect3");
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
FlowGraph.pushEffects([$effect1, $effect2, $effect3]);
|
|
295
|
+
|
|
296
|
+
const notify = vi.fn();
|
|
297
|
+
await FlowGraph.requestTrigger(notify);
|
|
298
|
+
|
|
299
|
+
await vi.waitFor(() =>
|
|
300
|
+
expect(executionOrder.length).toBeGreaterThan(3),
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
// Check that effects execute in order (after initial executions)
|
|
304
|
+
const lastThree = executionOrder.slice(-3);
|
|
305
|
+
expect(lastThree).toEqual(["effect1", "effect2", "effect3"]);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it("should clear effects queue after execution", async () => {
|
|
309
|
+
const effectFn1 = vi.fn();
|
|
310
|
+
const effectFn2 = vi.fn();
|
|
311
|
+
const $effect1 = effect(() => {
|
|
312
|
+
effectFn1();
|
|
313
|
+
});
|
|
314
|
+
const $effect2 = effect(() => {
|
|
315
|
+
effectFn2();
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
FlowGraph.pushEffects([$effect1, $effect2]);
|
|
319
|
+
|
|
320
|
+
const notify1 = vi.fn();
|
|
321
|
+
await FlowGraph.requestTrigger(notify1);
|
|
322
|
+
|
|
323
|
+
await vi.waitFor(() => {
|
|
324
|
+
expect(effectFn1).toHaveBeenCalledTimes(2);
|
|
325
|
+
expect(effectFn2).toHaveBeenCalledTimes(2);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
// Push effects again and trigger - should execute again
|
|
329
|
+
FlowGraph.pushEffects([$effect1, $effect2]);
|
|
330
|
+
|
|
331
|
+
const notify2 = vi.fn();
|
|
332
|
+
await FlowGraph.requestTrigger(notify2);
|
|
333
|
+
|
|
334
|
+
await vi.waitFor(() => {
|
|
335
|
+
expect(effectFn1).toHaveBeenCalledTimes(3);
|
|
336
|
+
expect(effectFn2).toHaveBeenCalledTimes(3);
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it("should handle async effects", async () => {
|
|
341
|
+
const executionOrder: string[] = [];
|
|
342
|
+
const $effect = effect(async () => {
|
|
343
|
+
executionOrder.push("effect-start");
|
|
344
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
345
|
+
executionOrder.push("effect-end");
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
FlowGraph.pushEffects([$effect]);
|
|
349
|
+
|
|
350
|
+
const notify = vi.fn();
|
|
351
|
+
const promise = FlowGraph.requestTrigger(notify);
|
|
352
|
+
|
|
353
|
+
executionOrder.push("trigger-called");
|
|
354
|
+
|
|
355
|
+
await promise;
|
|
356
|
+
executionOrder.push("trigger-resolved");
|
|
357
|
+
|
|
358
|
+
await vi.waitFor(() => expect(executionOrder).toContain("effect-end"));
|
|
359
|
+
|
|
360
|
+
expect(executionOrder).toContain("effect-start");
|
|
361
|
+
expect(executionOrder).toContain("effect-end");
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
describe("operation ordering", () => {
|
|
366
|
+
it("should process operations in correct order (read stops, write/trigger continue)", async () => {
|
|
367
|
+
const executionOrder: string[] = [];
|
|
368
|
+
const effectFn = vi.fn(() => executionOrder.push("effect"));
|
|
369
|
+
const $effect = effect(() => {
|
|
370
|
+
effectFn();
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
FlowGraph.pushEffects([$effect]);
|
|
374
|
+
|
|
375
|
+
// Read should stop processing
|
|
376
|
+
await FlowGraph.requestRead(() => {
|
|
377
|
+
executionOrder.push("read");
|
|
378
|
+
return 1;
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
executionOrder.push("after-read");
|
|
382
|
+
|
|
383
|
+
// Write should continue and execute effects
|
|
384
|
+
await FlowGraph.requestWrite(
|
|
385
|
+
() => executionOrder.push("write-notify"),
|
|
386
|
+
() => {
|
|
387
|
+
executionOrder.push("write-update");
|
|
388
|
+
return true;
|
|
389
|
+
},
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
executionOrder.push("after-write");
|
|
393
|
+
|
|
394
|
+
await vi.waitFor(() =>
|
|
395
|
+
expect(executionOrder.length).toBeGreaterThan(5),
|
|
396
|
+
);
|
|
397
|
+
|
|
398
|
+
// Read should not trigger effects
|
|
399
|
+
expect(
|
|
400
|
+
executionOrder.filter((x) => x === "effect").length,
|
|
401
|
+
).toBeGreaterThan(1); // At least initial + after write
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
it("should handle mixed operation types in queue", async () => {
|
|
405
|
+
const executionOrder: string[] = [];
|
|
406
|
+
|
|
407
|
+
const read1 = FlowGraph.requestRead(() => {
|
|
408
|
+
executionOrder.push("read1");
|
|
409
|
+
return 1;
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
const trigger1 = FlowGraph.requestTrigger(() => {
|
|
413
|
+
executionOrder.push("trigger1");
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
const write1 = FlowGraph.requestWrite(
|
|
417
|
+
() => executionOrder.push("write1-notify"),
|
|
418
|
+
() => {
|
|
419
|
+
executionOrder.push("write1-update");
|
|
420
|
+
return true;
|
|
421
|
+
},
|
|
422
|
+
);
|
|
423
|
+
|
|
424
|
+
const read2 = FlowGraph.requestRead(() => {
|
|
425
|
+
executionOrder.push("read2");
|
|
426
|
+
return 2;
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
await Promise.all([read1, trigger1, write1, read2]);
|
|
430
|
+
|
|
431
|
+
// Reads should execute first (stopping processing), then trigger/write
|
|
432
|
+
expect(executionOrder).toContain("read1");
|
|
433
|
+
expect(executionOrder).toContain("read2");
|
|
434
|
+
expect(executionOrder).toContain("trigger1");
|
|
435
|
+
expect(executionOrder).toContain("write1-update");
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
it("should prevent concurrent queue processing", async () => {
|
|
439
|
+
const executionOrder: string[] = [];
|
|
440
|
+
|
|
441
|
+
// Start multiple operations rapidly
|
|
442
|
+
const promises = [];
|
|
443
|
+
for (let i = 0; i < 5; i++) {
|
|
444
|
+
promises.push(
|
|
445
|
+
FlowGraph.requestTrigger(() => {
|
|
446
|
+
executionOrder.push(`trigger-${i}`);
|
|
447
|
+
}),
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
await Promise.all(promises);
|
|
452
|
+
|
|
453
|
+
// All triggers should have been processed sequentially
|
|
454
|
+
expect(executionOrder.length).toBe(5);
|
|
455
|
+
for (let i = 0; i < 5; i++) {
|
|
456
|
+
expect(executionOrder).toContain(`trigger-${i}`);
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
it("should execute effects after each write/trigger but not after read", async () => {
|
|
461
|
+
const effectFn = vi.fn();
|
|
462
|
+
const $effect = effect(() => {
|
|
463
|
+
effectFn();
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
// Initial execution
|
|
467
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(1));
|
|
468
|
+
|
|
469
|
+
// Read should not trigger effects
|
|
470
|
+
FlowGraph.pushEffects([$effect]);
|
|
471
|
+
await FlowGraph.requestRead(() => 1);
|
|
472
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(1));
|
|
473
|
+
|
|
474
|
+
// Write should trigger effects
|
|
475
|
+
await FlowGraph.requestWrite(vi.fn(), () => true);
|
|
476
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(2));
|
|
477
|
+
|
|
478
|
+
// Trigger should trigger effects
|
|
479
|
+
FlowGraph.pushEffects([$effect]);
|
|
480
|
+
await FlowGraph.requestTrigger(vi.fn());
|
|
481
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(3));
|
|
482
|
+
});
|
|
483
|
+
});
|
|
484
|
+
});
|
|
485
|
+
});
|