@zeix/cause-effect 0.17.2 → 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 (50) hide show
  1. package/.ai-context.md +11 -5
  2. package/.github/copilot-instructions.md +1 -1
  3. package/.zed/settings.json +3 -0
  4. package/CLAUDE.md +18 -79
  5. package/README.md +23 -37
  6. package/archive/benchmark.ts +0 -5
  7. package/archive/collection.ts +5 -62
  8. package/archive/composite.ts +85 -0
  9. package/archive/computed.ts +17 -20
  10. package/archive/list.ts +6 -67
  11. package/archive/memo.ts +13 -14
  12. package/archive/store.ts +7 -66
  13. package/archive/task.ts +18 -20
  14. package/index.dev.js +438 -614
  15. package/index.js +1 -1
  16. package/index.ts +8 -19
  17. package/package.json +6 -6
  18. package/src/classes/collection.ts +59 -112
  19. package/src/classes/computed.ts +146 -189
  20. package/src/classes/list.ts +138 -105
  21. package/src/classes/ref.ts +16 -42
  22. package/src/classes/state.ts +16 -45
  23. package/src/classes/store.ts +107 -72
  24. package/src/effect.ts +9 -12
  25. package/src/errors.ts +12 -8
  26. package/src/signal.ts +3 -1
  27. package/src/system.ts +136 -154
  28. package/test/batch.test.ts +4 -11
  29. package/test/benchmark.test.ts +4 -2
  30. package/test/collection.test.ts +46 -306
  31. package/test/computed.test.ts +205 -223
  32. package/test/list.test.ts +35 -303
  33. package/test/ref.test.ts +38 -66
  34. package/test/state.test.ts +6 -12
  35. package/test/store.test.ts +37 -489
  36. package/test/util/dependency-graph.ts +2 -2
  37. package/tsconfig.build.json +11 -0
  38. package/tsconfig.json +5 -7
  39. package/types/index.d.ts +2 -2
  40. package/types/src/classes/collection.d.ts +4 -6
  41. package/types/src/classes/computed.d.ts +17 -37
  42. package/types/src/classes/list.d.ts +8 -6
  43. package/types/src/classes/ref.d.ts +7 -20
  44. package/types/src/classes/state.d.ts +5 -17
  45. package/types/src/classes/store.d.ts +12 -11
  46. package/types/src/errors.d.ts +2 -4
  47. package/types/src/signal.d.ts +3 -2
  48. package/types/src/system.d.ts +41 -44
  49. package/src/classes/composite.ts +0 -171
  50. package/types/src/classes/composite.d.ts +0 -15
package/index.dev.js CHANGED
@@ -1,38 +1,25 @@
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);
15
-
16
1
  // src/system.ts
17
2
  var activeWatcher;
18
- var unwatchMap = new WeakMap;
3
+ var watchersMap = new WeakMap;
4
+ var watchedCallbackMap = new WeakMap;
5
+ var unwatchedCallbackMap = new WeakMap;
19
6
  var pendingReactions = new Set;
20
7
  var batchDepth = 0;
21
8
  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) => {
9
+ var createWatcher = (push, pull) => {
29
10
  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);
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);
36
23
  };
37
24
  watcher.stop = () => {
38
25
  try {
@@ -44,38 +31,55 @@ var createWatcher = (react) => {
44
31
  };
45
32
  return watcher;
46
33
  };
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);
55
- }
56
- }
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);
34
+ var untrack = (callback) => {
35
+ const prev = activeWatcher;
36
+ activeWatcher = undefined;
37
+ try {
38
+ callback();
39
+ } finally {
40
+ activeWatcher = prev;
75
41
  }
76
42
  };
77
- var notifyWatchers = (watchers) => {
78
- if (!watchers.size)
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)
79
83
  return false;
80
84
  for (const react of watchers) {
81
85
  if (batchDepth)
@@ -85,24 +89,24 @@ var notifyWatchers = (watchers) => {
85
89
  }
86
90
  return true;
87
91
  };
