@azure/event-hubs
Version:
Azure Event Hubs SDK for JS.
432 lines (431 loc) • 15.8 kB
JavaScript
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var partitionReceiver_exports = {};
__export(partitionReceiver_exports, {
checkOnInterval: () => checkOnInterval,
createQueueSignal: () => createQueueSignal,
createReceiver: () => createReceiver,
waitForEvents: () => waitForEvents
});
module.exports = __toCommonJS(partitionReceiver_exports);
var import_abort_controller = require("@azure/abort-controller");
var import_core_amqp = require("@azure/core-amqp");
var import_rhea_promise = require("rhea-promise");
var import_eventData = require("./eventData.js");
var import_eventPosition = require("./eventPosition.js");
var import_logger = require("./logger.js");
var import_retries = require("./util/retries.js");
var import_core_util = require("@azure/core-util");
var import_utils = require("./util/utils.js");
var import_withAuth = require("./withAuth.js");
var import_constants = require("./util/constants.js");
const abortLogMessage = "operation has been cancelled by the user";
const qReadIntervalInMs = 20;
function createReceiver(ctx, consumerGroup, consumerId, partitionId, eventPosition, options = {}) {
const address = ctx.config.getReceiverAddress(partitionId, consumerGroup);
const name = (0, import_utils.getRandomName)(address);
const audience = ctx.config.getReceiverAudience(partitionId, consumerGroup);
const logPrefix = (0, import_logger.createReceiverLogPrefix)(consumerId, ctx.connectionId, partitionId);
const logger = (0, import_logger.createSimpleLogger)(import_logger.logger, logPrefix);
const queue = [];
const queueSignal = createQueueSignal();
const state = {
isConnecting: false
};
const obj = {
_onError: void 0,
checkpoint: -1,
lastEnqueuedEventProperties: {},
isClosed: false,
close: async () => {
clearHandlers(obj);
delete ctx.receivers[name];
logger.verbose("deleted the receiver from the client cache");
state.authLoop?.stop();
return state.link?.close().catch((err) => {
logger.warning(`an error occurred while closing: ${err?.name}: ${err?.message}`);
(0, import_logger.logErrorStackTrace)(err);
throw err;
}).finally(() => {
obj.isClosed = true;
logger.verbose("is closed");
state.link = void 0;
state.authLoop = void 0;
});
},
abort: () => {
obj._onError?.(new import_abort_controller.AbortError(import_core_amqp.StandardAbortMessage));
logger.info(abortLogMessage);
return obj.close();
},
isOpen: () => {
const isOpen = !!state.link?.isOpen();
logger.verbose(`is open? -> ${isOpen}`);
return isOpen;
},
async connect({ abortSignal, timeoutInMs }) {
if (state.isConnecting || obj.isOpen()) {
return;
}
state.isConnecting = true;
logger.verbose("is trying to connect");
try {
await ctx.readyToOpenLink({ abortSignal });
state.authLoop = await (0, import_withAuth.withAuth)(
() => setupLink(
consumerId,
ctx,
name,
address,
obj,
state,
queue,
queueSignal,
eventPosition,
logger,
options,
abortSignal
),
ctx,
audience,
timeoutInMs,
logger,
{
abortSignal
}
);
} catch (err) {
state.isConnecting = false;
const error = (0, import_core_amqp.translate)(err);
logger.error(
`an error occurred while creating the receiver: ${error?.name}: ${error?.message}`
);
(0, import_logger.logErrorStackTrace)(err);
throw error;
}
},
receiveBatch: (maxMessageCount, maxWaitTimeInSeconds = 60, abortSignal) => {
const prefetchCount = options.prefetchCount ?? maxMessageCount * 3;
const cleanupBeforeAbort = () => {
logger.info(abortLogMessage);
return obj.close();
};
const retrieveEvents = () => {
const eventsToRetrieveCount = Math.max(maxMessageCount - queue.length, 0);
logger.verbose(
`already has ${queue.length} events and wants to receive ${eventsToRetrieveCount} more events`
);
if (abortSignal?.aborted) {
cleanupBeforeAbort().catch((err) => {
logger.verbose(`error during cleanup after abort: ${(0, import_logger.logObj)(err)}`);
});
return Promise.reject(new import_abort_controller.AbortError(import_core_amqp.StandardAbortMessage));
}
return obj.isClosed || ctx.wasConnectionCloseCalled || eventsToRetrieveCount === 0 ? Promise.resolve(queue.splice(0, maxMessageCount)) : new Promise((resolve, reject) => {
obj._onError = reject;
obj.connect({
abortSignal,
timeoutInMs: (0, import_retries.getRetryAttemptTimeoutInMs)(options.retryOptions)
}).then(() => {
addCredits(state.link, Math.max(prefetchCount, maxMessageCount) - queue.length);
logger.verbose(`setting the max wait time to ${maxWaitTimeInSeconds} seconds`);
return waitForEvents(
maxMessageCount,
maxWaitTimeInSeconds * 1e3,
qReadIntervalInMs,
queue,
{
abortSignal,
cleanupBeforeAbort,
receivedAfterWait: () => logger.info(
`${Math.min(
maxMessageCount,
queue.length
)} messages received within ${maxWaitTimeInSeconds} seconds`
),
receivedAlready: () => logger.info(`${maxMessageCount} messages already received`),
receivedNone: () => logger.info(
`no messages received when max wait time in seconds ${maxWaitTimeInSeconds} is over`
),
queueSignal
}
);
}).catch(reject).then(resolve);
}).then(() => queue.splice(0, maxMessageCount)).finally(() => clearHandlers(obj));
};
return (0, import_core_amqp.retry)(
Object.defineProperties(
{
operation: retrieveEvents,
operationType: import_core_amqp.RetryOperationType.receiveMessage,
abortSignal,
retryOptions: options.retryOptions ?? {}
},
{
connectionId: {
enumerable: true,
get: () => ctx.connectionId
},
connectionHost: {
enumerable: true,
get: () => ctx.config.host
}
}
)
);
}
};
return obj;
}
function delay(waitTimeInMs, options) {
let token;
return (0, import_core_util.createAbortablePromise)((resolve) => {
token = setTimeout(resolve, waitTimeInMs);
}, options).finally(() => clearTimeout(token));
}
function checkOnInterval(waitTimeInMs, check, options) {
let token;
return (0, import_core_util.createAbortablePromise)((resolve) => {
token = setInterval(() => {
if (check()) {
resolve();
}
}, waitTimeInMs);
}, options).finally(() => clearInterval(token));
}
function createQueueSignal() {
const waiters = /* @__PURE__ */ new Set();
return {
notify() {
for (const resolve of waiters) {
resolve();
}
waiters.clear();
},
wait(options) {
let resolveWaiter;
return (0, import_core_util.createAbortablePromise)((resolve) => {
resolveWaiter = () => {
waiters.delete(resolveWaiter);
resolve();
};
waiters.add(resolveWaiter);
}, options).finally(() => {
if (resolveWaiter) {
waiters.delete(resolveWaiter);
}
});
}
};
}
function waitForEvents(maxEventCount, maxWaitTimeInMs, readIntervalWaitTimeInMs, queue, options = {}) {
const {
abortSignal: clientAbortSignal,
cleanupBeforeAbort,
receivedNone,
receivedAfterWait,
receivedAlready,
queueSignal
} = options;
if (queue.length >= maxEventCount) {
return Promise.resolve().then(receivedAlready);
}
const aborter = new AbortController();
const abortListener = () => {
aborter.abort();
};
clientAbortSignal?.addEventListener("abort", abortListener);
let cleanupBeforeAbortCalled = false;
const updatedOptions = {
abortSignal: aborter.signal,
abortErrorMsg: import_core_amqp.StandardAbortMessage,
cleanupBeforeAbort: () => {
if (clientAbortSignal?.aborted && !cleanupBeforeAbortCalled) {
Promise.resolve(cleanupBeforeAbort?.()).catch((err) => {
import_logger.logger.verbose("error during cleanup after abort:", err);
});
cleanupBeforeAbortCalled = true;
}
}
};
const waitForMessage = queue.length > 0 ? Promise.resolve() : queueSignal ? queueSignal.wait(updatedOptions) : checkOnInterval(readIntervalWaitTimeInMs, () => queue.length > 0, updatedOptions);
return Promise.race([
waitForMessage.then(() => delay(readIntervalWaitTimeInMs, updatedOptions)).then(receivedAfterWait),
delay(maxWaitTimeInMs, updatedOptions).then(receivedNone)
]).finally(() => {
aborter.abort();
clientAbortSignal?.removeEventListener("abort", abortListener);
});
}
function convertAMQPMesage(data) {
const rawMessage = data.getRawAmqpMessage();
const receivedEventData = {
body: data.body,
properties: data.properties,
offset: data.offset,
sequenceNumber: data.sequenceNumber,
enqueuedTimeUtc: data.enqueuedTimeUtc,
partitionKey: data.partitionKey,
systemProperties: data.systemProperties,
getRawAmqpMessage() {
return rawMessage;
}
};
if (data.correlationId != null) {
receivedEventData.correlationId = data.correlationId;
}
if (data.contentType != null) {
receivedEventData.contentType = data.contentType;
}
if (data.messageId != null) {
receivedEventData.messageId = data.messageId;
}
return receivedEventData;
}
function setEventProps(eventProps, data) {
eventProps.sequenceNumber = data.lastSequenceNumber;
eventProps.enqueuedOn = data.lastEnqueuedTime;
eventProps.offset = data.lastEnqueuedOffset;
eventProps.retrievedOn = data.retrievalTime;
}
function clearHandlers(obj) {
obj._onError = void 0;
}
function onMessage(context, obj, queue, queueSignal, options) {
if (!context.message) {
return;
}
const data = (0, import_eventData.fromRheaMessage)(context.message, !!options.skipParsingBodyAsJson);
const receivedEventData = convertAMQPMesage(data);
obj.checkpoint = receivedEventData.sequenceNumber;
if (options.trackLastEnqueuedEventProperties) {
setEventProps(obj.lastEnqueuedEventProperties, data);
}
queue.push(receivedEventData);
queueSignal.notify();
}
function onError(context, obj, receiver, logger) {
const rheaReceiver = receiver || context.receiver;
const amqpError = rheaReceiver?.error;
logger.verbose(`'receiver_error' event occurred: ${(0, import_logger.logObj)(amqpError)}`);
if (obj._onError && amqpError) {
const error = (0, import_core_amqp.translate)(amqpError);
(0, import_logger.logErrorStackTrace)(error);
obj._onError(error);
}
}
function onSessionError(context, obj, logger) {
const sessionError = context.session?.error;
logger.verbose(`'session_error' event occurred: ${(0, import_logger.logObj)(sessionError)}`);
if (obj._onError && sessionError) {
const error = (0, import_core_amqp.translate)(sessionError);
(0, import_logger.logErrorStackTrace)(error);
obj._onError(error);
}
}
function onClose(context, state, logger) {
const rheaReceiver = state.link || context.receiver;
logger.verbose(
`'receiver_close' event occurred. Value for isItselfClosed on the receiver is: '${rheaReceiver?.isItselfClosed().toString()}' Value for isConnecting on the session is: '${state.isConnecting}'`
);
if (rheaReceiver && !state.isConnecting) {
rheaReceiver.close().catch((err) => {
logger.verbose(`error when closing after 'receiver_close' event: ${(0, import_logger.logObj)(err)}`);
});
}
}
function onSessionClose(context, state, logger) {
const rheaReceiver = state.link || context.receiver;
logger.verbose(
`'session_close' event occurred. Value for isSessionItselfClosed on the session is: '${rheaReceiver?.isSessionItselfClosed().toString()}' Value for isConnecting on the session is: '${state.isConnecting}'`
);
if (rheaReceiver && !state.isConnecting) {
rheaReceiver.close().catch((err) => {
logger.verbose(`error when closing after 'session_close' event: ${(0, import_logger.logObj)(err)}`);
});
}
}
function createRheaOptions(consumerId, name, address, obj, state, queue, queueSignal, eventPosition, logger, options) {
const rheaOptions = {
name,
autoaccept: true,
target: consumerId,
source: {
address
},
credit_window: 0,
properties: {
[import_constants.receiverIdPropertyName]: consumerId
},
onClose: (context) => onClose(context, state, logger),
onSessionClose: (context) => onSessionClose(context, state, logger),
onError: (context) => onError(context, obj, state.link, logger),
onMessage: (context) => onMessage(context, obj, queue, queueSignal, options),
onSessionError: (context) => onSessionError(context, obj, logger)
};
const ownerLevel = options.ownerLevel;
if (typeof ownerLevel === "number") {
rheaOptions.properties[import_core_amqp.Constants.attachEpoch] = import_rhea_promise.types.wrap_long(ownerLevel);
}
rheaOptions.desired_capabilities = [import_constants.geoReplication];
if (options.trackLastEnqueuedEventProperties) {
rheaOptions.desired_capabilities.push(import_core_amqp.Constants.enableReceiverRuntimeMetricName);
}
const filterClause = (0, import_eventPosition.getEventPositionFilter)(
obj.checkpoint > -1 ? { sequenceNumber: obj.checkpoint } : eventPosition
);
rheaOptions.source.filter = {
"apache.org:selector-filter:string": import_rhea_promise.types.wrap_described(filterClause, 77567109365764)
};
return rheaOptions;
}
async function setupLink(consumerId, ctx, name, address, obj, state, queue, queueSignal, eventPosition, logger, options, abortSignal) {
const rheaOptions = createRheaOptions(
consumerId,
name,
address,
obj,
state,
queue,
queueSignal,
eventPosition,
logger,
options
);
logger.verbose(`trying to be created with options ${(0, import_logger.logObj)(rheaOptions)}`);
state.link = await ctx.connection.createReceiver({
...rheaOptions,
abortSignal
});
state.isConnecting = false;
logger.verbose("is created successfully");
ctx.receivers[name] = obj;
}
function addCredits(receiver, creditsToAdd) {
if (creditsToAdd > 0) {
receiver?.addCredit(creditsToAdd);
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
checkOnInterval,
createQueueSignal,
createReceiver,
waitForEvents
});
//# sourceMappingURL=partitionReceiver.js.map