@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.
Files changed (94) hide show
  1. package/.ai-context.md +169 -227
  2. package/.cursorrules +41 -35
  3. package/.github/copilot-instructions.md +176 -116
  4. package/ARCHITECTURE.md +276 -0
  5. package/CHANGELOG.md +29 -0
  6. package/CLAUDE.md +201 -143
  7. package/GUIDE.md +298 -0
  8. package/README.md +246 -193
  9. package/REQUIREMENTS.md +100 -0
  10. package/bench/reactivity.bench.ts +577 -0
  11. package/context7.json +4 -0
  12. package/examples/events-sensor.ts +187 -0
  13. package/examples/selector-sensor.ts +173 -0
  14. package/index.dev.js +1390 -1008
  15. package/index.js +1 -1
  16. package/index.ts +60 -74
  17. package/package.json +5 -2
  18. package/skills/changelog-keeper/SKILL.md +59 -0
  19. package/skills/changelog-keeper/agents/openai.yaml +4 -0
  20. package/src/errors.ts +118 -74
  21. package/src/graph.ts +612 -0
  22. package/src/nodes/collection.ts +512 -0
  23. package/src/nodes/effect.ts +149 -0
  24. package/src/nodes/list.ts +589 -0
  25. package/src/nodes/memo.ts +148 -0
  26. package/src/nodes/sensor.ts +149 -0
  27. package/src/nodes/state.ts +135 -0
  28. package/src/nodes/store.ts +378 -0
  29. package/src/nodes/task.ts +174 -0
  30. package/src/signal.ts +112 -66
  31. package/src/util.ts +26 -57
  32. package/test/batch.test.ts +96 -62
  33. package/test/benchmark.test.ts +473 -487
  34. package/test/collection.test.ts +456 -707
  35. package/test/effect.test.ts +293 -696
  36. package/test/list.test.ts +335 -592
  37. package/test/memo.test.ts +574 -0
  38. package/test/regression.test.ts +156 -0
  39. package/test/scope.test.ts +191 -0
  40. package/test/sensor.test.ts +454 -0
  41. package/test/signal.test.ts +220 -213
  42. package/test/state.test.ts +217 -265
  43. package/test/store.test.ts +346 -446
  44. package/test/task.test.ts +529 -0
  45. package/test/untrack.test.ts +167 -0
  46. package/types/index.d.ts +13 -15
  47. package/types/src/errors.d.ts +73 -17
  48. package/types/src/graph.d.ts +218 -0
  49. package/types/src/nodes/collection.d.ts +69 -0
  50. package/types/src/nodes/effect.d.ts +48 -0
  51. package/types/src/nodes/list.d.ts +66 -0
  52. package/types/src/nodes/memo.d.ts +63 -0
  53. package/types/src/nodes/sensor.d.ts +81 -0
  54. package/types/src/nodes/state.d.ts +78 -0
  55. package/types/src/nodes/store.d.ts +51 -0
  56. package/types/src/nodes/task.d.ts +79 -0
  57. package/types/src/signal.d.ts +43 -29
  58. package/types/src/util.d.ts +9 -16
  59. package/archive/benchmark.ts +0 -683
  60. package/archive/collection.ts +0 -253
  61. package/archive/composite.ts +0 -85
  62. package/archive/computed.ts +0 -195
  63. package/archive/list.ts +0 -483
  64. package/archive/memo.ts +0 -139
  65. package/archive/state.ts +0 -90
  66. package/archive/store.ts +0 -298
  67. package/archive/task.ts +0 -189
  68. package/src/classes/collection.ts +0 -245
  69. package/src/classes/computed.ts +0 -349
  70. package/src/classes/list.ts +0 -343
  71. package/src/classes/ref.ts +0 -70
  72. package/src/classes/state.ts +0 -102
  73. package/src/classes/store.ts +0 -262
  74. package/src/diff.ts +0 -138
  75. package/src/effect.ts +0 -93
  76. package/src/match.ts +0 -45
  77. package/src/resolve.ts +0 -49
  78. package/src/system.ts +0 -257
  79. package/test/computed.test.ts +0 -1108
  80. package/test/diff.test.ts +0 -955
  81. package/test/match.test.ts +0 -388
  82. package/test/ref.test.ts +0 -353
  83. package/test/resolve.test.ts +0 -154
  84. package/types/src/classes/collection.d.ts +0 -45
  85. package/types/src/classes/computed.d.ts +0 -94
  86. package/types/src/classes/list.d.ts +0 -43
  87. package/types/src/classes/ref.d.ts +0 -35
  88. package/types/src/classes/state.d.ts +0 -49
  89. package/types/src/classes/store.d.ts +0 -52
  90. package/types/src/diff.d.ts +0 -28
  91. package/types/src/effect.d.ts +0 -15
  92. package/types/src/match.d.ts +0 -21
  93. package/types/src/resolve.d.ts +0 -29
  94. package/types/src/system.d.ts +0 -78
