@zeix/cause-effect 0.15.0 → 0.15.2

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.
package/index.dev.js CHANGED
@@ -1,3 +1,165 @@
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 InvalidSignalValueError extends TypeError {
10
+ constructor(where, value) {
11
+ super(`Invalid signal value ${value} in ${where}`);
12
+ this.name = "InvalidSignalValueError";
13
+ }
14
+ }
15
+
16
+ class NullishSignalValueError extends TypeError {
17
+ constructor(where) {
18
+ super(`Nullish signal values are not allowed in ${where}`);
19
+ this.name = "NullishSignalValueError";
20
+ }
21
+ }
22
+
23
+ class StoreKeyExistsError extends Error {
24
+ constructor(key, value) {
25
+ super(`Could not add store key "${key}" with value ${value} because it already exists`);
26
+ this.name = "StoreKeyExistsError";
27
+ }
28
+ }
29
+
30
+ class StoreKeyRangeError extends RangeError {
31
+ constructor(index) {
32
+ super(`Could not remove store index ${String(index)} because it is out of range`);
33
+ this.name = "StoreKeyRangeError";
34
+ }
35
+ }
36
+
37
+ class StoreKeyReadonlyError extends Error {
38
+ constructor(key, value) {
39
+ super(`Could not set store key "${key}" to ${value} because it is readonly`);
40
+ this.name = "StoreKeyReadonlyError";
41
+ }
42
+ }
43
+
44
+ // src/util.ts
45
+ var UNSET = Symbol();
46
+ var isString = (value) => typeof value === "string";
47
+ var isNumber = (value) => typeof value === "number";
48
+ var isSymbol = (value) => typeof value === "symbol";
49
+ var isFunction = (fn) => typeof fn === "function";
50
+ var isAsyncFunction = (fn) => isFunction(fn) && fn.constructor.name === "AsyncFunction";
51
+ var isDefinedObject = (value) => !!value && typeof value === "object";
52
+ var isObjectOfType = (value, type) => Object.prototype.toString.call(value) === `[object ${type}]`;
53
+ var isRecord = (value) => isObjectOfType(value, "Object");
54
+ var isRecordOrArray = (value) => isRecord(value) || Array.isArray(value);
55
+ var validArrayIndexes = (keys) => {
56
+ if (!keys.length)
57
+ return null;
58
+ const indexes = keys.map((k) => isString(k) ? parseInt(k, 10) : isNumber(k) ? k : NaN);
59
+ return indexes.every((index) => Number.isFinite(index) && index >= 0) ? indexes.sort((a, b) => a - b) : null;
60
+ };
61
+ var isAbortError = (error) => error instanceof DOMException && error.name === "AbortError";
62
+ var toError = (reason) => reason instanceof Error ? reason : Error(String(reason));
63
+ var recordToArray = (record) => {
64
+ const indexes = validArrayIndexes(Object.keys(record));
65
+ if (indexes === null)
66
+ return record;
67
+ const array = [];
68
+ for (const index of indexes) {
69
+ array.push(record[String(index)]);
70
+ }
71
+ return array;
72
+ };
73
+ var valueString = (value) => isString(value) ? `"${value}"` : isDefinedObject(value) ? JSON.stringify(value) : String(value);
74
+
75
+ // src/diff.ts
76
+ var isEqual = (a, b, visited) => {
77
+ if (Object.is(a, b))
78
+ return true;
79
+ if (typeof a !== typeof b)
80
+ return false;
81
+ if (typeof a !== "object" || a === null || b === null)
82
+ return false;
83
+ if (!visited)
84
+ visited = new WeakSet;
85
+ if (visited.has(a) || visited.has(b))
86
+ throw new CircularDependencyError("isEqual");
87
+ visited.add(a);
88
+ visited.add(b);
89
+ try {
90
+ if (Array.isArray(a) && Array.isArray(b)) {
91
+ if (a.length !== b.length)
92
+ return false;
93
+ for (let i = 0;i < a.length; i++) {
94
+ if (!isEqual(a[i], b[i], visited))
95
+ return false;
96
+ }
97
+ return true;
98
+ }
99
+ if (Array.isArray(a) !== Array.isArray(b))
100
+ return false;
101
+ if (isRecord(a) && isRecord(b)) {
102
+ const aKeys = Object.keys(a);
103
+ const bKeys = Object.keys(b);
104
+ if (aKeys.length !== bKeys.length)
105
+ return false;
106
+ for (const key of aKeys) {
107
+ if (!(key in b))
108
+ return false;
109
+ if (!isEqual(a[key], b[key], visited))
110
+ return false;
111
+ }
112
+ return true;
113
+ }
114
+ return false;
115
+ } finally {
116
+ visited.delete(a);
117
+ visited.delete(b);
118
+ }
119
+ };
120
+ var diff = (oldObj, newObj) => {
121
+ const oldValid = isRecordOrArray(oldObj);
122
+ const newValid = isRecordOrArray(newObj);
123
+ if (!oldValid || !newValid) {
124
+ const changed2 = !Object.is(oldObj, newObj);
125
+ return {
126
+ changed: changed2,
127
+ add: changed2 && newValid ? newObj : {},
128
+ change: {},
129
+ remove: changed2 && oldValid ? oldObj : {}
130
+ };
131
+ }
132
+ const visited = new WeakSet;
133
+ const add = {};
134
+ const change = {};
135
+ const remove = {};
136
+ const oldKeys = Object.keys(oldObj);
137
+ const newKeys = Object.keys(newObj);
138
+ const allKeys = new Set([...oldKeys, ...newKeys]);
139
+ for (const key of allKeys) {
140
+ const oldHas = key in oldObj;
141
+ const newHas = key in newObj;
142
+ if (!oldHas && newHas) {
143
+ add[key] = newObj[key];
144
+ continue;
145
+ } else if (oldHas && !newHas) {
146
+ remove[key] = UNSET;
147
+ continue;
148
+ }
149
+ const oldValue = oldObj[key];
150
+ const newValue = newObj[key];
151
+ if (!isEqual(oldValue, newValue, visited))
152
+ change[key] = newValue;
153
+ }
154
+ const changed = Object.keys(add).length > 0 || Object.keys(change).length > 0 || Object.keys(remove).length > 0;
155
+ return {
156
+ changed,
157
+ add,
158
+ change,
159
+ remove
160
+ };
161
+ };
162
+
1
163
  // src/scheduler.ts
