@zeix/cause-effect 0.16.1 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/.ai-context.md +71 -21
  2. package/.cursorrules +3 -2
  3. package/.github/copilot-instructions.md +59 -13
  4. package/CLAUDE.md +170 -24
  5. package/LICENSE +1 -1
  6. package/README.md +156 -52
  7. package/archive/benchmark.ts +688 -0
  8. package/archive/collection.ts +312 -0
  9. package/{src → archive}/computed.ts +19 -19
  10. package/archive/list.ts +551 -0
  11. package/archive/memo.ts +138 -0
  12. package/{src → archive}/state.ts +13 -11
  13. package/archive/store.ts +368 -0
  14. package/archive/task.ts +194 -0
  15. package/eslint.config.js +1 -0
  16. package/index.dev.js +899 -503
  17. package/index.js +1 -1
  18. package/index.ts +41 -22
  19. package/package.json +1 -1
  20. package/src/classes/collection.ts +272 -0
  21. package/src/classes/composite.ts +176 -0
  22. package/src/classes/computed.ts +333 -0
  23. package/src/classes/list.ts +304 -0
  24. package/src/classes/state.ts +98 -0
  25. package/src/classes/store.ts +210 -0
  26. package/src/diff.ts +26 -53
  27. package/src/effect.ts +9 -9
  28. package/src/errors.ts +50 -25
  29. package/src/signal.ts +58 -41
  30. package/src/system.ts +79 -42
  31. package/src/util.ts +16 -30
  32. package/test/batch.test.ts +15 -17
  33. package/test/benchmark.test.ts +4 -4
  34. package/test/collection.test.ts +796 -0
  35. package/test/computed.test.ts +138 -130
  36. package/test/diff.test.ts +2 -2
  37. package/test/effect.test.ts +36 -35
  38. package/test/list.test.ts +754 -0
  39. package/test/match.test.ts +25 -25
  40. package/test/resolve.test.ts +17 -19
  41. package/test/signal.test.ts +70 -119
  42. package/test/state.test.ts +44 -44
  43. package/test/store.test.ts +253 -929
  44. package/types/index.d.ts +10 -8
  45. package/types/src/classes/collection.d.ts +32 -0
  46. package/types/src/classes/composite.d.ts +15 -0
  47. package/types/src/classes/computed.d.ts +97 -0
  48. package/types/src/classes/list.d.ts +41 -0
  49. package/types/src/classes/state.d.ts +52 -0
  50. package/types/src/classes/store.d.ts +51 -0
  51. package/types/src/diff.d.ts +8 -12
  52. package/types/src/errors.d.ts +12 -11
  53. package/types/src/signal.d.ts +27 -14
  54. package/types/src/system.d.ts +41 -20
  55. package/types/src/util.d.ts +6 -3
  56. package/src/store.ts +0 -474
  57. package/types/src/collection.d.ts +0 -26
  58. package/types/src/computed.d.ts +0 -33
  59. package/types/src/scheduler.d.ts +0 -55
  60. package/types/src/state.d.ts +0 -24
  61. package/types/src/store.d.ts +0 -65
package/index.dev.js CHANGED
@@ -1,53 +1,3 @@
1
- // src/errors.ts
2
- class CircularDependencyError extends Error {
3
- constructor(where) {
4
- super(`Circular dependency detected in ${where}`);
5
- this.name = "CircularDependencyError";
6
- }
7
- }
8
-
9
- class InvalidCallbackError extends TypeError {
10
- constructor(where, value) {
11
- super(`Invalid ${where} callback ${value}`);
12
- this.name = "InvalidCallbackError";
13
- }
14
- }
15
-
16
- class InvalidSignalValueError extends TypeError {
17
- constructor(where, value) {
18
- super(`Invalid signal value ${value} in ${where}`);
19
- this.name = "InvalidSignalValueError";
20
- }
21
- }
22
-
23
- class NullishSignalValueError extends TypeError {
24
- constructor(where) {
25
- super(`Nullish signal values are not allowed in ${where}`);
26
- this.name = "NullishSignalValueError";
27
- }
28
- }
29
-
30
- class StoreKeyExistsError extends Error {
31
- constructor(key, value) {
32
- super(`Could not add store key "${key}" with value ${value} because it already exists`);
33
- this.name = "StoreKeyExistsError";
34
- }
35
- }
36
-
37
- class StoreKeyRangeError extends RangeError {
38
- constructor(index) {
39
- super(`Could not remove store index ${String(index)} because it is out of range`);
40
- this.name = "StoreKeyRangeError";
41
- }
42
- }
43
-
44
- class StoreKeyReadonlyError extends Error {
45
- constructor(key, value) {
46
- super(`Could not set store key "${key}" to ${value} because it is readonly`);
47
- this.name = "StoreKeyReadonlyError";
48
- }
49
- }
50
-
51
1
  // src/util.ts
52
2
  var UNSET = Symbol();
53
3
  var isString = (value) => typeof value === "string";
@@ -55,26 +5,14 @@ var isNumber = (value) => typeof value === "number";
55
5
  var isSymbol = (value) => typeof value === "symbol";
56
6
  var isFunction = (fn) => typeof fn === "function";
57
7
  var isAsyncFunction = (fn) => isFunction(fn) && fn.constructor.name === "AsyncFunction";
8
+ var isSyncFunction = (fn) => isFunction(fn) && fn.constructor.name !== "AsyncFunction";
9
+ var isNonNullObject = (value) => value != null && typeof value === "object";
58
10
  var isObjectOfType = (value, type) => Object.prototype.toString.call(value) === `[object ${type}]`;
59
11
  var isRecord = (value) => isObjectOfType(value, "Object");
60
12
  var isRecordOrArray = (value) => isRecord(value) || Array.isArray(value);
61
- var validArrayIndexes = (keys) => {
62
- if (!keys.length)
63
- return null;
64
- const indexes = keys.map((k) => isString(k) ? parseInt(k, 10) : isNumber(k) ? k : NaN);
65
- return indexes.every((index) => Number.isFinite(index) && index >= 0) ? indexes.sort((a, b) => a - b) : null;
66
- };
13
+ var isUniformArray = (value, guard = (item) => item != null) => Array.isArray(value) && value.every(guard);
67
14
  var isAbortError = (error) => error instanceof DOMException && error.name === "AbortError";
68
15
  var toError = (reason) => reason instanceof Error ? reason : Error(String(reason));
69
- var recordToArray = (record) => {
70
- const indexes = validArrayIndexes(Object.keys(record));
71
- if (indexes === null)
72
- return record;
73
- const array = [];
74
- for (const index of indexes)
75
- array.push(record[String(index)]);
76
- return array;
77
- };
78
16
  var valueString = (value) => isString(value) ? `"${value}"` : !!value && typeof value === "object" ? JSON.stringify(value) : String(value);
79
17
 
80
18
  // src/diff.ts
