hyperflow
Version:
A javascript state flow and mutation management toolkit & library for developing universal app.
1,082 lines (1,036 loc) • 95.3 kB
JavaScript
/**
* Copyright 2015-present Tuan Le.
*
* Licensed under the MIT License.
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://opensource.org/licenses/mit-license.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*------------------------------------------------------------------------
*
* @module EventStreamComposite
* @description - A reactive event stream composite, based on rxjs.
*
* @author Tuan Le (tuan.t.lei@gmail.com)
*
* @flow
*/
'use strict'; // eslint-disable-line
import CommonElement from '../../elements/common-element';
import CompositeElement from '../../elements/composite-element';
/* load Rx dependency */
import {
Subject as RxSubject,
Subscriber as RxSubscriber,
Observable as RxObservable,
NEVER as RxObservableNever,
merge as rxMerge,
of as rxOf,
interval as rxInterval
} from 'rxjs';
import {
bufferTime as rxBufferTimeOp,
delay as rxDelayOp,
debounceTime as rxDebounceTimeOp,
filter as rxFilterOp,
flatMap as rxFlatMapOp,
map as rxMapOp,
merge as rxMergeOp,
reduce as rxReduceOp,
scan as rxScanOp,
startWith as rxStartWithOp,
takeLast as rxTakeLastOp,
tap as rxTapOp,
timeout as rxTimeoutOp,
timeInterval as rxTimeIntervalOp,
throttle as rxThrottleOp,
share as rxShareOp
} from 'rxjs/operators';
// TODO: Only import part of Rx that is needed.
/* factory Ids */
import {
FIXTURE_FACTORY_CODE,
DOMAIN_FACTORY_CODE,
INTERFACE_FACTORY_CODE,
SERVICE_FACTORY_CODE,
STORE_FACTORY_CODE
} from '../factory-code';
const INCOMING_DIRECTION = 0;
const OUTGOING_DIRECTION = 1;
const DIVERTED_INCOMING_DIRECTION = 2;
const DIVERTED_OUTGOING_DIRECTION = 3;
const INCOMING_EVENT = 0;
const OUTGOING_EVENT = 1;
const RELAY_EVENT = 2;
const REPEATING_EVENT = 3;
const REPEATED_EVENT = 4;
const LOOPBACK_EVENT = 5;
const Hf = CommonElement();
export default CompositeElement({
template: {
/**
* @description - Initialized composite.
*
* @method $initEventStreamComposite
* @return void
*/
$initEventStreamComposite () {
const factory = this;
if (Hf.DEVELOPMENT) {
if (!Hf.isSchema({
fId: `string`,
name: `string`
}).of(factory) || !(factory.fId.substr(0, FIXTURE_FACTORY_CODE.length) === FIXTURE_FACTORY_CODE ||
factory.fId.substr(0, DOMAIN_FACTORY_CODE.length) === DOMAIN_FACTORY_CODE ||
factory.fId.substr(0, INTERFACE_FACTORY_CODE.length) === INTERFACE_FACTORY_CODE ||
factory.fId.substr(0, SERVICE_FACTORY_CODE.length) === SERVICE_FACTORY_CODE ||
factory.fId.substr(0, STORE_FACTORY_CODE.length) === STORE_FACTORY_CODE)) {
Hf.log(`error`, `EventStreamComposite.$init - Factory is invalid. Cannot apply composite.`);
}
}
}
},
enclosure: {
EventStreamComposite () {
/* ----- Private Variables ------------- */
let _outgoingStreamActivated = false;
let _incomingStreamActivated = false;
let _arbiter = {};
let _targetRegistrationCache = {};
/* a queue for payloads emitted before activation */
let _unemitPayloads = [];
let _observer;
let _outgoingSubscription;
let _incomingSubscription;
/* creating factory event stream emitter */
let _streamEmitter = new RxSubject();
/* converting event stream emitter as a subject to an observable */
let _outgoingStream = _streamEmitter.asObservable();
/* creating factory incoming and outgoing event stream */
let _incomingStream = RxObservableNever;
let _divertedOutgoingStream = RxObservableNever;
let _divertedIncomingStream = RxObservableNever;
/* ----- Private Functions ------------- */
/**
* @description - On subscription to next incoming payload...
*
* @method _next
* @param {array} payloads - Incoming payloads
* @return void
* @private
*/
function _next (...payloads) {
payloads.forEach((payload) => {
if (Hf.DEVELOPMENT) {
if (!Hf.isSchema({
eventId: `string`
}).of(payload)) {
Hf.log(`error`, `EventStreamComposite._next - Payload event Id is invalid.`);
}
}
const {
eventId,
value,
cancelled
} = Hf.fallback({
cancelled: false
}).of(payload);
if (_arbiter.hasOwnProperty(eventId)) {
const {
eventDirectionalState,
completed,
waitTime,
handler,
canceller,
relayer
} = Hf.fallback({
completed: false,
waitTime: 0
}).of(_arbiter[eventId]);
/**
* @description - Called on cancellation to assign/regsiter a canceller callback.
*
* @method onCancel
* @param {function} _canceller - A canceller callback
* @return void
*/
const onCancel = (_canceller) => {
if (Hf.DEVELOPMENT) {
if (!Hf.isFunction(_canceller)) {
Hf.log(`error`, `EventStreamComposite._next.onCancel - Input canceller callback is invalid.`);
}
}
_arbiter[eventId].canceller = _canceller;
};
/**
* @description - Called on handling async handled value.
*
* @method onAsyncHandle
* @return void
*/
const onAsyncHandle = async () => {
const handledValue = Hf.isFunction(handler) ? await handler(value, onCancel) : undefined;
if (cancelled && Hf.isFunction(canceller)) {
canceller();
}
if (Hf.isFunction(relayer)) {
relayer(handledValue, cancelled);
}
if (completed) {
delete _arbiter[eventId];
// _arbiter[eventId] = undefined;
}
};
if (eventDirectionalState !== REPEATED_EVENT) {
if (eventDirectionalState === REPEATING_EVENT) {
_arbiter[eventId].eventDirectionalState = REPEATED_EVENT;
}
if (waitTime > 0) {
setTimeout(() => onAsyncHandle(), waitTime);
} else {
onAsyncHandle();
}
} else {
_arbiter[eventId].eventDirectionalState = REPEATING_EVENT;
}
}
});
}
/**
* @description - On subscription to error...
*
* @method _error
* @param {string} errorMessage
* @return void
* @private
*/
function _error (errorMessage) {
Hf.log(`error`, `EventStreamComposite._error - Subscription error. ${errorMessage}`);
}
/**
* @description - On subscription to completion...
*
* @method _complete
* @return void
* @private
*/
function _complete () {
Hf.log(`info0`, `Subscription completed.`);
}
/**
* @description - Helper function to create stream operator object.
*
* @method _createStreamOperatorFor
* @param {number} direction
* @return {object}
* @private
*/
function _createStreamOperatorFor (direction) {
const factory = this;
const operator = {
/**
* @description - At observable stream, operates delay.
*
* @method delay
* @param {number} ms - Time in millisecond
* @return {object}
*/
delay (ms) {
if (Hf.DEVELOPMENT) {
if (!Hf.isInteger(ms)) {
Hf.log(`error`, `EventStreamComposite.delay - Input delay time is invalid.`);
}
}
if (ms < 1) {
ms = 1;
Hf.log(`warn1`, `EventStreamComposite.delay - Input delay time should be greater than 0. Reset to 1ms.`);
}
switch (direction) { // eslint-disable-line
case INCOMING_DIRECTION:
_incomingStream = _incomingStream.pipe(
rxDelayOp(ms),
rxShareOp()
);
break;
case OUTGOING_DIRECTION:
_outgoingStream = _outgoingStream.pipe(
rxDelayOp(ms),
rxShareOp()
);
break;
case DIVERTED_INCOMING_DIRECTION:
_divertedIncomingStream = _divertedIncomingStream.pipe(
rxDelayOp(ms),
rxShareOp()
);
break;
case DIVERTED_OUTGOING_DIRECTION:
_divertedOutgoingStream = _divertedOutgoingStream.pipe(
rxDelayOp(ms),
rxShareOp()
);
break;
default:
Hf.log(`error`, `EventStreamComposite.delay - Invalid direction:${direction}.`);
}
return operator;
},
/**
* @description - At observable stream, operates debounce.
*
* @method debounce
* @param {number} ms - Time in millisecond
* @return {object}
*/
debounce (ms) {
if (Hf.DEVELOPMENT) {
if (!Hf.isInteger(ms)) {
Hf.log(`error`, `EventStreamComposite.debounce - Input debounce time is invalid.`);
}
}
if (ms < 1) {
ms = 1;
Hf.log(`warn1`, `EventStreamComposite.debounce - Input debounce time should be greater than 0. Reset to 1ms.`);
}
switch (direction) { // eslint-disable-line
case INCOMING_DIRECTION:
_incomingStream = _incomingStream.pipe(
rxDebounceTimeOp(ms),
rxShareOp()
);
break;
case OUTGOING_DIRECTION:
_outgoingStream = _outgoingStream.pipe(
rxDebounceTimeOp(ms),
rxShareOp()
);
break;
case DIVERTED_INCOMING_DIRECTION:
_divertedIncomingStream = _divertedIncomingStream.pipe(
rxDebounceTimeOp(ms),
rxShareOp()
);
break;
case DIVERTED_OUTGOING_DIRECTION:
_divertedOutgoingStream = _divertedOutgoingStream.pipe(
rxDebounceTimeOp(ms),
rxShareOp()
);
break;
default:
Hf.log(`error`, `EventStreamComposite.debounce - Invalid direction:${direction}.`);
}
return operator;
},
/**
* @description - At observable stream, operates filter.
*
* @method filter
* @param {function} predicate
* @return {object}
*/
filter (predicate) {
if (Hf.DEVELOPMENT) {
if (!Hf.isFunction(predicate)) {
Hf.log(`error`, `EventStreamComposite.filter - Input filter predicate function is invalid.`);
}
}
switch (direction) { // eslint-disable-line
case INCOMING_DIRECTION:
_incomingStream = _incomingStream.pipe(
rxFilterOp(predicate),
rxShareOp()
);
break;
case OUTGOING_DIRECTION:
_outgoingStream = _outgoingStream.pipe(
rxFilterOp(predicate),
rxShareOp()
);
break;
case DIVERTED_INCOMING_DIRECTION:
_divertedIncomingStream = _divertedIncomingStream.pipe(
rxFilterOp(predicate),
rxShareOp()
);
break;
case DIVERTED_OUTGOING_DIRECTION:
_divertedOutgoingStream = _divertedOutgoingStream.pipe(
rxFilterOp(predicate),
rxShareOp()
);
break;
default:
Hf.log(`error`, `EventStreamComposite.filter - Invalid direction:${direction}.`);
}
return operator;
},
/**
* @description - At observable stream, operates map.
*
* @method map
* @param {function} selector
* @return {object}
*/
map (selector) {
if (Hf.DEVELOPMENT) {
if (!Hf.isFunction(selector)) {
Hf.log(`error`, `EventStreamComposite.map - Input map selector function is invalid.`);
}
}
switch (direction) { // eslint-disable-line
case INCOMING_DIRECTION:
_incomingStream = _incomingStream.pipe(
rxMapOp(selector),
rxShareOp()
);
break;
case OUTGOING_DIRECTION:
_outgoingStream = _outgoingStream.pipe(
rxMapOp(selector),
rxShareOp()
);
break;
case DIVERTED_INCOMING_DIRECTION:
_divertedIncomingStream = _divertedIncomingStream.pipe(
rxMapOp(selector),
rxShareOp()
);
break;
case DIVERTED_OUTGOING_DIRECTION:
_divertedOutgoingStream = _divertedOutgoingStream.pipe(
rxMapOp(selector),
rxShareOp()
);
break;
default:
Hf.log(`error`, `EventStreamComposite.map - Invalid direction:${direction}.`);
}
return operator;
},
/**
* @description - At observable stream, operates flatten and map.
*
* @method flatMap
* @param {function} selector
* @param {function} resultSelector
* @return {object}
*/
flatMap (selector, resultSelector) {
if (Hf.DEVELOPMENT) {
if (!Hf.isFunction(selector)) {
Hf.log(`error`, `EventStreamComposite.flatMap - Input flat map selector function is invalid.`);
} else if (!Hf.isFunction(resultSelector)) {
Hf.log(`error`, `EventStreamComposite.flatMap - Input flat map result selector function is invalid.`);
}
}
switch (direction) { // eslint-disable-line
case INCOMING_DIRECTION:
_incomingStream = _incomingStream.pipe(
rxFlatMapOp(selector, resultSelector),
rxShareOp()
);
break;
case OUTGOING_DIRECTION:
_outgoingStream = _outgoingStream.pipe(
rxFlatMapOp(selector, resultSelector),
rxShareOp()
);
break;
case DIVERTED_INCOMING_DIRECTION:
_divertedIncomingStream = _divertedIncomingStream.pipe(
rxFlatMapOp(selector, resultSelector),
rxShareOp()
);
break;
case DIVERTED_OUTGOING_DIRECTION:
_divertedOutgoingStream = _divertedOutgoingStream.pipe(
rxFlatMapOp(selector, resultSelector),
rxShareOp()
);
break;
default:
Hf.log(`error`, `EventStreamComposite.flatMap - Invalid direction:${direction}.`);
}
return operator;
},
/**
* @description - At observable stream, operates reduce.
*
* @method reduce
* @param {function} accumulator
* @param {object|undefined} defaultPayload
* @return {object}
*/
reduce (accumulator, defaultPayload) {
if (Hf.DEVELOPMENT) {
if (!Hf.isFunction(accumulator)) {
Hf.log(`error`, `EventStreamComposite.reduce - Input reduce accumulator function is invalid.`);
}
}
if (Hf.isObject(defaultPayload)) {
if (Hf.DEVELOPMENT) {
if (Hf.isSchema({
eventId: `string`
}).of(defaultPayload)) {
Hf.log(`error`, `EventStreamComposite.reduce - Payload event Id is invalid.`);
}
}
switch (direction) { // eslint-disable-line
case INCOMING_DIRECTION:
_incomingStream = _incomingStream.pipe(
rxReduceOp(accumulator, defaultPayload),
rxShareOp()
);
break;
case OUTGOING_DIRECTION:
_outgoingStream = _outgoingStream.pipe(
rxReduceOp(accumulator, defaultPayload),
rxShareOp()
);
break;
case DIVERTED_INCOMING_DIRECTION:
_divertedIncomingStream = _divertedIncomingStream.pipe(
rxReduceOp(accumulator, defaultPayload),
rxShareOp()
);
break;
case DIVERTED_OUTGOING_DIRECTION:
_divertedOutgoingStream = _divertedOutgoingStream.pipe(
rxReduceOp(accumulator, defaultPayload),
rxShareOp()
);
break;
default:
Hf.log(`error`, `EventStreamComposite.reduce - Invalid direction:${direction}.`);
}
} else {
switch (direction) { // eslint-disable-line
case INCOMING_DIRECTION:
_incomingStream = _incomingStream.pipe(
rxReduceOp(accumulator),
rxShareOp()
);
break;
case OUTGOING_DIRECTION:
_outgoingStream = _outgoingStream.pipe(
rxReduceOp(accumulator),
rxShareOp()
);
break;
case DIVERTED_INCOMING_DIRECTION:
_divertedIncomingStream = _divertedIncomingStream.pipe(
rxReduceOp(accumulator),
rxShareOp()
);
break;
case DIVERTED_OUTGOING_DIRECTION:
_divertedOutgoingStream = _divertedOutgoingStream.pipe(
rxReduceOp(accumulator),
rxShareOp()
);
break;
default:
Hf.log(`error`, `EventStreamComposite.reduce - Invalid direction:${direction}.`);
}
}
return operator;
},
/**
* @description - At observable stream, operates a start with on the entire stream.
*
* @method startWith
* @param {array} payloads
* @return {object}
*/
startWith (...payloads) {
// FIXME: startWith method not working? Needs testings.
if (Hf.DEVELOPMENT) {
payloads.forEach((payload) => {
if (!Hf.isSchema({
eventId: `string`
}).of(payload)) {
Hf.log(`error`, `EventStreamComposite.startWith - Payload event Id is invalid.`);
}
});
}
switch (direction) { // eslint-disable-line
case INCOMING_DIRECTION:
_incomingStream = _incomingStream.pipe(
rxStartWithOp(...payloads),
rxShareOp()
);
break;
case OUTGOING_DIRECTION:
_outgoingStream = _outgoingStream.pipe(
rxStartWithOp(...payloads),
rxShareOp()
);
break;
case DIVERTED_INCOMING_DIRECTION:
_divertedIncomingStream = _divertedIncomingStream.pipe(
rxStartWithOp(...payloads),
rxShareOp()
);
break;
case DIVERTED_OUTGOING_DIRECTION:
_divertedOutgoingStream = _divertedOutgoingStream.pipe(
rxStartWithOp(...payloads),
rxShareOp()
);
break;
default:
Hf.log(`error`, `EventStreamComposite.startWith - Invalid direction:${direction}.`);
}
return operator;
},
/**
* @description - At observable stream, operates take last.
*
* @method takeLast
* @param {number} count
* @return {object}
*/
takeLast (count) {
if (Hf.DEVELOPMENT) {
if (!Hf.isInteger(count)) {
Hf.log(`error`, `EventStreamComposite.takeLast - Input count number is invalid.`);
}
}
switch (direction) { // eslint-disable-line
case INCOMING_DIRECTION:
_incomingStream = _incomingStream.pipe(
rxTakeLastOp(count),
rxShareOp()
);
break;
case OUTGOING_DIRECTION:
_outgoingStream = _outgoingStream.pipe(
rxTakeLastOp(count),
rxShareOp()
);
break;
case DIVERTED_INCOMING_DIRECTION:
_divertedIncomingStream = _divertedIncomingStream.pipe(
rxTakeLastOp(count),
rxShareOp()
);
break;
case DIVERTED_OUTGOING_DIRECTION:
_divertedOutgoingStream = _divertedOutgoingStream.pipe(
rxTakeLastOp(count),
rxShareOp()
);
break;
default:
Hf.log(`error`, `EventStreamComposite.takeLast - Invalid direction:${direction}.`);
}
return operator;
},
/**
* @description - At observable stream, operates a throttle on the entire stream.
*
* @method throttle
* @param {number} ms - Time in ms to wait before emitting another item after emitting the last item.
* @return {object}
*/
throttle (ms) {
if (Hf.DEVELOPMENT) {
if (!Hf.isInteger(ms)) {
Hf.log(`error`, `EventStreamComposite.throttle - Input throttle time window is invalid.`);
}
}
if (ms < 1) {
ms = 1;
Hf.log(`warn1`, `EventStreamComposite.throttle - Input throttle time should be greater than 0. Reset to 1ms.`);
}
switch (direction) { // eslint-disable-line
case INCOMING_DIRECTION:
_incomingStream = _incomingStream.pipe(
rxThrottleOp(ms),
rxShareOp()
);
break;
case OUTGOING_DIRECTION:
_outgoingStream = _outgoingStream.pipe(
rxThrottleOp(ms),
rxShareOp()
);
break;
case DIVERTED_INCOMING_DIRECTION:
_divertedIncomingStream = _divertedIncomingStream.pipe(
rxThrottleOp(ms),
rxShareOp()
);
break;
case DIVERTED_OUTGOING_DIRECTION:
_divertedOutgoingStream = _divertedOutgoingStream.pipe(
rxThrottleOp(ms),
rxShareOp()
);
break;
default:
Hf.log(`error`, `EventStreamComposite.throttle - Invalid direction:${direction}.`);
}
return operator;
},
/**
* @description - At observable stream, operates a back pressure on the entire stream. Unlike throttle or debounce, this is not lossy.
*
* @method backPressure
* @param {number} ms - Maximum time length of a buffer.
* @return {object}
*/
backPressure (ms) {
if (Hf.DEVELOPMENT) {
if (!Hf.isInteger(ms)) {
Hf.log(`error`, `EventStreamComposite.backPressure - Input buffer time window is invalid.`);
}
}
if (ms < 1) {
ms = 1;
Hf.log(`warn1`, `EventStreamComposite.backPressure - Input buffer time span should be greater than 0. Reset to 1ms.`);
}
// FIXME: needs to reimplement flat map the outputs of bufferTime.
switch (direction) { // eslint-disable-line
case INCOMING_DIRECTION:
_incomingStream = _incomingStream.pipe(
rxBufferTimeOp(ms),
rxFilterOp((payloads) => Hf.isNonEmptyArray(payloads)),
rxFlatMapOp((payload) => payload),
rxShareOp()
);
break;
case OUTGOING_DIRECTION:
_outgoingStream = _outgoingStream.pipe(
rxBufferTimeOp(ms),
rxFilterOp((payloads) => Hf.isNonEmptyArray(payloads)),
rxFlatMapOp((payload) => payload),
rxShareOp()
);
break;
case DIVERTED_INCOMING_DIRECTION:
_divertedIncomingStream = _divertedIncomingStream.pipe(
rxBufferTimeOp(ms),
rxFilterOp((payloads) => Hf.isNonEmptyArray(payloads)),
rxFlatMapOp((payload) => payload),
rxShareOp()
);
break;
case DIVERTED_OUTGOING_DIRECTION:
_divertedOutgoingStream = _divertedOutgoingStream.pipe(
rxBufferTimeOp(ms),
rxFilterOp((payloads) => Hf.isNonEmptyArray(payloads)),
rxFlatMapOp((payload) => payload),
rxShareOp()
);
break;
default:
Hf.log(`error`, `EventStreamComposite.backPressure - Invalid direction:${direction}.`);
}
return operator;
},
/**
* @description - At observable stream, operates a timeout on the entire stream.
*
* @method timeout
* @param {object} timeoutPayload
* @param {number} ms
* @return {object}
*/
timeout (timeoutPayload, ms) {
if (Hf.DEVELOPMENT) {
if (!Hf.isSchema({
eventId: `string`
}).of(timeoutPayload)) {
Hf.log(`error`, `EventStreamComposite.timeout - Input timeout payload is invalid.`);
} else if (!Hf.isInteger(ms)) {
Hf.log(`error`, `EventStreamComposite.timeout - Input timeout is invalid.`);
}
}
if (ms < 1) {
ms = 1;
Hf.log(`warn1`, `EventStreamComposite.timeout - Input timeout should be greater than 0. Reset to 1ms.`);
}
switch (direction) { // eslint-disable-line
case INCOMING_DIRECTION:
_incomingStream = _incomingStream.pipe(
rxTimeoutOp(ms, rxOf(timeoutPayload)),
rxShareOp()
);
break;
case OUTGOING_DIRECTION:
_outgoingStream = _outgoingStream.pipe(
rxTimeoutOp(ms, rxOf(timeoutPayload)),
rxShareOp()
);
break;
case DIVERTED_INCOMING_DIRECTION:
_divertedIncomingStream = _divertedIncomingStream.pipe(
rxTimeoutOp(ms, rxOf(timeoutPayload)),
rxShareOp()
);
break;
case DIVERTED_OUTGOING_DIRECTION:
_divertedOutgoingStream = _divertedOutgoingStream.pipe(
rxTimeoutOp(ms, rxOf(timeoutPayload)),
rxShareOp()
);
break;
default:
Hf.log(`error`, `EventStreamComposite.timeout - Invalid direction:${direction}.`);
}
return operator;
},
/**
* @description - At observable stream, operates a monitor on the entire stream.
*
* @method monitor
* @param {object} logger
* @return {object}
*/
monitor (logger) {
if (Hf.DEVELOPMENT) {
if (!Hf.isSchema({
logNext: `function`
}).of(logger)) {
Hf.log(`error`, `EventStreamComposite.monitor - Input logger object is invalid.`);
}
}
const {
logNext,
logError,
logCompleted
} = Hf.fallback({
/**
* @description - On subscription to error...
*
* @method logOnError
* @param {string} error
* @return void
*/
logError (error) {
Hf.log(`error`, `EventStreamComposite.monitor.logError - ${error.message}`);
},
/**
* @description - On subscription to completion...
*
* @method logOnCompleted
* @return void
*/
logCompleted () {
Hf.log(`info0`, `Complete side subscription.`);
}
}).of(logger);
/* using a side observer for monitoring */
const sideObserver = RxSubscriber.create(
logNext,
logError,
logCompleted
);
switch (direction) { // eslint-disable-line
case INCOMING_DIRECTION:
_incomingStream = _incomingStream.pipe(
rxTapOp(sideObserver),
rxShareOp()
);
break;
case OUTGOING_DIRECTION:
_outgoingStream = _outgoingStream.pipe(
rxTapOp(sideObserver),
rxShareOp()
);
break;
case DIVERTED_INCOMING_DIRECTION:
_divertedIncomingStream = _divertedIncomingStream.pipe(
rxTapOp(sideObserver),
rxShareOp()
);
break;
case DIVERTED_OUTGOING_DIRECTION:
_divertedOutgoingStream = _divertedOutgoingStream.pipe(
rxTapOp(sideObserver),
rxShareOp()
);
break;
default:
Hf.log(`error`, `EventStreamComposite.monitor - Invalid direction:${direction}.`);
}
return operator;
},
recombine () {
switch (direction) { // eslint-disable-line
case DIVERTED_INCOMING_DIRECTION:
_incomingStream = _incomingStream.pipe(
rxMergeOp(_divertedIncomingStream),
rxShareOp()
);
break;
case DIVERTED_OUTGOING_DIRECTION:
_outgoingStream = _outgoingStream.pipe(
rxMergeOp(_divertedOutgoingStream),
rxShareOp()
);
Object.keys(_targetRegistrationCache).forEach((fId) => {
_targetRegistrationCache[fId].connectStream(_divertedOutgoingStream);
});
break;
default:
Hf.log(`warn1`, `EventStreamComposite.recombine - Cannot recombine non diverted direction:${direction}.`);
}
},
/**
* @description - At observable stream, operates a diversion on the entire stream.
*
* @method divert
* @param {array} eventIds
* @return {object}
*/
divert (...eventIds) {
if (Hf.DEVELOPMENT) {
if (!Hf.isNonEmptyArray(eventIds)) {
Hf.log(`error`, `EventStreamComposite.divert - Factory:${factory.name} input eventId array is empty.`);
} else if (eventIds.some((eventId) => !Hf.isString(eventId) || Hf.isEmpty(eventId))) {
Hf.log(`error`, `EventStreamComposite.divert - Factory:${factory.name} input event Id is invalid.`);
}
}
switch (direction) { // eslint-disable-line
case INCOMING_DIRECTION:
_divertedIncomingStream = _incomingStream.pipe(
rxFilterOp((payload) => eventIds.includes(payload.eventId)),
rxScanOp((payloads, payload) => {
payloads.push(payload);
return payloads;
}, []),
rxFlatMapOp((payload) => payload)
);
/* filler out diverted events from main incoming stream */
_incomingStream = _incomingStream.pipe(
rxFilterOp((payload) => eventIds.every((eventId) => eventId !== payload.eventId)),
rxShareOp()
);
return _createStreamOperatorFor.call(factory, DIVERTED_INCOMING_DIRECTION);
case OUTGOING_DIRECTION:
_divertedOutgoingStream = _outgoingStream.pipe(
rxFilterOp((payload) => eventIds.includes(payload.eventId)),
rxScanOp((payloads, payload) => {
payloads.push(payload);
return payloads;
}, []),
rxFlatMapOp((payload) => payload)
);
/* filler out diverted events from main outgoing stream */
_outgoingStream = _outgoingStream.pipe(
rxFilterOp((payload) => eventIds.every((eventId) => eventId !== payload.eventId)),
rxShareOp()
);
return _createStreamOperatorFor.call(factory, DIVERTED_OUTGOING_DIRECTION);
case `diversion`:
Hf.log(`error`, `EventStreamComposite.divert - Cannot divert a diverted stream.`);
break;
default:
Hf.log(`error`, `EventStreamComposite.divert - Invalid direction:${direction}.`);
}
}
};
return operator;
}
/* ----- Public Functions -------------- */
/**
* @description - Check if both outgoing and incoming stream are activated.
*
* @method isActivated
* @return {boolean}
*/
this.isActivated = function () {
return _outgoingStreamActivated && _incomingStreamActivated;
};
/**
* @description - Register factory outgoing event stream to an external observer.
*
* @method registerStream
* @return void
*/
this.registerStream = function (definition) {
const factory = this;
if (Hf.DEVELOPMENT) {
if (!Hf.isSchema({
fId: `string`,
connectStream: `function`
}).of(definition)) {
Hf.log(`error`, `EventStreamComposite.registerStream - Factory:${factory.name} input stream definition is invalid.`);
}
}
const {
fId,
connectStream
} = definition;
_targetRegistrationCache[fId] = {
connectStream
};
connectStream(_outgoingStream);
};
/**
* @description - Apply incoming event stream operators.
*
* @method operateIncomingStream
* @param {object} - operator
* @return void
*/
this.operateIncomingStream = function (operator) { // eslint-disable-line
Hf.log(`warn0`, `EventStreamComposite.operateIncomingStream - Method is not implemented by default. Ignore this warning if intended.`);
};
/**
* @description - Apply outgoing event stream operators.
*
* @method operateOutgoingStream
* @param {object} - operator
* @return void
*/
this.operateOutgoingStream = function (operator) { // eslint-disable-line
Hf.log(`warn0`, `EventStreamComposite.operateOutgoingStream - Method is not implemented by default. Ignore this warning if intended.`);
};
/**
* @description - When there is an outgoing event...
*
* @method outgoing
* @param {array} eventIds
* @return {object}
*/
this.outgoing = function (...eventIds) {
const factory = this;
if (Hf.DEVELOPMENT) {
if (!Hf.isNonEmptyArray(eventIds)) {
Hf.log(`error`, `EventStreamComposite.outgoing - Factory:${factory.name} input eventId array is empty.`);
} else if (eventIds.some((eventId) => !Hf.isString(eventId) || Hf.isEmpty(eventId))) {
Hf.log(`error`, `EventStreamComposite.outgoing - Factory:${factory.name} input event Id is invalid.`);
}
}
eventIds = eventIds.filter((eventId) => {
if (_arbiter.hasOwnProperty(eventId)) {
return _arbiter[eventId].eventDirectionalState !== REPEATING_EVENT;
}
return true;
});
const outgoingOperator = {
/**
* @description - At event, and after delay for some time...
*
* @method outgoing.delay
* @param {number} ms
* @return {object}
*/
delay (ms) {
if (Hf.DEVELOPMENT) {
if (!Hf.isInteger(ms)) {
Hf.log(`error`, `EventStreamComposite.outgoing.delay - Input wait time is invalid.`);
}
}
if (ms < 1) {