@cap-js-community/event-queue
Version:
An event queue that enables secure transactional processing of asynchronous and periodic events, featuring instant event processing with Redis Pub/Sub and load distribution across all application instances.
1,001 lines (851 loc) • 28.6 kB
JavaScript
"use strict";
const cds = require("@sap/cds");
const { CronExpressionParser } = require("cron-parser");
const { getEnvInstance } = require("./shared/env");
const redis = require("./shared/redis");
const EventQueueError = require("./EventQueueError");
const { Priorities } = require("./constants");
const FOR_UPDATE_TIMEOUT = 10;
const GLOBAL_TX_TIMEOUT = 30 * 60 * 1000;
const REDIS_PREFIX = "EVENT_QUEUE";
const REDIS_CONFIG_CHANNEL = "CONFIG_CHANNEL";
const REDIS_OFFBOARD_TENANT_CHANNEL = "REDIS_OFFBOARD_TENANT_CHANNEL";
const REDIS_CONFIG_BLOCKLIST_CHANNEL = "REDIS_CONFIG_BLOCKLIST_CHANNEL";
const COMMAND_BLOCK = "EVENT_BLOCK";
const COMMAND_UNBLOCK = "EVENT_UNBLOCK";
const COMPONENT_NAME = "/eventQueue/config";
const MIN_INTERVAL_SEC = 10;
const DEFAULT_LOAD = 1;
const DEFAULT_PRIORITY = Priorities.Medium;
const DEFAULT_INCREASE_PRIORITY = true;
const DEFAULT_KEEP_ALIVE_INTERVAL = 60;
const DEFAULT_MAX_FACTOR_STUCK_2_KEEP_ALIVE_INTERVAL = 3.5;
const DEFAULT_INHERIT_TRACE_CONTEXT = true;
const DEFAULT_CHECK_FOR_NEXT_CHUNK = true;
const SUFFIX_PERIODIC = "_PERIODIC";
const CAP_EVENT_TYPE = "CAP_OUTBOX";
const CAP_PARALLEL_DEFAULT = 5;
const CAP_MAX_ATTEMPTS_DEFAULT = 5;
const DELETE_TENANT_BLOCK_AFTER_MS = 5 * 60 * 1000;
const PRIORITIES = Object.values(Priorities);
const UTC_DEFAULT = false;
const USE_CRON_TZ_DEFAULT = true;
const BASE_TABLES = {
EVENT: "sap.eventqueue.Event",
LOCK: "sap.eventqueue.Lock",
};
const ALLOWED_EVENT_OPTIONS_BASE = [
"type",
"subType",
"load",
"impl",
"transactionMode",
"deleteFinishedEventsAfterDays",
"useEventQueueUser",
"priority",
"keepAliveInterval",
"increasePriorityOverTime",
"keepAliveMaxInProgressTime",
"appNames",
"appInstances",
"internalEvent",
];
const ALLOWED_EVENT_OPTIONS_AD_HOC = [
...ALLOWED_EVENT_OPTIONS_BASE,
"inheritTraceContext",
"selectMaxChunkSize",
"parallelEventProcessing",
"retryAttempts",
"processAfterCommit",
"checkForNextChunk",
"retryFailedAfter",
"multiInstanceProcessing",
"kind",
"timeBucket",
];
const ALLOWED_EVENT_OPTIONS_PERIODIC_EVENT = [
...ALLOWED_EVENT_OPTIONS_BASE,
"interval",
"cron",
"utc",
"useCronTimezone",
"randomOffset",
];
class Config {
#logger;
#config;
#forUpdateTimeout;
#globalTxTimeout;
#runInterval;
#redisEnabled;
#initialized;
#instanceLoadLimit;
#tableNameEventQueue;
#tableNameEventLock;
#isEventQueueActive;
#configFilePath;
#processEventsAfterPublish;
#registerAsEventProcessor;
#disableRedis;
#env;
#eventMap;
#updatePeriodicEvents;
#blockedEvents;
#isEventBlockedCb;
#thresholdLoggingEventProcessing;
#useAsCAPOutbox;
#userId;
#cleanupLocksAndEventsForDev;
#redisOptions;
#insertEventsBeforeCommit;
#enableTelemetry;
#unsubscribeHandlers = [];
#unsubscribedTenants = {};
#cronTimezone;
#randomOffsetPeriodicEvents;
#redisNamespace;
#publishEventBlockList;
#crashOnRedisUnavailable;
#tenantIdFilterTokenInfoCb;
#tenantIdFilterEventProcessingCb;
#configEvents;
#configPeriodicEvents;
#enableAdminService;
static #instance;
constructor() {
this.#logger = cds.log(COMPONENT_NAME);
this.#config = null;
this.#forUpdateTimeout = FOR_UPDATE_TIMEOUT;
this.#globalTxTimeout = GLOBAL_TX_TIMEOUT;
this.#runInterval = null;
this.#redisEnabled = null;
this.#initialized = false;
this.#instanceLoadLimit = 100;
this.#tableNameEventQueue = null;
this.#tableNameEventLock = null;
this.#isEventQueueActive = true;
this.#configFilePath = null;
this.#processEventsAfterPublish = null;
this.#disableRedis = null;
this.#env = getEnvInstance();
this.#blockedEvents = {};
}
getEventConfig(type, subType) {
return this.#eventMap[this.generateKey(type, subType)]
? { ...this.#eventMap[this.generateKey(type, subType)] }
: undefined;
}
isCapOutboxEvent(type) {
return type === CAP_EVENT_TYPE;
}
hasEventAfterCommitFlag(type, subType) {
return this.#eventMap[this.generateKey(type, subType)]?.processAfterCommit ?? true;
}
_checkRedisIsBound() {
return !!this.#env.redisRequires?.credentials;
}
#parseRegexOrString(str) {
const regexLiteralPattern = /^\/((?:\\.|[^\\/])*)\/([gimsuy]*)$/;
const match = str.match(regexLiteralPattern);
if (match) {
try {
return { type: "regex", value: new RegExp(match[1], match[2]) };
} catch {
return { type: "string", value: str };
}
}
return { type: "string", value: str };
}
#normalizeSubType(rawSubType) {
const [serviceName, actionName] = rawSubType.split(".");
const actionSpecificCall = this.getCdsOutboxEventSpecificConfig(serviceName, actionName);
return actionSpecificCall ? rawSubType : serviceName;
}
shouldBeProcessedInThisApplication(type, rawSubType) {
const subType = this.#normalizeSubType(rawSubType);
const config = this.#eventMap[this.generateKey(type, subType)];
const appNameConfig = config._appNameMap;
const appInstanceConfig = config._appInstancesMap;
let result = true;
if (!appNameConfig && !appInstanceConfig) {
return result;
}
if (appNameConfig) {
if (config._appNameContainsRegex) {
for (const configKey in appNameConfig) {
const config = appNameConfig[configKey];
if (config.type === "regex") {
result = config.value.test(this.#env.applicationName);
} else {
const shouldBeProcessedBasedOnAppName = appNameConfig[this.#env.applicationName];
result = !!shouldBeProcessedBasedOnAppName;
}
if (result) {
break;
}
}
} else {
const shouldBeProcessedBasedOnAppName = appNameConfig[this.#env.applicationName];
if (!shouldBeProcessedBasedOnAppName) {
return false;
}
}
}
if (appInstanceConfig) {
const shouldBeProcessedBasedOnAppInstance = appInstanceConfig[this.#env.applicationInstance];
if (!shouldBeProcessedBasedOnAppInstance) {
return false;
}
}
return result;
}
checkRedisEnabled() {
this.#redisEnabled = !this.#disableRedis && this._checkRedisIsBound();
return this.#redisEnabled;
}
attachConfigChangeHandler() {
this.#attachBlockListChangeHandler();
redis.subscribeRedisChannel(this.redisOptions, REDIS_CONFIG_CHANNEL, (messageData) => {
try {
const { key, value } = JSON.parse(messageData);
if (this[key] !== value) {
this.#logger.info("received config change", { key, value });
this[key] = value;
}
} catch (err) {
this.#logger.error("could not parse event config change", err, {
messageData,
});
}
});
}
attachRedisUnsubscribeHandler() {
this.#logger.info("attached redis handle for unsubscribe events");
redis.subscribeRedisChannel(this.redisOptions, REDIS_OFFBOARD_TENANT_CHANNEL, (messageData) => {
try {
const { tenantId } = JSON.parse(messageData);
this.#logger.info("received unsubscribe broadcast event", { tenantId });
this.executeUnsubscribeHandlers(tenantId);
} catch (err) {
this.#logger.error("could not parse unsubscribe broadcast event", err, {
messageData,
});
}
});
}
executeUnsubscribeHandlers(tenantId) {
this.#unsubscribedTenants[tenantId] = true;
setTimeout(() => delete this.#unsubscribedTenants[tenantId], DELETE_TENANT_BLOCK_AFTER_MS);
for (const unsubscribeHandler of this.#unsubscribeHandlers) {
try {
unsubscribeHandler(tenantId);
} catch (err) {
this.#logger.error("could executing unsubscribe handler", err, {
tenantId,
});
}
}
}
handleUnsubscribe(tenantId) {
if (this.redisEnabled) {
redis
.publishMessage(this.redisOptions, REDIS_OFFBOARD_TENANT_CHANNEL, JSON.stringify({ tenantId }))
.catch((error) => {
this.#logger.error(`publishing tenant unsubscribe failed. tenantId: ${tenantId}`, error);
});
} else {
this.executeUnsubscribeHandlers(tenantId);
}
}
attachUnsubscribeHandler(cb) {
this.#unsubscribeHandlers.push(cb);
}
publishConfigChange(key, value) {
if (!this.redisEnabled) {
this.#logger.info("redis not connected, config change won't be published", { key, value });
return;
}
redis.publishMessage(this.redisOptions, REDIS_CONFIG_CHANNEL, JSON.stringify({ key, value })).catch((error) => {
this.#logger.error(`publishing config change failed key: ${key}, value: ${value}`, error);
});
}
#attachBlockListChangeHandler() {
redis.subscribeRedisChannel(this.redisOptions, REDIS_CONFIG_BLOCKLIST_CHANNEL, (messageData) => {
try {
const { command, key, tenant } = JSON.parse(messageData);
if (command === COMMAND_BLOCK) {
this.#blockEventLocalState(key, tenant);
} else {
this.#unblockEventLocalState(key, tenant);
}
} catch (err) {
this.#logger.error("could not parse event blocklist change", err, {
messageData,
});
}
});
}
blockEvent(type, subType, isPeriodic, tenant = "*") {
const typeWithSuffix = `${type}${isPeriodic ? SUFFIX_PERIODIC : ""}`;
const config = this.getEventConfig(typeWithSuffix, subType);
if (!config) {
return;
}
const key = this.generateKey(typeWithSuffix, subType);
this.#blockEventLocalState(key, tenant);
if (!this.redisEnabled || !this.publishEventBlockList) {
return;
}
redis
.publishMessage(
this.redisOptions,
REDIS_CONFIG_BLOCKLIST_CHANNEL,
JSON.stringify({ command: COMMAND_BLOCK, key, tenant })
)
.catch((error) => {
this.#logger.error(`publishing config block failed key: ${key}`, error);
});
}
#blockEventLocalState(key, tenant) {
this.#blockedEvents[key] ??= {};
this.#blockedEvents[key][tenant] = true;
return key;
}
clearPeriodicEventBlockList() {
this.#blockedEvents = {};
}
unblockEvent(type, subType, isPeriodic, tenant = "*") {
const typeWithSuffix = `${type}${isPeriodic ? SUFFIX_PERIODIC : ""}`;
const key = this.generateKey(typeWithSuffix, subType);
const config = this.getEventConfig(typeWithSuffix, subType);
if (!config) {
return;
}
this.#unblockEventLocalState(key, tenant);
if (!this.redisEnabled) {
return;
}
redis
.publishMessage(
this.redisOptions,
REDIS_CONFIG_BLOCKLIST_CHANNEL,
JSON.stringify({ command: COMMAND_UNBLOCK, key, tenant })
)
.catch((error) => {
this.#logger.error(`publishing config block failed key: ${key}`, error);
});
}
addCAPOutboxEventBase(serviceName, config) {
if (this.#eventMap[this.generateKey(CAP_EVENT_TYPE, serviceName)]) {
const index = this.#config.events.findIndex(
(event) => event.type === CAP_EVENT_TYPE && event.subType === serviceName
);
this.#config.events.splice(index, 1);
}
// NOTE: CAP outbox defaults are injected by cds.requires.outbox
const eventConfig = this.#sanitizeParamsAdHocEvent({
type: CAP_EVENT_TYPE,
subType: serviceName,
impl: "./outbox/EventQueueGenericOutboxHandler",
kind: config.kind ?? "persistent-outbox",
selectMaxChunkSize: config.selectMaxChunkSize ?? config.chunkSize,
parallelEventProcessing: config.parallelEventProcessing ?? (config.parallel && CAP_PARALLEL_DEFAULT),
retryAttempts: config.retryAttempts ?? config.maxAttempts ?? CAP_MAX_ATTEMPTS_DEFAULT,
...config,
});
eventConfig.internalEvent = true;
this.#basicEventTransformation(eventConfig);
this.#validateAdHocEvents(this.#eventMap, eventConfig, false);
this.#basicEventTransformationAfterValidate(eventConfig);
this.#config.events.push(eventConfig);
this.#eventMap[this.generateKey(CAP_EVENT_TYPE, serviceName)] = eventConfig;
}
addCAPOutboxEventSpecificAction(serviceName, actionName) {
const subType = [serviceName, actionName].join(".");
if (this.#eventMap[this.generateKey(CAP_EVENT_TYPE, subType)]) {
const index = this.#config.events.findIndex(
(event) => event.type === CAP_EVENT_TYPE && event.subType === serviceName
);
this.#config.events.splice(index, 1);
}
const eventConfig = this.#sanitizeParamsAdHocEvent({
...this.getEventConfig(CAP_EVENT_TYPE, serviceName),
...this.getCdsOutboxEventSpecificConfig(serviceName, actionName),
subType,
});
eventConfig.internalEvent = true;
this.#basicEventTransformation(eventConfig);
this.#validateAdHocEvents(this.#eventMap, eventConfig, false);
this.#basicEventTransformationAfterValidate(eventConfig);
this.#config.events.push(eventConfig);
this.#eventMap[this.generateKey(CAP_EVENT_TYPE, subType)] = eventConfig;
return eventConfig;
}
#unblockEventLocalState(key, tenant) {
const map = this.#blockedEvents[key];
if (!map) {
return;
}
this.#blockedEvents[key][tenant] = false;
return key;
}
isEventBlocked(type, subType, isPeriodicEvent, tenant) {
const map = this.#blockedEvents[this.generateKey(`${type}${isPeriodicEvent ? SUFFIX_PERIODIC : ""}`, subType)];
if (!map) {
return false;
}
const tenantSpecific = map[tenant];
const allTenants = map["*"];
return tenantSpecific ?? allTenants;
}
get isEventQueueActive() {
return this.#isEventQueueActive;
}
set isEventQueueActive(value) {
this.#isEventQueueActive = value;
}
mixFileContentWithEnv(fileContent) {
fileContent.events ??= [];
fileContent.periodicEvents ??= [];
const events = this.#configEvents ?? {};
const periodicEvents = this.#configPeriodicEvents ?? {};
const periodicCapServiceEvents = this.#cdsPeriodicOutboxServicesFromEnv();
fileContent.events = fileContent.events.concat(this.#mapEnvEvents(events));
fileContent.periodicEvents = fileContent.periodicEvents
.concat(this.#mapEnvEvents(periodicEvents))
.concat(this.#mapCapOutboxPeriodicEvent(periodicCapServiceEvents));
this.fileContent = fileContent;
}
#mapCapOutboxPeriodicEvent(periodicEventMap) {
return Object.values(periodicEventMap);
}
#cdsPeriodicOutboxServicesFromEnv() {
return Object.entries(cds.env.requires).reduce((result, [name, value]) => {
if (value.outbox?.events) {
for (const fnName in value.outbox.events) {
const base = { ...value.outbox };
const fnConfig = value.outbox.events[fnName];
if (fnConfig.interval || fnConfig.cron) {
if ("interval" in base || "cron" in base) {
this.#logger.error(
"The properties interval|cron must be defined in the event section and will be ignored in the outbox section.",
{ serviceName: name }
);
delete base.cron;
delete base.interval;
}
const subType = `${name}.${fnName}`;
result[subType] = Object.assign(
{
type: CAP_EVENT_TYPE,
subType,
impl: "./outbox/EventQueueGenericOutboxHandler",
internalEvent: true,
},
base,
fnConfig
);
}
}
}
return result;
}, {});
}
getCdsOutboxEventSpecificConfig(serviceName, action) {
if (cds.env.requires[serviceName]?.outbox?.events?.[action]) {
return cds.env.requires[serviceName].outbox.events[action];
} else {
return null;
}
}
#mapEnvEvents(events) {
return Object.entries(events)
.map(([key, event]) => {
if (!event) {
return;
}
const [type, subType] = key.split("/");
event.type ??= type;
event.subType ??= subType;
return { ...event };
})
.filter((a) => a);
}
set fileContent(config) {
config.events = config.events ?? [];
config.periodicEvents = config.periodicEvents ?? [];
this.#eventMap = config.events.reduce((result, event) => {
const eventSanitized = this.#sanitizeParamsAdHocEvent(event);
this.#basicEventTransformation(eventSanitized);
this.#validateAdHocEvents(result, eventSanitized);
this.#basicEventTransformationAfterValidate(eventSanitized);
result[this.generateKey(eventSanitized.type, eventSanitized.subType)] = eventSanitized;
return result;
}, {});
this.#eventMap = config.periodicEvents.reduce((result, event) => {
const eventSanitized = this.#sanitizeParamsPeriodicEventEvent(event);
eventSanitized.type = `${eventSanitized.type}${SUFFIX_PERIODIC}`;
eventSanitized.isPeriodic = true;
this.#basicEventTransformation(eventSanitized);
this.#validatePeriodicConfig(result, eventSanitized);
this.#basicEventTransformationAfterValidate(eventSanitized);
result[this.generateKey(eventSanitized.type, eventSanitized.subType)] = eventSanitized;
return result;
}, this.#eventMap);
this.#config = config;
}
#basicEventTransformation(event) {
event.load = event.load ?? DEFAULT_LOAD;
event.priority = event.priority ?? DEFAULT_PRIORITY;
event.increasePriorityOverTime = event.increasePriorityOverTime ?? DEFAULT_INCREASE_PRIORITY;
event.keepAliveInterval = event.keepAliveInterval ?? DEFAULT_KEEP_ALIVE_INTERVAL;
event.keepAliveMaxInProgressTime = event.keepAliveInterval * DEFAULT_MAX_FACTOR_STUCK_2_KEEP_ALIVE_INTERVAL;
event.checkForNextChunk = event.checkForNextChunk ?? DEFAULT_CHECK_FOR_NEXT_CHUNK;
}
#sanitizeParamsBase(config, allowList) {
return Object.entries(config).reduce((result, [name, value]) => {
if (allowList.includes(name)) {
result[name] = value;
}
return result;
}, {});
}
#sanitizeParamsAdHocEvent(config) {
return this.#sanitizeParamsBase(config, ALLOWED_EVENT_OPTIONS_AD_HOC);
}
#sanitizeParamsPeriodicEventEvent(config) {
return this.#sanitizeParamsBase(config, ALLOWED_EVENT_OPTIONS_PERIODIC_EVENT);
}
#basicEventTransformationAfterValidate(event) {
event._appNameMap = event.appNames
? Object.fromEntries(new Map(event.appNames.map((a) => [a, this.#parseRegexOrString(a)])))
: null;
event._appNameContainsRegex = event.appNames
? event.appNames.some((appName) => this.#parseRegexOrString(appName).type === "regex")
: null;
event._appInstancesMap = event.appInstances
? Object.fromEntries(new Map(event.appInstances.map((a) => [a, true])))
: null;
}
#basicEventValidation(event) {
if (!event.impl) {
throw EventQueueError.missingImpl(event.type, event.subType);
}
if (!event.type) {
throw EventQueueError.missingType(event);
}
if (!event.subType) {
throw EventQueueError.missingSubType(event);
}
if (event.appNames) {
if (!Array.isArray(event.appNames) || event.appNames.some((appName) => typeof appName !== "string")) {
throw EventQueueError.appNamesFormat(event.type, event.subType, event.appNames);
}
}
if (event.appInstances) {
if (
!Array.isArray(event.appInstances) ||
event.appInstances.some((appInstance) => typeof appInstance !== "number")
) {
throw EventQueueError.appInstancesFormat(event.type, event.subType, event.appInstances);
}
}
if (!PRIORITIES.includes(event.priority)) {
throw EventQueueError.priorityNotAllowed(event.priority, "initEvent");
}
if (event.load > this.#instanceLoadLimit) {
throw EventQueueError.loadHigherThanLimit(event.load, "initEvent");
}
}
#validatePeriodicConfig(eventMap, event) {
const key = this.generateKey(event.type, event.subType);
if (eventMap[key] && eventMap[key].isPeriodic) {
throw EventQueueError.duplicateEventRegistration(event.type, event.subType);
}
if (!event.cron && !event.interval) {
throw EventQueueError.noCronOrInterval(event.type, event.subType);
}
if (event.cron && event.interval) {
throw EventQueueError.cronAndInterval(event.type, event.subType);
}
if (event.cron) {
let cron;
// NOTE: logic is as follows:
// - if event.utc is true --> always use UTC (default is false)
// - if event.useCronTimezone is false OR event.cronTimezone is not defined --> use UTC as well
// - if event.utc is not true AND event.cronTimezone is set AND event.useCronTimezone is NOT set to false use event.cronTimezone
event.utc = event.utc ?? UTC_DEFAULT;
if (!this.cronTimezone) {
event.useCronTimezone = false;
} else {
event.useCronTimezone = event.useCronTimezone ?? USE_CRON_TZ_DEFAULT;
}
event.tz = event.utc || !event.useCronTimezone ? "UTC" : this.cronTimezone;
try {
cron = CronExpressionParser.parse(event.cron);
} catch {
throw EventQueueError.cantParseCronExpression(event.type, event.subType, event.cron);
}
const next = cron.next();
const afterNext = cron.next();
const diffInSeconds = (afterNext.getTime() - next.getTime()) / 1000;
if (diffInSeconds <= MIN_INTERVAL_SEC) {
throw EventQueueError.invalidIntervalBetweenCron(event.type, event.subType, diffInSeconds);
}
return this.#basicEventValidation(event);
}
if (!event.interval || event.interval <= MIN_INTERVAL_SEC) {
throw EventQueueError.invalidInterval(event.type, event.subType, event.interval);
}
this.#basicEventValidation(event);
}
#validateAdHocEvents(eventMap, event, checkForDuplication = true) {
const key = this.generateKey(event.type, event.subType);
if (eventMap[key] && !eventMap[key].isPeriodic && checkForDuplication) {
throw EventQueueError.duplicateEventRegistration(event.type, event.subType);
}
if (this.isMultiTenancy && event.multiInstanceProcessing) {
throw EventQueueError.multiInstanceProcessingNotAllowed(event.type, event.subType);
}
event.inheritTraceContext = event.inheritTraceContext ?? DEFAULT_INHERIT_TRACE_CONTEXT;
if (event.timeBucket) {
try {
CronExpressionParser.parse(event.timeBucket);
} catch {
throw EventQueueError.cantParseCronExpression(event.type, event.subType, event.timeBucket);
}
}
this.#basicEventValidation(event);
}
generateKey(type, subType) {
return [type, subType].join("##");
}
removeEvent(type, subType) {
const index = this.#config.events.findIndex((event) => event.type === "CAP_OUTBOX");
if (index >= 0) {
this.#config.events.splice(index, 1);
}
delete this.#eventMap[this.generateKey(type, subType)];
}
isTenantUnsubscribed(tenantId) {
return this.#unsubscribedTenants[tenantId];
}
get fileContent() {
return this.#config;
}
get events() {
return Object.values(this.#eventMap).filter((e) => !e.isPeriodic);
}
set configEvents(value) {
this.#configEvents = JSON.parse(JSON.stringify(value));
}
get hasConfigEvents() {
return !!(Object.keys(this.#configEvents ?? {}).length || Object.keys(this.#configPeriodicEvents ?? {}).length);
}
set configPeriodicEvents(value) {
this.#configPeriodicEvents = JSON.parse(JSON.stringify(value));
}
get periodicEvents() {
return Object.values(this.#eventMap).filter((e) => e.isPeriodic);
}
isPeriodicEvent(type, subType) {
return this.#eventMap[this.generateKey(type, subType)]?.isPeriodic;
}
get allEvents() {
return Object.values(this.#eventMap);
}
get forUpdateTimeout() {
return this.#forUpdateTimeout;
}
get globalTxTimeout() {
return this.#globalTxTimeout;
}
set forUpdateTimeout(value) {
this.#forUpdateTimeout = value;
}
get publishEventBlockList() {
return this.#publishEventBlockList;
}
set publishEventBlockList(value) {
this.#publishEventBlockList = value;
}
get crashOnRedisUnavailable() {
return this.#crashOnRedisUnavailable;
}
set crashOnRedisUnavailable(value) {
this.#crashOnRedisUnavailable = value;
}
get tenantIdFilterTokenInfo() {
return this.#tenantIdFilterTokenInfoCb;
}
set tenantIdFilterTokenInfo(value) {
this.#tenantIdFilterTokenInfoCb = value;
}
get tenantIdFilterEventProcessing() {
return this.#tenantIdFilterEventProcessingCb;
}
set tenantIdFilterEventProcessing(value) {
this.#tenantIdFilterEventProcessingCb = value;
}
set globalTxTimeout(value) {
this.#globalTxTimeout = value;
}
get runInterval() {
return this.#runInterval;
}
set runInterval(value) {
if (!Number.isInteger(value) || value <= 10 * 1000) {
throw EventQueueError.invalidInterval();
}
this.#runInterval = value;
}
get redisEnabled() {
return this.#redisEnabled;
}
set redisEnabled(value) {
this.#redisEnabled = value;
}
get initialized() {
return this.#initialized;
}
set initialized(value) {
this.#initialized = value;
}
get cronTimezone() {
return this.#cronTimezone;
}
set cronTimezone(value) {
this.#cronTimezone = value;
}
get randomOffsetPeriodicEvents() {
return this.#randomOffsetPeriodicEvents;
}
set randomOffsetPeriodicEvents(value) {
this.#randomOffsetPeriodicEvents = value;
}
get instanceLoadLimit() {
return this.#instanceLoadLimit;
}
set instanceLoadLimit(value) {
this.#instanceLoadLimit = value;
}
get isEventBlockedCb() {
return this.#isEventBlockedCb;
}
set isEventBlockedCb(value) {
this.#isEventBlockedCb = value;
}
get tableNameEventQueue() {
return BASE_TABLES.EVENT;
}
get tableNameEventLock() {
return BASE_TABLES.LOCK;
}
set configFilePath(value) {
this.#configFilePath = value;
}
get configFilePath() {
return this.#configFilePath;
}
set processEventsAfterPublish(value) {
this.#processEventsAfterPublish = value;
}
get processEventsAfterPublish() {
return this.#processEventsAfterPublish;
}
set disableRedis(value) {
this.#disableRedis = value;
}
get disableRedis() {
return this.#disableRedis;
}
set updatePeriodicEvents(value) {
this.#updatePeriodicEvents = value;
}
get updatePeriodicEvents() {
return this.#updatePeriodicEvents;
}
set registerAsEventProcessor(value) {
this.#registerAsEventProcessor = value;
}
get registerAsEventProcessor() {
return this.#registerAsEventProcessor;
}
set thresholdLoggingEventProcessing(value) {
this.#thresholdLoggingEventProcessing = value;
}
get thresholdLoggingEventProcessing() {
return this.#thresholdLoggingEventProcessing;
}
set useAsCAPOutbox(value) {
this.#useAsCAPOutbox = value;
}
get useAsCAPOutbox() {
return this.#useAsCAPOutbox;
}
set userId(value) {
this.#userId = value;
}
get userId() {
return this.#userId;
}
set cleanupLocksAndEventsForDev(value) {
this.#cleanupLocksAndEventsForDev = value;
}
get cleanupLocksAndEventsForDev() {
return this.#cleanupLocksAndEventsForDev;
}
set redisOptions(value) {
this.#redisOptions = value;
}
get redisOptions() {
return {
...this.#redisOptions,
redisNamespace: `${[REDIS_PREFIX, this.redisNamespace].filter((a) => a).join("_")}`,
};
}
set redisNamespace(value) {
this.#redisNamespace = value;
}
get redisNamespace() {
return this.#redisNamespace;
}
set insertEventsBeforeCommit(value) {
this.#insertEventsBeforeCommit = value;
}
get insertEventsBeforeCommit() {
return this.#insertEventsBeforeCommit;
}
set enableTelemetry(value) {
this.#enableTelemetry = value;
}
get enableTelemetry() {
return this.#enableTelemetry;
}
get isMultiTenancy() {
return !!cds.requires.multitenancy;
}
get _rawEventMap() {
return this.#eventMap;
}
get developmentMode() {
return cds.env.profiles.find((profile) => profile === "development");
}
get enableAdminService() {
return this.#enableAdminService;
}
set enableAdminService(value) {
this.#enableAdminService = value;
}
/**
@return { Config }
**/
static get instance() {
if (!Config.#instance) {
Config.#instance = new Config();
}
return Config.#instance;
}
}
const instance = Config.instance;
module.exports = instance;