hook-engine
Version:
Production-grade webhook engine with comprehensive adapter support, security, reliability, structured logging, and CLI tools.
250 lines (249 loc) • 8.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.BaseAdvancedAdapter = void 0;
const uuid_1 = require("uuid");
class BaseAdvancedAdapter {
constructor() {
// Advanced feature flags
this.supportsBatch = true;
this.supportsFiltering = true;
this.supportsRouting = true;
this.supportsMultiTenant = true;
}
/**
* Parse batch payload - default implementation assumes array of events
*/
parseBatchPayload(rawBody) {
try {
const parsed = JSON.parse(rawBody.toString());
return Array.isArray(parsed) ? parsed : [parsed];
}
catch (error) {
throw new Error(`Failed to parse batch payload: ${error.message}`);
}
}
/**
* Normalize batch of events
*/
normalizeBatch(events, options) {
const batchId = options?.batchId || (0, uuid_1.v4)();
const normalizedEvents = [];
let failedCount = 0;
for (const event of events) {
try {
const normalized = this.normalize(event, options);
normalizedEvents.push(normalized);
}
catch (error) {
failedCount++;
if (!options?.skipInvalid) {
throw error;
}
}
}
return {
batchId,
events: normalizedEvents,
totalCount: events.length,
processedCount: normalizedEvents.length,
failedCount,
timestamp: Date.now(),
metadata: options?.metadata
};
}
/**
* Process batch of events with advanced options
*/
async processBatch(events, options) {
const batchId = (0, uuid_1.v4)();
const startTime = Date.now();
const results = [];
const errors = [];
const concurrency = options?.concurrency || 10;
const timeout = options?.timeout || 30000;
// Apply transformations if provided
let processedEvents = events;
if (options?.transformations) {
processedEvents = await this.applyTransformations(events, options.transformations);
}
// Process events in batches with concurrency control
const chunks = this.chunkArray(processedEvents, concurrency);
for (const chunk of chunks) {
const chunkPromises = chunk.map(async (event) => {
const eventStartTime = Date.now();
try {
// Simulate event processing (override in subclasses)
await this.processEvent(event, timeout);
const result = {
success: true,
eventId: event.id,
processingTime: Date.now() - eventStartTime
};
results.push(result);
}
catch (error) {
const result = {
success: false,
eventId: event.id,
error: error,
processingTime: Date.now() - eventStartTime
};
results.push(result);
errors.push(error);
if (!options?.continueOnError) {
throw error;
}
}
});
await Promise.all(chunkPromises);
}
const successCount = results.filter(r => r.success).length;
const failureCount = results.filter(r => !r.success).length;
return {
batchId,
totalEvents: events.length,
successCount,
failureCount,
results,
processingTime: Date.now() - startTime,
errors
};
}
/**
* Filter events based on criteria
*/
filterEvents(events, filter) {
return events.filter(event => {
// Filter by types
if (filter.types && !filter.types.includes(event.type)) {
return false;
}
// Filter by sources
if (filter.sources && !filter.sources.includes(event.source)) {
return false;
}
// Filter by tenants
if (filter.tenants && event.tenant && !filter.tenants.includes(event.tenant)) {
return false;
}
// Filter by tags
if (filter.tags && event.tags) {
const hasMatchingTag = filter.tags.some(tag => event.tags.includes(tag));
if (!hasMatchingTag) {
return false;
}
}
// Filter by priority
if (filter.priority && event.priority && !filter.priority.includes(event.priority)) {
return false;
}
// Apply custom filter
if (filter.customFilter && !filter.customFilter(event)) {
return false;
}
return true;
});
}
/**
* Route event to matching routes
*/
routeEvent(event, routes) {
return routes
.filter(route => route.enabled)
.filter(route => {
const matchingEvents = this.filterEvents([event], route.filter);
return matchingEvents.length > 0;
})
.sort((a, b) => b.priority - a.priority);
}
/**
* Extract tenant from event - default implementation
*/
extractTenant(event, req) {
// Try common tenant extraction patterns
if (event.tenant)
return event.tenant;
if (event.organization)
return event.organization;
if (event.account)
return event.account;
if (event.workspace)
return event.workspace;
// Try headers
if (req?.headers) {
const tenantHeader = req.headers['x-tenant'] ||
req.headers['x-organization'] ||
req.headers['x-account'];
if (tenantHeader)
return tenantHeader;
}
return undefined;
}
/**
* Validate tenant access
*/
validateTenant(tenant, event) {
// Default implementation - override in subclasses for specific validation
return event.tenant === tenant || !event.tenant;
}
/**
* Process individual event - override in subclasses
*/
async processEvent(event, timeout) {
// Default implementation - just simulate processing
await new Promise(resolve => setTimeout(resolve, 10));
}
/**
* Apply transformations to events
*/
async applyTransformations(events, transformations) {
let transformedEvents = events;
for (const transformation of transformations) {
transformedEvents = await this.applyTransformation(transformedEvents, transformation);
}
return transformedEvents;
}
/**
* Apply single transformation
*/
async applyTransformation(events, transformation) {
switch (transformation.type) {
case 'javascript':
return this.applyJavaScriptTransformation(events, transformation.script);
case 'jsonata':
// Would require jsonata library - simplified for now
return events;
case 'custom':
// Custom transformation logic
return events;
default:
return events;
}
}
/**
* Apply JavaScript transformation
*/
applyJavaScriptTransformation(events, script) {
try {
// Create a safe execution context
const transformFunction = new Function('events', `
return (${script})(events);
`);
return transformFunction(events);
}
catch (error) {
throw new Error(`JavaScript transformation failed: ${error.message}`);
}
}
/**
* Utility: Chunk array into smaller arrays
*/
chunkArray(array, size) {
const chunks = [];
for (let i = 0; i < array.length; i += size) {
chunks.push(array.slice(i, i + size));
}
return chunks;
}
}
exports.BaseAdvancedAdapter = BaseAdvancedAdapter;