All files / bus/src/handler observe.ts

100% Statements 110/110
100% Branches 7/7
100% Functions 4/4
100% Lines 110/110

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 1111x 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 2x 2x 2x 2x 2x 2x 2x 2x 1x 1x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 3x 3x 3x 1x 1x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 2x 2x 2x 2x 2x  
import { dematerialize, from, Observable, switchMap } from 'rxjs';
import { Bus } from '../bus/bus';
import { BusHandler } from './handler';
 
/**
 * Prototype property decorator factory. Applying this decorator replaces the
 * decorated property with a getter returning an {@link Observable} stream which
 * **Observe**s all values originating from the supplied `handle`. Depending on
 * the value of the `suffix` parameter, this {@link Observable} stream is either
 * assigned directly to the prototype using the supplied `handle`, or, if a
 * truthy value is supplied for the `suffix` parameter, this value is assumed to
 * reference another property of the class containing this decorated property.
 * The first truthy value assigned to this `suffix` property on an instance of
 * the class containing this **Stream** decorator will then be used to suffix
 * the supplied `handle` which is to be **Observe**d and assign the resulting
 * {@link Observable} stream to the decorated instance property.
 *
 * This decorator is more or less the opposite of the {@link Publish} decorator,
 * while both rely on the {@link BusHandler} to fulfill contracts.
 *
 * @param handle - The {@link Bus.Handle} to **Observe**.
 * @param suffix - An optional `suffix` property for the `handle`.
 * @returns A prototype property decorator.
 *
 * @example
 * **Observe** the `'io.github.sgrud.example'` stream:
 * ```ts
 * import { type Bus, Observe } from '@sgrud/bus';
 * import { type Observable } from 'rxjs';
 *
 * export class Observer {
 *
 *   ⁠@Observe('io.github.sgrud.example')
 *   public readonly stream!: Observable<Bus.Value<unknown>>;
 *
 * }
 *
 * Observer.prototype.stream.subscribe(console.log);
 * ```
 *
 * @example
 * **Observe** the `'io.github.sgrud.example'` stream:
 * ```ts
 * import { type Bus, Observe } from '@sgrud/bus';
 * import { type Observable } from 'rxjs';
 *
 * export class Observer {
 *
 *   ⁠@Observe('io.github.sgrud', 'suffix')
 *   public readonly stream!: Observable<Bus.Value<unknown>>;
 *
 *   public constructor(
 *     public readonly suffix: string
 *   ) { }
 *
 * }
 *
 * const observer = new Observer('example');
 * observer.stream.subscribe(console.log);
 * ```
 *
 * @see {@link BusHandler}
 * @see {@link Publish}
 * @see {@link Stream}
 */
export function Observe(handle: Bus.Handle, suffix?: PropertyKey) {
 
  /**
   * @param prototype - The `prototype` to be decorated.
   * @param propertyKey - The `prototype` property to be decorated.
   */
  return function(prototype: object, propertyKey: PropertyKey): void {
    if (!suffix) {
      const stream = from(BusHandler).pipe(switchMap((handler) => {
        return handler.observe(handle).pipe(dematerialize());
      }));
 
      Object.defineProperty(prototype, propertyKey, {
        enumerable: true,
        get: (): Observable<unknown> => stream,
        set: Function.prototype as (...args: any[]) => any
      });
    } else {
      Object.defineProperty(prototype, suffix, {
        enumerable: true,
        set(this: object, value: string): void {
          if (value) {
            const scoped = `${handle}.${value}` as Bus.Handle;
            const stream = from(BusHandler).pipe(switchMap((handler) => {
              return handler.observe(scoped).pipe(dematerialize());
            }));
 
            Object.defineProperties(this, {
              [suffix]: {
                enumerable: true,
                value
              },
              [propertyKey]: {
                enumerable: true,
                get: (): Observable<unknown> => stream,
                set: Function.prototype as (...args: any[]) => any
              }
            });
          }
        }
      });
    }
  };
 
}