@bitblit/ratchet-epsilon-common
Version:
Tiny adapter to simplify building API gateway Lambda APIS
282 lines • 11.6 kB
JavaScript
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