@bitblit/epsilon
Version:
Tiny adapter to simplify building API gateway Lambda APIS
153 lines • 8.87 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.BackgroundHttpAdapterHandler = void 0;
const common_1 = require("@bitblit/ratchet/common");
const background_process_handling_1 = require("./background-process-handling");
const bad_request_error_1 = require("../http/error/bad-request-error");
const not_found_error_1 = require("../http/error/not-found-error");
/**
* We use a FIFO queue so that 2 different Lambdas don't both work on the same
* thing at the same time.
*/
class BackgroundHttpAdapterHandler {
constructor(backgroundConfig, modelValidator, backgroundManager, maxWaitInMsForBackgroundJobToStart = 10000) {
this.backgroundConfig = backgroundConfig;
this.modelValidator = modelValidator;
this.backgroundManager = backgroundManager;
this.maxWaitInMsForBackgroundJobToStart = maxWaitInMsForBackgroundJobToStart;
}
get httpMetaEndpoint() {
return this.backgroundConfig.httpMetaEndpoint;
}
get httpSubmissionPath() {
return this.backgroundConfig.httpSubmissionPath;
}
get httpStatusPath() {
return this.backgroundConfig.httpStatusEndpoint;
}
get implyTypeFromPathSuffix() {
return this.backgroundConfig.implyTypeFromPathSuffix;
}
handleBackgroundStatusRequest(evt, context) {
return __awaiter(this, void 0, void 0, function* () {
common_1.Logger.info('handleBackgroundStatusRequest called');
if (!this.backgroundConfig.transactionLogger) {
throw new bad_request_error_1.BadRequestError('Process logging not enabled');
}
else {
const guid = common_1.StringRatchet.trimToNull(evt.pathParameters['guid']) || common_1.StringRatchet.trimToNull(evt.queryStringParameters['guid']);
if (guid) {
const sw = new common_1.StopWatch();
let log = null;
while (!log && sw.elapsedMS() < this.maxWaitInMsForBackgroundJobToStart) {
log = yield this.backgroundConfig.transactionLogger.readTransactionLog(guid);
if (!log) {
common_1.Logger.debug('No log found yet, waiting 500 ms and retrying (%s of %d waited so far)', sw.dump(), this.maxWaitInMsForBackgroundJobToStart);
yield common_1.PromiseRatchet.wait(500);
}
}
if (!log) {
throw new not_found_error_1.NotFoundError().withFormattedErrorMessage('No background result found for guid %s', guid);
}
return log;
}
else {
throw new bad_request_error_1.BadRequestError('No guid specified');
}
}
});
}
handleBackgroundMetaRequest(evt, context) {
return __awaiter(this, void 0, void 0, function* () {
common_1.Logger.info('handleBackgroundMetaRequest called');
const currentCount = yield this.backgroundManager.fetchApproximateNumberOfQueueEntries();
const valid = this.backgroundConfig.processors.map((b) => b.typeName).filter((a) => !!a);
valid.sort((a, b) => a.localeCompare(b));
const rval = {
currentQueueLength: currentCount,
validTypes: valid,
backgroundManagerName: this.backgroundManager.backgroundManagerName,
};
return rval;
});
}
handleBackgroundSubmission(evt, context) {
return __awaiter(this, void 0, void 0, function* () {
common_1.Logger.info('handleBackgroundSubmission : %j (mgr:%s)', evt.parsedBody, this.backgroundManager.backgroundManagerName);
let rval = null;
const startIdx = evt.path.indexOf(this.httpSubmissionPath) + this.httpSubmissionPath.length;
let pathSuppliedBackgroundType = this.backgroundConfig.implyTypeFromPathSuffix
? evt.path.substring(startIdx).split('-').join('').toLowerCase()
: '';
// Strip any query params or fragments
if (pathSuppliedBackgroundType.includes('?')) {
pathSuppliedBackgroundType = pathSuppliedBackgroundType.substring(0, pathSuppliedBackgroundType.indexOf('?'));
}
if (pathSuppliedBackgroundType.includes('#')) {
pathSuppliedBackgroundType = pathSuppliedBackgroundType.substring(0, pathSuppliedBackgroundType.indexOf('#'));
}
const entry = evt.parsedBody || {}; // Many background submissions contain no body (pure triggers)
// So, either this was configured pathed (like xxx/background/{typename} or non-pathed
// like /xxx/background. If non-pathed, you must supply the type field in the body. If
// pathed, you must either NOT supply the type field in the body, since it'll be determined
// by the path, or the types must match
if (common_1.StringRatchet.trimToNull(pathSuppliedBackgroundType)) {
if (common_1.StringRatchet.trimToNull(entry === null || entry === void 0 ? void 0 : entry.type) && entry.type.toLocaleLowerCase() !== pathSuppliedBackgroundType.toLocaleLowerCase()) {
throw new bad_request_error_1.BadRequestError('Background submission has type but does not match path supplied type');
}
else {
entry.type = pathSuppliedBackgroundType;
}
}
else {
// No path, must be in here
if (!common_1.StringRatchet.trimToNull(entry === null || entry === void 0 ? void 0 : entry.type)) {
throw new bad_request_error_1.BadRequestError('Background submission missing type and not configured in pathed mode');
}
}
const foundProc = this.backgroundConfig.processors.find((s) => s.typeName.toLowerCase() === entry.type.toLowerCase());
const immediate = common_1.BooleanRatchet.parseBool(evt.queryStringParameters['immediate']);
const startProcessor = common_1.BooleanRatchet.parseBool(evt.queryStringParameters['startProcessor']);
if (foundProc) {
// Perform a validation (if this is a path-supplied type it probably already happened, but don't hurt to do it again)
// If you're worried about that, and you do path-typing, just don't have your processors set the dataModelName
if (common_1.StringRatchet.trimToNull(foundProc.dataSchemaName)) {
// I'm not allowing empty and extra properties here since this is a fully internally defined object
const errors = this.modelValidator.validate(foundProc.dataSchemaName, entry.data, false, false);
if (errors.length > 0) {
throw new bad_request_error_1.BadRequestError().withErrors(errors);
}
}
let result = null;
if (immediate) {
result = yield this.backgroundManager.fireImmediateProcessRequest(entry);
}
else {
result = yield this.backgroundManager.addEntryToQueue(entry, startProcessor);
}
rval = {
processHandling: immediate ? background_process_handling_1.BackgroundProcessHandling.Immediate : background_process_handling_1.BackgroundProcessHandling.Queued,
startProcessorRequested: startProcessor,
success: true,
resultId: result,
error: null,
};
}
else {
throw new bad_request_error_1.BadRequestError().withFormattedErrorMessage('Could not find target background processor : %s', entry.type);
}
return rval;
});
}
}
exports.BackgroundHttpAdapterHandler = BackgroundHttpAdapterHandler;
//# sourceMappingURL=background-http-adapter-handler.js.map