2
164
  var active;
3
165
  var pending = new Set;
@@ -87,371 +249,6 @@ var enqueue = (fn, dedupe) => new Promise((resolve, reject) => {
87
249
  requestTick();
88
250
  });
89
251
 
90
- // src/util.ts
91
- var isFunction = (fn) => typeof fn === "function";
92
- var isAsyncFunction = (fn) => isFunction(fn) && fn.constructor.name === "AsyncFunction";
93
- var isObjectOfType = (value, type) => Object.prototype.toString.call(value) === `[object ${type}]`;
94
- var isRecord = (value) => isObjectOfType(value, "Object");
95
- var arrayToRecord = (array) => {
96
- const record = {};
97
- for (let i = 0;i < array.length; i++) {
98
- if (i in array)
99
- record[String(i)] = array[i];
100
- }
101
- return record;
102
- };
103
- var hasMethod = (obj, methodName) => (methodName in obj) && isFunction(obj[methodName]);
104
- var isAbortError = (error) => error instanceof DOMException && error.name === "AbortError";
105
- var toError = (reason) => reason instanceof Error ? reason : Error(String(reason));
106
-
107
- class CircularDependencyError extends Error {
108
- constructor(where) {
109
- super(`Circular dependency in ${where} detected`);
110
- this.name = "CircularDependencyError";
111
- }
112
- }
113
-
114
- // src/state.ts
115
- var TYPE_STATE = "State";
116
- var state = (initialValue) => {
117
- const watchers = new Set;
118
- let value = initialValue;
119
- const s = {
120
- [Symbol.toStringTag]: TYPE_STATE,
121
- get: () => {
122
- subscribe(watchers);
123
- return value;
124
- },
125
- set: (v) => {
126
- if (isEqual(value, v))
127
- return;
128
- value = v;
129
- notify(watchers);
130
- if (UNSET === value)
131
- watchers.clear();
132
- },
133
- update: (fn) => {
134
- s.set(fn(value));
135
- }
136
- };
137
- return s;
138
- };
139
- var isState = (value) => isObjectOfType(value, TYPE_STATE);
140
-
141
- // src/effect.ts
142
- var effect = (callback) => {
143
- const isAsync = isAsyncFunction(callback);
144
- let running = false;
145
- let controller;
146
- const run = watch(() => observe(() => {
147
- if (running)
148
- throw new CircularDependencyError("effect");
149
- running = true;
150
- controller?.abort();
151
- controller = undefined;
152
- let cleanup;
153
- try {
154
- if (isAsync) {
155
- controller = new AbortController;
156
- const currentController = controller;
157
- callback(controller.signal).then((cleanup2) => {
158
- if (isFunction(cleanup2) && controller === currentController) {
159
- run.off(cleanup2);
160
- }
161
- }).catch((error) => {
162
- if (!isAbortError(error))
163
- console.error("Async effect error:", error);
164
- });
165
- } else {
166
- cleanup = callback();
167
- if (isFunction(cleanup))
168
- run.off(cleanup);
169
- }
170
- } catch (error) {
171
- if (!isAbortError(error))
172
- console.error("Effect callback error:", error);
173
- }
174
- running = false;
175
- }, run));
176
- run();
177
- return () => {
178
- controller?.abort();
179
- run.cleanup();
180
- };
181
- };
182
-
183
- // src/store.ts
184
- var TYPE_STORE = "Store";
185
- var store = (initialValue) => {
186
- const watchers = new Set;
187
- const eventTarget = new EventTarget;
188
- const signals = new Map;
189
- const cleanups = new Map;
190
- const size = state(0);
191
- const current = () => {
192
- const record = {};
193
- for (const [key, value] of signals) {
194
- record[key] = value.get();
195
- }
196
- return record;
197
- };
198
- const emit = (type, detail) => eventTarget.dispatchEvent(new CustomEvent(type, { detail }));
199
- const addSignalAndEffect = (key, value) => {
200
- const signal = toMutableSignal(value);
201
- signals.set(key, signal);
202
- const cleanup = effect(() => {
203
- const value2 = signal.get();
204
- if (value2 != null)
205
- emit("store-change", { [key]: value2 });
206
- });
207
- cleanups.set(key, cleanup);
208
- };
209
- const removeSignalAndEffect = (key) => {
210
- signals.delete(key);
211
- const cleanup = cleanups.get(key);
212
- if (cleanup)
213
- cleanup();
214
- cleanups.delete(key);
215
- };
216
- const reconcile = (oldValue, newValue) => {
217
- const changes = diff(oldValue, newValue);
218
- batch(() => {
219
- if (Object.keys(changes.add).length) {
220
- for (const key in changes.add) {
221
- const value = changes.add[key];
222
- if (value != null)
223
- addSignalAndEffect(key, value);
224
- }
225
- emit("store-add", changes.add);
226
- }
227
- if (Object.keys(changes.change).length) {
228
- for (const key in changes.change) {
229
- const signal = signals.get(key);
230
- const value = changes.change[key];
231
- if (signal && value != null && hasMethod(signal, "set"))
232
- signal.set(value);
233
- }
234
- emit("store-change", changes.change);
235
- }
236
- if (Object.keys(changes.remove).length) {
237
- for (const key in changes.remove) {
238
- removeSignalAndEffect(key);
239
- }
240
- emit("store-remove", changes.remove);
241
- }
242
- size.set(signals.size);
243
- });
244
- return changes.changed;
245
- };
246
- reconcile({}, initialValue);
247
- setTimeout(() => {
248
- const initialAdditionsEvent = new CustomEvent("store-add", {
249
- detail: initialValue
250
- });
251
- eventTarget.dispatchEvent(initialAdditionsEvent);
252
- }, 0);
253
- const storeProps = [
254
- "add",
255
- "get",
256
- "remove",
257
- "set",
258
- "update",
259
- "addEventListener",
260
- "removeEventListener",
261
- "dispatchEvent",
262
- "size"
263
- ];
264
- return new Proxy({}, {
265
- get(_target, prop) {
266
- const key = String(prop);
267
- switch (prop) {
268
- case "add":
269
- return (k, v) => {
270
- if (!signals.has(k)) {
271
- addSignalAndEffect(k, v);
272
- notify(watchers);
273
- emit("store-add", {
274
- [k]: v
275
- });
276
- size.set(signals.size);
277
- }
278
- };
279
- case "get":
280
- return () => {
281
- subscribe(watchers);
282
- return current();
283
- };
284
- case "remove":
285
- return (k) => {
286
- if (signals.has(k)) {
287
- removeSignalAndEffect(k);
288
- notify(watchers);
289
- emit("store-remove", { [k]: UNSET });
290
- size.set(signals.size);
291
- }
292
- };
293
- case "set":
294
- return (v) => {
295
- if (reconcile(current(), v)) {
296
- notify(watchers);
297
- if (UNSET === v)
298
- watchers.clear();
299
- }
300
- };
301
- case "update":
302
- return (fn) => {
303
- const oldValue = current();
304
- const newValue = fn(oldValue);
305
- if (reconcile(oldValue, newValue)) {
306
- notify(watchers);
307
- if (UNSET === newValue)
308
- watchers.clear();
309
- }
310
- };
311
- case "addEventListener":
312
- return eventTarget.addEventListener.bind(eventTarget);
313
- case "removeEventListener":
314
- return eventTarget.removeEventListener.bind(eventTarget);
315
- case "dispatchEvent":
316
- return eventTarget.dispatchEvent.bind(eventTarget);
317
- case "size":
318
- return size;
319
- }
320
- if (prop === Symbol.toStringTag)
321
- return TYPE_STORE;
322
- if (prop === Symbol.iterator) {
323
- return function* () {
324
- for (const [key2, signal] of signals) {
325
- yield [key2, signal];
326
- }
327
- };
328
- }
329
- return signals.get(key);
330
- },
331
- has(_target, prop) {
332
- const key = String(prop);
333
- return signals.has(key) || storeProps.includes(key) || prop === Symbol.toStringTag || prop === Symbol.iterator;
334
- },
335
- ownKeys() {
336
- return Array.from(signals.keys());
337
- },
338
- getOwnPropertyDescriptor(_target, prop) {
339
- const signal = signals.get(String(prop));
340
- return signal ? {
341
- enumerable: true,
342
- configurable: true,
343
- writable: true,
344
- value: signal
345
- } : undefined;
346
- }
347
- });
348
- };
349
- var isStore = (value) => isObjectOfType(value, TYPE_STORE);
350
-
351
- // src/signal.ts
352
- var UNSET = Symbol();
353
- var isSignal = (value) => isState(value) || isComputed(value) || isStore(value);
354
- function toSignal(value) {
355
- if (isSignal(value))
356
- return value;
357
- if (isComputedCallback(value))
358
- return computed(value);
359
- if (Array.isArray(value))
360
- return store(arrayToRecord(value));
361
- if (isRecord(value))
362
- return store(value);
363
- return state(value);
364
- }
365
- function toMutableSignal(value) {
366
- if (isState(value) || isStore(value))
367
- return value;
368
- if (Array.isArray(value))
369
- return store(arrayToRecord(value));
370
- if (isRecord(value))
371
- return store(value);
372
- return state(value);
373
- }
374
-
375
- // src/diff.ts
376
- var isEqual = (a, b, visited) => {
377
- if (Object.is(a, b))
378
- return true;
379
- if (typeof a !== typeof b)
380
- return false;
381
- if (typeof a !== "object" || a === null || b === null)
382
- return false;
383
- if (!visited)
384
- visited = new WeakSet;
385
- if (visited.has(a) || visited.has(b))
386
- throw new CircularDependencyError("isEqual");
387
- visited.add(a);
388
- visited.add(b);
389
- try {
390
- if (Array.isArray(a) && Array.isArray(b)) {
391
- if (a.length !== b.length)
392
- return false;
393
- for (let i = 0;i < a.length; i++) {
394
- if (!isEqual(a[i], b[i], visited))
395
- return false;
396
- }
397
- return true;
398
- }
399
- if (Array.isArray(a) !== Array.isArray(b))
400
- return false;
401
- if (isRecord(a) && isRecord(b)) {
402
- const aKeys = Object.keys(a);
403
- const bKeys = Object.keys(b);
404
- if (aKeys.length !== bKeys.length)
405
- return false;
406
- for (const key of aKeys) {
407
- if (!(key in b))
408
- return false;
409
- if (!isEqual(a[key], b[key], visited))
410
- return false;
411
- }
412
- return true;
413
- }
414
- return false;
415
- } finally {
416
- visited.delete(a);
417
- visited.delete(b);
418
- }
419
- };
420
- var diff = (oldObj, newObj) => {
421
- const visited = new WeakSet;
422
- const diffRecords = (oldRecord, newRecord) => {
423
- const add = {};
424
- const change = {};
425
- const remove = {};
426
- const oldKeys = Object.keys(oldRecord);
427
- const newKeys = Object.keys(newRecord);
428
- const allKeys = new Set([...oldKeys, ...newKeys]);
429
- for (const key of allKeys) {
430
- const oldHas = key in oldRecord;
431
- const newHas = key in newRecord;
432
- if (!oldHas && newHas) {
433
- add[key] = newRecord[key];
434
- continue;
435
- } else if (oldHas && !newHas) {
436
- remove[key] = UNSET;
437
- continue;
438
- }
439
- const oldValue = oldRecord[key];
440
- const newValue = newRecord[key];
441
- if (!isEqual(oldValue, newValue, visited))
442
- change[key] = newValue;
443
- }
444
- const changed = Object.keys(add).length > 0 || Object.keys(change).length > 0 || Object.keys(remove).length > 0;
445
- return {
446
- changed,
447
- add,
448
- change,
449
- remove
450
- };
451
- };
452
- return diffRecords(oldObj, newObj);
453
- };
454
-
455
252
  // src/computed.ts
