@zeix/cause-effect 0.14.2 → 0.15.1

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
@@ -88,8 +88,20 @@ var enqueue = (fn, dedupe) => new Promise((resolve, reject) => {
88
88
  });
89
89
 
90
90
  // src/util.ts
91
- var isFunction = (value) => typeof value === "function";
91
+ var isString = (value) => typeof value === "string";
92
+ var isNumber = (value) => typeof value === "number";
93
+ var isFunction = (fn) => typeof fn === "function";
94
+ var isAsyncFunction = (fn) => isFunction(fn) && fn.constructor.name === "AsyncFunction";
92
95
  var isObjectOfType = (value, type) => Object.prototype.toString.call(value) === `[object ${type}]`;
96
+ var isRecord = (value) => isObjectOfType(value, "Object");
97
+ var validArrayIndexes = (keys) => {
98
+ if (!keys.length)
99
+ return null;
100
+ const indexes = keys.map((k) => isString(k) ? parseInt(k, 10) : isNumber(k) ? k : NaN);
101
+ return indexes.every((index) => Number.isFinite(index) && index >= 0) ? indexes.sort((a, b) => a - b) : null;
102
+ };
103
+ var hasMethod = (obj, methodName) => (methodName in obj) && isFunction(obj[methodName]);
104
+ var isAbortError = (error) => error instanceof DOMException && error.name === "AbortError";
93
105
  var toError = (reason) => reason instanceof Error ? reason : Error(String(reason));
94
106
 
95
107
  class CircularDependencyError extends Error {
@@ -111,7 +123,7 @@ var state = (initialValue) => {
111
123
  return value;
112
124
  },
113
125
  set: (v) => {
114
- if (Object.is(value, v))
126
+ if (isEqual(value, v))
115
127
  return;
116
128
  value = v;
117
129
  notify(watchers);
@@ -126,10 +138,323 @@ var state = (initialValue) => {
126
138
  };
127
139
  var isState = (value) => isObjectOfType(value, TYPE_STATE);
128
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
+ }
199
+ 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
+ });
352
+ };
353
+ var isStore = (value) => isObjectOfType(value, TYPE_STORE);
354
+
129
355
  // src/signal.ts
130
356
  var UNSET = Symbol();
131
- var isSignal = (value) => isState(value) || isComputed(value);
132
- var toSignal = (value) => isSignal(value) ? value : isComputedCallback(value) ? computed(value) : state(value);
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
+ }
378
+
379
+ // src/diff.ts
380
+ var isEqual = (a, b, visited) => {
381
+ if (Object.is(a, b))
382
+ return true;
383
+ if (typeof a !== typeof b)
384
+ return false;
385
+ if (typeof a !== "object" || a === null || b === null)
386
+ return false;
387
+ if (!visited)
388
+ visited = new WeakSet;
389
+ if (visited.has(a) || visited.has(b))
390
+ throw new CircularDependencyError("isEqual");
391
+ visited.add(a);
392
+ visited.add(b);
393
+ try {
394
+ if (Array.isArray(a) && Array.isArray(b)) {
395
+ if (a.length !== b.length)
396
+ return false;
397
+ for (let i = 0;i < a.length; i++) {
398
+ if (!isEqual(a[i], b[i], visited))
399
+ return false;
400
+ }
401
+ return true;
402
+ }
403
+ if (Array.isArray(a) !== Array.isArray(b))
404
+ return false;
405
+ if (isRecord(a) && isRecord(b)) {
406
+ const aKeys = Object.keys(a);
407
+ const bKeys = Object.keys(b);
408
+ if (aKeys.length !== bKeys.length)
409
+ return false;
410
+ for (const key of aKeys) {
411
+ if (!(key in b))
412
+ return false;
413
+ if (!isEqual(a[key], b[key], visited))
414
+ return false;
415
+ }
416
+ return true;
417
+ }
418
+ return false;
419
+ } finally {
420
+ visited.delete(a);
421
+ visited.delete(b);
422
+ }
423
+ };
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;
447
+ }
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);
457
+ };
133
458
 
134
459
  // src/computed.ts
