Releases: wpkyoto/class-resolver
Release Notes - v4.0.1
Bug Fixes
- Fix ESM build compatibility: Removed
module.exportsfrom 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 cicompatibility issues
Dependencies
- Updated dev dependencies:
esbuild: Updated to latest version@vitest/coverage-v8: Updated to ^4.0.16@vitest/ui: Updated to ^4.0.16vitest: 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
v4.0.0: Major Release: Webhook & Event-Driven Routing
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
priorityproperty - 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 === 2New Methods:
resolveAll(type): Returns all matching handlers sorted by priorityhandleAll(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>: ExtendsResolveTargetwith optionalpriorityproperty
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: ~300msNew Methods:
handleAllAsync(type, ...args): Executes all matching async handlers in parallelhandleAllSequential(type, ...args): Executes all matching async handlers sequentially (stops on first error)
New Interfaces:
AsyncResolveTarget<TArgs, TReturn, TType>: For async handlersPrioritizedAsyncResolveTarget<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
- Webhook Fanout: Process a single webhook event with multiple handlers (accounting, notifications, analytics)
- Event-Driven Architecture: Route events to multiple subscribers based on event type
- Validation Pipeline: Execute validation, business logic, and logging in priority order
- Async Workflows: Coordinate multiple async operations (DB saves, API calls, file operations)
- 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
π§ 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.0Basic 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
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.stringifyfor 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 useJSON.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
Added
- Generic type support for custom type resolution: Added
TTypegeneric parameter toResolveTargetinterface - 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 thesupports()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 thehandle()method (unchanged)TReturn: Return type of thehandle()method (unchanged)TType: Type for thesupports()method (new, defaults tostring)
Backward Compatibility
- β Fully backward compatible: All existing code continues to work without changes
- β
Default behavior:
TTypedefaults tostring, 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> { ... }