Skip to content

v4.0.0: Major Release: Webhook & Event-Driven Routing

Choose a tag to compare

@hideokamoto hideokamoto released this 17 Nov 12:21
· 10 commits to main since this release

This major release introduces powerful new features for handling webhook events and event-driven architectures, including multiple handler execution, priority-based resolution, and async support.

⚠️ Breaking Changes

resolve() Method Behavior Change

Important: The resolve() method now returns the highest priority handler instead of the first matching handler based on registration order.

Before (v2.x)

const resolver = new Resolver(handler1, handler2, handler3);
const result = resolver.resolve('type'); // Returns handler1 (first registered)

After (v4.0.0)

// Without priority - behavior unchanged (returns first matching handler)
const resolver = new Resolver(handler1, handler2, handler3);
const result = resolver.resolve('type'); // Still returns handler1

// With priority - returns highest priority handler
class HighPriority implements PrioritizedResolveTarget {
  priority = 100;
  // ...
}
class LowPriority implements PrioritizedResolveTarget {
  priority = 10;
  // ...
}

const resolver = new Resolver(lowPriority, highPriority);
const result = resolver.resolve('type'); // Returns highPriority (priority: 100)

Migration Guide: If you rely on registration order and don't want priority-based resolution:

  • Continue using handlers without the priority property - they will maintain registration order (all have default priority of 0)
  • Or explicitly set the same priority on all handlers to maintain registration order

✨ New Features

1. Multiple Handler Execution

Execute all matching handlers for a single event type. Perfect for webhook fanout patterns where one event needs multiple processors.

import Resolver from 'class-resolver';
import { ResolveTarget } from 'class-resolver';

interface StripeEvent {
  type: string;
  data: { amount: number };
}

class AccountingHandler implements ResolveTarget<[StripeEvent], string, StripeEvent> {
  supports(event: StripeEvent): boolean {
    return event.type === 'payment.succeeded';
  }
  handle(event: StripeEvent): string {
    return `Accounting: Recorded ${event.data.amount}`;
  }
}

class EmailHandler implements ResolveTarget<[StripeEvent], string, StripeEvent> {
  supports(event: StripeEvent): boolean {
    return event.type === 'payment.succeeded';
  }
  handle(event: StripeEvent): string {
    return `Email: Sent confirmation for ${event.data.amount}`;
  }
}

const resolver = new Resolver<ResolveTarget<[StripeEvent], string, StripeEvent>, StripeEvent>(
  new AccountingHandler(),
  new EmailHandler()
);

const event: StripeEvent = {
  type: 'payment.succeeded',
  data: { amount: 1000 }
};

// Execute ALL matching handlers
const results = resolver.handleAll(event, event);
// Results: ['Accounting: Recorded 1000', 'Email: Sent confirmation for 1000']

// Or get all matching handlers
const handlers = resolver.resolveAll(event);
// handlers.length === 2

New Methods:

  • resolveAll(type): Returns all matching handlers sorted by priority
  • handleAll(type, ...args): Executes all matching handlers and returns their results

2. Priority-Based Handler Resolution

Control execution order with priority levels. Higher priority handlers execute first.

import { PrioritizedResolveTarget } from 'class-resolver';

class ValidationHandler implements PrioritizedResolveTarget<[any], boolean, string> {
  priority = 100;  // Highest priority

  supports(type: string): boolean {
    return type === 'webhook';
  }

  handle(data: any): boolean {
    return data !== null && data !== undefined;
  }
}

class BusinessLogicHandler implements PrioritizedResolveTarget<[any], string, string> {
  priority = 50;  // Medium priority

  supports(type: string): boolean {
    return type === 'webhook';
  }

  handle(data: any): string {
    return `Processed: ${JSON.stringify(data)}`;
  }
}

const resolver = new Resolver<PrioritizedResolveTarget<[any], any, string>, string>(
  new BusinessLogicHandler(), // Registered second
  new ValidationHandler()      // Registered first
);

