All files / bus/src/handler publish.ts

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

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 114 115 116 117 118 119 120 121 122 123 124 1251x 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 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 1x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 2x 2x 2x 2x 2x  
import { Subject, from, switchMap } from 'rxjs';
import { Bus } from '../bus/bus';
import { BusHandler } from './handler';
 
/**
 * Prototype property decorator factory. This decorator **Publish**es a newly
 * instantiated {@link Subject} under the supplied `handle` and assigns it to
 * the decorated property. Depending on the value of the `suffix` parameter,
 * this newly instantiated {@link Subject} is either assigned directly to the
 * prototype and **Publish**ed 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 **Publish** decorator will then be used to suffix
 * the supplied `handle` upon **Publish**ment of the newly instantiated
 * {@link Subject}, which is assigned to the decorated instance property.
 *
 * Through these two different modes of operation, the {@link Subject} that will
 * be **Publish**ed can be assigned statically to the prototype of the class
 * containing the decorated property, or this assignment can be deferred until
 * an instance of the class containing the decorated property is constructed and
 * a truthy value is assigned to its `suffix` property.
 *
 * This decorator is more or less the opposite of the {@link Observe} decorator,
 * while both rely on the {@link BusHandler} to fulfill contracts. Furthermore,
 * precautions should be taken to ensure the completion of the **Publish**ed
 * {@link Subject} as memory leaks may occur due to dangling subscriptions.
 *
 * @param handle - The {@link Bus.Handle} to **Publish**.
 * @param suffix - An optional `suffix` property for the `handle`.
 * @returns A prototype property decorator.
 *
 * @example
 * **Publish** the `'io.github.sgrud.example'` stream:
 * ```ts
 * import { Publish } from '@sgrud/bus';
 * import { type Subject } from 'rxjs';
 *
 * export class Publisher {
 *
 *   ⁠@Publish('io.github.sgrud.example')
 *   public readonly stream!: Subject<unknown>;
 *
 * }
 *
 * Publisher.prototype.stream.next('value');
 * Publisher.prototype.stream.complete();
 * ```
 *
 * @example
 * **Publish** the `'io.github.sgrud.example'` stream:
 * ```ts
 * import { Publish } from '@sgrud/bus';
 * import { type Subject } from 'rxjs';
 *
 * export class Publisher {
 *
 *   ⁠@Publish('io.github.sgrud', 'suffix')
 *   public readonly stream: Subject<unknown>;
 *
 *   public constructor(
 *     private readonly suffix: string
 *   ) {}
 *
 * }
 *
 * const publisher = new Publisher('example');
 * publisher.stream.next('value');
 * publisher.stream.complete();
 * ```
 *
 * @see {@link BusHandler}
 * @see {@link Observe}
 * @see {@link Stream}
 */
export function Publish(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 = new Subject<unknown>();
 
      from(BusHandler).pipe(switchMap((handler) => {
        return handler.publish(handle, stream);
      })).subscribe();
 
      Object.defineProperty(prototype, propertyKey, {
        enumerable: true,
        get: (): Subject<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 = new Subject<unknown>();
 
            from(BusHandler).pipe(switchMap((handler) => {
              return handler.publish(scoped, stream);
            })).subscribe();
 
            Object.defineProperties(this, {
              [suffix]: {
                enumerable: true,
                value
              },
              [propertyKey]: {
                enumerable: true,
                get: (): Subject<unknown> => stream,
                set: Function.prototype as (...args: any[]) => any
              }
            });
          }
        }
      });
    }
  };
 
}