@zeix/cause-effect 0.17.1 → 0.17.3

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 (57) hide show
  1. package/.ai-context.md +13 -0
  2. package/.github/copilot-instructions.md +4 -0
  3. package/.zed/settings.json +3 -0
  4. package/CLAUDE.md +41 -7
  5. package/README.md +48 -25
  6. package/archive/benchmark.ts +0 -5
  7. package/archive/collection.ts +6 -65
  8. package/archive/composite.ts +85 -0
  9. package/archive/computed.ts +18 -20
  10. package/archive/list.ts +7 -75
  11. package/archive/memo.ts +15 -15
  12. package/archive/state.ts +2 -1
  13. package/archive/store.ts +8 -78
  14. package/archive/task.ts +20 -25
  15. package/index.dev.js +508 -526
  16. package/index.js +1 -1
  17. package/index.ts +9 -11
  18. package/package.json +6 -6
  19. package/src/classes/collection.ts +70 -107
  20. package/src/classes/computed.ts +165 -149
  21. package/src/classes/list.ts +145 -107
  22. package/src/classes/ref.ts +19 -17
  23. package/src/classes/state.ts +21 -17
  24. package/src/classes/store.ts +125 -73
  25. package/src/diff.ts +2 -1
  26. package/src/effect.ts +17 -10
  27. package/src/errors.ts +14 -1
  28. package/src/resolve.ts +1 -1
  29. package/src/signal.ts +3 -2
  30. package/src/system.ts +159 -61
  31. package/src/util.ts +0 -6
  32. package/test/batch.test.ts +4 -11
  33. package/test/benchmark.test.ts +4 -2
  34. package/test/collection.test.ts +106 -107
  35. package/test/computed.test.ts +351 -112
  36. package/test/effect.test.ts +2 -2
  37. package/test/list.test.ts +62 -102
  38. package/test/ref.test.ts +128 -2
  39. package/test/state.test.ts +16 -22
  40. package/test/store.test.ts +101 -108
  41. package/test/util/dependency-graph.ts +2 -2
  42. package/tsconfig.build.json +11 -0
  43. package/tsconfig.json +5 -7
  44. package/types/index.d.ts +3 -3
  45. package/types/src/classes/collection.d.ts +9 -10
  46. package/types/src/classes/computed.d.ts +17 -20
  47. package/types/src/classes/list.d.ts +8 -6
  48. package/types/src/classes/ref.d.ts +8 -12
  49. package/types/src/classes/state.d.ts +5 -8
  50. package/types/src/classes/store.d.ts +14 -13
  51. package/types/src/effect.d.ts +1 -2
  52. package/types/src/errors.d.ts +2 -1
  53. package/types/src/signal.d.ts +3 -2
  54. package/types/src/system.d.ts +47 -34
  55. package/types/src/util.d.ts +1 -2
  56. package/src/classes/composite.ts +0 -176
  57. package/types/src/classes/composite.d.ts +0 -15
package/index.dev.js CHANGED
@@ -1,5 +1,122 @@
1
- // src/util.ts
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;
7
+ var batchDepth = 0;
2
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;
19
+ }
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();
30
+ }
31
+ };
32
+ return watcher;
33
+ };
34
+ var untrack = (callback) => {
35
+ const prev = activeWatcher;
36
+ activeWatcher = undefined;
37
+ try {
38
+ callback();
39
+ } finally {
40
+ activeWatcher = prev;
41
+ }
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);
60
+ }
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);
68
+ }
69
+ });
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);
87
+ else
88
+ react();
89
+ }
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();
98
+ }
99
+ };
100
+ var batch = (callback) => {
101
+ batchDepth++;
102
+ try {
103
+ callback();
104
+ } finally {
105
+ flush();
106
+ batchDepth--;
107
+ }
108
+ };
109
+ var track = (watcher, run) => {
110
+ const prev = activeWatcher;
111
+ activeWatcher = watcher || undefined;
112
+ try {
113
+ run();
114
+ } finally {
115
+ activeWatcher = prev;
116
+ }
117
+ };
118
+
119
+ // src/util.ts
3
120
  var isString = (value) => typeof value === "string";
4
121
  var isNumber = (value) => typeof value === "number";
5
122
  var isSymbol = (value) => typeof value === "symbol";
@@ -101,128 +218,66 @@ var diff = (oldObj, newObj) => {
101
218
  };
102
219
  };
103
220
 
104
- // src/system.ts
105
- var activeWatcher;
106
- var pendingReactions = new Set;
107
- var batchDepth = 0;
108
- var createWatcher = (react) => {
109
- const cleanups = new Set;
110
- const watcher = react;
111
- watcher.onCleanup = (cleanup) => {
112
- cleanups.add(cleanup);
113
- };
114
- watcher.stop = () => {
115
- for (const cleanup of cleanups)
116
- cleanup();
117
- cleanups.clear();
118
- };
119
- return watcher;
120
- };
121
- var subscribeActiveWatcher = (watchers) => {
122
- if (activeWatcher && !watchers.has(activeWatcher)) {
123
- const watcher = activeWatcher;
124
- watcher.onCleanup(() => watchers.delete(watcher));
125
- watchers.add(watcher);
126
- }
127
- };
128
- var notifyWatchers = (watchers) => {
129
- for (const react of watchers) {
130
- if (batchDepth)
131
- pendingReactions.add(react);
132
- else
133
- react();
134
- }
135
- };
136
- var flushPendingReactions = () => {
137
- while (pendingReactions.size) {
138
- const watchers = Array.from(pendingReactions);
139
- pendingReactions.clear();
140
- for (const watcher of watchers)
141
- watcher();
142
- }
143
- };
144
- var batchSignalWrites = (callback) => {
145
- batchDepth++;
146
- try {
147
- callback();
148
- } finally {
149
- flushPendingReactions();
150
- batchDepth--;
151
- }
152
- };
153
- var trackSignalReads = (watcher, run) => {
154
- const prev = activeWatcher;
155
- activeWatcher = watcher || undefined;
156
- try {
157
- run();
158
- } finally {
159
- activeWatcher = prev;
160
- }
161
- };
162
- var emitNotification = (listeners, payload) => {
163
- for (const listener of listeners) {
164
- if (batchDepth)
165
- pendingReactions.add(() => listener(payload));
166
- else
167
- listener(payload);
168
- }
169
- };
170
-
171
221
  // src/classes/computed.ts
