All files / src Registry.ts

83.93% Statements 47/56
77.78% Branches 14/18
92.86% Functions 13/14
83.93% Lines 47/56

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 101 102 103 104 105 106 107 108 109 110 111 112 113                    27x 27x 27x 27x         27x                       28x 28x 27x 27x 27x 27x 27x   28x       47x 24x 24x 24x   47x       40x 40x 40x 40x     40x     40x 71x   40x       40x 22x   40x         3x 3x 4x 4x     3x       27x 27x 27x 10x         27x 27x       40x 71x 71x 28x   40x 31x   9x            
import * as ReactDom from 'react-dom';
import { empty, merge, Observable, queueScheduler, Subject } from 'rxjs';
import { mergeMap, observeOn, subscribeOn } from 'rxjs/operators';
import { Notify } from './Notify';
import { StateLogger } from './StateLogger';
import { Store } from './Store';
import { Action, ActionLike, Deps } from './types';
import { getDescription } from './utils';
 
export class Registry {
  private nameCount: Map<string, number> = new Map();
  private displayNames: Map<symbol, string> = new Map();
  private storesMap: Map<symbol, Store> = new Map();
  private stores: Store[] = [];
  private input$!: Subject<Action>;
  private output$!: Observable<Action>;
 
  constructor() {
    this.initStreams();
  }
 
  reset() {
    this.nameCount.clear();
    this.displayNames.clear();
    this.storesMap.clear();
    this.stores = [];
    this.initStreams();
  }
 
  getDisplayName(name: symbol) {
    const description = getDescription(name);
    if (!this.displayNames.has(name)) {
      let count = this.nameCount.get(description) || 0;
      count++;
      this.nameCount.set(description, count);
      const displayName = count > 1 ? `${description}#${count}` : description;
      this.displayNames.set(name, displayName);
    }
    return this.displayNames.get(name)!;
  }
 
  getStore<TState = any>(name: symbol): Store<TState> {
    if (!this.storesMap.has(name)) {
      const store = new Store<TState>(name, this.getDisplayName(name));
      this.storesMap.set(name, store);
      this.stores.push(store);
    }
    return this.storesMap.get(name)!;
  }
 
  dispatch(action: ActionLike) {
    ReactDom.unstable_batchedUpdates(() => {
      const notify = new Notify();
      let stateLogger: StateLogger | null = null;
      Iif (process.env.NODE_ENV === 'development') {
        stateLogger = new StateLogger(this.stores);
      }
      Iif (stateLogger) {
        stateLogger.setState('prevState', this.getState());
      }
      for (const store of this.stores) {
        store.dispatch(action, notify);
      }
      Iif (stateLogger) {
        stateLogger.setState('nextState', this.getState());
        stateLogger.log(action);
      }
      for (const fn of notify.handlers) {
        fn();
      }
      this.input$.next(action as Action);
    });
  }
 
  getState() {
    const state: Record<string, any> = {};
    for (const store of this.stores) {
      Eif (store.state !== undefined) {
        state[store.displayName] = store.state;
      }
    }
    return state;
  }
 
  private initStreams() {
    this.input$ = new Subject();
    this.output$ = this.createOutputStream(this.input$);
    this.output$.subscribe(action => {
      this.dispatch(action);
    });
  }
 
  private createOutputStream(action$: Subject<Action>): Observable<Action> {
    const deps = { action$: action$ as Deps['action$'] };
    return action$.pipe(
      subscribeOn(queueScheduler),
      observeOn(queueScheduler),
      mergeMap(sourceAction => {
        const handlers = this.stores
          .map(store => store.getOutputStream(sourceAction, deps))
          .filter(handler => handler !== null)
          .reduce((ret, arr) => [...ret, ...arr], [])!;
 
        if (handlers.length === 0) {
          return empty();
        } else {
          return merge(...handlers);
        }
      })
    );
  }
}