@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.
940 lines (790 loc) • 27 kB
JavaScript
"use strict";
const cds = require("@sap/cds");
const { CronExpressionParser } = require("cron-parser");
const { getEnvInstance } = require("./shared/env");
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 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 DEFAULT_RETRY_AFTER = 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",
"namespace",
"internalEvent",
];
const ALLOWED_EVENT_OPTIONS_AD_HOC = [
...ALLOWED_EVENT_OPTIONS_BASE,
"inheritTraceContext",
"selectMaxChunkSize",
"parallelEventProcessing",
"retryAttempts",
"processAfterCommit",
"checkForNextChunk",
"retryFailedAfter",
"propagateHeaders",
"retryOpenAfter",
"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;
#isEventBlockedCb;
#thresholdLoggingEventProcessing;
#useAsCAPOutbox;
#userId;
#cleanupLocksAndEventsForDev;
#redisOptions;
#insertEventsBeforeCommit;
#enableTelemetry;
#unsubscribeHandlers = [];
#unsubscribedTenants = {};
#cronTimezone;
#randomOffsetPeriodicEvents;
#crashOnRedisUnavailable;
#tenantIdFilterAuthContextCb;
#tenantIdFilterEventProcessingCb;
#configEvents;
#configPeriodicEvents;
#enableAdminService;
#disableProcessingOfSuspendedTenants;
#namespace;
#processingNamespaces;
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();
}
getEventConfig(type, subType, namespace = this.namespace) {
return this.#eventMap[this.generateKey(namespace, type, subType)]
? { ...this.#eventMap[this.generateKey(namespace, type, subType)] }
: undefined;
}
isCapOutboxEvent(type) {
return [CAP_EVENT_TYPE, [CAP_EVENT_TYPE, SUFFIX_PERIODIC].join("")].includes(type);
}
hasEventAfterCommitFlag(type, subType, namespace = this.namespace) {
return this.#eventMap[this.generateKey(namespace, 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(type, rawSubType) {
if (![CAP_EVENT_TYPE, [CAP_EVENT_TYPE, SUFFIX_PERIODIC].join("")].includes(type)) {
return { subType: rawSubType };
}
const serviceParts = rawSubType.split(".");
let srvName = serviceParts.shift();
while (!cds.env.requires[srvName] && serviceParts.length) {
srvName = [srvName, serviceParts.shift()].join(".");
}
const actionName = serviceParts.shift();
const actionSpecificCall = this.getCdsOutboxEventSpecificConfig(srvName, actionName);
return {
subType: actionSpecificCall ? rawSubType : srvName,
actionName,
srvName,
};
}
shouldBeProcessedInThisApplication(type, rawSubType, namespace = this.namespace) {
const { subType } = this.normalizeSubType(type, rawSubType);
const config = this.#eventMap[this.generateKey(namespace, 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;
}
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,
});
}
}
}
attachUnsubscribeHandler(cb) {
this.#unsubscribeHandlers.push(cb);
}
addCAPOutboxEventBase(serviceName, config, currentEvents) {
const namespace = config.namespace ?? this.#namespace;
// NOTE: CAP outbox defaults are injected by cds.requires.outbox // cds.requires.queue
const eventConfig = this.#sanitizeParamsAdHocEvent({
type: CAP_EVENT_TYPE,
subType: serviceName,
impl: "./outbox/EventQueueGenericOutboxHandler",
kind: config.kind ?? "persistent-queue",
...this.#mixCAPPropertyNamesWithEventQueueNames(config),
namespace,
...config,
});
eventConfig.retryAttempts ??= CAP_MAX_ATTEMPTS_DEFAULT;
eventConfig.internalEvent = true;
this.#basicEventTransformation(eventConfig);
this.#validateAdHocEvents(currentEvents, eventConfig, false);
this.#basicEventTransformationAfterValidate(eventConfig);
return [this.generateKey(namespace, CAP_EVENT_TYPE, serviceName), eventConfig];
}
#mixCAPPropertyNamesWithEventQueueNames(config) {
return this.#cleanUndefined({
selectMaxChunkSize: config.selectMaxChunkSize ?? config.chunkSize,
parallelEventProcessing: config.parallelEventProcessing ?? (config.parallel && CAP_PARALLEL_DEFAULT),
retryAttempts: config.retryAttempts ?? config.maxAttempts,
...config,
});
}
addCAPOutboxEventSpecificAction(baseServiceConfig, serviceName, actionName, currentEvents) {
const subType = [serviceName, actionName].join(".");
const eventConfig = this.#sanitizeParamsAdHocEvent({
...baseServiceConfig,
...this.getCdsOutboxEventSpecificConfig(serviceName, actionName),
subType,
});
eventConfig.internalEvent = true;
this.#basicEventTransformation(eventConfig);
this.#validateAdHocEvents(currentEvents, eventConfig, false);
this.#basicEventTransformationAfterValidate(eventConfig);
return [this.generateKey(eventConfig.namespace, CAP_EVENT_TYPE, subType), eventConfig];
}
findBaseCAPServiceWithoutNamespace(serviceName) {
return Object.values(this.#eventMap).find(
(event) => event.type === CAP_EVENT_TYPE && event.subType === serviceName
);
}
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 { adHoc: adHocCAP, periodicEvents: periodicEventsCAP } = this.#cdsQueueEventsFromEnv();
fileContent.events = fileContent.events.concat(this.#mapEnvEvents(events)).concat(Object.values(adHocCAP));
fileContent.periodicEvents = fileContent.periodicEvents
.concat(this.#mapEnvEvents(periodicEvents))
.concat(Object.values(periodicEventsCAP));
this.fileContent = fileContent;
}
#getBasicCapOutboxEventConfig(serviceName) {
return Object.assign(
{},
(typeof cds.requires.outbox === "object" && cds.requires.outbox) || {},
(typeof cds.requires.queue === "object" && cds.requires.queue) || {},
(typeof cds.env.requires[serviceName]?.outbox === "object" && cds.env.requires[serviceName].outbox) || {},
(typeof cds.env.requires[serviceName]?.queued === "object" && cds.env.requires[serviceName].queued) || {}
);
}
#cdsQueueEventsFromEnv() {
return Object.entries(cds.env.requires).reduce(
(result, [name, value]) => {
const config = value.outbox ?? value.queued;
if (!config) {
return result;
}
// make sure service is known in general
// only if not known
const basicServiceConfig = this.#getBasicCapOutboxEventConfig(name);
const [key, srvConfig] = this.addCAPOutboxEventBase(name, basicServiceConfig, result.adHoc);
result.adHoc[key] = srvConfig;
for (const fnName in config.events) {
const base = { ...config };
const fnConfig = config.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.periodicEvents[subType] = Object.assign(
{
type: CAP_EVENT_TYPE,
subType,
impl: "./outbox/EventQueueGenericOutboxHandler",
internalEvent: true,
},
base,
fnConfig
);
} else {
const [key, specificEventConfig] = this.addCAPOutboxEventSpecificAction(
srvConfig,
name,
fnName,
result.adHoc
);
result.adHoc[key] = specificEventConfig;
}
}
return result;
},
{ adHoc: {}, periodicEvents: {} }
);
}
addCAPServiceWithoutEnvConfig(serviceName, srv, customOpts = {}) {
const queueOptions = srv.options.queued ?? srv.options.outbox ?? {};
const basicServiceConfig = this.#getBasicCapOutboxEventConfig(serviceName);
const [key, srvConfig] = this.addCAPOutboxEventBase(
serviceName,
{ ...basicServiceConfig, ...queueOptions, ...customOpts },
this.#eventMap
);
this.#eventMap[key] = srvConfig;
}
getCdsOutboxEventSpecificConfig(serviceName, action) {
const srv = cds.env.requires[serviceName];
const config = srv?.outbox ?? srv?.queued;
if (config?.events?.[action]) {
return this.#mixCAPPropertyNamesWithEventQueueNames(config.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.namespace, 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.namespace, 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;
event.namespace = event.namespace === undefined ? this.#namespace : event.namespace;
}
#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;
event.propagateHeaders = event.propagateHeaders ?? [];
event.retryFailedAfter = event.retryFailedAfter ?? DEFAULT_RETRY_AFTER;
event.retryOpenAfter = event.retryOpenAfter ?? DEFAULT_RETRY_AFTER;
}
#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.namespace, 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.namespace, 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(namespace, type, subType) {
return [namespace, type, subType].join("##");
}
removeEvent(type, subType, namespace = this.namespace) {
delete this.#eventMap[this.generateKey(namespace, type, subType)];
}
isTenantUnsubscribed(tenantId) {
return this.#unsubscribedTenants[tenantId];
}
shouldProcessNamespace(namespace) {
return this.#processingNamespaces.includes(namespace);
}
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, namespace = this.namespace) {
return this.#eventMap[this.generateKey(namespace, 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 crashOnRedisUnavailable() {
return this.#crashOnRedisUnavailable;
}
set crashOnRedisUnavailable(value) {
this.#crashOnRedisUnavailable = value;
}
get tenantIdFilterAuthContext() {
return this.#tenantIdFilterAuthContextCb;
}
set tenantIdFilterAuthContext(value) {
this.#tenantIdFilterAuthContextCb = 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) {
if (this.#useAsCAPOutbox) {
return;
}
this.#useAsCAPOutbox = value;
}
get useAsCAPOutbox() {
return this.#useAsCAPOutbox;
}
set useAsCAPQueue(value) {
this.useAsCAPOutbox = value;
}
get useAsCAPQueue() {
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(addPublishNamespace = true) {
return addPublishNamespace ? `${[REDIS_PREFIX, this.#namespace].filter((a) => a).join("##")}` : REDIS_PREFIX;
}
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;
}
get disableProcessingOfSuspendedTenants() {
return this.#disableProcessingOfSuspendedTenants;
}
set disableProcessingOfSuspendedTenants(value) {
this.#disableProcessingOfSuspendedTenants = value;
}
get namespace() {
return this.#namespace;
}
set namespace(value) {
this.#namespace = value;
}
get processingNamespaces() {
return this.#processingNamespaces;
}
set processingNamespaces(value) {
this.#processingNamespaces = value;
}
#cleanUndefined(input) {
return Object.entries(input).reduce((acc, [key, value]) => {
if (value !== undefined) {
acc[key] = value;
}
return acc;
}, {});
}
/**
@return { Config }
**/
static get instance() {
if (!Config.#instance) {
Config.#instance = new Config();
}
return Config.#instance;
}
}
const instance = Config.instance;
module.exports = instance;