@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.
- package/CHANGELOG.md +54 -0
- package/README.md +21 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +12 -6
- package/dist/reactor/index.d.ts +3 -0
- package/dist/reactor/index.d.ts.map +1 -0
- package/dist/reactor/reactor-core/flags.d.ts.map +1 -0
- package/dist/reactor/reactor-core/index.d.ts.map +1 -0
- package/dist/reactor/reactor-core/primitive.d.ts +276 -0
- package/dist/reactor/reactor-core/primitive.d.ts.map +1 -0
- package/dist/{signal/signal-core → reactor/reactor-core}/reactive-system.d.ts +102 -22
- package/dist/reactor/reactor-core/reactive-system.d.ts.map +1 -0
- package/dist/reactor/reactor-operators/branch.d.ts +19 -0
- package/dist/reactor/reactor-operators/branch.d.ts.map +1 -0
- package/dist/reactor/reactor-operators/convert.d.ts +30 -0
- package/dist/reactor/reactor-operators/convert.d.ts.map +1 -0
- package/dist/reactor/reactor-operators/create.d.ts +26 -0
- package/dist/reactor/reactor-operators/create.d.ts.map +1 -0
- package/dist/reactor/reactor-operators/filter.d.ts +269 -0
- package/dist/reactor/reactor-operators/filter.d.ts.map +1 -0
- package/dist/reactor/reactor-operators/index.d.ts +8 -0
- package/dist/reactor/reactor-operators/index.d.ts.map +1 -0
- package/dist/reactor/reactor-operators/join.d.ts +48 -0
- package/dist/reactor/reactor-operators/join.d.ts.map +1 -0
- package/dist/reactor/reactor-operators/map.d.ts +165 -0
- package/dist/reactor/reactor-operators/map.d.ts.map +1 -0
- package/dist/reactor/reactor-operators/utility.d.ts +48 -0
- package/dist/reactor/reactor-operators/utility.d.ts.map +1 -0
- package/package.json +9 -12
- package/src/index.ts +1 -1
- package/src/reactor/README.md +18 -0
- package/src/reactor/index.ts +2 -0
- package/src/reactor/reactor-core/primitive.ts +1046 -0
- package/src/{signal/signal-core → reactor/reactor-core}/reactive-system.ts +392 -93
- package/src/reactor/reactor-operators/branch.ts +66 -0
- package/src/reactor/reactor-operators/convert.ts +70 -0
- package/src/reactor/reactor-operators/create.ts +66 -0
- package/src/reactor/reactor-operators/filter.ts +988 -0
- package/src/reactor/reactor-operators/index.ts +7 -0
- package/src/reactor/reactor-operators/join.ts +174 -0
- package/src/reactor/reactor-operators/map.ts +599 -0
- package/src/reactor/reactor-operators/utility.ts +102 -0
- package/tests/unit/{signal/computed.spec.ts → reactor/alien-signals-computed.spec.ts} +15 -10
- package/tests/unit/reactor/alien-signals-effect-scope.spec.ts +86 -0
- package/tests/unit/reactor/alien-signals-effect.spec.ts +395 -0
- package/tests/unit/reactor/alien-signals-topology.spec.ts +361 -0
- package/tests/unit/reactor/alien-signals-trigger.spec.ts +75 -0
- package/tests/unit/reactor/alien-signals-untrack.spec.ts +91 -0
- package/tests/unit/reactor/preact-signal.spec.ts +73 -0
- package/tests/unit/reactor/reactor-core.spec.ts +219 -0
- package/tests/unit/reactor/reactor-operators-branch.spec.ts +33 -0
- package/tests/unit/reactor/reactor-operators-convert.spec.ts +31 -0
- package/tests/unit/reactor/reactor-operators-create.spec.ts +47 -0
- package/tests/unit/reactor/reactor-operators-filter.spec.ts +604 -0
- package/tests/unit/reactor/reactor-operators-join.spec.ts +94 -0
- package/tests/unit/reactor/reactor-operators-map.spec.ts +327 -0
- package/tests/unit/reactor/reactor-operators-utility.spec.ts +55 -0
- package/dist/signal/index.d.ts +0 -3
- package/dist/signal/index.d.ts.map +0 -1
- package/dist/signal/signal-core/flags.d.ts.map +0 -1
- package/dist/signal/signal-core/index.d.ts.map +0 -1
- package/dist/signal/signal-core/primitive.d.ts +0 -67
- package/dist/signal/signal-core/primitive.d.ts.map +0 -1
- package/dist/signal/signal-core/reactive-system.d.ts.map +0 -1
- package/dist/signal/signal-operators/index.d.ts +0 -4
- package/dist/signal/signal-operators/index.d.ts.map +0 -1
- package/src/signal/index.ts +0 -2
- package/src/signal/signal-core/README.md +0 -4
- package/src/signal/signal-core/primitive.ts +0 -275
- package/src/signal/signal-operators/index.ts +0 -19
- package/tests/unit/signal/effect.spec.ts +0 -108
- /package/dist/{signal/signal-core → reactor/reactor-core}/flags.d.ts +0 -0
- /package/dist/{signal/signal-core → reactor/reactor-core}/index.d.ts +0 -0
- /package/src/{signal/signal-core → reactor/reactor-core}/flags.ts +0 -0
- /package/src/{signal/signal-core → reactor/reactor-core}/index.ts +0 -0
|
@@ -1,275 +0,0 @@
|
|
|
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 } from "./reactive-system.ts"
|
|
5
|
-
|
|
6
|
-
const VOID = Symbol("void") as unknown;
|
|
7
|
-
|
|
8
|
-
const queue: Effect[] = [];
|
|
9
|
-
const flush = (): void => {
|
|
10
|
-
while (queue.length !== 0) {
|
|
11
|
-
queue.shift()!.run();
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
let batchDepth = 0;
|
|
16
|
-
export const startBatch = (): void => {
|
|
17
|
-
batchDepth = batchDepth + 1;
|
|
18
|
-
}
|
|
19
|
-
export const endBatch = (): void => {
|
|
20
|
-
batchDepth = batchDepth - 1;
|
|
21
|
-
if (batchDepth === 0) {
|
|
22
|
-
flush();
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const reactiveSystem = createReactiveSystem({
|
|
27
|
-
update(node: Signal | Computed) {
|
|
28
|
-
return node.update();
|
|
29
|
-
},
|
|
30
|
-
notify(node: Effect) {
|
|
31
|
-
const nodes = []
|
|
32
|
-
let currentNode: Effect | undefined = node
|
|
33
|
-
while (currentNode !== undefined) {
|
|
34
|
-
if (currentNode.flags.hasWatching() === false) {
|
|
35
|
-
break
|
|
36
|
-
}
|
|
37
|
-
node.notify();
|
|
38
|
-
nodes.push(currentNode);
|
|
39
|
-
const nextNode: Node | undefined = currentNode?.headSubLink?.sub
|
|
40
|
-
currentNode = nextNode instanceof Effect ? nextNode : undefined
|
|
41
|
-
}
|
|
42
|
-
queue.push(...nodes.toReversed());
|
|
43
|
-
},
|
|
44
|
-
unwatched(node: Signal | Computed | Effect) {
|
|
45
|
-
return node.stop();
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
const {
|
|
49
|
-
unlinkAllDepLinksOfNode,
|
|
50
|
-
|
|
51
|
-
withTracking,
|
|
52
|
-
track,
|
|
53
|
-
|
|
54
|
-
shallowPropagate,
|
|
55
|
-
deeeeepPropagate,
|
|
56
|
-
resolvePending,
|
|
57
|
-
} = reactiveSystem
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Signal 的值只有在被获取的时候才会更新。
|
|
61
|
-
*/
|
|
62
|
-
export class Signal<T = unknown> implements Node {
|
|
63
|
-
headSubLink: Link | undefined;
|
|
64
|
-
tailSubLink: Link | undefined;
|
|
65
|
-
flags: Flags;
|
|
66
|
-
|
|
67
|
-
value: T;
|
|
68
|
-
pendingValue: T;
|
|
69
|
-
|
|
70
|
-
constructor(value: T) {
|
|
71
|
-
this.headSubLink = undefined;
|
|
72
|
-
this.tailSubLink = undefined;
|
|
73
|
-
this.flags = flags().setMutable();
|
|
74
|
-
|
|
75
|
-
this.pendingValue = value;
|
|
76
|
-
this.value = value;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
get(): T {
|
|
80
|
-
let valueChanged = false
|
|
81
|
-
|
|
82
|
-
// 如果包含 Dirty 标记,则进行更新
|
|
83
|
-
const shouldUpdate = this.flags.hasDirty();
|
|
84
|
-
if (shouldUpdate === true) {
|
|
85
|
-
valueChanged = this.update()
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// 如果值发生变化,则更新直接下游节点的标记
|
|
89
|
-
if (valueChanged === true) {
|
|
90
|
-
shallowPropagate(this);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// 自动进行依赖收集
|
|
94
|
-
track(this);
|
|
95
|
-
|
|
96
|
-
return this.value;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
set(value: T): void {
|
|
100
|
-
// 将新值暂存起来,等用到的时候再执行更新
|
|
101
|
-
this.pendingValue = value;
|
|
102
|
-
// 为当前节点添加 Dirty 标记
|
|
103
|
-
this.flags.setDirty();
|
|
104
|
-
|
|
105
|
-
// 更新所有下游节点的标记
|
|
106
|
-
deeeeepPropagate(this);
|
|
107
|
-
|
|
108
|
-
// 执行更新队列
|
|
109
|
-
if (batchDepth === 0) {
|
|
110
|
-
flush();
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* 返回值表示更新前后值是否变化。
|
|
116
|
-
*/
|
|
117
|
-
update(): boolean {
|
|
118
|
-
this.flags.unsetDirty();
|
|
119
|
-
|
|
120
|
-
const oldValue = this.value;
|
|
121
|
-
const newValue = this.pendingValue;
|
|
122
|
-
this.value = newValue;
|
|
123
|
-
|
|
124
|
-
return oldValue !== newValue;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
stop(): void {
|
|
128
|
-
// do nothing
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Computed 的值只有在被获取的时候才会更新。
|
|
134
|
-
* Computed 的值只有在上游节点的值发生变化时才会更新。
|
|
135
|
-
* Computed 的下游节点全部解除连接时会自动与所有上游节点解除连接,并回到初始状态。
|
|
136
|
-
*/
|
|
137
|
-
export class Computed<T = unknown> implements Node {
|
|
138
|
-
headDepLink: Link | undefined;
|
|
139
|
-
tailDepLink: Link | undefined;
|
|
140
|
-
headSubLink: Link | undefined;
|
|
141
|
-
tailSubLink: Link | undefined;
|
|
142
|
-
flags: Flags;
|
|
143
|
-
|
|
144
|
-
value: T;
|
|
145
|
-
getValue: () => T;
|
|
146
|
-
|
|
147
|
-
constructor(getValue: () => T) {
|
|
148
|
-
this.headDepLink = undefined;
|
|
149
|
-
this.tailDepLink = undefined;
|
|
150
|
-
this.headSubLink = undefined;
|
|
151
|
-
this.tailSubLink = undefined;
|
|
152
|
-
// 值在创建时尚未计算,因此初始时添加 Dirty 标记
|
|
153
|
-
this.flags = flags().setMutable().setDirty();
|
|
154
|
-
|
|
155
|
-
// undefined 可能是使用者期待的合法值,因此这里使用 VOID 占位
|
|
156
|
-
// oxlint-disable-next-line no-unsafe-type-assertion
|
|
157
|
-
this.value = VOID as T;
|
|
158
|
-
this.getValue = getValue;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
get(): T {
|
|
162
|
-
let valueChanged = false;
|
|
163
|
-
|
|
164
|
-
// 如果包含 Pending 标记,则进行解决
|
|
165
|
-
const shouldResolve = this.flags.hasPending();
|
|
166
|
-
if (shouldResolve === true) {
|
|
167
|
-
resolvePending(this);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// 如果包含 Dirty 标记,则进行更新
|
|
171
|
-
const shouldUpdate = this.flags.hasDirty();
|
|
172
|
-
if (shouldUpdate === true) {
|
|
173
|
-
valueChanged = this.update()
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// 如果值变化,则通知下游(仅通知直接下游)
|
|
177
|
-
if (valueChanged === true) {
|
|
178
|
-
shallowPropagate(this);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// 自动进行依赖收集
|
|
182
|
-
track(this);
|
|
183
|
-
|
|
184
|
-
return this.value;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* 返回值表示更新前后值是否变化。
|
|
189
|
-
*/
|
|
190
|
-
update(): boolean {
|
|
191
|
-
const result = withTracking(this, () => {
|
|
192
|
-
const oldValue = this.value;
|
|
193
|
-
const newValue = this.getValue();
|
|
194
|
-
this.value = newValue;
|
|
195
|
-
return oldValue !== newValue;
|
|
196
|
-
})
|
|
197
|
-
return result;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
stop(): void {
|
|
201
|
-
unlinkAllDepLinksOfNode(this);
|
|
202
|
-
this.flags = flags().setMutable().setDirty();
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Effect 会在创建时立即执行一次传入的函数。
|
|
208
|
-
* Effect 会在每次上游节点的值发生变化时重新执行传入的函数。
|
|
209
|
-
* Effect 在任意时刻最多只能一个待运行。
|
|
210
|
-
* Effect 可以与 Effect 建立连接。
|
|
211
|
-
* 作为上游节点的 Effect 在没有下游节点时会自动与所有上游节点解除连接,并将标记清空。
|
|
212
|
-
*/
|
|
213
|
-
export class Effect implements Node {
|
|
214
|
-
headDepLink: Link | undefined;
|
|
215
|
-
tailDepLink: Link | undefined;
|
|
216
|
-
headSubLink: Link | undefined;
|
|
217
|
-
tailSubLink: Link | undefined;
|
|
218
|
-
flags: Flags;
|
|
219
|
-
|
|
220
|
-
fn: () => void;
|
|
221
|
-
|
|
222
|
-
constructor(fn: () => void) {
|
|
223
|
-
this.headDepLink = undefined;
|
|
224
|
-
this.tailDepLink = undefined;
|
|
225
|
-
this.headSubLink = undefined;
|
|
226
|
-
this.tailSubLink = undefined;
|
|
227
|
-
// 在创建完成后即进入观察状态,因此初始时添加 Watching 标记
|
|
228
|
-
this.flags = flags().setWatching();
|
|
229
|
-
|
|
230
|
-
this.fn = fn;
|
|
231
|
-
this.internalRun();
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
notify(): void {
|
|
235
|
-
this.flags.unsetWatching();
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
run(): void {
|
|
239
|
-
this.flags.setWatching();
|
|
240
|
-
|
|
241
|
-
// 如果包含 Pending 标记,则进行解决
|
|
242
|
-
const shouldResolve = this.flags.hasPending();
|
|
243
|
-
if (shouldResolve === true) {
|
|
244
|
-
resolvePending(this);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// 如果包含 Dirty 标记,则进行更新
|
|
248
|
-
const shouldRun = this.flags.hasDirty();
|
|
249
|
-
if (shouldRun === true) {
|
|
250
|
-
this.internalRun();
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
private internalRun(): void {
|
|
255
|
-
track(this);
|
|
256
|
-
withTracking(this, () => {
|
|
257
|
-
return this.fn();
|
|
258
|
-
})
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
stop(): void {
|
|
262
|
-
unlinkAllDepLinksOfNode(this);
|
|
263
|
-
this.flags.clear();
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
export const signal = <T>(initialValue: T): Signal<T> => {
|
|
268
|
-
return new Signal(initialValue);
|
|
269
|
-
}
|
|
270
|
-
export const computed = <T>(getter: () => T): Computed<T> => {
|
|
271
|
-
return new Computed<T>(getter);
|
|
272
|
-
}
|
|
273
|
-
export const effect = (fn: () => void): Effect => {
|
|
274
|
-
return new Effect(fn);
|
|
275
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type { Signal } from "#Source/index.ts"
|
|
2
|
-
import { effect, signal } from "#Source/index.ts"
|
|
3
|
-
|
|
4
|
-
export const withHistory = <T>(orginal: Signal<T>): Signal<T[]> => {
|
|
5
|
-
const history: T[] = []
|
|
6
|
-
effect(() => {
|
|
7
|
-
history.push(orginal.get())
|
|
8
|
-
})
|
|
9
|
-
const result = signal<T[]>(history)
|
|
10
|
-
return result
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export const zip = <T, U>(signal1: Signal<T>, signal2: Signal<U>): Signal<[T, U]> => {
|
|
14
|
-
const result = signal<[T, U]>([signal1.get(), signal2.get()])
|
|
15
|
-
effect(() => {
|
|
16
|
-
result.set([signal1.get(), signal2.get()])
|
|
17
|
-
})
|
|
18
|
-
return result
|
|
19
|
-
}
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import { expect, test } from 'vitest';
|
|
2
|
-
import { computed, effect, endBatch, signal, startBatch } from '#Source/index.ts';
|
|
3
|
-
|
|
4
|
-
test('should clear subscriptions when untracked by all subscribers', () => {
|
|
5
|
-
/**
|
|
6
|
-
* a
|
|
7
|
-
* |
|
|
8
|
-
* b
|
|
9
|
-
* |
|
|
10
|
-
* e
|
|
11
|
-
*/
|
|
12
|
-
let bRunTimes = 0;
|
|
13
|
-
const a = signal(1);
|
|
14
|
-
const b = computed(() => {
|
|
15
|
-
bRunTimes = bRunTimes + 1;
|
|
16
|
-
return a.get() * 2;
|
|
17
|
-
});
|
|
18
|
-
const e = effect(() => {
|
|
19
|
-
b.get();
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
expect(bRunTimes).toBe(1);
|
|
23
|
-
a.set(2);
|
|
24
|
-
expect(bRunTimes).toBe(2);
|
|
25
|
-
e.stop();
|
|
26
|
-
a.set(3);
|
|
27
|
-
expect(bRunTimes).toBe(2);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
test('should not run untracked inner effect', () => {
|
|
31
|
-
/**
|
|
32
|
-
* a
|
|
33
|
-
* / \
|
|
34
|
-
* b e2(inner)
|
|
35
|
-
* \ /
|
|
36
|
-
* e1(outer)
|
|
37
|
-
*/
|
|
38
|
-
let countOfInnerEffect = 0;
|
|
39
|
-
const a = signal(3);
|
|
40
|
-
const b = computed(() => a.get() > 0);
|
|
41
|
-
effect(() => {
|
|
42
|
-
if (b.get() === true) {
|
|
43
|
-
countOfInnerEffect = countOfInnerEffect + 1;
|
|
44
|
-
effect(() => {
|
|
45
|
-
if (a.get() === 0) {
|
|
46
|
-
throw new Error("bad");
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
a.set(2);
|
|
53
|
-
a.set(1);
|
|
54
|
-
a.set(0);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test('should run outer effect first', () => {
|
|
58
|
-
/**
|
|
59
|
-
* a b
|
|
60
|
-
* | \ /
|
|
61
|
-
* | \ /
|
|
62
|
-
* | e2(inner)
|
|
63
|
-
* \ /
|
|
64
|
-
* e1(outer)
|
|
65
|
-
*/
|
|
66
|
-
const a = signal(1);
|
|
67
|
-
const b = signal(1);
|
|
68
|
-
effect(() => {
|
|
69
|
-
if (a.get() !== 0) {
|
|
70
|
-
effect(() => {
|
|
71
|
-
b.get();
|
|
72
|
-
if (a.get() === 0) {
|
|
73
|
-
throw new Error("bad");
|
|
74
|
-
}
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
startBatch();
|
|
80
|
-
b.set(0);
|
|
81
|
-
a.set(0);
|
|
82
|
-
endBatch();
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
test('should not trigger inner effect when resolve maybe dirty', () => {
|
|
86
|
-
/**
|
|
87
|
-
* a
|
|
88
|
-
* |
|
|
89
|
-
* b
|
|
90
|
-
* e2(inner)
|
|
91
|
-
* |
|
|
92
|
-
* e1(outer)
|
|
93
|
-
*/
|
|
94
|
-
const a = signal(0);
|
|
95
|
-
const b = computed(() => a.get() % 2);
|
|
96
|
-
let innerTriggerTimes = 0;
|
|
97
|
-
effect(() => {
|
|
98
|
-
effect(() => {
|
|
99
|
-
b.get();
|
|
100
|
-
innerTriggerTimes = innerTriggerTimes + 1;
|
|
101
|
-
if (innerTriggerTimes >= 2) {
|
|
102
|
-
throw new Error("bad");
|
|
103
|
-
}
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
a.set(2);
|
|
108
|
-
});
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|