All files / tao-utils/src Relay.js

100% Statements 27/27
100% Branches 22/22
100% Functions 10/10
100% Lines 26/26

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 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162        1x   1x   8x       3x                                                                                                         13x 1x       12x 12x       3x       9x 1x   8x 3x 3x   8x 8x 8x 8x     5x                       16x                               8x 4x                           13x 1x                 3x                         4x      
import { AppCtx } from '@tao.js/core';
 
/** @typedef {import('@tao.js/core').Kernel} Kernel */
 
const DEFAULT_SOURCE = 'FROM';
 
let sourceInstance = 0;
function sourceName(name) {
  return name || `${DEFAULT_SOURCE}${++sourceInstance}`;
}
 
function sourceControl(source) {
  return { source };
}
 
/**
 * A trigram in short (`t`/`a`/`o`) or long (`term`/`action`/`orient`) keys;
 * long-form keys win.
 *
 * @typedef {Object} Trigram
 * @property {string} [t] - term (short key)
 * @property {string} [term] - term (long key)
 * @property {string} [a] - action (short key)
 * @property {string} [action] - action (long key)
 * @property {string} [o] - orient (short key)
 * @property {string} [orient] - orient (long key)
 */
 
/**
 * Like Source, but `fromSrc` is required. Implemented as a Network
 * decoration; the origin marker rides the envelope's hop scope.
 * Decoration: `onDispatch` (phase-blind emission), self-filtered on
 * `envelope.hop.source`. Unexported / experimental.
 *
 * @export
 * @class Relay
 */
export default class Relay {
  /**
   * Creates an instance of Relay.
   *
   * Note the Relay resolves its network from `kernel._network` only (it
   * does not apply the utils surface-resolution convention, so a bare
   * `Network` is not accepted); the resolved network must support `enter`
   * and `decorate`.
   *
   * @param {Kernel} kernel - Kernel-shaped wrapper (exposing `_network`) to attach the Relay to
   * @param {function(Object, *): void} toSrc - outbound emitter called with
   *        `(tao, data)` — the unwrapped `{ t, a, o }` trigram and the
   *        datagram(s) — for every AppCon except those arriving from this Relay
   * @param {(string|function(function(Trigram, *): void): void)} [name] -
   *        the Relay's name, used as its hop-scope origin marker
   *        (auto-generated `FROM<n>` when omitted); passing a `function`
   *        here is the `fromSrc` overload
   * @param {function(function(Trigram, *): void): void} [fromSrc] - binder
   *        for the inbound side (required at runtime — supplied here or in
   *        the `name` position): called once with a setter `(tao, data)`
   *        that enters received signals stamped with this Relay's origin
   *        marker
   * @throws {Error} when `kernel` (or its `_network`) is missing, when the
   *         network lacks envelope support (`enter` + `decorate`) - upgrade
   *         `@tao.js/core`, or when `toSrc` is missing
   * @memberof Relay
   */
  constructor(kernel, toSrc, name, fromSrc) {
    if (!kernel || !kernel._network) {
      throw new Error(
        'must provide `kernel` to attach the Source to a network',
      );
    }
    this._network = kernel._network;
    if (
      typeof this._network.enter !== 'function' ||
      typeof this._network.decorate !== 'function'
    ) {
      throw new Error(
        'Relay requires a @tao.js/core version with envelope support - upgrade @tao.js/core',
      );
    }
    if (!toSrc) {
      throw new Error('must provide `toSrc` way to send ACs to the source');
    }
    if (typeof name === 'function') {
      fromSrc = name;
      name = null;
    }
    this._toSrc = toSrc;
    this._name = sourceName(name);
    fromSrc((tao, data) => this._enter(tao, data));
    this._undecorate = this._network.decorate({
      // Stryker disable next-line StringLiteral: decoration name is a diagnostic label with no observable behavior
      name: `relay:${this._name}`,
      onDispatch: (ac, envelope) => this.handleAppCon(ac, envelope),
    });
  }
 
  /**
   * The Relay's name — the hop-scope origin marker it stamps on entries
   * and filters emission on.
   *
   * @type {string}
   * @memberof Relay
   */
  get name() {
    return this._name;
  }
 
  /**
   * `onDispatch` decoration callback: emits every dispatched AppCon to the
   * source except those whose arriving hop carries this Relay's origin
   * marker. Suppression reads the hop scope only — hops after the entry
   * reset to `{}` — so AppCons chained in response are emitted back out
   * (the bidirectional reflex).
   *
   * @param {AppCtx} ac
   * @param {Object} envelope - the dispatch envelope `{ cascade, hop, chain }`
   * @returns {void}
   * @memberof Relay
   */
  handleAppCon(ac, envelope) {
    if (envelope.hop.source !== this.name) {
      this._toSrc(ac.unwrapCtx(), ac.data);
    }
  }
 
  /**
   * Enters a signal received from the source: builds an AppCtx (long-form
   * trigram keys win) and enters it with this Relay's origin marker on the
   * entry hop, suppressing the echo back to the source.
   *
   * @param {Trigram} trigram
   * @param {*} data - datagram(s) for the signal
   * @returns {void}
   * @memberof Relay
   */
  setCtx = ({ t, term, a, action, o, orient }, data) => {
    this._enter({ t, term, a, action, o, orient }, data);
  };
 
  /**
   * @param {Trigram} trigram
   * @param {*} data
   * @private
   */
  _enter({ t, term, a, action, o, orient }, data) {
    this._network.enter(new AppCtx(term || t, action || a, orient || o, data), {
      hop: sourceControl(this.name),
    });
  }
 
  /**
   * Detach this Relay's decoration from the network: stops emitting to the
   * source. Inbound entries via `setCtx`/`fromSrc` still dispatch.
   *
   * @returns {void}
   * @memberof Relay
   */
  dispose() {
    this._undecorate();
  }
}