UNPKG

@bitblit/ratchet-epsilon-common

Version:

Tiny adapter to simplify building API gateway Lambda APIS

282 lines 11.6 kB
import { Logger } from '@bitblit/ratchet-common/logger/logger'; import { ErrorRatchet } from '@bitblit/ratchet-common/lang/error-ratchet'; import { StringRatchet } from '@bitblit/ratchet-common/lang/string-ratchet'; import { StopWatch } from '@bitblit/ratchet-common/lang/stop-watch'; import { LambdaEventDetector } from '@bitblit/ratchet-aws/lambda/lambda-event-detector'; import { EpsilonConstants } from '../epsilon-constants.js'; import { BackgroundValidator } from './background-validator.js'; import { BackgroundExecutionEventType } from './background-execution-event-type.js'; import { ContextUtil } from '../util/context-util.js'; import { AbstractBackgroundManager } from './manager/abstract-background-manager.js'; export class BackgroundHandler { cfg; mgr; modelValidator; processors; validator; constructor(cfg, mgr, modelValidator) { this.cfg = cfg; this.mgr = mgr; this.modelValidator = modelValidator; const cfgErrors = BackgroundValidator.validateConfig(cfg); if (cfgErrors.length > 0) { ErrorRatchet.throwFormattedErr('Invalid background config : %j', cfgErrors); } Logger.silly('Starting Background processor, %d processors', cfg.processors.length); this.validator = new BackgroundValidator(cfg, modelValidator); this.processors = BackgroundValidator.validateAndMapProcessors(cfg.processors, modelValidator); if (mgr?.immediateProcessQueue && mgr.immediateProcessQueue()) { Logger.info('Attaching to immediate processing queue'); mgr.immediateProcessQueue().subscribe(async (evt) => { Logger.debug('Processing local background entry : %j', evt); const rval = await this.processSingleBackgroundEntry(evt); Logger.info('Processor returned %s', rval); }); } } get validProcessorNames() { return this.processors ? Array.from(this.processors.keys()) : []; } extractLabel(evt, _context) { let rval = null; if (this.isBackgroundStartSnsEvent(evt)) { rval = 'BG:START-EVT'; } else if (this.isBackgroundImmediateFireEvent(evt)) { const pEvt = this.parseImmediateFireBackgroundEntry(evt); rval = 'BG:' + pEvt.type + ':' + pEvt.guid; } else { rval = 'BG:UNKNOWN'; } return rval; } handlesEvent(evt) { return LambdaEventDetector.isValidSnsEvent(evt) && this.isBackgroundSNSEvent(evt); } isBackgroundSNSEvent(event) { return this.isBackgroundStartSnsEvent(event) || this.isBackgroundImmediateFireEvent(event); } isBackgroundStartSnsEvent(event) { let rval = false; if (event) { if (LambdaEventDetector.isSingleSnsEvent(event)) { const cast = event; rval = cast.Records[0].Sns.Message === EpsilonConstants.BACKGROUND_SNS_START_MARKER; } } return rval; } isBackgroundImmediateFireEvent(event) { let rval = false; if (event) { if (LambdaEventDetector.isSingleSnsEvent(event)) { const cast = event; const msg = cast.Records[0].Sns.Message; if (StringRatchet.trimToNull(msg)) { const parsed = JSON.parse(msg); rval = !!parsed && parsed['type'] === EpsilonConstants.BACKGROUND_SNS_IMMEDIATE_RUN_FLAG; } } } return rval; } parseImmediateFireBackgroundEntry(event) { let rval = null; try { if (event) { if (LambdaEventDetector.isSingleSnsEvent(event)) { const cast = event; const msg = cast.Records[0].Sns.Message; if (StringRatchet.trimToNull(msg)) { const parsed = JSON.parse(msg); if (!!parsed && parsed['type'] === EpsilonConstants.BACKGROUND_SNS_IMMEDIATE_RUN_FLAG) { rval = parsed['backgroundEntry']; } } } } } catch (err) { Logger.error('Could not parse %j as an immediate run event : %s', event, err, err); } return rval; } async processEvent(event, _context) { let procd = null; if (!this.isBackgroundStartSnsEvent(event)) { const backgroundEntry = this.parseImmediateFireBackgroundEntry(event); if (backgroundEntry) { Logger.silly('Processing immediate fire event : %j', backgroundEntry); const result = await this.processSingleBackgroundEntry(backgroundEntry); Logger.silly('Result was : %s', result); procd = 1; } else { Logger.warn('Tried to process non-background start / immediate event : %j returning false', event); } } else { Logger.info('Reading task from background queue'); procd = await this.takeAndProcessSingleBackgroundQueueEntry(); if (procd > 0) { Logger.info('Processed %d elements from background queue, refiring', procd); const refire = await this.mgr.fireStartProcessingRequest(); Logger.info('Refire returned %s', refire); } else { Logger.info('No items processed - stopping'); } } const rval = { statusCode: 200, body: StringRatchet.safeString(procd), isBase64Encoded: false, }; return rval; } async takeAndProcessSingleBackgroundQueueEntry() { let rval = null; const entries = await this.mgr.takeEntryFromBackgroundQueue(); Logger.info('Found %d entries - processing', entries.length); for (const e of entries) { const result = await this.processSingleBackgroundEntry(e); rval += result ? 1 : 0; } Logger.debug('Returning %d', rval); return rval; } async safeWriteToLogger(entry) { if (this.cfg.transactionLogger) { try { await this.cfg.transactionLogger.logTransaction(entry); } catch (err) { Logger.error('Failed to write to transaction logger : %j : %s', entry, err, err); } } else { Logger.silly('Skipping - no logger defined'); } } async conditionallyStartTransactionLog(e) { if (!StringRatchet.trimToNull(e.guid)) { Logger.warn('No guid found - creating'); e.guid = AbstractBackgroundManager.generateBackgroundGuid(); const log = { request: e, running: true, }; await this.safeWriteToLogger(log); } Logger.debug('Starting transaction log'); } async conditionallyCompleteTransactionLog(e, result, error, runtimeMS) { Logger.debug('Completing transaction log'); const log = { request: e, result: result, error: error ? ErrorRatchet.safeStringifyErr(error) : null, running: false, runtimeMS: runtimeMS, }; await this.safeWriteToLogger(log); } async conditionallyRunErrorProcessor(e, error) { try { if (this.cfg.errorProcessor) { Logger.info('Running error processor'); await this.cfg.errorProcessor.handleError(e, error); } } catch (err) { Logger.error('Background : BAD - Failed to run error processor : %s', err, err); } } async fireListenerEvent(event) { const listeners = this.cfg.executionListeners || []; for (const listener of listeners) { try { await listener.onEvent(event); } catch (err) { Logger.error('Failure triggering handler %s : %s', StringRatchet.trimToNull(listener?.label) || 'No-name', err); } } } async processSingleBackgroundEntry(inE) { let e = inE; ContextUtil.setOverrideTraceFromInternalBackgroundEntry(e); Logger.info('Background Process Start: %j', e); const sw = new StopWatch(); await this.conditionallyStartTransactionLog(e); if (this?.mgr?.modifyPayloadPreProcess) { Logger.silly('Firing payload pre-process'); e = await this.mgr.modifyPayloadPreProcess(e); } let rval = false; try { await this.fireListenerEvent({ type: BackgroundExecutionEventType.ProcessStarting, processorType: e.type, data: e.data, guid: e.guid, }); const processorInput = this.processors.get(e.type); if (!processorInput) { ErrorRatchet.throwFormattedErr('Found no processor for background entry : %j (returning false)', e); await this.fireListenerEvent({ type: BackgroundExecutionEventType.NoMatchProcessorName, processorType: e.type, data: e.data, guid: e.guid, }); } let dataValidationErrors = []; if (StringRatchet.trimToNull(processorInput.dataSchemaName)) { dataValidationErrors = this.modelValidator.validate(processorInput.dataSchemaName, e.data, false, false); } if (dataValidationErrors.length > 0) { await this.fireListenerEvent({ type: BackgroundExecutionEventType.DataValidationError, processorType: e.type, data: e.data, errors: dataValidationErrors, guid: e.guid, }); ErrorRatchet.throwFormattedErr('Not processing, data failed validation; entry was %j : errors : %j', e, dataValidationErrors); } else { let result = await processorInput.handleEvent(e.data, this.mgr); result = result || 'SUCCESSFUL COMPLETION : NO RESULT RETURNED'; await this.conditionallyCompleteTransactionLog(e, result, null, sw.elapsedMS()); await this.fireListenerEvent({ type: BackgroundExecutionEventType.ExecutionSuccessfullyComplete, processorType: e.type, data: result, guid: e.guid, }); rval = true; } } catch (err) { Logger.error('Background Process Error: %j : %s', e, err, err); await this.conditionallyRunErrorProcessor(e, err); await this.conditionallyCompleteTransactionLog(e, null, err, sw.elapsedMS()); await this.fireListenerEvent({ type: BackgroundExecutionEventType.ExecutionFailedError, processorType: e.type, data: e.data, errors: [ErrorRatchet.safeStringifyErr(err)], guid: e.guid, }); } Logger.info('Background Process Stop: %j : %s', e, sw.dump()); return rval; } getConfig() { const rval = Object.assign({}, this.cfg); return rval; } } //# sourceMappingURL=background-handler.js.map