import _ from 'lodash'; import { Code } from '@superblocksteam/types'; import { Global, PlaceholdersInfo } from '.'; // TODO(pbardea): Rename "view mode" everywhere to something more descriptive. export const PUBLISHED_VIEW_MODE = true; interface KVStore { read: (keys: string[]) => Promise<{ data: unknown[] }>; write: (key: string, value: string) => Promise; writeMany: (kvs: { key: string; value: unknown }[]) => Promise; } export class ExecutionContext { globals: { [key: string]: unknown }; variables?: Record; kvStore?: KVStore; outputs: { [key: string]: ExecutionOutput }; preparedStatementContext: unknown[]; error?: string; errorContext?: { actionId: string; actionName: string; pluginId: string; datasourceId?: string; authError?: boolean; }; constructor(context?: ExecutionContext) { this.globals = context && context.globals ? _.cloneDeep(context.globals) : {}; this.outputs = context && context.outputs ? _.cloneDeep(context.outputs) : {}; this.preparedStatementContext = context && context.preparedStatementContext ? _.cloneDeep(context.preparedStatementContext) : []; } // Do not merge the objects in arrays. Just replace the entire array. private static customMerger(objValue, srcValue) { if (_.isArray(objValue)) { return srcValue; } } addGlobalVariable(name: string, value: unknown): void { const obj = { [name]: value }; this.globals = _.mergeWith(obj, this.globals, ExecutionContext.customMerger); } addGlobalsOverride(global: Global): void { for (const key of global.keys()) { this.addGlobalVariableOverride(key, global.get(key)); } } addGlobalVariableOverride(name: string, value: unknown): void { const obj = { [name]: value }; this.globals = _.mergeWith(this.globals, obj, ExecutionContext.customMerger); } addOutput(name: string, output: ExecutionOutput): void { this.outputs[name] = output; } merge(other: ExecutionContext): void { this.globals = { ...this.globals, ...other.globals }; this.outputs = { ...this.outputs, ...other.outputs }; } globalBindingKeys(): string[] { if (_.isEmpty(this.globals)) { return []; } return Object.keys(this.globals); } outputBindingKeys(): string[] { if (_.isEmpty(this.outputs)) { return []; } return Object.keys(this.outputs); } } export enum WorkflowExecutionParamsKey { QUERY_PARAMS = 'params', BODY = 'body' } export enum ApiReservedQueryParams { AUTH = 'sb-auth', ENVIRONMENT = 'environment', TEST = 'test', RAW = 'raw', PROFILE = 'profile' } export interface ExecutionParam { key: string; value: unknown; } // Log message that includes the level alongside the message that was logged interface StructuredLog { msg: string; level: 'info' | 'warn' | 'error'; } // Plugin execution could show the underlying raw request, e.g. curl for RestAPI // It's controlled by hasRawRequest of the Plugin type RawRequest = string | undefined; export class ExecutionOutput { error?: string; authError?: boolean; children?: string[]; startTimeUtc?: Date; executionTime: number; log: string[]; structuredLog: StructuredLog[]; output: unknown; request: RawRequest; placeholdersInfo?: PlaceholdersInfo; integrationErrorCode?: Code; constructor() { this.output = {}; this.log = []; this.structuredLog = []; } static fromJSONString(json: string): ExecutionOutput { const obj = JSON.parse(json); const instance = new ExecutionOutput(); instance.error = obj.error; instance.children = obj.children; instance.executionTime = obj.executionTime; instance.log = obj.log; instance.structuredLog = obj.structuredLog; instance.output = obj.output; instance.request = obj.request; instance.startTimeUtc = obj.startTimeUtc; return instance; } logInfo(msg: string): void { if (msg) { this.log.push(`${msg}`); this.structuredLog.push({ msg, level: 'info' }); } } logWarn(msg: string): void { if (msg) { this.log.push(`[WARN] ${msg}`); this.structuredLog.push({ msg, level: 'warn' }); } } logError(msg: string, authError?: boolean): void { if (msg) { this.error = msg; this.authError = authError; this.log.push(`[ERROR] ${msg}`); this.structuredLog.push({ msg, level: 'error' }); } } }