88
- var flushPendingReactions = () => {
92
+ var flush = () => {
89
93
  while (pendingReactions.size) {
90
94
  const watchers = Array.from(pendingReactions);
91
95
  pendingReactions.clear();
92
- for (const watcher of watchers)
93
- watcher();
96
+ for (const react of watchers)
97
+ react();
94
98
  }
95
99
  };
96
- var batchSignalWrites = (callback) => {
100
+ var batch = (callback) => {
97
101
  batchDepth++;
98
102
  try {
99
103
  callback();
100
104
  } finally {
101
- flushPendingReactions();
105
+ flush();
102
106
  batchDepth--;
103
107
  }
104
108
  };
105
- var trackSignalReads = (watcher, run) => {
109
+ var track = (watcher, run) => {
106
110
  const prev = activeWatcher;
107
111
  activeWatcher = watcher || undefined;
108
112
  try {
@@ -111,44 +115,21 @@ var trackSignalReads = (watcher, run) => {
111
115
  activeWatcher = prev;
112
116
  }
113
117
  };
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
- }
134
- }
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
- }
147
- }
148
- throwError(true);
149
- };
150
- };
151
- var isHandledHook = (type, handled) => handled.includes(type);
118
+
119
+ // src/util.ts
120
+ var isString = (value) => typeof value === "string";
121
+ var isNumber = (value) => typeof value === "number";
122
+ var isSymbol = (value) => typeof value === "symbol";
123
+ var isFunction = (fn) => typeof fn === "function";
124
+ var isAsyncFunction = (fn) => isFunction(fn) && fn.constructor.name === "AsyncFunction";
125
+ var isSyncFunction = (fn) => isFunction(fn) && fn.constructor.name !== "AsyncFunction";
126
+ var isNonNullObject = (value) => value != null && typeof value === "object";
127
+ var isObjectOfType = (value, type) => Object.prototype.toString.call(value) === `[object ${type}]`;
128
+ var isRecord = (value) => isObjectOfType(value, "Object");
129
+ var isRecordOrArray = (value) => isRecord(value) || Array.isArray(value);
130
+ var isUniformArray = (value, guard = (item) => item != null) => Array.isArray(value) && value.every(guard);
131
+ var isAbortError = (error) => error instanceof DOMException && error.name === "AbortError";
132
+ var valueString = (value) => isString(value) ? `"${value}"` : !!value && typeof value === "object" ? JSON.stringify(value) : String(value);
152
133
 
153
134
  // src/diff.ts
154
135
  var isEqual = (a, b, visited) => {
@@ -241,83 +222,69 @@ var diff = (oldObj, newObj) => {
241
222
  var TYPE_COMPUTED = "Computed";
242
223
 
243
224
  class Memo {
244
- #watchers = new Set;
245
225
  #callback;
246
226
  #value;
247
227
  #error;
248
228
  #dirty = true;
249
229
  #computing = false;
250
230
  #watcher;
251
- #watchHookCallbacks;
252
- constructor(callback, initialValue = UNSET) {
231
+ constructor(callback, options) {
253
232
  validateCallback(this.constructor.name, callback, isMemoCallback);
254
- validateSignalValue(this.constructor.name, initialValue);
233
+ const initialValue = options?.initialValue ?? UNSET;
234
+ validateSignalValue(this.constructor.name, initialValue, options?.guard);
255
235
  this.#callback = callback;
256
236
  this.#value = initialValue;
237
+ if (options?.watched)
238
+ registerWatchCallbacks(this, options.watched, options.unwatched);
257
239
  }
258
240
  #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
- }
241
+ this.#watcher ||= createWatcher(() => {
242
+ this.#dirty = true;
243
+ if (!notifyOf(this))
244
+ this.#watcher?.stop();
245
+ }, () => {
246
+ if (this.#computing)
247
+ throw new CircularDependencyError("memo");
248
+ let result;
249
+ this.#computing = true;
250
+ try {
251
+ result = this.#callback(this.#value);
252
+ } catch (e) {
253
+ this.#value = UNSET;
254
+ this.#error = createError(e);
255
+ this.#computing = false;
256
+ return;
257
+ }
258
+ if (result == null || UNSET === result) {
259
+ this.#value = UNSET;
260
+ this.#error = undefined;
261
+ } else {
262
+ this.#value = result;
263
+ this.#error = undefined;
264
+ this.#dirty = false;
265
+ }
266
+ this.#computing = false;
267
+ });
268
+ this.#watcher.onCleanup(() => {
269
+ this.#watcher = undefined;
270
+ });
269
271
  return this.#watcher;
270
272
  }
271
273
  get [Symbol.toStringTag]() {
272
274
  return TYPE_COMPUTED;
273
275
  }
274
276
  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
- }
277
+ subscribeTo(this);
278
+ flush();
279
+ if (this.#dirty)
280
+ this.#getWatcher().run();
303
281
  if (this.#error)
304
282
  throw this.#error;
305
283
  return this.#value;
306
284
  }
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
- };
314
- }
315
- throw new InvalidHookError(this.constructor.name, type);
316
- }
317
285
  }
318
286
 
