Skip to content

Releases: wpkyoto/class-resolver

Release Notes - v4.0.1

02 Jan 10:44

Choose a tag to compare

Bug Fixes

  • Fix ESM build compatibility: Removed module.exports from ESM build output to ensure proper ES module compatibility (#29)
  • Fix CI compatibility: Updated CI configuration to use Node.js 22.x only to resolve npm ci compatibility issues

Dependencies

  • Updated dev dependencies:
    • esbuild: Updated to latest version
    • @vitest/coverage-v8: Updated to ^4.0.16
    • @vitest/ui: Updated to ^4.0.16
    • vitest: Updated to ^4.0.16
      (#30)

Internal Changes

  • Refactoring: Moved verify-build script to a separate file for better maintainability
  • Chore: Updated package-lock.json to fix CI and npm compatibility issues

Full Changelog

Compare v4.0.0...v4.0.1

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

17 Nov 12:21

Choose a tag to compare

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!

Release Notes - v2.2.0

25 Aug 14:18

Choose a tag to compare

πŸ”§ Fallback Handler Improvements

This release focuses on improving the fallback handler functionality and error message formatting for better debugging experience.

πŸ”„ Migration Guide

From v2.1.x

No breaking changes! This release is fully backward compatible. You can continue using your existing code without modifications.

What's Different

  • Error messages for unsupported object types will now show detailed object information
  • Fallback handler integration is more robust

πŸš€ Getting Started

Installation

npm install class-resolver@2.2.0

Basic Usage (Unchanged)

import Resolver from 'class-resolver';

const resolver = new Resolver<string, string>();
resolver.addUpdater({
  supports: (type: string) => type === 'test',
  handle: (input: string) => `Processed: ${input}`
});

// Set fallback handler
resolver.setFallbackHandler((type: string) => `Fallback: ${type}`);

// Enhanced error handling for unsupported types
try {
  const result = resolver.resolve('unsupported');
} catch (error) {
  // Now provides more detailed error information for objects
  console.log(error.message);
}

Thank you for using class-resolver! πŸŽ‰

If you find this release helpful, please consider giving us a ⭐ on GitHub.

v2.1.1

25 Aug 05:38

Choose a tag to compare

Fixed

  • Fixed typo in error message: "Unasigned resolve target." β†’ "Unassigned resolve target."
  • Improved error messages for unsupported types to provide more detailed information
  • Enhanced debugging experience by using JSON.stringify for object types instead of generic [object Object] representation

Changed

  • Error messages now display detailed object information when an unsupported object type is passed to the resolver
  • More informative error messages help developers quickly identify and resolve type-related issues

Technical Details

  • Modified Resolver.resolve() method to conditionally use JSON.stringify() for object types
  • Added comprehensive error handling with type-aware string representation
  • Updated test cases to reflect the improved error message format

v.2.1.0

25 Aug 05:34

Choose a tag to compare

Added

  • Generic type support for custom type resolution: Added TType generic parameter to ResolveTarget interface
  • Custom object type handling: Support for complex objects beyond simple strings in the supports() method
  • Domain-specific object resolution: Ability to handle Stripe events, database records, or custom business objects
  • Enhanced type safety: Full TypeScript support for custom types while maintaining backward compatibility
  • Comprehensive test coverage: Added tests demonstrating Stripe Event handling with complex types

Changed

  • Interface signature: ResolveTarget<TArgs, TReturn, TType> now supports custom types for the supports() method
  • Resolver class: Updated to support custom types through generic type parameters
  • Documentation: Enhanced README with advanced type support examples and usage patterns

Technical Details

  • Type Parameters:
    • TArgs: Arguments array for the handle() method (unchanged)
    • TReturn: Return type of the handle() method (unchanged)
    • TType: Type for the supports() method (new, defaults to string)

Backward Compatibility

  • βœ… Fully backward compatible: All existing code continues to work without changes
  • βœ… Default behavior: TType defaults to string, maintaining existing functionality
  • βœ… Existing interfaces: ResolveTarget<[], string> syntax remains unchanged

Examples

Before (v2.0.0)

class StringHandler implements ResolveTarget<[], string> {
  supports(type: string): boolean {
    return type === 'my-type';
  }
  handle(): string {
    return 'result';
  }
}

After (v2.1.0) - Enhanced

interface CustomEvent {
  type: string;
  data: any;
}

class CustomHandler implements ResolveTarget<[CustomEvent], string, CustomEvent> {
  supports(event: CustomEvent): boolean {
    return event.type === 'payment_intent.succeeded';
  }
  
  handle(event: CustomEvent): string {
    return `Handled: ${event.type}`;
  }
}

Use Cases

  • Stripe Integration: Handle different Stripe event types with type-safe resolution
  • Database Operations: Resolve handlers based on complex database record types
  • Business Logic: Create domain-specific object resolution systems
  • Event Processing: Handle custom event objects with full type safety

Migration Guide

No migration required for existing code. To use new features, simply add the third type parameter:

// Existing code (continues to work)
class MyHandler implements ResolveTarget<[], string> { ... }

// New enhanced code
class MyHandler implements ResolveTarget<[], string, CustomType> { ... }