@azure/functions-extensions-servicebus
Version:
Node.js Azure ServiceBus extension implementations for Azure Functions
1,089 lines (1,039 loc) • 68.5 kB
JavaScript
/******/ (() => { // webpackBootstrap
/******/ "use strict";
/******/ var __webpack_modules__ = ({
/***/ "./src/constants/settlementProtoConstant.ts"
/*!**************************************************!*\
!*** ./src/constants/settlementProtoConstant.ts ***!
\**************************************************/
(__unused_webpack_module, exports) {
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License.
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.SETTLEMENT_PROTO_CONTENT = void 0;
/**
* Embedded protobuf definition for the Settlement service.
* This eliminates the need for external .proto files by providing the complete
* proto definition as a string constant.
*/
exports.SETTLEMENT_PROTO_CONTENT = `
syntax = "proto3";
import "google/protobuf/empty.proto";
import "google/protobuf/wrappers.proto";
import "google/protobuf/timestamp.proto";
// this namespace will be shared between isolated worker and WebJobs extension so make it somewhat generic
option csharp_namespace = "Microsoft.Azure.ServiceBus.Grpc";
// The settlement service definition.
service Settlement {
// Completes a message
rpc Complete (CompleteRequest) returns (google.protobuf.Empty) {}
// Abandons a message
rpc Abandon (AbandonRequest) returns (google.protobuf.Empty) {}
// Deadletters a message
rpc Deadletter (DeadletterRequest) returns (google.protobuf.Empty) {}
// Defers a message
rpc Defer (DeferRequest) returns (google.protobuf.Empty) {}
// Renew message lock
rpc RenewMessageLock (RenewMessageLockRequest) returns (google.protobuf.Empty) {}
// Get session state
rpc GetSessionState (GetSessionStateRequest) returns (GetSessionStateResponse) {}
// Set session state
rpc SetSessionState (SetSessionStateRequest) returns (google.protobuf.Empty) {}
// Release session
rpc ReleaseSession (ReleaseSessionRequest) returns (google.protobuf.Empty) {}
// Renew session lock
rpc RenewSessionLock (RenewSessionLockRequest) returns (RenewSessionLockResponse) {}
}
// The complete message request containing the locktoken.
message CompleteRequest {
string locktoken = 1;
}
// The abandon message request containing the locktoken and properties to modify.
message AbandonRequest {
string locktoken = 1;
bytes propertiesToModify = 2;
}
// The deadletter message request containing the locktoken and properties to modify along with the reason/description.
message DeadletterRequest {
string locktoken = 1;
bytes propertiesToModify = 2;
google.protobuf.StringValue deadletterReason = 3;
google.protobuf.StringValue deadletterErrorDescription = 4;
}
// The defer message request containing the locktoken and properties to modify.
message DeferRequest {
string locktoken = 1;
bytes propertiesToModify = 2;
}
// The renew message lock request containing the locktoken.
message RenewMessageLockRequest {
string locktoken = 1;
}
// The get message request.
message GetSessionStateRequest {
string sessionId = 1;
}
// The set message request.
message SetSessionStateRequest {
string sessionId = 1;
bytes sessionState = 2;
}
// Get response containing the session state.
message GetSessionStateResponse {
bytes sessionState = 1;
}
// Release session.
message ReleaseSessionRequest {
string sessionId = 1;
}
// Renew session lock.
message RenewSessionLockRequest {
string sessionId = 1;
}
// Renew session lock.
message RenewSessionLockResponse {
google.protobuf.Timestamp lockedUntil = 1;
}
`;
/***/ },
/***/ "./src/grpcClientFactory.ts"
/*!**********************************!*\
!*** ./src/grpcClientFactory.ts ***!
\**********************************/
(__unused_webpack_module, exports, __webpack_require__) {
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.createGrpcClient = void 0;
const grpc = __importStar(__webpack_require__(/*! @grpc/grpc-js */ "@grpc/grpc-js"));
const protoLoader = __importStar(__webpack_require__(/*! @grpc/proto-loader */ "@grpc/proto-loader"));
const fs = __importStar(__webpack_require__(/*! fs */ "fs"));
const os = __importStar(__webpack_require__(/*! os */ "os"));
const path = __importStar(__webpack_require__(/*! path */ "path"));
const settlementProtoConstant_1 = __webpack_require__(/*! ./constants/settlementProtoConstant */ "./src/constants/settlementProtoConstant.ts");
// Cache for the loaded package definition to avoid repeated parsing
let cachedPackageDefinition = null;
/**
* Creates and returns a gRPC client for the Settlement service using embedded proto definition.
* This approach completely eliminates the need for external .proto files by creating a temporary
* proto file from the embedded content, loading it, and then cleaning up.
*
* @template T - The type of gRPC client to create, extends grpc.Client
*
* @param options - The configuration options for creating the gRPC client
* @param options.serviceName - Name of the service in the proto definition
* @param options.address - The server address to connect to (e.g., "localhost:50051")
* @param options.credentials - gRPC channel credentials to use for secure communication (defaults to insecure)
* @param options.grpcMaxMessageLength - Maximum message length in bytes for both sending and receiving gRPC messages
*
* @returns A new instance of the specified gRPC client
*/
function createGrpcClient({ serviceName, address, credentials = grpc.credentials.createInsecure(), grpcMaxMessageLength, }) {
// Load the embedded proto definition if not already cached
if (!cachedPackageDefinition) {
// Create a temporary proto file from the embedded content
const tempDir = os.tmpdir();
const tempProtoFile = path.join(tempDir, `settlement.proto`);
try {
// Write the proto content to a temporary file
fs.writeFileSync(tempProtoFile, settlementProtoConstant_1.SETTLEMENT_PROTO_CONTENT);
// Load the proto definition from the temporary file
const packageDefinition = protoLoader.loadSync(tempProtoFile, {
keepCase: false,
longs: String,
enums: String,
defaults: true,
oneofs: true,
includeDirs: [tempDir],
});
cachedPackageDefinition = grpc.loadPackageDefinition(packageDefinition);
}
finally {
// Clean up the temporary file
try {
fs.unlinkSync(tempProtoFile);
}
catch (_a) {
// Ignore cleanup errors
}
}
}
// Retrieve the service client constructor from the loaded gRPC object
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
const ServiceClientConstructor = cachedPackageDefinition[serviceName];
// Throw an error if the service is not found
if (!ServiceClientConstructor) {
throw new Error(`Service "${serviceName}" not found in embedded proto definition`);
}
const clientOptions = {
'grpc.max_send_message_length': grpcMaxMessageLength,
'grpc.max_receive_message_length': grpcMaxMessageLength,
};
// Create and return a new instance of the service client
return new ServiceClientConstructor(address, credentials, clientOptions);
}
exports.createGrpcClient = createGrpcClient;
/***/ },
/***/ "./src/servicebus/ServiceBusMessageActions.ts"
/*!****************************************************!*\
!*** ./src/servicebus/ServiceBusMessageActions.ts ***!
\****************************************************/
(__unused_webpack_module, exports, __webpack_require__) {
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
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.ServiceBusMessageActions = void 0;
const grpc = __importStar(__webpack_require__(/*! @grpc/grpc-js */ "@grpc/grpc-js"));
const grpcClientFactory_1 = __webpack_require__(/*! ../grpcClientFactory */ "./src/grpcClientFactory.ts");
const amqpPropertyEncoder_1 = __webpack_require__(/*! ../util/amqpPropertyEncoder */ "./src/util/amqpPropertyEncoder.ts");
const grpcUriBuilder_1 = __webpack_require__(/*! ../util/grpcUriBuilder */ "./src/util/grpcUriBuilder.ts");
// Using the original proto-loader approach with better path resolution
// Client implementation with Promise-based methods
class ServiceBusMessageActions {
constructor() {
const { uri, grpcMaxMessageLength } = grpcUriBuilder_1.GrpcUriBuilder.build();
this.client = (0, grpcClientFactory_1.createGrpcClient)({
serviceName: 'Settlement',
address: uri,
credentials: grpc.credentials.createInsecure(),
grpcMaxMessageLength,
});
}
static getInstance() {
if (!ServiceBusMessageActions.instance) {
ServiceBusMessageActions.instance = new ServiceBusMessageActions();
}
return ServiceBusMessageActions.instance;
}
// Add this private helper method to the ServiceBusMessageActions class
validateLockToken(message) {
const locktoken = message.lockToken;
if (!locktoken) {
throw new Error('ArgumentException: lockToken is required in ServiceBusReceivedMessage.');
}
return locktoken;
}
/**
* Completes (settles) the specified message, removing it from the queue or subscription.
*
* @param message - The received Service Bus message to complete.
* @returns A promise that resolves when the operation is successful.
* @throws Error if the lockToken is missing or the gRPC call fails.
*/
complete(message) {
return __awaiter(this, void 0, void 0, function* () {
const locktoken = this.validateLockToken(message);
return new Promise((resolve, reject) => {
const request = { locktoken };
this.client.complete(request, (error) => {
if (error) {
console.error('Complete request failed:', {
code: error.code,
message: error.message,
details: error.details,
});
reject(error);
}
else {
resolve();
}
});
});
});
}
/**
* Abandons the specified message, making it available again for processing.
*
* @param message - The received Service Bus message to abandon.
* @param propertiesToModify - Optional properties to modify on the message.
* @returns A promise that resolves when the operation is successful.
* @throws Error if the lockToken is missing or the gRPC call fails.
*/
abandon(message, propertiesToModify) {
return __awaiter(this, void 0, void 0, function* () {
const locktoken = this.validateLockToken(message);
const encodedProperties = (0, amqpPropertyEncoder_1.encodePropertiesForOperation)(propertiesToModify, 'abandon');
return new Promise((resolve, reject) => {
const request = {
locktoken,
propertiesToModify: encodedProperties,
};
this.client.abandon(request, (error) => {
if (error) {
console.error('Abandon request failed:', {
code: error.code,
message: error.message,
details: error.details,
});
reject(error);
}
else {
resolve();
}
});
});
});
}
/**
* Deadletters the specified message, moving it to the dead-letter queue.
*
* @param message - The received Service Bus message to deadletter.
* @param propertiesToModify - Optional properties to modify on the message.
* @param deadletterReason - Optional reason for deadlettering the message.
* @param deadletterErrorDescription - Optional error description for deadlettering.
* @returns A promise that resolves when the operation is successful.
* @throws Error if the lockToken is missing or the gRPC call fails.
*/
deadletter(message, propertiesToModify, deadletterReason, deadletterErrorDescription) {
return __awaiter(this, void 0, void 0, function* () {
const locktoken = this.validateLockToken(message);
const encodedProperties = (0, amqpPropertyEncoder_1.encodePropertiesForOperation)(propertiesToModify, 'deadletter');
return new Promise((resolve, reject) => {
const request = {
locktoken,
propertiesToModify: encodedProperties,
deadletterReason: deadletterReason ? { value: deadletterReason } : undefined,
deadletterErrorDescription: deadletterErrorDescription
? { value: deadletterErrorDescription }
: undefined,
};
this.client.deadletter(request, (error) => {
if (error) {
console.error('Deadletter request failed:', {
code: error.code,
message: error.message,
details: error.details,
});
reject(error);
}
else {
resolve();
}
});
});
});
}
/**
* Defers the specified message, making it invisible until retrieved by sequence number.
*
* @param message - The received Service Bus message to defer.
* @param propertiesToModify - Optional properties to modify on the message.
* @returns A promise that resolves when the operation is successful.
* @throws Error if the lockToken is missing or the gRPC call fails.
*/
defer(message, propertiesToModify) {
return __awaiter(this, void 0, void 0, function* () {
const locktoken = this.validateLockToken(message);
const encodedProperties = (0, amqpPropertyEncoder_1.encodePropertiesForOperation)(propertiesToModify, 'defer');
return new Promise((resolve, reject) => {
const request = {
locktoken,
propertiesToModify: encodedProperties,
};
this.client.defer(request, (error) => {
if (error) {
console.error('Defer request failed:', {
code: error.code,
message: error.message,
details: error.details,
});
reject(error);
}
else {
resolve();
}
});
});
});
}
/**
* Renews the lock on the specified message, extending its lock duration.
*
* @param message - The received Service Bus message whose lock should be renewed.
* @returns A promise that resolves when the operation is successful.
* @throws Error if the lockToken is missing or the gRPC call fails.
*/
renewMessageLock(message) {
return __awaiter(this, void 0, void 0, function* () {
const locktoken = this.validateLockToken(message);
return new Promise((resolve, reject) => {
const request = { locktoken };
this.client.renewMessageLock(request, (error) => {
if (error) {
console.error('Renew message lock request failed:', {
code: error.code,
message: error.message,
details: error.details,
});
reject(error);
}
else {
resolve();
}
});
});
});
}
/**
* Sets the state for the specified session.
*
* @param sessionId - The session ID for which to set the state.
* @param sessionState - The state to set for the session.
* @returns A promise that resolves when the operation is successful.
* @throws Error if the gRPC call fails.
*/
setSessionState(sessionId, sessionState) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => {
const request = { sessionId, sessionState };
this.client.setSessionState(request, (error) => {
if (error) {
console.error('Set session state request failed:', {
code: error.code,
message: error.message,
details: error.details,
});
reject(error);
}
else {
resolve();
}
});
});
});
}
/**
* Releases the specified session, making it available for other receivers.
*
* @param sessionId - The session ID to release.
* @returns A promise that resolves when the operation is successful.
* @throws Error if the gRPC call fails.
*/
releaseSession(sessionId) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => {
const request = { sessionId };
this.client.releaseSession(request, (error) => {
if (error) {
console.error('Release session request failed:', {
code: error.code,
message: error.message,
details: error.details,
});
reject(error);
}
else {
resolve();
}
});
});
});
}
/**
* Renews the lock on the specified session, extending its lock duration.
*
* @param sessionId - The session ID whose lock should be renewed.
* @returns A promise that resolves to the new locked-until date.
* @throws Error if the gRPC call fails or if no response is returned.
*/
renewSessionLock(sessionId) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => {
const request = { sessionId };
this.client.renewSessionLock(request, (error, response) => {
if (error) {
console.error('Renew session lock request failed:', {
code: error.code,
message: error.message,
details: error.details,
});
reject(error);
}
else if (response && response.lockedUntil) {
resolve(response.lockedUntil);
}
else {
const err = new Error('No response or lockedUntil returned from renewSessionLock');
reject(err);
}
});
});
});
}
/**
* Resets the singleton instance (for testing purposes).
*/
static resetInstance() {
ServiceBusMessageActions.instance = null;
}
}
exports.ServiceBusMessageActions = ServiceBusMessageActions;
ServiceBusMessageActions.instance = null;
/***/ },
/***/ "./src/servicebus/azureServiceBusMessageFactory.ts"
/*!*********************************************************!*\
!*** ./src/servicebus/azureServiceBusMessageFactory.ts ***!
\*********************************************************/
(__unused_webpack_module, exports, __webpack_require__) {
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License.
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.AzureServiceBusMessageFactory = void 0;
const core_amqp_1 = __webpack_require__(/*! @azure/core-amqp */ "@azure/core-amqp");
const long_1 = __importDefault(__webpack_require__(/*! long */ "long"));
const serviceBusMessageDecoder_1 = __webpack_require__(/*! ../util/serviceBusMessageDecoder */ "./src/util/serviceBusMessageDecoder.ts");
const ServiceBusMessageActions_1 = __webpack_require__(/*! ./ServiceBusMessageActions */ "./src/servicebus/ServiceBusMessageActions.ts");
const ENQUEUED_TIME_ANNOTATION = 'x-opt-enqueued-time';
const LOCKED_UNTIL_ANNOTATION = 'x-opt-locked-until';
const SEQUENCE_NUMBER_ANNOTATION = 'x-opt-sequence-number';
const ENQUEUED_SEQUENCE_NUMBER_ANNOTATION = 'x-opt-offset';
const DEAD_LETTER_SOURCE_ANNOTATION = 'x-opt-deadletter-source';
const DEAD_LETTER_REASON_ANNOTATION = 'DeadLetterReason';
const DEAD_LETTER_ERROR_DESCRIPTION_ANNOTATION = 'DeadLetterErrorDescription';
/**
* Factory class for creating and processing Azure Service Bus messages Manager.
*
* This factory class provides methods to:
* - Build ServiceBusMessage instances from model binding data in Azure Functions
* - Convert between different message formats (AMQP, Rhea)
* - Extract and decode message body content with proper type handling
*
* The factory handles all necessary transformations of message properties,
* annotations, and content to ensure proper integration with the Azure
* Service Bus messaging system.
*/
class AzureServiceBusMessageFactory {
/**
* Builds a ServiceBusMessageContext instance from model binding data.
* This method extracts the Service Bus message content from the provided model binding data,
* @param modelBindingData - The model binding data containing the Service Bus message content.
* This can be a single ModelBindingData object or an array of ModelBindingData objects.
* @returns A ServiceBusMessageContext instance with messages always returned as an array.
*/
static buildServiceBusMessageFromModelBindingData(modelBindingData) {
const client = ServiceBusMessageActions_1.ServiceBusMessageActions.getInstance();
const toMessage = (data) => {
if (!data.content) {
throw new Error('ModelBindingData.content is null or undefined.');
}
const { decodedMessage, lockToken } = serviceBusMessageDecoder_1.ServiceBusMessageDecoder.decode(data.content);
return this.createServiceBusReceivedMessageFromRhea(decodedMessage, lockToken);
};
const messages = Array.isArray(modelBindingData)
? modelBindingData.map(toMessage)
: [toMessage(modelBindingData)];
return {
messages,
actions: client,
};
}
/**
* Creates a ServiceBusReceivedMessage from an AMQP annotated message.
* This method extracts relevant properties and formats them into the ServiceBusReceivedMessage structure.
*
* @param amqpMessage - The AMQP annotated message to convert.
* @param lockToken - lock token for the message.
* @returns A ServiceBusReceivedMessage object.
*/
static createServiceBusReceivedMessageFromAmqp(amqpMessage, lockToken) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v;
// Extract common properties from the AMQP message
const receivedMessage = {
// Message body - return raw Buffer without automatic parsing
body: AzureServiceBusMessageFactory.decodeAmqpBody(amqpMessage.body),
// Message properties
messageId: (_b = (_a = amqpMessage.properties) === null || _a === void 0 ? void 0 : _a.messageId) === null || _b === void 0 ? void 0 : _b.toString(),
correlationId: (_d = (_c = amqpMessage.properties) === null || _c === void 0 ? void 0 : _c.correlationId) === null || _d === void 0 ? void 0 : _d.toString(),
contentType: (_e = amqpMessage.properties) === null || _e === void 0 ? void 0 : _e.contentType,
subject: (_f = amqpMessage.properties) === null || _f === void 0 ? void 0 : _f.subject,
to: (_g = amqpMessage.properties) === null || _g === void 0 ? void 0 : _g.to,
replyTo: (_h = amqpMessage.properties) === null || _h === void 0 ? void 0 : _h.replyTo,
replyToSessionId: (_j = amqpMessage.properties) === null || _j === void 0 ? void 0 : _j.replyToGroupId,
sessionId: (_k = amqpMessage.properties) === null || _k === void 0 ? void 0 : _k.groupId,
timeToLive: (_l = amqpMessage.header) === null || _l === void 0 ? void 0 : _l.timeToLive,
// Application properties
applicationProperties: amqpMessage.applicationProperties || {},
// Message annotations and delivery annotations
deliveryCount: ((_m = amqpMessage.header) === null || _m === void 0 ? void 0 : _m.deliveryCount) || 0,
// Lock token (if provided)
lockToken: lockToken,
// AMQP annotated message for full access
_rawAmqpMessage: amqpMessage,
// Timestamps (convert from AMQP format if available)
enqueuedTimeUtc: AzureServiceBusMessageFactory.extractDateFromAnnotation((_o = amqpMessage.messageAnnotations) === null || _o === void 0 ? void 0 : _o[ENQUEUED_TIME_ANNOTATION]),
lockedUntilUtc: AzureServiceBusMessageFactory.extractDateFromAnnotation((_p = amqpMessage.messageAnnotations) === null || _p === void 0 ? void 0 : _p[LOCKED_UNTIL_ANNOTATION]),
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
sequenceNumber: ((_q = amqpMessage.messageAnnotations) === null || _q === void 0 ? void 0 : _q[SEQUENCE_NUMBER_ANNOTATION]) !== undefined
? // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
long_1.default.fromNumber(Number(amqpMessage.messageAnnotations[SEQUENCE_NUMBER_ANNOTATION]))
: undefined,
enqueuedSequenceNumber: ((_r = amqpMessage.messageAnnotations) === null || _r === void 0 ? void 0 : _r[ENQUEUED_SEQUENCE_NUMBER_ANNOTATION]) !== undefined
? Number(amqpMessage.messageAnnotations[ENQUEUED_SEQUENCE_NUMBER_ANNOTATION])
: ((_s = amqpMessage.messageAnnotations) === null || _s === void 0 ? void 0 : _s[SEQUENCE_NUMBER_ANNOTATION]) !== undefined
? Number(amqpMessage.messageAnnotations[SEQUENCE_NUMBER_ANNOTATION])
: undefined,
// Dead letter properties
deadLetterReason: (_t = amqpMessage.applicationProperties) === null || _t === void 0 ? void 0 : _t[DEAD_LETTER_REASON_ANNOTATION],
deadLetterErrorDescription: (_u = amqpMessage.applicationProperties) === null || _u === void 0 ? void 0 : _u[DEAD_LETTER_ERROR_DESCRIPTION_ANNOTATION],
deadLetterSource: (_v = amqpMessage.messageAnnotations) === null || _v === void 0 ? void 0 : _v[DEAD_LETTER_SOURCE_ANNOTATION],
// State
state: 'active',
};
return receivedMessage;
}
/**
* Creates a ServiceBusReceivedMessage from a Rhea message.
* This method extracts relevant properties and formats them into the ServiceBusReceivedMessage structure.
*
* @param rheaMessage - The Rhea message to convert.
* @param lockToken - Optional lock token for the message.
* @returns A ServiceBusReceivedMessage object.
*/
static createServiceBusReceivedMessageFromRhea(rheaMessage, lockToken) {
const amqpMessage = core_amqp_1.AmqpAnnotatedMessage.fromRheaMessage(rheaMessage);
return AzureServiceBusMessageFactory.createServiceBusReceivedMessageFromAmqp(amqpMessage, lockToken);
}
/**
* Decodes the body of an AMQP message section based on its typecode.
* Returns the raw binary content as a Buffer without any automatic parsing.
*
* This approach aligns with the Python Azure Functions Extension behavior,
* where the message body is returned as-is without automatic JSON parsing.
* Users who need to parse JSON can do so explicitly with their own logic,
* allowing for custom revivers and full control over the parsing process.
*
* @param section - The AMQP message section containing a typecode and content buffer.
* @returns The raw Buffer content for binary messages, or the original section if not a valid AMQP body.
*/
static decodeAmqpBody(section) {
if (typeof section === 'object' &&
section !== null &&
'typecode' in section &&
'content' in section &&
typeof section.typecode === 'number' &&
Buffer.isBuffer(section.content)) {
const { typecode, content } = section;
// typecode = 117 is Binary content
// Return raw Buffer without any parsing - consistent with Python Extension behavior
if (typecode === 117) {
return content;
}
return content;
}
// Not a valid AMQP body section
return section;
}
/**
* Extracts a Date from an AMQP message annotation value.
* Handles cases where the value is already a Date, or is a string/number that can be converted to a Date.
*
* @param annotationValue - The annotation value from messageAnnotations
* @returns A Date object if the value can be converted, undefined otherwise
*/
static extractDateFromAnnotation(annotationValue) {
if (annotationValue === undefined || annotationValue === null) {
return undefined;
}
// If it's already a Date object, return it
if (annotationValue instanceof Date) {
return annotationValue;
}
// If it's a string or number, try to convert it to a Date
if (typeof annotationValue === 'string' || typeof annotationValue === 'number') {
return new Date(annotationValue);
}
// For any other type, return undefined
return undefined;
}
}
exports.AzureServiceBusMessageFactory = AzureServiceBusMessageFactory;
/***/ },
/***/ "./src/servicebus/registerServiceBusMessageFactory.ts"
/*!************************************************************!*\
!*** ./src/servicebus/registerServiceBusMessageFactory.ts ***!
\************************************************************/
(__unused_webpack_module, exports, __webpack_require__) {
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License.
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.registerServiceBusMessageFactory = void 0;
const functions_extensions_base_1 = __webpack_require__(/*! @azure/functions-extensions-base */ "@azure/functions-extensions-base");
const azureServiceBusMessageFactory_1 = __webpack_require__(/*! ./azureServiceBusMessageFactory */ "./src/servicebus/azureServiceBusMessageFactory.ts");
const AZURE_SERVICE_BUS = 'AzureServiceBusReceivedMessage';
function registerServiceBusMessageFactory() {
try {
const resourceFactoryResolver = functions_extensions_base_1.ResourceFactoryResolver.getInstance();
// Check if a factory is already registered to avoid conflicts
if (!resourceFactoryResolver.hasResourceFactory(AZURE_SERVICE_BUS)) {
resourceFactoryResolver.registerResourceFactory(AZURE_SERVICE_BUS, (modelBindingData) => {
return azureServiceBusMessageFactory_1.AzureServiceBusMessageFactory.buildServiceBusMessageFromModelBindingData(modelBindingData);
});
}
}
catch (error) {
throw new Error(`Service Bus Message Factory initialization failed: ${error instanceof Error ? error.message : String(error)}`);
}
}
exports.registerServiceBusMessageFactory = registerServiceBusMessageFactory;
/***/ },
/***/ "./src/util/amqpPropertyEncoder.ts"
/*!*****************************************!*\
!*** ./src/util/amqpPropertyEncoder.ts ***!
\*****************************************/
(__unused_webpack_module, exports, __webpack_require__) {
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License.
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.validateAmqpProperties = exports.encodePropertiesForOperation = exports.convertPropertiesToAmqpBytes = void 0;
/**
* AMQP Property Encoder Utility
*
* Converts TypeScript/JavaScript property values to AMQP-encoded byte arrays.
* Date objects are automatically converted to UTC ISO format for cross-platform compatibility.
*/
/**
* Supported AMQP property types
*/
var AmqpType;
(function (AmqpType) {
AmqpType["Null"] = "null";
AmqpType["Byte"] = "byte";
AmqpType["SByte"] = "sbyte";
AmqpType["Char"] = "char";
AmqpType["Int16"] = "int16";
AmqpType["UInt16"] = "uint16";
AmqpType["Int32"] = "int32";
AmqpType["UInt32"] = "uint32";
AmqpType["Int64"] = "int64";
AmqpType["UInt64"] = "uint64";
AmqpType["Single"] = "single";
AmqpType["Double"] = "double";
AmqpType["Decimal"] = "decimal";
AmqpType["Boolean"] = "boolean";
AmqpType["Guid"] = "guid";
AmqpType["String"] = "string";
AmqpType["Uri"] = "uri";
AmqpType["DateTime"] = "datetime";
AmqpType["DateTimeOffset"] = "datetimeoffset";
AmqpType["TimeSpan"] = "timespan";
AmqpType["Stream"] = "stream";
AmqpType["Array"] = "array";
AmqpType["Unknown"] = "unknown";
})(AmqpType || (AmqpType = {}));
/**
* Type mapping from JavaScript primitive types to AMQP types
*/
const PRIMITIVE_TYPE_MAP = new Map([
['number', AmqpType.Double],
['boolean', AmqpType.Boolean],
['string', AmqpType.String],
['bigint', AmqpType.Int64],
]);
/**
* Type conversion utilities for specific AMQP types
*
* Contains validation functions to determine if JavaScript values fit within
* specific AMQP type ranges and formats.
*/
const TypeConverters = {
/** Checks if number fits in unsigned 8-bit range (0-255) */
isByte: (value) => Number.isInteger(value) && value >= 0 && value <= 255,
/** Checks if number fits in signed 8-bit range (-128 to 127) */
isSByte: (value) => Number.isInteger(value) && value >= -128 && value <= 127,
/** Checks if number fits in signed 16-bit range (-32,768 to 32,767) */
isInt16: (value) => Number.isInteger(value) && value >= -32768 && value <= 32767,
/** Checks if number fits in unsigned 16-bit range (0 to 65,535) */
isUInt16: (value) => Number.isInteger(value) && value >= 0 && value <= 65535,
/** Checks if number fits in signed 32-bit range (-2,147,483,648 to 2,147,483,647) */
isInt32: (value) => Number.isInteger(value) && value >= -2147483648 && value <= 2147483647,
/** Checks if number fits in unsigned 32-bit range (0 to 4,294,967,295) */
isUInt32: (value) => Number.isInteger(value) && value >= 0 && value <= 4294967295,
/** Checks if floating-point number fits in 32-bit single precision range */
isSingle: (value) => !Number.isInteger(value) && Math.abs(value) <= 3.4028235e38,
/** Checks if string is a single character */
isChar: (value) => value.length === 1,
/** Checks if string matches GUID/UUID format (8-4-4-4-12 hex digits) */
isGuid: (value) => /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value),
/** Checks if string is a valid URI by attempting URL construction */
isUri: (value) => {
try {
new URL(value);
return true;
}
catch (_a) {
return false;
}
},
/** Checks if string can be parsed as a date */
isDateTime: (value) => !isNaN(Date.parse(value)),
/** Checks if string matches TimeSpan format ([-][d.]hh:mm:ss[.fffffff]) */
isTimeSpan: (value) => /^-?(\d+\.)?(\d{2}:)?(\d{2}:)?\d{2}(\.\d{1,7})?$/.test(value),
};
/**
* Checks if an object represents a decimal-like value structure
* @param obj - The object to check
* @returns True if the object has properties indicating it's a decimal type
*/
function isDecimalLike(obj) {
return (obj !== null &&
typeof obj === 'object' &&
'toString' in obj &&
('precision' in obj || 'scale' in obj || 'value' in obj));
}
/**
* Wraps a value with the appropriate AMQP type using the rhea library
* @param type - The AMQP type string identifier
* @param value - The value to wrap
* @returns The wrapped value ready for AMQP encoding
*/
function wrapAmqpValue(type, value) {
// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-assignment
const rhea = __webpack_require__(/*! rhea */ "rhea");
switch (type) {
case 'null':
return null;
case 'boolean':
return value;
case 'byte':
case 'sbyte':
case 'int16':
case 'uint16':
case 'int32':
case 'uint32':
case 'single':
case 'double':
return value;
case 'int64':
case 'uint64':
// Convert BigInt to number for rhea compatibility
if (typeof value === 'bigint') {
// For BigInt values that are within safe integer range, convert to number
if (value >= BigInt(Number.MIN_SAFE_INTEGER) && value <= BigInt(Number.MAX_SAFE_INTEGER)) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
return rhea.types.wrap_long(Number(value));
}
else {
// For very large values, handle as number but this may lose precision
// This is a limitation of the JavaScript-AMQP bridge
console.warn(`BigInt value ${value} is outside safe integer range, precision may be lost`);
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
return rhea.types.wrap_long(Number(value));
}
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
return rhea.types.wrap_long(value);
case 'decimal':
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
return rhea.types.wrap_decimal128(value);
case 'char':
case 'string':
case 'guid':
case 'uri':
case 'datetime':
case 'datetimeoffset':
case 'timespan':
return value;
case 'stream':
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
return rhea.types.wrap_binary(value);
default:
return value;
}
}
const SUPPORTED_TYPES_MESSAGE = 'Supported types: ' +
'Primitives: null, boolean, string (including char, guid, uri, datetime, timespan), ' +
'Numbers: byte, sbyte, int16, uint16, int32, uint32, int64, uint64, single, double, decimal, ' +
'Objects: Date (datetimeoffset), URL (uri), Buffer/Uint8Array (stream), arrays of supported types';
/**
* Converts a Record<string, any> to AMQP-encoded byte array
*
* @param propertiesToModify - The properties to encode
* @returns Uint8Array containing AMQP-encoded properties
*/
function convertPropertiesToAmqpBytes(propertiesToModify) {
const amqpMap = new Map();
for (const [key, value] of Object.entries(propertiesToModify)) {
const amqpValue = tryCreateAmqpPropertyValue(value);
if (amqpValue !== null) {
amqpMap.set(key, amqpValue);
}
else {
const error = new Error(`The key '${key}' has a value of type '${typeof value}' which is not supported for AMQP transport. ${SUPPORTED_TYPES_MESSAGE}`);
throw error;
}
}
const encodedBytes = encodeAmqpMap(amqpMap);
return encodedBytes;
}
exports.convertPropertiesToAmqpBytes = convertPropertiesToAmqpBytes;
/**
* Encodes properties for Service Bus message operations with proper error handling
*
* This function provides a safe wrapper around AMQP property encoding specifically
* designed for Service Bus message operations (abandon, deadletter, defer).
* It handles null/undefined properties and provides operation-specific error messages.
*
* @param propertiesToModify - Optional properties to modify on the message
* @param operationName - The name of the operation (for error messages)
* @returns Encoded properties as Uint8Array, or empty array if no properties provided
* @throws Error with operation-specific context if encoding fails
*
* @example
* ```typescript
* // For abandon operation
* const encoded = encodePropertiesForOperation(
* { messageId: 'test-123', priority: 1 },
* 'abandon'
* );
*
* // For operations without properties
* const empty = encodePropertiesForOperation(undefined, 'defer');
* // Returns: new Uint8Array()
* ```
*/
function encodePropertiesForOperation(propertiesToModify, operationName) {
// Return empty array if no properties provided
if (!propertiesToModify || Object.keys(propertiesToModify).length === 0) {
return new Uint8Array();
}
try {
return new Uint8Array(convertPropertiesToAmqpBytes(propertiesToModify));
}
catch (error) {
throw new Error(`Failed to encode properties for ${operationName} operation: ${error instanceof Error ? error.message : String(error)}`);
}
}
exports.encodePropertiesForOperation = encodePropertiesForOperation;
/**
* Attempts to create an AMQP property value from a JavaScript value with intelligent type detection
*
* Performs automatic type detection and conversion:
* - Numbers: Detects optimal integer types (byte, int16, int32, int64) or floating point (single, double)
* - Strings: Detects char, GUID, URI, DateTime, TimeSpan patterns
* - Objects: Handles Date, URL, Buffer/Uint8Array, decimal-like objects, arrays
* - Primitives: Boolean, BigInt, null/undefined
*
* @param propertyValue - The JavaScript value to convert to AMQP format
* @returns Typed AMQP value object with { type, value } structure, or null if conversion fails
*/
function tryCreateAmqpPropertyValue(propertyValue) {
if (propertyValue === null || propertyValue === undefined) {
return { type: 'null', value: null };
}
const valueType = typeof propertyValue;
switch (valueType) {
case 'string': {
const strValue = propertyValue;
if (strValue === '')
return { type: 'string', value: strValue };
if (TypeConverters.isChar(strValue))
return { type: 'char', value: strValue };
if (TypeConverters.isGuid(strValue))
return { type: 'guid', value: strValue };
if (TypeConverters.isUri(strValue))
return { type: 'uri', value: strValue };
if (TypeConverters.isTimeSpan(strValue))
return { type: 'timespan', value: strValue };
if (TypeConverters.isDateTime(strValue))
return { type: 'datetime', value: strValue };
return { type: 'string', value: strValue };
}
case 'number': {
const numValue = propertyValue;
if (!Number.isFinite(numValue)) {
return numValue;
}
if (Number.isInteger(numValue)) {
if (TypeConverters.isByte(numValue))
return { type: 'byte', value: numValue };
if (TypeConverters.isSByte(numValue))
return { type: 'sbyte', value: numValue };
if (TypeConverters.isInt16(numValue))
return { type: 'int16', value: numValue };
if (TypeConverters.isUInt16(numValue))
return { type: 'uint16', value: numValue };
if (TypeConverters.isInt32(numValue))
return { type: 'int32', value: numValue };
if (TypeConverters.isUInt32(numValue))
return { type: 'uint32', value: numValue };
return { type: 'int64', value: BigInt(numValue) };
}
if (TypeConverters.isSingle(numValue)) {
return { type: 'single', value: numValue };
}
return { type: 'double', value: numValue };
}
case 'boolean':
return { type: 'boolean', value: propertyValue };
case 'bigint': {
const bigintValue = propertyValue;
if (bigintValue >= BigInt(0) && bigintValue <= BigInt('18446744073709551615')) {
return { type: 'uint64', value: bigintValue };
}
return { type: 'int64', value: bigintValue };
}
case 'object': {
if (propertyValue instanceof Date) {
return { type: 'datetimeoffset', value: propertyValue.toISOString() };
}
if (propertyValue instanceof Buffer || propertyValue instanceof Uint8Array) {
return { type: 'stream', value: propertyValue };
}
if