@zeix/cause-effect 0.17.3 → 0.18.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/.ai-context.md +169 -227
- package/.cursorrules +41 -35
- package/.github/copilot-instructions.md +176 -116
- package/ARCHITECTURE.md +276 -0
- package/CHANGELOG.md +29 -0
- package/CLAUDE.md +201 -143
- package/GUIDE.md +298 -0
- package/README.md +246 -193
- package/REQUIREMENTS.md +100 -0
- package/bench/reactivity.bench.ts +577 -0
- package/context7.json +4 -0
- package/examples/events-sensor.ts +187 -0
- package/examples/selector-sensor.ts +173 -0
- package/index.dev.js +1390 -1008
- package/index.js +1 -1
- package/index.ts +60 -74
- package/package.json +5 -2
- package/skills/changelog-keeper/SKILL.md +59 -0
- package/skills/changelog-keeper/agents/openai.yaml +4 -0
- package/src/errors.ts +118 -74
- package/src/graph.ts +612 -0
- package/src/nodes/collection.ts +512 -0
- package/src/nodes/effect.ts +149 -0
- package/src/nodes/list.ts +589 -0
- package/src/nodes/memo.ts +148 -0
- package/src/nodes/sensor.ts +149 -0
- package/src/nodes/state.ts +135 -0
- package/src/nodes/store.ts +378 -0
- package/src/nodes/task.ts +174 -0
- package/src/signal.ts +112 -66
- package/src/util.ts +26 -57
- package/test/batch.test.ts +96 -62
- package/test/benchmark.test.ts +473 -487
- package/test/collection.test.ts +456 -707
- package/test/effect.test.ts +293 -696
- package/test/list.test.ts +335 -592
- package/test/memo.test.ts +574 -0
- package/test/regression.test.ts +156 -0
- package/test/scope.test.ts +191 -0
- package/test/sensor.test.ts +454 -0
- package/test/signal.test.ts +220 -213
- package/test/state.test.ts +217 -265
- package/test/store.test.ts +346 -446
- package/test/task.test.ts +529 -0
- package/test/untrack.test.ts +167 -0
- package/types/index.d.ts +13 -15
- package/types/src/errors.d.ts +73 -17
- package/types/src/graph.d.ts +218 -0
- package/types/src/nodes/collection.d.ts +69 -0
- package/types/src/nodes/effect.d.ts +48 -0
- package/types/src/nodes/list.d.ts +66 -0
- package/types/src/nodes/memo.d.ts +63 -0
- package/types/src/nodes/sensor.d.ts +81 -0
- package/types/src/nodes/state.d.ts +78 -0
- package/types/src/nodes/store.d.ts +51 -0
- package/types/src/nodes/task.d.ts +79 -0
- package/types/src/signal.d.ts +43 -29
- package/types/src/util.d.ts +9 -16
- package/archive/benchmark.ts +0 -683
- package/archive/collection.ts +0 -253
- package/archive/composite.ts +0 -85
- package/archive/computed.ts +0 -195
- package/archive/list.ts +0 -483
- package/archive/memo.ts +0 -139
- package/archive/state.ts +0 -90
- package/archive/store.ts +0 -298
- package/archive/task.ts +0 -189
- package/src/classes/collection.ts +0 -245
- package/src/classes/computed.ts +0 -349
- package/src/classes/list.ts +0 -343
- package/src/classes/ref.ts +0 -70
- package/src/classes/state.ts +0 -102
- package/src/classes/store.ts +0 -262
- package/src/diff.ts +0 -138
- package/src/effect.ts +0 -93
- package/src/match.ts +0 -45
- package/src/resolve.ts +0 -49
- package/src/system.ts +0 -257
- package/test/computed.test.ts +0 -1108
- package/test/diff.test.ts +0 -955
- package/test/match.test.ts +0 -388
- package/test/ref.test.ts +0 -353
- package/test/resolve.test.ts +0 -154
- package/types/src/classes/collection.d.ts +0 -45
- package/types/src/classes/computed.d.ts +0 -94
- package/types/src/classes/list.d.ts +0 -43
- package/types/src/classes/ref.d.ts +0 -35
- package/types/src/classes/state.d.ts +0 -49
- package/types/src/classes/store.d.ts +0 -52
- package/types/src/diff.d.ts +0 -28
- package/types/src/effect.d.ts +0 -15
- package/types/src/match.d.ts +0 -21
- package/types/src/resolve.d.ts +0 -29
- package/types/src/system.d.ts +0 -78
package/index.dev.js
CHANGED
|
@@ -1,143 +1,426 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
// src/util.ts
|
|
2
|
+
function isFunction(fn) {
|
|
3
|
+
return typeof fn === "function";
|
|
4
|
+
}
|
|
5
|
+
function isAsyncFunction(fn) {
|
|
6
|
+
return isFunction(fn) && fn.constructor.name === "AsyncFunction";
|
|
7
|
+
}
|
|
8
|
+
function isSyncFunction(fn) {
|
|
9
|
+
return isFunction(fn) && fn.constructor.name !== "AsyncFunction";
|
|
10
|
+
}
|
|
11
|
+
function isObjectOfType(value, type) {
|
|
12
|
+
return Object.prototype.toString.call(value) === `[object ${type}]`;
|
|
13
|
+
}
|
|
14
|
+
function isRecord(value) {
|
|
15
|
+
return isObjectOfType(value, "Object");
|
|
16
|
+
}
|
|
17
|
+
function isUniformArray(value, guard = (item) => item != null) {
|
|
18
|
+
return Array.isArray(value) && value.every(guard);
|
|
19
|
+
}
|
|
20
|
+
function valueString(value) {
|
|
21
|
+
return typeof value === "string" ? `"${value}"` : !!value && typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// src/errors.ts
|
|
25
|
+
class CircularDependencyError extends Error {
|
|
26
|
+
constructor(where) {
|
|
27
|
+
super(`[${where}] Circular dependency detected`);
|
|
28
|
+
this.name = "CircularDependencyError";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
class NullishSignalValueError extends TypeError {
|
|
33
|
+
constructor(where) {
|
|
34
|
+
super(`[${where}] Signal value cannot be null or undefined`);
|
|
35
|
+
this.name = "NullishSignalValueError";
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
class UnsetSignalValueError extends Error {
|
|
40
|
+
constructor(where) {
|
|
41
|
+
super(`[${where}] Signal value is unset`);
|
|
42
|
+
this.name = "UnsetSignalValueError";
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
class InvalidSignalValueError extends TypeError {
|
|
47
|
+
constructor(where, value) {
|
|
48
|
+
super(`[${where}] Signal value ${valueString(value)} is invalid`);
|
|
49
|
+
this.name = "InvalidSignalValueError";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
class InvalidCallbackError extends TypeError {
|
|
54
|
+
constructor(where, value) {
|
|
55
|
+
super(`[${where}] Callback ${valueString(value)} is invalid`);
|
|
56
|
+
this.name = "InvalidCallbackError";
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
class RequiredOwnerError extends Error {
|
|
61
|
+
constructor(where) {
|
|
62
|
+
super(`[${where}] Active owner is required`);
|
|
63
|
+
this.name = "RequiredOwnerError";
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
class DuplicateKeyError extends Error {
|
|
68
|
+
constructor(where, key, value) {
|
|
69
|
+
super(`[${where}] Could not add key "${key}"${value ? ` with value ${JSON.stringify(value)}` : ""} because it already exists`);
|
|
70
|
+
this.name = "DuplicateKeyError";
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function validateSignalValue(where, value, guard) {
|
|
74
|
+
if (value == null)
|
|
75
|
+
throw new NullishSignalValueError(where);
|
|
76
|
+
if (guard && !guard(value))
|
|
77
|
+
throw new InvalidSignalValueError(where, value);
|
|
78
|
+
}
|
|
79
|
+
function validateReadValue(where, value) {
|
|
80
|
+
if (value == null)
|
|
81
|
+
throw new UnsetSignalValueError(where);
|
|
82
|
+
}
|
|
83
|
+
function validateCallback(where, value, guard = isFunction) {
|
|
84
|
+
if (!guard(value))
|
|
85
|
+
throw new InvalidCallbackError(where, value);
|
|
86
|
+
}
|
|
87
|
+
// src/graph.ts
|
|
88
|
+
var TYPE_STATE = "State";
|
|
89
|
+
var TYPE_MEMO = "Memo";
|
|
90
|
+
var TYPE_TASK = "Task";
|
|
91
|
+
var TYPE_SENSOR = "Sensor";
|
|
92
|
+
var TYPE_LIST = "List";
|
|
93
|
+
var TYPE_COLLECTION = "Collection";
|
|
94
|
+
var TYPE_STORE = "Store";
|
|
95
|
+
var FLAG_CLEAN = 0;
|
|
96
|
+
var FLAG_CHECK = 1 << 0;
|
|
97
|
+
var FLAG_DIRTY = 1 << 1;
|
|
98
|
+
var FLAG_RUNNING = 1 << 2;
|
|
99
|
+
var activeSink = null;
|
|
100
|
+
var activeOwner = null;
|
|
101
|
+
var queuedEffects = [];
|
|
7
102
|
var batchDepth = 0;
|
|
8
|
-
var
|
|
9
|
-
var
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
103
|
+
var flushing = false;
|
|
104
|
+
var DEFAULT_EQUALITY = (a, b) => a === b;
|
|
105
|
+
var SKIP_EQUALITY = (_a, _b) => false;
|
|
106
|
+
function isValidEdge(checkEdge, node) {
|
|
107
|
+
const sourcesTail = node.sourcesTail;
|
|
108
|
+
if (sourcesTail) {
|
|
109
|
+
let edge = node.sources;
|
|
110
|
+
while (edge) {
|
|
111
|
+
if (edge === checkEdge)
|
|
112
|
+
return true;
|
|
113
|
+
if (edge === sourcesTail)
|
|
114
|
+
break;
|
|
115
|
+
edge = edge.nextSource;
|
|
19
116
|
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
117
|
+
}
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
function link(source, sink) {
|
|
121
|
+
const prevSource = sink.sourcesTail;
|
|
122
|
+
if (prevSource?.source === source)
|
|
123
|
+
return;
|
|
124
|
+
let nextSource = null;
|
|
125
|
+
const isRecomputing = sink.flags & FLAG_RUNNING;
|
|
126
|
+
if (isRecomputing) {
|
|
127
|
+
nextSource = prevSource ? prevSource.nextSource : sink.sources;
|
|
128
|
+
if (nextSource?.source === source) {
|
|
129
|
+
sink.sourcesTail = nextSource;
|
|
130
|
+
return;
|
|
30
131
|
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
|
|
132
|
+
}
|
|
133
|
+
const prevSink = source.sinksTail;
|
|
134
|
+
if (prevSink?.sink === sink && (!isRecomputing || isValidEdge(prevSink, sink)))
|
|
135
|
+
return;
|
|
136
|
+
const newEdge = { source, sink, nextSource, prevSink, nextSink: null };
|
|
137
|
+
sink.sourcesTail = source.sinksTail = newEdge;
|
|
138
|
+
if (prevSource)
|
|
139
|
+
prevSource.nextSource = newEdge;
|
|
140
|
+
else
|
|
141
|
+
sink.sources = newEdge;
|
|
142
|
+
if (prevSink)
|
|
143
|
+
prevSink.nextSink = newEdge;
|
|
144
|
+
else
|
|
145
|
+
source.sinks = newEdge;
|
|
146
|
+
}
|
|
147
|
+
function unlink(edge) {
|
|
148
|
+
const { source, nextSource, nextSink, prevSink } = edge;
|
|
149
|
+
if (nextSink)
|
|
150
|
+
nextSink.prevSink = prevSink;
|
|
151
|
+
else
|
|
152
|
+
source.sinksTail = prevSink;
|
|
153
|
+
if (prevSink)
|
|
154
|
+
prevSink.nextSink = nextSink;
|
|
155
|
+
else
|
|
156
|
+
source.sinks = nextSink;
|
|
157
|
+
if (!source.sinks && source.stop) {
|
|
158
|
+
source.stop();
|
|
159
|
+
source.stop = undefined;
|
|
160
|
+
}
|
|
161
|
+
return nextSource;
|
|
162
|
+
}
|
|
163
|
+
function trimSources(node) {
|
|
164
|
+
const tail = node.sourcesTail;
|
|
165
|
+
let source = tail ? tail.nextSource : node.sources;
|
|
166
|
+
while (source)
|
|
167
|
+
source = unlink(source);
|
|
168
|
+
if (tail)
|
|
169
|
+
tail.nextSource = null;
|
|
170
|
+
else
|
|
171
|
+
node.sources = null;
|
|
172
|
+
}
|
|
173
|
+
function propagate(node, newFlag = FLAG_DIRTY) {
|
|
174
|
+
const flags = node.flags;
|
|
175
|
+
if ("sinks" in node) {
|
|
176
|
+
if ((flags & (FLAG_DIRTY | FLAG_CHECK)) >= newFlag)
|
|
177
|
+
return;
|
|
178
|
+
node.flags = flags | newFlag;
|
|
179
|
+
if ("controller" in node && node.controller) {
|
|
180
|
+
node.controller.abort();
|
|
181
|
+
node.controller = undefined;
|
|
182
|
+
}
|
|
183
|
+
for (let e = node.sinks;e; e = e.nextSink)
|
|
184
|
+
propagate(e.sink, FLAG_CHECK);
|
|
185
|
+
} else {
|
|
186
|
+
if (flags & FLAG_DIRTY)
|
|
187
|
+
return;
|
|
188
|
+
node.flags = FLAG_DIRTY;
|
|
189
|
+
queuedEffects.push(node);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function setState(node, next) {
|
|
193
|
+
if (node.equals(node.value, next))
|
|
194
|
+
return;
|
|
195
|
+
node.value = next;
|
|
196
|
+
for (let e = node.sinks;e; e = e.nextSink)
|
|
197
|
+
propagate(e.sink);
|
|
198
|
+
if (batchDepth === 0)
|
|
199
|
+
flush();
|
|
200
|
+
}
|
|
201
|
+
function registerCleanup(owner, fn) {
|
|
202
|
+
if (!owner.cleanup)
|
|
203
|
+
owner.cleanup = fn;
|
|
204
|
+
else if (Array.isArray(owner.cleanup))
|
|
205
|
+
owner.cleanup.push(fn);
|
|
206
|
+
else
|
|
207
|
+
owner.cleanup = [owner.cleanup, fn];
|
|
208
|
+
}
|
|
209
|
+
function runCleanup(owner) {
|
|
210
|
+
if (!owner.cleanup)
|
|
211
|
+
return;
|
|
212
|
+
if (Array.isArray(owner.cleanup))
|
|
213
|
+
for (let i = 0;i < owner.cleanup.length; i++)
|
|
214
|
+
owner.cleanup[i]();
|
|
215
|
+
else
|
|
216
|
+
owner.cleanup();
|
|
217
|
+
owner.cleanup = null;
|
|
218
|
+
}
|
|
219
|
+
function recomputeMemo(node) {
|
|
220
|
+
const prevWatcher = activeSink;
|
|
221
|
+
activeSink = node;
|
|
222
|
+
node.sourcesTail = null;
|
|
223
|
+
node.flags = FLAG_RUNNING;
|
|
224
|
+
let changed = false;
|
|
37
225
|
try {
|
|
38
|
-
|
|
226
|
+
const next = node.fn(node.value);
|
|
227
|
+
if (node.error || !node.equals(next, node.value)) {
|
|
228
|
+
node.value = next;
|
|
229
|
+
node.error = undefined;
|
|
230
|
+
changed = true;
|
|
231
|
+
}
|
|
232
|
+
} catch (err) {
|
|
233
|
+
changed = true;
|
|
234
|
+
node.error = err instanceof Error ? err : new Error(String(err));
|
|
39
235
|
} finally {
|
|
40
|
-
|
|
236
|
+
activeSink = prevWatcher;
|
|
237
|
+
trimSources(node);
|
|
41
238
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
239
|
+
if (changed) {
|
|
240
|
+
for (let e = node.sinks;e; e = e.nextSink)
|
|
241
|
+
if (e.sink.flags & FLAG_CHECK)
|
|
242
|
+
e.sink.flags |= FLAG_DIRTY;
|
|
243
|
+
}
|
|
244
|
+
node.flags = FLAG_CLEAN;
|
|
245
|
+
}
|
|
246
|
+
function recomputeTask(node) {
|
|
247
|
+
node.controller?.abort();
|
|
248
|
+
const controller = new AbortController;
|
|
249
|
+
node.controller = controller;
|
|
250
|
+
node.error = undefined;
|
|
251
|
+
const prevWatcher = activeSink;
|
|
252
|
+
activeSink = node;
|
|
253
|
+
node.sourcesTail = null;
|
|
254
|
+
node.flags = FLAG_RUNNING;
|
|
255
|
+
let promise;
|
|
256
|
+
try {
|
|
257
|
+
promise = node.fn(node.value, controller.signal);
|
|
258
|
+
} catch (err) {
|
|
259
|
+
node.controller = undefined;
|
|
260
|
+
node.error = err instanceof Error ? err : new Error(String(err));
|
|
261
|
+
return;
|
|
262
|
+
} finally {
|
|
263
|
+
activeSink = prevWatcher;
|
|
264
|
+
trimSources(node);
|
|
60
265
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
266
|
+
promise.then((next) => {
|
|
267
|
+
if (controller.signal.aborted)
|
|
268
|
+
return;
|
|
269
|
+
node.controller = undefined;
|
|
270
|
+
if (node.error || !node.equals(next, node.value)) {
|
|
271
|
+
node.value = next;
|
|
272
|
+
node.error = undefined;
|
|
273
|
+
for (let e = node.sinks;e; e = e.nextSink)
|
|
274
|
+
propagate(e.sink);
|
|
275
|
+
if (batchDepth === 0)
|
|
276
|
+
flush();
|
|
277
|
+
}
|
|
278
|
+
}, (err) => {
|
|
279
|
+
if (controller.signal.aborted)
|
|
280
|
+
return;
|
|
281
|
+
node.controller = undefined;
|
|
282
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
283
|
+
if (!node.error || error.name !== node.error.name || error.message !== node.error.message) {
|
|
284
|
+
node.error = error;
|
|
285
|
+
for (let e = node.sinks;e; e = e.nextSink)
|
|
286
|
+
propagate(e.sink);
|
|
287
|
+
if (batchDepth === 0)
|
|
288
|
+
flush();
|
|
68
289
|
}
|
|
69
290
|
});
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
291
|
+
node.flags = FLAG_CLEAN;
|
|
292
|
+
}
|
|
293
|
+
function runEffect(node) {
|
|
294
|
+
runCleanup(node);
|
|
295
|
+
const prevContext = activeSink;
|
|
296
|
+
const prevOwner = activeOwner;
|
|
297
|
+
activeSink = activeOwner = node;
|
|
298
|
+
node.sourcesTail = null;
|
|
299
|
+
node.flags = FLAG_RUNNING;
|
|
300
|
+
try {
|
|
301
|
+
const out = node.fn();
|
|
302
|
+
if (typeof out === "function")
|
|
303
|
+
registerCleanup(node, out);
|
|
304
|
+
} finally {
|
|
305
|
+
activeSink = prevContext;
|
|
306
|
+
activeOwner = prevOwner;
|
|
307
|
+
trimSources(node);
|
|
308
|
+
}
|
|
309
|
+
node.flags = FLAG_CLEAN;
|
|
310
|
+
}
|
|
311
|
+
function refresh(node) {
|
|
312
|
+
if (node.flags & FLAG_CHECK) {
|
|
313
|
+
for (let e = node.sources;e; e = e.nextSource) {
|
|
314
|
+
if ("fn" in e.source)
|
|
315
|
+
refresh(e.source);
|
|
316
|
+
if (node.flags & FLAG_DIRTY)
|
|
317
|
+
break;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
if (node.flags & FLAG_RUNNING) {
|
|
321
|
+
throw new CircularDependencyError("controller" in node ? TYPE_TASK : ("value" in node) ? TYPE_MEMO : "Effect");
|
|
322
|
+
}
|
|
323
|
+
if (node.flags & FLAG_DIRTY) {
|
|
324
|
+
if ("controller" in node)
|
|
325
|
+
recomputeTask(node);
|
|
326
|
+
else if ("value" in node)
|
|
327
|
+
recomputeMemo(node);
|
|
87
328
|
else
|
|
88
|
-
|
|
329
|
+
runEffect(node);
|
|
330
|
+
} else {
|
|
331
|
+
node.flags = FLAG_CLEAN;
|
|
89
332
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
for (
|
|
97
|
-
|
|
333
|
+
}
|
|
334
|
+
function flush() {
|
|
335
|
+
if (flushing)
|
|
336
|
+
return;
|
|
337
|
+
flushing = true;
|
|
338
|
+
try {
|
|
339
|
+
for (let i = 0;i < queuedEffects.length; i++) {
|
|
340
|
+
const effect = queuedEffects[i];
|
|
341
|
+
if (effect.flags & FLAG_DIRTY)
|
|
342
|
+
refresh(effect);
|
|
343
|
+
}
|
|
344
|
+
queuedEffects.length = 0;
|
|
345
|
+
} finally {
|
|
346
|
+
flushing = false;
|
|
98
347
|
}
|
|
99
|
-
}
|
|
100
|
-
|
|
348
|
+
}
|
|
349
|
+
function batch(fn) {
|
|
101
350
|
batchDepth++;
|
|
102
351
|
try {
|
|
103
|
-
|
|
352
|
+
fn();
|
|
104
353
|
} finally {
|
|
105
|
-
flush();
|
|
106
354
|
batchDepth--;
|
|
355
|
+
if (batchDepth === 0)
|
|
356
|
+
flush();
|
|
107
357
|
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const prev =
|
|
111
|
-
|
|
358
|
+
}
|
|
359
|
+
function untrack(fn) {
|
|
360
|
+
const prev = activeSink;
|
|
361
|
+
activeSink = null;
|
|
112
362
|
try {
|
|
113
|
-
|
|
363
|
+
return fn();
|
|
114
364
|
} finally {
|
|
115
|
-
|
|
365
|
+
activeSink = prev;
|
|
116
366
|
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
367
|
+
}
|
|
368
|
+
function createScope(fn) {
|
|
369
|
+
const prevOwner = activeOwner;
|
|
370
|
+
const scope = { cleanup: null };
|
|
371
|
+
activeOwner = scope;
|
|
372
|
+
try {
|
|
373
|
+
const out = fn();
|
|
374
|
+
if (typeof out === "function")
|
|
375
|
+
registerCleanup(scope, out);
|
|
376
|
+
const dispose = () => runCleanup(scope);
|
|
377
|
+
if (prevOwner)
|
|
378
|
+
registerCleanup(prevOwner, dispose);
|
|
379
|
+
return dispose;
|
|
380
|
+
} finally {
|
|
381
|
+
activeOwner = prevOwner;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
// src/nodes/state.ts
|
|
385
|
+
function createState(value, options) {
|
|
386
|
+
validateSignalValue(TYPE_STATE, value, options?.guard);
|
|
387
|
+
const node = {
|
|
388
|
+
value,
|
|
389
|
+
sinks: null,
|
|
390
|
+
sinksTail: null,
|
|
391
|
+
equals: options?.equals ?? DEFAULT_EQUALITY,
|
|
392
|
+
guard: options?.guard
|
|
393
|
+
};
|
|
394
|
+
return {
|
|
395
|
+
[Symbol.toStringTag]: TYPE_STATE,
|
|
396
|
+
get() {
|
|
397
|
+
if (activeSink)
|
|
398
|
+
link(node, activeSink);
|
|
399
|
+
return node.value;
|
|
400
|
+
},
|
|
401
|
+
set(next) {
|
|
402
|
+
validateSignalValue(TYPE_STATE, next, node.guard);
|
|
403
|
+
setState(node, next);
|
|
404
|
+
},
|
|
405
|
+
update(fn) {
|
|
406
|
+
validateCallback(TYPE_STATE, fn);
|
|
407
|
+
const next = fn(node.value);
|
|
408
|
+
validateSignalValue(TYPE_STATE, next, node.guard);
|
|
409
|
+
setState(node, next);
|
|
410
|
+
}
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
function isState(value) {
|
|
414
|
+
return isObjectOfType(value, TYPE_STATE);
|
|
415
|
+
}
|
|
133
416
|
|
|
134
|
-
// src/
|
|
135
|
-
|
|
417
|
+
// src/nodes/list.ts
|
|
418
|
+
function isEqual(a, b, visited) {
|
|
136
419
|
if (Object.is(a, b))
|
|
137
420
|
return true;
|
|
138
421
|
if (typeof a !== typeof b)
|
|
139
422
|
return false;
|
|
140
|
-
if (
|
|
423
|
+
if (a == null || typeof a !== "object" || b == null || typeof b !== "object")
|
|
141
424
|
return false;
|
|
142
425
|
if (!visited)
|
|
143
426
|
visited = new WeakSet;
|
|
@@ -146,17 +429,19 @@ var isEqual = (a, b, visited) => {
|
|
|
146
429
|
visited.add(a);
|
|
147
430
|
visited.add(b);
|
|
148
431
|
try {
|
|
149
|
-
|
|
150
|
-
|
|
432
|
+
const aIsArray = Array.isArray(a);
|
|
433
|
+
if (aIsArray !== Array.isArray(b))
|
|
434
|
+
return false;
|
|
435
|
+
if (aIsArray) {
|
|
436
|
+
const aa = a;
|
|
437
|
+
const ba = b;
|
|
438
|
+
if (aa.length !== ba.length)
|
|
151
439
|
return false;
|
|
152
|
-
for (let i = 0;i <
|
|
153
|
-
if (!isEqual(
|
|
440
|
+
for (let i = 0;i < aa.length; i++)
|
|
441
|
+
if (!isEqual(aa[i], ba[i], visited))
|
|
154
442
|
return false;
|
|
155
|
-
}
|
|
156
443
|
return true;
|
|
157
444
|
}
|
|
158
|
-
if (Array.isArray(a) !== Array.isArray(b))
|
|
159
|
-
return false;
|
|
160
445
|
if (isRecord(a) && isRecord(b)) {
|
|
161
446
|
const aKeys = Object.keys(a);
|
|
162
447
|
const bKeys = Object.keys(b);
|
|
@@ -175,573 +460,976 @@ var isEqual = (a, b, visited) => {
|
|
|
175
460
|
visited.delete(a);
|
|
176
461
|
visited.delete(b);
|
|
177
462
|
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
463
|
+
}
|
|
464
|
+
function keysEqual(a, b) {
|
|
465
|
+
if (a.length !== b.length)
|
|
466
|
+
return false;
|
|
467
|
+
for (let i = 0;i < a.length; i++)
|
|
468
|
+
if (a[i] !== b[i])
|
|
469
|
+
return false;
|
|
470
|
+
return true;
|
|
471
|
+
}
|
|
472
|
+
function getKeyGenerator(keyConfig) {
|
|
473
|
+
let keyCounter = 0;
|
|
474
|
+
const contentBased = typeof keyConfig === "function";
|
|
475
|
+
return [
|
|
476
|
+
typeof keyConfig === "string" ? () => `${keyConfig}${keyCounter++}` : contentBased ? (item) => keyConfig(item) || String(keyCounter++) : () => String(keyCounter++),
|
|
477
|
+
contentBased
|
|
478
|
+
];
|
|
479
|
+
}
|
|
480
|
+
function diffArrays(prev, next, prevKeys, generateKey, contentBased) {
|
|
191
481
|
const visited = new WeakSet;
|
|
192
482
|
const add = {};
|
|
193
483
|
const change = {};
|
|
194
484
|
const remove = {};
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
const
|
|
198
|
-
for (
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
485
|
+
const nextKeys = [];
|
|
486
|
+
let changed = false;
|
|
487
|
+
const prevByKey = new Map;
|
|
488
|
+
for (let i = 0;i < prev.length; i++) {
|
|
489
|
+
const key = prevKeys[i];
|
|
490
|
+
if (key && prev[i])
|
|
491
|
+
prevByKey.set(key, prev[i]);
|
|
492
|
+
}
|
|
493
|
+
const seenKeys = new Set;
|
|
494
|
+
for (let i = 0;i < next.length; i++) {
|
|
495
|
+
const val = next[i];
|
|
496
|
+
if (val === undefined)
|
|
206
497
|
continue;
|
|
498
|
+
const key = contentBased ? generateKey(val) : prevKeys[i] ?? generateKey(val);
|
|
499
|
+
if (seenKeys.has(key))
|
|
500
|
+
throw new DuplicateKeyError(TYPE_LIST, key, val);
|
|
501
|
+
nextKeys.push(key);
|
|
502
|
+
seenKeys.add(key);
|
|
503
|
+
if (!prevByKey.has(key)) {
|
|
504
|
+
add[key] = val;
|
|
505
|
+
changed = true;
|
|
506
|
+
} else if (!isEqual(prevByKey.get(key), val, visited)) {
|
|
507
|
+
change[key] = val;
|
|
508
|
+
changed = true;
|
|
207
509
|
}
|
|
208
|
-
const oldValue = oldObj[key];
|
|
209
|
-
const newValue = newObj[key];
|
|
210
|
-
if (!isEqual(oldValue, newValue, visited))
|
|
211
|
-
change[key] = newValue;
|
|
212
|
-
}
|
|
213
|
-
return {
|
|
214
|
-
add,
|
|
215
|
-
change,
|
|
216
|
-
remove,
|
|
217
|
-
changed: !!(Object.keys(add).length || Object.keys(change).length || Object.keys(remove).length)
|
|
218
|
-
};
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
// src/classes/computed.ts
|
|
222
|
-
var TYPE_COMPUTED = "Computed";
|
|
223
|
-
|
|
224
|
-
class Memo {
|
|
225
|
-
#callback;
|
|
226
|
-
#value;
|
|
227
|
-
#error;
|
|
228
|
-
#dirty = true;
|
|
229
|
-
#computing = false;
|
|
230
|
-
#watcher;
|
|
231
|
-
constructor(callback, options) {
|
|
232
|
-
validateCallback(this.constructor.name, callback, isMemoCallback);
|
|
233
|
-
const initialValue = options?.initialValue ?? UNSET;
|
|
234
|
-
validateSignalValue(this.constructor.name, initialValue, options?.guard);
|
|
235
|
-
this.#callback = callback;
|
|
236
|
-
this.#value = initialValue;
|
|
237
|
-
if (options?.watched)
|
|
238
|
-
registerWatchCallbacks(this, options.watched, options.unwatched);
|
|
239
|
-
}
|
|
240
|
-
#getWatcher() {
|
|
241
|
-
this.#watcher ||= createWatcher(() => {
|
|
242
|
-
this.#dirty = true;
|
|
243
|
-
if (!notifyOf(this))
|
|
244
|
-
this.#watcher?.stop();
|
|
245
|
-
}, () => {
|
|
246
|
-
if (this.#computing)
|
|
247
|
-
throw new CircularDependencyError("memo");
|
|
248
|
-
let result;
|
|
249
|
-
this.#computing = true;
|
|
250
|
-
try {
|
|
251
|
-
result = this.#callback(this.#value);
|
|
252
|
-
} catch (e) {
|
|
253
|
-
this.#value = UNSET;
|
|
254
|
-
this.#error = createError(e);
|
|
255
|
-
this.#computing = false;
|
|
256
|
-
return;
|
|
257
|
-
}
|
|
258
|
-
if (result == null || UNSET === result) {
|
|
259
|
-
this.#value = UNSET;
|
|
260
|
-
this.#error = undefined;
|
|
261
|
-
} else {
|
|
262
|
-
this.#value = result;
|
|
263
|
-
this.#error = undefined;
|
|
264
|
-
this.#dirty = false;
|
|
265
|
-
}
|
|
266
|
-
this.#computing = false;
|
|
267
|
-
});
|
|
268
|
-
this.#watcher.onCleanup(() => {
|
|
269
|
-
this.#watcher = undefined;
|
|
270
|
-
});
|
|
271
|
-
return this.#watcher;
|
|
272
|
-
}
|
|
273
|
-
get [Symbol.toStringTag]() {
|
|
274
|
-
return TYPE_COMPUTED;
|
|
275
|
-
}
|
|
276
|
-
get() {
|
|
277
|
-
subscribeTo(this);
|
|
278
|
-
flush();
|
|
279
|
-
if (this.#dirty)
|
|
280
|
-
this.#getWatcher().run();
|
|
281
|
-
if (this.#error)
|
|
282
|
-
throw this.#error;
|
|
283
|
-
return this.#value;
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
class Task {
|
|
288
|
-
#callback;
|
|
289
|
-
#value;
|
|
290
|
-
#error;
|
|
291
|
-
#dirty = true;
|
|
292
|
-
#computing = false;
|
|
293
|
-
#changed = false;
|
|
294
|
-
#watcher;
|
|
295
|
-
#controller;
|
|
296
|
-
constructor(callback, options) {
|
|
297
|
-
validateCallback(this.constructor.name, callback, isTaskCallback);
|
|
298
|
-
const initialValue = options?.initialValue ?? UNSET;
|
|
299
|
-
validateSignalValue(this.constructor.name, initialValue, options?.guard);
|
|
300
|
-
this.#callback = callback;
|
|
301
|
-
this.#value = initialValue;
|
|
302
|
-
if (options?.watched)
|
|
303
|
-
registerWatchCallbacks(this, options.watched, options.unwatched);
|
|
304
510
|
}
|
|
305
|
-
|
|
306
|
-
if (!
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
this.#value = v;
|
|
310
|
-
this.#changed = true;
|
|
311
|
-
}
|
|
312
|
-
this.#error = undefined;
|
|
313
|
-
this.#dirty = false;
|
|
314
|
-
};
|
|
315
|
-
const nil = () => {
|
|
316
|
-
this.#changed = UNSET !== this.#value;
|
|
317
|
-
this.#value = UNSET;
|
|
318
|
-
this.#error = undefined;
|
|
319
|
-
};
|
|
320
|
-
const err = (e) => {
|
|
321
|
-
const newError = createError(e);
|
|
322
|
-
this.#changed = !this.#error || newError.name !== this.#error.name || newError.message !== this.#error.message;
|
|
323
|
-
this.#value = UNSET;
|
|
324
|
-
this.#error = newError;
|
|
325
|
-
};
|
|
326
|
-
const settle = (fn) => (arg) => {
|
|
327
|
-
this.#computing = false;
|
|
328
|
-
this.#controller = undefined;
|
|
329
|
-
fn(arg);
|
|
330
|
-
if (this.#changed && !notifyOf(this))
|
|
331
|
-
this.#watcher?.stop();
|
|
332
|
-
};
|
|
333
|
-
this.#watcher = createWatcher(() => {
|
|
334
|
-
this.#dirty = true;
|
|
335
|
-
this.#controller?.abort();
|
|
336
|
-
if (!notifyOf(this))
|
|
337
|
-
this.#watcher?.stop();
|
|
338
|
-
}, () => {
|
|
339
|
-
if (this.#computing)
|
|
340
|
-
throw new CircularDependencyError("task");
|
|
341
|
-
this.#changed = false;
|
|
342
|
-
if (this.#controller)
|
|
343
|
-
return this.#value;
|
|
344
|
-
this.#controller = new AbortController;
|
|
345
|
-
this.#controller.signal.addEventListener("abort", () => {
|
|
346
|
-
this.#computing = false;
|
|
347
|
-
this.#controller = undefined;
|
|
348
|
-
this.#getWatcher().run();
|
|
349
|
-
}, {
|
|
350
|
-
once: true
|
|
351
|
-
});
|
|
352
|
-
let result;
|
|
353
|
-
this.#computing = true;
|
|
354
|
-
try {
|
|
355
|
-
result = this.#callback(this.#value, this.#controller.signal);
|
|
356
|
-
} catch (e) {
|
|
357
|
-
if (isAbortError(e))
|
|
358
|
-
nil();
|
|
359
|
-
else
|
|
360
|
-
err(e);
|
|
361
|
-
this.#computing = false;
|
|
362
|
-
return;
|
|
363
|
-
}
|
|
364
|
-
if (result instanceof Promise)
|
|
365
|
-
result.then(settle(ok), settle(err));
|
|
366
|
-
else if (result == null || UNSET === result)
|
|
367
|
-
nil();
|
|
368
|
-
else
|
|
369
|
-
ok(result);
|
|
370
|
-
this.#computing = false;
|
|
371
|
-
});
|
|
372
|
-
this.#watcher.onCleanup(() => {
|
|
373
|
-
this.#controller?.abort();
|
|
374
|
-
this.#controller = undefined;
|
|
375
|
-
this.#watcher = undefined;
|
|
376
|
-
});
|
|
511
|
+
for (const [key] of prevByKey) {
|
|
512
|
+
if (!seenKeys.has(key)) {
|
|
513
|
+
remove[key] = null;
|
|
514
|
+
changed = true;
|
|
377
515
|
}
|
|
378
|
-
return this.#watcher;
|
|
379
|
-
}
|
|
380
|
-
get [Symbol.toStringTag]() {
|
|
381
|
-
return TYPE_COMPUTED;
|
|
382
|
-
}
|
|
383
|
-
get() {
|
|
384
|
-
subscribeTo(this);
|
|
385
|
-
flush();
|
|
386
|
-
if (this.#dirty)
|
|
387
|
-
this.#getWatcher().run();
|
|
388
|
-
if (this.#error)
|
|
389
|
-
throw this.#error;
|
|
390
|
-
return this.#value;
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
var createComputed = (callback, options) => isAsyncFunction(callback) ? new Task(callback, options) : new Memo(callback, options);
|
|
394
|
-
var isComputed = (value) => isObjectOfType(value, TYPE_COMPUTED);
|
|
395
|
-
var isMemoCallback = (value) => isSyncFunction(value) && value.length < 2;
|
|
396
|
-
var isTaskCallback = (value) => isAsyncFunction(value) && value.length < 3;
|
|
397
|
-
|
|
398
|
-
// src/classes/state.ts
|
|
399
|
-
var TYPE_STATE = "State";
|
|
400
|
-
|
|
401
|
-
class State {
|
|
402
|
-
#value;
|
|
403
|
-
constructor(initialValue, options) {
|
|
404
|
-
validateSignalValue(TYPE_STATE, initialValue, options?.guard);
|
|
405
|
-
this.#value = initialValue;
|
|
406
|
-
if (options?.watched)
|
|
407
|
-
registerWatchCallbacks(this, options.watched, options.unwatched);
|
|
408
|
-
}
|
|
409
|
-
get [Symbol.toStringTag]() {
|
|
410
|
-
return TYPE_STATE;
|
|
411
|
-
}
|
|
412
|
-
get() {
|
|
413
|
-
subscribeTo(this);
|
|
414
|
-
return this.#value;
|
|
415
|
-
}
|
|
416
|
-
set(newValue) {
|
|
417
|
-
validateSignalValue(TYPE_STATE, newValue);
|
|
418
|
-
if (isEqual(this.#value, newValue))
|
|
419
|
-
return;
|
|
420
|
-
this.#value = newValue;
|
|
421
|
-
notifyOf(this);
|
|
422
|
-
if (UNSET === this.#value)
|
|
423
|
-
unsubscribeAllFrom(this);
|
|
424
|
-
}
|
|
425
|
-
update(updater) {
|
|
426
|
-
validateCallback(`${TYPE_STATE} update`, updater);
|
|
427
|
-
this.set(updater(this.#value));
|
|
428
516
|
}
|
|
517
|
+
if (!changed && !keysEqual(prevKeys, nextKeys))
|
|
518
|
+
changed = true;
|
|
519
|
+
return { add, change, remove, newKeys: nextKeys, changed };
|
|
429
520
|
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
};
|
|
449
|
-
this.#change({
|
|
450
|
-
add: this.#toRecord(initialValue),
|
|
451
|
-
change: {},
|
|
452
|
-
remove: {},
|
|
453
|
-
changed: true
|
|
454
|
-
});
|
|
455
|
-
if (options?.watched)
|
|
456
|
-
registerWatchCallbacks(this, options.watched, options.unwatched);
|
|
457
|
-
}
|
|
458
|
-
#toRecord(array) {
|
|
521
|
+
function createList(value, options) {
|
|
522
|
+
validateSignalValue(TYPE_LIST, value, Array.isArray);
|
|
523
|
+
const signals = new Map;
|
|
524
|
+
let keys = [];
|
|
525
|
+
const [generateKey, contentBased] = getKeyGenerator(options?.keyConfig);
|
|
526
|
+
const buildValue = () => keys.map((key) => signals.get(key)?.get()).filter((v) => v !== undefined);
|
|
527
|
+
const node = {
|
|
528
|
+
fn: buildValue,
|
|
529
|
+
value,
|
|
530
|
+
flags: FLAG_DIRTY,
|
|
531
|
+
sources: null,
|
|
532
|
+
sourcesTail: null,
|
|
533
|
+
sinks: null,
|
|
534
|
+
sinksTail: null,
|
|
535
|
+
equals: isEqual,
|
|
536
|
+
error: undefined
|
|
537
|
+
};
|
|
538
|
+
const toRecord = (array) => {
|
|
459
539
|
const record = {};
|
|
460
540
|
for (let i = 0;i < array.length; i++) {
|
|
461
|
-
const
|
|
462
|
-
if (
|
|
541
|
+
const val = array[i];
|
|
542
|
+
if (val === undefined)
|
|
463
543
|
continue;
|
|
464
|
-
let key =
|
|
544
|
+
let key = keys[i];
|
|
465
545
|
if (!key) {
|
|
466
|
-
key =
|
|
467
|
-
|
|
546
|
+
key = generateKey(val);
|
|
547
|
+
keys[i] = key;
|
|
468
548
|
}
|
|
469
|
-
record[key] =
|
|
549
|
+
record[key] = val;
|
|
470
550
|
}
|
|
471
551
|
return record;
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
if (Object.keys(changes.add).length) {
|
|
481
|
-
for (const key in changes.add)
|
|
482
|
-
this.#add(key, changes.add[key]);
|
|
552
|
+
};
|
|
553
|
+
const applyChanges = (changes) => {
|
|
554
|
+
let structural = false;
|
|
555
|
+
for (const key in changes.add) {
|
|
556
|
+
const val = changes.add[key];
|
|
557
|
+
validateSignalValue(`${TYPE_LIST} item for key "${key}"`, val);
|
|
558
|
+
signals.set(key, createState(val));
|
|
559
|
+
structural = true;
|
|
483
560
|
}
|
|
484
561
|
if (Object.keys(changes.change).length) {
|
|
485
562
|
batch(() => {
|
|
486
563
|
for (const key in changes.change) {
|
|
487
|
-
const
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
signal.set(value);
|
|
564
|
+
const val = changes.change[key];
|
|
565
|
+
validateSignalValue(`${TYPE_LIST} item for key "${key}"`, val);
|
|
566
|
+
const signal = signals.get(key);
|
|
567
|
+
if (signal)
|
|
568
|
+
signal.set(val);
|
|
493
569
|
}
|
|
494
570
|
});
|
|
495
571
|
}
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
}
|
|
503
|
-
this.#keys = this.#keys.filter(() => true);
|
|
504
|
-
}
|
|
505
|
-
return changes.changed;
|
|
506
|
-
}
|
|
507
|
-
get #value() {
|
|
508
|
-
return this.#keys.map((key) => this.#signals.get(key)?.get()).filter((v) => v !== undefined);
|
|
509
|
-
}
|
|
510
|
-
get [Symbol.toStringTag]() {
|
|
511
|
-
return TYPE_LIST;
|
|
512
|
-
}
|
|
513
|
-
get [Symbol.isConcatSpreadable]() {
|
|
514
|
-
return true;
|
|
515
|
-
}
|
|
516
|
-
*[Symbol.iterator]() {
|
|
517
|
-
for (const key of this.#keys) {
|
|
518
|
-
const signal = this.#signals.get(key);
|
|
519
|
-
if (signal)
|
|
520
|
-
yield signal;
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
get length() {
|
|
524
|
-
subscribeTo(this);
|
|
525
|
-
return this.#keys.length;
|
|
526
|
-
}
|
|
527
|
-
get() {
|
|
528
|
-
subscribeTo(this);
|
|
529
|
-
return this.#value;
|
|
530
|
-
}
|
|
531
|
-
set(newValue) {
|
|
532
|
-
if (UNSET === newValue) {
|
|
533
|
-
this.#signals.clear();
|
|
534
|
-
notifyOf(this);
|
|
535
|
-
unsubscribeAllFrom(this);
|
|
536
|
-
return;
|
|
572
|
+
for (const key in changes.remove) {
|
|
573
|
+
signals.delete(key);
|
|
574
|
+
const index = keys.indexOf(key);
|
|
575
|
+
if (index !== -1)
|
|
576
|
+
keys.splice(index, 1);
|
|
577
|
+
structural = true;
|
|
537
578
|
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
}
|
|
542
|
-
update(fn) {
|
|
543
|
-
this.set(fn(this.get()));
|
|
544
|
-
}
|
|
545
|
-
at(index) {
|
|
546
|
-
return this.#signals.get(this.#keys[index]);
|
|
547
|
-
}
|
|
548
|
-
keys() {
|
|
549
|
-
subscribeTo(this);
|
|
550
|
-
return this.#keys.values();
|
|
551
|
-
}
|
|
552
|
-
byKey(key) {
|
|
553
|
-
return this.#signals.get(key);
|
|
554
|
-
}
|
|
555
|
-
keyAt(index) {
|
|
556
|
-
return this.#keys[index];
|
|
557
|
-
}
|
|
558
|
-
indexOfKey(key) {
|
|
559
|
-
return this.#keys.indexOf(key);
|
|
560
|
-
}
|
|
561
|
-
add(value) {
|
|
562
|
-
const key = this.#generateKey(value);
|
|
563
|
-
if (this.#signals.has(key))
|
|
564
|
-
throw new DuplicateKeyError("store", key, value);
|
|
565
|
-
if (!this.#keys.includes(key))
|
|
566
|
-
this.#keys.push(key);
|
|
567
|
-
const ok = this.#add(key, value);
|
|
568
|
-
if (ok)
|
|
569
|
-
notifyOf(this);
|
|
570
|
-
return key;
|
|
571
|
-
}
|
|
572
|
-
remove(keyOrIndex) {
|
|
573
|
-
const key = isNumber(keyOrIndex) ? this.#keys[keyOrIndex] : keyOrIndex;
|
|
574
|
-
const ok = this.#signals.delete(key);
|
|
575
|
-
if (ok) {
|
|
576
|
-
const index = isNumber(keyOrIndex) ? keyOrIndex : this.#keys.indexOf(key);
|
|
577
|
-
if (index >= 0)
|
|
578
|
-
this.#keys.splice(index, 1);
|
|
579
|
-
this.#keys = this.#keys.filter(() => true);
|
|
580
|
-
notifyOf(this);
|
|
579
|
+
if (structural) {
|
|
580
|
+
node.sources = null;
|
|
581
|
+
node.sourcesTail = null;
|
|
581
582
|
}
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
if (
|
|
587
|
-
|
|
588
|
-
|
|
583
|
+
return changes.changed;
|
|
584
|
+
};
|
|
585
|
+
const watched = options?.watched;
|
|
586
|
+
const subscribe = watched ? () => {
|
|
587
|
+
if (activeSink) {
|
|
588
|
+
if (!node.sinks)
|
|
589
|
+
node.stop = watched();
|
|
590
|
+
link(node, activeSink);
|
|
589
591
|
}
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
const
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
592
|
+
} : () => {
|
|
593
|
+
if (activeSink)
|
|
594
|
+
link(node, activeSink);
|
|
595
|
+
};
|
|
596
|
+
const initRecord = toRecord(value);
|
|
597
|
+
for (const key in initRecord) {
|
|
598
|
+
const val = initRecord[key];
|
|
599
|
+
validateSignalValue(`${TYPE_LIST} item for key "${key}"`, val);
|
|
600
|
+
signals.set(key, createState(val));
|
|
601
|
+
}
|
|
602
|
+
node.value = value;
|
|
603
|
+
node.flags = 0;
|
|
604
|
+
const list = {
|
|
605
|
+
[Symbol.toStringTag]: TYPE_LIST,
|
|
606
|
+
[Symbol.isConcatSpreadable]: true,
|
|
607
|
+
*[Symbol.iterator]() {
|
|
608
|
+
for (const key of keys) {
|
|
609
|
+
const signal = signals.get(key);
|
|
602
610
|
if (signal)
|
|
603
|
-
|
|
611
|
+
yield signal;
|
|
604
612
|
}
|
|
613
|
+
},
|
|
614
|
+
get length() {
|
|
615
|
+
subscribe();
|
|
616
|
+
return keys.length;
|
|
617
|
+
},
|
|
618
|
+
get() {
|
|
619
|
+
subscribe();
|
|
620
|
+
if (node.sources) {
|
|
621
|
+
if (node.flags) {
|
|
622
|
+
node.value = untrack(buildValue);
|
|
623
|
+
node.flags = FLAG_CLEAN;
|
|
624
|
+
}
|
|
625
|
+
} else {
|
|
626
|
+
refresh(node);
|
|
627
|
+
if (node.error)
|
|
628
|
+
throw node.error;
|
|
629
|
+
}
|
|
630
|
+
return node.value;
|
|
631
|
+
},
|
|
632
|
+
set(next) {
|
|
633
|
+
const prev = node.flags & FLAG_DIRTY ? buildValue() : node.value;
|
|
634
|
+
const changes = diffArrays(prev, next, keys, generateKey, contentBased);
|
|
635
|
+
if (changes.changed) {
|
|
636
|
+
keys = changes.newKeys;
|
|
637
|
+
applyChanges(changes);
|
|
638
|
+
node.flags |= FLAG_DIRTY;
|
|
639
|
+
for (let e = node.sinks;e; e = e.nextSink)
|
|
640
|
+
propagate(e.sink);
|
|
641
|
+
if (batchDepth === 0)
|
|
642
|
+
flush();
|
|
643
|
+
}
|
|
644
|
+
},
|
|
645
|
+
update(fn) {
|
|
646
|
+
list.set(fn(list.get()));
|
|
647
|
+
},
|
|
648
|
+
at(index) {
|
|
649
|
+
return signals.get(keys[index]);
|
|
650
|
+
},
|
|
651
|
+
keys() {
|
|
652
|
+
subscribe();
|
|
653
|
+
return keys.values();
|
|
654
|
+
},
|
|
655
|
+
byKey(key) {
|
|
656
|
+
return signals.get(key);
|
|
657
|
+
},
|
|
658
|
+
keyAt(index) {
|
|
659
|
+
return keys[index];
|
|
660
|
+
},
|
|
661
|
+
indexOfKey(key) {
|
|
662
|
+
return keys.indexOf(key);
|
|
663
|
+
},
|
|
664
|
+
add(value2) {
|
|
665
|
+
const key = generateKey(value2);
|
|
666
|
+
if (signals.has(key))
|
|
667
|
+
throw new DuplicateKeyError(TYPE_LIST, key, value2);
|
|
668
|
+
if (!keys.includes(key))
|
|
669
|
+
keys.push(key);
|
|
670
|
+
validateSignalValue(`${TYPE_LIST} item for key "${key}"`, value2);
|
|
671
|
+
signals.set(key, createState(value2));
|
|
672
|
+
node.sources = null;
|
|
673
|
+
node.sourcesTail = null;
|
|
674
|
+
node.flags |= FLAG_DIRTY;
|
|
675
|
+
for (let e = node.sinks;e; e = e.nextSink)
|
|
676
|
+
propagate(e.sink);
|
|
677
|
+
if (batchDepth === 0)
|
|
678
|
+
flush();
|
|
679
|
+
return key;
|
|
680
|
+
},
|
|
681
|
+
remove(keyOrIndex) {
|
|
682
|
+
const key = typeof keyOrIndex === "number" ? keys[keyOrIndex] : keyOrIndex;
|
|
683
|
+
const ok = signals.delete(key);
|
|
684
|
+
if (ok) {
|
|
685
|
+
const index = typeof keyOrIndex === "number" ? keyOrIndex : keys.indexOf(key);
|
|
686
|
+
if (index >= 0)
|
|
687
|
+
keys.splice(index, 1);
|
|
688
|
+
node.sources = null;
|
|
689
|
+
node.sourcesTail = null;
|
|
690
|
+
node.flags |= FLAG_DIRTY;
|
|
691
|
+
for (let e = node.sinks;e; e = e.nextSink)
|
|
692
|
+
propagate(e.sink);
|
|
693
|
+
if (batchDepth === 0)
|
|
694
|
+
flush();
|
|
695
|
+
}
|
|
696
|
+
},
|
|
697
|
+
sort(compareFn) {
|
|
698
|
+
const entries = keys.map((key) => [key, signals.get(key)?.get()]).sort(isFunction(compareFn) ? (a, b) => compareFn(a[1], b[1]) : (a, b) => String(a[1]).localeCompare(String(b[1])));
|
|
699
|
+
const newOrder = entries.map(([key]) => key);
|
|
700
|
+
if (!keysEqual(keys, newOrder)) {
|
|
701
|
+
keys = newOrder;
|
|
702
|
+
node.flags |= FLAG_DIRTY;
|
|
703
|
+
for (let e = node.sinks;e; e = e.nextSink)
|
|
704
|
+
propagate(e.sink);
|
|
705
|
+
if (batchDepth === 0)
|
|
706
|
+
flush();
|
|
707
|
+
}
|
|
708
|
+
},
|
|
709
|
+
splice(start, deleteCount, ...items) {
|
|
710
|
+
const length = keys.length;
|
|
711
|
+
const actualStart = start < 0 ? Math.max(0, length + start) : Math.min(start, length);
|
|
712
|
+
const actualDeleteCount = Math.max(0, Math.min(deleteCount ?? Math.max(0, length - Math.max(0, actualStart)), length - actualStart));
|
|
713
|
+
const add = {};
|
|
714
|
+
const remove = {};
|
|
715
|
+
for (let i = 0;i < actualDeleteCount; i++) {
|
|
716
|
+
const index = actualStart + i;
|
|
717
|
+
const key = keys[index];
|
|
718
|
+
if (key) {
|
|
719
|
+
const signal = signals.get(key);
|
|
720
|
+
if (signal)
|
|
721
|
+
remove[key] = signal.get();
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
const newOrder = keys.slice(0, actualStart);
|
|
725
|
+
for (const item of items) {
|
|
726
|
+
const key = generateKey(item);
|
|
727
|
+
if (signals.has(key) && !(key in remove))
|
|
728
|
+
throw new DuplicateKeyError(TYPE_LIST, key, item);
|
|
729
|
+
newOrder.push(key);
|
|
730
|
+
add[key] = item;
|
|
731
|
+
}
|
|
732
|
+
newOrder.push(...keys.slice(actualStart + actualDeleteCount));
|
|
733
|
+
const changed = !!(Object.keys(add).length || Object.keys(remove).length);
|
|
734
|
+
if (changed) {
|
|
735
|
+
applyChanges({
|
|
736
|
+
add,
|
|
737
|
+
change: {},
|
|
738
|
+
remove,
|
|
739
|
+
changed
|
|
740
|
+
});
|
|
741
|
+
keys = newOrder;
|
|
742
|
+
node.flags |= FLAG_DIRTY;
|
|
743
|
+
for (let e = node.sinks;e; e = e.nextSink)
|
|
744
|
+
propagate(e.sink);
|
|
745
|
+
if (batchDepth === 0)
|
|
746
|
+
flush();
|
|
747
|
+
}
|
|
748
|
+
return Object.values(remove);
|
|
749
|
+
},
|
|
750
|
+
deriveCollection(cb) {
|
|
751
|
+
return deriveCollection(list, cb);
|
|
605
752
|
}
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
753
|
+
};
|
|
754
|
+
return list;
|
|
755
|
+
}
|
|
756
|
+
function isList(value) {
|
|
757
|
+
return isObjectOfType(value, TYPE_LIST);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// src/nodes/memo.ts
|
|
761
|
+
function createMemo(fn, options) {
|
|
762
|
+
validateCallback(TYPE_MEMO, fn, isSyncFunction);
|
|
763
|
+
if (options?.value !== undefined)
|
|
764
|
+
validateSignalValue(TYPE_MEMO, options.value, options?.guard);
|
|
765
|
+
const node = {
|
|
766
|
+
fn,
|
|
767
|
+
value: options?.value,
|
|
768
|
+
flags: FLAG_DIRTY,
|
|
769
|
+
sources: null,
|
|
770
|
+
sourcesTail: null,
|
|
771
|
+
sinks: null,
|
|
772
|
+
sinksTail: null,
|
|
773
|
+
equals: options?.equals ?? DEFAULT_EQUALITY,
|
|
774
|
+
error: undefined,
|
|
775
|
+
stop: undefined
|
|
776
|
+
};
|
|
777
|
+
const watched = options?.watched;
|
|
778
|
+
const subscribe = watched ? () => {
|
|
779
|
+
if (activeSink) {
|
|
780
|
+
if (!node.sinks)
|
|
781
|
+
node.stop = watched(() => {
|
|
782
|
+
node.flags |= FLAG_DIRTY;
|
|
783
|
+
for (let e = node.sinks;e; e = e.nextSink)
|
|
784
|
+
propagate(e.sink);
|
|
785
|
+
if (batchDepth === 0)
|
|
786
|
+
flush();
|
|
787
|
+
});
|
|
788
|
+
link(node, activeSink);
|
|
789
|
+
}
|
|
790
|
+
} : () => {
|
|
791
|
+
if (activeSink)
|
|
792
|
+
link(node, activeSink);
|
|
793
|
+
};
|
|
794
|
+
return {
|
|
795
|
+
[Symbol.toStringTag]: TYPE_MEMO,
|
|
796
|
+
get() {
|
|
797
|
+
subscribe();
|
|
798
|
+
refresh(node);
|
|
799
|
+
if (node.error)
|
|
800
|
+
throw node.error;
|
|
801
|
+
validateReadValue(TYPE_MEMO, node.value);
|
|
802
|
+
return node.value;
|
|
803
|
+
}
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
function isMemo(value) {
|
|
807
|
+
return isObjectOfType(value, TYPE_MEMO);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// src/nodes/task.ts
|
|
811
|
+
function createTask(fn, options) {
|
|
812
|
+
validateCallback(TYPE_TASK, fn, isAsyncFunction);
|
|
813
|
+
if (options?.value !== undefined)
|
|
814
|
+
validateSignalValue(TYPE_TASK, options.value, options?.guard);
|
|
815
|
+
const node = {
|
|
816
|
+
fn,
|
|
817
|
+
value: options?.value,
|
|
818
|
+
sources: null,
|
|
819
|
+
sourcesTail: null,
|
|
820
|
+
sinks: null,
|
|
821
|
+
sinksTail: null,
|
|
822
|
+
flags: FLAG_DIRTY,
|
|
823
|
+
equals: options?.equals ?? DEFAULT_EQUALITY,
|
|
824
|
+
controller: undefined,
|
|
825
|
+
error: undefined,
|
|
826
|
+
stop: undefined
|
|
827
|
+
};
|
|
828
|
+
const watched = options?.watched;
|
|
829
|
+
const subscribe = watched ? () => {
|
|
830
|
+
if (activeSink) {
|
|
831
|
+
if (!node.sinks)
|
|
832
|
+
node.stop = watched(() => {
|
|
833
|
+
node.flags |= FLAG_DIRTY;
|
|
834
|
+
for (let e = node.sinks;e; e = e.nextSink)
|
|
835
|
+
propagate(e.sink);
|
|
836
|
+
if (batchDepth === 0)
|
|
837
|
+
flush();
|
|
838
|
+
});
|
|
839
|
+
link(node, activeSink);
|
|
840
|
+
}
|
|
841
|
+
} : () => {
|
|
842
|
+
if (activeSink)
|
|
843
|
+
link(node, activeSink);
|
|
844
|
+
};
|
|
845
|
+
return {
|
|
846
|
+
[Symbol.toStringTag]: TYPE_TASK,
|
|
847
|
+
get() {
|
|
848
|
+
subscribe();
|
|
849
|
+
refresh(node);
|
|
850
|
+
if (node.error)
|
|
851
|
+
throw node.error;
|
|
852
|
+
validateReadValue(TYPE_TASK, node.value);
|
|
853
|
+
return node.value;
|
|
854
|
+
},
|
|
855
|
+
isPending() {
|
|
856
|
+
return !!node.controller;
|
|
857
|
+
},
|
|
858
|
+
abort() {
|
|
859
|
+
node.controller?.abort();
|
|
860
|
+
node.controller = undefined;
|
|
861
|
+
}
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
function isTask(value) {
|
|
865
|
+
return isObjectOfType(value, TYPE_TASK);
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
// src/nodes/collection.ts
|
|
869
|
+
function deriveCollection(source, callback) {
|
|
870
|
+
validateCallback(TYPE_COLLECTION, callback);
|
|
871
|
+
if (!isCollectionSource(source))
|
|
872
|
+
throw new TypeError(`[${TYPE_COLLECTION}] Invalid collection source: expected a List or Collection`);
|
|
873
|
+
const isAsync = isAsyncFunction(callback);
|
|
874
|
+
const signals = new Map;
|
|
875
|
+
const addSignal = (key) => {
|
|
876
|
+
const signal = isAsync ? createTask(async (prev, abort) => {
|
|
877
|
+
const sourceValue = source.byKey(key)?.get();
|
|
878
|
+
if (sourceValue == null)
|
|
879
|
+
return prev;
|
|
880
|
+
return callback(sourceValue, abort);
|
|
881
|
+
}) : createMemo(() => {
|
|
882
|
+
const sourceValue = source.byKey(key)?.get();
|
|
883
|
+
if (sourceValue == null)
|
|
884
|
+
return;
|
|
885
|
+
return callback(sourceValue);
|
|
886
|
+
});
|
|
887
|
+
signals.set(key, signal);
|
|
888
|
+
};
|
|
889
|
+
function syncKeys() {
|
|
890
|
+
const nextKeys = Array.from(source.keys());
|
|
891
|
+
const prevKeys = node.value;
|
|
892
|
+
if (!keysEqual(prevKeys, nextKeys)) {
|
|
893
|
+
const a = new Set(prevKeys);
|
|
894
|
+
const b = new Set(nextKeys);
|
|
895
|
+
for (const key of prevKeys)
|
|
896
|
+
if (!b.has(key))
|
|
897
|
+
signals.delete(key);
|
|
898
|
+
for (const key of nextKeys)
|
|
899
|
+
if (!a.has(key))
|
|
900
|
+
addSignal(key);
|
|
901
|
+
}
|
|
902
|
+
return nextKeys;
|
|
903
|
+
}
|
|
904
|
+
const node = {
|
|
905
|
+
fn: syncKeys,
|
|
906
|
+
value: [],
|
|
907
|
+
flags: FLAG_DIRTY,
|
|
908
|
+
sources: null,
|
|
909
|
+
sourcesTail: null,
|
|
910
|
+
sinks: null,
|
|
911
|
+
sinksTail: null,
|
|
912
|
+
equals: keysEqual,
|
|
913
|
+
error: undefined
|
|
914
|
+
};
|
|
915
|
+
function ensureSynced() {
|
|
916
|
+
if (node.sources) {
|
|
917
|
+
if (node.flags) {
|
|
918
|
+
node.value = untrack(syncKeys);
|
|
919
|
+
node.flags = FLAG_CLEAN;
|
|
920
|
+
}
|
|
921
|
+
} else {
|
|
922
|
+
refresh(node);
|
|
923
|
+
if (node.error)
|
|
924
|
+
throw node.error;
|
|
925
|
+
}
|
|
926
|
+
return node.value;
|
|
927
|
+
}
|
|
928
|
+
const initialKeys = Array.from(source.keys());
|
|
929
|
+
for (const key of initialKeys)
|
|
930
|
+
addSignal(key);
|
|
931
|
+
node.value = initialKeys;
|
|
932
|
+
const collection = {
|
|
933
|
+
[Symbol.toStringTag]: TYPE_COLLECTION,
|
|
934
|
+
[Symbol.isConcatSpreadable]: true,
|
|
935
|
+
*[Symbol.iterator]() {
|
|
936
|
+
for (const key of node.value) {
|
|
937
|
+
const signal = signals.get(key);
|
|
938
|
+
if (signal)
|
|
939
|
+
yield signal;
|
|
940
|
+
}
|
|
941
|
+
},
|
|
942
|
+
get length() {
|
|
943
|
+
if (activeSink)
|
|
944
|
+
link(node, activeSink);
|
|
945
|
+
return ensureSynced().length;
|
|
946
|
+
},
|
|
947
|
+
keys() {
|
|
948
|
+
if (activeSink)
|
|
949
|
+
link(node, activeSink);
|
|
950
|
+
return ensureSynced().values();
|
|
951
|
+
},
|
|
952
|
+
get() {
|
|
953
|
+
if (activeSink)
|
|
954
|
+
link(node, activeSink);
|
|
955
|
+
const currentKeys = ensureSynced();
|
|
956
|
+
const result = [];
|
|
957
|
+
for (const key of currentKeys) {
|
|
958
|
+
try {
|
|
959
|
+
const v = signals.get(key)?.get();
|
|
960
|
+
if (v != null)
|
|
961
|
+
result.push(v);
|
|
962
|
+
} catch (e) {
|
|
963
|
+
if (!(e instanceof UnsetSignalValueError))
|
|
964
|
+
throw e;
|
|
678
965
|
}
|
|
679
|
-
}
|
|
966
|
+
}
|
|
967
|
+
return result;
|
|
968
|
+
},
|
|
969
|
+
at(index) {
|
|
970
|
+
return signals.get(node.value[index]);
|
|
971
|
+
},
|
|
972
|
+
byKey(key) {
|
|
973
|
+
return signals.get(key);
|
|
974
|
+
},
|
|
975
|
+
keyAt(index) {
|
|
976
|
+
return node.value[index];
|
|
977
|
+
},
|
|
978
|
+
indexOfKey(key) {
|
|
979
|
+
return node.value.indexOf(key);
|
|
980
|
+
},
|
|
981
|
+
deriveCollection(cb) {
|
|
982
|
+
return deriveCollection(collection, cb);
|
|
680
983
|
}
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
984
|
+
};
|
|
985
|
+
return collection;
|
|
986
|
+
}
|
|
987
|
+
function createCollection(watched, options) {
|
|
988
|
+
const value = options?.value ?? [];
|
|
989
|
+
if (value.length)
|
|
990
|
+
validateSignalValue(TYPE_COLLECTION, value, Array.isArray);
|
|
991
|
+
validateCallback(TYPE_COLLECTION, watched, isSyncFunction);
|
|
992
|
+
const signals = new Map;
|
|
993
|
+
const keys = [];
|
|
994
|
+
const itemToKey = new Map;
|
|
995
|
+
const [generateKey, contentBased] = getKeyGenerator(options?.keyConfig);
|
|
996
|
+
const resolveKey = (item) => itemToKey.get(item) ?? (contentBased ? generateKey(item) : undefined);
|
|
997
|
+
const itemFactory = options?.createItem ?? createState;
|
|
998
|
+
function buildValue() {
|
|
999
|
+
const result = [];
|
|
1000
|
+
for (const key of keys) {
|
|
1001
|
+
try {
|
|
1002
|
+
const v = signals.get(key)?.get();
|
|
1003
|
+
if (v != null)
|
|
1004
|
+
result.push(v);
|
|
1005
|
+
} catch (e) {
|
|
1006
|
+
if (!(e instanceof UnsetSignalValueError))
|
|
1007
|
+
throw e;
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
return result;
|
|
1011
|
+
}
|
|
1012
|
+
const node = {
|
|
1013
|
+
fn: buildValue,
|
|
1014
|
+
value,
|
|
1015
|
+
flags: FLAG_DIRTY,
|
|
1016
|
+
sources: null,
|
|
1017
|
+
sourcesTail: null,
|
|
1018
|
+
sinks: null,
|
|
1019
|
+
sinksTail: null,
|
|
1020
|
+
equals: SKIP_EQUALITY,
|
|
1021
|
+
error: undefined
|
|
1022
|
+
};
|
|
1023
|
+
for (const item of value) {
|
|
1024
|
+
const key = generateKey(item);
|
|
1025
|
+
signals.set(key, itemFactory(item));
|
|
1026
|
+
itemToKey.set(item, key);
|
|
1027
|
+
keys.push(key);
|
|
1028
|
+
}
|
|
1029
|
+
node.value = value;
|
|
1030
|
+
node.flags = FLAG_DIRTY;
|
|
1031
|
+
function subscribe() {
|
|
1032
|
+
if (activeSink) {
|
|
1033
|
+
if (!node.sinks)
|
|
1034
|
+
node.stop = watched((changes) => {
|
|
1035
|
+
const { add, change, remove } = changes;
|
|
1036
|
+
if (!add?.length && !change?.length && !remove?.length)
|
|
1037
|
+
return;
|
|
1038
|
+
let structural = false;
|
|
1039
|
+
batch(() => {
|
|
1040
|
+
if (add) {
|
|
1041
|
+
for (const item of add) {
|
|
1042
|
+
const key = generateKey(item);
|
|
1043
|
+
signals.set(key, itemFactory(item));
|
|
1044
|
+
itemToKey.set(item, key);
|
|
1045
|
+
if (!keys.includes(key))
|
|
1046
|
+
keys.push(key);
|
|
1047
|
+
structural = true;
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
if (change) {
|
|
1051
|
+
for (const item of change) {
|
|
1052
|
+
const key = resolveKey(item);
|
|
1053
|
+
if (!key)
|
|
1054
|
+
continue;
|
|
1055
|
+
const signal = signals.get(key);
|
|
1056
|
+
if (signal && isState(signal)) {
|
|
1057
|
+
itemToKey.delete(signal.get());
|
|
1058
|
+
signal.set(item);
|
|
1059
|
+
itemToKey.set(item, key);
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
if (remove) {
|
|
1064
|
+
for (const item of remove) {
|
|
1065
|
+
const key = resolveKey(item);
|
|
1066
|
+
if (!key)
|
|
1067
|
+
continue;
|
|
1068
|
+
itemToKey.delete(item);
|
|
1069
|
+
signals.delete(key);
|
|
1070
|
+
const index = keys.indexOf(key);
|
|
1071
|
+
if (index !== -1)
|
|
1072
|
+
keys.splice(index, 1);
|
|
1073
|
+
structural = true;
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
if (structural) {
|
|
1077
|
+
node.sources = null;
|
|
1078
|
+
node.sourcesTail = null;
|
|
1079
|
+
}
|
|
1080
|
+
node.flags = FLAG_DIRTY;
|
|
1081
|
+
for (let e = node.sinks;e; e = e.nextSink)
|
|
1082
|
+
propagate(e.sink);
|
|
1083
|
+
});
|
|
1084
|
+
});
|
|
1085
|
+
link(node, activeSink);
|
|
684
1086
|
}
|
|
685
|
-
return changes.changed;
|
|
686
|
-
}
|
|
687
|
-
get [Symbol.toStringTag]() {
|
|
688
|
-
return TYPE_STORE;
|
|
689
|
-
}
|
|
690
|
-
get [Symbol.isConcatSpreadable]() {
|
|
691
|
-
return false;
|
|
692
|
-
}
|
|
693
|
-
*[Symbol.iterator]() {
|
|
694
|
-
for (const [key, signal] of this.#signals.entries())
|
|
695
|
-
yield [key, signal];
|
|
696
|
-
}
|
|
697
|
-
keys() {
|
|
698
|
-
subscribeTo(this);
|
|
699
|
-
return this.#signals.keys();
|
|
700
1087
|
}
|
|
701
|
-
|
|
702
|
-
|
|
1088
|
+
const collection = {
|
|
1089
|
+
[Symbol.toStringTag]: TYPE_COLLECTION,
|
|
1090
|
+
[Symbol.isConcatSpreadable]: true,
|
|
1091
|
+
*[Symbol.iterator]() {
|
|
1092
|
+
for (const key of keys) {
|
|
1093
|
+
const signal = signals.get(key);
|
|
1094
|
+
if (signal)
|
|
1095
|
+
yield signal;
|
|
1096
|
+
}
|
|
1097
|
+
},
|
|
1098
|
+
get length() {
|
|
1099
|
+
subscribe();
|
|
1100
|
+
return keys.length;
|
|
1101
|
+
},
|
|
1102
|
+
keys() {
|
|
1103
|
+
subscribe();
|
|
1104
|
+
return keys.values();
|
|
1105
|
+
},
|
|
1106
|
+
get() {
|
|
1107
|
+
subscribe();
|
|
1108
|
+
if (node.sources) {
|
|
1109
|
+
if (node.flags) {
|
|
1110
|
+
node.value = untrack(buildValue);
|
|
1111
|
+
node.flags = FLAG_CLEAN;
|
|
1112
|
+
}
|
|
1113
|
+
} else {
|
|
1114
|
+
refresh(node);
|
|
1115
|
+
if (node.error)
|
|
1116
|
+
throw node.error;
|
|
1117
|
+
}
|
|
1118
|
+
return node.value;
|
|
1119
|
+
},
|
|
1120
|
+
at(index) {
|
|
1121
|
+
return signals.get(keys[index]);
|
|
1122
|
+
},
|
|
1123
|
+
byKey(key) {
|
|
1124
|
+
return signals.get(key);
|
|
1125
|
+
},
|
|
1126
|
+
keyAt(index) {
|
|
1127
|
+
return keys[index];
|
|
1128
|
+
},
|
|
1129
|
+
indexOfKey(key) {
|
|
1130
|
+
return keys.indexOf(key);
|
|
1131
|
+
},
|
|
1132
|
+
deriveCollection(cb) {
|
|
1133
|
+
return deriveCollection(collection, cb);
|
|
1134
|
+
}
|
|
1135
|
+
};
|
|
1136
|
+
return collection;
|
|
1137
|
+
}
|
|
1138
|
+
function isCollection(value) {
|
|
1139
|
+
return isObjectOfType(value, TYPE_COLLECTION);
|
|
1140
|
+
}
|
|
1141
|
+
function isCollectionSource(value) {
|
|
1142
|
+
return isList(value) || isCollection(value);
|
|
1143
|
+
}
|
|
1144
|
+
// src/nodes/effect.ts
|
|
1145
|
+
function createEffect(fn) {
|
|
1146
|
+
validateCallback("Effect", fn);
|
|
1147
|
+
const node = {
|
|
1148
|
+
fn,
|
|
1149
|
+
flags: FLAG_DIRTY,
|
|
1150
|
+
sources: null,
|
|
1151
|
+
sourcesTail: null,
|
|
1152
|
+
cleanup: null
|
|
1153
|
+
};
|
|
1154
|
+
const dispose = () => {
|
|
1155
|
+
runCleanup(node);
|
|
1156
|
+
node.fn = undefined;
|
|
1157
|
+
node.flags = FLAG_CLEAN;
|
|
1158
|
+
node.sourcesTail = null;
|
|
1159
|
+
trimSources(node);
|
|
1160
|
+
};
|
|
1161
|
+
if (activeOwner)
|
|
1162
|
+
registerCleanup(activeOwner, dispose);
|
|
1163
|
+
runEffect(node);
|
|
1164
|
+
return dispose;
|
|
1165
|
+
}
|
|
1166
|
+
function match(signals, handlers) {
|
|
1167
|
+
if (!activeOwner)
|
|
1168
|
+
throw new RequiredOwnerError("match");
|
|
1169
|
+
const { ok, err = console.error, nil } = handlers;
|
|
1170
|
+
let errors;
|
|
1171
|
+
let pending = false;
|
|
1172
|
+
const values = new Array(signals.length);
|
|
1173
|
+
for (let i = 0;i < signals.length; i++) {
|
|
1174
|
+
try {
|
|
1175
|
+
values[i] = signals[i].get();
|
|
1176
|
+
} catch (e) {
|
|
1177
|
+
if (e instanceof UnsetSignalValueError) {
|
|
1178
|
+
pending = true;
|
|
1179
|
+
continue;
|
|
1180
|
+
}
|
|
1181
|
+
if (!errors)
|
|
1182
|
+
errors = [];
|
|
1183
|
+
errors.push(e instanceof Error ? e : new Error(String(e)));
|
|
1184
|
+
}
|
|
703
1185
|
}
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
1186
|
+
let out;
|
|
1187
|
+
try {
|
|
1188
|
+
if (pending)
|
|
1189
|
+
out = nil?.();
|
|
1190
|
+
else if (errors)
|
|
1191
|
+
out = err(errors);
|
|
1192
|
+
else
|
|
1193
|
+
out = ok(values);
|
|
1194
|
+
} catch (e) {
|
|
1195
|
+
err([e instanceof Error ? e : new Error(String(e))]);
|
|
1196
|
+
}
|
|
1197
|
+
if (typeof out === "function")
|
|
1198
|
+
return out;
|
|
1199
|
+
if (out instanceof Promise) {
|
|
1200
|
+
const owner = activeOwner;
|
|
1201
|
+
const controller = new AbortController;
|
|
1202
|
+
registerCleanup(owner, () => controller.abort());
|
|
1203
|
+
out.then((cleanup) => {
|
|
1204
|
+
if (!controller.signal.aborted && typeof cleanup === "function")
|
|
1205
|
+
registerCleanup(owner, cleanup);
|
|
1206
|
+
}).catch((e) => {
|
|
1207
|
+
err([e instanceof Error ? e : new Error(String(e))]);
|
|
1208
|
+
});
|
|
707
1209
|
}
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
1210
|
+
}
|
|
1211
|
+
// src/nodes/sensor.ts
|
|
1212
|
+
function createSensor(watched, options) {
|
|
1213
|
+
validateCallback(TYPE_SENSOR, watched, isSyncFunction);
|
|
1214
|
+
if (options?.value !== undefined)
|
|
1215
|
+
validateSignalValue(TYPE_SENSOR, options.value, options?.guard);
|
|
1216
|
+
const node = {
|
|
1217
|
+
value: options?.value,
|
|
1218
|
+
sinks: null,
|
|
1219
|
+
sinksTail: null,
|
|
1220
|
+
equals: options?.equals ?? DEFAULT_EQUALITY,
|
|
1221
|
+
guard: options?.guard,
|
|
1222
|
+
stop: undefined
|
|
1223
|
+
};
|
|
1224
|
+
return {
|
|
1225
|
+
[Symbol.toStringTag]: TYPE_SENSOR,
|
|
1226
|
+
get() {
|
|
1227
|
+
if (activeSink) {
|
|
1228
|
+
if (!node.sinks)
|
|
1229
|
+
node.stop = watched((next) => {
|
|
1230
|
+
validateSignalValue(TYPE_SENSOR, next, node.guard);
|
|
1231
|
+
setState(node, next);
|
|
1232
|
+
});
|
|
1233
|
+
link(node, activeSink);
|
|
1234
|
+
}
|
|
1235
|
+
validateReadValue(TYPE_SENSOR, node.value);
|
|
1236
|
+
return node.value;
|
|
714
1237
|
}
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
1238
|
+
};
|
|
1239
|
+
}
|
|
1240
|
+
function isSensor(value) {
|
|
1241
|
+
return isObjectOfType(value, TYPE_SENSOR);
|
|
1242
|
+
}
|
|
1243
|
+
// src/nodes/store.ts
|
|
1244
|
+
function diffRecords(prev, next) {
|
|
1245
|
+
const prevValid = isRecord(prev) || Array.isArray(prev);
|
|
1246
|
+
const nextValid = isRecord(next) || Array.isArray(next);
|
|
1247
|
+
if (!prevValid || !nextValid) {
|
|
1248
|
+
const changed2 = !Object.is(prev, next);
|
|
1249
|
+
return {
|
|
1250
|
+
changed: changed2,
|
|
1251
|
+
add: changed2 && nextValid ? next : {},
|
|
1252
|
+
change: {},
|
|
1253
|
+
remove: changed2 && prevValid ? prev : {}
|
|
1254
|
+
};
|
|
721
1255
|
}
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
1256
|
+
const visited = new WeakSet;
|
|
1257
|
+
const add = {};
|
|
1258
|
+
const change = {};
|
|
1259
|
+
const remove = {};
|
|
1260
|
+
let changed = false;
|
|
1261
|
+
const prevKeys = Object.keys(prev);
|
|
1262
|
+
const nextKeys = Object.keys(next);
|
|
1263
|
+
for (const key of nextKeys) {
|
|
1264
|
+
if (key in prev) {
|
|
1265
|
+
if (!isEqual(prev[key], next[key], visited)) {
|
|
1266
|
+
change[key] = next[key];
|
|
1267
|
+
changed = true;
|
|
1268
|
+
}
|
|
1269
|
+
} else {
|
|
1270
|
+
add[key] = next[key];
|
|
1271
|
+
changed = true;
|
|
1272
|
+
}
|
|
729
1273
|
}
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
1274
|
+
for (const key of prevKeys) {
|
|
1275
|
+
if (!(key in next)) {
|
|
1276
|
+
remove[key] = undefined;
|
|
1277
|
+
changed = true;
|
|
1278
|
+
}
|
|
734
1279
|
}
|
|
1280
|
+
return { add, change, remove, changed };
|
|
735
1281
|
}
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
1282
|
+
function createStore(value, options) {
|
|
1283
|
+
validateSignalValue(TYPE_STORE, value, isRecord);
|
|
1284
|
+
const signals = new Map;
|
|
1285
|
+
const addSignal = (key, val) => {
|
|
1286
|
+
validateSignalValue(`${TYPE_STORE} for key "${key}"`, val);
|
|
1287
|
+
if (Array.isArray(val))
|
|
1288
|
+
signals.set(key, createList(val));
|
|
1289
|
+
else if (isRecord(val))
|
|
1290
|
+
signals.set(key, createStore(val));
|
|
1291
|
+
else
|
|
1292
|
+
signals.set(key, createState(val));
|
|
1293
|
+
};
|
|
1294
|
+
const buildValue = () => {
|
|
1295
|
+
const record = {};
|
|
1296
|
+
signals.forEach((signal, key) => {
|
|
1297
|
+
record[key] = signal.get();
|
|
1298
|
+
});
|
|
1299
|
+
return record;
|
|
1300
|
+
};
|
|
1301
|
+
const node = {
|
|
1302
|
+
fn: buildValue,
|
|
1303
|
+
value,
|
|
1304
|
+
flags: FLAG_DIRTY,
|
|
1305
|
+
sources: null,
|
|
1306
|
+
sourcesTail: null,
|
|
1307
|
+
sinks: null,
|
|
1308
|
+
sinksTail: null,
|
|
1309
|
+
equals: isEqual,
|
|
1310
|
+
error: undefined
|
|
1311
|
+
};
|
|
1312
|
+
const applyChanges = (changes) => {
|
|
1313
|
+
let structural = false;
|
|
1314
|
+
for (const key in changes.add) {
|
|
1315
|
+
addSignal(key, changes.add[key]);
|
|
1316
|
+
structural = true;
|
|
1317
|
+
}
|
|
1318
|
+
if (Object.keys(changes.change).length) {
|
|
1319
|
+
batch(() => {
|
|
1320
|
+
for (const key in changes.change) {
|
|
1321
|
+
const val = changes.change[key];
|
|
1322
|
+
validateSignalValue(`${TYPE_STORE} for key "${key}"`, val);
|
|
1323
|
+
const signal = signals.get(key);
|
|
1324
|
+
if (signal) {
|
|
1325
|
+
if (isRecord(val) !== isStore(signal)) {
|
|
1326
|
+
addSignal(key, val);
|
|
1327
|
+
structural = true;
|
|
1328
|
+
} else
|
|
1329
|
+
signal.set(val);
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
});
|
|
1333
|
+
}
|
|
1334
|
+
for (const key in changes.remove) {
|
|
1335
|
+
signals.delete(key);
|
|
1336
|
+
structural = true;
|
|
1337
|
+
}
|
|
1338
|
+
if (structural) {
|
|
1339
|
+
node.sources = null;
|
|
1340
|
+
node.sourcesTail = null;
|
|
1341
|
+
}
|
|
1342
|
+
return changes.changed;
|
|
1343
|
+
};
|
|
1344
|
+
const watched = options?.watched;
|
|
1345
|
+
const subscribe = watched ? () => {
|
|
1346
|
+
if (activeSink) {
|
|
1347
|
+
if (!node.sinks)
|
|
1348
|
+
node.stop = watched();
|
|
1349
|
+
link(node, activeSink);
|
|
1350
|
+
}
|
|
1351
|
+
} : () => {
|
|
1352
|
+
if (activeSink)
|
|
1353
|
+
link(node, activeSink);
|
|
1354
|
+
};
|
|
1355
|
+
for (const key of Object.keys(value))
|
|
1356
|
+
addSignal(key, value[key]);
|
|
1357
|
+
const store = {
|
|
1358
|
+
[Symbol.toStringTag]: TYPE_STORE,
|
|
1359
|
+
[Symbol.isConcatSpreadable]: false,
|
|
1360
|
+
*[Symbol.iterator]() {
|
|
1361
|
+
for (const key of Array.from(signals.keys())) {
|
|
1362
|
+
const signal = signals.get(key);
|
|
1363
|
+
if (signal)
|
|
1364
|
+
yield [key, signal];
|
|
743
1365
|
}
|
|
744
|
-
|
|
1366
|
+
},
|
|
1367
|
+
keys() {
|
|
1368
|
+
subscribe();
|
|
1369
|
+
return signals.keys();
|
|
1370
|
+
},
|
|
1371
|
+
byKey(key) {
|
|
1372
|
+
return signals.get(key);
|
|
1373
|
+
},
|
|
1374
|
+
get() {
|
|
1375
|
+
subscribe();
|
|
1376
|
+
if (node.sources) {
|
|
1377
|
+
if (node.flags) {
|
|
1378
|
+
node.value = untrack(buildValue);
|
|
1379
|
+
node.flags = FLAG_CLEAN;
|
|
1380
|
+
}
|
|
1381
|
+
} else {
|
|
1382
|
+
refresh(node);
|
|
1383
|
+
if (node.error)
|
|
1384
|
+
throw node.error;
|
|
1385
|
+
}
|
|
1386
|
+
return node.value;
|
|
1387
|
+
},
|
|
1388
|
+
set(next) {
|
|
1389
|
+
const prev = node.flags & FLAG_DIRTY ? buildValue() : node.value;
|
|
1390
|
+
const changes = diffRecords(prev, next);
|
|
1391
|
+
if (applyChanges(changes)) {
|
|
1392
|
+
node.flags |= FLAG_DIRTY;
|
|
1393
|
+
for (let e = node.sinks;e; e = e.nextSink)
|
|
1394
|
+
propagate(e.sink);
|
|
1395
|
+
if (batchDepth === 0)
|
|
1396
|
+
flush();
|
|
1397
|
+
}
|
|
1398
|
+
},
|
|
1399
|
+
update(fn) {
|
|
1400
|
+
store.set(fn(store.get()));
|
|
1401
|
+
},
|
|
1402
|
+
add(key, value2) {
|
|
1403
|
+
if (signals.has(key))
|
|
1404
|
+
throw new DuplicateKeyError(TYPE_STORE, key, value2);
|
|
1405
|
+
addSignal(key, value2);
|
|
1406
|
+
node.sources = null;
|
|
1407
|
+
node.sourcesTail = null;
|
|
1408
|
+
node.flags |= FLAG_DIRTY;
|
|
1409
|
+
for (let e = node.sinks;e; e = e.nextSink)
|
|
1410
|
+
propagate(e.sink);
|
|
1411
|
+
if (batchDepth === 0)
|
|
1412
|
+
flush();
|
|
1413
|
+
return key;
|
|
1414
|
+
},
|
|
1415
|
+
remove(key) {
|
|
1416
|
+
const ok = signals.delete(key);
|
|
1417
|
+
if (ok) {
|
|
1418
|
+
node.sources = null;
|
|
1419
|
+
node.sourcesTail = null;
|
|
1420
|
+
node.flags |= FLAG_DIRTY;
|
|
1421
|
+
for (let e = node.sinks;e; e = e.nextSink)
|
|
1422
|
+
propagate(e.sink);
|
|
1423
|
+
if (batchDepth === 0)
|
|
1424
|
+
flush();
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
};
|
|
1428
|
+
return new Proxy(store, {
|
|
1429
|
+
get(target, prop) {
|
|
1430
|
+
if (prop in target)
|
|
1431
|
+
return Reflect.get(target, prop);
|
|
1432
|
+
if (typeof prop !== "symbol")
|
|
745
1433
|
return target.byKey(prop);
|
|
746
1434
|
},
|
|
747
1435
|
has(target, prop) {
|
|
@@ -755,7 +1443,7 @@ var createStore = (initialValue, options) => {
|
|
|
755
1443
|
getOwnPropertyDescriptor(target, prop) {
|
|
756
1444
|
if (prop in target)
|
|
757
1445
|
return Reflect.getOwnPropertyDescriptor(target, prop);
|
|
758
|
-
if (
|
|
1446
|
+
if (typeof prop === "symbol")
|
|
759
1447
|
return;
|
|
760
1448
|
const signal = target.byKey(String(prop));
|
|
761
1449
|
return signal ? {
|
|
@@ -766,402 +1454,96 @@ var createStore = (initialValue, options) => {
|
|
|
766
1454
|
} : undefined;
|
|
767
1455
|
}
|
|
768
1456
|
});
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
|
|
1457
|
+
}
|
|
1458
|
+
function isStore(value) {
|
|
1459
|
+
return isObjectOfType(value, TYPE_STORE);
|
|
1460
|
+
}
|
|
772
1461
|
// src/signal.ts
|
|
773
|
-
|
|
774
|
-
|
|
1462
|
+
function createComputed(callback, options) {
|
|
1463
|
+
return isAsyncFunction(callback) ? createTask(callback, options) : createMemo(callback, options);
|
|
1464
|
+
}
|
|
775
1465
|
function createSignal(value) {
|
|
776
|
-
if (
|
|
777
|
-
return
|
|
778
|
-
if (
|
|
779
|
-
|
|
1466
|
+
if (isSignal(value))
|
|
1467
|
+
return value;
|
|
1468
|
+
if (value == null)
|
|
1469
|
+
throw new InvalidSignalValueError("createSignal", value);
|
|
1470
|
+
if (isAsyncFunction(value))
|
|
1471
|
+
return createTask(value);
|
|
1472
|
+
if (isFunction(value))
|
|
1473
|
+
return createMemo(value);
|
|
780
1474
|
if (isUniformArray(value))
|
|
781
|
-
return
|
|
1475
|
+
return createList(value);
|
|
782
1476
|
if (isRecord(value))
|
|
783
1477
|
return createStore(value);
|
|
784
|
-
return
|
|
1478
|
+
return createState(value);
|
|
785
1479
|
}
|
|
786
1480
|
function createMutableSignal(value) {
|
|
1481
|
+
if (isMutableSignal(value))
|
|
1482
|
+
return value;
|
|
1483
|
+
if (value == null || isFunction(value) || isSignal(value))
|
|
1484
|
+
throw new InvalidSignalValueError("createMutableSignal", value);
|
|
787
1485
|
if (isUniformArray(value))
|
|
788
|
-
return
|
|
1486
|
+
return createList(value);
|
|
789
1487
|
if (isRecord(value))
|
|
790
1488
|
return createStore(value);
|
|
791
|
-
return
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
// src/errors.ts
|
|
795
|
-
class CircularDependencyError extends Error {
|
|
796
|
-
constructor(where) {
|
|
797
|
-
super(`Circular dependency detected in ${where}`);
|
|
798
|
-
this.name = "CircularDependencyError";
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
class DuplicateKeyError extends Error {
|
|
803
|
-
constructor(where, key, value) {
|
|
804
|
-
super(`Could not add ${where} key "${key}"${value ? ` with value ${valueString(value)}` : ""} because it already exists`);
|
|
805
|
-
this.name = "DuplicateKeyError";
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
class FailedAssertionError extends Error {
|
|
810
|
-
constructor(message = "unexpected condition") {
|
|
811
|
-
super(`Assertion failed: ${message}`);
|
|
812
|
-
this.name = "FailedAssertionError";
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
class InvalidCallbackError extends TypeError {
|
|
817
|
-
constructor(where, value) {
|
|
818
|
-
super(`Invalid ${where} callback ${valueString(value)}`);
|
|
819
|
-
this.name = "InvalidCallbackError";
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
class InvalidCollectionSourceError extends TypeError {
|
|
824
|
-
constructor(where, value) {
|
|
825
|
-
super(`Invalid ${where} source ${valueString(value)}`);
|
|
826
|
-
this.name = "InvalidCollectionSourceError";
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
class InvalidSignalValueError extends TypeError {
|
|
831
|
-
constructor(where, value) {
|
|
832
|
-
super(`Invalid signal value ${valueString(value)} in ${where}`);
|
|
833
|
-
this.name = "InvalidSignalValueError";
|
|
834
|
-
}
|
|
1489
|
+
return createState(value);
|
|
835
1490
|
}
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
constructor(where) {
|
|
839
|
-
super(`Nullish signal values are not allowed in ${where}`);
|
|
840
|
-
this.name = "NullishSignalValueError";
|
|
841
|
-
}
|
|
1491
|
+
function isComputed(value) {
|
|
1492
|
+
return isMemo(value) || isTask(value);
|
|
842
1493
|
}
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
1494
|
+
function isSignal(value) {
|
|
1495
|
+
const signalsTypes = [
|
|
1496
|
+
TYPE_STATE,
|
|
1497
|
+
TYPE_MEMO,
|
|
1498
|
+
TYPE_TASK,
|
|
1499
|
+
TYPE_SENSOR,
|
|
1500
|
+
TYPE_LIST,
|
|
1501
|
+
TYPE_COLLECTION,
|
|
1502
|
+
TYPE_STORE
|
|
1503
|
+
];
|
|
1504
|
+
const typeStyle = Object.prototype.toString.call(value).slice(8, -1);
|
|
1505
|
+
return signalsTypes.includes(typeStyle);
|
|
853
1506
|
}
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
if (!guard(value))
|
|
857
|
-
throw new InvalidCallbackError(where, value);
|
|
858
|
-
};
|
|
859
|
-
var validateSignalValue = (where, value, guard = () => !(isSymbol(value) && value !== UNSET) || isFunction(value)) => {
|
|
860
|
-
if (value == null)
|
|
861
|
-
throw new NullishSignalValueError(where);
|
|
862
|
-
if (!guard(value))
|
|
863
|
-
throw new InvalidSignalValueError(where, value);
|
|
864
|
-
};
|
|
865
|
-
var guardMutableSignal = (what, value, signal) => {
|
|
866
|
-
if (!isMutableSignal(signal))
|
|
867
|
-
throw new ReadonlySignalError(what, value);
|
|
868
|
-
return true;
|
|
869
|
-
};
|
|
870
|
-
|
|
871
|
-
// src/classes/collection.ts
|
|
872
|
-
var TYPE_COLLECTION = "Collection";
|
|
873
|
-
|
|
874
|
-
class DerivedCollection {
|
|
875
|
-
#source;
|
|
876
|
-
#callback;
|
|
877
|
-
#signals = new Map;
|
|
878
|
-
#keys = [];
|
|
879
|
-
#dirty = true;
|
|
880
|
-
#watcher;
|
|
881
|
-
constructor(source, callback, options) {
|
|
882
|
-
validateCallback(TYPE_COLLECTION, callback);
|
|
883
|
-
if (isFunction(source))
|
|
884
|
-
source = source();
|
|
885
|
-
if (!isCollectionSource(source))
|
|
886
|
-
throw new InvalidCollectionSourceError(TYPE_COLLECTION, source);
|
|
887
|
-
this.#source = source;
|
|
888
|
-
this.#callback = callback;
|
|
889
|
-
for (let i = 0;i < this.#source.length; i++) {
|
|
890
|
-
const key = this.#source.keyAt(i);
|
|
891
|
-
if (!key)
|
|
892
|
-
continue;
|
|
893
|
-
this.#add(key);
|
|
894
|
-
}
|
|
895
|
-
if (options?.watched)
|
|
896
|
-
registerWatchCallbacks(this, options.watched, options.unwatched);
|
|
897
|
-
}
|
|
898
|
-
#getWatcher() {
|
|
899
|
-
this.#watcher ||= createWatcher(() => {
|
|
900
|
-
this.#dirty = true;
|
|
901
|
-
if (!notifyOf(this))
|
|
902
|
-
this.#watcher?.stop();
|
|
903
|
-
}, () => {
|
|
904
|
-
const newKeys = Array.from(this.#source.keys());
|
|
905
|
-
const allKeys = new Set([...this.#keys, ...newKeys]);
|
|
906
|
-
const addedKeys = [];
|
|
907
|
-
const removedKeys = [];
|
|
908
|
-
for (const key of allKeys) {
|
|
909
|
-
const oldHas = this.#keys.includes(key);
|
|
910
|
-
const newHas = newKeys.includes(key);
|
|
911
|
-
if (!oldHas && newHas)
|
|
912
|
-
addedKeys.push(key);
|
|
913
|
-
else if (oldHas && !newHas)
|
|
914
|
-
removedKeys.push(key);
|
|
915
|
-
}
|
|
916
|
-
for (const key of removedKeys)
|
|
917
|
-
this.#signals.delete(key);
|
|
918
|
-
for (const key of addedKeys)
|
|
919
|
-
this.#add(key);
|
|
920
|
-
this.#keys = newKeys;
|
|
921
|
-
this.#dirty = false;
|
|
922
|
-
});
|
|
923
|
-
this.#watcher.onCleanup(() => {
|
|
924
|
-
this.#watcher = undefined;
|
|
925
|
-
});
|
|
926
|
-
return this.#watcher;
|
|
927
|
-
}
|
|
928
|
-
#add(key) {
|
|
929
|
-
const computedCallback = isAsyncCollectionCallback(this.#callback) ? async (_, abort) => {
|
|
930
|
-
const sourceValue = this.#source.byKey(key)?.get();
|
|
931
|
-
if (sourceValue === UNSET)
|
|
932
|
-
return UNSET;
|
|
933
|
-
return this.#callback(sourceValue, abort);
|
|
934
|
-
} : () => {
|
|
935
|
-
const sourceValue = this.#source.byKey(key)?.get();
|
|
936
|
-
if (sourceValue === UNSET)
|
|
937
|
-
return UNSET;
|
|
938
|
-
return this.#callback(sourceValue);
|
|
939
|
-
};
|
|
940
|
-
const signal = createComputed(computedCallback);
|
|
941
|
-
this.#signals.set(key, signal);
|
|
942
|
-
if (!this.#keys.includes(key))
|
|
943
|
-
this.#keys.push(key);
|
|
944
|
-
return true;
|
|
945
|
-
}
|
|
946
|
-
get [Symbol.toStringTag]() {
|
|
947
|
-
return TYPE_COLLECTION;
|
|
948
|
-
}
|
|
949
|
-
get [Symbol.isConcatSpreadable]() {
|
|
950
|
-
return true;
|
|
951
|
-
}
|
|
952
|
-
*[Symbol.iterator]() {
|
|
953
|
-
for (const key of this.#keys) {
|
|
954
|
-
const signal = this.#signals.get(key);
|
|
955
|
-
if (signal)
|
|
956
|
-
yield signal;
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
keys() {
|
|
960
|
-
subscribeTo(this);
|
|
961
|
-
if (this.#dirty)
|
|
962
|
-
this.#getWatcher().run();
|
|
963
|
-
return this.#keys.values();
|
|
964
|
-
}
|
|
965
|
-
get() {
|
|
966
|
-
subscribeTo(this);
|
|
967
|
-
if (this.#dirty)
|
|
968
|
-
this.#getWatcher().run();
|
|
969
|
-
return this.#keys.map((key) => this.#signals.get(key)?.get()).filter((v) => v != null && v !== UNSET);
|
|
970
|
-
}
|
|
971
|
-
at(index) {
|
|
972
|
-
return this.#signals.get(this.#keys[index]);
|
|
973
|
-
}
|
|
974
|
-
byKey(key) {
|
|
975
|
-
return this.#signals.get(key);
|
|
976
|
-
}
|
|
977
|
-
keyAt(index) {
|
|
978
|
-
return this.#keys[index];
|
|
979
|
-
}
|
|
980
|
-
indexOfKey(key) {
|
|
981
|
-
return this.#keys.indexOf(key);
|
|
982
|
-
}
|
|
983
|
-
deriveCollection(callback, options) {
|
|
984
|
-
return new DerivedCollection(this, callback, options);
|
|
985
|
-
}
|
|
986
|
-
get length() {
|
|
987
|
-
subscribeTo(this);
|
|
988
|
-
if (this.#dirty)
|
|
989
|
-
this.#getWatcher().run();
|
|
990
|
-
return this.#keys.length;
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
var isCollection = (value) => isObjectOfType(value, TYPE_COLLECTION);
|
|
994
|
-
var isCollectionSource = (value) => isList(value) || isCollection(value);
|
|
995
|
-
var isAsyncCollectionCallback = (callback) => isAsyncFunction(callback);
|
|
996
|
-
// src/classes/ref.ts
|
|
997
|
-
var TYPE_REF = "Ref";
|
|
998
|
-
|
|
999
|
-
class Ref {
|
|
1000
|
-
#value;
|
|
1001
|
-
constructor(value, options) {
|
|
1002
|
-
validateSignalValue(TYPE_REF, value, options?.guard);
|
|
1003
|
-
this.#value = value;
|
|
1004
|
-
if (options?.watched)
|
|
1005
|
-
registerWatchCallbacks(this, options.watched, options.unwatched);
|
|
1006
|
-
}
|
|
1007
|
-
get [Symbol.toStringTag]() {
|
|
1008
|
-
return TYPE_REF;
|
|
1009
|
-
}
|
|
1010
|
-
get() {
|
|
1011
|
-
subscribeTo(this);
|
|
1012
|
-
return this.#value;
|
|
1013
|
-
}
|
|
1014
|
-
notify() {
|
|
1015
|
-
notifyOf(this);
|
|
1016
|
-
}
|
|
1017
|
-
}
|
|
1018
|
-
var isRef = (value) => isObjectOfType(value, TYPE_REF);
|
|
1019
|
-
// src/effect.ts
|
|
1020
|
-
var createEffect = (callback) => {
|
|
1021
|
-
if (!isFunction(callback) || callback.length > 1)
|
|
1022
|
-
throw new InvalidCallbackError("effect", callback);
|
|
1023
|
-
const isAsync = isAsyncFunction(callback);
|
|
1024
|
-
let running = false;
|
|
1025
|
-
let controller;
|
|
1026
|
-
const watcher = createWatcher(() => {
|
|
1027
|
-
watcher.run();
|
|
1028
|
-
}, () => {
|
|
1029
|
-
if (running)
|
|
1030
|
-
throw new CircularDependencyError("effect");
|
|
1031
|
-
running = true;
|
|
1032
|
-
controller?.abort();
|
|
1033
|
-
controller = undefined;
|
|
1034
|
-
let cleanup;
|
|
1035
|
-
try {
|
|
1036
|
-
if (isAsync) {
|
|
1037
|
-
controller = new AbortController;
|
|
1038
|
-
const currentController = controller;
|
|
1039
|
-
callback(controller.signal).then((cleanup2) => {
|
|
1040
|
-
if (isFunction(cleanup2) && controller === currentController)
|
|
1041
|
-
watcher.onCleanup(cleanup2);
|
|
1042
|
-
}).catch((error) => {
|
|
1043
|
-
if (!isAbortError(error))
|
|
1044
|
-
console.error("Error in async effect callback:", error);
|
|
1045
|
-
});
|
|
1046
|
-
} else {
|
|
1047
|
-
cleanup = callback();
|
|
1048
|
-
if (isFunction(cleanup))
|
|
1049
|
-
watcher.onCleanup(cleanup);
|
|
1050
|
-
}
|
|
1051
|
-
} catch (error) {
|
|
1052
|
-
if (!isAbortError(error))
|
|
1053
|
-
console.error("Error in effect callback:", error);
|
|
1054
|
-
}
|
|
1055
|
-
running = false;
|
|
1056
|
-
});
|
|
1057
|
-
watcher();
|
|
1058
|
-
return () => {
|
|
1059
|
-
controller?.abort();
|
|
1060
|
-
try {
|
|
1061
|
-
watcher.stop();
|
|
1062
|
-
} catch (error) {
|
|
1063
|
-
console.error("Error in effect cleanup:", error);
|
|
1064
|
-
}
|
|
1065
|
-
};
|
|
1066
|
-
};
|
|
1067
|
-
// src/match.ts
|
|
1068
|
-
function match(result, handlers) {
|
|
1069
|
-
try {
|
|
1070
|
-
if (result.pending)
|
|
1071
|
-
handlers.nil?.();
|
|
1072
|
-
else if (result.errors)
|
|
1073
|
-
handlers.err?.(result.errors);
|
|
1074
|
-
else if (result.ok)
|
|
1075
|
-
handlers.ok(result.values);
|
|
1076
|
-
} catch (e) {
|
|
1077
|
-
const error = createError(e);
|
|
1078
|
-
if (handlers.err && (!result.errors || !result.errors.includes(error)))
|
|
1079
|
-
handlers.err(result.errors ? [...result.errors, error] : [error]);
|
|
1080
|
-
else
|
|
1081
|
-
throw error;
|
|
1082
|
-
}
|
|
1083
|
-
}
|
|
1084
|
-
// src/resolve.ts
|
|
1085
|
-
function resolve(signals) {
|
|
1086
|
-
const errors = [];
|
|
1087
|
-
let pending = false;
|
|
1088
|
-
const values = {};
|
|
1089
|
-
for (const [key, signal] of Object.entries(signals)) {
|
|
1090
|
-
try {
|
|
1091
|
-
const value = signal.get();
|
|
1092
|
-
if (value === UNSET)
|
|
1093
|
-
pending = true;
|
|
1094
|
-
else
|
|
1095
|
-
values[key] = value;
|
|
1096
|
-
} catch (e) {
|
|
1097
|
-
errors.push(createError(e));
|
|
1098
|
-
}
|
|
1099
|
-
}
|
|
1100
|
-
if (pending)
|
|
1101
|
-
return { ok: false, pending: true };
|
|
1102
|
-
if (errors.length > 0)
|
|
1103
|
-
return { ok: false, errors };
|
|
1104
|
-
return { ok: true, values };
|
|
1507
|
+
function isMutableSignal(value) {
|
|
1508
|
+
return isState(value) || isStore(value) || isList(value);
|
|
1105
1509
|
}
|
|
1106
1510
|
export {
|
|
1107
1511
|
valueString,
|
|
1108
|
-
validateSignalValue,
|
|
1109
|
-
validateCallback,
|
|
1110
1512
|
untrack,
|
|
1111
|
-
track,
|
|
1112
|
-
subscribeTo,
|
|
1113
|
-
resolve,
|
|
1114
|
-
notifyOf,
|
|
1115
1513
|
match,
|
|
1116
|
-
|
|
1117
|
-
isSymbol,
|
|
1118
|
-
isString,
|
|
1514
|
+
isTask,
|
|
1119
1515
|
isStore,
|
|
1120
1516
|
isState,
|
|
1121
1517
|
isSignal,
|
|
1122
|
-
|
|
1123
|
-
isRecordOrArray,
|
|
1518
|
+
isSensor,
|
|
1124
1519
|
isRecord,
|
|
1125
1520
|
isObjectOfType,
|
|
1126
|
-
isNumber,
|
|
1127
1521
|
isMutableSignal,
|
|
1128
|
-
|
|
1522
|
+
isMemo,
|
|
1129
1523
|
isList,
|
|
1130
1524
|
isFunction,
|
|
1131
1525
|
isEqual,
|
|
1132
1526
|
isComputed,
|
|
1133
1527
|
isCollection,
|
|
1134
1528
|
isAsyncFunction,
|
|
1135
|
-
|
|
1136
|
-
guardMutableSignal,
|
|
1137
|
-
flush,
|
|
1138
|
-
diff,
|
|
1139
|
-
createWatcher,
|
|
1529
|
+
createTask,
|
|
1140
1530
|
createStore,
|
|
1531
|
+
createState,
|
|
1141
1532
|
createSignal,
|
|
1142
|
-
|
|
1533
|
+
createSensor,
|
|
1534
|
+
createScope,
|
|
1535
|
+
createMutableSignal,
|
|
1536
|
+
createMemo,
|
|
1537
|
+
createList,
|
|
1143
1538
|
createEffect,
|
|
1144
1539
|
createComputed,
|
|
1540
|
+
createCollection,
|
|
1145
1541
|
batch,
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
TYPE_STATE,
|
|
1150
|
-
TYPE_REF,
|
|
1151
|
-
TYPE_LIST,
|
|
1152
|
-
TYPE_COMPUTED,
|
|
1153
|
-
TYPE_COLLECTION,
|
|
1154
|
-
State,
|
|
1155
|
-
Ref,
|
|
1156
|
-
ReadonlySignalError,
|
|
1542
|
+
UnsetSignalValueError,
|
|
1543
|
+
SKIP_EQUALITY,
|
|
1544
|
+
RequiredOwnerError,
|
|
1157
1545
|
NullishSignalValueError,
|
|
1158
|
-
Memo,
|
|
1159
|
-
List,
|
|
1160
1546
|
InvalidSignalValueError,
|
|
1161
|
-
InvalidCollectionSourceError,
|
|
1162
1547
|
InvalidCallbackError,
|
|
1163
|
-
|
|
1164
|
-
DerivedCollection,
|
|
1165
|
-
CircularDependencyError,
|
|
1166
|
-
BaseStore
|
|
1548
|
+
CircularDependencyError
|
|
1167
1549
|
};
|