UNPKG

smart-saga-pattern

Version:

A library implementing the Saga pattern for microservice architecture

147 lines (146 loc) 6.54 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SagaBuilder = void 0; const SagaDefinition_1 = require("../orchestration/SagaDefinition"); const decorators_1 = require("./decorators"); /** * Builder for creating Saga definitions from decorated classes */ class SagaBuilder { /** * Builds a Saga definition from all registered steps * @template T Type of the context */ static buildFromAllSteps() { const steps = decorators_1.SagaStepContainer.getAllSteps(); // Sort steps by order const sortedSteps = steps.sort((a, b) => { const metadataA = Reflect.getMetadata(decorators_1.SAGA_STEP_METADATA, a); const metadataB = Reflect.getMetadata(decorators_1.SAGA_STEP_METADATA, b); return (metadataA.options.order || 0) - (metadataB.options.order || 0); }); // Create a SagaDefinition builder const builder = SagaDefinition_1.SagaDefinition.builder(); // Add each step to the builder for (const stepClass of sortedSteps) { this.addStepToBuilder(builder, stepClass); } return builder.build(); } /** * Builds a Saga definition from specific step classes * @param stepClasses Step classes to include in the Saga * @template T Type of the context */ static buildFromSteps(stepClasses) { // Create a SagaDefinition builder const builder = SagaDefinition_1.SagaDefinition.builder(); // Add each step to the builder for (const stepClass of stepClasses) { this.addStepToBuilder(builder, stepClass); } return builder.build(); } /** * Adds a step to a SagaDefinition builder * @param builder SagaDefinition builder * @param stepClass Step class to add * @template T Type of the context */ static addStepToBuilder(builder, stepClass) { // Get step metadata const stepMetadata = Reflect.getMetadata(decorators_1.SAGA_STEP_METADATA, stepClass); if (!stepMetadata) { throw new Error(`Class ${stepClass.name} is not a valid Saga step`); } // Find invoke method const invokeMethod = this.findMethodWithMetadata(stepClass, decorators_1.INVOKE_METADATA); if (!invokeMethod) { throw new Error(`Step ${stepClass.name} does not have an @Invoke method`); } // Find compensation method (optional) const compensationMethod = this.findMethodWithMetadata(stepClass, decorators_1.COMPENSATION_METADATA); // Create transaction function const transaction = async (context, metadata) => { // Create an instance of the step class const instance = new stepClass(); // Create metadata if not provided const transactionMetadata = metadata || { id: `tx-${Date.now()}`, name: stepClass.name, timestamp: Date.now(), }; // Log the context for debugging console.log(`[${stepClass.name}] Transaction context:`, JSON.stringify(context, null, 2)); // Initialize step data if using structured context if (context && typeof context === "object" && context.steps) { // Check if we're using structured context if (!context.steps[stepClass.name]) { // Initialize empty step data context.steps[stepClass.name] = { input: {}, output: {}, compensation: {}, }; // Initialize input based on step name if (context.global) { // For DebitAccount (first step) if (stepClass.name === "DebitAccount") { context.steps[stepClass.name].input = { accountId: context.global.sourceAccountId, amount: context.global.amount, }; } } } } // Call the invoke method return await instance[invokeMethod](context, transactionMetadata); }; // Create compensation function (if compensation method exists) let compensation; if (compensationMethod) { compensation = async (context, transactionOutput, metadata) => { // Create an instance of the step class const instance = new stepClass(); // Create metadata const compensationMetadata = { id: `comp-${Date.now()}`, name: `${stepClass.name} Compensation`, timestamp: Date.now(), transactionId: (transactionOutput === null || transactionOutput === void 0 ? void 0 : transactionOutput.transactionId) || "unknown", }; // Log the context and transaction output for debugging console.log(`[${stepClass.name}] Compensation context:`, JSON.stringify(context, null, 2)); console.log(`[${stepClass.name}] Transaction output:`, JSON.stringify(transactionOutput, null, 2)); // Call the compensation method await instance[compensationMethod](context, transactionOutput, compensationMetadata); }; } // Add step to the builder builder.step(stepClass.name, transaction, compensation, stepMetadata.options.requiresCompensation); } /** * Finds a method with specific metadata in a class * @param stepClass Class to search in * @param metadataKey Metadata key to look for */ static findMethodWithMetadata(stepClass, metadataKey) { // Get all property names of the prototype const propertyNames = Object.getOwnPropertyNames(stepClass.prototype); // Find the method with the metadata for (const propertyName of propertyNames) { // Skip constructor if (propertyName === "constructor") { continue; } // Check if the method has the metadata const metadata = Reflect.getMetadata(metadataKey, stepClass, propertyName); if (metadata) { return propertyName; } } return undefined; } } exports.SagaBuilder = SagaBuilder;