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