319
287
  class Task {
320
- #watchers = new Set;
321
288
  #callback;
322
289
  #value;
323
290
  #error;
@@ -326,22 +293,83 @@ class Task {
326
293
  #changed = false;
327
294
  #watcher;
328
295
  #controller;
329
- #watchHookCallbacks;
330
- constructor(callback, initialValue = UNSET) {
296
+ constructor(callback, options) {
331
297
  validateCallback(this.constructor.name, callback, isTaskCallback);
332
- validateSignalValue(this.constructor.name, initialValue);
298
+ const initialValue = options?.initialValue ?? UNSET;
299
+ validateSignalValue(this.constructor.name, initialValue, options?.guard);
333
300
  this.#callback = callback;
334
301
  this.#value = initialValue;
302
+ if (options?.watched)
303
+ registerWatchCallbacks(this, options.watched, options.unwatched);
335
304
  }
336
305
  #getWatcher() {
337
306
  if (!this.#watcher) {
307
+ const ok = (v) => {
308
+ if (!isEqual(v, this.#value)) {
309
+ this.#value = v;
310
+ this.#changed = true;
311
+ }
312
+ this.#error = undefined;
313
+ this.#dirty = false;
314
+ };
315
+ const nil = () => {
316
+ this.#changed = UNSET !== this.#value;
317
+ this.#value = UNSET;
318
+ this.#error = undefined;
319
+ };
320
+ const err = (e) => {
321
+ const newError = createError(e);
322
+ this.#changed = !this.#error || newError.name !== this.#error.name || newError.message !== this.#error.message;
323
+ this.#value = UNSET;
324
+ this.#error = newError;
325
+ };
326
+ const settle = (fn) => (arg) => {
327
+ this.#computing = false;
328
+ this.#controller = undefined;
329
+ fn(arg);
330
+ if (this.#changed && !notifyOf(this))
331
+ this.#watcher?.stop();
332
+ };
338
333
  this.#watcher = createWatcher(() => {
339
334
  this.#dirty = true;
340
335
  this.#controller?.abort();
341
- if (!notifyWatchers(this.#watchers))
336
+ if (!notifyOf(this))
342
337
  this.#watcher?.stop();
338
+ }, () => {
339
+ if (this.#computing)
340
+ throw new CircularDependencyError("task");
341
+ this.#changed = false;
342
+ if (this.#controller)
343
+ return this.#value;
344
+ this.#controller = new AbortController;
345
+ this.#controller.signal.addEventListener("abort", () => {
346
+ this.#computing = false;
347
+ this.#controller = undefined;
348
+ this.#getWatcher().run();
349
+ }, {
350
+ once: true
351
+ });
352
+ let result;
353
+ this.#computing = true;
354
+ try {
355
+ result = this.#callback(this.#value, this.#controller.signal);
356
+ } catch (e) {
357
+ if (isAbortError(e))
358
+ nil();
359
+ else
360
+ err(e);
361
+ this.#computing = false;
362
+ return;
363
+ }
364
+ if (result instanceof Promise)
365
+ result.then(settle(ok), settle(err));
366
+ else if (result == null || UNSET === result)
367
+ nil();
368
+ else
369
+ ok(result);
370
+ this.#computing = false;
343
371
  });
344
- this.#watcher.on(HOOK_CLEANUP, () => {
372
+ this.#watcher.onCleanup(() => {
345
373
  this.#controller?.abort();
346
374
  this.#controller = undefined;
347
375
  this.#watcher = undefined;
@@ -353,221 +381,36 @@ class Task {
353
381
  return TYPE_COMPUTED;
354
382
  }
355
383
  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;
409
- }
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
- });
384
+ subscribeTo(this);
385
+ flush();
418
386
  if (this.#dirty)
419
- compute();
387
+ this.#getWatcher().run();
420
388
  if (this.#error)
421
389
  throw this.#error;
422
390
  return this.#value;
423
391
  }
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
392
  }
435
- 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);
436
394
  var isComputed = (value) => isObjectOfType(value, TYPE_COMPUTED);
437
395
  var isMemoCallback = (value) => isSyncFunction(value) && value.length < 2;
438
396
  var isTaskCallback = (value) => isAsyncFunction(value) && value.length < 3;
439
397
 
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);
487
- }
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();
502
- }
503
- if (Object.keys(changes.change).length) {
504
- batchSignalWrites(() => {
505
- for (const key in changes.change) {
506
- 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))
511
- signal.set(value);
512
- }
513
- });
514
- triggerHook(this.#hookCallbacks.change, Object.keys(changes.change));
515
- }
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));
520
- }
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;
541
- }
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
- }
550
- }
551
- };
552
- }
553
- }
554
-
555
398
  // src/classes/state.ts
556
399
  var TYPE_STATE = "State";
557
400
 