// Handlers execute in PRIORITY order (not registration order):
// 1. ValidationHandler (priority: 100)
// 2. BusinessLogicHandler (priority: 50)
const results = resolver.handleAll('webhook', { test: true });

New Interface:

  • PrioritizedResolveTarget<TArgs, TReturn, TType>: Extends ResolveTarget with optional priority property

3. Async Handler Support

Execute async handlers in parallel or sequentially.

import { AsyncResolveTarget } from 'class-resolver';

class SaveToDBHandler implements AsyncResolveTarget<[any], string, string> {
  supports(type: string): boolean {
    return type === 'payment';
  }

  async handle(data: any): Promise<string> {
    await new Promise(resolve => setTimeout(resolve, 100));
    return 'Saved to DB';
  }
}

class SendWebhookHandler implements AsyncResolveTarget<[any], string, string> {
  supports(type: string): boolean {
    return type === 'payment';
  }

  async handle(data: any): Promise<string> {
    await new Promise(resolve => setTimeout(resolve, 200));
    return 'Webhook sent';
  }
}

const resolver = new Resolver<AsyncResolveTarget<[any], string, string>, string>(
  new SaveToDBHandler(),
  new SendWebhookHandler()
);

// Execute handlers in PARALLEL (fastest)
const results = await resolver.handleAllAsync('payment', { amount: 1000 });
// Results: ['Saved to DB', 'Webhook sent']
// Total time: ~200ms (not 300ms)

// Or execute SEQUENTIALLY (ordered, stops on error)
const results2 = await resolver.handleAllSequential('payment', { amount: 1000 });
// Results: ['Saved to DB', 'Webhook sent']
// Total time: ~300ms

New Methods:

  • handleAllAsync(type, ...args): Executes all matching async handlers in parallel
  • handleAllSequential(type, ...args): Executes all matching async handlers sequentially (stops on first error)

New Interfaces:

  • AsyncResolveTarget<TArgs, TReturn, TType>: For async handlers
  • PrioritizedAsyncResolveTarget<TArgs, TReturn, TType>: Combines async support with priority

4. Priority + Async Combined

You can combine priority and async support for powerful event processing pipelines:

import { PrioritizedAsyncResolveTarget } from 'class-resolver';

class ValidationHandler implements PrioritizedAsyncResolveTarget<[any], boolean, string> {
  priority = 100;

  supports(type: string): boolean {
    return type === 'order';
  }

  async handle(data: any): Promise<boolean> {
    return data.amount > 0;
  }
}

class ProcessHandler implements PrioritizedAsyncResolveTarget<[any], string, string> {
  priority = 50;

  supports(type: string): boolean {
    return type === 'order';
  }

  async handle(data: any): Promise<string> {
    return `Processed order ${data.id}`;
  }
}

const resolver = new Resolver<PrioritizedAsyncResolveTarget<[any], any, string>, string>(
  new ProcessHandler(),    // priority: 50
  new ValidationHandler()  // priority: 100
);

// Executes in priority order: Validation → Process
const results = await resolver.handleAllAsync('order', { id: 123, amount: 1000 });
// Results: [true, 'Processed order 123']

🎯 Use Cases

  1. Webhook Fanout: Process a single webhook event with multiple handlers (accounting, notifications, analytics)
  2. Event-Driven Architecture: Route events to multiple subscribers based on event type
  3. Validation Pipeline: Execute validation, business logic, and logging in priority order
  4. Async Workflows: Coordinate multiple async operations (DB saves, API calls, file operations)
  5. Plugin System: Implement a plugin system where different plugins handle specific types of operations

📦 Installation

npm install class-resolver@^4.0.0
# or
yarn add class-resolver@^4.0.0

📚 Documentation

Full documentation is available in the README.md.

🔗 Links

🙏 Thanks

Thank you to all contributors and users who have helped make this release possible!