@planet-matrix/mobius-model 0.1.3 → 0.3.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 (77) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/README.md +21 -0
  3. package/dist/index.d.ts +1 -1
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +2 -2
  6. package/dist/index.js.map +12 -6
  7. package/dist/reactor/index.d.ts +3 -0
  8. package/dist/reactor/index.d.ts.map +1 -0
  9. package/dist/reactor/reactor-core/flags.d.ts.map +1 -0
  10. package/dist/reactor/reactor-core/index.d.ts.map +1 -0
  11. package/dist/reactor/reactor-core/primitive.d.ts +276 -0
  12. package/dist/reactor/reactor-core/primitive.d.ts.map +1 -0
  13. package/dist/{signal/signal-core → reactor/reactor-core}/reactive-system.d.ts +102 -22
  14. package/dist/reactor/reactor-core/reactive-system.d.ts.map +1 -0
  15. package/dist/reactor/reactor-operators/branch.d.ts +19 -0
  16. package/dist/reactor/reactor-operators/branch.d.ts.map +1 -0
  17. package/dist/reactor/reactor-operators/convert.d.ts +30 -0
  18. package/dist/reactor/reactor-operators/convert.d.ts.map +1 -0
  19. package/dist/reactor/reactor-operators/create.d.ts +26 -0
  20. package/dist/reactor/reactor-operators/create.d.ts.map +1 -0
  21. package/dist/reactor/reactor-operators/filter.d.ts +269 -0
  22. package/dist/reactor/reactor-operators/filter.d.ts.map +1 -0
  23. package/dist/reactor/reactor-operators/index.d.ts +8 -0
  24. package/dist/reactor/reactor-operators/index.d.ts.map +1 -0
  25. package/dist/reactor/reactor-operators/join.d.ts +48 -0
  26. package/dist/reactor/reactor-operators/join.d.ts.map +1 -0
  27. package/dist/reactor/reactor-operators/map.d.ts +165 -0
  28. package/dist/reactor/reactor-operators/map.d.ts.map +1 -0
  29. package/dist/reactor/reactor-operators/utility.d.ts +48 -0
  30. package/dist/reactor/reactor-operators/utility.d.ts.map +1 -0
  31. package/package.json +9 -12
  32. package/src/index.ts +1 -1
  33. package/src/reactor/README.md +18 -0
  34. package/src/reactor/index.ts +2 -0
  35. package/src/reactor/reactor-core/primitive.ts +1046 -0
  36. package/src/{signal/signal-core → reactor/reactor-core}/reactive-system.ts +392 -93
  37. package/src/reactor/reactor-operators/branch.ts +66 -0
  38. package/src/reactor/reactor-operators/convert.ts +70 -0
  39. package/src/reactor/reactor-operators/create.ts +66 -0
  40. package/src/reactor/reactor-operators/filter.ts +988 -0
  41. package/src/reactor/reactor-operators/index.ts +7 -0
  42. package/src/reactor/reactor-operators/join.ts +174 -0
  43. package/src/reactor/reactor-operators/map.ts +599 -0
  44. package/src/reactor/reactor-operators/utility.ts +102 -0
  45. package/tests/unit/{signal/computed.spec.ts → reactor/alien-signals-computed.spec.ts} +15 -10
  46. package/tests/unit/reactor/alien-signals-effect-scope.spec.ts +86 -0
  47. package/tests/unit/reactor/alien-signals-effect.spec.ts +395 -0
  48. package/tests/unit/reactor/alien-signals-topology.spec.ts +361 -0
  49. package/tests/unit/reactor/alien-signals-trigger.spec.ts +75 -0
  50. package/tests/unit/reactor/alien-signals-untrack.spec.ts +91 -0
  51. package/tests/unit/reactor/preact-signal.spec.ts +73 -0
  52. package/tests/unit/reactor/reactor-core.spec.ts +219 -0
  53. package/tests/unit/reactor/reactor-operators-branch.spec.ts +33 -0
  54. package/tests/unit/reactor/reactor-operators-convert.spec.ts +31 -0
  55. package/tests/unit/reactor/reactor-operators-create.spec.ts +47 -0
  56. package/tests/unit/reactor/reactor-operators-filter.spec.ts +604 -0
  57. package/tests/unit/reactor/reactor-operators-join.spec.ts +94 -0
  58. package/tests/unit/reactor/reactor-operators-map.spec.ts +327 -0
  59. package/tests/unit/reactor/reactor-operators-utility.spec.ts +55 -0
  60. package/dist/signal/index.d.ts +0 -3
  61. package/dist/signal/index.d.ts.map +0 -1
  62. package/dist/signal/signal-core/flags.d.ts.map +0 -1
  63. package/dist/signal/signal-core/index.d.ts.map +0 -1
  64. package/dist/signal/signal-core/primitive.d.ts +0 -67
  65. package/dist/signal/signal-core/primitive.d.ts.map +0 -1
  66. package/dist/signal/signal-core/reactive-system.d.ts.map +0 -1
  67. package/dist/signal/signal-operators/index.d.ts +0 -4
  68. package/dist/signal/signal-operators/index.d.ts.map +0 -1
  69. package/src/signal/index.ts +0 -2
  70. package/src/signal/signal-core/README.md +0 -4
  71. package/src/signal/signal-core/primitive.ts +0 -275
  72. package/src/signal/signal-operators/index.ts +0 -19
  73. package/tests/unit/signal/effect.spec.ts +0 -108
  74. /package/dist/{signal/signal-core → reactor/reactor-core}/flags.d.ts +0 -0
  75. /package/dist/{signal/signal-core → reactor/reactor-core}/index.d.ts +0 -0
  76. /package/src/{signal/signal-core → reactor/reactor-core}/flags.ts +0 -0
  77. /package/src/{signal/signal-core → reactor/reactor-core}/index.ts +0 -0