558
401
  class State {
559
- #watchers = new Set;
560
402
  #value;
561
- #watchHookCallbacks;
562
- constructor(initialValue) {
563
- validateSignalValue(TYPE_STATE, initialValue);
403
+ constructor(initialValue, options) {
404
+ validateSignalValue(TYPE_STATE, initialValue, options?.guard);
564
405
  this.#value = initialValue;
406
+ if (options?.watched)
407
+ registerWatchCallbacks(this, options.watched, options.unwatched);
565
408
  }
566
409
  get [Symbol.toStringTag]() {
567
410
  return TYPE_STATE;
568
411
  }
569
412
  get() {
570
- subscribeActiveWatcher(this.#watchers, this.#watchHookCallbacks);
413
+ subscribeTo(this);
571
414
  return this.#value;
572
415
  }
573
416
  set(newValue) {
@@ -575,25 +418,14 @@ class State {
575
418
  if (isEqual(this.#value, newValue))
576
419
  return;
577
420
  this.#value = newValue;
578
- if (this.#watchers.size)
579
- notifyWatchers(this.#watchers);
421
+ notifyOf(this);
580
422
  if (UNSET === this.#value)
581
- this.#watchers.clear();
423
+ unsubscribeAllFrom(this);
582
424
  }
583
425
  update(updater) {
584
426
  validateCallback(`${TYPE_STATE} update`, updater);
585
427
  this.set(updater(this.#value));
586
428
  }
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
429
  }
598
430
  var isState = (value) => isObjectOfType(value, TYPE_STATE);
599
431
 
@@ -601,19 +433,27 @@ var isState = (value) => isObjectOfType(value, TYPE_STATE);
601
433
  var TYPE_LIST = "List";
602
434
 
603
435
  class List {
604
- #composite;
605
- #watchers = new Set;
606
- #hookCallbacks = {};
607
- #order = [];
436
+ #signals = new Map;
437
+ #keys = [];
608
438
  #generateKey;
609
- constructor(initialValue, keyConfig) {
439
+ #validate;
440
+ constructor(initialValue, options) {
610
441
  validateSignalValue(TYPE_LIST, initialValue, Array.isArray);
611
442
  let keyCounter = 0;
443
+ const keyConfig = options?.keyConfig;
612
444
  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);
445
+ this.#validate = (key, value) => {
446
+ validateSignalValue(`${TYPE_LIST} item for key "${key}"`, value, options?.guard);
615
447
  return true;
616
- }, (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);
617
457
  }
618
458
  #toRecord(array) {
619
459
  const record = {};
@@ -621,17 +461,51 @@ class List {
621
461
  const value = array[i];
622
462
  if (value === undefined)
623
463
  continue;
624
- let key = this.#order[i];
464
+ let key = this.#keys[i];
625
465
  if (!key) {
626
466
  key = this.#generateKey(value);
627
- this.#order[i] = key;
467
+ this.#keys[i] = key;
628
468
  }
629
469
  record[key] = value;
630
470
  }
631
471
  return record;
632
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
+ }
633
507
  get #value() {
634
- 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);
635
509
  }
636
510
  get [Symbol.toStringTag]() {
637
511
  return TYPE_LIST;
@@ -640,139 +514,117 @@ class List {
640
514
  return true;
641
515
  }
642
516
  *[Symbol.iterator]() {
643
- for (const key of this.#order) {
644
- const signal = this.#composite.signals.get(key);
517
+ for (const key of this.#keys) {
518
+ const signal = this.#signals.get(key);
645
519
  if (signal)
646
520
  yield signal;
647
521
  }
648
522
  }
649
523
  get length() {
650
- subscribeActiveWatcher(this.#watchers, this.#hookCallbacks[HOOK_WATCH]);
651
- return this.#order.length;
524
+ subscribeTo(this);
525
+ return this.#keys.length;
652
526
  }
653
527
  get() {
654
- subscribeActiveWatcher(this.#watchers, this.#hookCallbacks[HOOK_WATCH]);
528
+ subscribeTo(this);
655
529
  return this.#value;
656
530
  }
657
531
  set(newValue) {
658
532
  if (UNSET === newValue) {
659
- this.#composite.clear();
660
- notifyWatchers(this.#watchers);
661
- this.#watchers.clear();
533
+ this.#signals.clear();
534
+ notifyOf(this);
535
+ unsubscribeAllFrom(this);
662
536
  return;
663
537
  }
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);
673
- }
674
- this.#order = this.#order.filter(() => true);
675
- notifyWatchers(this.#watchers);
676
- }
538
+ const changes = diff(this.#toRecord(this.#value), this.#toRecord(newValue));
539
+ if (this.#change(changes))
540
+ notifyOf(this);
677
541
  }
678
542
  update(fn) {
679
543
  this.set(fn(this.get()));
680
544
  }
681
545
  at(index) {
682
- return this.#composite.signals.get(this.#order[index]);
546
+ return this.#signals.get(this.#keys[index]);
683
547
  }
684
548
  keys() {
685
- return this.#order.values();
549
+ subscribeTo(this);
550
+ return this.#keys.values();
686
551
  }
687
552
  byKey(key) {
688
- return this.#composite.signals.get(key);
553
+ return this.#signals.get(key);
689
554
  }
690
555
  keyAt(index) {
691
- return this.#order[index];
556
+ return this.#keys[index];
692
557
  }
693
558
  indexOfKey(key) {
694
- return this.#order.indexOf(key);
559
+ return this.#keys.indexOf(key);
695
560
  }
696
561
  add(value) {
697
562
  const key = this.#generateKey(value);
698
- if (this.#composite.signals.has(key))
563
+ if (this.#signals.has(key))
699
564
  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);
565
+ if (!this.#keys.includes(key))
566
+ this.#keys.push(key);
567
+ const ok = this.#add(key, value);
703
568
  if (ok)
704
- notifyWatchers(this.#watchers);
569
+ notifyOf(this);
705
570
  return key;
706
571
  }
707
572
  remove(keyOrIndex) {
708
- const key = isNumber(keyOrIndex) ? this.#order[keyOrIndex] : keyOrIndex;
709
- const ok = this.#composite.remove(key);
573
+ const key = isNumber(keyOrIndex) ? this.#keys[keyOrIndex] : keyOrIndex;
574
+ const ok = this.#signals.delete(key);
710
575
  if (ok) {
711
- const index = isNumber(keyOrIndex) ? keyOrIndex : this.#order.indexOf(key);
576
+ const index = isNumber(keyOrIndex) ? keyOrIndex : this.#keys.indexOf(key);
712
577
  if (index >= 0)
713
- this.#order.splice(index, 1);
714
- this.#order = this.#order.filter(() => true);
715
- notifyWatchers(this.#watchers);
578
+ this.#keys.splice(index, 1);
579
+ this.#keys = this.#keys.filter(() => true);
580
+ notifyOf(this);
716
581
  }
717
582
  }
718
583
  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])));
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])));
720
585
  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);
586
+ if (!isEqual(this.#keys, newOrder)) {
587
+ this.#keys = newOrder;
588
+ notifyOf(this);
725
589
  }
726
590
  }
727
591
  splice(start, deleteCount, ...items) {
728
- const length = this.#order.length;
592
+ const length = this.#keys.length;
729
593
  const actualStart = start < 0 ? Math.max(0, length + start) : Math.min(start, length);
730
594
  const actualDeleteCount = Math.max(0, Math.min(deleteCount ?? Math.max(0, length - Math.max(0, actualStart)), length - actualStart));
731
595
  const add = {};
732
596
  const remove = {};
733
597
  for (let i = 0;i < actualDeleteCount; i++) {
734
598
  const index = actualStart + i;
735
- const key = this.#order[index];
599
+ const key = this.#keys[index];
736
600
  if (key) {
737
- const signal = this.#composite.signals.get(key);
601
+ const signal = this.#signals.get(key);
738
602
  if (signal)
739
603
  remove[key] = signal.get();
740
604
  }
741
605
  }
742
- const newOrder = this.#order.slice(0, actualStart);
606
+ const newOrder = this.#keys.slice(0, actualStart);
743
607
  for (const item of items) {
744
608
  const key = this.#generateKey(item);
745
609
  newOrder.push(key);
746
610
  add[key] = item;
747
611
  }
748
- newOrder.push(...this.#order.slice(actualStart + actualDeleteCount));
612
+ newOrder.push(...this.#keys.slice(actualStart + actualDeleteCount));
749
613
  const changed = !!(Object.keys(add).length || Object.keys(remove).length);
750
614
  if (changed) {
751
- this.#composite.change({
615
+ this.#change({
752
616
  add,
753
617
  change: {},
754
618
  remove,
755
619
  changed
756
620
  });
757
- this.#order = newOrder.filter(() => true);
758
- notifyWatchers(this.#watchers);
621
+ this.#keys = newOrder.filter(() => true);
622
+ notifyOf(this);
759
623
  }
760
624
  return Object.values(remove);
761
625
  }
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);
771
- }
772
- throw new InvalidHookError(TYPE_LIST, type);
773
- }
774
- deriveCollection(callback) {
775
- return new DerivedCollection(this, callback);
626
+ deriveCollection(callback, options) {
627
+ return new DerivedCollection(this, callback, options);
776
628
  }
777
629
  }
778
630
  var isList = (value) => isObjectOfType(value, TYPE_LIST);
@@ -781,22 +633,57 @@ var isList = (value) => isObjectOfType(value, TYPE_LIST);
781
633
  var TYPE_STORE = "Store";
782
634
 
783
635
  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));
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);
793
647
  }
794
648
  get #value() {
795
649
  const record = {};
796
- for (const [key, signal] of this.#composite.signals.entries())
650
+ for (const [key, signal] of this.#signals.entries())
797
651
  record[key] = signal.get();
798
652
  return record;
799
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
+ }
800
687
  get [Symbol.toStringTag]() {
801
688
  return TYPE_STORE;
802
689
  }
@@ -804,62 +691,50 @@ class BaseStore {
804
691
  return false;
805
692
  }
806
693
  *[Symbol.iterator]() {
807
- for (const [key, signal] of this.#composite.signals.entries())
694
+ for (const [key, signal] of this.#signals.entries())
808
695
  yield [key, signal];
809
696
  }
810
697
  keys() {
811
- return this.#composite.signals.keys();
698
+ subscribeTo(this);
699
+ return this.#signals.keys();
812
700
  }
813
701
  byKey(key) {
814
- return this.#composite.signals.get(key);
702
+ return this.#signals.get(key);
815
703
  }
816
704
  get() {
817
- subscribeActiveWatcher(this.#watchers, this.#watchHookCallbacks);
705
+ subscribeTo(this);
818
706
  return this.#value;
819
707
  }
820
708
  set(newValue) {
821
709
  if (UNSET === newValue) {
822
- this.#composite.clear();
823
- notifyWatchers(this.#watchers);
824
- this.#watchers.clear();
710
+ this.#signals.clear();
711
+ notifyOf(this);
712
+ unsubscribeAllFrom(this);
825
713
  return;
826
714
  }
827
- const oldValue = this.#value;
828
- const changed = this.#composite.change(diff(oldValue, newValue));
715
+ const changed = this.#change(diff(this.#value, newValue));
829
716
  if (changed)
830
- notifyWatchers(this.#watchers);
717
+ notifyOf(this);
831
718
  }
832
719
  update(fn) {
833
720
  this.set(fn(this.get()));
834
721
  }
835
722
  add(key, value) {
836
- if (this.#composite.signals.has(key))
723
+ if (this.#signals.has(key))
837
724
  throw new DuplicateKeyError(TYPE_STORE, key, value);
838
- const ok = this.#composite.add(key, value);
725
+ const ok = this.#add(key, value);
839
726
  if (ok)
840
- notifyWatchers(this.#watchers);
727
+ notifyOf(this);
841
728
  return key;
842
729
  }
843
730
  remove(key) {
844
- const ok = this.#composite.remove(key);
731
+ const ok = this.#signals.delete(key);
845
732
  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);
857
- }
858
- throw new InvalidHookError(TYPE_STORE, type);
733
+ notifyOf(this);
859
734
  }
860
735
  }
861
- var createStore = (initialValue) => {
862
- const instance = new BaseStore(initialValue);
736
+ var createStore = (initialValue, options) => {
737
+ const instance = new BaseStore(initialValue, options);
863
738
  return new Proxy(instance, {
864
739
  get(target, prop) {
865
740
  if (prop in target) {
@@ -931,6 +806,13 @@ class DuplicateKeyError extends Error {
931
806
  }
932
807
  }
933
808
 
809
+ class FailedAssertionError extends Error {
810
+ constructor(message = "unexpected condition") {
811
+ super(`Assertion failed: ${message}`);
812
+ this.name = "FailedAssertionError";
813
+ }
814
+ }
815
+
934
816
  class InvalidCallbackError extends TypeError {
935
817
  constructor(where, value) {
936
818
  super(`Invalid ${where} callback ${valueString(value)}`);
@@ -945,13 +827,6 @@ class InvalidCollectionSourceError extends TypeError {
945
827
  }
946
828
  }
947
829
 
948
- class InvalidHookError extends TypeError {
949
- constructor(where, type) {
950
- super(`Invalid hook "${type}" in ${where}`);
951
- this.name = "InvalidHookError";
952
- }
953
- }
954
-
955
830
  class InvalidSignalValueError extends TypeError {
956
831
  constructor(where, value) {
957
832
  super(`Invalid signal value ${valueString(value)} in ${where}`);
@@ -972,6 +847,10 @@ class ReadonlySignalError extends Error {
972
847
  this.name = "ReadonlySignalError";
973
848
  }
974
849
  }
850
+ function assert(condition, msg) {
851
+ if (!condition)
852
+ throw new FailedAssertionError(msg);
853
+ }
975
854
  var createError = (reason) => reason instanceof Error ? reason : Error(String(reason));
976
855
  var validateCallback = (where, value, guard = isFunction) => {
977
856
  if (!guard(value))
@@ -993,14 +872,13 @@ var guardMutableSignal = (what, value, signal) => {
993
872
  var TYPE_COLLECTION = "Collection";
994
873
 
995
874
  class DerivedCollection {
996
- #watchers = new Set;
997
875
  #source;
998
876
  #callback;
999
877
  #signals = new Map;
1000
- #ownWatchers = new Map;
1001
- #hookCallbacks = {};
1002
- #order = [];
1003
- constructor(source, callback) {
878
+ #keys = [];
879
+ #dirty = true;
880
+ #watcher;
881
+ constructor(source, callback, options) {
1004
882
  validateCallback(TYPE_COLLECTION, callback);
1005
883
  if (isFunction(source))
1006
884
  source = source();
@@ -1014,46 +892,38 @@ class DerivedCollection {
1014
892
  continue;
1015
893
  this.#add(key);
1016
894
  }
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
- }
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);
1027
915
  }
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;
916
+ for (const key of removedKeys)
1037
917
  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);
918
+ for (const key of addedKeys)
919
+ this.#add(key);
920
+ this.#keys = newKeys;
921
+ this.#dirty = false;
1050
922
  });
1051
- this.#source.on(HOOK_SORT, (newOrder) => {
1052
- if (newOrder)
1053
- this.#order = [...newOrder];
1054
- notifyWatchers(this.#watchers);
1055
- triggerHook(this.#hookCallbacks.sort, newOrder);
923
+ this.#watcher.onCleanup(() => {
924
+ this.#watcher = undefined;
1056
925
  });
926
+ return this.#watcher;
1057
927
  }
1058
928
  #add(key) {
1059
929
  const computedCallback = isAsyncCollectionCallback(this.#callback) ? async (_, abort) => {
@@ -1069,21 +939,10 @@ class DerivedCollection {
1069
939
  };
1070
940
  const signal = createComputed(computedCallback);
1071
941
  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);
942
+ if (!this.#keys.includes(key))
943
+ this.#keys.push(key);
1076
944
  return true;
1077
945
  }
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
946
  get [Symbol.toStringTag]() {
1088
947
  return TYPE_COLLECTION;
1089
948
  }
@@ -1091,64 +950,44 @@ class DerivedCollection {
1091
950
  return true;
1092
951
  }
1093
952
  *[Symbol.iterator]() {
1094
- for (const key of this.#order) {
953
+ for (const key of this.#keys) {
1095
954
  const signal = this.#signals.get(key);
1096
955
  if (signal)
1097
956
  yield signal;
1098
957
  }
1099
958
  }
1100
959
  keys() {
1101
- return this.#order.values();
960
+ subscribeTo(this);
961
+ if (this.#dirty)
962
+ this.#getWatcher().run();
963
+ return this.#keys.values();
1102
964
  }
1103
965
  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);
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);
1106
970
  }
1107
971
  at(index) {
1108
- return this.#signals.get(this.#order[index]);
972
+ return this.#signals.get(this.#keys[index]);
1109
973
  }
1110
974
  byKey(key) {
1111
975
  return this.#signals.get(key);
1112
976
  }
1113
977
  keyAt(index) {
1114
- return this.#order[index];
978
+ return this.#keys[index];
1115
979
  }
1116
980
  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);
981
+ return this.#keys.indexOf(key);
1145
982
  }
1146
- deriveCollection(callback) {
1147
- return new DerivedCollection(this, callback);
983
+ deriveCollection(callback, options) {
984
+ return new DerivedCollection(this, callback, options);
1148
985
  }
1149
986
  get length() {
1150
- subscribeActiveWatcher(this.#watchers, this.#hookCallbacks[HOOK_WATCH]);
1151
- return this.#order.length;
987
+ subscribeTo(this);
988
+ if (this.#dirty)
989
+ this.#getWatcher().run();
990
+ return this.#keys.length;
1152
991
  }
1153
992
  }
1154
993
  var isCollection = (value) => isObjectOfType(value, TYPE_COLLECTION);
@@ -1158,32 +997,22 @@ var isAsyncCollectionCallback = (callback) => isAsyncFunction(callback);
1158
997
  var TYPE_REF = "Ref";
1159
998
 
1160
999
  class Ref {
1161
- #watchers = new Set;
1162
1000
  #value;
1163
- #watchHookCallbacks;
1164
- constructor(value, guard) {
1165
- validateSignalValue(TYPE_REF, value, guard);
1001
+ constructor(value, options) {
1002
+ validateSignalValue(TYPE_REF, value, options?.guard);
1166
1003
  this.#value = value;
1004
+ if (options?.watched)
1005
+ registerWatchCallbacks(this, options.watched, options.unwatched);
1167
1006
  }
1168
1007
  get [Symbol.toStringTag]() {
1169
1008
  return TYPE_REF;
1170
1009
  }
1171
1010
  get() {
1172
- subscribeActiveWatcher(this.#watchers, this.#watchHookCallbacks);
1011
+ subscribeTo(this);
1173
1012
  return this.#value;
1174
1013
  }
1175
1014
  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);
1015
+ notifyOf(this);
1187
1016
  }
1188
1017
  }
1189
1018
  var isRef = (value) => isObjectOfType(value, TYPE_REF);
@@ -1194,7 +1023,9 @@ var createEffect = (callback) => {
1194
1023
  const isAsync = isAsyncFunction(callback);
1195
1024
  let running = false;
1196
1025
  let controller;
1197
- const watcher = createWatcher(() => trackSignalReads(watcher, () => {
1026
+ const watcher = createWatcher(() => {
1027
+ watcher.run();
1028
+ }, () => {
1198
1029
  if (running)
1199
1030
  throw new CircularDependencyError("effect");
1200
1031
  running = true;
@@ -1207,7 +1038,7 @@ var createEffect = (callback) => {
1207
1038
  const currentController = controller;
1208
1039
  callback(controller.signal).then((cleanup2) => {
1209
1040
  if (isFunction(cleanup2) && controller === currentController)
1210
- watcher.on(HOOK_CLEANUP, cleanup2);
1041
+ watcher.onCleanup(cleanup2);
1211
1042
  }).catch((error) => {
1212
1043
  if (!isAbortError(error))
1213
1044
  console.error("Error in async effect callback:", error);
@@ -1215,14 +1046,14 @@ var createEffect = (callback) => {
1215
1046
  } else {
1216
1047
  cleanup = callback();
1217
1048
  if (isFunction(cleanup))
1218
- watcher.on(HOOK_CLEANUP, cleanup);
1049
+ watcher.onCleanup(cleanup);
1219
1050
  }
1220
1051
  } catch (error) {
1221
1052
  if (!isAbortError(error))
1222
1053
  console.error("Error in effect callback:", error);
1223
1054
  }
1224
1055
  running = false;
1225
- }));
1056
+ });
1226
1057
  watcher();
1227
1058
  return () => {
1228
1059
  controller?.abort();
@@ -1276,11 +1107,11 @@ export {
1276
1107
  valueString,
1277
1108
  validateSignalValue,
1278
1109
  validateCallback,
1279
- triggerHook,
1280
- trackSignalReads,
1281
- subscribeActiveWatcher,
1110
+ untrack,
1111
+ track,
1112
+ subscribeTo,
1282
1113
  resolve,
1283
- notifyWatchers,
1114
+ notifyOf,
1284
1115
  match,
1285
1116
  isTaskCallback,
1286
1117
  isSymbol,
@@ -1296,7 +1127,6 @@ export {
1296
1127
  isMutableSignal,
1297
1128
  isMemoCallback,
1298
1129
  isList,
1299
- isHandledHook,
1300
1130
  isFunction,
1301
1131
  isEqual,
1302
1132
  isComputed,
@@ -1304,7 +1134,7 @@ export {
1304
1134
  isAsyncFunction,
1305
1135
  isAbortError,
1306
1136
  guardMutableSignal,
1307
- flushPendingReactions,
1137
+ flush,
1308
1138
  diff,
1309
1139
  createWatcher,
1310
1140
  createStore,
@@ -1312,7 +1142,7 @@ export {
1312
1142
  createError,
1313
1143
  createEffect,
1314
1144
  createComputed,
1315
- batchSignalWrites,
1145
+ batch,
1316
1146
  UNSET,
1317
1147
  Task,
1318
1148
  TYPE_STORE,
@@ -1330,12 +1160,6 @@ export {
1330
1160
  InvalidSignalValueError,
1331
1161
  InvalidCollectionSourceError,
1332
1162
  InvalidCallbackError,
1333
- HOOK_WATCH,
1334
- HOOK_SORT,
1335
- HOOK_REMOVE,
1336
- HOOK_CLEANUP,
1337
- HOOK_CHANGE,
1338
- HOOK_ADD,
1339
1163
  DuplicateKeyError,
1340
1164
  DerivedCollection,
1341
1165
  CircularDependencyError,