@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
@@ -0,0 +1,1046 @@
1
+ import type { Flags } from "./flags.ts"
2
+ import { createReactiveSystem } from "./reactive-system.ts"
3
+ import { flags } from "./flags.ts"
4
+ import type { Link, Node, ReactiveSystem } from "./reactive-system.ts"
5
+
6
+ export interface BaseReactorOptions {
7
+ name?: string | undefined;
8
+
9
+ onDepChanged?: (() => void) | undefined;
10
+ onSubChanged?: (() => void) | undefined;
11
+ onHasDep?: ((dep: BaseReactor) => void) | undefined;
12
+ onNoDep?: ((dep: BaseReactor) => void) | undefined;
13
+ onHasSub?: ((sub: BaseReactor) => void) | undefined;
14
+ onNoSub?: ((sub: BaseReactor) => void) | undefined;
15
+
16
+ onDispose?: (() => void) | undefined;
17
+ }
18
+ export abstract class BaseReactor<V = unknown> implements Node {
19
+ headDepLink?: Link | undefined;
20
+ tailDepLink?: Link | undefined;
21
+ headSubLink?: Link | undefined
22
+ tailSubLink?: Link | undefined
23
+ flags: Flags;
24
+
25
+ protected value: V;
26
+
27
+ protected _name: string | undefined;
28
+
29
+ protected _onDepChanged?: (() => void) | undefined;
30
+ protected _onSubChanged?: (() => void) | undefined;
31
+ protected _onHasDep?: ((dep: BaseReactor) => void) | undefined;
32
+ protected _onNoDep?: ((dep: BaseReactor) => void) | undefined;
33
+ protected _onHasSub?: ((sub: BaseReactor) => void) | undefined;
34
+ protected _onNoSub?: ((sub: BaseReactor) => void) | undefined;
35
+
36
+ protected _onDispose?: (() => void) | undefined;
37
+
38
+ constructor(options: BaseReactorOptions) {
39
+ this.flags = flags();
40
+
41
+ // undefined 可能是使用者期待的合法值,因此这里使用 VOID 占位
42
+ // oxlint-disable-next-line no-unsafe-type-assertion
43
+ this.value = VOID as V;
44
+
45
+ this._name = options.name;
46
+
47
+ this._onDepChanged = options.onDepChanged;
48
+ this._onSubChanged = options.onSubChanged;
49
+ this._onHasDep = options.onHasDep;
50
+ this._onNoDep = options.onNoDep;
51
+ this._onHasSub = options.onHasSub;
52
+ this._onNoSub = options.onNoSub;
53
+
54
+ this._onDispose = options.onDispose;
55
+ }
56
+
57
+ getName(): string | undefined {
58
+ return this._name;
59
+ }
60
+
61
+ setName(name: string | undefined): void {
62
+ this._name = name;
63
+ }
64
+
65
+ abstract trackThis(): void;
66
+
67
+ /**
68
+ * Reactor 在运行过程中状态可能会发生变化,此方法用于将状态重置。比如:
69
+ * 在错误发生时调用。
70
+ */
71
+ abstract reset(): void;
72
+
73
+ /**
74
+ * 删除当前 Reactor 与其它 Reactor 之间的所有连接,将状态重置,删除有必要删除的资源,
75
+ * 目标是使当前 Reactor 可以被安全地丢弃而不会引起内存泄漏。
76
+ */
77
+ abstract dispose(): void;
78
+
79
+ triggerOnDepChanged(): void {
80
+ this._onDepChanged?.();
81
+ };
82
+
83
+ triggerOnSubChanged(): void {
84
+ this._onSubChanged?.();
85
+ };
86
+
87
+ triggerOnHasDep(dep: BaseReactor): void {
88
+ this._onHasDep?.(dep);
89
+ };
90
+
91
+ triggerOnNoDep(dep: BaseReactor): void {
92
+ this._onNoDep?.(dep);
93
+ };
94
+
95
+ triggerOnHasSub(sub: BaseReactor): void {
96
+ this._onHasSub?.(sub);
97
+ };
98
+
99
+ triggerOnNoSub(sub: BaseReactor): void {
100
+ this._onNoSub?.(sub);
101
+ this.dispose();
102
+ };
103
+
104
+ triggerOnDispose(): void {
105
+ this._onDispose?.();
106
+ }
107
+ }
108
+
109
+ const VOID = Symbol("void") as unknown;
110
+ const isVoid = <T>(value: T): boolean => {
111
+ return value === VOID;
112
+ }
113
+
114
+ let queue: Effect[] = [];
115
+ const flush = (): void => {
116
+ const tempFlushQueue = queue;
117
+ queue = [];
118
+ try {
119
+ while (tempFlushQueue.length !== 0) {
120
+ const effect = tempFlushQueue.shift()!;
121
+ effect.run();
122
+ }
123
+ } catch {
124
+ while (tempFlushQueue.length !== 0) {
125
+ const effect = tempFlushQueue.shift()!;
126
+ effect.reset();
127
+ }
128
+ }
129
+ }
130
+
131
+ let batchDepth = 0;
132
+ export const flushBatch = (): void => {
133
+ if (batchDepth === 0) {
134
+ flush();
135
+ }
136
+ }
137
+ export const startBatch = (): void => {
138
+ batchDepth = batchDepth + 1;
139
+ }
140
+ export const endBatch = (): void => {
141
+ batchDepth = batchDepth - 1;
142
+ if (batchDepth < 0) {
143
+ throw new Error("endBatch called without matching startBatch");
144
+ }
145
+ flushBatch();
146
+ }
147
+ export const batch = (fn: () => void): void => {
148
+ startBatch();
149
+ try {
150
+ fn();
151
+ } finally {
152
+ endBatch();
153
+ }
154
+ }
155
+
156
+ export const reactiveSystem: ReactiveSystem = createReactiveSystem({
157
+ onDepChanged: (sub: Node) => {
158
+ if (sub instanceof BaseReactor) {
159
+ return sub.triggerOnDepChanged();
160
+ }
161
+ },
162
+ onSubChanged: (dep: Node) => {
163
+ if (dep instanceof BaseReactor) {
164
+ return dep.triggerOnSubChanged();
165
+ }
166
+ },
167
+ onHasDep: (sub: Node, dep: Node) => {
168
+ if (sub instanceof BaseReactor && dep instanceof BaseReactor) {
169
+ return sub.triggerOnHasDep(dep);
170
+ }
171
+ },
172
+ onNoDep: (sub: Node, dep: Node) => {
173
+ if (sub instanceof BaseReactor && dep instanceof BaseReactor) {
174
+ return sub.triggerOnNoDep(dep);
175
+ }
176
+ },
177
+ onHasSub: (dep: Node, sub: Node) => {
178
+ if (dep instanceof BaseReactor && sub instanceof BaseReactor) {
179
+ return dep.triggerOnHasSub(sub);
180
+ }
181
+ },
182
+ onNoSub: (dep: Node, sub: Node) => {
183
+ if (dep instanceof BaseReactor && sub instanceof BaseReactor) {
184
+ return dep.triggerOnNoSub(sub);
185
+ }
186
+ },
187
+
188
+ update(node: Signal | Derived | Computed) {
189
+ return node.update();
190
+ },
191
+ notify(node: Effect) {
192
+ const nodes = []
193
+ let currentNode: Effect | undefined = node
194
+ while (currentNode !== undefined) {
195
+ if (currentNode.flags.hasWatching() === false) {
196
+ break
197
+ }
198
+ currentNode.notify();
199
+ nodes.push(currentNode);
200
+ const nextNode: Node | undefined = currentNode?.headSubLink?.sub
201
+ currentNode = nextNode instanceof Effect ? nextNode : undefined
202
+ }
203
+ queue.push(...nodes.toReversed());
204
+ },
205
+ });
206
+ const {
207
+ removeAllDepLinksOfNode,
208
+ removeAllSubLinksOfNode,
209
+
210
+ withTracking,
211
+ track,
212
+ trackNearestMutableOrWatching,
213
+
214
+ shallowPropagate,
215
+ deeeeepPropagate,
216
+ resolvePending,
217
+ } = reactiveSystem
218
+
219
+ const defaultIsEqual = <V>(oldValue: V, newValue: V): boolean => {
220
+ return Object.is(oldValue, newValue);
221
+ }
222
+
223
+ export interface BaseSignalValueGetterContext { }
224
+ export interface InitializingSignalValueGetterContext extends BaseSignalValueGetterContext {
225
+ isInitializingRun: true;
226
+ }
227
+ export interface UpdatingSignalValueGetterContext<V> extends BaseSignalValueGetterContext {
228
+ isInitializingRun: false;
229
+ previousValue: V;
230
+ }
231
+ export type SignalValueGetterContext<V> =
232
+ | InitializingSignalValueGetterContext
233
+ | UpdatingSignalValueGetterContext<V>;
234
+ export type SignalValueInitializer<V> = (context: InitializingSignalValueGetterContext) => V;
235
+ export type SignalValueUpdater<V> = (context: UpdatingSignalValueGetterContext<V>) => V;
236
+ export type SignalValueGetter<V> = (context: SignalValueGetterContext<V>) => V;
237
+ export interface SignalOptions<V> extends BaseReactorOptions {
238
+ isEqual?: ((oldValue: V, newValue: V) => boolean) | undefined;
239
+ }
240
+ /**
241
+ * Signal 的值只有在被获取的时候才会更新。
242
+ */
243
+ export class Signal<V = unknown> extends BaseReactor<V> {
244
+ private isEqual: (oldValue: V, newValue: V) => boolean;
245
+
246
+ private valueGetter: SignalValueGetter<V>;
247
+
248
+ constructor(valueInitializer: SignalValueInitializer<V>, options: SignalOptions<V>) {
249
+ super(options);
250
+ this.headSubLink = undefined;
251
+ this.tailSubLink = undefined;
252
+ this.flags = this.getInitialFlags();
253
+
254
+ this.isEqual = options.isEqual ?? defaultIsEqual;
255
+
256
+ // oxlint-disable-next-line no-unsafe-type-assertion
257
+ this.valueGetter = valueInitializer as SignalValueGetter<V>;
258
+ }
259
+
260
+ private getInitialFlags(): Flags {
261
+ // 值在创建时尚未计算,因此初始时添加 Dirty 标记
262
+ return flags().setMutable().setDirty();
263
+ }
264
+
265
+ trackThis(): void {
266
+ trackNearestMutableOrWatching(this);
267
+ }
268
+
269
+ reset(): void {
270
+ this.flags = this.getInitialFlags();
271
+ }
272
+
273
+ dispose(): void {
274
+ removeAllDepLinksOfNode(this);
275
+ removeAllSubLinksOfNode(this);
276
+ this.reset();
277
+ this.triggerOnDispose();
278
+ }
279
+
280
+ private internalGet(options: { shouldTrack: boolean }): V {
281
+ const { shouldTrack } = options;
282
+ if (shouldTrack === true) {
283
+ this.trackThis();
284
+ }
285
+
286
+ let valueChanged = false
287
+
288
+ // 如果包含 Dirty 标记,则进行更新
289
+ const shouldUpdate = this.flags.hasDirty();
290
+ if (shouldUpdate === true) {
291
+ valueChanged = this.update()
292
+ }
293
+
294
+ // 如果值变化,则通知下游(仅通知直接下游)
295
+ if (valueChanged === true) {
296
+ shallowPropagate(this);
297
+ }
298
+
299
+ return this.value;
300
+ }
301
+
302
+ get(): V {
303
+ return this.internalGet({ shouldTrack: true });
304
+ }
305
+
306
+ getWithoutTrack(): V {
307
+ return this.internalGet({ shouldTrack: false });
308
+ }
309
+
310
+ private internalSet(options: { valueUpdater: SignalValueUpdater<V> }): void {
311
+ const { valueUpdater } = options;
312
+
313
+ // 将新值暂存起来,等用到的时候再执行更新
314
+ // oxlint-disable-next-line no-unsafe-type-assertion
315
+ this.valueGetter = valueUpdater as SignalValueGetter<V>;
316
+ // 为当前节点添加 Dirty 标记
317
+ this.flags.setDirty();
318
+
319
+ // 更新所有下游节点的标记
320
+ deeeeepPropagate(this);
321
+
322
+ // 执行更新队列
323
+ flushBatch();
324
+ }
325
+
326
+ set(value: V): void {
327
+ return this.internalSet({ valueUpdater: () => value });
328
+ }
329
+
330
+ setWithoutCalculate(valueUpdater: SignalValueUpdater<V>): void {
331
+ return this.internalSet({ valueUpdater });
332
+ }
333
+
334
+ /**
335
+ * 返回值表示更新前后值是否变化。
336
+ */
337
+ update(): boolean {
338
+ this.flags.unsetDirty();
339
+
340
+ const isInitializingRun = isVoid(this.value) === true;
341
+
342
+ if (isInitializingRun === true) {
343
+ const newValue = this.valueGetter({ isInitializingRun: true });
344
+ this.value = newValue;
345
+
346
+ return true;
347
+ } else {
348
+ const oldValue = this.value;
349
+ const newValue = this.valueGetter({ isInitializingRun: false, previousValue: oldValue });
350
+ this.value = newValue;
351
+
352
+ const valueChanged = this.isEqual(oldValue, newValue) === false
353
+ return valueChanged;
354
+ }
355
+ }
356
+ }
357
+
358
+ export interface BaseDerivedValueGetterContext { }
359
+ export interface InitializingSignalValueGetterContext extends BaseDerivedValueGetterContext {
360
+ isInitializingRun: true;
361
+ }
362
+ export interface UpdatingDerivedValueGetterContext<V> extends BaseDerivedValueGetterContext {
363
+ isInitializingRun: false;
364
+ previousValue: V;
365
+ }
366
+ export type DerivedValueGetterContext<V> =
367
+ | InitializingSignalValueGetterContext
368
+ | UpdatingDerivedValueGetterContext<V>;
369
+ export type DerivedValueUpdater<V> = (context: UpdatingDerivedValueGetterContext<V>) => V;
370
+ export type DerivedValueGetter<V> = (context: DerivedValueGetterContext<V>) => V;
371
+ export interface DerivedOptions<V> extends BaseReactorOptions {
372
+ isEqual?: ((oldValue: V, newValue: V) => boolean) | undefined;
373
+ }
374
+ /**
375
+ * Derived 的值只有在被获取的时候才会更新。
376
+ * Derived 的值只有在上游节点的值发生变化时才会更新。
377
+ * Derived 的下游节点全部解除连接时会自动与所有上游节点解除连接,并回到初始状态。
378
+ */
379
+ export class Derived<V = unknown> extends BaseReactor<V> {
380
+ private isEqual: (oldValue: V, newValue: V) => boolean;
381
+
382
+ private valueGetter: DerivedValueGetter<V>;
383
+ private manualValueGetter: DerivedValueGetter<V> | undefined;
384
+
385
+ constructor(valueGetter: DerivedValueGetter<V>, options: DerivedOptions<V>) {
386
+ super(options);
387
+ this.headDepLink = undefined;
388
+ this.tailDepLink = undefined;
389
+ this.headSubLink = undefined;
390
+ this.tailSubLink = undefined;
391
+ this.flags = this.getInitialFlags();
392
+
393
+ this.isEqual = options.isEqual ?? defaultIsEqual;
394
+
395
+ this.valueGetter = valueGetter;
396
+ this.manualValueGetter = undefined;
397
+ }
398
+
399
+ private getInitialFlags(): Flags {
400
+ // 值在创建时尚未计算,因此初始时添加 Dirty 标记
401
+ return flags().setMutable().setDirty();
402
+ }
403
+
404
+ trackThis(): void {
405
+ trackNearestMutableOrWatching(this);
406
+ }
407
+
408
+ reset(): void {
409
+ this.flags = this.getInitialFlags();
410
+ }
411
+
412
+ dispose(): void {
413
+ removeAllDepLinksOfNode(this);
414
+ removeAllSubLinksOfNode(this);
415
+ this.reset();
416
+ this.triggerOnDispose();
417
+ }
418
+
419
+ private internalGet(options: { shouldTrack: boolean }): V {
420
+ const { shouldTrack } = options;
421
+ if (shouldTrack === true) {
422
+ this.trackThis();
423
+ }
424
+
425
+ let valueChanged = false;
426
+
427
+ // 如果包含 Pending 标记,则进行解决
428
+ const shouldResolve = this.flags.hasPending();
429
+ if (shouldResolve === true) {
430
+ resolvePending(this);
431
+ }
432
+
433
+ // 如果包含 Dirty 标记,则进行更新
434
+ const shouldUpdate = this.flags.hasDirty();
435
+ if (shouldUpdate === true) {
436
+ valueChanged = this.update()
437
+ }
438
+
439
+ // 如果值变化,则通知下游(仅通知直接下游)
440
+ if (valueChanged === true) {
441
+ shallowPropagate(this);
442
+ }
443
+
444
+ return this.value;
445
+ }
446
+
447
+ get(): V {
448
+ return this.internalGet({ shouldTrack: true });
449
+ }
450
+
451
+ getWithoutTrack(): V {
452
+ return this.internalGet({ shouldTrack: false });
453
+ }
454
+
455
+ private internalSet(options: { valueUpdater: DerivedValueUpdater<V> }): void {
456
+ const { valueUpdater } = options;
457
+
458
+ // 将新值暂存起来,等用到的时候再执行更新
459
+ // oxlint-disable-next-line no-unsafe-type-assertion
460
+ this.manualValueGetter = valueUpdater as DerivedValueGetter<V>;
461
+ // 为当前节点添加 Dirty 标记
462
+ this.flags.setDirty();
463
+
464
+ // 更新所有下游节点的标记
465
+ deeeeepPropagate(this);
466
+
467
+ // 执行更新队列
468
+ flushBatch();
469
+ }
470
+
471
+ set(value: V): void {
472
+ return this.internalSet({ valueUpdater: () => value });
473
+ }
474
+
475
+ setWithoutCalculate(valueUpdater: DerivedValueUpdater<V>): void {
476
+ return this.internalSet({ valueUpdater });
477
+ }
478
+
479
+ /**
480
+ * 返回值表示更新前后值是否变化。
481
+ */
482
+ private manualUpdate(): boolean {
483
+ const manualValueGetter = this.manualValueGetter;
484
+ if (manualValueGetter === undefined) {
485
+ throw new Error("Derived: manualValueGetter is undefined.");
486
+ }
487
+ this.manualValueGetter = undefined;
488
+
489
+ this.flags.unsetDirty();
490
+
491
+ const isInitializingRun = isVoid(this.value) === true;
492
+
493
+ if (isInitializingRun === true) {
494
+ const newValue = manualValueGetter({ isInitializingRun: true });
495
+ this.value = newValue;
496
+
497
+ return true;
498
+ } else {
499
+ const oldValue = this.value;
500
+ const newValue = manualValueGetter({ isInitializingRun: false, previousValue: oldValue });
501
+ this.value = newValue;
502
+
503
+ const valueChanged = this.isEqual(oldValue, newValue) === false
504
+ return valueChanged;
505
+ }
506
+ }
507
+
508
+ /**
509
+ * 返回值表示更新前后值是否变化。
510
+ */
511
+ update(): boolean {
512
+ if (this.manualValueGetter !== undefined) {
513
+ return this.manualUpdate();
514
+ }
515
+
516
+ const result = withTracking(this, () => {
517
+ this.flags.unsetDirty();
518
+
519
+ const isInitializingRun = isVoid(this.value) === true;
520
+
521
+ if (isInitializingRun === true) {
522
+ const newValue = this.valueGetter({ isInitializingRun: true });
523
+ this.value = newValue;
524
+
525
+ return true;
526
+ } else {
527
+ const oldValue = this.value;
528
+ const newValue = this.valueGetter({ isInitializingRun: false, previousValue: oldValue });
529
+ this.value = newValue;
530
+
531
+ const valueChanged = this.isEqual(oldValue, newValue) === false
532
+ return valueChanged;
533
+ }
534
+
535
+ })
536
+ return result;
537
+ }
538
+ }
539
+
540
+ export interface BaseComputedValueGetterContext { }
541
+ export interface InitializingComputedValueGetterContext extends BaseComputedValueGetterContext {
542
+ isInitializingRun: true;
543
+ }
544
+ export interface UpdatingComputedValueGetterContext<V> extends BaseComputedValueGetterContext {
545
+ isInitializingRun: false;
546
+ previousValue: V;
547
+ }
548
+ export type ComputedValueGetterContext<V> =
549
+ | InitializingComputedValueGetterContext
550
+ | UpdatingComputedValueGetterContext<V>;
551
+ export type ComputedValueGetter<V> = (context: ComputedValueGetterContext<V>) => V;
552
+ export interface ComputedOptions<V> extends BaseReactorOptions {
553
+ isEqual?: ((oldValue: V, newValue: V) => boolean) | undefined;
554
+ }
555
+ /**
556
+ * Computed 的值只有在被获取的时候才会更新。
557
+ * Computed 的值只有在上游节点的值发生变化时才会更新。
558
+ * Computed 的下游节点全部解除连接时会自动与所有上游节点解除连接,并回到初始状态。
559
+ */
560
+ export class Computed<V = unknown> extends BaseReactor<V> {
561
+ private isEqual: (oldValue: V, newValue: V) => boolean;
562
+
563
+ private valueGetter: ComputedValueGetter<V>;
564
+
565
+ constructor(valueGetter: ComputedValueGetter<V>, options: ComputedOptions<V>) {
566
+ super(options);
567
+ this.headDepLink = undefined;
568
+ this.tailDepLink = undefined;
569
+ this.headSubLink = undefined;
570
+ this.tailSubLink = undefined;
571
+ this.flags = this.getInitialFlags();
572
+
573
+ this.isEqual = options.isEqual ?? defaultIsEqual;
574
+
575
+ this.valueGetter = valueGetter;
576
+ }
577
+
578
+ private getInitialFlags(): Flags {
579
+ // 值在创建时尚未计算,因此初始时添加 Dirty 标记
580
+ return flags().setMutable().setDirty();
581
+ }
582
+
583
+ trackThis(): void {
584
+ trackNearestMutableOrWatching(this);
585
+ }
586
+
587
+ reset(): void {
588
+ this.flags = this.getInitialFlags();
589
+ }
590
+
591
+ dispose(): void {
592
+ removeAllDepLinksOfNode(this);
593
+ removeAllSubLinksOfNode(this);
594
+ this.reset();
595
+ this.triggerOnDispose();
596
+ }
597
+
598
+ private internalGet(options: { shouldTrack: boolean }): V {
599
+ const { shouldTrack } = options;
600
+ if (shouldTrack === true) {
601
+ this.trackThis();
602
+ }
603
+
604
+ let valueChanged = false;
605
+
606
+ // 如果包含 Pending 标记,则进行解决
607
+ const shouldResolve = this.flags.hasPending();
608
+ if (shouldResolve === true) {
609
+ resolvePending(this);
610
+ }
611
+
612
+ // 如果包含 Dirty 标记,则进行更新
613
+ const shouldUpdate = this.flags.hasDirty();
614
+ if (shouldUpdate === true) {
615
+ valueChanged = this.update()
616
+ }
617
+
618
+ // 如果值变化,则通知下游(仅通知直接下游)
619
+ if (valueChanged === true) {
620
+ shallowPropagate(this);
621
+ }
622
+
623
+ return this.value;
624
+ }
625
+
626
+ get(): V {
627
+ return this.internalGet({ shouldTrack: true });
628
+ }
629
+
630
+ getWithoutTrack(): V {
631
+ return this.internalGet({ shouldTrack: false });
632
+ }
633
+
634
+ /**
635
+ * 返回值表示更新前后值是否变化。
636
+ */
637
+ update(): boolean {
638
+ const result = withTracking(this, () => {
639
+ this.flags.unsetDirty();
640
+
641
+ const isInitializingRun = isVoid(this.value) === true;
642
+
643
+ if (isInitializingRun === true) {
644
+ const newValue = this.valueGetter({ isInitializingRun: true });
645
+ this.value = newValue;
646
+ return true;
647
+ } else {
648
+ const oldValue = this.value;
649
+ const newValue = this.valueGetter({ isInitializingRun: false, previousValue: oldValue });
650
+ this.value = newValue;
651
+
652
+ const valueChanged = this.isEqual(oldValue, newValue) === false
653
+ return valueChanged;
654
+ }
655
+
656
+ })
657
+ return result;
658
+ }
659
+ }
660
+
661
+
662
+ export type CleanupFn = () => void;
663
+ export interface BaseEffectValueGetterContext {
664
+ setCleanup: (cleanup: CleanupFn) => void;
665
+ }
666
+ export interface InitializingEffectValueGetterContext extends BaseEffectValueGetterContext {
667
+ isInitializingRun: true;
668
+ }
669
+ export interface UpdatingEffectValueGetterContext<V> extends BaseEffectValueGetterContext {
670
+ isInitializingRun: false;
671
+ previousValue: V;
672
+ }
673
+ export type EffectValueGetterContext<V> =
674
+ | InitializingEffectValueGetterContext
675
+ | UpdatingEffectValueGetterContext<V>;
676
+ export type EffectValueGetter<V> = (context: EffectValueGetterContext<V>) => V;
677
+ export interface EffectOptions extends BaseReactorOptions {
678
+ }
679
+ /**
680
+ * Effect 会在创建时立即执行一次传入的函数。
681
+ * Effect 会在每次上游节点的值发生变化时重新执行传入的函数。
682
+ * Effect 在任意时刻最多只能一个待运行。
683
+ * Effect 可以与 Effect 建立连接。
684
+ * 作为上游节点的 Effect 在没有下游节点时会自动与所有上游节点解除连接,并将标记清空。
685
+ */
686
+ export class Effect<V = void> extends BaseReactor<V> {
687
+ private valueGetter: EffectValueGetter<V>;
688
+ private cleanupFn: CleanupFn | undefined;
689
+
690
+ constructor(valueGetter: EffectValueGetter<V>, options: EffectOptions) {
691
+ super(options);
692
+ this.headDepLink = undefined;
693
+ this.tailDepLink = undefined;
694
+ this.headSubLink = undefined;
695
+ this.tailSubLink = undefined;
696
+ this.flags = this.getInitialFlags();
697
+
698
+ this.valueGetter = valueGetter;
699
+ this.cleanupFn = undefined;
700
+
701
+ this.trackThis();
702
+ this.runWithTracking();
703
+ }
704
+
705
+ private getInitialFlags(): Flags {
706
+ // 在创建完成后即进入观察状态,因此初始时添加 Watching 标记
707
+ return flags().setWatching();
708
+ }
709
+
710
+ trackThis(): void {
711
+ track(this);
712
+ }
713
+
714
+ private cleanup(): void {
715
+ this.cleanupFn?.();
716
+ this.cleanupFn = undefined;
717
+ }
718
+
719
+ private runWithTracking(): V {
720
+ const result = withTracking(this, () => {
721
+ this.cleanup();
722
+
723
+ const isInitializingRun = isVoid(this.value) === true;
724
+
725
+ if (isInitializingRun === true) {
726
+ const context: EffectValueGetterContext<V> = {
727
+ setCleanup: (cleanup) => {
728
+ this.cleanupFn = cleanup;
729
+ },
730
+ isInitializingRun,
731
+ }
732
+ const value = this.valueGetter(context);
733
+ this.value = value;
734
+ return value;
735
+ } else {
736
+ const context: EffectValueGetterContext<V> = {
737
+ setCleanup: (cleanup) => {
738
+ this.cleanupFn = cleanup;
739
+ },
740
+ isInitializingRun,
741
+ previousValue: this.value,
742
+ }
743
+ const value = this.valueGetter(context);
744
+ this.value = value;
745
+ return value;
746
+ }
747
+ })
748
+
749
+ return result;
750
+ }
751
+
752
+ reset(): void {
753
+ this.flags = this.getInitialFlags();
754
+ }
755
+
756
+ dispose(): void {
757
+ this.cleanup();
758
+ removeAllDepLinksOfNode(this);
759
+ removeAllSubLinksOfNode(this);
760
+ this.reset();
761
+ this.triggerOnDispose();
762
+ }
763
+
764
+ notify(): void {
765
+ this.flags.unsetWatching();
766
+ }
767
+
768
+ run(): void {
769
+ // 如果包含 Pending 标记,则进行解决
770
+ const shouldResolve = this.flags.hasPending();
771
+ if (shouldResolve === true) {
772
+ resolvePending(this);
773
+ }
774
+
775
+ // order matters:
776
+ // - 需要在 resolvePending 之后,因为:resolvePending 时可能触发 shallowPropagate,
777
+ // 进而触发 notify,notify 只会作用于包含 Watching 标记的节点。
778
+ // - 需要在 runWithTracking 之前,因为:Signal 只会 track 包含 Watching 标记的 Effect。
779
+ this.flags.setWatching();
780
+
781
+ // 如果包含 Dirty 标记,则进行更新
782
+ const shouldRun = this.flags.hasDirty();
783
+ if (shouldRun === true) {
784
+ this.runWithTracking();
785
+ }
786
+ }
787
+ }
788
+
789
+ export interface BaseEffectScopeValueGetterContext extends BaseEffectValueGetterContext { }
790
+ export interface InitializingEffectScopeValueGetterContext
791
+ extends BaseEffectScopeValueGetterContext, InitializingEffectValueGetterContext { }
792
+ export interface UpdatingEffectScopeValueGetterContext<V>
793
+ extends BaseEffectScopeValueGetterContext, UpdatingEffectValueGetterContext<V> {
794
+ }
795
+ export type EffectScopeValueGetterContext<V> =
796
+ | InitializingEffectScopeValueGetterContext
797
+ | UpdatingEffectScopeValueGetterContext<V>;
798
+ export type EffectScopeValueGetter<V> = (context: EffectScopeValueGetterContext<V>) => V;
799
+ export interface EffectScopeOptions extends BaseReactorOptions {
800
+ }
801
+ export class EffectScope<V = void> extends BaseReactor<V> {
802
+ private valueGetter: EffectScopeValueGetter<V>;
803
+ private cleanupFn: CleanupFn | void;
804
+
805
+ constructor(valueGetter: EffectScopeValueGetter<V>, options: EffectScopeOptions) {
806
+ super(options);
807
+ this.headDepLink = undefined;
808
+ this.tailDepLink = undefined;
809
+ this.headSubLink = undefined;
810
+ this.tailSubLink = undefined;
811
+ this.flags = this.getInitialFlags();
812
+
813
+ this.valueGetter = valueGetter;
814
+ this.cleanupFn = undefined;
815
+
816
+ this.trackThis();
817
+ this.runWithTracking();
818
+ }
819
+
820
+ private getInitialFlags(): Flags {
821
+ // 只负责管理作用域,不需要添加任何标记
822
+ return flags();
823
+ }
824
+
825
+ private cleanup(): void {
826
+ this.cleanupFn?.();
827
+ this.cleanupFn = undefined;
828
+ }
829
+
830
+ private runWithTracking(): V {
831
+ const result = withTracking(this, () => {
832
+ this.cleanup();
833
+
834
+ const isInitializingRun = isVoid(this.value) === true;
835
+
836
+ if (isInitializingRun === true) {
837
+ const context: EffectScopeValueGetterContext<V> = {
838
+ setCleanup: (cleanup) => {
839
+ this.cleanupFn = cleanup;
840
+ },
841
+ isInitializingRun,
842
+ }
843
+ const value = this.valueGetter(context);
844
+ this.value = value;
845
+ return value;
846
+ } else {
847
+ const context: EffectScopeValueGetterContext<V> = {
848
+ setCleanup: (cleanup) => {
849
+ this.cleanupFn = cleanup;
850
+ },
851
+ isInitializingRun,
852
+ previousValue: this.value,
853
+ }
854
+ const value = this.valueGetter(context);
855
+ this.value = value;
856
+ return value;
857
+ }
858
+ })
859
+
860
+ return result;
861
+ }
862
+
863
+ trackThis(): void {
864
+ track(this);
865
+ }
866
+
867
+ reset(): void {
868
+ this.flags = this.getInitialFlags();
869
+ }
870
+
871
+ dispose(): void {
872
+ this.cleanup();
873
+ removeAllDepLinksOfNode(this);
874
+ removeAllSubLinksOfNode(this);
875
+ this.reset();
876
+ this.triggerOnDispose();
877
+ }
878
+ }
879
+
880
+ export interface BaseTriggerValueGetterContext {
881
+ setCleanup: (cleanup: CleanupFn) => void;
882
+ }
883
+ export interface InitializingTriggerValueGetterContext extends BaseTriggerValueGetterContext {
884
+ isInitializingRun: true;
885
+ }
886
+ export interface UpdatingTriggerValueGetterContext<V> extends BaseTriggerValueGetterContext {
887
+ isInitializingRun: false;
888
+ previousValue: V;
889
+ }
890
+ export type TriggerValueGetterContext<V> =
891
+ | InitializingTriggerValueGetterContext
892
+ | UpdatingTriggerValueGetterContext<V>
893
+ export type TriggerValueGetter<V> = (context: TriggerValueGetterContext<V>) => V;
894
+ export interface TriggerOptions extends BaseReactorOptions {
895
+ }
896
+ export class Trigger<V = void> extends BaseReactor<V> {
897
+ private valueGetter: TriggerValueGetter<V>;
898
+ private cleanupFn: CleanupFn | void;
899
+
900
+ constructor(valueGetter: TriggerValueGetter<V>, options: TriggerOptions) {
901
+ super(options);
902
+ this.headDepLink = undefined;
903
+ this.tailDepLink = undefined;
904
+ this.headSubLink = undefined;
905
+ this.tailSubLink = undefined;
906
+ this.flags = this.getInitialFlags();
907
+
908
+ this.valueGetter = valueGetter;
909
+ this.cleanupFn = undefined;
910
+
911
+ this.runWithTracking();
912
+ }
913
+
914
+ private getInitialFlags(): Flags {
915
+ return flags().setWatching();
916
+ }
917
+
918
+ private cleanup(): void {
919
+ this.cleanupFn?.();
920
+ this.cleanupFn = undefined;
921
+ }
922
+
923
+ private runWithTracking(): V {
924
+ // 以依赖收集的方式获取目标 Signals
925
+ const result = withTracking(this, () => {
926
+ this.cleanup();
927
+
928
+ const isInitializingRun = isVoid(this.value) === true;
929
+
930
+ if (isInitializingRun === true) {
931
+ const context: TriggerValueGetterContext<V> = {
932
+ isInitializingRun,
933
+ setCleanup: (cleanup) => {
934
+ this.cleanupFn = cleanup;
935
+ },
936
+ }
937
+ const value = this.valueGetter(context);
938
+ this.value = value;
939
+ return value
940
+ } else {
941
+ const context: TriggerValueGetterContext<V> = {
942
+ isInitializingRun,
943
+ setCleanup: (cleanup) => {
944
+ this.cleanupFn = cleanup;
945
+ },
946
+ previousValue: this.value,
947
+ }
948
+ const value = this.valueGetter(context);
949
+ this.value = value;
950
+ return value
951
+ }
952
+
953
+ })
954
+
955
+ // 移除目标 Singals 与当前 Signal 之间的连接,并触发更新机制
956
+ const targetDeps = removeAllDepLinksOfNode(this);
957
+ targetDeps.forEach(dep => {
958
+ deeeeepPropagate(dep);
959
+ shallowPropagate(dep);
960
+ })
961
+
962
+ // 执行更新队列
963
+ flushBatch();
964
+
965
+ this.dispose();
966
+
967
+ return result;
968
+ }
969
+
970
+ trackThis(): void {
971
+ track(this);
972
+ }
973
+
974
+ reset(): void {
975
+ this.flags = this.getInitialFlags();
976
+ }
977
+
978
+ dispose(): void {
979
+ this.cleanup();
980
+ removeAllDepLinksOfNode(this);
981
+ removeAllSubLinksOfNode(this);
982
+ this.reset();
983
+ this.triggerOnDispose();
984
+ }
985
+ }
986
+
987
+ export const isReactor = (target: unknown): target is BaseReactor => {
988
+ return target instanceof BaseReactor;
989
+ }
990
+
991
+ export const isSignal = <V>(target: unknown): target is Signal<V> => {
992
+ return target instanceof Signal;
993
+ }
994
+ export const signal = <V>(valueInitializer: SignalValueInitializer<V>, options?: SignalOptions<V>): Signal<V> => {
995
+ return new Signal(valueInitializer, options ?? {});
996
+ }
997
+ export const isDerived = <V>(target: unknown): target is Derived<V> => {
998
+ return target instanceof Derived;
999
+ }
1000
+ export const derived = <V>(valueGetter: DerivedValueGetter<V>, options?: DerivedOptions<V>): Derived<V> => {
1001
+ return new Derived(valueGetter, options ?? {});
1002
+ }
1003
+ export const isComputed = <V>(target: unknown): target is Computed<V> => {
1004
+ return target instanceof Computed;
1005
+ }
1006
+ export const computed = <V>(valueGetter: ComputedValueGetter<V>, options?: ComputedOptions<V>): Computed<V> => {
1007
+ return new Computed(valueGetter, options ?? {});
1008
+ }
1009
+ export const isEffect = <V>(target: unknown): target is Effect<V> => {
1010
+ return target instanceof Effect;
1011
+ }
1012
+ export const effect = <V>(valueGetter: EffectValueGetter<V>, options?: EffectOptions): Effect<V> => {
1013
+ return new Effect(valueGetter, options ?? {});
1014
+ }
1015
+ export const isEffectScope = <V>(target: unknown): target is EffectScope<V> => {
1016
+ return target instanceof EffectScope;
1017
+ }
1018
+ export const effectScope = <V>(valueGetter: EffectScopeValueGetter<V>, options?: EffectScopeOptions): EffectScope<V> => {
1019
+ return new EffectScope(valueGetter, options ?? {});
1020
+ }
1021
+ export const isTrigger = <V>(target: unknown): target is Trigger<V> => {
1022
+ return target instanceof Trigger;
1023
+ }
1024
+ export const trigger = <V>(valueGetter: TriggerValueGetter<V>, options?: TriggerOptions): Trigger<V> => {
1025
+ return new Trigger(valueGetter, options ?? {});
1026
+ }
1027
+
1028
+ export type ValueReactor<V = unknown> = Signal<V> | Derived<V> | Computed<V>;
1029
+ export type ActionReactor<V = unknown> = Effect<V> | EffectScope<V> | Trigger<V>;
1030
+ export const isValueReactor = <V>(target: unknown): target is ValueReactor<V> => {
1031
+ return isSignal(target) || isDerived(target) || isComputed(target);
1032
+ }
1033
+ export const isActionReactor = <V>(target: unknown): target is ActionReactor<V> => {
1034
+ return isEffect(target) || isEffectScope(target) || isTrigger(target);
1035
+ }
1036
+
1037
+ // oxlint-disable-next-line no-explicit-any
1038
+ export type AnyReactor = BaseReactor<any>
1039
+ // oxlint-disable-next-line no-explicit-any
1040
+ export type AnyValueReactor = ValueReactor<any>
1041
+ // oxlint-disable-next-line no-explicit-any
1042
+ export type AnyActionReactor = ActionReactor<any>
1043
+
1044
+ export type ValueOfReactor<R extends AnyReactor> = R extends BaseReactor<infer V> ? V : never;
1045
+ export type ValueOfValueReactor<R extends AnyValueReactor> = R extends ValueReactor<infer V> ? V : never;
1046
+ export type ValueOfActionReactor<R extends AnyActionReactor> = R extends ActionReactor<infer V> ? V : never;