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
JavaScript
;
/**
* @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;