@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.
Files changed (89) hide show
  1. package/.ai-context.md +163 -232
  2. package/.cursorrules +41 -35
  3. package/.github/copilot-instructions.md +166 -116
  4. package/ARCHITECTURE.md +274 -0
  5. package/CLAUDE.md +199 -143
  6. package/COLLECTION_REFACTORING.md +161 -0
  7. package/GUIDE.md +298 -0
  8. package/README.md +232 -197
  9. package/REQUIREMENTS.md +100 -0
  10. package/bench/reactivity.bench.ts +577 -0
  11. package/index.dev.js +1325 -997
  12. package/index.js +1 -1
  13. package/index.ts +58 -74
  14. package/package.json +4 -1
  15. package/src/errors.ts +118 -74
  16. package/src/graph.ts +601 -0
  17. package/src/nodes/collection.ts +474 -0
  18. package/src/nodes/effect.ts +149 -0
  19. package/src/nodes/list.ts +588 -0
  20. package/src/nodes/memo.ts +120 -0
  21. package/src/nodes/sensor.ts +139 -0
  22. package/src/nodes/state.ts +135 -0
  23. package/src/nodes/store.ts +383 -0
  24. package/src/nodes/task.ts +146 -0
  25. package/src/signal.ts +112 -66
  26. package/src/util.ts +26 -57
  27. package/test/batch.test.ts +96 -62
  28. package/test/benchmark.test.ts +473 -487
  29. package/test/collection.test.ts +466 -706
  30. package/test/effect.test.ts +293 -696
  31. package/test/list.test.ts +335 -592
  32. package/test/memo.test.ts +380 -0
  33. package/test/regression.test.ts +156 -0
  34. package/test/scope.test.ts +191 -0
  35. package/test/sensor.test.ts +454 -0
  36. package/test/signal.test.ts +220 -213
  37. package/test/state.test.ts +217 -265
  38. package/test/store.test.ts +346 -446
  39. package/test/task.test.ts +395 -0
  40. package/test/untrack.test.ts +167 -0
  41. package/types/index.d.ts +13 -15
  42. package/types/src/errors.d.ts +73 -17
  43. package/types/src/graph.d.ts +208 -0
  44. package/types/src/nodes/collection.d.ts +64 -0
  45. package/types/src/nodes/effect.d.ts +48 -0
  46. package/types/src/nodes/list.d.ts +65 -0
  47. package/types/src/nodes/memo.d.ts +57 -0
  48. package/types/src/nodes/sensor.d.ts +75 -0
  49. package/types/src/nodes/state.d.ts +78 -0
  50. package/types/src/nodes/store.d.ts +51 -0
  51. package/types/src/nodes/task.d.ts +73 -0
  52. package/types/src/signal.d.ts +43 -29
  53. package/types/src/util.d.ts +9 -16
  54. package/archive/benchmark.ts +0 -683
  55. package/archive/collection.ts +0 -253
  56. package/archive/composite.ts +0 -85
  57. package/archive/computed.ts +0 -195
  58. package/archive/list.ts +0 -483
  59. package/archive/memo.ts +0 -139
  60. package/archive/state.ts +0 -90
  61. package/archive/store.ts +0 -298
  62. package/archive/task.ts +0 -189
  63. package/src/classes/collection.ts +0 -245
  64. package/src/classes/computed.ts +0 -349
  65. package/src/classes/list.ts +0 -343
  66. package/src/classes/ref.ts +0 -70
  67. package/src/classes/state.ts +0 -102
  68. package/src/classes/store.ts +0 -262
  69. package/src/diff.ts +0 -138
  70. package/src/effect.ts +0 -93
  71. package/src/match.ts +0 -45
  72. package/src/resolve.ts +0 -49
  73. package/src/system.ts +0 -257
  74. package/test/computed.test.ts +0 -1108
  75. package/test/diff.test.ts +0 -955
  76. package/test/match.test.ts +0 -388
  77. package/test/ref.test.ts +0 -353
  78. package/test/resolve.test.ts +0 -154
  79. package/types/src/classes/collection.d.ts +0 -45
  80. package/types/src/classes/computed.d.ts +0 -94
  81. package/types/src/classes/list.d.ts +0 -43
  82. package/types/src/classes/ref.d.ts +0 -35
  83. package/types/src/classes/state.d.ts +0 -49
  84. package/types/src/classes/store.d.ts +0 -52
  85. package/types/src/diff.d.ts +0 -28
  86. package/types/src/effect.d.ts +0 -15
  87. package/types/src/match.d.ts +0 -21
  88. package/types/src/resolve.d.ts +0 -29
  89. package/types/src/system.d.ts +0 -78
