UNPKG

@azure/event-hubs

Version:
432 lines (431 loc) • 15.8 kB
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