All files / src/channels index.ts

100% Statements 30/30
100% Branches 10/10
100% Functions 8/8
100% Lines 30/30

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 982x 2x   2x 2x     2x 2x                                             2x   2x 38x   1x     37x 37x   4x       37x   1x     17x     17x 27x     1x     12x   12x 12x     12x                 23x   12x   4x     8x 8x     7x   1x                
import Debug from 'debug';
import { get, compact, flattenDeep, noop } from 'lodash';
import { Channel } from './channel/base';
import { CombinedChannel } from './channel/combined';
import { channelMixin, publishMixin, keys } from './mixins';
import { Application, Service } from '@feathersjs/feathers';
 
const debug = Debug('@feathersjs/transport-commons/channels');
const { CHANNELS, PUBLISHERS, ALL_EVENTS } = keys;
 
declare module '@feathersjs/feathers' {
  interface ServiceAddons<T> {
    publish(callback: (data: T, hook: HookContext<T>) => Channel): this;
 
    publish(event: string, callback: (data: T, hook: HookContext<T>) => Channel): this;
  }
 
  interface Application<ServiceTypes> {
    channels: string[];
 
    channel(name: string[]): Channel;
    channel(...names: string[]): Channel;
 
    // tslint:disable-next-line void-return
    publish<T>(callback: (data: T, hook: HookContext<T>) => Channel | Channel[] | void): Application<ServiceTypes>;
 
    // tslint:disable-next-line void-return
    publish<T>(event: string, callback: (data: T, hook: HookContext<T>) => Channel | Channel[] | void): Application<ServiceTypes>;
  }
}
 
export { keys };
 
export function channels () {
  return (app: Application) => {
    if (typeof app.channel === 'function' && typeof app.publish === 'function') {
      return;
    }
 
    Object.assign(app, channelMixin(), publishMixin());
    Object.defineProperty(app, 'channels', {
      get () {
        return Object.keys(this[CHANNELS]);
      }
    });
 
    app.mixins.push((service: Service<any>, path: string) => {
      if (typeof service.publish === 'function' || !service._serviceEvents) {
        return;
      }
 
      Object.assign(service, publishMixin());
 
      // @ts-ignore
      service._serviceEvents.forEach((event: string) => {
        service.on(event, function (data, hook) {
          if (!hook) {
            // Fake hook for custom events
            hook = { path, service, app, result: data };
          }
 
          debug('Publishing event', event, hook.path);
 
          const servicePublishers = (service as any)[PUBLISHERS];
          const appPublishers = (app as any)[PUBLISHERS];
          // This will return the first publisher list that is not empty
          // In the following precedence
          const callback = [
            // 1. Service publisher for a specific event
            get(servicePublishers, event),
            // 2. Service publisher for all events
            get(servicePublishers, ALL_EVENTS),
            // 3. App publishers for a specific event
            get(appPublishers, event),
            // 4. App publishers for all events
            get(appPublishers, ALL_EVENTS)
          ].find(current => typeof current === 'function') || noop;
 
          Promise.resolve(callback(data, hook)).then(result => {
            if (!result) {
              return;
            }
 
            const results = Array.isArray(result) ? compact(flattenDeep(result)) : [ result ];
            const channel = new CombinedChannel(results);
 
            if (channel && channel.length > 0) {
              app.emit('publish', event, channel, hook, data);
            } else {
              debug('No connections to publish to');
            }
          });
        });
      });
    });
  };
}