All files sync.ts

100% Statements 47/47
100% Branches 7/7
100% Functions 14/14
100% Lines 39/39

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100  1x 1x 1x 1x     38x   38x 43x 38x     27x 1x 26x 26x     1x     25x 25x 25x 25x   25x 25x 25x 25x 25x 31x 31x 21x 21x   31x       43x 43x 5x 51x       31x       2x 2x     1x 27x 1x 14x 1x                                                                            
import {FanOut} from './fanout';
 
const NO_CACHE: unique symbol = Symbol();
 
/** React.js synchronous state interface. */
export interface SyncStore<T> {
  subscribe: SyncStoreSubscribe;
  getSnapshot: () => T;
}
export type SyncStoreSubscribe = (callback: () => void) => SyncStoreUnsubscribe;
export type SyncStoreUnsubscribe = () => void;
 
export type SyncDep<T> = SyncValue<T> & SyncStore<T> & FanOut<void>;
 
export type WrapListInSyncDep<T extends unknown[]> = {
  [K in keyof T]: SyncDep<T[K]>;
};
 
export interface Disposable {
  dispose(): void;
}
 
export interface SyncValue<T> {
  value: T;
}
 
export class Value<V> extends FanOut<void> implements SyncStore<V>, SyncValue<V> {
  constructor(value: V) {
    super();
    this.value = value;
  }
 
  public next(value: V, force = false): void {
    if (!force && this.value === value) return;
    this.value = value;
    this.emit();
  }
 
  /** ----------------------------------------------------- {@link SyncValue} */
 
  public value: V;
 
  /** ----------------------------------------------------- {@link SyncStore} */
 
  public readonly subscribe: SyncStoreSubscribe = (cb) => this.listen(cb);
  public readonly getSnapshot: () => V = () => this.value;
}
 
export class Computed<N, V extends unknown[] = any>
  extends FanOut<void>
  implements SyncValue<N>, SyncStore<N>, Disposable
{
  private cache: N | typeof NO_CACHE = NO_CACHE;
  private subs: SyncStoreUnsubscribe[];
 
  constructor(
    protected readonly deps: WrapListInSyncDep<V>,
    protected readonly compute: (args: V) => N,
  ) {
    super();
    const subs = (this.subs = [] as SyncStoreUnsubscribe[]);
    const length = deps.length;
    for (let i = 0; i < length; i++) {
      const dep = deps[i];
      const sub = dep.listen(() => {
        this.cache = NO_CACHE;
        this.emit();
      });
      subs.push(sub);
    }
  }
 
  private _comp(): N {
    const cached = this.cache;
    if (cached !== NO_CACHE) return cached;
    return (this.cache = this.compute(this.deps.map((dep) => dep.getSnapshot()) as V));
  }
 
  /** ----------------------------------------------------- {@link SyncValue} */
 
  public get value(): N {
    return this._comp();
  }
 
  /** ----------------------------------------------------- {@link SyncStore} */
 
  public readonly subscribe: SyncStoreSubscribe = (cb) => this.listen(cb);
  public readonly getSnapshot: () => N = () => this._comp();
 
  /** ---------------------------------------------------- {@link Disposable} */
 
  public dispose() {
    for (const sub of this.subs) sub();
  }
}
 
export const val = <V>(initial: V): Value<V> => new Value(initial);
export const comp = <V extends unknown[], N>(deps: WrapListInSyncDep<V>, compute: (args: V) => N): Computed<N, V> =>
  new Computed(deps, compute);