UNPKG

n8n-nodes-semble

Version:

n8n community node for Semble practice management system - automate bookings, patients, and product/service catalog management

439 lines (438 loc) 16.5 kB
"use strict"; /** * @fileoverview MiddlewarePipeline.ts * @description Request/response processing pipeline with middleware chain execution for validation, permission checking, and error transformation * @author Mike Hatcher * @website https://progenious.com * @namespace N8nNodesSemble.Core.MiddlewarePipeline * @since 2.0.0 */ var _a; Object.defineProperty(exports, "__esModule", { value: true }); exports.MiddlewarePipelineUtils = exports.MiddlewarePipeline = void 0; const SembleError_1 = require("./SembleError"); /** * Request/response processing pipeline with middleware chain * Provides extensible processing for API requests and responses */ class MiddlewarePipeline { constructor(eventSystem) { this.middlewares = []; this.eventSystem = eventSystem; } /** * Register a middleware function */ register(name, middleware, priority = 100, enabled = true) { var _b; // Check for duplicate names if (this.middlewares.some(m => m.name === name)) { throw new SembleError_1.SembleError(`Middleware '${name}' is already registered`, 'MIDDLEWARE_DUPLICATE'); } this.middlewares.push({ name, middleware, priority, enabled }); // Sort by priority (lower = earlier) this.middlewares.sort((a, b) => a.priority - b.priority); (_b = this.eventSystem) === null || _b === void 0 ? void 0 : _b.emit({ type: 'middleware:registered', source: 'MiddlewarePipeline', id: `middleware:registered:${name}:${Date.now()}`, timestamp: Date.now(), name, priority, enabled }); } /** * Unregister a middleware */ unregister(name) { var _b; const index = this.middlewares.findIndex(m => m.name === name); if (index === -1) { return false; } this.middlewares.splice(index, 1); (_b = this.eventSystem) === null || _b === void 0 ? void 0 : _b.emit({ type: 'middleware:unregistered', source: 'MiddlewarePipeline', id: `middleware:unregistered:${name}:${Date.now()}`, timestamp: Date.now(), name }); return true; } /** * Enable or disable a middleware */ setEnabled(name, enabled) { var _b; const middleware = this.middlewares.find(m => m.name === name); if (!middleware) { return false; } middleware.enabled = enabled; (_b = this.eventSystem) === null || _b === void 0 ? void 0 : _b.emit({ type: 'middleware:toggled', source: 'MiddlewarePipeline', id: `middleware:toggled:${name}:${Date.now()}`, timestamp: Date.now(), name, enabled }); return true; } /** * Get all registered middleware */ getMiddlewares() { return [...this.middlewares]; } /** * Get enabled middleware in execution order */ getEnabledMiddlewares() { return this.middlewares.filter(m => m.enabled); } /** * Clear all registered middleware */ clear() { var _b; const count = this.middlewares.length; this.middlewares = []; (_b = this.eventSystem) === null || _b === void 0 ? void 0 : _b.emit({ type: 'middleware:cleared', source: 'MiddlewarePipeline', id: `middleware:cleared:${Date.now()}`, timestamp: Date.now(), count }); } /** * Execute the middleware pipeline */ async execute(context, options = {}) { const startTime = Date.now(); const trace = []; const enabledMiddlewares = this.getEnabledMiddlewares(); // Default options const { continueOnError = false, timeout = 30000, emitEvents = true } = options; if (emitEvents && this.eventSystem) { this.eventSystem.emit({ type: 'pipeline:started', source: 'MiddlewarePipeline', id: `pipeline:started:${Date.now()}`, timestamp: Date.now(), middlewareCount: enabledMiddlewares.length, resource: context.request.metadata.resource, action: context.request.metadata.action }); } // Create execution context with timeout let timeoutId; let isTimedOut = false; if (timeout > 0) { timeoutId = setTimeout(() => { isTimedOut = true; }, timeout); } try { // Execute middleware chain let currentIndex = 0; const executeNext = async () => { if (isTimedOut) { throw new SembleError_1.SembleError(`Pipeline execution timed out after ${timeout}ms`, 'PIPELINE_TIMEOUT'); } const middleware = enabledMiddlewares[currentIndex]; if (!middleware) { return; // End of chain } const middlewareStartTime = Date.now(); let middlewareError; try { // Execute middleware currentIndex++; await middleware.middleware(context, executeNext); trace.push({ name: middleware.name, startTime: middlewareStartTime, endTime: Date.now(), success: true }); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); middlewareError = errorMessage; trace.push({ name: middleware.name, startTime: middlewareStartTime, endTime: Date.now(), success: false, error: errorMessage }); if (!continueOnError) { throw error; } // Store error in context but continue context.error = error instanceof SembleError_1.SembleError ? error : new SembleError_1.SembleError(errorMessage, 'MIDDLEWARE_ERROR'); if (emitEvents && this.eventSystem) { this.eventSystem.emit({ type: 'pipeline:middleware_error', source: 'MiddlewarePipeline', id: `pipeline:middleware_error:${middleware.name}:${Date.now()}`, timestamp: Date.now(), middlewareName: middleware.name, error: errorMessage, continuing: true }); } // Continue to next middleware instead of calling next() await executeNext(); } }; // Start the chain await executeNext(); const executionTime = Date.now() - startTime; const success = !context.error; if (emitEvents && this.eventSystem) { this.eventSystem.emit({ type: success ? 'pipeline:completed' : 'pipeline:completed_with_errors', source: 'MiddlewarePipeline', id: `pipeline:${success ? 'completed' : 'completed_with_errors'}:${Date.now()}`, timestamp: Date.now(), executionTime, middlewareCount: enabledMiddlewares.length, successfulMiddleware: trace.filter(t => t.success).length, resource: context.request.metadata.resource, action: context.request.metadata.action }); } return { success, context, executionTime, trace }; } catch (error) { const executionTime = Date.now() - startTime; const errorMessage = error instanceof Error ? error.message : String(error); if (emitEvents && this.eventSystem) { this.eventSystem.emit({ type: 'pipeline:failed', source: 'MiddlewarePipeline', id: `pipeline:failed:${Date.now()}`, timestamp: Date.now(), error: errorMessage, executionTime, resource: context.request.metadata.resource, action: context.request.metadata.action }); } // Ensure error is stored in context context.error = error instanceof SembleError_1.SembleError ? error : new SembleError_1.SembleError(errorMessage, 'PIPELINE_ERROR'); return { success: false, context, executionTime, trace }; } finally { if (timeoutId) { clearTimeout(timeoutId); } } } /** * Create a new pipeline with pre-configured middleware */ static createWithDefaults(eventSystem) { const pipeline = new _a(eventSystem); // Register default middleware in order of execution pipeline.register('request-validation', _a.requestValidationMiddleware, 10); pipeline.register('permission-check', _a.permissionCheckMiddleware, 20); pipeline.register('api-execution', _a.apiExecutionMiddleware, 50); pipeline.register('response-processing', _a.responseProcessingMiddleware, 80); pipeline.register('error-mapping', _a.errorMappingMiddleware, 90); return pipeline; } } exports.MiddlewarePipeline = MiddlewarePipeline; _a = MiddlewarePipeline; /** * Built-in request validation middleware */ MiddlewarePipeline.requestValidationMiddleware = async (context, next) => { const { request } = context; // Validate required fields if (!request.query || typeof request.query !== 'string') { throw new SembleError_1.SembleError('Query is required and must be a string', 'VALIDATION_ERROR'); } if (!request.metadata.resource) { throw new SembleError_1.SembleError('Resource is required in metadata', 'VALIDATION_ERROR'); } if (!request.metadata.action) { throw new SembleError_1.SembleError('Action is required in metadata', 'VALIDATION_ERROR'); } // Store validation result context.shared.requestValidated = true; context.shared.validationTime = Date.now(); await next(); }; /** * Built-in permission check middleware */ MiddlewarePipeline.permissionCheckMiddleware = async (context, next) => { // This would integrate with PermissionCheckService // For now, we'll implement a basic check const { request, executeFunctions } = context; try { // Get credentials to verify they exist const credentials = await executeFunctions.getCredentials('sembleApi'); if (!credentials || !credentials.token) { throw new SembleError_1.SembleError('Invalid or missing credentials', 'PERMISSION_ERROR'); } // Store permission check result context.shared.permissionChecked = true; context.shared.permissionCheckTime = Date.now(); await next(); } catch (error) { throw new SembleError_1.SembleError(`Permission check failed: ${error instanceof Error ? error.message : String(error)}`, 'PERMISSION_ERROR'); } }; /** * Built-in API execution middleware */ MiddlewarePipeline.apiExecutionMiddleware = async (context, next) => { // This would integrate with SembleQueryService // For now, we'll simulate API execution const { request } = context; try { // Simulate API call context.response = { data: { // Mock response based on action [request.metadata.action]: { success: true, timestamp: new Date().toISOString(), resource: request.metadata.resource } }, metadata: { executionTime: Date.now() - (context.shared.validationTime || Date.now()), query: request.query.substring(0, 100) + '...' } }; context.shared.apiExecuted = true; context.shared.apiExecutionTime = Date.now(); await next(); } catch (error) { throw new SembleError_1.SembleError(`API execution failed: ${error instanceof Error ? error.message : String(error)}`, 'API_ERROR'); } }; /** * Built-in response processing middleware */ MiddlewarePipeline.responseProcessingMiddleware = async (context, next) => { if (!context.response) { throw new SembleError_1.SembleError('No response data to process', 'PROCESSING_ERROR'); } try { // Process the response data const processedData = { ...context.response.data, processed: true, processingTime: Date.now() }; context.response.processedData = processedData; context.shared.responseProcessed = true; context.shared.responseProcessingTime = Date.now(); await next(); } catch (error) { throw new SembleError_1.SembleError(`Response processing failed: ${error instanceof Error ? error.message : String(error)}`, 'PROCESSING_ERROR'); } }; /** * Built-in error mapping middleware */ MiddlewarePipeline.errorMappingMiddleware = async (context, next) => { try { await next(); } catch (error) { // Map errors to user-friendly messages let mappedError; if (error instanceof SembleError_1.SembleError) { mappedError = error; } else { const errorMessage = error instanceof Error ? error.message : String(error); // Map common error patterns if (errorMessage.includes('permission') || errorMessage.includes('unauthorized')) { mappedError = new SembleError_1.SembleError('You do not have permission to perform this action', 'PERMISSION_ERROR'); } else if (errorMessage.includes('timeout')) { mappedError = new SembleError_1.SembleError('The request timed out. Please try again.', 'TIMEOUT_ERROR'); } else if (errorMessage.includes('network') || errorMessage.includes('connection')) { mappedError = new SembleError_1.SembleError('Network error occurred. Please check your connection.', 'NETWORK_ERROR'); } else { mappedError = new SembleError_1.SembleError(`An error occurred: ${errorMessage}`, 'UNKNOWN_ERROR'); } } context.shared.errorMapped = true; context.shared.errorMappingTime = Date.now(); throw mappedError; } }; /** * Utility functions for pipeline management */ class MiddlewarePipelineUtils { /** * Create a basic pipeline context */ static createContext(executeFunctions, query, variables, resource, action, itemIndex) { return { executeFunctions, request: { query, variables, metadata: { resource, action, itemIndex } }, shared: {} }; } /** * Create a pipeline with common Semble middleware */ static createSemblePipeline(eventSystem) { return MiddlewarePipeline.createWithDefaults(eventSystem); } /** * Execute a simple request through the pipeline */ static async executeRequest(pipeline, executeFunctions, query, variables, resource, action, options) { const context = this.createContext(executeFunctions, query, variables, resource, action); return pipeline.execute(context, options); } } exports.MiddlewarePipelineUtils = MiddlewarePipelineUtils;