135
460
  var TYPE_COMPUTED = "Computed";
@@ -142,7 +467,7 @@ var computed = (fn) => {
142
467
  let changed = false;
143
468
  let computing = false;
144
469
  const ok = (v) => {
145
- if (!Object.is(v, value)) {
470
+ if (!isEqual(v, value)) {
146
471
  value = v;
147
472
  changed = true;
148
473
  }
@@ -169,17 +494,20 @@ var computed = (fn) => {
169
494
  };
170
495
  const mark = watch(() => {
171
496
  dirty = true;
172
- controller?.abort("Aborted because source signal changed");
497
+ controller?.abort();
173
498
  if (watchers.size)
174
499
  notify(watchers);
175
500
  else
176
501
  mark.cleanup();
177
502
  });
503
+ mark.off(() => {
504
+ controller?.abort();
505
+ });
178
506
  const compute = () => observe(() => {
179
507
  if (computing)
180
508
  throw new CircularDependencyError("computed");
181
509
  changed = false;
182
- if (isFunction(fn) && fn.constructor.name === "AsyncFunction") {
510
+ if (isAsyncFunction(fn)) {
183
511
  if (controller)
184
512
  return value;
185
513
  controller = new AbortController;
@@ -196,7 +524,7 @@ var computed = (fn) => {
196
524
  try {
197
525
  result = controller ? fn(controller.signal) : fn();
198
526
  } catch (e) {
199
- if (e instanceof DOMException && e.name === "AbortError")
527
+ if (isAbortError(e))
200
528
  nil();
201
529
  else
202
530
  err(e);
@@ -227,67 +555,75 @@ var computed = (fn) => {
227
555
  };
228
556
  var isComputed = (value) => isObjectOfType(value, TYPE_COMPUTED);
229
557
  var isComputedCallback = (value) => isFunction(value) && value.length < 2;
230
- // src/effect.ts
231
- function effect(matcher) {
232
- const {
233
- signals,
234
- ok,
235
- err = (error) => {
236
- console.error(error);
237
- },
238
- nil = () => {
239
- }
240
- } = isFunction(matcher) ? { signals: [], ok: matcher } : matcher;
241
- let running = false;
242
- const run = watch(() => observe(() => {
243
- if (running)
244
- throw new CircularDependencyError("effect");
245
- running = true;
246
- const errors = [];
247
- let pending2 = false;
248
- const values = signals.map((signal) => {
249
- try {
250
- const value = signal.get();
251
- if (value === UNSET)
252
- pending2 = true;
253
- return value;
254
- } catch (e) {
255
- errors.push(toError(e));
256
- return UNSET;
257
- }
258
- });
259
- let cleanup;
558
+ // src/match.ts
559
+ function match(result, handlers) {
560
+ try {
561
+ if (result.pending)
562
+ handlers.nil?.();
563
+ else if (result.errors)
564
+ handlers.err?.(result.errors);
565
+ else
566
+ handlers.ok?.(result.values);
567
+ } catch (error) {
568
+ if (handlers.err && (!result.errors || !result.errors.includes(toError(error))))
569
+ handlers.err(result.errors ? [...result.errors, toError(error)] : [toError(error)]);
570
+ else
571
+ throw error;
572
+ }
573
+ }
574
+ // src/resolve.ts
575
+ function resolve(signals) {
576
+ const errors = [];
577
+ let pending2 = false;
578
+ const values = {};
579
+ for (const [key, signal] of Object.entries(signals)) {
260
580
  try {
261
- cleanup = pending2 ? nil() : errors.length ? err(...errors) : ok(...values);
581
+ const value = signal.get();
582
+ if (value === UNSET)
583
+ pending2 = true;
584
+ else
585
+ values[key] = value;
262
586
  } catch (e) {
263
- cleanup = err(toError(e));
264
- } finally {
265
- if (isFunction(cleanup))
266
- run.off(cleanup);
587
+ errors.push(toError(e));
267
588
  }
268
- running = false;
269
- }, run));
270
- run();
271
- return () => run.cleanup();
589
+ }
590
+ if (pending2)
591
+ return { ok: false, pending: true };
592
+ if (errors.length > 0)
593
+ return { ok: false, errors };
594
+ return { ok: true, values };
272
595
  }
273
596
  export {
274
597
  watch,
275
598
  toSignal,
599
+ toMutableSignal,
600
+ toError,
276
601
  subscribe,
602
+ store,
277
603
  state,
604
+ resolve,
278
605
  observe,
279
606
  notify,
607
+ match,
608
+ isString,
609
+ isStore,
280
610
  isState,
281
611
  isSignal,
612
+ isNumber,
282
613
  isFunction,
614
+ isEqual,
283
615
  isComputedCallback,
284
616
  isComputed,
617
+ isAsyncFunction,
618
+ isAbortError,
285
619
  flush,
286
620
  enqueue,
287
621
  effect,
622
+ diff,
288
623
  computed,
289
624
  batch,
290
625
  UNSET,
626
+ TYPE_STORE,
291
627
  TYPE_STATE,
292
628
  TYPE_COMPUTED,
293
629
  CircularDependencyError
package/index.js CHANGED
@@ -1 +1 @@
1
- var V,M=new Set,U=0,k=new Map,D,f=()=>{D=void 0;let $=Array.from(k.values());k.clear();for(let B of $)B()},d=()=>{if(D)cancelAnimationFrame(D);D=requestAnimationFrame(f)};queueMicrotask(f);var Y=($)=>{let B=new Set,W=$;return W.off=(z)=>{B.add(z)},W.cleanup=()=>{for(let z of B)z();B.clear()},W},I=($)=>{if(V&&!$.has(V)){let B=V;$.add(B),V.off(()=>{$.delete(B)})}},F=($)=>{for(let B of $)if(U)M.add(B);else B()},N=()=>{while(M.size){let $=Array.from(M);M.clear();for(let B of $)B()}},h=($)=>{U++;try{$()}finally{N(),U--}},O=($,B)=>{let W=V;V=B;try{$()}finally{V=W}},v=($,B)=>new Promise((W,z)=>{k.set(B||Symbol(),()=>{try{W($())}catch(G){z(G)}}),d()});var j=($)=>typeof $==="function",P=($,B)=>Object.prototype.toString.call($)===`[object ${B}]`,C=($)=>$ instanceof Error?$:Error(String($));class R extends Error{constructor($){super(`Circular dependency in ${$} detected`);this.name="CircularDependencyError"}}var _="State",S=($)=>{let B=new Set,W=$,z={[Symbol.toStringTag]:_,get:()=>{return I(B),W},set:(G)=>{if(Object.is(W,G))return;if(W=G,F(B),K===W)B.clear()},update:(G)=>{z.set(G(W))}};return z},T=($)=>P($,_);var K=Symbol(),p=($)=>T($)||b($),o=($)=>p($)?$:g($)?E($):S($);var m="Computed",E=($)=>{let B=new Set,W=K,z,G,X=!0,L=!1,J=!1,x=(H)=>{if(!Object.is(H,W))W=H,L=!0;z=void 0,X=!1},q=()=>{L=K!==W,W=K,z=void 0},Z=(H)=>{let Q=C(H);L=!z||Q.name!==z.name||Q.message!==z.message,W=K,z=Q},A=(H)=>(Q)=>{if(J=!1,G=void 0,H(Q),L)F(B)},y=Y(()=>{if(X=!0,G?.abort("Aborted because source signal changed"),B.size)F(B);else y.cleanup()}),w=()=>O(()=>{if(J)throw new R("computed");if(L=!1,j($)&&$.constructor.name==="AsyncFunction"){if(G)return W;G=new AbortController,G.signal.addEventListener("abort",()=>{J=!1,G=void 0,w()},{once:!0})}let H;J=!0;try{H=G?$(G.signal):$()}catch(Q){if(Q instanceof DOMException&&Q.name==="AbortError")q();else Z(Q);J=!1;return}if(H instanceof Promise)H.then(A(x),A(Z));else if(H==null||K===H)q();else x(H);J=!1},y);return{[Symbol.toStringTag]:m,get:()=>{if(I(B),N(),X)w();if(z)throw z;return W}}},b=($)=>P($,m),g=($)=>j($)&&$.length<2;function s($){let{signals:B,ok:W,err:z=(J)=>{console.error(J)},nil:G=()=>{}}=j($)?{signals:[],ok:$}:$,X=!1,L=Y(()=>O(()=>{if(X)throw new R("effect");X=!0;let J=[],x=!1,q=B.map((A)=>{try{let y=A.get();if(y===K)x=!0;return y}catch(y){return J.push(C(y)),K}}),Z;try{Z=x?G():J.length?z(...J):W(...q)}catch(A){Z=z(C(A))}finally{if(j(Z))L.off(Z)}X=!1},L));return L(),()=>L.cleanup()}export{Y as watch,o as toSignal,I as subscribe,S as state,O as observe,F as notify,T as isState,p as isSignal,j as isFunction,g as isComputedCallback,b as isComputed,N as flush,v as enqueue,s as effect,E as computed,h as batch,K as UNSET,_ as TYPE_STATE,m as TYPE_COMPUTED,R as CircularDependencyError};
1
+ var q,f=new Set,p=0,h=new Map,k,r=()=>{k=void 0;let $=Array.from(h.values());h.clear();for(let B of $)B()},F$=()=>{if(k)cancelAnimationFrame(k);k=requestAnimationFrame(r)};queueMicrotask(r);var E=($)=>{let B=new Set,W=$;return W.off=(F)=>{B.add(F)},W.cleanup=()=>{for(let F of B)F();B.clear()},W},O=($)=>{if(q&&!$.has(q)){let B=q;$.add(B),q.off(()=>{$.delete(B)})}},C=($)=>{for(let B of $)if(p)f.add(B);else B()},w=()=>{while(f.size){let $=Array.from(f);f.clear();for(let B of $)B()}},v=($)=>{p++;try{$()}finally{w(),p--}},T=($,B)=>{let W=q;q=B;try{$()}finally{q=W}},G$=($,B)=>new Promise((W,F)=>{h.set(B||Symbol(),()=>{try{W($())}catch(Q){F(Q)}}),F$()});var a=($)=>typeof $==="string",e=($)=>typeof $==="number",D=($)=>typeof $==="function",y=($)=>D($)&&$.constructor.name==="AsyncFunction",P=($,B)=>Object.prototype.toString.call($)===`[object ${B}]`,R=($)=>P($,"Object"),$$=($)=>{if(!$.length)return null;let B=$.map((W)=>a(W)?parseInt(W,10):e(W)?W:NaN);return B.every((W)=>Number.isFinite(W)&&W>=0)?B.sort((W,F)=>W-F):null},B$=($,B)=>(B in $)&&D($[B]),V=($)=>$ instanceof DOMException&&$.name==="AbortError",Y=($)=>$ instanceof Error?$:Error(String($));class I extends Error{constructor($){super(`Circular dependency in ${$} detected`);this.name="CircularDependencyError"}}var d="State",S=($)=>{let B=new Set,W=$,F={[Symbol.toStringTag]:d,get:()=>{return O(B),W},set:(Q)=>{if(K(W,Q))return;if(W=Q,C(B),z===W)B.clear()},update:(Q)=>{F.set(Q(W))}};return F},b=($)=>P($,d);var o=($)=>{let B=y($),W=!1,F,Q=E(()=>T(()=>{if(W)throw new I("effect");W=!0,F?.abort(),F=void 0;let Z;try{if(B){F=new AbortController;let A=F;$(F.signal).then((H)=>{if(D(H)&&F===A)Q.off(H)}).catch((H)=>{if(!V(H))console.error("Async effect error:",H)})}else if(Z=$(),D(Z))Q.off(Z)}catch(A){if(!V(A))console.error("Effect callback error:",A)}W=!1},Q));return Q(),()=>{F?.abort(),Q.cleanup()}};var c="Store",m=($)=>{let B=new Set,W=new EventTarget,F=new Map,Q=new Map,Z=S(0),A=()=>{let M=Array.from(F.keys()),L=$$(M);if(L)return L.map((J)=>F.get(String(J))?.get());let G={};for(let[J,X]of F)G[J]=X.get();return G},H=(M,L)=>W.dispatchEvent(new CustomEvent(M,{detail:L})),x=(M,L)=>{let G=String(M),J=n(L);F.set(G,J);let X=o(()=>{let j=J.get();if(j!=null)H("store-change",{[M]:j})});Q.set(G,X)},U=(M)=>{let L=String(M);F.delete(L);let G=Q.get(L);if(G)G();Q.delete(L)},N=(M,L)=>{let G=u(M,L);return v(()=>{if(Object.keys(G.add).length){for(let J in G.add){let X=G.add[J];if(X!=null)x(J,X)}H("store-add",G.add)}if(Object.keys(G.change).length){for(let J in G.change){let X=F.get(J),j=G.change[J];if(X&&j!=null&&B$(X,"set"))X.set(j)}H("store-change",G.change)}if(Object.keys(G.remove).length){for(let J in G.remove)U(J);H("store-remove",G.remove)}Z.set(F.size)}),G.changed};N({},$),setTimeout(()=>{let M=new CustomEvent("store-add",{detail:$});W.dispatchEvent(M)},0);let _=["add","get","remove","set","update","addEventListener","removeEventListener","dispatchEvent","size"];return new Proxy({},{get(M,L){switch(L){case"add":return(G,J)=>{if(!F.has(G))x(G,J),C(B),H("store-add",{[G]:J}),Z.set(F.size)};case"get":return()=>{return O(B),A()};case"remove":return(G)=>{if(F.has(G))U(G),C(B),H("store-remove",{[G]:z}),Z.set(F.size)};case"set":return(G)=>{if(N(A(),G)){if(C(B),z===G)B.clear()}};case"update":return(G)=>{let J=A(),X=G(J);if(N(J,X)){if(C(B),z===X)B.clear()}};case"addEventListener":return W.addEventListener.bind(W);case"removeEventListener":return W.removeEventListener.bind(W);case"dispatchEvent":return W.dispatchEvent.bind(W);case"size":return Z}if(L===Symbol.toStringTag)return c;if(L===Symbol.iterator)return function*(){for(let[G,J]of F)yield[G,J]};return F.get(String(L))},has(M,L){let G=String(L);return F.has(G)||_.includes(G)||L===Symbol.toStringTag||L===Symbol.iterator},ownKeys(){return Array.from(F.keys()).map((M)=>String(M))},getOwnPropertyDescriptor(M,L){let G=F.get(String(L));return G?{enumerable:!0,configurable:!0,writable:!0,value:G}:void 0}})},g=($)=>P($,c);var z=Symbol(),W$=($)=>b($)||t($)||g($);function J$($){if(W$($))return $;if(s($))return i($);if(Array.isArray($))return m($);if(Array.isArray($)||R($))return m($);return S($)}function n($){if(b($)||g($))return $;if(Array.isArray($))return m($);if(R($))return m($);return S($)}var K=($,B,W)=>{if(Object.is($,B))return!0;if(typeof $!==typeof B)return!1;if(typeof $!=="object"||$===null||B===null)return!1;if(!W)W=new WeakSet;if(W.has($)||W.has(B))throw new I("isEqual");W.add($),W.add(B);try{if(Array.isArray($)&&Array.isArray(B)){if($.length!==B.length)return!1;for(let F=0;F<$.length;F++)if(!K($[F],B[F],W))return!1;return!0}if(Array.isArray($)!==Array.isArray(B))return!1;if(R($)&&R(B)){let F=Object.keys($),Q=Object.keys(B);if(F.length!==Q.length)return!1;for(let Z of F){if(!(Z in B))return!1;if(!K($[Z],B[Z],W))return!1}return!0}return!1}finally{W.delete($),W.delete(B)}},u=($,B)=>{let W=new WeakSet;return((Q,Z)=>{let A={},H={},x={},U=Object.keys(Q),N=Object.keys(Z),_=new Set([...U,...N]);for(let L of _){let G=L in Q,J=L in Z;if(!G&&J){A[L]=Z[L];continue}else if(G&&!J){x[L]=z;continue}let X=Q[L],j=Z[L];if(!K(X,j,W))H[L]=j}return{changed:Object.keys(A).length>0||Object.keys(H).length>0||Object.keys(x).length>0,add:A,change:H,remove:x}})($,B)};var l="Computed",i=($)=>{let B=new Set,W=z,F,Q,Z=!0,A=!1,H=!1,x=(J)=>{if(!K(J,W))W=J,A=!0;F=void 0,Z=!1},U=()=>{A=z!==W,W=z,F=void 0},N=(J)=>{let X=Y(J);A=!F||X.name!==F.name||X.message!==F.message,W=z,F=X},_=(J)=>(X)=>{if(H=!1,Q=void 0,J(X),A)C(B)},M=E(()=>{if(Z=!0,Q?.abort(),B.size)C(B);else M.cleanup()});M.off(()=>{Q?.abort()});let L=()=>T(()=>{if(H)throw new I("computed");if(A=!1,y($)){if(Q)return W;Q=new AbortController,Q.signal.addEventListener("abort",()=>{H=!1,Q=void 0,L()},{once:!0})}let J;H=!0;try{J=Q?$(Q.signal):$()}catch(X){if(V(X))U();else N(X);H=!1;return}if(J instanceof Promise)J.then(_(x),_(N));else if(J==null||z===J)U();else x(J);H=!1},M);return{[Symbol.toStringTag]:l,get:()=>{if(O(B),w(),Z)L();if(F)throw F;return W}}},t=($)=>P($,l),s=($)=>D($)&&$.length<2;function L$($,B){try{if($.pending)B.nil?.();else if($.errors)B.err?.($.errors);else B.ok?.($.values)}catch(W){if(B.err&&(!$.errors||!$.errors.includes(Y(W))))B.err($.errors?[...$.errors,Y(W)]:[Y(W)]);else throw W}}function Q$($){let B=[],W=!1,F={};for(let[Q,Z]of Object.entries($))try{let A=Z.get();if(A===z)W=!0;else F[Q]=A}catch(A){B.push(Y(A))}if(W)return{ok:!1,pending:!0};if(B.length>0)return{ok:!1,errors:B};return{ok:!0,values:F}}export{E as watch,J$ as toSignal,n as toMutableSignal,Y as toError,O as subscribe,m as store,S as state,Q$ as resolve,T as observe,C as notify,L$ as match,a as isString,g as isStore,b as isState,W$ as isSignal,e as isNumber,D as isFunction,K as isEqual,s as isComputedCallback,t as isComputed,y as isAsyncFunction,V as isAbortError,w as flush,G$ as enqueue,o as effect,u as diff,i as computed,v as batch,z as UNSET,c as TYPE_STORE,d as TYPE_STATE,l as TYPE_COMPUTED,I as CircularDependencyError};
package/index.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @name Cause & Effect
3
- * @version 0.14.2
3
+ * @version 0.15.1
4
4
  * @author Esther Brunner
5
5
  */
6
6
 
@@ -9,9 +9,19 @@ export {
9
9
  type ComputedCallback,
10
10
  computed,
11
11
  isComputed,
12
+ isComputedCallback,
12
13
  TYPE_COMPUTED,
13
14
  } from './src/computed'
14
- export { type EffectMatcher, effect } from './src/effect'
15
+ export {
16
+ type DiffResult,
17
+ diff,
18
+ isEqual,
19
+ type UnknownRecord,
20
+ type UnknownRecordOrArray,
21
+ } from './src/diff'
22
+ export { type EffectCallback, effect, type MaybeCleanup } from './src/effect'
23
+ export { type MatchHandlers, match } from './src/match'
24
+ export { type ResolveResult, resolve } from './src/resolve'
15
25
  export {
16
26
  batch,
17
27
  type Cleanup,
@@ -25,13 +35,32 @@ export {
25
35
  watch,
26
36
  } from './src/scheduler'
27
37
  export {
28
- isComputedCallback,
29
38
  isSignal,
30
39
  type MaybeSignal,
31
40
  type Signal,
32
41
  type SignalValues,
42
+ toMutableSignal,
33
43
  toSignal,
34
44
  UNSET,
45
+ type UnknownSignalRecord,
35
46
  } from './src/signal'
36
47
  export { isState, type State, state, TYPE_STATE } from './src/state'
37
- export { CircularDependencyError, isFunction } from './src/util'
48
+ export {
49
+ isStore,
50
+ type Store,
51
+ type StoreAddEvent,
52
+ type StoreChangeEvent,
53
+ type StoreEventMap,
54
+ type StoreRemoveEvent,
55
+ store,
56
+ TYPE_STORE,
57
+ } from './src/store'
58
+ export {
59
+ CircularDependencyError,
60
+ isAbortError,
61
+ isAsyncFunction,
62
+ isFunction,
63
+ isNumber,
64
+ isString,
65
+ toError,
66
+ } from './src/util'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeix/cause-effect",
3
- "version": "0.14.2",
3
+ "version": "0.15.1",
4
4
  "author": "Esther Brunner",
5
5
  "main": "index.js",
6
6
  "module": "index.ts",
@@ -29,5 +29,5 @@
29
29
  "lint": "bunx biome lint --write"
30
30
  },
31
31
  "type": "module",
32
- "types": "index.d.ts"
32
+ "types": "types/index.d.ts"
33
33
  }
package/src/computed.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { isEqual } from './diff'
1
2
  import {
2
3
  flush,
3
4
  notify,
@@ -9,6 +10,8 @@ import {
9
10
  import { UNSET } from './signal'
10
11
  import {
11
12
  CircularDependencyError,
13
+ isAbortError,
14
+ isAsyncFunction,
12
15
  isFunction,
13
16
  isObjectOfType,
14
17
  toError,
@@ -50,7 +53,7 @@ const computed = <T extends {}>(fn: ComputedCallback<T>): Computed<T> => {
50
53
 
51
54
  // Functions to update internal state
52
55
  const ok = (v: T): undefined => {
53
- if (!Object.is(v, value)) {
56
+ if (!isEqual(v, value)) {
54
57
  value = v
55
58
  changed = true
56
59
  }
@@ -83,25 +86,31 @@ const computed = <T extends {}>(fn: ComputedCallback<T>): Computed<T> => {
83
86
  // Own watcher: called when notified from sources (push)
84
87
  const mark = watch(() => {
85
88
  dirty = true
86
- controller?.abort('Aborted because source signal changed')
89
+ controller?.abort()
87
90
  if (watchers.size) notify(watchers)
88
91
  else mark.cleanup()
89
92
  })
93
+ mark.off(() => {
94
+ controller?.abort()
95
+ })
90
96
 
91
97
  // Called when requested by dependencies (pull)
92
98
  const compute = () =>
93
99
  observe(() => {
94
100
  if (computing) throw new CircularDependencyError('computed')
95
101
  changed = false
96
- if (isFunction(fn) && fn.constructor.name === 'AsyncFunction') {
97
- if (controller) return value // return current value until promise resolves
102
+ if (isAsyncFunction(fn)) {
103
+ // Return current value until promise resolves
104
+ if (controller) return value
98
105
  controller = new AbortController()
99
106
  controller.signal.addEventListener(
100
107
  'abort',
101
108
  () => {
102
109
  computing = false
103
110
  controller = undefined
104
- compute() // retry
111
+
112
+ // Retry computation with updated state
113
+ compute()
105
114
  },
106
115
  {
107
116
  once: true,
@@ -113,7 +122,7 @@ const computed = <T extends {}>(fn: ComputedCallback<T>): Computed<T> => {
113
122
  try {
114
123
  result = controller ? fn(controller.signal) : (fn as () => T)()
115
124
  } catch (e) {
116
- if (e instanceof DOMException && e.name === 'AbortError') nil()
125
+ if (isAbortError(e)) nil()
117
126
  else err(e)
118
127
  computing = false
119
128
  return