All files / tao-utils/src seive.js

100% Statements 15/15
100% Branches 14/14
100% Functions 5/5
100% Lines 15/15

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            3x     10x                                                             21x           10x     11x 11x   11x       16x 2x   14x 10x             11x 11x 11x 11x      
import { Network } from '@tao.js/core';
import trigramFilter from './trigram-filter';
 
/** @typedef {import('@tao.js/core').Kernel} Kernel */
/** @typedef {import('./Channel').default} Channel */
 
const NOOP = () => {};
 
function addControl(name, control) {
  return { ...control, seive: name };
}
 
// filter function signature: (ac:AppCtx, control:Object)
// filters is: [filterFunc, [exact,]] ...trigrams
 
/**
 * Forwards matching AppCons flowing on the `source`'s network into the
 * `destination` Channel's private network. Implemented as an `onDispatch`
 * decoration on the source network; chains from destination-channel
 * handlers continue the source cascade through the core hop engine.
 *
 * Clone-and-redirect: the destination entry carries a copy of the observed
 * cascade with a `{ seive: name }` tag added (the source cascade object is
 * never mutated), and passes the observed hop's `forward` continuation so
 * chained AppCons re-join the source cascade.
 *
 * @param {(string|number)} name - seive tag stamped on redirected cascades
 *        (`cascade.seive`) and used in the decoration's diagnostic name
 * @param {Kernel} source - Kernel-shaped wrapper whose `_network` is a
 *        `Network` to observe
 * @param {Channel} destination - Channel whose private network (`_channel`)
 *        receives the matching AppCons
 * @param {...*} filters - optional leading filter function
 *        `(ac: AppCtx, cascade: Object) => boolean`, then trigram filters as
 *        `trigramFilter` accepts (optional `exact` boolean, then short-key
 *        trigrams or a single array of trigrams)
 * @returns {function(): void} dispose - detaches the decoration (a no-op
 *          when `source`/`destination` are not the required shapes)
 */
export default function seive(name, source, destination, ...filters) {
  if (
    !source ||
    !(source._network instanceof Network) ||
    !destination ||
    !(destination._channel instanceof Network)
  ) {
    return NOOP;
  }
  let filterFunction =
    typeof filters[0] === 'function' ? filters.shift() : undefined;
  let handleFilter = trigramFilter(...filters);
 
  const undecorate = source._network.decorate({
    // Stryker disable next-line StringLiteral: decoration name is a diagnostic label with no observable behavior
    name: `seive:${name}`,
    onDispatch: (ac, envelope, handler, forward) => {
      if (filterFunction && !filterFunction(ac, envelope.cascade)) {
        return;
      }
      if (handleFilter(ac)) {
        destination._channel.enter(ac, {
          cascade: addControl(name, envelope.cascade),
          forward,
        });
      }
    },
  });
  return () => {
    undecorate();
    filterFunction = null;
    handleFilter = null;
  };
}