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;
};
}
|