172
222
  var TYPE_COMPUTED = "Computed";
173
223
 
174
224
  class Memo {
175
- #watchers = new Set;
176
225
  #callback;
177
226
  #value;
178
227
  #error;
179
228
  #dirty = true;
180
229
  #computing = false;
181
230
  #watcher;
182
- constructor(callback, initialValue = UNSET) {
183
- validateCallback("memo", callback, isMemoCallback);
184
- validateSignalValue("memo", initialValue);
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);
185
235
  this.#callback = callback;
186
236
  this.#value = initialValue;
187
- this.#watcher = createWatcher(() => {
237
+ if (options?.watched)
238
+ registerWatchCallbacks(this, options.watched, options.unwatched);
239
+ }
240
+ #getWatcher() {
241
+ this.#watcher ||= createWatcher(() => {
188
242
  this.#dirty = true;
189
- if (this.#watchers.size)
190
- notifyWatchers(this.#watchers);
191
- else
192
- this.#watcher.stop();
243
+ if (!notifyOf(this))
244
+ this.#watcher?.stop();
245
+ }, () => {
246
+ if (this.#computing)
247
+ throw new CircularDependencyError("memo");
248
+ let result;
249
+ this.#computing = true;
250
+ try {
251
+ result = this.#callback(this.#value);
252
+ } catch (e) {
253
+ this.#value = UNSET;
254
+ this.#error = createError(e);
255
+ this.#computing = false;
256
+ return;
257
+ }
258
+ if (result == null || UNSET === result) {
259
+ this.#value = UNSET;
260
+ this.#error = undefined;
261
+ } else {
262
+ this.#value = result;
263
+ this.#error = undefined;
264
+ this.#dirty = false;
265
+ }
266
+ this.#computing = false;
267
+ });
268
+ this.#watcher.onCleanup(() => {
269
+ this.#watcher = undefined;
193
270
  });
271
+ return this.#watcher;
194
272
  }
195
273
  get [Symbol.toStringTag]() {
196
274
  return TYPE_COMPUTED;
197
275
  }
