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 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x | import { Bus, BusHandler } from '@sgrud/bus';
import { Symbol } from '@sgrud/core';
import { Observable, ReplaySubject, connectable, dematerialize, from, switchMap } from 'rxjs';
import { Store } from '../store/store';
import { StateHandler } from './handler';
 
/**
 * The **Stateful** decorator, when applied to classes extending the abstract
 * {@link Store} base class, converts those extending classes into type-guarding
 * {@link Store} facades implementing only the {@link Store.dispatch} and the
 * well-known `Symbol.observable` methods. This resulting facade provides
 * convenient access to the current and upcoming {@link Store.State}s of the
 * decorated {@link Store} and its {@link Store.dispatch} method. The decorated
 * class is {@link StateHandler.deploy}ed under the supplied `handle` using the
 * supplied `state` as an initial {@link Store.State}. If the {@link Store} is
 * to be {@link StateHandler.deploy}ed `transient`ly, the supplied `state` is
 * guaranteed to be used as initial {@link Store.State}. Otherwise, a previously
 * persisted {@link Store.State} takes precedence over the supplied `state`.
 *
 * @param handle - The {@link Bus.Handle} representing the {@link Store}.
 * @param state - An initial {@link Store.State} for the {@link Store}.
 * @param transient - Whether the {@link Store} is considered `transient`.
 * @typeParam I - The extending {@link Store} {@link InstanceType}.
 * @typeParam T - A constructor type extending the {@link Store.Type}.
 * @returns A class constructor decorator.
 *
 * @example
 * A simple `ExampleStore` facade:
 * ```ts
 * import { Stateful, Store } from '@sgrud/state';
 *
 * @Stateful('io.github.sgrud.store.example', {
 *   property: 'default',
 *   timestamp: Date.now()
 * })
 * export class ExampleStore extends Store<ExampleStore> {
 *
 *   public readonly property!: string;
 *
 *   public readonly timestamp!: number;
 *
 *   public async action(property: string): Promise<Store.State<this>> {
 *     return { ...this, property, timestamp: Date.now() };
 *   }
 *
 * }
 * ```
 *
 * @example
 * Subscribe to the `ExampleStore` facade:
 * ```ts
 * import { ExampleStore } from './example-store';
 *
 * const store = new ExampleStore();
 * from(store).subscribe(console.log);
 * // { property: 'default', timestamp: [...] }
 * ```
 *
 * @example
 * Dispatch an {@link Store.Action} through the `ExampleStore` facade:
 * ```ts
 * import { ExampleStore } from './example-store';
 *
 * const store = new ExampleStore();
 * store.dispatch('action', ['value']).subscribe(console.log);
 * // { property: 'value', timestamp: [...] }
 * ```
 * @see {@link StateHandler}
 * @see {@link Implant}
 */
export function Stateful<
  T extends Store.Type<I>,
  I extends Store = InstanceType<T>
>(handle: Bus.Handle, state: Store.State<I>, transient: boolean = false) {
 
  /**
   * @param constructor - The class `constructor` to be decorated.
   * @returns The decorated class `constructor`.
   */
  return function(constructor: T): T {
    let loader: Observable<void>;
 
    (loader = connectable(from(StateHandler).pipe(switchMap((handler) => {
      return handler.deploy(handle, constructor, state, transient);
    })), {
      connector: () => new ReplaySubject<void>(1),
      resetOnDisconnect: false
    })).connect();
 
    return class {
 
      public [Symbol.observable]() {
        return loader.pipe(switchMap(() => from(BusHandler).pipe(
          switchMap((handler) => handler.observe(handle).pipe(dematerialize()))
        )));
      }
 
      public dispatch(...action: Store.Action<I>) {
        return loader.pipe(switchMap(() => from(StateHandler).pipe(
          switchMap((handler) => handler.dispatch<I>(handle, ...action))
        )));
      }
 
    } as unknown as T;
  };
 
}
  |