@@ -1,5 +1,10 @@
1
1
  import { expect, test } from 'vitest';
2
- import { computed, signal } from '#Source/index.ts';
2
+
3
+ import { computed, signal } from '#Source/reactor/index.ts';
4
+
5
+ /**
6
+ * 这个测试文件应该与 https://github.com/stackblitz/alien-signals/blob/master/tests/computed.spec.ts 保持同步。
7
+ */
3
8
 
4
9
  test('should correctly propagate changes through computed signals', () => {
5
10
  /**
@@ -11,7 +16,7 @@ test('should correctly propagate changes through computed signals', () => {
11
16
  * |
12
17
  * c3
13
18
  */
14
- const src = signal(0);
19
+ const src = signal(() => 0);
15
20
  const c1 = computed(() => src.get() % 2);
16
21
  const c2 = computed(() => c1.get());
17
22
  const c3 = computed(() => c2.get());
@@ -33,7 +38,7 @@ test('should propagate updated source value through chained computations', () =>
33
38
  * \ /
34
39
  * d
35
40
  */
36
- const src = signal(0);
41
+ const src = signal(() => 0);
37
42
  const a = computed(() => src.get());
38
43
  const b = computed(() => a.get() % 2);
39
44
  const c = computed(() => src.get());
@@ -50,11 +55,11 @@ test('should handle flags are indirectly updated during resolve pending', () =>
50
55
  * |
51
56
  * b
52
57
  * / \
53
- * c d
58
+ * c |
54
59
  * \ /
55
60
  * d
56
61
  */
57
- const a = signal(false);
62
+ const a = signal(() => false);
58
63
  const b = computed(() => a.get());
59
64
  const c = computed(() => {
60
65
  b.get();
@@ -76,17 +81,17 @@ test('should not update if the signal value is reverted', () => {
76
81
  * |
77
82
  * c1
78
83
  */
79
- let times = 0;
80
- const src = signal(0);
84
+ const src = signal(() => 0);
85
+ let c1RunTimes = 0;
81
86
  const c1 = computed(() => {
82
- times = times + 1;
87
+ c1RunTimes = c1RunTimes + 1;
83
88
  return src.get();
84
89
  });
85
90
 
86
91
  expect(c1.get()).toBe(0);
87
- expect(times).toBe(1);
92
+ expect(c1RunTimes).toBe(1);
88
93
  src.set(1);
89
94
  src.set(0);
90
95
  expect(c1.get()).toBe(0);
91
- expect(times).toBe(1);
96
+ expect(c1RunTimes).toBe(1);
92
97
  });
@@ -0,0 +1,86 @@
1
+ import { expect, test } from 'vitest';
2
+
3
+ import { effect, effectScope, signal } from '#Source/reactor/index.ts';
4
+
5
+ /**
6
+ * 这个测试文件应该与 https://github.com/stackblitz/alien-signals/blob/master/tests/effectScope.spec.ts 保持同步。
7
+ */
8
+
9
+ test('should not trigger after dispose', () => {
10
+ /**
11
+ * count
12
+ * |
13
+ * e(inner)
14
+ * |
15
+ * e(scope)
16
+ */
17
+ const count = signal(() => 1);
18
+ let innerEffectRunTimes = 0;
19
+ const scope = effectScope(() => {
20
+ effect(() => {
21
+ innerEffectRunTimes = innerEffectRunTimes + 1;
22
+ count.get();
23
+ });
24
+ expect(innerEffectRunTimes).toBe(1);
25
+
26
+ count.set(2);
27
+ expect(innerEffectRunTimes).toBe(2);
28
+ });
29
+
30
+ count.set(3);
31
+ expect(innerEffectRunTimes).toBe(3);
32
+ scope.dispose();
33
+ count.set(4);
34
+ expect(innerEffectRunTimes).toBe(3);
35
+ });
36
+
37
+ test('should dispose inner effects if created in an effect', () => {
38
+ /**
39
+ * source
40
+ * |
41
+ * e(inner)
42
+ * |
43
+ * e(scope)
44
+ * |
45
+ * e(outer)
46
+ */
47
+ const source = signal(() => 1);
48
+ let innerEffectRunTimes = 0;
49
+ effect(() => {
50
+ const scope = effectScope(() => {
51
+ effect(() => {
52
+ source.get();
53
+ innerEffectRunTimes = innerEffectRunTimes + 1;
54
+ });
55
+ });
56
+ expect(innerEffectRunTimes).toBe(1);
57
+
58
+ source.set(2);
59
+ expect(innerEffectRunTimes).toBe(2);
60
+ scope.dispose();
61
+ source.set(3);
62
+ expect(innerEffectRunTimes).toBe(2);
63
+ });
64
+ });
65
+
66
+ test('should track signal updates in an inner scope when accessed by an outer effect', () => {
67
+ /**
68
+ * source
69
+ * |
70
+ * e(scope)
71
+ * |
72
+ * e(outer)
73
+ */
74
+ const source = signal(() => 1);
75
+ let outerEffectRunTimes = 0;
76
+ effect(() => {
77
+ effectScope(() => {
78
+ source.get();
79
+ });
80
+ outerEffectRunTimes = outerEffectRunTimes + 1;
81
+ });
82
+
83
+ expect(outerEffectRunTimes).toBe(1);
84
+ source.set(2);
85
+ expect(outerEffectRunTimes).toBe(2);
86
+ });
@@ -0,0 +1,395 @@
1
+ import { expect, test } from 'vitest';
2
+
3
+ import type { Effect } from '#Source/reactor/index.ts';
4
+ import { computed, effect, effectScope, endBatch, reactiveSystem, signal, startBatch } from '#Source/reactor/index.ts';
5
+
6
+ /**
7
+ * 这个测试文件应该与 https://github.com/stackblitz/alien-signals/blob/master/tests/effect.spec.ts 保持同步。
8
+ */
9
+
10
+ test('should clear subscriptions when untracked by all subscribers', () => {
11
+ /**
12
+ * a
13
+ * |
14
+ * b
15
+ * |
16
+ * e
17
+ */
18
+ let bRunTimes = 0;
19
+ const a = signal(() => 1);
20
+ const b = computed(() => {
21
+ bRunTimes = bRunTimes + 1;
22
+ return a.get() * 2;
23
+ });
24
+ const e = effect(() => {
25
+ b.get();
26
+ });
27
+
28
+ expect(bRunTimes).toBe(1);
29
+ a.set(2);
30
+ expect(bRunTimes).toBe(2);
31
+ e.dispose();
32
+ a.set(3);
33
+ expect(bRunTimes).toBe(2);
34
+ });
35
+
36
+ test('should not run untracked inner effect', () => {
37
+ /**
38
+ * a
39
+ * / \
40
+ * b e2(inner)
41
+ * \ /
42
+ * e1(outer)
43
+ *
44
+ * 解释:当 a 发生变更时,e1 总是会先于 e2 运行,e1 运行时会重新收集依赖,由于
45
+ * e2 是在条件分支中创建的,若收集依赖时该条件分支未被运行,依赖收集机制也
46
+ * 会将之前 e1 和 e2 之间的依赖关系清除掉,同时 e2 和 a 之间的依赖关系也会
47
+ * 被清除掉。
48
+ */
49
+ let outerEffectTruthyBranchRunTimes = 0;
50
+ const a = signal(() => 3);
51
+ const b = computed(() => a.get() > 0);
52
+ const _outerEffect = effect(() => {
53
+ if (b.get() === true) {
54
+ outerEffectTruthyBranchRunTimes = outerEffectTruthyBranchRunTimes + 1;
55
+ const _innerEffect = effect(() => {
56
+ if (a.get() === 0) {
57
+ throw new Error("bad");
58
+ }
59
+ });
60
+ }
61
+ });
62
+
63
+ expect(outerEffectTruthyBranchRunTimes).toBe(1);
64
+ a.set(2);
65
+ expect(outerEffectTruthyBranchRunTimes).toBe(1);
66
+ a.set(1);
67
+ expect(outerEffectTruthyBranchRunTimes).toBe(1);
68
+ a.set(0);
69
+ expect(outerEffectTruthyBranchRunTimes).toBe(1);
70
+ });
71
+
72
+ test('should run outer effect first', () => {
73
+ /**
74
+ * a b
75
+ * | \ /
76
+ * | \ /
77
+ * | e2(inner)
78
+ * \ /
79
+ * e1(outer)
80
+ */
81
+ const a = signal(() => 1);
82
+ const b = signal(() => 1);
83
+ effect(() => {
84
+ if (a.get() !== 0) {
85
+ effect(() => {
86
+ b.get();
87
+ if (a.get() === 0) {
88
+ throw new Error("bad");
89
+ }
90
+ });
91
+ }
92
+ });
93
+
94
+ startBatch();
95
+ b.set(0);
96
+ a.set(0);
97
+ endBatch();
98
+ });
99
+
100
+ test('should not trigger inner effect when resolve maybe dirty', () => {
101
+ /**
102
+ * a
103
+ * |
104
+ * b
105
+ * |
106
+ * e2(inner)
107
+ * |
108
+ * e1(outer)
109
+ */
110
+ const a = signal(() => 0);
111
+ const b = computed(() => a.get() % 2);
112
+ let innerTriggerTimes = 0;
113
+ effect(() => {
114
+ effect(() => {
115
+ b.get();
116
+ innerTriggerTimes = innerTriggerTimes + 1;
117
+ if (innerTriggerTimes >= 2) {
118
+ throw new Error("bad");
119
+ }
120
+ });
121
+ });
122
+
123
+ expect(innerTriggerTimes).toBe(1);
124
+ a.set(2);
125
+ expect(innerTriggerTimes).toBe(1);
126
+ });
127
+
128
+ test('should notify inner effects in the same order as non-inner effects', () => {
129
+ const a = signal(() => 0);
130
+ const b = signal(() => 0);
131
+ const c = computed(() => a.get() - b.get());
132
+ const order1: string[] = [];
133
+ const order2: string[] = [];
134
+ const order3: string[] = [];
135
+
136
+ effect(() => {
137
+ order1.push('effect1');
138
+ a.get();
139
+ });
140
+ effect(() => {
141
+ order1.push('effect2');
142
+ a.get();
143
+ b.get();
144
+ });
145
+
146
+ effect(() => {
147
+ c.get();
148
+ effect(() => {
149
+ order2.push('effect1');
150
+ a.get();
151
+ });
152
+ effect(() => {
153
+ order2.push('effect2');
154
+ a.get();
155
+ b.get();
156
+ });
157
+ });
158
+
159
+ effectScope(() => {
160
+ effect(() => {
161
+ order3.push('effect1');
162
+ a.get();
163
+ });
164
+ effect(() => {
165
+ order3.push('effect2');
166
+ a.get();
167
+ b.get();
168
+ });
169
+ });
170
+
171
+ order1.length = 0;
172
+ order2.length = 0;
173
+ order3.length = 0;
174
+
175
+ startBatch();
176
+ b.set(1);
177
+ a.set(1);
178
+ endBatch();
179
+
180
+ expect(order1).toEqual(['effect2', 'effect1']);
181
+ expect(order2).toEqual(order1);
182
+ expect(order3).toEqual(order1);
183
+ });
184
+
185
+ test('should custom effect support batch', () => {
186
+ const batchEffect = (fn: () => void): Effect => {
187
+ return effect(() => {
188
+ startBatch();
189
+ try {
190
+ return fn();
191
+ } finally {
192
+ endBatch();
193
+ }
194
+ });
195
+ }
196
+
197
+ const logs: string[] = [];
198
+ const a = signal(() => 0);
199
+ const b = signal(() => 0);
200
+
201
+ const aa = computed(() => {
202
+ logs.push('aa-0');
203
+ if (!a.get()) {
204
+ b.set(1);
205
+ }
206
+ logs.push('aa-1');
207
+ });
208
+
209
+ const bb = computed(() => {
210
+ logs.push('bb');
211
+ return b.get();
212
+ });
213
+
214
+ batchEffect(() => {
215
+ bb.get();
216
+ });
217
+ batchEffect(() => {
218
+ aa.get();
219
+ });
220
+
221
+ expect(logs).toEqual(['bb', 'aa-0', 'aa-1', 'bb']);
222
+ });
223
+
224
+ test('should duplicate subscribers do not affect the notify order', () => {
225
+ const srcA = signal(() => 0, { name: "srcA" });
226
+ const srcB = signal(() => 0, { name: "srcB" });
227
+ const order: string[] = [];
228
+
229
+ const _effectA = effect(() => {
230
+ order.push('a');
231
+ reactiveSystem.setNoActiveNodeAsSub();
232
+ const isOne = srcB.get() === 1;
233
+ reactiveSystem.resetActiveNodeAsSub();
234
+ if (isOne) {
235
+ srcA.get();
236
+ }
237
+ srcB.get();
238
+ srcA.get();
239
+ }, { name: "effect-a" });
240
+ const _effectB = effect(() => {
241
+ order.push('b');
242
+ srcA.get();
243
+ }, { name: "effect-b" });
244
+ srcB.set(1);
245
+ expect(order).toEqual(["a", "b", "a"]);
246
+
247
+ order.length = 0;
248
+ srcA.set(srcA.get() + 1);
249
+ expect(order).toEqual(["a", "b"]);
250
+ });
251
+
252
+ test('should handle side effect with inner effects', () => {
253
+ const a = signal(() => 0);
254
+ const b = signal(() => 0);
255
+ const order: string[] = [];
256
+
257
+ effect(() => {
258
+ effect(() => {
259
+ a.get();
260
+ order.push('a');
261
+ });
262
+ effect(() => {
263
+ b.get();
264
+ order.push('b');
265
+ });
266
+ expect(order).toEqual(['a', 'b']);
267
+
268
+ order.length = 0;
269
+ b.set(1);
270
+ a.set(1);
271
+ expect(order).toEqual(['b', 'a']);
272
+ });
273
+ });
274
+
275
+ test('should handle flags are indirectly updated during resolve pending', () => {
276
+ /**
277
+ * a
278
+ * |
279
+ * b
280
+ * / \
281
+ * c |
282
+ * \ /
283
+ * d
284
+ * |
285
+ * e
286
+ */
287
+ const a = signal(() => false);
288
+ const b = computed(() => a.get());
289
+ const c = computed(() => {
290
+ b.get();
291
+ return 0;
292
+ });
293
+ const d = computed(() => {
294
+ c.get();
295
+ return b.get();
296
+ });
297
+
298
+ let effectRunTimes = 0;
299
+ effect(() => {
300
+ d.get();
301
+ effectRunTimes = effectRunTimes + 1;
302
+ });
303
+ expect(effectRunTimes).toBe(1);
304
+ a.set(true);
305
+ expect(effectRunTimes).toBe(2);
306
+ });
307
+
308
+ test('should handle effect recursion for the first execution', () => {
309
+ const src1 = signal(() => 0);
310
+ const src2 = signal(() => 0);
311
+
312
+ let effectARunTimes = 0;
313
+ let effectBRunTimes = 0;
314
+
315
+ effect(() => {
316
+ effectARunTimes = effectARunTimes + 1;
317
+ src1.set(Math.min(src1.get() + 1, 5));
318
+ });
319
+ effect(() => {
320
+ effectBRunTimes = effectBRunTimes + 1;
321
+ src2.set(Math.min(src2.get() + 1, 5));
322
+ src2.get();
323
+ });
324
+
325
+ expect(effectARunTimes).toBe(1);
326
+ expect(effectBRunTimes).toBe(1);
327
+ });
328
+
329
+ test('should support custom recurse effect', () => {
330
+ const src = signal(() => 0);
331
+
332
+ let effectRunTimes = 0;
333
+
334
+ effect(() => {
335
+ reactiveSystem.getActiveNodeAsSub()!.flags.unsetTracking();
336
+ effectRunTimes = effectRunTimes + 1;
337
+ src.set(Math.min(src.get() + 1, 5));
338
+ });
339
+
340
+ expect(effectRunTimes).toBe(6);
341
+ });
342
+
343
+ test('should not execute skipped effects from previous failed flush when updating unrelated signal', () => {
344
+
345
+ /**
346
+ * a c b
347
+ * | | |
348
+ * / | \ d |
349
+ * / | \ / |
350
+ * e1 e2 e3 e4
351
+ */
352
+
353
+ const a = signal(() => 0, { name: "a" });
354
+ const b = signal(() => 0, { name: "b" });
355
+ const c = signal(() => 0, { name: "c" });
356
+ const d = computed(() => c.get(), { name: "d" });
357
+
358
+ let effect3Executed = false;
359
+
360
+ const _effect1 = effect(() => {
361
+ a.get();
362
+ }, { name: "effect1" });
363
+ const _effect2 = effect(() => {
364
+ if (a.get() === 2) {
365
+ throw new Error('Error in effect 2');
366
+ }
367
+ }, { name: "effect2" });
368
+ const _effect3 = effect(() => {
369
+ a.get();
370
+ d.get();
371
+ effect3Executed = true;
372
+ }, { name: "effect3" });
373
+ const _effect4 = effect(() => {
374
+ b.get();
375
+ }, { name: "effect4" });
376
+
377
+ expect(effect3Executed).toBe(true);
378
+ a.set(1);
379
+ expect(effect3Executed).toBe(true);
380
+
381
+ effect3Executed = false;
382
+ try {
383
+ a.set(2);
384
+ } catch (exception) {
385
+ // oxlint-disable-next-line no-unsafe-type-assertion
386
+ expect((exception as Error).message).toBe('Error in effect 2');
387
+ }
388
+ expect(effect3Executed).toBe(false);
389
+
390
+ b.set(1);
391
+ expect(effect3Executed).toBe(false);
392
+
393
+ c.set(1);
394
+ expect(effect3Executed).toBe(true);
395
+ });