@zeix/cause-effect 0.14.2 → 0.15.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.
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 isFunction = (fn) => typeof fn === "function";
92
+ var isAsyncFunction = (fn) => isFunction(fn) && fn.constructor.name === "AsyncFunction";
92
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";
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,319 @@ 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
+ }
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
+
129
351
  // src/signal.ts
130
352
  var UNSET = Symbol();
131
- var isSignal = (value) => isState(value) || isComputed(value);
132
- var toSignal = (value) => isSignal(value) ? value : isComputedCallback(value) ? computed(value) : state(value);
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
+ };
133
454
 
134
455
  // src/computed.ts
135
456
  var TYPE_COMPUTED = "Computed";
@@ -142,7 +463,7 @@ var computed = (fn) => {
142
463
  let changed = false;
143
464
  let computing = false;
144
465
  const ok = (v) => {
145
- if (!Object.is(v, value)) {
466
+ if (!isEqual(v, value)) {
146
467
  value = v;
147
468
  changed = true;
148
469
  }
@@ -169,17 +490,20 @@ var computed = (fn) => {
169
490
  };
170
491
  const mark = watch(() => {
171
492
  dirty = true;
172
- controller?.abort("Aborted because source signal changed");
493
+ controller?.abort();
173
494
  if (watchers.size)
174
495
  notify(watchers);
175
496
  else
176
497
  mark.cleanup();
177
498
  });
499
+ mark.off(() => {
500
+ controller?.abort();
501
+ });
178
502
  const compute = () => observe(() => {
179
503
  if (computing)
180
504
  throw new CircularDependencyError("computed");
181
505
  changed = false;
182
- if (isFunction(fn) && fn.constructor.name === "AsyncFunction") {
506
+ if (isAsyncFunction(fn)) {
183
507
  if (controller)
184
508
  return value;
185
509
  controller = new AbortController;
@@ -196,7 +520,7 @@ var computed = (fn) => {
196
520
  try {
197
521
  result = controller ? fn(controller.signal) : fn();
198
522
  } catch (e) {
199
- if (e instanceof DOMException && e.name === "AbortError")
523
+ if (isAbortError(e))
200
524
  nil();
201
525
  else
202
526
  err(e);
@@ -227,67 +551,78 @@ var computed = (fn) => {
227
551
  };
228
552
  var isComputed = (value) => isObjectOfType(value, TYPE_COMPUTED);
229
553
  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 = () => {
554
+ // src/match.ts
555
+ function match(result, handlers) {
556
+ try {
557
+ if (result.pending) {
558
+ handlers.nil?.();
559
+ } else if (result.errors) {
560
+ handlers.err?.(result.errors);
561
+ } else {
562
+ handlers.ok?.(result.values);
239
563
  }
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;
564
+ } 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 {
569
+ throw error;
570
+ }
571
+ }
572
+ }
573
+ // src/resolve.ts
574
+ function resolve(signals) {
575
+ const errors = [];
576
+ let pending2 = false;
577
+ const values = {};
578
+ for (const [key, signal] of Object.entries(signals)) {
260
579
  try {
261
- cleanup = pending2 ? nil() : errors.length ? err(...errors) : ok(...values);
580
+ const value = signal.get();
581
+ if (value === UNSET) {
582
+ pending2 = true;
583
+ } else {
584
+ values[key] = value;
585
+ }
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
+ }
593
+ if (errors.length > 0) {
594
+ return { ok: false, errors };
595
+ }
596
+ return { ok: true, values };
272
597
  }
273
598
  export {
274
599
  watch,
275
600
  toSignal,
601
+ toError,
276
602
  subscribe,
603
+ store,
277
604
  state,
605
+ resolve,
278
606
  observe,
279
607
  notify,
608
+ match,
609
+ isStore,
280
610
  isState,
281
611
  isSignal,
282
612
  isFunction,
613
+ isEqual,
283
614
  isComputedCallback,
284
615
  isComputed,
616
+ isAsyncFunction,
617
+ isAbortError,
285
618
  flush,
286
619
  enqueue,
287
620
  effect,
621
+ diff,
288
622
  computed,
289
623
  batch,
290
624
  UNSET,
625
+ TYPE_STORE,
291
626
  TYPE_STATE,
292
627
  TYPE_COMPUTED,
293
628
  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,l=()=>{k=void 0;let $=Array.from(h.values());h.clear();for(let B of $)B()},B$=()=>{if(k)cancelAnimationFrame(k);k=requestAnimationFrame(l)};queueMicrotask(l);var m=($)=>{let B=new Set,W=$;return W.off=(x)=>{B.add(x)},W.cleanup=()=>{for(let x of B)x();B.clear()},W},U=($)=>{if(q&&!$.has(q)){let B=q;$.add(B),q.off(()=>{$.delete(B)})}},M=($)=>{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--}},y=($,B)=>{let W=q;q=B;try{$()}finally{q=W}},W$=($,B)=>new Promise((W,x)=>{h.set(B||Symbol(),()=>{try{W($())}catch(J){x(J)}}),B$()});var A=($)=>typeof $==="function",E=($)=>A($)&&$.constructor.name==="AsyncFunction",O=($,B)=>Object.prototype.toString.call($)===`[object ${B}]`,V=($)=>O($,"Object");var d=($)=>{let B={};for(let W=0;W<$.length;W++)if(W in $)B[String(W)]=$[W];return B},a=($,B)=>(B in $)&&A($[B]),R=($)=>$ instanceof DOMException&&$.name==="AbortError",Y=($)=>$ instanceof Error?$:Error(String($));class D extends Error{constructor($){super(`Circular dependency in ${$} detected`);this.name="CircularDependencyError"}}var o="State",S=($)=>{let B=new Set,W=$,x={[Symbol.toStringTag]:o,get:()=>{return U(B),W},set:(J)=>{if(P(W,J))return;if(W=J,M(B),C===W)B.clear()},update:(J)=>{x.set(J(W))}};return x},b=($)=>O($,o);var c=($)=>{let B=E($),W=!1,x,J=m(()=>y(()=>{if(W)throw new D("effect");W=!0,x?.abort(),x=void 0;let F;try{if(B){x=new AbortController;let H=x;$(x.signal).then((z)=>{if(A(z)&&x===H)J.off(z)}).catch((z)=>{if(!R(z))console.error("Async effect error:",z)})}else if(F=$(),A(F))J.off(F)}catch(H){if(!R(H))console.error("Effect callback error:",H)}W=!1},J));return J(),()=>{x?.abort(),J.cleanup()}};var n="Store",T=($)=>{let B=new Set,W=new EventTarget,x=new Map,J=new Map,F=S(0),H=()=>{let Z={};for(let[L,Q]of x)Z[L]=Q.get();return Z},z=(Z,L)=>W.dispatchEvent(new CustomEvent(Z,{detail:L})),K=(Z,L)=>{let Q=e(L);x.set(Z,Q);let G=c(()=>{let X=Q.get();if(X!=null)z("store-change",{[Z]:X})});J.set(Z,G)},N=(Z)=>{x.delete(Z);let L=J.get(Z);if(L)L();J.delete(Z)},j=(Z,L)=>{let Q=u(Z,L);return v(()=>{if(Object.keys(Q.add).length){for(let G in Q.add){let X=Q.add[G];if(X!=null)K(G,X)}z("store-add",Q.add)}if(Object.keys(Q.change).length){for(let G in Q.change){let X=x.get(G),I=Q.change[G];if(X&&I!=null&&a(X,"set"))X.set(I)}z("store-change",Q.change)}if(Object.keys(Q.remove).length){for(let G in Q.remove)N(G);z("store-remove",Q.remove)}F.set(x.size)}),Q.changed};j({},$),setTimeout(()=>{let Z=new CustomEvent("store-add",{detail:$});W.dispatchEvent(Z)},0);let _=["add","get","remove","set","update","addEventListener","removeEventListener","dispatchEvent","size"];return new Proxy({},{get(Z,L){let Q=String(L);switch(L){case"add":return(G,X)=>{if(!x.has(G))K(G,X),M(B),z("store-add",{[G]:X}),F.set(x.size)};case"get":return()=>{return U(B),H()};case"remove":return(G)=>{if(x.has(G))N(G),M(B),z("store-remove",{[G]:C}),F.set(x.size)};case"set":return(G)=>{if(j(H(),G)){if(M(B),C===G)B.clear()}};case"update":return(G)=>{let X=H(),I=G(X);if(j(X,I)){if(M(B),C===I)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 F}if(L===Symbol.toStringTag)return n;if(L===Symbol.iterator)return function*(){for(let[G,X]of x)yield[G,X]};return x.get(Q)},has(Z,L){let Q=String(L);return x.has(Q)||_.includes(Q)||L===Symbol.toStringTag||L===Symbol.iterator},ownKeys(){return Array.from(x.keys())},getOwnPropertyDescriptor(Z,L){let Q=x.get(String(L));return Q?{enumerable:!0,configurable:!0,writable:!0,value:Q}:void 0}})},g=($)=>O($,n);var C=Symbol(),$$=($)=>b($)||t($)||g($);function x$($){if($$($))return $;if(s($))return i($);if(Array.isArray($))return T(d($));if(V($))return T($);return S($)}function e($){if(b($)||g($))return $;if(Array.isArray($))return T(d($));if(V($))return T($);return S($)}var P=($,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 D("isEqual");W.add($),W.add(B);try{if(Array.isArray($)&&Array.isArray(B)){if($.length!==B.length)return!1;for(let x=0;x<$.length;x++)if(!P($[x],B[x],W))return!1;return!0}if(Array.isArray($)!==Array.isArray(B))return!1;if(V($)&&V(B)){let x=Object.keys($),J=Object.keys(B);if(x.length!==J.length)return!1;for(let F of x){if(!(F in B))return!1;if(!P($[F],B[F],W))return!1}return!0}return!1}finally{W.delete($),W.delete(B)}},u=($,B)=>{let W=new WeakSet;return((J,F)=>{let H={},z={},K={},N=Object.keys(J),j=Object.keys(F),_=new Set([...N,...j]);for(let L of _){let Q=L in J,G=L in F;if(!Q&&G){H[L]=F[L];continue}else if(Q&&!G){K[L]=C;continue}let X=J[L],I=F[L];if(!P(X,I,W))z[L]=I}return{changed:Object.keys(H).length>0||Object.keys(z).length>0||Object.keys(K).length>0,add:H,change:z,remove:K}})($,B)};var r="Computed",i=($)=>{let B=new Set,W=C,x,J,F=!0,H=!1,z=!1,K=(G)=>{if(!P(G,W))W=G,H=!0;x=void 0,F=!1},N=()=>{H=C!==W,W=C,x=void 0},j=(G)=>{let X=Y(G);H=!x||X.name!==x.name||X.message!==x.message,W=C,x=X},_=(G)=>(X)=>{if(z=!1,J=void 0,G(X),H)M(B)},Z=m(()=>{if(F=!0,J?.abort(),B.size)M(B);else Z.cleanup()});Z.off(()=>{J?.abort()});let L=()=>y(()=>{if(z)throw new D("computed");if(H=!1,E($)){if(J)return W;J=new AbortController,J.signal.addEventListener("abort",()=>{z=!1,J=void 0,L()},{once:!0})}let G;z=!0;try{G=J?$(J.signal):$()}catch(X){if(R(X))N();else j(X);z=!1;return}if(G instanceof Promise)G.then(_(K),_(j));else if(G==null||C===G)N();else K(G);z=!1},Z);return{[Symbol.toStringTag]:r,get:()=>{if(U(B),w(),F)L();if(x)throw x;return W}}},t=($)=>O($,r),s=($)=>A($)&&$.length<2;function G$($,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)))){let x=$.errors?[...$.errors,Y(W)]:[Y(W)];B.err(x)}else throw W}}function J$($){let B=[],W=!1,x={};for(let[J,F]of Object.entries($))try{let H=F.get();if(H===C)W=!0;else x[J]=H}catch(H){B.push(Y(H))}if(W)return{ok:!1,pending:!0};if(B.length>0)return{ok:!1,errors:B};return{ok:!0,values:x}}export{m as watch,x$ as toSignal,Y as toError,U as subscribe,T as store,S as state,J$ as resolve,y as observe,M as notify,G$ as match,g as isStore,b as isState,$$ as isSignal,A as isFunction,P as isEqual,s as isComputedCallback,t as isComputed,E as isAsyncFunction,R as isAbortError,w as flush,W$ as enqueue,c as effect,u as diff,i as computed,v as batch,C as UNSET,n as TYPE_STORE,o as TYPE_STATE,r as TYPE_COMPUTED,D 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.0
4
4
  * @author Esther Brunner
5
5
  */
6
6
 
@@ -9,9 +9,13 @@ 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 { type DiffResult, diff, isEqual, type UnknownRecord } from './src/diff'
16
+ export { type EffectCallback, effect, type MaybeCleanup } from './src/effect'
17
+ export { type MatchHandlers, match } from './src/match'
18
+ export { type ResolveResult, resolve } from './src/resolve'
15
19
  export {
16
20
  batch,
17
21
  type Cleanup,
@@ -25,7 +29,6 @@ export {
25
29
  watch,
26
30
  } from './src/scheduler'
27
31
  export {
28
- isComputedCallback,
29
32
  isSignal,
30
33
  type MaybeSignal,
31
34
  type Signal,
@@ -34,4 +37,20 @@ export {
34
37
  UNSET,
35
38
  } from './src/signal'
36
39
  export { isState, type State, state, TYPE_STATE } from './src/state'
37
- export { CircularDependencyError, isFunction } from './src/util'
40
+ export {
41
+ isStore,
42
+ type Store,
43
+ type StoreAddEvent,
44
+ type StoreChangeEvent,
45
+ type StoreEventMap,
46
+ type StoreRemoveEvent,
47
+ store,
48
+ TYPE_STORE,
49
+ } from './src/store'
50
+ export {
51
+ CircularDependencyError,
52
+ isAbortError,
53
+ isAsyncFunction,
54
+ isFunction,
55
+ toError,
56
+ } 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.0",
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