import { Logs } from '@aimee-blue/ab-contracts'; import { time } from '../time'; import * as PubSub from '../pubsub'; import { appVersion, appName } from '../app'; import { Observable, merge, empty, defer } from 'rxjs'; import { ignoreElements, catchError, filter, publish, mergeMap, map, } from 'rxjs/operators'; import { IAction } from '../action'; import { timesRegistered } from './streams'; import { BasicLogger, defaultBasicLogger } from '../logging'; interface ISendParams { event: string; traceKey?: string; data?: { [key: string]: unknown; }; } export const sendOne = async (params: ISendParams) => { const timestamp = await time(); const version = await appVersion(); const name = await appName(); const data: Logs.ILogParams = { event: params.event, data: { version, appName: name, ...params.data, }, source: 'server', timestamp, ...(params.traceKey && { traceKey: params.traceKey, }), }; return PubSub.publish('profiler', data); }; function optionalFilter( predicate?: (action: A) => action is AOut ) { return (stream: Observable) => { if (predicate) { return stream.pipe(filter(predicate)); } else { return stream; } }; } export interface ISendActionsParams { event?: string; traceKey?: string; filter?: (action: A) => action is AOut; transform?: (action: A, params?: ISendActionsParams) => ISendParams; logger?: BasicLogger; } function defaultTransform( action: IAction, params?: ISendActionsParams ): ISendParams { const { filter: _, transform: __, ...logParams } = params || {}; return { event: action.type, data: (action as unknown) as { [key: string]: unknown }, ...logParams, }; } export const sendActions = ( params?: ISendActionsParams, deps = { sendOne, } ) => (input: Observable) => input.pipe( publish(shared => merge( shared, shared.pipe( optionalFilter(params && params.filter), mergeMap(action => defer(() => deps.sendOne( ((params && params.transform) || defaultTransform)( action, params ) ) ).pipe( catchError(err => { (params?.logger ?? defaultBasicLogger()).error( '💥 An error when profiling actions', err ); return empty(); }) ) ), ignoreElements() ) ) ) ); export function sendAllTimings() { return timesRegistered().pipe( map(timing => ({ type: `PROFILER/${timing.name.toUpperCase()}`, timeTook: timing.time, ...timing.details, })), sendActions(), ignoreElements() ); }