package/index.dev.js CHANGED
@@ -1,143 +1,426 @@
1
- // src/system.ts
2
- var activeWatcher;
3
- var watchersMap = new WeakMap;
4
- var watchedCallbackMap = new WeakMap;
5
- var unwatchedCallbackMap = new WeakMap;
6
- var pendingReactions = new Set;
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 UNSET = Symbol();
9
- var createWatcher = (push, pull) => {
10
- const cleanups = new Set;
11
- const watcher = push;
12
- watcher.run = () => {
13
- const prev = activeWatcher;
14
- activeWatcher = watcher;
15
- try {
16
- pull();
17
- } finally {
18
- activeWatcher = prev;
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
- watcher.onCleanup = (cleanup) => {
22
- cleanups.add(cleanup);
23
- };
24
- watcher.stop = () => {
25
- try {
26
- for (const cleanup of cleanups)
27
- cleanup();
28
- } finally {
29
- cleanups.clear();
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
- return watcher;
33
- };
34
- var untrack = (callback) => {
35
- const prev = activeWatcher;
36
- activeWatcher = undefined;
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
- callback();
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
- activeWatcher = prev;
236
+ activeSink = prevWatcher;
237
+ trimSources(node);
41
238
  }
42
- };
43
- var registerWatchCallbacks = (signal, watched, unwatched) => {
44
- watchedCallbackMap.set(signal, watched);
45
- if (unwatched)
46
- unwatchedCallbackMap.set(signal, unwatched);
47
- };
48
- var subscribeTo = (signal) => {
49
- if (!activeWatcher || watchersMap.get(signal)?.has(activeWatcher))
50
- return false;
51
- const watcher = activeWatcher;
52
- if (!watchersMap.has(signal))
53
- watchersMap.set(signal, new Set);
54
- const watchers = watchersMap.get(signal);
55
- assert(watchers);
56
- if (!watchers.size) {
57
- const watchedCallback = watchedCallbackMap.get(signal);
58
- if (watchedCallback)
59
- untrack(watchedCallback);
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
- watchers.add(watcher);
62
- watcher.onCleanup(() => {
63
- watchers.delete(watcher);
64
- if (!watchers.size) {
65
- const unwatchedCallback = unwatchedCallbackMap.get(signal);
66
- if (unwatchedCallback)
67
- untrack(unwatchedCallback);
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
- return true;
71
- };
72
- var unsubscribeAllFrom = (signal) => {
73
- const watchers = watchersMap.get(signal);
74
- if (!watchers)
75
- return;
76
- for (const watcher of watchers)
77
- watcher.stop();
78
- watchers.clear();
79
- };
80
- var notifyOf = (signal) => {
81
- const watchers = watchersMap.get(signal);
82
- if (!watchers?.size)
83
- return false;
84
- for (const react of watchers) {
85
- if (batchDepth)
86
- pendingReactions.add(react);
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
- react();
329
+ runEffect(node);
330
+ } else {
331
+ node.flags = FLAG_CLEAN;
89
332
  }
90
- return true;
91
- };
92
- var flush = () => {
93
- while (pendingReactions.size) {
94
- const watchers = Array.from(pendingReactions);
95
- pendingReactions.clear();
96
- for (const react of watchers)
97
- react();
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
- var batch = (callback) => {
348
+ }
349
+ function batch(fn) {
101
350
  batchDepth++;
102
351
  try {
103
- callback();
352
+ fn();
104
353
  } finally {
105
- flush();
106
354
  batchDepth--;
355
+ if (batchDepth === 0)
356
+ flush();
107
357
  }
108
- };
109
- var track = (watcher, run) => {
110
- const prev = activeWatcher;
111
- activeWatcher = watcher || undefined;
358
+ }
359
+ function untrack(fn) {
360
+ const prev = activeSink;
361
+ activeSink = null;
112
362
  try {
113
- run();
363
+ return fn();
114
364
  } finally {
115
- activeWatcher = prev;
365
+ activeSink = prev;
116
366
  }
117
- };
118
-
119
- // src/util.ts
120
- var isString = (value) => typeof value === "string";
121
- var isNumber = (value) => typeof value === "number";
122
- var isSymbol = (value) => typeof value === "symbol";
123
- var isFunction = (fn) => typeof fn === "function";
124
- var isAsyncFunction = (fn) => isFunction(fn) && fn.constructor.name === "AsyncFunction";
125
- var isSyncFunction = (fn) => isFunction(fn) && fn.constructor.name !== "AsyncFunction";
126
- var isNonNullObject = (value) => value != null && typeof value === "object";
127
- var isObjectOfType = (value, type) => Object.prototype.toString.call(value) === `[object ${type}]`;
128
- var isRecord = (value) => isObjectOfType(value, "Object");
129
- var isRecordOrArray = (value) => isRecord(value) || Array.isArray(value);
130
- var isUniformArray = (value, guard = (item) => item != null) => Array.isArray(value) && value.every(guard);
131
- var isAbortError = (error) => error instanceof DOMException && error.name === "AbortError";
132
- var valueString = (value) => isString(value) ? `"${value}"` : !!value && typeof value === "object" ? JSON.stringify(value) : String(value);
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/diff.ts
135
- var isEqual = (a, b, visited) => {
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 (!isNonNullObject(a) || !isNonNullObject(b))
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
- if (Array.isArray(a) && Array.isArray(b)) {
150
- if (a.length !== b.length)
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 < a.length; i++) {
153
- if (!isEqual(a[i], b[i], visited))
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
- var diff = (oldObj, newObj) => {
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
- }
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 oldKeys = Object.keys(oldObj);
196
- const newKeys = Object.keys(newObj);
197
- const allKeys = new Set([...oldKeys, ...newKeys]);
198
- for (const key of allKeys) {
199
- const oldHas = key in oldObj;
200
- const newHas = key in newObj;
201
- if (!oldHas && newHas) {
202
- add[key] = newObj[key];
203
- continue;
204
- } else if (oldHas && !newHas) {
205
- remove[key] = UNSET;
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
- #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
- });
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
- var isState = (value) => isObjectOfType(value, TYPE_STATE);
431
-
432
- // src/classes/list.ts
433
- var TYPE_LIST = "List";
434
-
435
- class List {
436
- #signals = new Map;
437
- #keys = [];
438
- #generateKey;
439
- #validate;
440
- constructor(initialValue, options) {
441
- validateSignalValue(TYPE_LIST, initialValue, Array.isArray);
442
- let keyCounter = 0;
443
- const keyConfig = options?.keyConfig;
444
- this.#generateKey = isString(keyConfig) ? () => `${keyConfig}${keyCounter++}` : isFunction(keyConfig) ? (item) => keyConfig(item) : () => String(keyCounter++);
445
- this.#validate = (key, value) => {
446
- validateSignalValue(`${TYPE_LIST} item for key "${key}"`, value, options?.guard);
447
- return true;
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 value = array[i];
462
- if (value === undefined)
541
+ const val = array[i];
542
+ if (val === undefined)
463
543
  continue;
464
- let key = this.#keys[i];
544
+ let key = keys[i];
465
545
  if (!key) {
466
- key = this.#generateKey(value);
467
- this.#keys[i] = key;
546
+ key = generateKey(val);
547
+ keys[i] = key;
468
548
  }
469
- record[key] = value;
549
+ record[key] = val;
470
550
  }
471
551
  return record;
472
- }
473
- #add(key, value) {
474
- if (!this.#validate(key, value))
475
- return false;
476
- this.#signals.set(key, new State(value));
477
- return true;
478
- }
479
- #change(changes) {
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 value = changes.change[key];
488
- if (!this.#validate(key, value))
489
- continue;
490
- const signal = this.#signals.get(key);
491
- if (guardMutableSignal(`${TYPE_LIST} item "${key}"`, value, signal))
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
- if (Object.keys(changes.remove).length) {
497
- for (const key in changes.remove) {
498
- this.#signals.delete(key);
499
- const index = this.#keys.indexOf(key);
500
- if (index !== -1)
501
- this.#keys.splice(index, 1);
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
- 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);
579
+ if (structural) {
580
+ node.sources = null;
581
+ node.sourcesTail = null;
581
582
  }
582
- }
583
- sort(compareFn) {
584
- const entries = this.#keys.map((key) => [key, this.#signals.get(key)?.get()]).sort(isFunction(compareFn) ? (a, b) => compareFn(a[1], b[1]) : (a, b) => String(a[1]).localeCompare(String(b[1])));
585
- const newOrder = entries.map(([key]) => key);
586
- if (!isEqual(this.#keys, newOrder)) {
587
- this.#keys = newOrder;
588
- notifyOf(this);
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
- splice(start, deleteCount, ...items) {
592
- const length = this.#keys.length;
593
- const actualStart = start < 0 ? Math.max(0, length + start) : Math.min(start, length);
594
- const actualDeleteCount = Math.max(0, Math.min(deleteCount ?? Math.max(0, length - Math.max(0, actualStart)), length - actualStart));
595
- const add = {};
596
- const remove = {};
597
- for (let i = 0;i < actualDeleteCount; i++) {
598
- const index = actualStart + i;
599
- const key = this.#keys[index];
600
- if (key) {
601
- const signal = this.#signals.get(key);
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
- remove[key] = signal.get();
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
- const newOrder = this.#keys.slice(0, actualStart);
607
- for (const item of items) {
608
- const key = this.#generateKey(item);
609
- newOrder.push(key);
610
- add[key] = item;
611
- }
612
- newOrder.push(...this.#keys.slice(actualStart + actualDeleteCount));
613
- const changed = !!(Object.keys(add).length || Object.keys(remove).length);
614
- if (changed) {
615
- this.#change({
616
- add,
617
- change: {},
618
- remove,
619
- changed
620
- });
621
- this.#keys = newOrder.filter(() => true);
622
- notifyOf(this);
623
- }
624
- return Object.values(remove);
625
- }
626
- deriveCollection(callback, options) {
627
- return new DerivedCollection(this, callback, options);
628
- }
629
- }
630
- var isList = (value) => isObjectOfType(value, TYPE_LIST);
631
-
632
- // src/classes/store.ts
633
- var TYPE_STORE = "Store";
634
-
635
- class BaseStore {
636
- #signals = new Map;
637
- constructor(initialValue, options) {
638
- validateSignalValue(TYPE_STORE, initialValue, options?.guard ?? isRecord);
639
- this.#change({
640
- add: initialValue,
641
- change: {},
642
- remove: {},
643
- changed: true
644
- });
645
- if (options?.watched)
646
- registerWatchCallbacks(this, options.watched, options.unwatched);
647
- }
648
- get #value() {
649
- const record = {};
650
- for (const [key, signal] of this.#signals.entries())
651
- record[key] = signal.get();
652
- return record;
653
- }
654
- #validate(key, value) {
655
- validateSignalValue(`${TYPE_STORE} for key "${key}"`, value);
656
- return true;
657
- }
658
- #add(key, value) {
659
- if (!this.#validate(key, value))
660
- return false;
661
- this.#signals.set(key, createMutableSignal(value));
662
- return true;
663
- }
664
- #change(changes) {
665
- if (Object.keys(changes.add).length) {
666
- for (const key in changes.add)
667
- this.#add(key, changes.add[key]);
668
- }
669
- if (Object.keys(changes.change).length) {
670
- batch(() => {
671
- for (const key in changes.change) {
672
- const value = changes.change[key];
673
- if (!this.#validate(key, value))
674
- continue;
675
- const signal = this.#signals.get(key);
676
- if (guardMutableSignal(`list item "${key}"`, value, signal))
677
- signal.set(value);
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
- if (Object.keys(changes.remove).length) {
682
- for (const key in changes.remove)
683
- this.remove(key);
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
- byKey(key) {
702
- return this.#signals.get(key);
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
- get() {
705
- subscribeTo(this);
706
- return this.#value;
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
- set(newValue) {
709
- if (UNSET === newValue) {
710
- this.#signals.clear();
711
- notifyOf(this);
712
- unsubscribeAllFrom(this);
713
- return;
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
- const changed = this.#change(diff(this.#value, newValue));
716
- if (changed)
717
- notifyOf(this);
718
- }
719
- update(fn) {
720
- this.set(fn(this.get()));
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
- 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;
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
- remove(key) {
731
- const ok = this.#signals.delete(key);
732
- if (ok)
733
- notifyOf(this);
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
- var createStore = (initialValue, options) => {
737
- const instance = new BaseStore(initialValue, options);
738
- return new Proxy(instance, {
739
- get(target, prop) {
740
- if (prop in target) {
741
- const value = Reflect.get(target, prop);
742
- return isFunction(value) ? value.bind(target) : value;
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
- if (!isSymbol(prop))
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 (isSymbol(prop))
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
- var isStore = (value) => isObjectOfType(value, TYPE_STORE);
771
-
1457
+ }
1458
+ function isStore(value) {
1459
+ return isObjectOfType(value, TYPE_STORE);
1460
+ }
772
1461
  // src/signal.ts
773
- var isSignal = (value) => isState(value) || isComputed(value) || isStore(value);
774
- var isMutableSignal = (value) => isState(value) || isStore(value) || isList(value);
1462
+ function createComputed(callback, options) {
1463
+ return isAsyncFunction(callback) ? createTask(callback, options) : createMemo(callback, options);
1464
+ }
775
1465
  function createSignal(value) {
776
- if (isMemoCallback(value))
777
- return new Memo(value);
778
- if (isTaskCallback(value))
779
- return new Task(value);
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 new List(value);
1475
+ return createList(value);
782
1476
  if (isRecord(value))
783
1477
  return createStore(value);
784
- return new State(value);
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 new List(value);
1486
+ return createList(value);
789
1487
  if (isRecord(value))
790
1488
  return createStore(value);
791
- return new State(value);
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
- class NullishSignalValueError extends TypeError {
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
- class ReadonlySignalError extends Error {
845
- constructor(what, value) {
846
- super(`Could not set ${what} to ${valueString(value)} because signal is read-only`);
847
- this.name = "ReadonlySignalError";
848
- }
849
- }
850
- function assert(condition, msg) {
851
- if (!condition)
852
- throw new FailedAssertionError(msg);
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
- var createError = (reason) => reason instanceof Error ? reason : Error(String(reason));
855
- var validateCallback = (where, value, guard = isFunction) => {
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
- isTaskCallback,
1117
- isSymbol,
1118
- isString,
1514
+ isTask,
1119
1515
  isStore,
1120
1516
  isState,
1121
1517
  isSignal,
1122
- isRef,
1123
- isRecordOrArray,
1518
+ isSensor,
1124
1519
  isRecord,
1125
1520
  isObjectOfType,
1126
- isNumber,
1127
1521
  isMutableSignal,
1128
- isMemoCallback,
1522
+ isMemo,
1129
1523
  isList,
1130
1524
  isFunction,
1131
1525
  isEqual,
1132
1526
  isComputed,
1133
1527
  isCollection,
1134
1528
  isAsyncFunction,
1135
- isAbortError,
1136
- guardMutableSignal,
1137
- flush,
1138
- diff,
1139
- createWatcher,
1529
+ createTask,
1140
1530
  createStore,
1531
+ createState,
1141
1532
  createSignal,
1142
- createError,
1533
+ createSensor,
1534
+ createScope,
1535
+ createMutableSignal,
1536
+ createMemo,
1537
+ createList,
1143
1538
  createEffect,
1144
1539
  createComputed,
1540
+ createCollection,
1145
1541
  batch,
1146
- UNSET,
1147
- Task,
1148
- TYPE_STORE,
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
- DuplicateKeyError,
1164
- DerivedCollection,
1165
- CircularDependencyError,
1166
- BaseStore
1548
+ CircularDependencyError
1167
1549
  };