456
253
  var TYPE_COMPUTED = "Computed";
457
254
  var computed = (fn) => {
@@ -551,23 +348,60 @@ var computed = (fn) => {
551
348
  };
552
349
  var isComputed = (value) => isObjectOfType(value, TYPE_COMPUTED);
553
350
  var isComputedCallback = (value) => isFunction(value) && value.length < 2;
351
+ // src/effect.ts
352
+ var effect = (callback) => {
353
+ const isAsync = isAsyncFunction(callback);
354
+ let running = false;
355
+ let controller;
356
+ const run = watch(() => observe(() => {
357
+ if (running)
358
+ throw new CircularDependencyError("effect");
359
+ running = true;
360
+ controller?.abort();
361
+ controller = undefined;
362
+ let cleanup;
363
+ try {
364
+ if (isAsync) {
365
+ controller = new AbortController;
366
+ const currentController = controller;
367
+ callback(controller.signal).then((cleanup2) => {
368
+ if (isFunction(cleanup2) && controller === currentController)
369
+ run.off(cleanup2);
370
+ }).catch((error) => {
371
+ if (!isAbortError(error))
372
+ console.error("Async effect error:", error);
373
+ });
374
+ } else {
375
+ cleanup = callback();
376
+ if (isFunction(cleanup))
377
+ run.off(cleanup);
378
+ }
379
+ } catch (error) {
380
+ if (!isAbortError(error))
381
+ console.error("Effect callback error:", error);
382
+ }
383
+ running = false;
384
+ }, run));
385
+ run();
386
+ return () => {
387
+ controller?.abort();
388
+ run.cleanup();
389
+ };
390
+ };
554
391
  // src/match.ts
555
392
  function match(result, handlers) {
556
393
  try {
557
- if (result.pending) {
394
+ if (result.pending)
558
395
  handlers.nil?.();
559
- } else if (result.errors) {
396
+ else if (result.errors)
560
397
  handlers.err?.(result.errors);
561
- } else {
562
- handlers.ok?.(result.values);
563
- }
398
+ else if (result.ok)
399
+ handlers.ok(result.values);
564
400
  } catch (error) {
565
- if (handlers.err && (!result.errors || !result.errors.includes(toError(error)))) {
566
- const allErrors = result.errors ? [...result.errors, toError(error)] : [toError(error)];
567
- handlers.err(allErrors);
568
- } else {
401
+ if (handlers.err && (!result.errors || !result.errors.includes(toError(error))))
402
+ handlers.err(result.errors ? [...result.errors, toError(error)] : [toError(error)]);
403
+ else
569
404
  throw error;
570
- }
571
405
  }
572
406
  }
573
407
  // src/resolve.ts
@@ -578,23 +412,304 @@ function resolve(signals) {
578
412
  for (const [key, signal] of Object.entries(signals)) {
579
413
  try {
580
414
  const value = signal.get();
581
- if (value === UNSET) {
415
+ if (value === UNSET)
582
416
  pending2 = true;
583
- } else {
417
+ else
584
418
  values[key] = value;
585
- }
586
419
  } catch (e) {
587
420
  errors.push(toError(e));
588
421
  }
589
422
  }
590
- if (pending2) {
423
+ if (pending2)
591
424
  return { ok: false, pending: true };
592
- }
593
- if (errors.length > 0) {
425
+ if (errors.length > 0)
594
426
  return { ok: false, errors };
595
- }
596
427
  return { ok: true, values };
597
428
  }
429
+ // src/state.ts
430
+ var TYPE_STATE = "State";
431
+ var state = (initialValue) => {
432
+ const watchers = new Set;
433
+ let value = initialValue;
434
+ const s = {
435
+ [Symbol.toStringTag]: TYPE_STATE,
436
+ get: () => {
437
+ subscribe(watchers);
438
+ return value;
439
+ },
440
+ set: (v) => {
441
+ if (v == null)
442
+ throw new NullishSignalValueError("state");
443
+ if (isEqual(value, v))
444
+ return;
445
+ value = v;
446
+ notify(watchers);
447
+ if (UNSET === value)
448
+ watchers.clear();
449
+ },
450
+ update: (fn) => {
451
+ s.set(fn(value));
452
+ }
453
+ };
454
+ return s;
455
+ };
456
+ var isState = (value) => isObjectOfType(value, TYPE_STATE);
457
+
458
+ // src/store.ts
459
+ var TYPE_STORE = "Store";
460
+ var STORE_EVENT_ADD = "store-add";
461
+ var STORE_EVENT_CHANGE = "store-change";
462
+ var STORE_EVENT_REMOVE = "store-remove";
463
+ var STORE_EVENT_SORT = "store-sort";
464
+ var store = (initialValue) => {
465
+ const watchers = new Set;
466
+ const eventTarget = new EventTarget;
467
+ const signals = new Map;
468
+ const cleanups = new Map;
469
+ const isArrayLike = Array.isArray(initialValue);
470
+ const size = state(0);
471
+ const current = () => {
472
+ const record = {};
473
+ for (const [key, signal] of signals) {
474
+ record[key] = signal.get();
475
+ }
476
+ return record;
477
+ };
478
+ const emit = (type, detail) => eventTarget.dispatchEvent(new CustomEvent(type, { detail }));
479
+ const getSortedIndexes = () => Array.from(signals.keys()).map((k) => Number(k)).filter((n) => Number.isInteger(n)).sort((a, b) => a - b);
480
+ const isValidValue = (key, value) => {
481
+ if (value == null)
482
+ throw new NullishSignalValueError(`store for key "${key}"`);
483
+ if (value === UNSET)
484
+ return true;
485
+ if (isSymbol(value) || isFunction(value) || isComputed(value))
486
+ throw new InvalidSignalValueError(`store for key "${key}"`, valueString(value));
487
+ return true;
488
+ };
489
+ const addProperty = (key, value, single = false) => {
490
+ if (!isValidValue(key, value))
491
+ return false;
492
+ const signal = isState(value) || isStore(value) ? value : isRecord(value) ? store(value) : Array.isArray(value) ? store(value) : state(value);
493
+ signals.set(key, signal);
494
+ const cleanup = effect(() => {
495
+ const currentValue = signal.get();
496
+ if (currentValue != null)
497
+ emit(STORE_EVENT_CHANGE, {
498
+ [key]: currentValue
499
+ });
500
+ });
501
+ cleanups.set(key, cleanup);
502
+ if (single) {
503
+ size.set(signals.size);
504
+ notify(watchers);
505
+ emit(STORE_EVENT_ADD, {
506
+ [key]: value
507
+ });
508
+ }
509
+ return true;
510
+ };
511
+ const removeProperty = (key, single = false) => {
512
+ const ok = signals.delete(key);
513
+ if (ok) {
514
+ const cleanup = cleanups.get(key);
515
+ if (cleanup)
516
+ cleanup();
517
+ cleanups.delete(key);
518
+ }
519
+ if (single) {
520
+ size.set(signals.size);
521
+ notify(watchers);
522
+ emit(STORE_EVENT_REMOVE, {
523
+ [key]: UNSET
524
+ });
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
+ const value = changes.add[key] ?? UNSET;
534
+ addProperty(key, value);
535
+ }
536
+ if (initialRun) {
537
+ setTimeout(() => {
538
+ emit(STORE_EVENT_ADD, changes.add);
539
+ }, 0);
540
+ } else {
541
+ emit(STORE_EVENT_ADD, changes.add);
542
+ }
543
+ }
544
+ if (Object.keys(changes.change).length) {
545
+ for (const key in changes.change) {
546
+ const value = changes.change[key];
547
+ if (!isValidValue(key, value))
548
+ continue;
549
+ const signal = signals.get(key);
550
+ if (isMutableSignal(signal))
551
+ signal.set(value);
552
+ else
553
+ throw new StoreKeyReadonlyError(key, valueString(value));
554
+ }
555
+ emit(STORE_EVENT_CHANGE, changes.change);
556
+ }
557
+ if (Object.keys(changes.remove).length) {
558
+ for (const key in changes.remove)
559
+ removeProperty(key);
560
+ emit(STORE_EVENT_REMOVE, changes.remove);
561
+ }
562
+ size.set(signals.size);
563
+ });
564
+ return changes.changed;
565
+ };
566
+ reconcile({}, initialValue, true);
567
+ const s = {
568
+ add: isArrayLike ? (v) => {
569
+ const nextIndex = signals.size;
570
+ const key = String(nextIndex);
571
+ addProperty(key, v, true);
572
+ } : (k, v) => {
573
+ if (!signals.has(k))
574
+ addProperty(k, v, true);
575
+ else
576
+ throw new StoreKeyExistsError(k, valueString(v));
577
+ },
578
+ get: () => {
579
+ subscribe(watchers);
580
+ return recordToArray(current());
581
+ },
582
+ remove: isArrayLike ? (index) => {
583
+ const currentArray = recordToArray(current());
584
+ const currentLength = signals.size;
585
+ if (!Array.isArray(currentArray) || index <= -currentLength || index >= currentLength)
586
+ throw new StoreKeyRangeError(index);
587
+ const newArray = [...currentArray];
588
+ newArray.splice(index, 1);
589
+ if (reconcile(currentArray, newArray))
590
+ notify(watchers);
591
+ } : (k) => {
592
+ if (signals.has(k))
593
+ removeProperty(k, true);
594
+ },
595
+ set: (v) => {
596
+ if (reconcile(current(), v)) {
597
+ notify(watchers);
598
+ if (UNSET === v)
599
+ watchers.clear();
600
+ }
601
+ },
602
+ update: (fn) => {
603
+ const oldValue = current();
604
+ const newValue = fn(recordToArray(oldValue));
605
+ if (reconcile(oldValue, newValue)) {
606
+ notify(watchers);
607
+ if (UNSET === newValue)
608
+ watchers.clear();
609
+ }
610
+ },
611
+ sort: (compareFn) => {
612
+ 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])));
613
+ const newOrder = entries.map(([key]) => String(key));
614
+ const newSignals = new Map;
615
+ entries.forEach(([key], newIndex) => {
616
+ const oldKey = String(key);
617
+ const newKey = isArrayLike ? String(newIndex) : String(key);
618
+ const signal = signals.get(oldKey);
619
+ if (signal)
620
+ newSignals.set(newKey, signal);
621
+ });
622
+ signals.clear();
623
+ newSignals.forEach((signal, key) => signals.set(key, signal));
624
+ notify(watchers);
625
+ emit(STORE_EVENT_SORT, newOrder);
626
+ },
627
+ addEventListener: eventTarget.addEventListener.bind(eventTarget),
628
+ removeEventListener: eventTarget.removeEventListener.bind(eventTarget),
629
+ dispatchEvent: eventTarget.dispatchEvent.bind(eventTarget),
630
+ size
631
+ };
632
+ return new Proxy({}, {
633
+ get(_target, prop) {
634
+ if (prop === Symbol.toStringTag)
635
+ return TYPE_STORE;
636
+ if (prop === Symbol.isConcatSpreadable)
637
+ return isArrayLike;
638
+ if (prop === Symbol.iterator)
639
+ return isArrayLike ? function* () {
640
+ const indexes = getSortedIndexes();
641
+ for (const index of indexes) {
642
+ const signal = signals.get(String(index));
643
+ if (signal)
644
+ yield signal;
645
+ }
646
+ } : function* () {
647
+ for (const [key, signal] of signals)
648
+ yield [key, signal];
649
+ };
650
+ if (isSymbol(prop))
651
+ return;
652
+ if (prop in s)
653
+ return s[prop];
654
+ if (prop === "length" && isArrayLike) {
655
+ subscribe(watchers);
656
+ return size.get();
657
+ }
658
+ return signals.get(prop);
659
+ },
660
+ has(_target, prop) {
661
+ const stringProp = String(prop);
662
+ return stringProp && signals.has(stringProp) || Object.keys(s).includes(stringProp) || prop === Symbol.toStringTag || prop === Symbol.iterator || prop === Symbol.isConcatSpreadable || prop === "length" && isArrayLike;
663
+ },
664
+ ownKeys() {
665
+ return isArrayLike ? getSortedIndexes().map((key) => String(key)).concat(["length"]) : Array.from(signals.keys()).map((key) => String(key));
666
+ },
667
+ getOwnPropertyDescriptor(_target, prop) {
668
+ const nonEnumerable = (value) => ({
669
+ enumerable: false,
670
+ configurable: true,
671
+ writable: false,
672
+ value
673
+ });
674
+ if (prop === "length" && isArrayLike)
675
+ return {
676
+ enumerable: true,
677
+ configurable: true,
678
+ writable: false,
679
+ value: size.get()
680
+ };
681
+ if (prop === Symbol.isConcatSpreadable)
682
+ return nonEnumerable(isArrayLike);
683
+ if (prop === Symbol.toStringTag)
684
+ return nonEnumerable(TYPE_STORE);
685
+ if (isSymbol(prop))
686
+ return;
687
+ if (Object.keys(s).includes(prop))
688
+ return nonEnumerable(s[prop]);
689
+ const signal = signals.get(prop);
690
+ return signal ? {
691
+ enumerable: true,
692
+ configurable: true,
693
+ writable: true,
694
+ value: signal
695
+ } : undefined;
696
+ }
697
+ });
698
+ };
699
+ var isStore = (value) => isObjectOfType(value, TYPE_STORE);
700
+
701
+ // src/signal.ts
702
+ var isSignal = (value) => isState(value) || isComputed(value) || isStore(value);
703
+ var isMutableSignal = (value) => isState(value) || isStore(value);
704
+ function toSignal(value) {
705
+ if (isSignal(value))
706
+ return value;
707
+ if (isComputedCallback(value))
708
+ return computed(value);
709
+ if (Array.isArray(value) || isRecord(value))
710
+ return store(value);
711
+ return state(value);
712
+ }
598
713
  export {
599
714
  watch,
600
715
  toSignal,
@@ -606,9 +721,15 @@ export {
606
721
  observe,
607
722
  notify,
608
723
  match,
724
+ isSymbol,
725
+ isString,
609
726
  isStore,
610
727
  isState,
611
728
  isSignal,
729
+ isRecordOrArray,
730
+ isRecord,
731
+ isNumber,
732
+ isMutableSignal,
612
733
  isFunction,
613
734
  isEqual,
614
735
  isComputedCallback,
@@ -625,5 +746,10 @@ export {
625
746
  TYPE_STORE,
626
747
  TYPE_STATE,
627
748
  TYPE_COMPUTED,
749
+ StoreKeyReadonlyError,
750
+ StoreKeyRangeError,
751
+ StoreKeyExistsError,
752
+ NullishSignalValueError,
753
+ InvalidSignalValueError,
628
754
  CircularDependencyError
629
755
  };