@zeix/cause-effect 0.15.1 → 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,380 +1,76 @@
1
- // src/scheduler.ts
2
- var active;
3
- var pending = new Set;
4
- var batchDepth = 0;
5
- var updateMap = new Map;
6
- var requestId;
7
- var updateDOM = () => {
8
- requestId = undefined;
9
- const updates = Array.from(updateMap.values());
10
- updateMap.clear();
11
- for (const update of updates) {
12
- update();
1
+ // src/errors.ts
2
+ class CircularDependencyError extends Error {
3
+ constructor(where) {
4
+ super(`Circular dependency detected in ${where}`);
5
+ this.name = "CircularDependencyError";
13
6
  }
14
- };
15
- var requestTick = () => {
16
- if (requestId)
17
- cancelAnimationFrame(requestId);
18
- requestId = requestAnimationFrame(updateDOM);
19
- };
20
- queueMicrotask(updateDOM);
21
- var watch = (notice) => {
22
- const cleanups = new Set;
23
- const w = notice;
24
- w.off = (on) => {
25
- cleanups.add(on);
26
- };
27
- w.cleanup = () => {
28
- for (const cleanup of cleanups) {
29
- cleanup();
30
- }
31
- cleanups.clear();
32
- };
33
- return w;
34
- };
35
- var subscribe = (watchers) => {
36
- if (active && !watchers.has(active)) {
37
- const watcher = active;
38
- watchers.add(watcher);
39
- active.off(() => {
40
- watchers.delete(watcher);
41
- });
7
+ }
8
+
9
+ class InvalidSignalValueError extends TypeError {
10
+ constructor(where, value) {
11
+ super(`Invalid signal value ${value} in ${where}`);
12
+ this.name = "InvalidSignalValueError";
42
13
  }
43
- };
44
- var notify = (watchers) => {
45
- for (const watcher of watchers) {
46
- if (batchDepth)
47
- pending.add(watcher);
48
- else
49
- watcher();
14
+ }
15
+
16
+ class NullishSignalValueError extends TypeError {
17
+ constructor(where) {
18
+ super(`Nullish signal values are not allowed in ${where}`);
19
+ this.name = "NullishSignalValueError";
50
20
  }
51
- };
52
- var flush = () => {
53
- while (pending.size) {
54
- const watchers = Array.from(pending);
55
- pending.clear();
56
- for (const watcher of watchers) {
57
- watcher();
58
- }
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";
59
27
  }
60
- };
61
- var batch = (fn) => {
62
- batchDepth++;
63
- try {
64
- fn();
65
- } finally {
66
- flush();
67
- batchDepth--;
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";
68
34
  }
69
- };
70
- var observe = (run, watcher) => {
71
- const prev = active;
72
- active = watcher;
73
- try {
74
- run();
75
- } finally {
76
- active = prev;
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";
77
41
  }
78
- };
79
- var enqueue = (fn, dedupe) => new Promise((resolve, reject) => {
80
- updateMap.set(dedupe || Symbol(), () => {
81
- try {
82
- resolve(fn());
83
- } catch (error) {
84
- reject(error);
85
- }
86
- });
87
- requestTick();
88
- });
42
+ }
89
43
 
90
44
  // src/util.ts
45
+ var UNSET = Symbol();
91
46
  var isString = (value) => typeof value === "string";
92
47
  var isNumber = (value) => typeof value === "number";
48
+ var isSymbol = (value) => typeof value === "symbol";
93
49
  var isFunction = (fn) => typeof fn === "function";
94
50
  var isAsyncFunction = (fn) => isFunction(fn) && fn.constructor.name === "AsyncFunction";
51
+ var isDefinedObject = (value) => !!value && typeof value === "object";
95
52
  var isObjectOfType = (value, type) => Object.prototype.toString.call(value) === `[object ${type}]`;
96
53
  var isRecord = (value) => isObjectOfType(value, "Object");
54
+ var isRecordOrArray = (value) => isRecord(value) || Array.isArray(value);
97
55
  var validArrayIndexes = (keys) => {
98
56
  if (!keys.length)
99
57
  return null;
100
58
  const indexes = keys.map((k) => isString(k) ? parseInt(k, 10) : isNumber(k) ? k : NaN);
101
59
  return indexes.every((index) => Number.isFinite(index) && index >= 0) ? indexes.sort((a, b) => a - b) : null;
102
60
  };
103
- var hasMethod = (obj, methodName) => (methodName in obj) && isFunction(obj[methodName]);
104
61
  var isAbortError = (error) => error instanceof DOMException && error.name === "AbortError";
105
62
  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
- }).catch((error) => {
161
- if (!isAbortError(error))
162
- console.error("Async effect error:", error);
163
- });
164
- } else {
165
- cleanup = callback();
166
- if (isFunction(cleanup))
167
- run.off(cleanup);
168
- }
169
- } catch (error) {
170
- if (!isAbortError(error))
171
- console.error("Effect callback error:", error);
172
- }
173
- running = false;
174
- }, run));
175
- run();
176
- return () => {
177
- controller?.abort();
178
- run.cleanup();
179
- };
180
- };
181
-
182
- // src/store.ts
183
- var TYPE_STORE = "Store";
184
- var store = (initialValue) => {
185
- const watchers = new Set;
186
- const eventTarget = new EventTarget;
187
- const signals = new Map;
188
- const cleanups = new Map;
189
- const size = state(0);
190
- const current = () => {
191
- const keys = Array.from(signals.keys());
192
- const arrayIndexes = validArrayIndexes(keys);
193
- if (arrayIndexes)
194
- return arrayIndexes.map((index) => signals.get(String(index))?.get());
195
- const record = {};
196
- for (const [key, signal] of signals) {
197
- record[key] = signal.get();
198
- }
63
+ var recordToArray = (record) => {
64
+ const indexes = validArrayIndexes(Object.keys(record));
65
+ if (indexes === null)
199
66
  return record;
200
- };
201
- const emit = (type, detail) => eventTarget.dispatchEvent(new CustomEvent(type, { detail }));
202
- const addProperty = (key, value) => {
203
- const stringKey = String(key);
204
- const signal = toMutableSignal(value);
205
- signals.set(stringKey, signal);
206
- const cleanup = effect(() => {
207
- const currentValue = signal.get();
208
- if (currentValue != null)
209
- emit("store-change", { [key]: currentValue });
210
- });
211
- cleanups.set(stringKey, cleanup);
212
- };
213
- const removeProperty = (key) => {
214
- const stringKey = String(key);
215
- signals.delete(stringKey);
216
- const cleanup = cleanups.get(stringKey);
217
- if (cleanup)
218
- cleanup();
219
- cleanups.delete(stringKey);
220
- };
221
- const reconcile = (oldValue, newValue) => {
222
- const changes = diff(oldValue, newValue);
223
- batch(() => {
224
- if (Object.keys(changes.add).length) {
225
- for (const key in changes.add) {
226
- const value = changes.add[key];
227
- if (value != null)
228
- addProperty(key, value);
229
- }
230
- emit("store-add", changes.add);
231
- }
232
- if (Object.keys(changes.change).length) {
233
- for (const key in changes.change) {
234
- const signal = signals.get(key);
235
- const value = changes.change[key];
236
- if (signal && value != null && hasMethod(signal, "set"))
237
- signal.set(value);
238
- }
239
- emit("store-change", changes.change);
240
- }
241
- if (Object.keys(changes.remove).length) {
242
- for (const key in changes.remove) {
243
- removeProperty(key);
244
- }
245
- emit("store-remove", changes.remove);
246
- }
247
- size.set(signals.size);
248
- });
249
- return changes.changed;
250
- };
251
- reconcile({}, initialValue);
252
- setTimeout(() => {
253
- const initialAdditionsEvent = new CustomEvent("store-add", {
254
- detail: initialValue
255
- });
256
- eventTarget.dispatchEvent(initialAdditionsEvent);
257
- }, 0);
258
- const storeProps = [
259
- "add",
260
- "get",
261
- "remove",
262
- "set",
263
- "update",
264
- "addEventListener",
265
- "removeEventListener",
266
- "dispatchEvent",
267
- "size"
268
- ];
269
- return new Proxy({}, {
270
- get(_target, prop) {
271
- switch (prop) {
272
- case "add":
273
- return (k, v) => {
274
- if (!signals.has(k)) {
275
- addProperty(k, v);
276
- notify(watchers);
277
- emit("store-add", {
278
- [k]: v
279
- });
280
- size.set(signals.size);
281
- }
282
- };
283
- case "get":
284
- return () => {
285
- subscribe(watchers);
286
- return current();
287
- };
288
- case "remove":
289
- return (k) => {
290
- if (signals.has(k)) {
291
- removeProperty(k);
292
- notify(watchers);
293
- emit("store-remove", { [k]: UNSET });
294
- size.set(signals.size);
295
- }
296
- };
297
- case "set":
298
- return (v) => {
299
- if (reconcile(current(), v)) {
300
- notify(watchers);
301
- if (UNSET === v)
302
- watchers.clear();
303
- }
304
- };
305
- case "update":
306
- return (fn) => {
307
- const oldValue = current();
308
- const newValue = fn(oldValue);
309
- if (reconcile(oldValue, newValue)) {
310
- notify(watchers);
311
- if (UNSET === newValue)
312
- watchers.clear();
313
- }
314
- };
315
- case "addEventListener":
316
- return eventTarget.addEventListener.bind(eventTarget);
317
- case "removeEventListener":
318
- return eventTarget.removeEventListener.bind(eventTarget);
319
- case "dispatchEvent":
320
- return eventTarget.dispatchEvent.bind(eventTarget);
321
- case "size":
322
- return size;
323
- }
324
- if (prop === Symbol.toStringTag)
325
- return TYPE_STORE;
326
- if (prop === Symbol.iterator) {
327
- return function* () {
328
- for (const [key, signal] of signals) {
329
- yield [key, signal];
330
- }
331
- };
332
- }
333
- return signals.get(String(prop));
334
- },
335
- has(_target, prop) {
336
- const key = String(prop);
337
- return signals.has(key) || storeProps.includes(key) || prop === Symbol.toStringTag || prop === Symbol.iterator;
338
- },
339
- ownKeys() {
340
- return Array.from(signals.keys()).map((key) => String(key));
341
- },
342
- getOwnPropertyDescriptor(_target, prop) {
343
- const signal = signals.get(String(prop));
344
- return signal ? {
345
- enumerable: true,
346
- configurable: true,
347
- writable: true,
348
- value: signal
349
- } : undefined;
350
- }
351
- });
67
+ const array = [];
68
+ for (const index of indexes) {
69
+ array.push(record[String(index)]);
70
+ }
71
+ return array;
352
72
  };
353
- var isStore = (value) => isObjectOfType(value, TYPE_STORE);
354
-
355
- // src/signal.ts
356
- var UNSET = Symbol();
357
- var isSignal = (value) => isState(value) || isComputed(value) || isStore(value);
358
- function toSignal(value) {
359
- if (isSignal(value))
360
- return value;
361
- if (isComputedCallback(value))
362
- return computed(value);
363
- if (Array.isArray(value))
364
- return store(value);
365
- if (Array.isArray(value) || isRecord(value))
366
- return store(value);
367
- return state(value);
368
- }
369
- function toMutableSignal(value) {
370
- if (isState(value) || isStore(value))
371
- return value;
372
- if (Array.isArray(value))
373
- return store(value);
374
- if (isRecord(value))
375
- return store(value);
376
- return state(value);
377
- }
73
+ var valueString = (value) => isString(value) ? `"${value}"` : isDefinedObject(value) ? JSON.stringify(value) : String(value);
378
74
 
379
75
  // src/diff.ts
380
76
  var isEqual = (a, b, visited) => {
@@ -415,46 +111,143 @@ var isEqual = (a, b, visited) => {
415
111
  }
416
112
  return true;
417
113
  }
418
- return false;
419
- } finally {
420
- visited.delete(a);
421
- visited.delete(b);
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
+
163
+ // src/scheduler.ts
164
+ var active;
165
+ var pending = new Set;
166
+ var batchDepth = 0;
167
+ var updateMap = new Map;
168
+ var requestId;
169
+ var updateDOM = () => {
170
+ requestId = undefined;
171
+ const updates = Array.from(updateMap.values());
172
+ updateMap.clear();
173
+ for (const update of updates) {
174
+ update();
175
+ }
176
+ };
177
+ var requestTick = () => {
178
+ if (requestId)
179
+ cancelAnimationFrame(requestId);
180
+ requestId = requestAnimationFrame(updateDOM);
181
+ };
182
+ queueMicrotask(updateDOM);
183
+ var watch = (notice) => {
184
+ const cleanups = new Set;
185
+ const w = notice;
186
+ w.off = (on) => {
187
+ cleanups.add(on);
188
+ };
189
+ w.cleanup = () => {
190
+ for (const cleanup of cleanups) {
191
+ cleanup();
192
+ }
193
+ cleanups.clear();
194
+ };
195
+ return w;
196
+ };
197
+ var subscribe = (watchers) => {
198
+ if (active && !watchers.has(active)) {
199
+ const watcher = active;
200
+ watchers.add(watcher);
201
+ active.off(() => {
202
+ watchers.delete(watcher);
203
+ });
422
204
  }
423
205
  };
424
- var diff = (oldObj, newObj) => {
425
- const visited = new WeakSet;
426
- const diffRecords = (oldRecord, newRecord) => {
427
- const add = {};
428
- const change = {};
429
- const remove = {};
430
- const oldKeys = Object.keys(oldRecord);
431
- const newKeys = Object.keys(newRecord);
432
- const allKeys = new Set([...oldKeys, ...newKeys]);
433
- for (const key of allKeys) {
434
- const oldHas = key in oldRecord;
435
- const newHas = key in newRecord;
436
- if (!oldHas && newHas) {
437
- add[key] = newRecord[key];
438
- continue;
439
- } else if (oldHas && !newHas) {
440
- remove[key] = UNSET;
441
- continue;
442
- }
443
- const oldValue = oldRecord[key];
444
- const newValue = newRecord[key];
445
- if (!isEqual(oldValue, newValue, visited))
446
- change[key] = newValue;
206
+ var notify = (watchers) => {
207
+ for (const watcher of watchers) {
208
+ if (batchDepth)
209
+ pending.add(watcher);
210
+ else
211
+ watcher();
212
+ }
213
+ };
214
+ var flush = () => {
215
+ while (pending.size) {
216
+ const watchers = Array.from(pending);
217
+ pending.clear();
218
+ for (const watcher of watchers) {
219
+ watcher();
447
220
  }
448
- const changed = Object.keys(add).length > 0 || Object.keys(change).length > 0 || Object.keys(remove).length > 0;
449
- return {
450
- changed,
451
- add,
452
- change,
453
- remove
454
- };
455
- };
456
- return diffRecords(oldObj, newObj);
221
+ }
222
+ };
223
+ var batch = (fn) => {
224
+ batchDepth++;
225
+ try {
226
+ fn();
227
+ } finally {
228
+ flush();
229
+ batchDepth--;
230
+ }
231
+ };
232
+ var observe = (run, watcher) => {
233
+ const prev = active;
234
+ active = watcher;
235
+ try {
236
+ run();
237
+ } finally {
238
+ active = prev;
239
+ }
457
240
  };
241
+ var enqueue = (fn, dedupe) => new Promise((resolve, reject) => {
242
+ updateMap.set(dedupe || Symbol(), () => {
243
+ try {
244
+ resolve(fn());
245
+ } catch (error) {
246
+ reject(error);
247
+ }
248
+ });
249
+ requestTick();
250
+ });
458
251
 
459
252
  // src/computed.ts
460
253
  var TYPE_COMPUTED = "Computed";
@@ -555,6 +348,46 @@ var computed = (fn) => {
555
348
  };
556
349
  var isComputed = (value) => isObjectOfType(value, TYPE_COMPUTED);
557
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
+ };
558
391
  // src/match.ts
559
392
  function match(result, handlers) {
560
393
  try {
@@ -562,8 +395,8 @@ function match(result, handlers) {
562
395
  handlers.nil?.();
563
396
  else if (result.errors)
564
397
  handlers.err?.(result.errors);
565
- else
566
- handlers.ok?.(result.values);
398
+ else if (result.ok)
399
+ handlers.ok(result.values);
567
400
  } catch (error) {
568
401
  if (handlers.err && (!result.errors || !result.errors.includes(toError(error))))
569
402
  handlers.err(result.errors ? [...result.errors, toError(error)] : [toError(error)]);
@@ -593,10 +426,293 @@ function resolve(signals) {
593
426
  return { ok: false, errors };
594
427
  return { ok: true, values };
595
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
+ }
596
713
  export {
597
714
  watch,
598
715
  toSignal,
599
- toMutableSignal,
600
716
  toError,
601
717
  subscribe,
602
718
  store,
@@ -605,11 +721,15 @@ export {
605
721
  observe,
606
722
  notify,
607
723
  match,
724
+ isSymbol,
608
725
  isString,
609
726
  isStore,
610
727
  isState,
611
728
  isSignal,
729
+ isRecordOrArray,
730
+ isRecord,
612
731
  isNumber,
732
+ isMutableSignal,
613
733
  isFunction,
614
734
  isEqual,
615
735
  isComputedCallback,
@@ -626,5 +746,10 @@ export {
626
746
  TYPE_STORE,
627
747
  TYPE_STATE,
628
748
  TYPE_COMPUTED,
749
+ StoreKeyReadonlyError,
750
+ StoreKeyRangeError,
751
+ StoreKeyExistsError,
752
+ NullishSignalValueError,
753
+ InvalidSignalValueError,
629
754
  CircularDependencyError
630
755
  };