@zeix/cause-effect 0.17.3 → 0.18.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/.ai-context.md +163 -232
- package/.cursorrules +41 -35
- package/.github/copilot-instructions.md +166 -116
- package/ARCHITECTURE.md +274 -0
- package/CLAUDE.md +199 -143
- package/COLLECTION_REFACTORING.md +161 -0
- package/GUIDE.md +298 -0
- package/README.md +232 -197
- package/REQUIREMENTS.md +100 -0
- package/bench/reactivity.bench.ts +577 -0
- package/index.dev.js +1325 -997
- package/index.js +1 -1
- package/index.ts +58 -74
- package/package.json +4 -1
- package/src/errors.ts +118 -74
- package/src/graph.ts +601 -0
- package/src/nodes/collection.ts +474 -0
- package/src/nodes/effect.ts +149 -0
- package/src/nodes/list.ts +588 -0
- package/src/nodes/memo.ts +120 -0
- package/src/nodes/sensor.ts +139 -0
- package/src/nodes/state.ts +135 -0
- package/src/nodes/store.ts +383 -0
- package/src/nodes/task.ts +146 -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 +466 -706
- package/test/effect.test.ts +293 -696
- package/test/list.test.ts +335 -592
- package/test/memo.test.ts +380 -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 +395 -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 +208 -0
- package/types/src/nodes/collection.d.ts +64 -0
- package/types/src/nodes/effect.d.ts +48 -0
- package/types/src/nodes/list.d.ts +65 -0
- package/types/src/nodes/memo.d.ts +57 -0
- package/types/src/nodes/sensor.d.ts +75 -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 +73 -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,434 @@
|
|
|
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 keysEqual(a, b) {
|
|
419
|
+
if (a.length !== b.length)
|
|
420
|
+
return false;
|
|
421
|
+
for (let i = 0;i < a.length; i++)
|
|
422
|
+
if (a[i] !== b[i])
|
|
423
|
+
return false;
|
|
424
|
+
return true;
|
|
425
|
+
}
|
|
426
|
+
function isEqual(a, b, visited) {
|
|
136
427
|
if (Object.is(a, b))
|
|
137
428
|
return true;
|
|
138
429
|
if (typeof a !== typeof b)
|
|
139
430
|
return false;
|
|
140
|
-
if (
|
|
431
|
+
if (a == null || typeof a !== "object" || b == null || typeof b !== "object")
|
|
141
432
|
return false;
|
|
142
433
|
if (!visited)
|
|
143
434
|
visited = new WeakSet;
|
|
@@ -146,17 +437,20 @@ var isEqual = (a, b, visited) => {
|
|
|
146
437
|
visited.add(a);
|
|
147
438
|
visited.add(b);
|
|
148
439
|
try {
|
|
149
|
-
|
|
150
|
-
|
|
440
|
+
const aIsArray = Array.isArray(a);
|
|
441
|
+
if (aIsArray !== Array.isArray(b))
|
|
442
|
+
return false;
|
|
443
|
+
if (aIsArray) {
|
|
444
|
+
const aa = a;
|
|
445
|
+
const ba = b;
|
|
446
|
+
if (aa.length !== ba.length)
|
|
151
447
|
return false;
|
|
152
|
-
for (let i = 0;i <
|
|
153
|
-
if (!isEqual(
|
|
448
|
+
for (let i = 0;i < aa.length; i++) {
|
|
449
|
+
if (!isEqual(aa[i], ba[i], visited))
|
|
154
450
|
return false;
|
|
155
451
|
}
|
|
156
452
|
return true;
|
|
157
453
|
}
|
|
158
|
-
if (Array.isArray(a) !== Array.isArray(b))
|
|
159
|
-
return false;
|
|
160
454
|
if (isRecord(a) && isRecord(b)) {
|
|
161
455
|
const aKeys = Object.keys(a);
|
|
162
456
|
const bKeys = Object.keys(b);
|
|
@@ -175,573 +469,913 @@ var isEqual = (a, b, visited) => {
|
|
|
175
469
|
visited.delete(a);
|
|
176
470
|
visited.delete(b);
|
|
177
471
|
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
const oldValid = isRecordOrArray(oldObj);
|
|
181
|
-
const newValid = isRecordOrArray(newObj);
|
|
182
|
-
if (!oldValid || !newValid) {
|
|
183
|
-
const changed = !Object.is(oldObj, newObj);
|
|
184
|
-
return {
|
|
185
|
-
changed,
|
|
186
|
-
add: changed && newValid ? newObj : {},
|
|
187
|
-
change: {},
|
|
188
|
-
remove: changed && oldValid ? oldObj : {}
|
|
189
|
-
};
|
|
190
|
-
}
|
|
472
|
+
}
|
|
473
|
+
function diffArrays(oldArray, newArray, currentKeys, generateKey, contentBased) {
|
|
191
474
|
const visited = new WeakSet;
|
|
192
475
|
const add = {};
|
|
193
476
|
const change = {};
|
|
194
477
|
const remove = {};
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
const
|
|
198
|
-
for (
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
478
|
+
const newKeys = [];
|
|
479
|
+
let changed = false;
|
|
480
|
+
const oldByKey = new Map;
|
|
481
|
+
for (let i = 0;i < oldArray.length; i++) {
|
|
482
|
+
const key = currentKeys[i];
|
|
483
|
+
if (key && oldArray[i])
|
|
484
|
+
oldByKey.set(key, oldArray[i]);
|
|
485
|
+
}
|
|
486
|
+
const seenKeys = new Set;
|
|
487
|
+
for (let i = 0;i < newArray.length; i++) {
|
|
488
|
+
const newValue = newArray[i];
|
|
489
|
+
if (newValue === undefined)
|
|
203
490
|
continue;
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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;
|
|
491
|
+
const key = contentBased ? generateKey(newValue) : currentKeys[i] ?? generateKey(newValue);
|
|
492
|
+
if (seenKeys.has(key))
|
|
493
|
+
throw new DuplicateKeyError(TYPE_LIST, key, newValue);
|
|
494
|
+
newKeys.push(key);
|
|
495
|
+
seenKeys.add(key);
|
|
496
|
+
if (!oldByKey.has(key)) {
|
|
497
|
+
add[key] = newValue;
|
|
498
|
+
changed = true;
|
|
499
|
+
} else {
|
|
500
|
+
const oldValue = oldByKey.get(key);
|
|
501
|
+
if (!isEqual(oldValue, newValue, visited)) {
|
|
502
|
+
change[key] = newValue;
|
|
503
|
+
changed = true;
|
|
257
504
|
}
|
|
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
|
-
}
|
|
305
|
-
#getWatcher() {
|
|
306
|
-
if (!this.#watcher) {
|
|
307
|
-
const ok = (v) => {
|
|
308
|
-
if (!isEqual(v, this.#value)) {
|
|
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
|
-
});
|
|
377
505
|
}
|
|
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
506
|
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
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));
|
|
507
|
+
for (const [key] of oldByKey) {
|
|
508
|
+
if (!seenKeys.has(key)) {
|
|
509
|
+
remove[key] = null;
|
|
510
|
+
changed = true;
|
|
511
|
+
}
|
|
428
512
|
}
|
|
513
|
+
if (!changed && !keysEqual(currentKeys, newKeys))
|
|
514
|
+
changed = true;
|
|
515
|
+
return { add, change, remove, newKeys, changed };
|
|
429
516
|
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
change: {},
|
|
452
|
-
remove: {},
|
|
453
|
-
changed: true
|
|
454
|
-
});
|
|
455
|
-
if (options?.watched)
|
|
456
|
-
registerWatchCallbacks(this, options.watched, options.unwatched);
|
|
457
|
-
}
|
|
458
|
-
#toRecord(array) {
|
|
517
|
+
function createList(initialValue, options) {
|
|
518
|
+
validateSignalValue(TYPE_LIST, initialValue, Array.isArray);
|
|
519
|
+
const signals = new Map;
|
|
520
|
+
let keys = [];
|
|
521
|
+
let keyCounter = 0;
|
|
522
|
+
const keyConfig = options?.keyConfig;
|
|
523
|
+
const contentBased = isFunction(keyConfig);
|
|
524
|
+
const generateKey = typeof keyConfig === "string" ? () => `${keyConfig}${keyCounter++}` : contentBased ? (item) => keyConfig(item) : () => String(keyCounter++);
|
|
525
|
+
const buildValue = () => keys.map((key) => signals.get(key)?.get()).filter((v) => v !== undefined);
|
|
526
|
+
const node = {
|
|
527
|
+
fn: buildValue,
|
|
528
|
+
value: initialValue,
|
|
529
|
+
flags: FLAG_DIRTY,
|
|
530
|
+
sources: null,
|
|
531
|
+
sourcesTail: null,
|
|
532
|
+
sinks: null,
|
|
533
|
+
sinksTail: null,
|
|
534
|
+
equals: isEqual,
|
|
535
|
+
error: undefined
|
|
536
|
+
};
|
|
537
|
+
const toRecord = (array) => {
|
|
459
538
|
const record = {};
|
|
460
539
|
for (let i = 0;i < array.length; i++) {
|
|
461
540
|
const value = array[i];
|
|
462
541
|
if (value === undefined)
|
|
463
542
|
continue;
|
|
464
|
-
let key =
|
|
543
|
+
let key = keys[i];
|
|
465
544
|
if (!key) {
|
|
466
|
-
key =
|
|
467
|
-
|
|
545
|
+
key = generateKey(value);
|
|
546
|
+
keys[i] = key;
|
|
468
547
|
}
|
|
469
548
|
record[key] = value;
|
|
470
549
|
}
|
|
471
550
|
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]);
|
|
551
|
+
};
|
|
552
|
+
const applyChanges = (changes) => {
|
|
553
|
+
let structural = false;
|
|
554
|
+
for (const key in changes.add) {
|
|
555
|
+
const value = changes.add[key];
|
|
556
|
+
validateSignalValue(`${TYPE_LIST} item for key "${key}"`, value);
|
|
557
|
+
signals.set(key, createState(value));
|
|
558
|
+
structural = true;
|
|
483
559
|
}
|
|
484
560
|
if (Object.keys(changes.change).length) {
|
|
485
561
|
batch(() => {
|
|
486
562
|
for (const key in changes.change) {
|
|
487
563
|
const value = changes.change[key];
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
if (guardMutableSignal(`${TYPE_LIST} item "${key}"`, value, signal))
|
|
564
|
+
validateSignalValue(`${TYPE_LIST} item for key "${key}"`, value);
|
|
565
|
+
const signal = signals.get(key);
|
|
566
|
+
if (signal)
|
|
492
567
|
signal.set(value);
|
|
493
568
|
}
|
|
494
569
|
});
|
|
495
570
|
}
|
|
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;
|
|
537
|
-
}
|
|
538
|
-
const changes = diff(this.#toRecord(this.#value), this.#toRecord(newValue));
|
|
539
|
-
if (this.#change(changes))
|
|
540
|
-
notifyOf(this);
|
|
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);
|
|
571
|
+
for (const key in changes.remove) {
|
|
572
|
+
signals.delete(key);
|
|
573
|
+
const index = keys.indexOf(key);
|
|
574
|
+
if (index !== -1)
|
|
575
|
+
keys.splice(index, 1);
|
|
576
|
+
structural = true;
|
|
581
577
|
}
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
const newOrder = entries.map(([key]) => key);
|
|
586
|
-
if (!isEqual(this.#keys, newOrder)) {
|
|
587
|
-
this.#keys = newOrder;
|
|
588
|
-
notifyOf(this);
|
|
578
|
+
if (structural) {
|
|
579
|
+
node.sources = null;
|
|
580
|
+
node.sourcesTail = null;
|
|
589
581
|
}
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
const
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
582
|
+
return changes.changed;
|
|
583
|
+
};
|
|
584
|
+
const initRecord = toRecord(initialValue);
|
|
585
|
+
for (const key in initRecord) {
|
|
586
|
+
const value = initRecord[key];
|
|
587
|
+
validateSignalValue(`${TYPE_LIST} item for key "${key}"`, value);
|
|
588
|
+
signals.set(key, createState(value));
|
|
589
|
+
}
|
|
590
|
+
node.value = initialValue;
|
|
591
|
+
node.flags = 0;
|
|
592
|
+
const list = {
|
|
593
|
+
[Symbol.toStringTag]: TYPE_LIST,
|
|
594
|
+
[Symbol.isConcatSpreadable]: true,
|
|
595
|
+
*[Symbol.iterator]() {
|
|
596
|
+
for (const key of keys) {
|
|
597
|
+
const signal = signals.get(key);
|
|
602
598
|
if (signal)
|
|
603
|
-
|
|
599
|
+
yield signal;
|
|
604
600
|
}
|
|
605
|
-
}
|
|
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
|
-
|
|
601
|
+
},
|
|
602
|
+
get length() {
|
|
603
|
+
if (activeSink) {
|
|
604
|
+
if (!node.sinks && options?.watched)
|
|
605
|
+
node.stop = options.watched();
|
|
606
|
+
link(node, activeSink);
|
|
607
|
+
}
|
|
608
|
+
return keys.length;
|
|
609
|
+
},
|
|
610
|
+
get() {
|
|
611
|
+
if (activeSink) {
|
|
612
|
+
if (!node.sinks && options?.watched)
|
|
613
|
+
node.stop = options.watched();
|
|
614
|
+
link(node, activeSink);
|
|
615
|
+
}
|
|
616
|
+
if (node.sources) {
|
|
617
|
+
if (node.flags) {
|
|
618
|
+
node.value = untrack(buildValue);
|
|
619
|
+
node.flags = FLAG_CLEAN;
|
|
620
|
+
}
|
|
621
|
+
} else {
|
|
622
|
+
refresh(node);
|
|
623
|
+
if (node.error)
|
|
624
|
+
throw node.error;
|
|
625
|
+
}
|
|
626
|
+
return node.value;
|
|
627
|
+
},
|
|
628
|
+
set(newValue) {
|
|
629
|
+
const currentValue = node.flags & FLAG_DIRTY ? buildValue() : node.value;
|
|
630
|
+
const changes = diffArrays(currentValue, newValue, keys, generateKey, contentBased);
|
|
631
|
+
if (changes.changed) {
|
|
632
|
+
keys = changes.newKeys;
|
|
633
|
+
applyChanges(changes);
|
|
634
|
+
propagate(node);
|
|
635
|
+
node.flags |= FLAG_DIRTY;
|
|
636
|
+
if (batchDepth === 0)
|
|
637
|
+
flush();
|
|
638
|
+
}
|
|
639
|
+
},
|
|
640
|
+
update(fn) {
|
|
641
|
+
list.set(fn(list.get()));
|
|
642
|
+
},
|
|
643
|
+
at(index) {
|
|
644
|
+
return signals.get(keys[index]);
|
|
645
|
+
},
|
|
646
|
+
keys() {
|
|
647
|
+
if (activeSink) {
|
|
648
|
+
if (!node.sinks && options?.watched)
|
|
649
|
+
node.stop = options.watched();
|
|
650
|
+
link(node, activeSink);
|
|
651
|
+
}
|
|
652
|
+
return keys.values();
|
|
653
|
+
},
|
|
654
|
+
byKey(key) {
|
|
655
|
+
return signals.get(key);
|
|
656
|
+
},
|
|
657
|
+
keyAt(index) {
|
|
658
|
+
return keys[index];
|
|
659
|
+
},
|
|
660
|
+
indexOfKey(key) {
|
|
661
|
+
return keys.indexOf(key);
|
|
662
|
+
},
|
|
663
|
+
add(value) {
|
|
664
|
+
const key = generateKey(value);
|
|
665
|
+
if (signals.has(key))
|
|
666
|
+
throw new DuplicateKeyError(TYPE_LIST, key, value);
|
|
667
|
+
if (!keys.includes(key))
|
|
668
|
+
keys.push(key);
|
|
669
|
+
validateSignalValue(`${TYPE_LIST} item for key "${key}"`, value);
|
|
670
|
+
signals.set(key, createState(value));
|
|
671
|
+
node.sources = null;
|
|
672
|
+
node.sourcesTail = null;
|
|
673
|
+
propagate(node);
|
|
674
|
+
node.flags |= FLAG_DIRTY;
|
|
675
|
+
if (batchDepth === 0)
|
|
676
|
+
flush();
|
|
677
|
+
return key;
|
|
678
|
+
},
|
|
679
|
+
remove(keyOrIndex) {
|
|
680
|
+
const key = typeof keyOrIndex === "number" ? keys[keyOrIndex] : keyOrIndex;
|
|
681
|
+
const ok = signals.delete(key);
|
|
682
|
+
if (ok) {
|
|
683
|
+
const index = typeof keyOrIndex === "number" ? keyOrIndex : keys.indexOf(key);
|
|
684
|
+
if (index >= 0)
|
|
685
|
+
keys.splice(index, 1);
|
|
686
|
+
node.sources = null;
|
|
687
|
+
node.sourcesTail = null;
|
|
688
|
+
propagate(node);
|
|
689
|
+
node.flags |= FLAG_DIRTY;
|
|
690
|
+
if (batchDepth === 0)
|
|
691
|
+
flush();
|
|
692
|
+
}
|
|
693
|
+
},
|
|
694
|
+
sort(compareFn) {
|
|
695
|
+
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])));
|
|
696
|
+
const newOrder = entries.map(([key]) => key);
|
|
697
|
+
if (!keysEqual(keys, newOrder)) {
|
|
698
|
+
keys = newOrder;
|
|
699
|
+
propagate(node);
|
|
700
|
+
node.flags |= FLAG_DIRTY;
|
|
701
|
+
if (batchDepth === 0)
|
|
702
|
+
flush();
|
|
703
|
+
}
|
|
704
|
+
},
|
|
705
|
+
splice(start, deleteCount, ...items) {
|
|
706
|
+
const length = keys.length;
|
|
707
|
+
const actualStart = start < 0 ? Math.max(0, length + start) : Math.min(start, length);
|
|
708
|
+
const actualDeleteCount = Math.max(0, Math.min(deleteCount ?? Math.max(0, length - Math.max(0, actualStart)), length - actualStart));
|
|
709
|
+
const add = {};
|
|
710
|
+
const remove = {};
|
|
711
|
+
for (let i = 0;i < actualDeleteCount; i++) {
|
|
712
|
+
const index = actualStart + i;
|
|
713
|
+
const key = keys[index];
|
|
714
|
+
if (key) {
|
|
715
|
+
const signal = signals.get(key);
|
|
716
|
+
if (signal)
|
|
717
|
+
remove[key] = signal.get();
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
const newOrder = keys.slice(0, actualStart);
|
|
721
|
+
for (const item of items) {
|
|
722
|
+
const key = generateKey(item);
|
|
723
|
+
if (signals.has(key) && !(key in remove))
|
|
724
|
+
throw new DuplicateKeyError(TYPE_LIST, key, item);
|
|
725
|
+
newOrder.push(key);
|
|
726
|
+
add[key] = item;
|
|
727
|
+
}
|
|
728
|
+
newOrder.push(...keys.slice(actualStart + actualDeleteCount));
|
|
729
|
+
const changed = !!(Object.keys(add).length || Object.keys(remove).length);
|
|
730
|
+
if (changed) {
|
|
731
|
+
applyChanges({
|
|
732
|
+
add,
|
|
733
|
+
change: {},
|
|
734
|
+
remove,
|
|
735
|
+
changed
|
|
736
|
+
});
|
|
737
|
+
keys = newOrder;
|
|
738
|
+
propagate(node);
|
|
739
|
+
node.flags |= FLAG_DIRTY;
|
|
740
|
+
if (batchDepth === 0)
|
|
741
|
+
flush();
|
|
742
|
+
}
|
|
743
|
+
return Object.values(remove);
|
|
744
|
+
},
|
|
745
|
+
deriveCollection(cb) {
|
|
746
|
+
return deriveCollection(list, cb);
|
|
747
|
+
}
|
|
748
|
+
};
|
|
749
|
+
return list;
|
|
750
|
+
}
|
|
751
|
+
function isList(value) {
|
|
752
|
+
return isObjectOfType(value, TYPE_LIST);
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// src/nodes/memo.ts
|
|
756
|
+
function createMemo(fn, options) {
|
|
757
|
+
validateCallback(TYPE_MEMO, fn, isSyncFunction);
|
|
758
|
+
if (options?.value !== undefined)
|
|
759
|
+
validateSignalValue(TYPE_MEMO, options.value, options?.guard);
|
|
760
|
+
const node = {
|
|
761
|
+
fn,
|
|
762
|
+
value: options?.value,
|
|
763
|
+
flags: FLAG_DIRTY,
|
|
764
|
+
sources: null,
|
|
765
|
+
sourcesTail: null,
|
|
766
|
+
sinks: null,
|
|
767
|
+
sinksTail: null,
|
|
768
|
+
equals: options?.equals ?? DEFAULT_EQUALITY,
|
|
769
|
+
error: undefined
|
|
770
|
+
};
|
|
771
|
+
return {
|
|
772
|
+
[Symbol.toStringTag]: TYPE_MEMO,
|
|
773
|
+
get() {
|
|
774
|
+
if (activeSink)
|
|
775
|
+
link(node, activeSink);
|
|
776
|
+
refresh(node);
|
|
777
|
+
if (node.error)
|
|
778
|
+
throw node.error;
|
|
779
|
+
validateReadValue(TYPE_MEMO, node.value);
|
|
780
|
+
return node.value;
|
|
781
|
+
}
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
function isMemo(value) {
|
|
785
|
+
return isObjectOfType(value, TYPE_MEMO);
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// src/nodes/task.ts
|
|
789
|
+
function createTask(fn, options) {
|
|
790
|
+
validateCallback(TYPE_TASK, fn, isAsyncFunction);
|
|
791
|
+
if (options?.value !== undefined)
|
|
792
|
+
validateSignalValue(TYPE_TASK, options.value, options?.guard);
|
|
793
|
+
const node = {
|
|
794
|
+
fn,
|
|
795
|
+
value: options?.value,
|
|
796
|
+
sources: null,
|
|
797
|
+
sourcesTail: null,
|
|
798
|
+
sinks: null,
|
|
799
|
+
sinksTail: null,
|
|
800
|
+
flags: FLAG_DIRTY,
|
|
801
|
+
equals: options?.equals ?? DEFAULT_EQUALITY,
|
|
802
|
+
controller: undefined,
|
|
803
|
+
error: undefined
|
|
804
|
+
};
|
|
805
|
+
return {
|
|
806
|
+
[Symbol.toStringTag]: TYPE_TASK,
|
|
807
|
+
get() {
|
|
808
|
+
if (activeSink)
|
|
809
|
+
link(node, activeSink);
|
|
810
|
+
refresh(node);
|
|
811
|
+
if (node.error)
|
|
812
|
+
throw node.error;
|
|
813
|
+
validateReadValue(TYPE_TASK, node.value);
|
|
814
|
+
return node.value;
|
|
815
|
+
},
|
|
816
|
+
isPending() {
|
|
817
|
+
return !!node.controller;
|
|
818
|
+
},
|
|
819
|
+
abort() {
|
|
820
|
+
node.controller?.abort();
|
|
821
|
+
node.controller = undefined;
|
|
822
|
+
}
|
|
823
|
+
};
|
|
824
|
+
}
|
|
825
|
+
function isTask(value) {
|
|
826
|
+
return isObjectOfType(value, TYPE_TASK);
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// src/nodes/collection.ts
|
|
830
|
+
function deriveCollection(source, callback) {
|
|
831
|
+
validateCallback(TYPE_COLLECTION, callback);
|
|
832
|
+
if (!isCollectionSource(source))
|
|
833
|
+
throw new TypeError(`[${TYPE_COLLECTION}] Invalid collection source: expected a List or Collection`);
|
|
834
|
+
const isAsync = isAsyncFunction(callback);
|
|
835
|
+
const signals = new Map;
|
|
836
|
+
const addSignal = (key) => {
|
|
837
|
+
const signal = isAsync ? createTask(async (prev, abort) => {
|
|
838
|
+
const sourceValue = source.byKey(key)?.get();
|
|
839
|
+
if (sourceValue == null)
|
|
840
|
+
return prev;
|
|
841
|
+
return callback(sourceValue, abort);
|
|
842
|
+
}) : createMemo(() => {
|
|
843
|
+
const sourceValue = source.byKey(key)?.get();
|
|
844
|
+
if (sourceValue == null)
|
|
845
|
+
return;
|
|
846
|
+
return callback(sourceValue);
|
|
847
|
+
});
|
|
848
|
+
signals.set(key, signal);
|
|
849
|
+
};
|
|
850
|
+
function syncKeys() {
|
|
851
|
+
const newKeys = Array.from(source.keys());
|
|
852
|
+
const oldKeys = node.value;
|
|
853
|
+
if (!keysEqual(oldKeys, newKeys)) {
|
|
854
|
+
const oldKeySet = new Set(oldKeys);
|
|
855
|
+
const newKeySet = new Set(newKeys);
|
|
856
|
+
for (const key of oldKeys)
|
|
857
|
+
if (!newKeySet.has(key))
|
|
858
|
+
signals.delete(key);
|
|
859
|
+
for (const key of newKeys)
|
|
860
|
+
if (!oldKeySet.has(key))
|
|
861
|
+
addSignal(key);
|
|
862
|
+
}
|
|
863
|
+
return newKeys;
|
|
864
|
+
}
|
|
865
|
+
const node = {
|
|
866
|
+
fn: syncKeys,
|
|
867
|
+
value: [],
|
|
868
|
+
flags: FLAG_DIRTY,
|
|
869
|
+
sources: null,
|
|
870
|
+
sourcesTail: null,
|
|
871
|
+
sinks: null,
|
|
872
|
+
sinksTail: null,
|
|
873
|
+
equals: keysEqual,
|
|
874
|
+
error: undefined
|
|
875
|
+
};
|
|
876
|
+
function ensureSynced() {
|
|
877
|
+
if (node.sources) {
|
|
878
|
+
if (node.flags) {
|
|
879
|
+
node.value = untrack(syncKeys);
|
|
880
|
+
node.flags = FLAG_CLEAN;
|
|
881
|
+
}
|
|
882
|
+
} else {
|
|
883
|
+
refresh(node);
|
|
884
|
+
if (node.error)
|
|
885
|
+
throw node.error;
|
|
886
|
+
}
|
|
887
|
+
return node.value;
|
|
888
|
+
}
|
|
889
|
+
const initialKeys = Array.from(source.keys());
|
|
890
|
+
for (const key of initialKeys)
|
|
891
|
+
addSignal(key);
|
|
892
|
+
node.value = initialKeys;
|
|
893
|
+
const collection = {
|
|
894
|
+
[Symbol.toStringTag]: TYPE_COLLECTION,
|
|
895
|
+
[Symbol.isConcatSpreadable]: true,
|
|
896
|
+
*[Symbol.iterator]() {
|
|
897
|
+
for (const key of node.value) {
|
|
898
|
+
const signal = signals.get(key);
|
|
899
|
+
if (signal)
|
|
900
|
+
yield signal;
|
|
901
|
+
}
|
|
902
|
+
},
|
|
903
|
+
get length() {
|
|
904
|
+
if (activeSink)
|
|
905
|
+
link(node, activeSink);
|
|
906
|
+
return ensureSynced().length;
|
|
907
|
+
},
|
|
908
|
+
keys() {
|
|
909
|
+
if (activeSink)
|
|
910
|
+
link(node, activeSink);
|
|
911
|
+
return ensureSynced().values();
|
|
912
|
+
},
|
|
913
|
+
get() {
|
|
914
|
+
if (activeSink)
|
|
915
|
+
link(node, activeSink);
|
|
916
|
+
const currentKeys = ensureSynced();
|
|
917
|
+
const result = [];
|
|
918
|
+
for (const key of currentKeys) {
|
|
919
|
+
try {
|
|
920
|
+
const v = signals.get(key)?.get();
|
|
921
|
+
if (v != null)
|
|
922
|
+
result.push(v);
|
|
923
|
+
} catch (e) {
|
|
924
|
+
if (!(e instanceof UnsetSignalValueError))
|
|
925
|
+
throw e;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
return result;
|
|
929
|
+
},
|
|
930
|
+
at(index) {
|
|
931
|
+
return signals.get(node.value[index]);
|
|
932
|
+
},
|
|
933
|
+
byKey(key) {
|
|
934
|
+
return signals.get(key);
|
|
935
|
+
},
|
|
936
|
+
keyAt(index) {
|
|
937
|
+
return node.value[index];
|
|
938
|
+
},
|
|
939
|
+
indexOfKey(key) {
|
|
940
|
+
return node.value.indexOf(key);
|
|
941
|
+
},
|
|
942
|
+
deriveCollection(cb) {
|
|
943
|
+
return deriveCollection(collection, cb);
|
|
944
|
+
}
|
|
945
|
+
};
|
|
946
|
+
return collection;
|
|
947
|
+
}
|
|
948
|
+
function createCollection(start, options) {
|
|
949
|
+
const initialValue = options?.value ?? [];
|
|
950
|
+
if (initialValue.length)
|
|
951
|
+
validateSignalValue(TYPE_COLLECTION, initialValue, Array.isArray);
|
|
952
|
+
validateCallback(TYPE_COLLECTION, start);
|
|
953
|
+
const signals = new Map;
|
|
954
|
+
const keys = [];
|
|
955
|
+
let keyCounter = 0;
|
|
956
|
+
const keyConfig = options?.keyConfig;
|
|
957
|
+
const generateKey = typeof keyConfig === "string" ? () => `${keyConfig}${keyCounter++}` : isFunction(keyConfig) ? (item) => keyConfig(item) : () => String(keyCounter++);
|
|
958
|
+
const itemFactory = options?.createItem ?? ((_key, value) => createState(value));
|
|
959
|
+
function buildValue() {
|
|
960
|
+
const result = [];
|
|
961
|
+
for (const key of keys) {
|
|
962
|
+
try {
|
|
963
|
+
const v = signals.get(key)?.get();
|
|
964
|
+
if (v != null)
|
|
965
|
+
result.push(v);
|
|
966
|
+
} catch (e) {
|
|
967
|
+
if (!(e instanceof UnsetSignalValueError))
|
|
968
|
+
throw e;
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
return result;
|
|
972
|
+
}
|
|
973
|
+
const node = {
|
|
974
|
+
fn: buildValue,
|
|
975
|
+
value: initialValue,
|
|
976
|
+
flags: FLAG_DIRTY,
|
|
977
|
+
sources: null,
|
|
978
|
+
sourcesTail: null,
|
|
979
|
+
sinks: null,
|
|
980
|
+
sinksTail: null,
|
|
981
|
+
equals: () => false,
|
|
982
|
+
error: undefined
|
|
983
|
+
};
|
|
984
|
+
function applyChanges(changes) {
|
|
985
|
+
if (!changes.changed)
|
|
986
|
+
return;
|
|
987
|
+
let structural = false;
|
|
988
|
+
batch(() => {
|
|
989
|
+
for (const key in changes.add) {
|
|
990
|
+
const value = changes.add[key];
|
|
991
|
+
signals.set(key, itemFactory(key, value));
|
|
992
|
+
if (!keys.includes(key))
|
|
993
|
+
keys.push(key);
|
|
994
|
+
structural = true;
|
|
995
|
+
}
|
|
996
|
+
for (const key in changes.change) {
|
|
997
|
+
const signal = signals.get(key);
|
|
998
|
+
if (signal && isState(signal)) {
|
|
999
|
+
signal.set(changes.change[key]);
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
for (const key in changes.remove) {
|
|
1003
|
+
signals.delete(key);
|
|
1004
|
+
const index = keys.indexOf(key);
|
|
1005
|
+
if (index !== -1)
|
|
1006
|
+
keys.splice(index, 1);
|
|
1007
|
+
structural = true;
|
|
1008
|
+
}
|
|
1009
|
+
if (structural) {
|
|
1010
|
+
node.sources = null;
|
|
1011
|
+
node.sourcesTail = null;
|
|
1012
|
+
}
|
|
1013
|
+
node.flags = FLAG_CLEAN;
|
|
1014
|
+
propagate(node);
|
|
1015
|
+
node.flags |= FLAG_DIRTY;
|
|
1016
|
+
});
|
|
653
1017
|
}
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
1018
|
+
for (const item of initialValue) {
|
|
1019
|
+
const key = generateKey(item);
|
|
1020
|
+
signals.set(key, itemFactory(key, item));
|
|
1021
|
+
keys.push(key);
|
|
1022
|
+
}
|
|
1023
|
+
node.value = initialValue;
|
|
1024
|
+
node.flags = FLAG_DIRTY;
|
|
1025
|
+
function startWatching() {
|
|
1026
|
+
if (!node.sinks)
|
|
1027
|
+
node.stop = start(applyChanges);
|
|
1028
|
+
}
|
|
1029
|
+
const collection = {
|
|
1030
|
+
[Symbol.toStringTag]: TYPE_COLLECTION,
|
|
1031
|
+
[Symbol.isConcatSpreadable]: true,
|
|
1032
|
+
*[Symbol.iterator]() {
|
|
1033
|
+
for (const key of keys) {
|
|
1034
|
+
const signal = signals.get(key);
|
|
1035
|
+
if (signal)
|
|
1036
|
+
yield signal;
|
|
1037
|
+
}
|
|
1038
|
+
},
|
|
1039
|
+
get length() {
|
|
1040
|
+
if (activeSink) {
|
|
1041
|
+
startWatching();
|
|
1042
|
+
link(node, activeSink);
|
|
1043
|
+
}
|
|
1044
|
+
return keys.length;
|
|
1045
|
+
},
|
|
1046
|
+
keys() {
|
|
1047
|
+
if (activeSink) {
|
|
1048
|
+
startWatching();
|
|
1049
|
+
link(node, activeSink);
|
|
1050
|
+
}
|
|
1051
|
+
return keys.values();
|
|
1052
|
+
},
|
|
1053
|
+
get() {
|
|
1054
|
+
if (activeSink) {
|
|
1055
|
+
startWatching();
|
|
1056
|
+
link(node, activeSink);
|
|
1057
|
+
}
|
|
1058
|
+
if (node.sources) {
|
|
1059
|
+
if (node.flags) {
|
|
1060
|
+
node.value = untrack(buildValue);
|
|
1061
|
+
node.flags = FLAG_CLEAN;
|
|
1062
|
+
}
|
|
1063
|
+
} else {
|
|
1064
|
+
refresh(node);
|
|
1065
|
+
if (node.error)
|
|
1066
|
+
throw node.error;
|
|
1067
|
+
}
|
|
1068
|
+
return node.value;
|
|
1069
|
+
},
|
|
1070
|
+
at(index) {
|
|
1071
|
+
return signals.get(keys[index]);
|
|
1072
|
+
},
|
|
1073
|
+
byKey(key) {
|
|
1074
|
+
return signals.get(key);
|
|
1075
|
+
},
|
|
1076
|
+
keyAt(index) {
|
|
1077
|
+
return keys[index];
|
|
1078
|
+
},
|
|
1079
|
+
indexOfKey(key) {
|
|
1080
|
+
return keys.indexOf(key);
|
|
1081
|
+
},
|
|
1082
|
+
deriveCollection(cb) {
|
|
1083
|
+
return deriveCollection(collection, cb);
|
|
1084
|
+
}
|
|
1085
|
+
};
|
|
1086
|
+
return collection;
|
|
1087
|
+
}
|
|
1088
|
+
function isCollection(value) {
|
|
1089
|
+
return isObjectOfType(value, TYPE_COLLECTION);
|
|
1090
|
+
}
|
|
1091
|
+
function isCollectionSource(value) {
|
|
1092
|
+
return isList(value) || isCollection(value);
|
|
1093
|
+
}
|
|
1094
|
+
// src/nodes/effect.ts
|
|
1095
|
+
function createEffect(fn) {
|
|
1096
|
+
validateCallback("Effect", fn);
|
|
1097
|
+
const node = {
|
|
1098
|
+
fn,
|
|
1099
|
+
flags: FLAG_DIRTY,
|
|
1100
|
+
sources: null,
|
|
1101
|
+
sourcesTail: null,
|
|
1102
|
+
cleanup: null
|
|
1103
|
+
};
|
|
1104
|
+
const dispose = () => {
|
|
1105
|
+
runCleanup(node);
|
|
1106
|
+
node.fn = undefined;
|
|
1107
|
+
node.flags = FLAG_CLEAN;
|
|
1108
|
+
node.sourcesTail = null;
|
|
1109
|
+
trimSources(node);
|
|
1110
|
+
};
|
|
1111
|
+
if (activeOwner)
|
|
1112
|
+
registerCleanup(activeOwner, dispose);
|
|
1113
|
+
runEffect(node);
|
|
1114
|
+
return dispose;
|
|
1115
|
+
}
|
|
1116
|
+
function match(signals, handlers) {
|
|
1117
|
+
if (!activeOwner)
|
|
1118
|
+
throw new RequiredOwnerError("match");
|
|
1119
|
+
const { ok, err = console.error, nil } = handlers;
|
|
1120
|
+
let errors;
|
|
1121
|
+
let pending = false;
|
|
1122
|
+
const values = new Array(signals.length);
|
|
1123
|
+
for (let i = 0;i < signals.length; i++) {
|
|
1124
|
+
try {
|
|
1125
|
+
values[i] = signals[i].get();
|
|
1126
|
+
} catch (e) {
|
|
1127
|
+
if (e instanceof UnsetSignalValueError) {
|
|
1128
|
+
pending = true;
|
|
1129
|
+
continue;
|
|
1130
|
+
}
|
|
1131
|
+
if (!errors)
|
|
1132
|
+
errors = [];
|
|
1133
|
+
errors.push(e instanceof Error ? e : new Error(String(e)));
|
|
1134
|
+
}
|
|
657
1135
|
}
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
1136
|
+
let out;
|
|
1137
|
+
try {
|
|
1138
|
+
if (pending)
|
|
1139
|
+
out = nil?.();
|
|
1140
|
+
else if (errors)
|
|
1141
|
+
out = err(errors);
|
|
1142
|
+
else
|
|
1143
|
+
out = ok(values);
|
|
1144
|
+
} catch (e) {
|
|
1145
|
+
err([e instanceof Error ? e : new Error(String(e))]);
|
|
1146
|
+
}
|
|
1147
|
+
if (typeof out === "function")
|
|
1148
|
+
return out;
|
|
1149
|
+
if (out instanceof Promise) {
|
|
1150
|
+
const owner = activeOwner;
|
|
1151
|
+
const controller = new AbortController;
|
|
1152
|
+
registerCleanup(owner, () => controller.abort());
|
|
1153
|
+
out.then((cleanup) => {
|
|
1154
|
+
if (!controller.signal.aborted && typeof cleanup === "function")
|
|
1155
|
+
registerCleanup(owner, cleanup);
|
|
1156
|
+
}).catch((e) => {
|
|
1157
|
+
err([e instanceof Error ? e : new Error(String(e))]);
|
|
1158
|
+
});
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
// src/nodes/sensor.ts
|
|
1162
|
+
function createSensor(start, options) {
|
|
1163
|
+
validateCallback(TYPE_SENSOR, start, isSyncFunction);
|
|
1164
|
+
if (options?.value !== undefined)
|
|
1165
|
+
validateSignalValue(TYPE_SENSOR, options.value, options?.guard);
|
|
1166
|
+
const node = {
|
|
1167
|
+
value: options?.value,
|
|
1168
|
+
sinks: null,
|
|
1169
|
+
sinksTail: null,
|
|
1170
|
+
equals: options?.equals ?? DEFAULT_EQUALITY,
|
|
1171
|
+
guard: options?.guard,
|
|
1172
|
+
stop: undefined
|
|
1173
|
+
};
|
|
1174
|
+
return {
|
|
1175
|
+
[Symbol.toStringTag]: TYPE_SENSOR,
|
|
1176
|
+
get() {
|
|
1177
|
+
if (activeSink) {
|
|
1178
|
+
if (!node.sinks)
|
|
1179
|
+
node.stop = start((next) => {
|
|
1180
|
+
validateSignalValue(TYPE_SENSOR, next, node.guard);
|
|
1181
|
+
setState(node, next);
|
|
1182
|
+
});
|
|
1183
|
+
link(node, activeSink);
|
|
1184
|
+
}
|
|
1185
|
+
validateReadValue(TYPE_SENSOR, node.value);
|
|
1186
|
+
return node.value;
|
|
1187
|
+
}
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1190
|
+
function isSensor(value) {
|
|
1191
|
+
return isObjectOfType(value, TYPE_SENSOR);
|
|
1192
|
+
}
|
|
1193
|
+
// src/nodes/store.ts
|
|
1194
|
+
function diffRecords(oldObj, newObj) {
|
|
1195
|
+
const oldValid = isRecord(oldObj) || Array.isArray(oldObj);
|
|
1196
|
+
const newValid = isRecord(newObj) || Array.isArray(newObj);
|
|
1197
|
+
if (!oldValid || !newValid) {
|
|
1198
|
+
const changed2 = !Object.is(oldObj, newObj);
|
|
1199
|
+
return {
|
|
1200
|
+
changed: changed2,
|
|
1201
|
+
add: changed2 && newValid ? newObj : {},
|
|
1202
|
+
change: {},
|
|
1203
|
+
remove: changed2 && oldValid ? oldObj : {}
|
|
1204
|
+
};
|
|
663
1205
|
}
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
1206
|
+
const visited = new WeakSet;
|
|
1207
|
+
const add = {};
|
|
1208
|
+
const change = {};
|
|
1209
|
+
const remove = {};
|
|
1210
|
+
let changed = false;
|
|
1211
|
+
const oldKeys = Object.keys(oldObj);
|
|
1212
|
+
const newKeys = Object.keys(newObj);
|
|
1213
|
+
for (const key of newKeys) {
|
|
1214
|
+
if (key in oldObj) {
|
|
1215
|
+
if (!isEqual(oldObj[key], newObj[key], visited)) {
|
|
1216
|
+
change[key] = newObj[key];
|
|
1217
|
+
changed = true;
|
|
1218
|
+
}
|
|
1219
|
+
} else {
|
|
1220
|
+
add[key] = newObj[key];
|
|
1221
|
+
changed = true;
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
for (const key of oldKeys) {
|
|
1225
|
+
if (!(key in newObj)) {
|
|
1226
|
+
remove[key] = undefined;
|
|
1227
|
+
changed = true;
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
return { add, change, remove, changed };
|
|
1231
|
+
}
|
|
1232
|
+
function createStore(initialValue, options) {
|
|
1233
|
+
validateSignalValue(TYPE_STORE, initialValue, isRecord);
|
|
1234
|
+
const signals = new Map;
|
|
1235
|
+
const addSignal = (key, value) => {
|
|
1236
|
+
validateSignalValue(`${TYPE_STORE} for key "${key}"`, value);
|
|
1237
|
+
if (Array.isArray(value))
|
|
1238
|
+
signals.set(key, createList(value));
|
|
1239
|
+
else if (isRecord(value))
|
|
1240
|
+
signals.set(key, createStore(value));
|
|
1241
|
+
else
|
|
1242
|
+
signals.set(key, createState(value));
|
|
1243
|
+
};
|
|
1244
|
+
const buildValue = () => {
|
|
1245
|
+
const record = {};
|
|
1246
|
+
signals.forEach((signal, key) => {
|
|
1247
|
+
record[key] = signal.get();
|
|
1248
|
+
});
|
|
1249
|
+
return record;
|
|
1250
|
+
};
|
|
1251
|
+
const node = {
|
|
1252
|
+
fn: buildValue,
|
|
1253
|
+
value: initialValue,
|
|
1254
|
+
flags: FLAG_DIRTY,
|
|
1255
|
+
sources: null,
|
|
1256
|
+
sourcesTail: null,
|
|
1257
|
+
sinks: null,
|
|
1258
|
+
sinksTail: null,
|
|
1259
|
+
equals: isEqual,
|
|
1260
|
+
error: undefined
|
|
1261
|
+
};
|
|
1262
|
+
const applyChanges = (changes) => {
|
|
1263
|
+
let structural = false;
|
|
1264
|
+
for (const key in changes.add) {
|
|
1265
|
+
addSignal(key, changes.add[key]);
|
|
1266
|
+
structural = true;
|
|
668
1267
|
}
|
|
669
1268
|
if (Object.keys(changes.change).length) {
|
|
670
1269
|
batch(() => {
|
|
671
1270
|
for (const key in changes.change) {
|
|
672
1271
|
const value = changes.change[key];
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
1272
|
+
validateSignalValue(`${TYPE_STORE} for key "${key}"`, value);
|
|
1273
|
+
const signal = signals.get(key);
|
|
1274
|
+
if (signal) {
|
|
1275
|
+
if (isRecord(value) !== isStore(signal)) {
|
|
1276
|
+
addSignal(key, value);
|
|
1277
|
+
structural = true;
|
|
1278
|
+
} else
|
|
1279
|
+
signal.set(value);
|
|
1280
|
+
}
|
|
678
1281
|
}
|
|
679
1282
|
});
|
|
680
1283
|
}
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
1284
|
+
for (const key in changes.remove) {
|
|
1285
|
+
signals.delete(key);
|
|
1286
|
+
structural = true;
|
|
1287
|
+
}
|
|
1288
|
+
if (structural) {
|
|
1289
|
+
node.sources = null;
|
|
1290
|
+
node.sourcesTail = null;
|
|
684
1291
|
}
|
|
685
1292
|
return changes.changed;
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
1293
|
+
};
|
|
1294
|
+
for (const key of Object.keys(initialValue))
|
|
1295
|
+
addSignal(key, initialValue[key]);
|
|
1296
|
+
const store = {
|
|
1297
|
+
[Symbol.toStringTag]: TYPE_STORE,
|
|
1298
|
+
[Symbol.isConcatSpreadable]: false,
|
|
1299
|
+
*[Symbol.iterator]() {
|
|
1300
|
+
for (const key of Array.from(signals.keys())) {
|
|
1301
|
+
const signal = signals.get(key);
|
|
1302
|
+
if (signal)
|
|
1303
|
+
yield [key, signal];
|
|
1304
|
+
}
|
|
1305
|
+
},
|
|
1306
|
+
keys() {
|
|
1307
|
+
if (activeSink) {
|
|
1308
|
+
if (!node.sinks && options?.watched)
|
|
1309
|
+
node.stop = options.watched();
|
|
1310
|
+
link(node, activeSink);
|
|
1311
|
+
}
|
|
1312
|
+
return signals.keys();
|
|
1313
|
+
},
|
|
1314
|
+
byKey(key) {
|
|
1315
|
+
return signals.get(key);
|
|
1316
|
+
},
|
|
1317
|
+
get() {
|
|
1318
|
+
if (activeSink) {
|
|
1319
|
+
if (!node.sinks && options?.watched)
|
|
1320
|
+
node.stop = options.watched();
|
|
1321
|
+
link(node, activeSink);
|
|
1322
|
+
}
|
|
1323
|
+
if (node.sources) {
|
|
1324
|
+
if (node.flags) {
|
|
1325
|
+
node.value = untrack(buildValue);
|
|
1326
|
+
node.flags = FLAG_CLEAN;
|
|
1327
|
+
}
|
|
1328
|
+
} else {
|
|
1329
|
+
refresh(node);
|
|
1330
|
+
if (node.error)
|
|
1331
|
+
throw node.error;
|
|
1332
|
+
}
|
|
1333
|
+
return node.value;
|
|
1334
|
+
},
|
|
1335
|
+
set(newValue) {
|
|
1336
|
+
const currentValue = node.flags & FLAG_DIRTY ? buildValue() : node.value;
|
|
1337
|
+
const changes = diffRecords(currentValue, newValue);
|
|
1338
|
+
if (applyChanges(changes)) {
|
|
1339
|
+
propagate(node);
|
|
1340
|
+
node.flags |= FLAG_DIRTY;
|
|
1341
|
+
if (batchDepth === 0)
|
|
1342
|
+
flush();
|
|
1343
|
+
}
|
|
1344
|
+
},
|
|
1345
|
+
update(fn) {
|
|
1346
|
+
store.set(fn(store.get()));
|
|
1347
|
+
},
|
|
1348
|
+
add(key, value) {
|
|
1349
|
+
if (signals.has(key))
|
|
1350
|
+
throw new DuplicateKeyError(TYPE_STORE, key, value);
|
|
1351
|
+
addSignal(key, value);
|
|
1352
|
+
node.sources = null;
|
|
1353
|
+
node.sourcesTail = null;
|
|
1354
|
+
propagate(node);
|
|
1355
|
+
node.flags |= FLAG_DIRTY;
|
|
1356
|
+
if (batchDepth === 0)
|
|
1357
|
+
flush();
|
|
1358
|
+
return key;
|
|
1359
|
+
},
|
|
1360
|
+
remove(key) {
|
|
1361
|
+
const ok = signals.delete(key);
|
|
1362
|
+
if (ok) {
|
|
1363
|
+
node.sources = null;
|
|
1364
|
+
node.sourcesTail = null;
|
|
1365
|
+
propagate(node);
|
|
1366
|
+
node.flags |= FLAG_DIRTY;
|
|
1367
|
+
if (batchDepth === 0)
|
|
1368
|
+
flush();
|
|
1369
|
+
}
|
|
714
1370
|
}
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
notifyOf(this);
|
|
718
|
-
}
|
|
719
|
-
update(fn) {
|
|
720
|
-
this.set(fn(this.get()));
|
|
721
|
-
}
|
|
722
|
-
add(key, value) {
|
|
723
|
-
if (this.#signals.has(key))
|
|
724
|
-
throw new DuplicateKeyError(TYPE_STORE, key, value);
|
|
725
|
-
const ok = this.#add(key, value);
|
|
726
|
-
if (ok)
|
|
727
|
-
notifyOf(this);
|
|
728
|
-
return key;
|
|
729
|
-
}
|
|
730
|
-
remove(key) {
|
|
731
|
-
const ok = this.#signals.delete(key);
|
|
732
|
-
if (ok)
|
|
733
|
-
notifyOf(this);
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
var createStore = (initialValue, options) => {
|
|
737
|
-
const instance = new BaseStore(initialValue, options);
|
|
738
|
-
return new Proxy(instance, {
|
|
1371
|
+
};
|
|
1372
|
+
return new Proxy(store, {
|
|
739
1373
|
get(target, prop) {
|
|
740
1374
|
if (prop in target) {
|
|
741
1375
|
const value = Reflect.get(target, prop);
|
|
742
1376
|
return isFunction(value) ? value.bind(target) : value;
|
|
743
1377
|
}
|
|
744
|
-
if (
|
|
1378
|
+
if (typeof prop !== "symbol")
|
|
745
1379
|
return target.byKey(prop);
|
|
746
1380
|
},
|
|
747
1381
|
has(target, prop) {
|
|
@@ -755,7 +1389,7 @@ var createStore = (initialValue, options) => {
|
|
|
755
1389
|
getOwnPropertyDescriptor(target, prop) {
|
|
756
1390
|
if (prop in target)
|
|
757
1391
|
return Reflect.getOwnPropertyDescriptor(target, prop);
|
|
758
|
-
if (
|
|
1392
|
+
if (typeof prop === "symbol")
|
|
759
1393
|
return;
|
|
760
1394
|
const signal = target.byKey(String(prop));
|
|
761
1395
|
return signal ? {
|
|
@@ -766,402 +1400,96 @@ var createStore = (initialValue, options) => {
|
|
|
766
1400
|
} : undefined;
|
|
767
1401
|
}
|
|
768
1402
|
});
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
|
|
1403
|
+
}
|
|
1404
|
+
function isStore(value) {
|
|
1405
|
+
return isObjectOfType(value, TYPE_STORE);
|
|
1406
|
+
}
|
|
772
1407
|
// src/signal.ts
|
|
773
|
-
|
|
774
|
-
|
|
1408
|
+
function createComputed(callback, options) {
|
|
1409
|
+
return isAsyncFunction(callback) ? createTask(callback, options) : createMemo(callback, options);
|
|
1410
|
+
}
|
|
775
1411
|
function createSignal(value) {
|
|
776
|
-
if (
|
|
777
|
-
return
|
|
778
|
-
if (
|
|
779
|
-
|
|
1412
|
+
if (isSignal(value))
|
|
1413
|
+
return value;
|
|
1414
|
+
if (value == null)
|
|
1415
|
+
throw new InvalidSignalValueError("createSignal", value);
|
|
1416
|
+
if (isAsyncFunction(value))
|
|
1417
|
+
return createTask(value);
|
|
1418
|
+
if (isFunction(value))
|
|
1419
|
+
return createMemo(value);
|
|
780
1420
|
if (isUniformArray(value))
|
|
781
|
-
return
|
|
1421
|
+
return createList(value);
|
|
782
1422
|
if (isRecord(value))
|
|
783
1423
|
return createStore(value);
|
|
784
|
-
return
|
|
1424
|
+
return createState(value);
|
|
785
1425
|
}
|
|
786
1426
|
function createMutableSignal(value) {
|
|
1427
|
+
if (isMutableSignal(value))
|
|
1428
|
+
return value;
|
|
1429
|
+
if (value == null || isFunction(value) || isSignal(value))
|
|
1430
|
+
throw new InvalidSignalValueError("createMutableSignal", value);
|
|
787
1431
|
if (isUniformArray(value))
|
|
788
|
-
return
|
|
1432
|
+
return createList(value);
|
|
789
1433
|
if (isRecord(value))
|
|
790
1434
|
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
|
-
}
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
class NullishSignalValueError extends TypeError {
|
|
838
|
-
constructor(where) {
|
|
839
|
-
super(`Nullish signal values are not allowed in ${where}`);
|
|
840
|
-
this.name = "NullishSignalValueError";
|
|
841
|
-
}
|
|
1435
|
+
return createState(value);
|
|
842
1436
|
}
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
constructor(what, value) {
|
|
846
|
-
super(`Could not set ${what} to ${valueString(value)} because signal is read-only`);
|
|
847
|
-
this.name = "ReadonlySignalError";
|
|
848
|
-
}
|
|
1437
|
+
function isComputed(value) {
|
|
1438
|
+
return isMemo(value) || isTask(value);
|
|
849
1439
|
}
|
|
850
|
-
function
|
|
851
|
-
|
|
852
|
-
|
|
1440
|
+
function isSignal(value) {
|
|
1441
|
+
const signalsTypes = [
|
|
1442
|
+
TYPE_STATE,
|
|
1443
|
+
TYPE_MEMO,
|
|
1444
|
+
TYPE_TASK,
|
|
1445
|
+
TYPE_SENSOR,
|
|
1446
|
+
TYPE_LIST,
|
|
1447
|
+
TYPE_COLLECTION,
|
|
1448
|
+
TYPE_STORE
|
|
1449
|
+
];
|
|
1450
|
+
const typeStyle = Object.prototype.toString.call(value).slice(8, -1);
|
|
1451
|
+
return signalsTypes.includes(typeStyle);
|
|
853
1452
|
}
|
|
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 };
|
|
1453
|
+
function isMutableSignal(value) {
|
|
1454
|
+
return isState(value) || isStore(value) || isList(value);
|
|
1105
1455
|
}
|
|
1106
1456
|
export {
|
|
1107
1457
|
valueString,
|
|
1108
|
-
validateSignalValue,
|
|
1109
|
-
validateCallback,
|
|
1110
1458
|
untrack,
|
|
1111
|
-
track,
|
|
1112
|
-
subscribeTo,
|
|
1113
|
-
resolve,
|
|
1114
|
-
notifyOf,
|
|
1115
1459
|
match,
|
|
1116
|
-
|
|
1117
|
-
isSymbol,
|
|
1118
|
-
isString,
|
|
1460
|
+
isTask,
|
|
1119
1461
|
isStore,
|
|
1120
1462
|
isState,
|
|
1121
1463
|
isSignal,
|
|
1122
|
-
|
|
1123
|
-
isRecordOrArray,
|
|
1464
|
+
isSensor,
|
|
1124
1465
|
isRecord,
|
|
1125
1466
|
isObjectOfType,
|
|
1126
|
-
isNumber,
|
|
1127
1467
|
isMutableSignal,
|
|
1128
|
-
|
|
1468
|
+
isMemo,
|
|
1129
1469
|
isList,
|
|
1130
1470
|
isFunction,
|
|
1131
1471
|
isEqual,
|
|
1132
1472
|
isComputed,
|
|
1133
1473
|
isCollection,
|
|
1134
1474
|
isAsyncFunction,
|
|
1135
|
-
|
|
1136
|
-
guardMutableSignal,
|
|
1137
|
-
flush,
|
|
1138
|
-
diff,
|
|
1139
|
-
createWatcher,
|
|
1475
|
+
createTask,
|
|
1140
1476
|
createStore,
|
|
1477
|
+
createState,
|
|
1141
1478
|
createSignal,
|
|
1142
|
-
|
|
1479
|
+
createSensor,
|
|
1480
|
+
createScope,
|
|
1481
|
+
createMutableSignal,
|
|
1482
|
+
createMemo,
|
|
1483
|
+
createList,
|
|
1143
1484
|
createEffect,
|
|
1144
1485
|
createComputed,
|
|
1486
|
+
createCollection,
|
|
1145
1487
|
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,
|
|
1488
|
+
UnsetSignalValueError,
|
|
1489
|
+
SKIP_EQUALITY,
|
|
1490
|
+
RequiredOwnerError,
|
|
1157
1491
|
NullishSignalValueError,
|
|
1158
|
-
Memo,
|
|
1159
|
-
List,
|
|
1160
1492
|
InvalidSignalValueError,
|
|
1161
|
-
InvalidCollectionSourceError,
|
|
1162
1493
|
InvalidCallbackError,
|
|
1163
|
-
|
|
1164
|
-
DerivedCollection,
|
|
1165
|
-
CircularDependencyError,
|
|
1166
|
-
BaseStore
|
|
1494
|
+
CircularDependencyError
|
|
1167
1495
|
};
|