198
276
  get() {
199
- subscribeActiveWatcher(this.#watchers);
200
- flushPendingReactions();
201
- if (this.#dirty) {
202
- trackSignalReads(this.#watcher, () => {
203
- if (this.#computing)
204
- throw new CircularDependencyError("memo");
205
- let result;
206
- this.#computing = true;
207
- try {
208
- result = this.#callback(this.#value);
209
- } catch (e) {
210
- this.#value = UNSET;
211
- this.#error = createError(e);
212
- this.#computing = false;
213
- return;
214
- }
215
- if (result == null || UNSET === result) {
216
- this.#value = UNSET;
217
- this.#error = undefined;
218
- } else {
219
- this.#value = result;
220
- this.#error = undefined;
221
- this.#dirty = false;
222
- }
223
- this.#computing = false;
224
- });
225
- }
277
+ subscribeTo(this);
278
+ flush();
279
+ if (this.#dirty)
280
+ this.#getWatcher().run();
226
281
  if (this.#error)
227
282
  throw this.#error;
228
283
  return this.#value;
@@ -230,7 +285,6 @@ class Memo {
230
285
  }
231
286
 
232
287
  class Task {
233
- #watchers = new Set;
234
288
  #callback;
235
289
  #value;
236
290
  #error;
@@ -239,245 +293,137 @@ class Task {
239
293
  #changed = false;
240
294
  #watcher;
241
295
  #controller;
242
- constructor(callback, initialValue = UNSET) {
243
- validateCallback("task", callback, isTaskCallback);
244
- validateSignalValue("task", initialValue);
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);
245
300
  this.#callback = callback;
246
301
  this.#value = initialValue;
247
- this.#watcher = createWatcher(() => {
248
- this.#dirty = true;
249
- this.#controller?.abort();
250
- if (this.#watchers.size)
251
- notifyWatchers(this.#watchers);
252
- else
253
- this.#watcher.stop();
254
- });
255
- this.#watcher.onCleanup(() => {
256
- this.#controller?.abort();
257
- });
258
- }
259
- get [Symbol.toStringTag]() {
260
- return TYPE_COMPUTED;
261
- }
262
- get() {
263
- subscribeActiveWatcher(this.#watchers);
264
- flushPendingReactions();
265
- const ok = (v) => {
266
- if (!isEqual(v, this.#value)) {
267
- this.#value = v;
268
- this.#changed = true;
269
- }
270
- this.#error = undefined;
271
- this.#dirty = false;
272
- };
273
- const nil = () => {
274
- this.#changed = UNSET !== this.#value;
275
- this.#value = UNSET;
276
- this.#error = undefined;
277
- };
278
- const err = (e) => {
279
- const newError = createError(e);
280
- this.#changed = !this.#error || newError.name !== this.#error.name || newError.message !== this.#error.message;
281
- this.#value = UNSET;
282
- this.#error = newError;
283
- };
284
- const settle = (fn) => (arg) => {
285
- this.#computing = false;
286
- this.#controller = undefined;
287
- fn(arg);
288
- if (this.#changed)
289
- notifyWatchers(this.#watchers);
290
- };
291
- const compute = () => trackSignalReads(this.#watcher, () => {
292
- if (this.#computing)
293
- throw new CircularDependencyError("task");
294
- this.#changed = false;
295
- if (this.#controller)
296
- return this.#value;
297
- this.#controller = new AbortController;
298
- this.#controller.signal.addEventListener("abort", () => {
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) => {
299
327
  this.#computing = false;
300
328
  this.#controller = undefined;
301
- compute();
302
- }, {
303
- once: true
304
- });
305
- let result;
306
- this.#computing = true;
307
- try {
308
- result = this.#callback(this.#value, this.#controller.signal);
309
- } catch (e) {
310
- if (isAbortError(e))
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)
311
367
  nil();
312
368
  else
313
- err(e);
369
+ ok(result);
314
370
  this.#computing = false;
315
- return;
316
- }
317
- if (result instanceof Promise)
318
- result.then(settle(ok), settle(err));
319
- else if (result == null || UNSET === result)
320
- nil();
321
- else
322
- ok(result);
323
- this.#computing = false;
324
- });
371
+ });
372
+ this.#watcher.onCleanup(() => {
373
+ this.#controller?.abort();
374
+ this.#controller = undefined;
375
+ this.#watcher = undefined;
376
+ });
377
+ }
378
+ return this.#watcher;
379
+ }
380
+ get [Symbol.toStringTag]() {
381
+ return TYPE_COMPUTED;
382
+ }
383
+ get() {
384
+ subscribeTo(this);
385
+ flush();
325
386
  if (this.#dirty)
326
- compute();
387
+ this.#getWatcher().run();
327
388
  if (this.#error)
328
389
  throw this.#error;
329
390
  return this.#value;
330
391
  }
331
392
  }
332
- var createComputed = (callback, initialValue = UNSET) => isAsyncFunction(callback) ? new Task(callback, initialValue) : new Memo(callback, initialValue);
393
+ var createComputed = (callback, options) => isAsyncFunction(callback) ? new Task(callback, options) : new Memo(callback, options);
333
394
  var isComputed = (value) => isObjectOfType(value, TYPE_COMPUTED);
334
395
  var isMemoCallback = (value) => isSyncFunction(value) && value.length < 2;
335
396
  var isTaskCallback = (value) => isAsyncFunction(value) && value.length < 3;
336
397
 
337
- // src/classes/composite.ts
338
- class Composite {
339
- signals = new Map;
340
- #validate;
341
- #create;
342
- #watchers = new Map;
343
- #listeners = {
344
- add: new Set,
345
- change: new Set,
346
- remove: new Set
347
- };
348
- #batching = false;
349
- constructor(values, validate, create) {
350
- this.#validate = validate;
351
- this.#create = create;
352
- this.change({
353
- add: values,
354
- change: {},
355
- remove: {},
356
- changed: true
357
- }, true);
358
- }
359
- #addWatcher(key) {
360
- const watcher = createWatcher(() => {
361
- trackSignalReads(watcher, () => {
362
- this.signals.get(key)?.get();
363
- if (!this.#batching)
364
- emitNotification(this.#listeners.change, [key]);
365
- });
366
- });
367
- this.#watchers.set(key, watcher);
368
- watcher();
369
- }
370
- add(key, value) {
371
- if (!this.#validate(key, value))
372
- return false;
373
- this.signals.set(key, this.#create(value));
374
- if (this.#listeners.change.size)
375
- this.#addWatcher(key);
376
- if (!this.#batching)
377
- emitNotification(this.#listeners.add, [key]);
378
- return true;
379
- }
380
- remove(key) {
381
- const ok = this.signals.delete(key);
382
- if (!ok)
383
- return false;
384
- const watcher = this.#watchers.get(key);
385
- if (watcher) {
386
- watcher.stop();
387
- this.#watchers.delete(key);
388
- }
389
- if (!this.#batching)
390
- emitNotification(this.#listeners.remove, [key]);
391
- return true;
392
- }
393
- change(changes, initialRun) {
394
- this.#batching = true;
395
- if (Object.keys(changes.add).length) {
396
- for (const key in changes.add)
397
- this.add(key, changes.add[key]);
398
- const notify = () => emitNotification(this.#listeners.add, Object.keys(changes.add));
399
- if (initialRun)
400
- setTimeout(notify, 0);
401
- else
402
- notify();
403
- }
404
- if (Object.keys(changes.change).length) {
405
- batchSignalWrites(() => {
406
- for (const key in changes.change) {
407
- const value = changes.change[key];
408
- if (!this.#validate(key, value))
409
- continue;
410
- const signal = this.signals.get(key);
411
- if (guardMutableSignal(`list item "${key}"`, value, signal))
412
- signal.set(value);
413
- }
414
- });
415
- emitNotification(this.#listeners.change, Object.keys(changes.change));
416
- }
417
- if (Object.keys(changes.remove).length) {
418
- for (const key in changes.remove)
419
- this.remove(key);
420
- emitNotification(this.#listeners.remove, Object.keys(changes.remove));
421
- }
422
- this.#batching = false;
423
- return changes.changed;
424
- }
425
- clear() {
426
- const keys = Array.from(this.signals.keys());
427
- this.signals.clear();
428
- this.#watchers.clear();
429
- emitNotification(this.#listeners.remove, keys);
430
- return true;
431
- }
432
- on(type, listener) {
433
- this.#listeners[type].add(listener);
434
- if (type === "change" && !this.#watchers.size) {
435
- this.#batching = true;
436
- for (const key of this.signals.keys())
437
- this.#addWatcher(key);
438
- this.#batching = false;
439
- }
440
- return () => {
441
- this.#listeners[type].delete(listener);
442
- if (type === "change" && !this.#listeners.change.size) {
443
- if (this.#watchers.size) {
444
- for (const watcher of this.#watchers.values())
445
- watcher.stop();
446
- this.#watchers.clear();
447
- }
448
- }
449
- };
450
- }
451
- }
452
-
453
398
  // src/classes/state.ts
454
399
  var TYPE_STATE = "State";
455
400
 
456
401
  class State {
457
- #watchers = new Set;
458
402
  #value;
459
- constructor(initialValue) {
460
- validateSignalValue("state", initialValue);
403
+ constructor(initialValue, options) {
404
+ validateSignalValue(TYPE_STATE, initialValue, options?.guard);
461
405
  this.#value = initialValue;
406
+ if (options?.watched)
407
+ registerWatchCallbacks(this, options.watched, options.unwatched);
462
408
  }
463
409
  get [Symbol.toStringTag]() {
464
410
  return TYPE_STATE;
465
411
  }
466
412
  get() {
467
- subscribeActiveWatcher(this.#watchers);
413
+ subscribeTo(this);
468
414
  return this.#value;
469
415
  }
470
416
  set(newValue) {
471
- validateSignalValue("state", newValue);
417
+ validateSignalValue(TYPE_STATE, newValue);
472
418
  if (isEqual(this.#value, newValue))
473
419
  return;
474
420
  this.#value = newValue;
475
- notifyWatchers(this.#watchers);
421
+ notifyOf(this);
476
422
  if (UNSET === this.#value)
477
- this.#watchers.clear();
423
+ unsubscribeAllFrom(this);
478
424
  }
479
425
  update(updater) {
480
- validateCallback("state update", updater);
426
+ validateCallback(`${TYPE_STATE} update`, updater);
481
427
  this.set(updater(this.#value));
482
428
  }
483
429
  }
@@ -487,21 +433,27 @@ var isState = (value) => isObjectOfType(value, TYPE_STATE);
487
433
  var TYPE_LIST = "List";
488
434
 
489
435
  class List {
490
- #composite;
491
- #watchers = new Set;
492
- #listeners = {
493
- sort: new Set
494
- };
495
- #order = [];
436
+ #signals = new Map;
437
+ #keys = [];
496
438
  #generateKey;
497
- constructor(initialValue, keyConfig) {
498
- validateSignalValue("list", initialValue, Array.isArray);
439
+ #validate;
440
+ constructor(initialValue, options) {
441
+ validateSignalValue(TYPE_LIST, initialValue, Array.isArray);
499
442
  let keyCounter = 0;
443
+ const keyConfig = options?.keyConfig;
500
444
  this.#generateKey = isString(keyConfig) ? () => `${keyConfig}${keyCounter++}` : isFunction(keyConfig) ? (item) => keyConfig(item) : () => String(keyCounter++);
501
- this.#composite = new Composite(this.#toRecord(initialValue), (key, value) => {
502
- validateSignalValue(`list for key "${key}"`, value);
445
+ this.#validate = (key, value) => {
446
+ validateSignalValue(`${TYPE_LIST} item for key "${key}"`, value, options?.guard);
503
447
  return true;
504
- }, (value) => new State(value));
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);
505
457
  }
506
458
  #toRecord(array) {
507
459
  const record = {};
@@ -509,17 +461,51 @@ class List {
509
461
  const value = array[i];
510
462
  if (value === undefined)
511
463
  continue;
512
- let key = this.#order[i];
464
+ let key = this.#keys[i];
513
465
  if (!key) {
514
466
  key = this.#generateKey(value);
515
- this.#order[i] = key;
467
+ this.#keys[i] = key;
516
468
  }
517
469
  record[key] = value;
518
470
  }
519
471
  return record;
520
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]);
483
+ }
484
+ if (Object.keys(changes.change).length) {
485
+ batch(() => {
486
+ for (const key in changes.change) {
487
+ const value = changes.change[key];
488
+ if (!this.#validate(key, value))
489
+ continue;
490
+ const signal = this.#signals.get(key);
491
+ if (guardMutableSignal(`${TYPE_LIST} item "${key}"`, value, signal))
492
+ signal.set(value);
493
+ }
494
+ });
495
+ }
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
+ }
521
507
  get #value() {
522
- return this.#order.map((key) => this.#composite.signals.get(key)?.get()).filter((v) => v !== undefined);
508
+ return this.#keys.map((key) => this.#signals.get(key)?.get()).filter((v) => v !== undefined);
523
509
  }
524
510
  get [Symbol.toStringTag]() {
525
511
  return TYPE_LIST;
@@ -528,136 +514,117 @@ class List {
528
514
  return true;
529
515
  }
530
516
  *[Symbol.iterator]() {
531
- for (const key of this.#order) {
532
- const signal = this.#composite.signals.get(key);
517
+ for (const key of this.#keys) {
518
+ const signal = this.#signals.get(key);
533
519
  if (signal)
534
520
  yield signal;
535
521
  }
536
522
  }
537
523
  get length() {
538
- subscribeActiveWatcher(this.#watchers);
539
- return this.#order.length;
524
+ subscribeTo(this);
525
+ return this.#keys.length;
540
526
  }
541
527
  get() {
542
- subscribeActiveWatcher(this.#watchers);
528
+ subscribeTo(this);
543
529
  return this.#value;
544
530
  }
545
531
  set(newValue) {
546
532
  if (UNSET === newValue) {
547
- this.#composite.clear();
548
- notifyWatchers(this.#watchers);
549
- this.#watchers.clear();
533
+ this.#signals.clear();
534
+ notifyOf(this);
535
+ unsubscribeAllFrom(this);
550
536
  return;
551
537
  }
552
- const oldValue = this.#value;
553
- const changes = diff(this.#toRecord(oldValue), this.#toRecord(newValue));
554
- const removedKeys = Object.keys(changes.remove);
555
- const changed = this.#composite.change(changes);
556
- if (changed) {
557
- for (const key of removedKeys) {
558
- const index = this.#order.indexOf(key);
559
- if (index !== -1)
560
- this.#order.splice(index, 1);
561
- }
562
- this.#order = this.#order.filter(() => true);
563
- notifyWatchers(this.#watchers);
564
- }
538
+ const changes = diff(this.#toRecord(this.#value), this.#toRecord(newValue));
539
+ if (this.#change(changes))
540
+ notifyOf(this);
565
541
  }
566
542
  update(fn) {
567
543
  this.set(fn(this.get()));
568
544
  }
569
545
  at(index) {
570
- return this.#composite.signals.get(this.#order[index]);
546
+ return this.#signals.get(this.#keys[index]);
571
547
  }
572
548
  keys() {
573
- return this.#order.values();
549
+ subscribeTo(this);
550
+ return this.#keys.values();
574
551
  }
575
552
  byKey(key) {
576
- return this.#composite.signals.get(key);
553
+ return this.#signals.get(key);
577
554
  }
578
555
  keyAt(index) {
579
- return this.#order[index];
556
+ return this.#keys[index];
580
557
  }
581
558
  indexOfKey(key) {
582
- return this.#order.indexOf(key);
559
+ return this.#keys.indexOf(key);
583
560
  }
584
561
  add(value) {
585
562
  const key = this.#generateKey(value);
586
- if (this.#composite.signals.has(key))
563
+ if (this.#signals.has(key))
587
564
  throw new DuplicateKeyError("store", key, value);
588
- if (!this.#order.includes(key))
589
- this.#order.push(key);
590
- const ok = this.#composite.add(key, value);
565
+ if (!this.#keys.includes(key))
566
+ this.#keys.push(key);
567
+ const ok = this.#add(key, value);
591
568
  if (ok)
592
- notifyWatchers(this.#watchers);
569
+ notifyOf(this);
593
570
  return key;
594
571
  }
595
572
  remove(keyOrIndex) {
596
- const key = isNumber(keyOrIndex) ? this.#order[keyOrIndex] : keyOrIndex;
597
- const ok = this.#composite.remove(key);
573
+ const key = isNumber(keyOrIndex) ? this.#keys[keyOrIndex] : keyOrIndex;
574
+ const ok = this.#signals.delete(key);
598
575
  if (ok) {
599
- const index = isNumber(keyOrIndex) ? keyOrIndex : this.#order.indexOf(key);
576
+ const index = isNumber(keyOrIndex) ? keyOrIndex : this.#keys.indexOf(key);
600
577
  if (index >= 0)
601
- this.#order.splice(index, 1);
602
- this.#order = this.#order.filter(() => true);
603
- notifyWatchers(this.#watchers);
578
+ this.#keys.splice(index, 1);
579
+ this.#keys = this.#keys.filter(() => true);
580
+ notifyOf(this);
604
581
  }
605
582
  }
606
583
  sort(compareFn) {
607
- 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])));
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])));
608
585
  const newOrder = entries.map(([key]) => key);
609
- if (!isEqual(this.#order, newOrder)) {
610
- this.#order = newOrder;
611
- notifyWatchers(this.#watchers);
612
- emitNotification(this.#listeners.sort, this.#order);
586
+ if (!isEqual(this.#keys, newOrder)) {
587
+ this.#keys = newOrder;
588
+ notifyOf(this);
613
589
  }
614
590
  }
615
591
  splice(start, deleteCount, ...items) {
616
- const length = this.#order.length;
592
+ const length = this.#keys.length;
617
593
  const actualStart = start < 0 ? Math.max(0, length + start) : Math.min(start, length);
618
594
  const actualDeleteCount = Math.max(0, Math.min(deleteCount ?? Math.max(0, length - Math.max(0, actualStart)), length - actualStart));
619
595
  const add = {};
620
596
  const remove = {};
621
597
  for (let i = 0;i < actualDeleteCount; i++) {
622
598
  const index = actualStart + i;
623
- const key = this.#order[index];
599
+ const key = this.#keys[index];
624
600
  if (key) {
625
- const signal = this.#composite.signals.get(key);
601
+ const signal = this.#signals.get(key);
626
602
  if (signal)
627
603
  remove[key] = signal.get();
628
604
  }
629
605
  }
630
- const newOrder = this.#order.slice(0, actualStart);
606
+ const newOrder = this.#keys.slice(0, actualStart);
631
607
  for (const item of items) {
632
608
  const key = this.#generateKey(item);
633
609
  newOrder.push(key);
634
610
  add[key] = item;
635
611
  }
636
- newOrder.push(...this.#order.slice(actualStart + actualDeleteCount));
612
+ newOrder.push(...this.#keys.slice(actualStart + actualDeleteCount));
637
613
  const changed = !!(Object.keys(add).length || Object.keys(remove).length);
638
614
  if (changed) {
639
- this.#composite.change({
615
+ this.#change({
640
616
  add,
641
617
  change: {},
642
618
  remove,
643
619
  changed
644
620
  });
645
- this.#order = newOrder.filter(() => true);
646
- notifyWatchers(this.#watchers);
621
+ this.#keys = newOrder.filter(() => true);
622
+ notifyOf(this);
647
623
  }
648
624
  return Object.values(remove);
649
625
  }
650
- on(type, listener) {
651
- if (type === "sort") {
652
- this.#listeners.sort.add(listener);
653
- return () => {
654
- this.#listeners.sort.delete(listener);
655
- };
656
- }
657
- return this.#composite.on(type, listener);
658
- }
659
- deriveCollection(callback) {
660
- return new DerivedCollection(this, callback);
626
+ deriveCollection(callback, options) {
627
+ return new DerivedCollection(this, callback, options);
661
628
  }
662
629
  }
663
630
  var isList = (value) => isObjectOfType(value, TYPE_LIST);
@@ -666,21 +633,57 @@ var isList = (value) => isObjectOfType(value, TYPE_LIST);
666
633
  var TYPE_STORE = "Store";
667
634
 
668
635
  class BaseStore {
669
- #composite;
670
- #watchers = new Set;
671
- constructor(initialValue) {
672
- validateSignalValue("store", initialValue, isRecord);
673
- this.#composite = new Composite(initialValue, (key, value) => {
674
- validateSignalValue(`store for key "${key}"`, value);
675
- return true;
676
- }, (value) => createMutableSignal(value));
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);
677
647
  }
678
648
  get #value() {
679
649
  const record = {};
680
- for (const [key, signal] of this.#composite.signals.entries())
650
+ for (const [key, signal] of this.#signals.entries())
681
651
  record[key] = signal.get();
682
652
  return record;
683
653
  }
654
+ #validate(key, value) {
655
+ validateSignalValue(`${TYPE_STORE} for key "${key}"`, value);
656
+ return true;
657
+ }
658
+ #add(key, value) {
659
+ if (!this.#validate(key, value))
660
+ return false;
661
+ this.#signals.set(key, createMutableSignal(value));
662
+ return true;
663
+ }
664
+ #change(changes) {
665
+ if (Object.keys(changes.add).length) {
666
+ for (const key in changes.add)
667
+ this.#add(key, changes.add[key]);
668
+ }
669
+ if (Object.keys(changes.change).length) {
670
+ batch(() => {
671
+ for (const key in changes.change) {
672
+ const value = changes.change[key];
673
+ if (!this.#validate(key, value))
674
+ continue;
675
+ const signal = this.#signals.get(key);
676
+ if (guardMutableSignal(`list item "${key}"`, value, signal))
677
+ signal.set(value);
678
+ }
679
+ });
680
+ }
681
+ if (Object.keys(changes.remove).length) {
682
+ for (const key in changes.remove)
683
+ this.remove(key);
684
+ }
685
+ return changes.changed;
686
+ }
684
687
  get [Symbol.toStringTag]() {
685
688
  return TYPE_STORE;
686
689
  }
@@ -688,53 +691,50 @@ class BaseStore {
688
691
  return false;
689
692
  }
690
693
  *[Symbol.iterator]() {
691
- for (const [key, signal] of this.#composite.signals.entries())
694
+ for (const [key, signal] of this.#signals.entries())
692
695
  yield [key, signal];
693
696
  }
697
+ keys() {
698
+ subscribeTo(this);
699
+ return this.#signals.keys();
700
+ }
701
+ byKey(key) {
702
+ return this.#signals.get(key);
703
+ }
694
704
  get() {
695
- subscribeActiveWatcher(this.#watchers);
705
+ subscribeTo(this);
696
706
  return this.#value;
697
707
  }
698
708
  set(newValue) {
699
709
  if (UNSET === newValue) {
700
- this.#composite.clear();
701
- notifyWatchers(this.#watchers);
702
- this.#watchers.clear();
710
+ this.#signals.clear();
711
+ notifyOf(this);
712
+ unsubscribeAllFrom(this);
703
713
  return;
704
714
  }
705
- const oldValue = this.#value;
706
- const changed = this.#composite.change(diff(oldValue, newValue));
715
+ const changed = this.#change(diff(this.#value, newValue));
707
716
  if (changed)
708
- notifyWatchers(this.#watchers);
709
- }
710
- keys() {
711
- return this.#composite.signals.keys();
712
- }
713
- byKey(key) {
714
- return this.#composite.signals.get(key);
717
+ notifyOf(this);
715
718
  }
716
719
  update(fn) {
717
720
  this.set(fn(this.get()));
718
721
  }
719
722
  add(key, value) {
720
- if (this.#composite.signals.has(key))
721
- throw new DuplicateKeyError("store", key, value);
722
- const ok = this.#composite.add(key, value);
723
+ if (this.#signals.has(key))
724
+ throw new DuplicateKeyError(TYPE_STORE, key, value);
725
+ const ok = this.#add(key, value);
723
726
  if (ok)
724
- notifyWatchers(this.#watchers);
727
+ notifyOf(this);
725
728
  return key;
726
729
  }
727
730
  remove(key) {
728
- const ok = this.#composite.remove(key);
731
+ const ok = this.#signals.delete(key);
729
732
  if (ok)
730
- notifyWatchers(this.#watchers);
731
- }
732
- on(type, listener) {
733
- return this.#composite.on(type, listener);
733
+ notifyOf(this);
734
734
  }
735
735
  }
736
- var createStore = (initialValue) => {
737
- const instance = new BaseStore(initialValue);
736
+ var createStore = (initialValue, options) => {
737
+ const instance = new BaseStore(initialValue, options);
738
738
  return new Proxy(instance, {
739
739
  get(target, prop) {
740
740
  if (prop in target) {
@@ -806,6 +806,13 @@ class DuplicateKeyError extends Error {
806
806
  }
807
807
  }
808
808
 
809
+ class FailedAssertionError extends Error {
810
+ constructor(message = "unexpected condition") {
811
+ super(`Assertion failed: ${message}`);
812
+ this.name = "FailedAssertionError";
813
+ }
814
+ }
815
+
809
816
  class InvalidCallbackError extends TypeError {
810
817
  constructor(where, value) {
811
818
  super(`Invalid ${where} callback ${valueString(value)}`);
@@ -840,6 +847,10 @@ class ReadonlySignalError extends Error {
840
847
  this.name = "ReadonlySignalError";
841
848
  }
842
849
  }
850
+ function assert(condition, msg) {
851
+ if (!condition)
852
+ throw new FailedAssertionError(msg);
853
+ }
843
854
  var createError = (reason) => reason instanceof Error ? reason : Error(String(reason));
844
855
  var validateCallback = (where, value, guard = isFunction) => {
845
856
  if (!guard(value))
@@ -861,24 +872,18 @@ var guardMutableSignal = (what, value, signal) => {
861
872
  var TYPE_COLLECTION = "Collection";
862
873
 
863
874
  class DerivedCollection {
864
- #watchers = new Set;
865
875
  #source;
866
876
  #callback;
867
877
  #signals = new Map;
868
- #ownWatchers = new Map;
869
- #listeners = {
870
- add: new Set,
871
- change: new Set,
872
- remove: new Set,
873
- sort: new Set
874
- };
875
- #order = [];
876
- constructor(source, callback) {
877
- validateCallback("collection", callback);
878
+ #keys = [];
879
+ #dirty = true;
880
+ #watcher;
881
+ constructor(source, callback, options) {
882
+ validateCallback(TYPE_COLLECTION, callback);
878
883
  if (isFunction(source))
879
884
  source = source();
880
885
  if (!isCollectionSource(source))
881
- throw new InvalidCollectionSourceError("derived collection", source);
886
+ throw new InvalidCollectionSourceError(TYPE_COLLECTION, source);
882
887
  this.#source = source;
883
888
  this.#callback = callback;
884
889
  for (let i = 0;i < this.#source.length; i++) {
@@ -887,77 +892,57 @@ class DerivedCollection {
887
892
  continue;
888
893
  this.#add(key);
889
894
  }
890
- this.#source.on("add", (additions) => {
891
- for (const key of additions) {
892
- if (!this.#signals.has(key)) {
893
- this.#add(key);
894
- const signal = this.#signals.get(key);
895
- if (signal && isAsyncCollectionCallback(this.#callback))
896
- signal.get();
897
- }
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);
898
915
  }
899
- notifyWatchers(this.#watchers);
900
- emitNotification(this.#listeners.add, additions);
901
- });
902
- this.#source.on("remove", (removals) => {
903
- for (const key of removals) {
904
- if (!this.#signals.has(key))
905
- continue;
916
+ for (const key of removedKeys)
906
917
  this.#signals.delete(key);
907
- const index = this.#order.indexOf(key);
908
- if (index >= 0)
909
- this.#order.splice(index, 1);
910
- const watcher = this.#ownWatchers.get(key);
911
- if (watcher) {
912
- watcher.stop();
913
- this.#ownWatchers.delete(key);
914
- }
915
- }
916
- this.#order = this.#order.filter(() => true);
917
- notifyWatchers(this.#watchers);
918
- emitNotification(this.#listeners.remove, removals);
918
+ for (const key of addedKeys)
919
+ this.#add(key);
920
+ this.#keys = newKeys;
921
+ this.#dirty = false;
919
922
  });
920
- this.#source.on("sort", (newOrder) => {
921
- this.#order = [...newOrder];
922
- notifyWatchers(this.#watchers);
923
- emitNotification(this.#listeners.sort, newOrder);
923
+ this.#watcher.onCleanup(() => {
924
+ this.#watcher = undefined;
924
925
  });
926
+ return this.#watcher;
925
927
  }
926
928
  #add(key) {
927
929
  const computedCallback = isAsyncCollectionCallback(this.#callback) ? async (_, abort) => {
928
- const sourceSignal = this.#source.byKey(key);
929
- if (!sourceSignal)
930
- return UNSET;
931
- const sourceValue = sourceSignal.get();
930
+ const sourceValue = this.#source.byKey(key)?.get();
932
931
  if (sourceValue === UNSET)
933
932
  return UNSET;
934
933
  return this.#callback(sourceValue, abort);
935
934
  } : () => {
936
- const sourceSignal = this.#source.byKey(key);
937
- if (!sourceSignal)
938
- return UNSET;
939
- const sourceValue = sourceSignal.get();
935
+ const sourceValue = this.#source.byKey(key)?.get();
940
936
  if (sourceValue === UNSET)
941
937
  return UNSET;
942
938
  return this.#callback(sourceValue);
943
939
  };
944
940
  const signal = createComputed(computedCallback);
945
941
  this.#signals.set(key, signal);
946
- if (!this.#order.includes(key))
947
- this.#order.push(key);
948
- if (this.#listeners.change.size)
949
- this.#addWatcher(key);
942
+ if (!this.#keys.includes(key))
943
+ this.#keys.push(key);
950
944
  return true;
951
945
  }
952
- #addWatcher(key) {
953
- const watcher = createWatcher(() => {
954
- trackSignalReads(watcher, () => {
955
- this.#signals.get(key)?.get();
956
- });
957
- });
958
- this.#ownWatchers.set(key, watcher);
959
- watcher();
960
- }
961
946
  get [Symbol.toStringTag]() {
962
947
  return TYPE_COLLECTION;
963
948
  }
@@ -965,54 +950,44 @@ class DerivedCollection {
965
950
  return true;
966
951
  }
967
952
  *[Symbol.iterator]() {
968
- for (const key of this.#order) {
953
+ for (const key of this.#keys) {
969
954
  const signal = this.#signals.get(key);
970
955
  if (signal)
971
956
  yield signal;
972
957
  }
973
958
  }
974
- get length() {
975
- subscribeActiveWatcher(this.#watchers);
976
- return this.#order.length;
959
+ keys() {
960
+ subscribeTo(this);
961
+ if (this.#dirty)
962
+ this.#getWatcher().run();
963
+ return this.#keys.values();
977
964
  }
978
965
  get() {
979
- subscribeActiveWatcher(this.#watchers);
980
- return this.#order.map((key) => this.#signals.get(key)?.get()).filter((v) => v != null && v !== UNSET);
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);
981
970
  }
982
971
  at(index) {
983
- return this.#signals.get(this.#order[index]);
984
- }
985
- keys() {
986
- return this.#order.values();
972
+ return this.#signals.get(this.#keys[index]);
987
973
  }
988
974
  byKey(key) {
989
975
  return this.#signals.get(key);
990
976
  }
991
977
  keyAt(index) {
992
- return this.#order[index];
978
+ return this.#keys[index];
993
979
  }
994
980
  indexOfKey(key) {
995
- return this.#order.indexOf(key);
981
+ return this.#keys.indexOf(key);
996
982
  }
997
- on(type, listener) {
998
- this.#listeners[type].add(listener);
999
- if (type === "change" && !this.#ownWatchers.size) {
1000
- for (const key of this.#signals.keys())
1001
- this.#addWatcher(key);
1002
- }
1003
- return () => {
1004
- this.#listeners[type].delete(listener);
1005
- if (type === "change" && !this.#listeners.change.size) {
1006
- if (this.#ownWatchers.size) {
1007
- for (const watcher of this.#ownWatchers.values())
1008
- watcher.stop();
1009
- this.#ownWatchers.clear();
1010
- }
1011
- }
1012
- };
983
+ deriveCollection(callback, options) {
984
+ return new DerivedCollection(this, callback, options);
1013
985
  }
1014
- deriveCollection(callback) {
1015
- return new DerivedCollection(this, callback);
986
+ get length() {
987
+ subscribeTo(this);
988
+ if (this.#dirty)
989
+ this.#getWatcher().run();
990
+ return this.#keys.length;
1016
991
  }
1017
992
  }
1018
993
  var isCollection = (value) => isObjectOfType(value, TYPE_COLLECTION);
@@ -1022,21 +997,22 @@ var isAsyncCollectionCallback = (callback) => isAsyncFunction(callback);
1022
997
  var TYPE_REF = "Ref";
1023
998
 
1024
999
  class Ref {
1025
- #watchers = new Set;
1026
1000
  #value;
1027
- constructor(value, guard) {
1028
- validateSignalValue("ref", value, guard);
1001
+ constructor(value, options) {
1002
+ validateSignalValue(TYPE_REF, value, options?.guard);
1029
1003
  this.#value = value;
1004
+ if (options?.watched)
1005
+ registerWatchCallbacks(this, options.watched, options.unwatched);
1030
1006
  }
1031
1007
  get [Symbol.toStringTag]() {
1032
1008
  return TYPE_REF;
1033
1009
  }
1034
1010
  get() {
1035
- subscribeActiveWatcher(this.#watchers);
1011
+ subscribeTo(this);
1036
1012
  return this.#value;
1037
1013
  }
1038
1014
  notify() {
1039
- notifyWatchers(this.#watchers);
1015
+ notifyOf(this);
1040
1016
  }
1041
1017
  }
1042
1018
  var isRef = (value) => isObjectOfType(value, TYPE_REF);
@@ -1047,7 +1023,9 @@ var createEffect = (callback) => {
1047
1023
  const isAsync = isAsyncFunction(callback);
1048
1024
  let running = false;
1049
1025
  let controller;
1050
- const watcher = createWatcher(() => trackSignalReads(watcher, () => {
1026
+ const watcher = createWatcher(() => {
1027
+ watcher.run();
1028
+ }, () => {
1051
1029
  if (running)
1052
1030
  throw new CircularDependencyError("effect");
1053
1031
  running = true;
@@ -1063,7 +1041,7 @@ var createEffect = (callback) => {
1063
1041
  watcher.onCleanup(cleanup2);
1064
1042
  }).catch((error) => {
1065
1043
  if (!isAbortError(error))
1066
- console.error("Async effect error:", error);
1044
+ console.error("Error in async effect callback:", error);
1067
1045
  });
1068
1046
  } else {
1069
1047
  cleanup = callback();
@@ -1072,14 +1050,18 @@ var createEffect = (callback) => {
1072
1050
  }
1073
1051
  } catch (error) {
1074
1052
  if (!isAbortError(error))
1075
- console.error("Effect callback error:", error);
1053
+ console.error("Error in effect callback:", error);
1076
1054
  }
1077
1055
  running = false;
1078
- }));
1056
+ });
1079
1057
  watcher();
1080
1058
  return () => {
1081
1059
  controller?.abort();
1082
- watcher.stop();
1060
+ try {
1061
+ watcher.stop();
1062
+ } catch (error) {
1063
+ console.error("Error in effect cleanup:", error);
1064
+ }
1083
1065
  };
1084
1066
  };
1085
1067
  // src/match.ts
@@ -1125,10 +1107,11 @@ export {
1125
1107
  valueString,
1126
1108
  validateSignalValue,
1127
1109
  validateCallback,
1128
- trackSignalReads,
1129
- subscribeActiveWatcher,
1110
+ untrack,
1111
+ track,
1112
+ subscribeTo,
1130
1113
  resolve,
1131
- notifyWatchers,
1114
+ notifyOf,
1132
1115
  match,
1133
1116
  isTaskCallback,
1134
1117
  isSymbol,
@@ -1151,8 +1134,7 @@ export {
1151
1134
  isAsyncFunction,
1152
1135
  isAbortError,
1153
1136
  guardMutableSignal,
1154
- flushPendingReactions,
1155
- emitNotification,
1137
+ flush,
1156
1138
  diff,
1157
1139
  createWatcher,
1158
1140
  createStore,
@@ -1160,7 +1142,7 @@ export {
1160
1142
  createError,
1161
1143
  createEffect,
1162
1144
  createComputed,
1163
- batchSignalWrites,
1145
+ batch,
1164
1146
  UNSET,
1165
1147
  Task,
1166
1148
  TYPE_STORE,