UNPKG

@azure/functions-extensions-servicebus

Version:
1,089 lines (1,039 loc) 68.5 kB
/******/ (() => { // 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