@@ -83,7 +21,7 @@ var isEqual = (a, b, visited) => {
83
21
  return true;
84
22
  if (typeof a !== typeof b)
85
23
  return false;
86
- if (typeof a !== "object" || a === null || b === null)
24
+ if (!isNonNullObject(a) || !isNonNullObject(b))
87
25
  return false;
88
26
  if (!visited)
89
27
  visited = new WeakSet;
@@ -126,12 +64,12 @@ var diff = (oldObj, newObj) => {
126
64
  const oldValid = isRecordOrArray(oldObj);
127
65
  const newValid = isRecordOrArray(newObj);
128
66
  if (!oldValid || !newValid) {
129
- const changed2 = !Object.is(oldObj, newObj);
67
+ const changed = !Object.is(oldObj, newObj);
130
68
  return {
131
- changed: changed2,
132
- add: changed2 && newValid ? newObj : {},
69
+ changed,
70
+ add: changed && newValid ? newObj : {},
133
71
  change: {},
134
- remove: changed2 && oldValid ? oldObj : {}
72
+ remove: changed && oldValid ? oldObj : {}
135
73
  };
136
74
  }
137
75
  const visited = new WeakSet;
@@ -156,192 +94,935 @@ var diff = (oldObj, newObj) => {
156
94
  if (!isEqual(oldValue, newValue, visited))
157
95
  change[key] = newValue;
158
96
  }
159
- const changed = Object.keys(add).length > 0 || Object.keys(change).length > 0 || Object.keys(remove).length > 0;
160
97
  return {
161
- changed,
162
98
  add,
163
99
  change,
164
- remove
100
+ remove,
101
+ changed: !!(Object.keys(add).length || Object.keys(change).length || Object.keys(remove).length)
165
102
  };
166
103
  };
167
104
 
168
105
  // src/system.ts
169
106
  var activeWatcher;
170
- var pendingWatchers = new Set;
107
+ var pendingReactions = new Set;
171
108
  var batchDepth = 0;
172
- var createWatcher = (watch) => {
109
+ var createWatcher = (react) => {
173
110
  const cleanups = new Set;
174
- const w = watch;
175
- w.unwatch = (cleanup) => {
111
+ const watcher = react;
112
+ watcher.onCleanup = (cleanup) => {
176
113
  cleanups.add(cleanup);
177
114
  };
178
- w.cleanup = () => {
115
+ watcher.stop = () => {
179
116
  for (const cleanup of cleanups)
180
117
  cleanup();
181
118
  cleanups.clear();
182
119
  };
183
- return w;
120
+ return watcher;
184
121
  };
185
- var subscribe = (watchers) => {
122
+ var subscribeActiveWatcher = (watchers) => {
186
123
  if (activeWatcher && !watchers.has(activeWatcher)) {
187
124
  const watcher = activeWatcher;
188
- watcher.unwatch(() => {
189
- watchers.delete(watcher);
190
- });
125
+ watcher.onCleanup(() => watchers.delete(watcher));
191
126
  watchers.add(watcher);
192
127
  }
193
128
  };
194
- var notify = (watchers) => {
195
- for (const watcher of watchers) {
129
+ var notifyWatchers = (watchers) => {
130
+ for (const react of watchers) {
196
131
  if (batchDepth)
197
- pendingWatchers.add(watcher);
132
+ pendingReactions.add(react);
198
133
  else
199
- watcher();
134
+ react();
200
135
  }
201
136
  };
202
- var flush = () => {
203
- while (pendingWatchers.size) {
204
- const watchers = Array.from(pendingWatchers);
205
- pendingWatchers.clear();
137
+ var flushPendingReactions = () => {
138
+ while (pendingReactions.size) {
139
+ const watchers = Array.from(pendingReactions);
140
+ pendingReactions.clear();
206
141
  for (const watcher of watchers)
207
142
  watcher();
208
143
  }
209
144
  };
210
- var batch = (fn) => {
145
+ var batchSignalWrites = (callback) => {
211
146
  batchDepth++;
212
147
  try {
213
- fn();
148
+ callback();
214
149
  } finally {
215
- flush();
150
+ flushPendingReactions();
216
151
  batchDepth--;
217
152
  }
218
153
  };
219
- var observe = (run, watcher) => {
154
+ var trackSignalReads = (watcher, run) => {
220
155
  const prev = activeWatcher;
221
- activeWatcher = watcher;
156
+ activeWatcher = watcher || undefined;
222
157
  try {
223
158
  run();
224
159
  } finally {
225
160
  activeWatcher = prev;
226
161
  }
227
162
  };
163
+ var emitNotification = (listeners, payload) => {
164
+ for (const listener of listeners) {
165
+ if (batchDepth)
166
+ pendingReactions.add(() => listener(payload));
167
+ else
168
+ listener(payload);
169
+ }
170
+ };
228
171
 
229
- // src/computed.ts
172
+ // src/classes/computed.ts
230
173
  var TYPE_COMPUTED = "Computed";
231
- var createComputed = (callback, initialValue = UNSET) => {
232
- if (!isComputedCallback(callback))
233
- throw new InvalidCallbackError("computed", valueString(callback));
234
- if (initialValue == null)
235
- throw new NullishSignalValueError("computed");
236
- const watchers = new Set;
237
- let value = initialValue;
238
- let error;
239
- let controller;
240
- let dirty = true;
241
- let changed = false;
242
- let computing = false;
243
- const ok = (v) => {
244
- if (!isEqual(v, value)) {
245
- value = v;
246
- changed = true;
174
+
175
+ class Memo {
176
+ #watchers = new Set;
177
+ #callback;
178
+ #value;
179
+ #error;
180
+ #dirty = true;
181
+ #computing = false;
182
+ #watcher;
183
+ constructor(callback, initialValue = UNSET) {
184
+ validateCallback("memo", callback, isMemoCallback);
185
+ validateSignalValue("memo", initialValue);
186
+ this.#callback = callback;
187
+ this.#value = initialValue;
188
+ this.#watcher = createWatcher(() => {
189
+ this.#dirty = true;
190
+ if (this.#watchers.size)
191
+ notifyWatchers(this.#watchers);
192
+ else
193
+ this.#watcher.stop();
194
+ });
195
+ }
196
+ get [Symbol.toStringTag]() {
197
+ return TYPE_COMPUTED;
198
+ }
199
+ get() {
200
+ subscribeActiveWatcher(this.#watchers);
201
+ flushPendingReactions();
202
+ if (this.#dirty) {
203
+ trackSignalReads(this.#watcher, () => {
204
+ if (this.#computing)
205
+ throw new CircularDependencyError("memo");
206
+ let result;
207
+ this.#computing = true;
208
+ try {
209
+ result = this.#callback(this.#value);
210
+ } catch (e) {
211
+ this.#value = UNSET;
212
+ this.#error = toError(e);
213
+ this.#computing = false;
214
+ return;
215
+ }
216
+ if (result == null || UNSET === result) {
217
+ this.#value = UNSET;
218
+ this.#error = undefined;
219
+ } else {
220
+ this.#value = result;
221
+ this.#error = undefined;
222
+ this.#dirty = false;
223
+ }
224
+ this.#computing = false;
225
+ });
247
226
  }
248
- error = undefined;
249
- dirty = false;
250
- };
251
- const nil = () => {
252
- changed = UNSET !== value;
253
- value = UNSET;
254
- error = undefined;
255
- };
256
- const err = (e) => {
257
- const newError = toError(e);
258
- changed = !error || newError.name !== error.name || newError.message !== error.message;
259
- value = UNSET;
260
- error = newError;
261
- };
262
- const settle = (fn) => (arg) => {
263
- computing = false;
264
- controller = undefined;
265
- fn(arg);
266
- if (changed)
267
- notify(watchers);
268
- };
269
- const watcher = createWatcher(() => {
270
- dirty = true;
271
- controller?.abort();
272
- if (watchers.size)
273
- notify(watchers);
274
- else
275
- watcher.cleanup();
276
- });
277
- watcher.unwatch(() => {
278
- controller?.abort();
279
- });
280
- const compute = () => observe(() => {
281
- if (computing)
282
- throw new CircularDependencyError("computed");
283
- changed = false;
284
- if (isAsyncFunction(callback)) {
285
- if (controller)
286
- return value;
287
- controller = new AbortController;
288
- controller.signal.addEventListener("abort", () => {
289
- computing = false;
290
- controller = undefined;
227
+ if (this.#error)
228
+ throw this.#error;
229
+ return this.#value;
230
+ }
231
+ }
232
+
233
+ class Task {
234
+ #watchers = new Set;
235
+ #callback;
236
+ #value;
237
+ #error;
238
+ #dirty = true;
239
+ #computing = false;
240
+ #changed = false;
241
+ #watcher;
242
+ #controller;
243
+ constructor(callback, initialValue = UNSET) {
244
+ validateCallback("task", callback, isTaskCallback);
245
+ validateSignalValue("task", initialValue);
246
+ this.#callback = callback;
247
+ this.#value = initialValue;
248
+ this.#watcher = createWatcher(() => {
249
+ this.#dirty = true;
250
+ this.#controller?.abort();
251
+ if (this.#watchers.size)
252
+ notifyWatchers(this.#watchers);
253
+ else
254
+ this.#watcher.stop();
255
+ });
256
+ this.#watcher.onCleanup(() => {
257
+ this.#controller?.abort();
258
+ });
259
+ }
260
+ get [Symbol.toStringTag]() {
261
+ return TYPE_COMPUTED;
262
+ }
263
+ get() {
264
+ subscribeActiveWatcher(this.#watchers);
265
+ flushPendingReactions();
266
+ const ok = (v) => {
267
+ if (!isEqual(v, this.#value)) {
268
+ this.#value = v;
269
+ this.#changed = true;
270
+ }
271
+ this.#error = undefined;
272
+ this.#dirty = false;
273
+ };
274
+ const nil = () => {
275
+ this.#changed = UNSET !== this.#value;
276
+ this.#value = UNSET;
277
+ this.#error = undefined;
278
+ };
279
+ const err = (e) => {
280
+ const newError = toError(e);
281
+ this.#changed = !this.#error || newError.name !== this.#error.name || newError.message !== this.#error.message;
282
+ this.#value = UNSET;
283
+ this.#error = newError;
284
+ };
285
+ const settle = (fn) => (arg) => {
286
+ this.#computing = false;
287
+ this.#controller = undefined;
288
+ fn(arg);
289
+ if (this.#changed)
290
+ notifyWatchers(this.#watchers);
291
+ };
292
+ const compute = () => trackSignalReads(this.#watcher, () => {
293
+ if (this.#computing)
294
+ throw new CircularDependencyError("task");
295
+ this.#changed = false;
296
+ if (this.#controller)
297
+ return this.#value;
298
+ this.#controller = new AbortController;
299
+ this.#controller.signal.addEventListener("abort", () => {
300
+ this.#computing = false;
301
+ this.#controller = undefined;
291
302
  compute();
292
303
  }, {
293
304
  once: true
294
305
  });
295
- }
296
- let result;
297
- computing = true;
298
- try {
299
- result = controller ? callback(value, controller.signal) : callback(value);
300
- } catch (e) {
301
- if (isAbortError(e))
306
+ let result;
307
+ this.#computing = true;
308
+ try {
309
+ result = this.#callback(this.#value, this.#controller.signal);
310
+ } catch (e) {
311
+ if (isAbortError(e))
312
+ nil();
313
+ else
314
+ err(e);
315
+ this.#computing = false;
316
+ return;
317
+ }
318
+ if (result instanceof Promise)
319
+ result.then(settle(ok), settle(err));
320
+ else if (result == null || UNSET === result)
302
321
  nil();
303
322
  else
304
- err(e);
305
- computing = false;
323
+ ok(result);
324
+ this.#computing = false;
325
+ });
326
+ if (this.#dirty)
327
+ compute();
328
+ if (this.#error)
329
+ throw this.#error;
330
+ return this.#value;
331
+ }
332
+ }
333
+ var createComputed = (callback, initialValue = UNSET) => isAsyncFunction(callback) ? new Task(callback, initialValue) : new Memo(callback, initialValue);
334
+ var isComputed = (value) => isObjectOfType(value, TYPE_COMPUTED);
335
+ var isMemoCallback = (value) => isSyncFunction(value) && value.length < 2;
336
+ var isTaskCallback = (value) => isAsyncFunction(value) && value.length < 3;
337
+
338
+ // src/classes/composite.ts
339
+ class Composite {
340
+ signals = new Map;
341
+ #validate;
342
+ #create;
343
+ #watchers = new Map;
344
+ #listeners = {
345
+ add: new Set,
346
+ change: new Set,
347
+ remove: new Set
348
+ };
349
+ #batching = false;
350
+ constructor(values, validate, create) {
351
+ this.#validate = validate;
352
+ this.#create = create;
353
+ this.change({
354
+ add: values,
355
+ change: {},
356
+ remove: {},
357
+ changed: true
358
+ }, true);
359
+ }
360
+ #addWatcher(key) {
361
+ const watcher = createWatcher(() => {
362
+ trackSignalReads(watcher, () => {
363
+ this.signals.get(key)?.get();
364
+ if (!this.#batching)
365
+ emitNotification(this.#listeners.change, [key]);
366
+ });
367
+ });
368
+ this.#watchers.set(key, watcher);
369
+ watcher();
370
+ }
371
+ add(key, value) {
372
+ if (!this.#validate(key, value))
373
+ return false;
374
+ this.signals.set(key, this.#create(value));
375
+ if (this.#listeners.change.size)
376
+ this.#addWatcher(key);
377
+ if (!this.#batching)
378
+ emitNotification(this.#listeners.add, [key]);
379
+ return true;
380
+ }
381
+ remove(key) {
382
+ const ok = this.signals.delete(key);
383
+ if (!ok)
384
+ return false;
385
+ const watcher = this.#watchers.get(key);
386
+ if (watcher) {
387
+ watcher.stop();
388
+ this.#watchers.delete(key);
389
+ }
390
+ if (!this.#batching)
391
+ emitNotification(this.#listeners.remove, [key]);
392
+ return true;
393
+ }
394
+ change(changes, initialRun) {
395
+ this.#batching = true;
396
+ if (Object.keys(changes.add).length) {
397
+ for (const key in changes.add)
398
+ this.add(key, changes.add[key]);
399
+ const notify = () => emitNotification(this.#listeners.add, Object.keys(changes.add));
400
+ if (initialRun)
401
+ setTimeout(notify, 0);
402
+ else
403
+ notify();
404
+ }
405
+ if (Object.keys(changes.change).length) {
406
+ batchSignalWrites(() => {
407
+ for (const key in changes.change) {
408
+ const value = changes.change[key];
409
+ if (!this.#validate(key, value))
410
+ continue;
411
+ const signal = this.signals.get(key);
412
+ if (guardMutableSignal(`list item "${key}"`, value, signal))
413
+ signal.set(value);
414
+ }
415
+ });
416
+ emitNotification(this.#listeners.change, Object.keys(changes.change));
417
+ }
418
+ if (Object.keys(changes.remove).length) {
419
+ for (const key in changes.remove)
420
+ this.remove(key);
421
+ emitNotification(this.#listeners.remove, Object.keys(changes.remove));
422
+ }
423
+ this.#batching = false;
424
+ return changes.changed;
425
+ }
426
+ clear() {
427
+ const keys = Array.from(this.signals.keys());
428
+ this.signals.clear();
429
+ this.#watchers.clear();
430
+ emitNotification(this.#listeners.remove, keys);
431
+ return true;
432
+ }
433
+ on(type, listener) {
434
+ this.#listeners[type].add(listener);
435
+ if (type === "change" && !this.#watchers.size) {
436
+ this.#batching = true;
437
+ for (const key of this.signals.keys())
438
+ this.#addWatcher(key);
439
+ this.#batching = false;
440
+ }
441
+ return () => {
442
+ this.#listeners[type].delete(listener);
443
+ if (type === "change" && !this.#listeners.change.size) {
444
+ if (this.#watchers.size) {
445
+ for (const watcher of this.#watchers.values())
446
+ watcher.stop();
447
+ this.#watchers.clear();
448
+ }
449
+ }
450
+ };
451
+ }
452
+ }
453
+
454
+ // src/classes/state.ts
455
+ var TYPE_STATE = "State";
456
+
457
+ class State {
458
+ #watchers = new Set;
459
+ #value;
460
+ constructor(initialValue) {
461
+ validateSignalValue("state", initialValue);
462
+ this.#value = initialValue;
463
+ }
464
+ get [Symbol.toStringTag]() {
465
+ return TYPE_STATE;
466
+ }
467
+ get() {
468
+ subscribeActiveWatcher(this.#watchers);
469
+ return this.#value;
470
+ }
471
+ set(newValue) {
472
+ validateSignalValue("state", newValue);
473
+ if (isEqual(this.#value, newValue))
306
474
  return;
475
+ this.#value = newValue;
476
+ notifyWatchers(this.#watchers);
477
+ if (UNSET === this.#value)
478
+ this.#watchers.clear();
479
+ }
480
+ update(updater) {
481
+ validateCallback("state update", updater);
482
+ this.set(updater(this.#value));
483
+ }
484
+ }
485
+ var isState = (value) => isObjectOfType(value, TYPE_STATE);
486
+
487
+ // src/classes/list.ts
488
+ var TYPE_LIST = "List";
489
+
490
+ class List {
491
+ #composite;
492
+ #watchers = new Set;
493
+ #listeners = {
494
+ sort: new Set
495
+ };
496
+ #order = [];
497
+ #generateKey;
498
+ constructor(initialValue, keyConfig) {
499
+ validateSignalValue("list", initialValue, Array.isArray);
500
+ let keyCounter = 0;
501
+ this.#generateKey = isString(keyConfig) ? () => `${keyConfig}${keyCounter++}` : isFunction(keyConfig) ? (item) => keyConfig(item) : () => String(keyCounter++);
502
+ this.#composite = new Composite(this.#toRecord(initialValue), (key, value) => {
503
+ validateSignalValue(`list for key "${key}"`, value);
504
+ return true;
505
+ }, (value) => new State(value));
506
+ }
507
+ #toRecord(array) {
508
+ const record = {};
509
+ for (let i = 0;i < array.length; i++) {
510
+ const value = array[i];
511
+ if (value === undefined)
512
+ continue;
513
+ let key = this.#order[i];
514
+ if (!key) {
515
+ key = this.#generateKey(value);
516
+ this.#order[i] = key;
517
+ }
518
+ record[key] = value;
307
519
  }
308
- if (result instanceof Promise)
309
- result.then(settle(ok), settle(err));
310
- else if (result == null || UNSET === result)
311
- nil();
312
- else
313
- ok(result);
314
- computing = false;
315
- }, watcher);
316
- const computed = {};
317
- Object.defineProperties(computed, {
318
- [Symbol.toStringTag]: {
319
- value: TYPE_COMPUTED
320
- },
321
- get: {
322
- value: () => {
323
- subscribe(watchers);
324
- flush();
325
- if (dirty)
326
- compute();
327
- if (error)
328
- throw error;
329
- return value;
520
+ return record;
521
+ }
522
+ get #value() {
523
+ return this.#order.map((key) => this.#composite.signals.get(key)?.get()).filter((v) => v !== undefined);
524
+ }
525
+ get [Symbol.toStringTag]() {
526
+ return TYPE_LIST;
527
+ }
528
+ get [Symbol.isConcatSpreadable]() {
529
+ return true;
530
+ }
531
+ *[Symbol.iterator]() {
532
+ for (const key of this.#order) {
533
+ const signal = this.#composite.signals.get(key);
534
+ if (signal)
535
+ yield signal;
536
+ }
537
+ }
538
+ get length() {
539
+ subscribeActiveWatcher(this.#watchers);
540
+ return this.#order.length;
541
+ }
542
+ get() {
543
+ subscribeActiveWatcher(this.#watchers);
544
+ return this.#value;
545
+ }
546
+ set(newValue) {
547
+ if (UNSET === newValue) {
548
+ this.#composite.clear();
549
+ notifyWatchers(this.#watchers);
550
+ this.#watchers.clear();
551
+ return;
552
+ }
553
+ const oldValue = this.#value;
554
+ const changes = diff(this.#toRecord(oldValue), this.#toRecord(newValue));
555
+ const removedKeys = Object.keys(changes.remove);
556
+ const changed = this.#composite.change(changes);
557
+ if (changed) {
558
+ for (const key of removedKeys) {
559
+ const index = this.#order.indexOf(key);
560
+ if (index !== -1)
561
+ this.#order.splice(index, 1);
562
+ }
563
+ this.#order = this.#order.filter(() => true);
564
+ notifyWatchers(this.#watchers);
565
+ }
566
+ }
567
+ update(fn) {
568
+ this.set(fn(this.get()));
569
+ }
570
+ at(index) {
571
+ return this.#composite.signals.get(this.#order[index]);
572
+ }
573
+ keys() {
574
+ return this.#order.values();
575
+ }
576
+ byKey(key) {
577
+ return this.#composite.signals.get(key);
578
+ }
579
+ keyAt(index) {
580
+ return this.#order[index];
581
+ }
582
+ indexOfKey(key) {
583
+ return this.#order.indexOf(key);
584
+ }
585
+ add(value) {
586
+ const key = this.#generateKey(value);
587
+ if (this.#composite.signals.has(key))
588
+ throw new DuplicateKeyError("store", key, value);
589
+ if (!this.#order.includes(key))
590
+ this.#order.push(key);
591
+ const ok = this.#composite.add(key, value);
592
+ if (ok)
593
+ notifyWatchers(this.#watchers);
594
+ return key;
595
+ }
596
+ remove(keyOrIndex) {
597
+ const key = isNumber(keyOrIndex) ? this.#order[keyOrIndex] : keyOrIndex;
598
+ const ok = this.#composite.remove(key);
599
+ if (ok) {
600
+ const index = isNumber(keyOrIndex) ? keyOrIndex : this.#order.indexOf(key);
601
+ if (index >= 0)
602
+ this.#order.splice(index, 1);
603
+ this.#order = this.#order.filter(() => true);
604
+ notifyWatchers(this.#watchers);
605
+ }
606
+ }
607
+ sort(compareFn) {
608
+ 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])));
609
+ const newOrder = entries.map(([key]) => key);
610
+ if (!isEqual(this.#order, newOrder)) {
611
+ this.#order = newOrder;
612
+ notifyWatchers(this.#watchers);
613
+ emitNotification(this.#listeners.sort, this.#order);
614
+ }
615
+ }
616
+ splice(start, deleteCount, ...items) {
617
+ const length = this.#order.length;
618
+ const actualStart = start < 0 ? Math.max(0, length + start) : Math.min(start, length);
619
+ const actualDeleteCount = Math.max(0, Math.min(deleteCount ?? Math.max(0, length - Math.max(0, actualStart)), length - actualStart));
620
+ const add = {};
621
+ const remove = {};
622
+ for (let i = 0;i < actualDeleteCount; i++) {
623
+ const index = actualStart + i;
624
+ const key = this.#order[index];
625
+ if (key) {
626
+ const signal = this.#composite.signals.get(key);
627
+ if (signal)
628
+ remove[key] = signal.get();
330
629
  }
331
630
  }
631
+ const newOrder = this.#order.slice(0, actualStart);
632
+ for (const item of items) {
633
+ const key = this.#generateKey(item);
634
+ newOrder.push(key);
635
+ add[key] = item;
636
+ }
637
+ newOrder.push(...this.#order.slice(actualStart + actualDeleteCount));
638
+ const changed = !!(Object.keys(add).length || Object.keys(remove).length);
639
+ if (changed) {
640
+ this.#composite.change({
641
+ add,
642
+ change: {},
643
+ remove,
644
+ changed
645
+ });
646
+ this.#order = newOrder.filter(() => true);
647
+ notifyWatchers(this.#watchers);
648
+ }
649
+ return Object.values(remove);
650
+ }
651
+ on(type, listener) {
652
+ if (type === "sort") {
653
+ this.#listeners.sort.add(listener);
654
+ return () => this.#listeners.sort.delete(listener);
655
+ }
656
+ return this.#composite.on(type, listener);
657
+ }
658
+ deriveCollection(callback) {
659
+ return new Collection(this, callback);
660
+ }
661
+ }
662
+ var isList = (value) => isObjectOfType(value, TYPE_LIST);
663
+
664
+ // src/classes/store.ts
665
+ var TYPE_STORE = "Store";
666
+
667
+ class BaseStore {
668
+ #composite;
669
+ #watchers = new Set;
670
+ constructor(initialValue) {
671
+ validateSignalValue("store", initialValue, isRecord);
672
+ this.#composite = new Composite(initialValue, (key, value) => {
673
+ validateSignalValue(`store for key "${key}"`, value);
674
+ return true;
675
+ }, (value) => createMutableSignal(value));
676
+ }
677
+ get #value() {
678
+ const record = {};
679
+ for (const [key, signal] of this.#composite.signals.entries())
680
+ record[key] = signal.get();
681
+ return record;
682
+ }
683
+ get [Symbol.toStringTag]() {
684
+ return TYPE_STORE;
685
+ }
686
+ get [Symbol.isConcatSpreadable]() {
687
+ return false;
688
+ }
689
+ *[Symbol.iterator]() {
690
+ for (const [key, signal] of this.#composite.signals.entries())
691
+ yield [key, signal];
692
+ }
693
+ get() {
694
+ subscribeActiveWatcher(this.#watchers);
695
+ return this.#value;
696
+ }
697
+ set(newValue) {
698
+ if (UNSET === newValue) {
699
+ this.#composite.clear();
700
+ notifyWatchers(this.#watchers);
701
+ this.#watchers.clear();
702
+ return;
703
+ }
704
+ const oldValue = this.#value;
705
+ const changed = this.#composite.change(diff(oldValue, newValue));
706
+ if (changed)
707
+ notifyWatchers(this.#watchers);
708
+ }
709
+ keys() {
710
+ return this.#composite.signals.keys();
711
+ }
712
+ byKey(key) {
713
+ return this.#composite.signals.get(key);
714
+ }
715
+ update(fn) {
716
+ this.set(fn(this.get()));
717
+ }
718
+ add(key, value) {
719
+ if (this.#composite.signals.has(key))
720
+ throw new DuplicateKeyError("store", key, value);
721
+ const ok = this.#composite.add(key, value);
722
+ if (ok)
723
+ notifyWatchers(this.#watchers);
724
+ return key;
725
+ }
726
+ remove(key) {
727
+ const ok = this.#composite.remove(key);
728
+ if (ok)
729
+ notifyWatchers(this.#watchers);
730
+ }
731
+ on(type, listener) {
732
+ return this.#composite.on(type, listener);
733
+ }
734
+ }
735
+ var createStore = (initialValue) => {
736
+ const instance = new BaseStore(initialValue);
737
+ return new Proxy(instance, {
738
+ get(target, prop) {
739
+ if (prop in target) {
740
+ const value = Reflect.get(target, prop);
741
+ return isFunction(value) ? value.bind(target) : value;
742
+ }
743
+ if (!isSymbol(prop))
744
+ return target.byKey(prop);
745
+ },
746
+ has(target, prop) {
747
+ if (prop in target)
748
+ return true;
749
+ return target.byKey(String(prop)) !== undefined;
750
+ },
751
+ ownKeys(target) {
752
+ return Array.from(target.keys());
753
+ },
754
+ getOwnPropertyDescriptor(target, prop) {
755
+ if (prop in target)
756
+ return Reflect.getOwnPropertyDescriptor(target, prop);
757
+ if (isSymbol(prop))
758
+ return;
759
+ const signal = target.byKey(String(prop));
760
+ return signal ? {
761
+ enumerable: true,
762
+ configurable: true,
763
+ writable: true,
764
+ value: signal
765
+ } : undefined;
766
+ }
332
767
  });
333
- return computed;
334
768
  };
335
- var isComputed = (value) => isObjectOfType(value, TYPE_COMPUTED);
336
- var isComputedCallback = (value) => isFunction(value) && value.length < 3;
769
+ var isStore = (value) => isObjectOfType(value, TYPE_STORE);
770
+
771
+ // src/signal.ts
772
+ var isSignal = (value) => isState(value) || isComputed(value) || isStore(value);
773
+ var isMutableSignal = (value) => isState(value) || isStore(value) || isList(value);
774
+ function createSignal(value) {
775
+ if (isMemoCallback(value))
776
+ return new Memo(value);
777
+ if (isTaskCallback(value))
778
+ return new Task(value);
779
+ if (isUniformArray(value))
780
+ return new List(value);
781
+ if (isRecord(value))
782
+ return createStore(value);
783
+ return new State(value);
784
+ }
785
+ function createMutableSignal(value) {
786
+ if (isUniformArray(value))
787
+ return new List(value);
788
+ if (isRecord(value))
789
+ return createStore(value);
790
+ return new State(value);
791
+ }
792
+
793
+ // src/errors.ts
794
+ class CircularDependencyError extends Error {
795
+ constructor(where) {
796
+ super(`Circular dependency detected in ${where}`);
797
+ this.name = "CircularDependencyError";
798
+ }
799
+ }
800
+
801
+ class DuplicateKeyError extends Error {
802
+ constructor(where, key, value) {
803
+ super(`Could not add ${where} key "${key}"${value ? ` with value ${valueString(value)}` : ""} because it already exists`);
804
+ this.name = "DuplicateKeyError";
805
+ }
806
+ }
807
+
808
+ class InvalidCallbackError extends TypeError {
809
+ constructor(where, value) {
810
+ super(`Invalid ${where} callback ${valueString(value)}`);
811
+ this.name = "InvalidCallbackError";
812
+ }
813
+ }
814
+
815
+ class InvalidSignalValueError extends TypeError {
816
+ constructor(where, value) {
817
+ super(`Invalid signal value ${valueString(value)} in ${where}`);
818
+ this.name = "InvalidSignalValueError";
819
+ }
820
+ }
821
+
822
+ class NullishSignalValueError extends TypeError {
823
+ constructor(where) {
824
+ super(`Nullish signal values are not allowed in ${where}`);
825
+ this.name = "NullishSignalValueError";
826
+ }
827
+ }
828
+
829
+ class ReadonlySignalError extends Error {
830
+ constructor(what, value) {
831
+ super(`Could not set ${what} to ${valueString(value)} because signal is read-only`);
832
+ this.name = "ReadonlySignalError";
833
+ }
834
+ }
835
+ var validateCallback = (where, value, guard = isFunction) => {
836
+ if (!guard(value))
837
+ throw new InvalidCallbackError(where, value);
838
+ };
839
+ var validateSignalValue = (where, value, guard = () => !(isSymbol(value) && value !== UNSET) || isFunction(value)) => {
840
+ if (value == null)
841
+ throw new NullishSignalValueError(where);
842
+ if (!guard(value))
843
+ throw new InvalidSignalValueError(where, value);
844
+ };
845
+ var guardMutableSignal = (what, value, signal) => {
846
+ if (!isMutableSignal(signal))
847
+ throw new ReadonlySignalError(what, value);
848
+ return true;
849
+ };
850
+
851
+ // src/classes/collection.ts
852
+ var TYPE_COLLECTION = "Collection";
853
+
854
+ class Collection {
855
+ #watchers = new Set;
856
+ #source;
857
+ #callback;
858
+ #signals = new Map;
859
+ #ownWatchers = new Map;
860
+ #listeners = {
861
+ add: new Set,
862
+ change: new Set,
863
+ remove: new Set,
864
+ sort: new Set
865
+ };
866
+ #order = [];
867
+ constructor(source, callback) {
868
+ validateCallback("collection", callback);
869
+ if (isFunction(source))
870
+ source = source();
871
+ if (!isCollectionSource(source))
872
+ throw new Error("Invalid collection source");
873
+ this.#source = source;
874
+ this.#callback = callback;
875
+ for (let i = 0;i < this.#source.length; i++) {
876
+ const key = this.#source.keyAt(i);
877
+ if (!key)
878
+ continue;
879
+ this.#add(key);
880
+ }
881
+ this.#source.on("add", (additions) => {
882
+ for (const key of additions) {
883
+ if (!this.#signals.has(key)) {
884
+ this.#add(key);
885
+ const signal = this.#signals.get(key);
886
+ if (signal && isAsyncCollectionCallback(this.#callback))
887
+ signal.get();
888
+ }
889
+ }
890
+ notifyWatchers(this.#watchers);
891
+ emitNotification(this.#listeners.add, additions);
892
+ });
893
+ this.#source.on("remove", (removals) => {
894
+ for (const key of removals) {
895
+ if (!this.#signals.has(key))
896
+ continue;
897
+ this.#signals.delete(key);
898
+ const index = this.#order.indexOf(key);
899
+ if (index >= 0)
900
+ this.#order.splice(index, 1);
901
+ this.#removeWatcher(key);
902
+ }
903
+ this.#order = this.#order.filter(() => true);
904
+ notifyWatchers(this.#watchers);
905
+ emitNotification(this.#listeners.remove, removals);
906
+ });
907
+ this.#source.on("sort", (newOrder) => {
908
+ this.#order = [...newOrder];
909
+ notifyWatchers(this.#watchers);
910
+ emitNotification(this.#listeners.sort, newOrder);
911
+ });
912
+ }
913
+ get #value() {
914
+ return this.#order.map((key) => this.#signals.get(key)?.get()).filter((v) => v != null && v !== UNSET);
915
+ }
916
+ #add(key) {
917
+ const computedCallback = isAsyncCollectionCallback(this.#callback) ? async (_, abort) => {
918
+ const sourceSignal = this.#source.byKey(key);
919
+ if (!sourceSignal)
920
+ return UNSET;
921
+ const sourceValue = sourceSignal.get();
922
+ if (sourceValue === UNSET)
923
+ return UNSET;
924
+ return this.#callback(sourceValue, abort);
925
+ } : () => {
926
+ const sourceSignal = this.#source.byKey(key);
927
+ if (!sourceSignal)
928
+ return UNSET;
929
+ const sourceValue = sourceSignal.get();
930
+ if (sourceValue === UNSET)
931
+ return UNSET;
932
+ return this.#callback(sourceValue);
933
+ };
934
+ const signal = createComputed(computedCallback);
935
+ this.#signals.set(key, signal);
936
+ if (!this.#order.includes(key))
937
+ this.#order.push(key);
938
+ if (this.#listeners.change.size)
939
+ this.#addWatcher(key);
940
+ return true;
941
+ }
942
+ #addWatcher(key) {
943
+ const watcher = createWatcher(() => {
944
+ trackSignalReads(watcher, () => {
945
+ this.#signals.get(key)?.get();
946
+ });
947
+ });
948
+ this.#ownWatchers.set(key, watcher);
949
+ watcher();
950
+ }
951
+ #removeWatcher(key) {
952
+ const watcher = this.#ownWatchers.get(key);
953
+ if (watcher) {
954
+ watcher.stop();
955
+ this.#ownWatchers.delete(key);
956
+ }
957
+ }
958
+ get [Symbol.toStringTag]() {
959
+ return TYPE_COLLECTION;
960
+ }
961
+ get [Symbol.isConcatSpreadable]() {
962
+ return true;
963
+ }
964
+ *[Symbol.iterator]() {
965
+ for (const key of this.#order) {
966
+ const signal = this.#signals.get(key);
967
+ if (signal)
968
+ yield signal;
969
+ }
970
+ }
971
+ get length() {
972
+ subscribeActiveWatcher(this.#watchers);
973
+ return this.#order.length;
974
+ }
975
+ get() {
976
+ subscribeActiveWatcher(this.#watchers);
977
+ return this.#value;
978
+ }
979
+ at(index) {
980
+ return this.#signals.get(this.#order[index]);
981
+ }
982
+ keys() {
983
+ return this.#order.values();
984
+ }
985
+ byKey(key) {
986
+ return this.#signals.get(key);
987
+ }
988
+ keyAt(index) {
989
+ return this.#order[index];
990
+ }
991
+ indexOfKey(key) {
992
+ return this.#order.indexOf(key);
993
+ }
994
+ on(type, listener) {
995
+ this.#listeners[type].add(listener);
996
+ if (type === "change" && !this.#ownWatchers.size) {
997
+ for (const key of this.#signals.keys())
998
+ this.#addWatcher(key);
999
+ }
1000
+ return () => {
1001
+ this.#listeners[type].delete(listener);
1002
+ if (type === "change" && !this.#listeners.change.size) {
1003
+ if (this.#ownWatchers.size) {
1004
+ for (const watcher of this.#ownWatchers.values())
1005
+ watcher.stop();
1006
+ this.#ownWatchers.clear();
1007
+ }
1008
+ }
1009
+ };
1010
+ }
1011
+ deriveCollection(callback) {
1012
+ return new Collection(this, callback);
1013
+ }
1014
+ }
1015
+ var isCollection = (value) => isObjectOfType(value, TYPE_COLLECTION);
1016
+ var isCollectionSource = (value) => isList(value) || isCollection(value);
1017
+ var isAsyncCollectionCallback = (callback) => isAsyncFunction(callback);
337
1018
  // src/effect.ts
338
1019
  var createEffect = (callback) => {
339
1020
  if (!isFunction(callback) || callback.length > 1)
340
- throw new InvalidCallbackError("effect", valueString(callback));
1021
+ throw new InvalidCallbackError("effect", callback);
341
1022
  const isAsync = isAsyncFunction(callback);
342
1023
  let running = false;
343
1024
  let controller;
344
- const watcher = createWatcher(() => observe(() => {
1025
+ const watcher = createWatcher(() => trackSignalReads(watcher, () => {
345
1026
  if (running)
346
1027
  throw new CircularDependencyError("effect");
347
1028
  running = true;
@@ -354,7 +1035,7 @@ var createEffect = (callback) => {
354
1035
  const currentController = controller;
355
1036
  callback(controller.signal).then((cleanup2) => {
356
1037
  if (isFunction(cleanup2) && controller === currentController)
357
- watcher.unwatch(cleanup2);
1038
+ watcher.onCleanup(cleanup2);
358
1039
  }).catch((error) => {
359
1040
  if (!isAbortError(error))
360
1041
  console.error("Async effect error:", error);
@@ -362,18 +1043,18 @@ var createEffect = (callback) => {
362
1043
  } else {
363
1044
  cleanup = callback();
364
1045
  if (isFunction(cleanup))
365
- watcher.unwatch(cleanup);
1046
+ watcher.onCleanup(cleanup);
366
1047
  }
367
1048
  } catch (error) {
368
1049
  if (!isAbortError(error))
369
1050
  console.error("Effect callback error:", error);
370
1051
  }
371
1052
  running = false;
372
- }, watcher));
1053
+ }));
373
1054
  watcher();
374
1055
  return () => {
375
1056
  controller?.abort();
376
- watcher.cleanup();
1057
+ watcher.stop();
377
1058
  };
378
1059
  };
379
1060
  // src/match.ts
@@ -414,310 +1095,15 @@ function resolve(signals) {
414
1095
  return { ok: false, errors };
415
1096
  return { ok: true, values };
416
1097
  }
417
- // src/state.ts
418
- var TYPE_STATE = "State";
419
- var createState = (initialValue) => {
420
- if (initialValue == null)
421
- throw new NullishSignalValueError("state");
422
- const watchers = new Set;
423
- let value = initialValue;
424
- const setValue = (newValue) => {
425
- if (newValue == null)
426
- throw new NullishSignalValueError("state");
427
- if (isEqual(value, newValue))
428
- return;
429
- value = newValue;
430
- notify(watchers);
431
- if (UNSET === value)
432
- watchers.clear();
433
- };
434
- const state = {};
435
- Object.defineProperties(state, {
436
- [Symbol.toStringTag]: {
437
- value: TYPE_STATE
438
- },
439
- get: {
440
- value: () => {
441
- subscribe(watchers);
442
- return value;
443
- }
444
- },
445
- set: {
446
- value: (newValue) => {
447
- setValue(newValue);
448
- }
449
- },
450
- update: {
451
- value: (updater) => {
452
- if (!isFunction(updater))
453
- throw new InvalidCallbackError("state update", valueString(updater));
454
- setValue(updater(value));
455
- }
456
- }
457
- });
458
- return state;
459
- };
460
- var isState = (value) => isObjectOfType(value, TYPE_STATE);
461
-
462
- // src/store.ts
463
- var TYPE_STORE = "Store";
464
- var createStore = (initialValue) => {
465
- if (initialValue == null)
466
- throw new NullishSignalValueError("store");
467
- const watchers = new Set;
468
- const listeners = {
469
- add: new Set,
470
- change: new Set,
471
- remove: new Set,
472
- sort: new Set
473
- };
474
- const signals = new Map;
475
- const signalWatchers = new Map;
476
- const isArrayLike = Array.isArray(initialValue);
477
- const current = () => {
478
- const record = {};
479
- for (const [key, signal] of signals)
480
- record[key] = signal.get();
481
- return record;
482
- };
483
- const emit = (key, changes) => {
484
- Object.freeze(changes);
485
- for (const listener of listeners[key])
486
- listener(changes);
487
- };
488
- const getSortedIndexes = () => Array.from(signals.keys()).map((k) => Number(k)).filter((n) => Number.isInteger(n)).sort((a, b) => a - b);
489
- const isValidValue = (key, value) => {
490
- if (value == null)
491
- throw new NullishSignalValueError(`store for key "${key}"`);
492
- if (value === UNSET)
493
- return true;
494
- if (isSymbol(value) || isFunction(value) || isComputed(value))
495
- throw new InvalidSignalValueError(`store for key "${key}"`, valueString(value));
496
- return true;
497
- };
498
- const addProperty = (key, value, single = false) => {
499
- if (!isValidValue(key, value))
500
- return false;
501
- const signal = isState(value) || isStore(value) ? value : isRecord(value) || Array.isArray(value) ? createStore(value) : createState(value);
502
- signals.set(key, signal);
503
- const watcher = createWatcher(() => observe(() => {
504
- emit("change", { [key]: signal.get() });
505
- }, watcher));
506
- watcher();
507
- signalWatchers.set(key, watcher);
508
- if (single) {
509
- notify(watchers);
510
- emit("add", { [key]: value });
511
- }
512
- return true;
513
- };
514
- const removeProperty = (key, single = false) => {
515
- const ok = signals.delete(key);
516
- if (ok) {
517
- const watcher = signalWatchers.get(key);
518
- if (watcher)
519
- watcher.cleanup();
520
- signalWatchers.delete(key);
521
- }
522
- if (single) {
523
- notify(watchers);
524
- emit("remove", { [key]: UNSET });
525
- }
526
- return ok;
527
- };
528
- const reconcile = (oldValue, newValue, initialRun) => {
529
- const changes = diff(oldValue, newValue);
530
- batch(() => {
531
- if (Object.keys(changes.add).length) {
532
- for (const key in changes.add)
533
- addProperty(key, changes.add[key] ?? UNSET);
534
- if (initialRun) {
535
- setTimeout(() => {
536
- emit("add", changes.add);
537
- }, 0);
538
- } else {
539
- emit("add", changes.add);
540
- }
541
- }
542
- if (Object.keys(changes.change).length) {
543
- for (const key in changes.change) {
544
- const value = changes.change[key];
545
- if (!isValidValue(key, value))
546
- continue;
547
- const signal = signals.get(key);
548
- if (isMutableSignal(signal))
549
- signal.set(value);
550
- else
551
- throw new StoreKeyReadonlyError(key, valueString(value));
552
- }
553
- emit("change", changes.change);
554
- }
555
- if (Object.keys(changes.remove).length) {
556
- for (const key in changes.remove)
557
- removeProperty(key);
558
- emit("remove", changes.remove);
559
- }
560
- });
561
- return changes.changed;
562
- };
563
- reconcile({}, initialValue, true);
564
- const store = {};
565
- Object.defineProperties(store, {
566
- [Symbol.toStringTag]: {
567
- value: TYPE_STORE
568
- },
569
- [Symbol.isConcatSpreadable]: {
570
- value: isArrayLike
571
- },
572
- [Symbol.iterator]: {
573
- value: isArrayLike ? function* () {
574
- const indexes = getSortedIndexes();
575
- for (const index of indexes) {
576
- const signal = signals.get(String(index));
577
- if (signal)
578
- yield signal;
579
- }
580
- } : function* () {
581
- for (const [key, signal] of signals)
582
- yield [key, signal];
583
- }
584
- },
585
- add: {
586
- value: isArrayLike ? (v) => {
587
- addProperty(String(signals.size), v, true);
588
- } : (k, v) => {
589
- if (!signals.has(k))
590
- addProperty(k, v, true);
591
- else
592
- throw new StoreKeyExistsError(k, valueString(v));
593
- }
594
- },
595
- get: {
596
- value: () => {
597
- subscribe(watchers);
598
- return recordToArray(current());
599
- }
600
- },
601
- remove: {
602
- value: isArrayLike ? (index) => {
603
- const currentArray = recordToArray(current());
604
- const currentLength = signals.size;
605
- if (!Array.isArray(currentArray) || index <= -currentLength || index >= currentLength)
606
- throw new StoreKeyRangeError(index);
607
- const newArray = [...currentArray];
608
- newArray.splice(index, 1);
609
- if (reconcile(currentArray, newArray))
610
- notify(watchers);
611
- } : (k) => {
612
- if (signals.has(k))
613
- removeProperty(k, true);
614
- }
615
- },
616
- set: {
617
- value: (v) => {
618
- if (reconcile(current(), v)) {
619
- notify(watchers);
620
- if (UNSET === v)
621
- watchers.clear();
622
- }
623
- }
624
- },
625
- update: {
626
- value: (fn) => {
627
- const oldValue = current();
628
- const newValue = fn(recordToArray(oldValue));
629
- if (reconcile(oldValue, newValue)) {
630
- notify(watchers);
631
- if (UNSET === newValue)
632
- watchers.clear();
633
- }
634
- }
635
- },
636
- sort: {
637
- value: (compareFn) => {
638
- const entries = Array.from(signals.entries()).map(([key, signal]) => [key, signal.get()]).sort(compareFn ? (a, b) => compareFn(a[1], b[1]) : (a, b) => String(a[1]).localeCompare(String(b[1])));
639
- const newOrder = entries.map(([key]) => String(key));
640
- const newSignals = new Map;
641
- entries.forEach(([key], newIndex) => {
642
- const oldKey = String(key);
643
- const newKey = isArrayLike ? String(newIndex) : String(key);
644
- const signal = signals.get(oldKey);
645
- if (signal)
646
- newSignals.set(newKey, signal);
647
- });
648
- signals.clear();
649
- newSignals.forEach((signal, key) => signals.set(key, signal));
650
- notify(watchers);
651
- emit("sort", newOrder);
652
- }
653
- },
654
- on: {
655
- value: (type, listener) => {
656
- listeners[type].add(listener);
657
- return () => listeners[type].delete(listener);
658
- }
659
- },
660
- length: {
661
- get() {
662
- subscribe(watchers);
663
- return signals.size;
664
- }
665
- }
666
- });
667
- return new Proxy(store, {
668
- get(target, prop) {
669
- if (prop in target)
670
- return Reflect.get(target, prop);
671
- if (isSymbol(prop))
672
- return;
673
- return signals.get(prop);
674
- },
675
- has(target, prop) {
676
- if (prop in target)
677
- return true;
678
- return signals.has(String(prop));
679
- },
680
- ownKeys(target) {
681
- const staticKeys = Reflect.ownKeys(target);
682
- const signalKeys = isArrayLike ? getSortedIndexes().map((key) => String(key)) : Array.from(signals.keys());
683
- return [...new Set([...signalKeys, ...staticKeys])];
684
- },
685
- getOwnPropertyDescriptor(target, prop) {
686
- if (prop in target)
687
- return Reflect.getOwnPropertyDescriptor(target, prop);
688
- const signal = signals.get(String(prop));
689
- return signal ? {
690
- enumerable: true,
691
- configurable: true,
692
- writable: true,
693
- value: signal
694
- } : undefined;
695
- }
696
- });
697
- };
698
- var isStore = (value) => isObjectOfType(value, TYPE_STORE);
699
-
700
- // src/signal.ts
701
- var isSignal = (value) => isState(value) || isComputed(value) || isStore(value);
702
- var isMutableSignal = (value) => isState(value) || isStore(value);
703
- function toSignal(value) {
704
- if (isSignal(value))
705
- return value;
706
- if (isComputedCallback(value))
707
- return createComputed(value);
708
- if (Array.isArray(value) || isRecord(value))
709
- return createStore(value);
710
- return createState(value);
711
- }
712
1098
  export {
713
1099
  valueString,
714
- toSignal,
1100
+ trackSignalReads,
715
1101
  toError,
716
- subscribe,
1102
+ subscribeActiveWatcher,
717
1103
  resolve,
718
- observe,
719
- notify,
1104
+ notifyWatchers,
720
1105
  match,
1106
+ isTaskCallback,
721
1107
  isSymbol,
722
1108
  isString,
723
1109
  isStore,
@@ -728,29 +1114,39 @@ export {
728
1114
  isObjectOfType,
729
1115
  isNumber,
730
1116
  isMutableSignal,
1117
+ isMemoCallback,
1118
+ isList,
731
1119
  isFunction,
732
1120
  isEqual,
733
- isComputedCallback,
734
1121
  isComputed,
1122
+ isCollection,
735
1123
  isAsyncFunction,
736
1124
  isAbortError,
737
- flush,
1125
+ flushPendingReactions,
1126
+ emitNotification,
738
1127
  diff,
739
1128
  createWatcher,
740
1129
  createStore,
741
- createState,
1130
+ createSignal,
742
1131
  createEffect,
743
1132
  createComputed,
744
- batch,
1133
+ batchSignalWrites,
745
1134
  UNSET,
1135
+ Task,
746
1136
  TYPE_STORE,
747
1137
  TYPE_STATE,
1138
+ TYPE_LIST,
748
1139
  TYPE_COMPUTED,
749
- StoreKeyReadonlyError,
750
- StoreKeyRangeError,
751
- StoreKeyExistsError,
1140
+ TYPE_COLLECTION,
1141
+ State,
1142
+ ReadonlySignalError,
752
1143
  NullishSignalValueError,
1144
+ Memo,
1145
+ List,
753
1146
  InvalidSignalValueError,
754
1147
  InvalidCallbackError,
755
- CircularDependencyError
1148
+ DuplicateKeyError,
1149
+ Collection,
1150
+ CircularDependencyError,
1151
+ BaseStore
756
1152
  };