package/index.dev.js CHANGED
@@ -1,143 +1,434 @@
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 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 (!isNonNullObject(a) || !isNonNullObject(b))
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
- if (Array.isArray(a) && Array.isArray(b)) {
150
- if (a.length !== b.length)
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 < a.length; i++) {
153
- if (!isEqual(a[i], b[i], visited))
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
- 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
- }
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 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];
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
- } else if (oldHas && !newHas) {
205
- remove[key] = UNSET;
206
- continue;
207
- }
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;
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
- 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));
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
- 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) {
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 = this.#keys[i];
543
+ let key = keys[i];
465
544
  if (!key) {
466
- key = this.#generateKey(value);
467
- this.#keys[i] = key;
545
+ key = generateKey(value);
546
+ keys[i] = key;
468
547
  }
469
548
  record[key] = value;
470
549
  }
471
550
  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]);
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
- if (!this.#validate(key, value))
489
- continue;
490
- const signal = this.#signals.get(key);
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
- 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;
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
- 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);
578
+ if (structural) {
579
+ node.sources = null;
580
+ node.sourcesTail = null;
589
581
  }
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);
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
- remove[key] = signal.get();
599
+ yield signal;
604
600
  }
605
- }
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;
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
- #validate(key, value) {
655
- validateSignalValue(`${TYPE_STORE} for key "${key}"`, value);
656
- return true;
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
- #add(key, value) {
659
- if (!this.#validate(key, value))
660
- return false;
661
- this.#signals.set(key, createMutableSignal(value));
662
- return true;
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
- #change(changes) {
665
- if (Object.keys(changes.add).length) {
666
- for (const key in changes.add)
667
- this.#add(key, changes.add[key]);
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
- 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);
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
- if (Object.keys(changes.remove).length) {
682
- for (const key in changes.remove)
683
- this.remove(key);
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
- 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
- }
701
- byKey(key) {
702
- return this.#signals.get(key);
703
- }
704
- get() {
705
- subscribeTo(this);
706
- return this.#value;
707
- }
708
- set(newValue) {
709
- if (UNSET === newValue) {
710
- this.#signals.clear();
711
- notifyOf(this);
712
- unsubscribeAllFrom(this);
713
- return;
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
- const changed = this.#change(diff(this.#value, newValue));
716
- if (changed)
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 (!isSymbol(prop))
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 (isSymbol(prop))
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
- var isStore = (value) => isObjectOfType(value, TYPE_STORE);
771
-
1403
+ }
1404
+ function isStore(value) {
1405
+ return isObjectOfType(value, TYPE_STORE);
1406
+ }
772
1407
  // src/signal.ts
773
- var isSignal = (value) => isState(value) || isComputed(value) || isStore(value);
774
- var isMutableSignal = (value) => isState(value) || isStore(value) || isList(value);
1408
+ function createComputed(callback, options) {
1409
+ return isAsyncFunction(callback) ? createTask(callback, options) : createMemo(callback, options);
1410
+ }
775
1411
  function createSignal(value) {
776
- if (isMemoCallback(value))
777
- return new Memo(value);
778
- if (isTaskCallback(value))
779
- return new Task(value);
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 new List(value);
1421
+ return createList(value);
782
1422
  if (isRecord(value))
783
1423
  return createStore(value);
784
- return new State(value);
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 new List(value);
1432
+ return createList(value);
789
1433
  if (isRecord(value))
790
1434
  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
- }
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
- 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
- }
1437
+ function isComputed(value) {
1438
+ return isMemo(value) || isTask(value);
849
1439
  }
850
- function assert(condition, msg) {
851
- if (!condition)
852
- throw new FailedAssertionError(msg);
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
- 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 };
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
- isTaskCallback,
1117
- isSymbol,
1118
- isString,
1460
+ isTask,
1119
1461
  isStore,
1120
1462
  isState,
1121
1463
  isSignal,
1122
- isRef,
1123
- isRecordOrArray,
1464
+ isSensor,
1124
1465
  isRecord,
1125
1466
  isObjectOfType,
1126
- isNumber,
1127
1467
  isMutableSignal,
1128
- isMemoCallback,
1468
+ isMemo,
1129
1469
  isList,
1130
1470
  isFunction,
1131
1471
  isEqual,
1132
1472
  isComputed,
1133
1473
  isCollection,
1134
1474
  isAsyncFunction,
1135
- isAbortError,
1136
- guardMutableSignal,
1137
- flush,
1138
- diff,
1139
- createWatcher,
1475
+ createTask,
1140
1476
  createStore,
1477
+ createState,
1141
1478
  createSignal,
1142
- createError,
1479
+ createSensor,
1480
+ createScope,
1481
+ createMutableSignal,
1482
+ createMemo,
1483
+ createList,
1143
1484
  createEffect,
1144
1485
  createComputed,
1486
+ createCollection,
1145
1487
  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,
1488
+ UnsetSignalValueError,
1489
+ SKIP_EQUALITY,
1490
+ RequiredOwnerError,
1157
1491
  NullishSignalValueError,
1158
- Memo,
1159
- List,
1160
1492
  InvalidSignalValueError,
1161
- InvalidCollectionSourceError,
1162
1493
  InvalidCallbackError,
1163
- DuplicateKeyError,
1164
- DerivedCollection,
1165
- CircularDependencyError,
1166
- BaseStore
1494
+ CircularDependencyError
1167
1495
  };