npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@typescript-eda/infrastructure

v1.0.0

Published

Infrastructure adapters and implementations for TypeScript-EDA event-driven architecture

Readme

TypeScript-EDA Infrastructure

Infrastructure adapters and implementations for TypeScript-EDA event-driven architecture.

Overview

TypeScript-EDA Infrastructure provides the tools and patterns for building adapters that connect your pure domain logic to external systems. This package enables clean separation between domain concerns and infrastructure details while maintaining architectural integrity.

Features

  • @AdapterFor Decorator for explicit port-adapter mapping
  • Lifecycle Management with initialization, health checks, and shutdown
  • Configuration Management with validation and environment awareness
  • Error Translation from external systems to domain-specific errors
  • Monitoring Integration with metrics and structured logging
  • Testing Utilities for adapter contract and integration testing

Installation

npm install @typescript-eda/infrastructure @typescript-eda/domain
# or
pnpm add @typescript-eda/infrastructure @typescript-eda/domain

Quick Start

Define Your Domain Port

import { Port } from '@typescript-eda/domain';

export abstract class NotificationPort extends Port {
  public readonly name = 'NotificationPort';
  
  public abstract sendEmail(to: Email, subject: string, body: string): Promise<void>;
}

Create an Infrastructure Adapter

import { AdapterFor } from '@typescript-eda/infrastructure';

@AdapterFor(NotificationPort)
export class SendGridNotificationAdapter extends NotificationPort {
  constructor(private apiKey: string, private httpClient: HttpClient) {
    super();
  }

  public async initialize(): Promise<void> {
    // Verify configuration and connectivity
    await this.httpClient.get('https://api.sendgrid.com/v3/user/profile', {
      headers: { 'Authorization': `Bearer ${this.apiKey}` }
    });
    console.log('✅ SendGrid adapter initialized');
  }

  public async sendEmail(to: Email, subject: string, body: string): Promise<void> {
    try {
      await this.httpClient.post('https://api.sendgrid.com/v3/mail/send', {
        headers: { 'Authorization': `Bearer ${this.apiKey}` },
        body: JSON.stringify({
          personalizations: [{ to: [{ email: to.getValue() }], subject }],
          from: { email: '[email protected]' },
          content: [{ type: 'text/html', value: body }]
        })
      });
    } catch (error) {
      throw new NotificationDeliveryError(`Failed to send email: ${error.message}`, error);
    }
  }

  public async shutdown(): Promise<void> {
    console.log('📧 SendGrid adapter shutdown');
  }

  public async isHealthy(): Promise<boolean> {
    try {
      await this.httpClient.get('https://api.sendgrid.com/v3/user/profile', {
        headers: { 'Authorization': `Bearer ${this.apiKey}` }
      });
      return true;
    } catch {
      return false;
    }
  }
}

Use in Your Application

import { Application, Enable } from '@typescript-eda/application';

@Enable(SendGridNotificationAdapter)
export class MyApplication extends Application {
  public readonly metadata = new Map([
    ['name', 'MyApplication'],
    ['description', 'Application with email notifications']
  ]);
}

Core Concepts

Adapters

Adapters implement domain ports and handle communication with external systems:

  • Database Adapters: PostgreSQL, MongoDB, Redis repositories
  • API Adapters: REST, GraphQL, gRPC service clients
  • Message Adapters: RabbitMQ, Kafka, SQS message handling
  • File Adapters: S3, local filesystem, document storage
  • Authentication Adapters: JWT, OAuth, API key validation

@AdapterFor Decorator

The @AdapterFor decorator creates explicit mappings between ports and adapters:

@AdapterFor(UserRepository)
export class PostgresUserRepository extends UserRepository {
  // Implementation that translates domain operations to SQL
}

This enables:

  • Automatic discovery and dependency injection
  • Clear contracts between domain and infrastructure
  • Type-safe adapter registration
  • Runtime validation of adapter implementations

Lifecycle Management

All adapters implement a standard lifecycle:

export abstract class Port {
  public abstract initialize(): Promise<void>;    // Startup configuration
  public abstract shutdown(): Promise<void>;      // Graceful cleanup
  public abstract isHealthy(): Promise<boolean>;  // Health monitoring
}

Error Translation

Adapters translate external errors to domain-specific errors:

private translateApiError(error: any): Error {
  if (error.status === 401) {
    return new AuthenticationError('API authentication failed');
  }
  if (error.status === 429) {
    return new RateLimitError('API rate limit exceeded', error.headers['retry-after']);
  }
  return new ExternalServiceError(`API error: ${error.message}`, error);
}

Adapter Patterns

Repository Adapters

Repository adapters provide domain-friendly data access:

@AdapterFor(UserRepository)
export class PostgresUserRepository extends UserRepository {
  public async findByEmail(email: Email): Promise<User | null> {
    const result = await this.connection.query(
      'SELECT * FROM users WHERE email = $1',
      [email.getValue()]
    );
    return result.rows.length > 0 ? this.mapToUser(result.rows[0]) : null;
  }

  private mapToUser(row: any): User {
    return new User(
      new UserId(row.id),
      new Email(row.email),
      new UserName(row.first_name, row.last_name),
      new UserStatus(row.status)
    );
  }
}

API Client Adapters

API adapters handle external service integration:

@AdapterFor(PaymentPort)
export class StripePaymentAdapter extends PaymentPort {
  public async processPayment(payment: Payment): Promise<PaymentResult> {
    try {
      const intent = await this.stripe.paymentIntents.create({
        amount: payment.getAmount().getAmount() * 100,
        currency: payment.getAmount().getCurrency(),
        payment_method: payment.getPaymentMethod().getStripeId()
      });
      
      return new PaymentResult(
        new PaymentId(intent.id),
        PaymentStatus.fromStripeStatus(intent.status)
      );
    } catch (error) {
      throw this.translateStripeError(error);
    }
  }
}

Message Queue Adapters

Message adapters enable event-driven communication:

@AdapterFor(EventBusPort)
export class RabbitMQEventBusAdapter extends EventBusPort {
  public async publishEvent(event: Event): Promise<void> {
    const message = Buffer.from(JSON.stringify(event.toJSON()));
    await this.channel.publish('domain-events', event.type, message);
  }

  public async subscribeToEvents(handler: EventHandler): Promise<void> {
    await this.channel.consume('event-queue', async (msg) => {
      if (msg) {
        const event = this.deserializeEvent(msg.content);
        await handler(event);
        this.channel.ack(msg);
      }
    });
  }
}

Configuration Management

Environment-Aware Configuration

export class InfrastructureConfig {
  public readonly database: DatabaseConfig;
  public readonly notification: NotificationConfig;

  constructor() {
    this.database = this.loadDatabaseConfig();
    this.notification = this.loadNotificationConfig();
    this.validate();
  }

  private validate(): void {
    if (!this.database.password) {
      throw new ConfigurationError('Database password is required');
    }
    // Additional validations...
  }
}

Adapter Factories

Factories create adapters based on configuration:

export class AdapterFactory {
  public createNotificationAdapter(): NotificationPort {
    switch (this.config.notification.provider) {
      case 'sendgrid':
        return new SendGridNotificationAdapter(this.config.notification.apiKey);
      case 'smtp':
        return new SMTPNotificationAdapter(this.config.notification.smtp);
      default:
        return new ConsoleNotificationAdapter();
    }
  }
}

Testing

Contract Testing

Test that adapters fulfill their port contracts:

describe('NotificationAdapter Contract', () => {
  const adapters = [
    new SendGridAdapter(apiKey, httpClient),
    new SMTPAdapter(smtpConfig),
    new ConsoleAdapter()
  ];

  adapters.forEach(adapter => {
    describe(`${adapter.constructor.name}`, () => {
      it('should send email successfully', async () => {
        await expect(adapter.sendEmail(email, 'Subject', 'Body'))
          .resolves.not.toThrow();
      });
    });
  });
});

Integration Testing

Test real integration with external systems:

describe('PostgresRepository Integration', () => {
  let container: StartedTestContainer;
  let repository: PostgresUserRepository;

  beforeAll(async () => {
    container = await new PostgreSqlContainer().start();
    repository = new PostgresUserRepository(createConnection(container));
    await repository.initialize();
  });

  afterAll(async () => {
    await repository.shutdown();
    await container.stop();
  });

  it('should persist and retrieve users', async () => {
    const user = createTestUser();
    await repository.save(user);
    
    const retrieved = await repository.findById(user.id);
    expect(retrieved?.id.equals(user.id)).toBe(true);
  });
});

Best Practices

Adapter Design

  1. Single Responsibility: Each adapter handles one external system
  2. Error Translation: Convert external errors to domain-specific errors
  3. Configuration Validation: Validate configuration at initialization
  4. Resource Management: Implement proper lifecycle management
  5. Monitoring: Include health checks and performance metrics

Error Handling

// Good: Domain-specific error translation
private handlePaymentError(error: any): never {
  if (error.type === 'card_error') {
    throw new PaymentDeclinedError(error.message, error.decline_code);
  }
  if (error.type === 'rate_limit_error') {
    throw new RateLimitError('Payment service unavailable', error.retry_after);
  }
  throw new PaymentProcessingError(`Unexpected error: ${error.message}`, error);
}

Configuration

// Good: Comprehensive validation with helpful messages
private validateConfig(): void {
  const errors: string[] = [];
  
  if (!this.apiKey) {
    errors.push('API key is required');
  }
  
  if (this.timeout < 1000) {
    errors.push('Timeout must be at least 1000ms');
  }
  
  if (errors.length > 0) {
    throw new ConfigurationError(
      'Configuration errors:\n' + errors.map(e => `  - ${e}`).join('\n')
    );
  }
}

Examples

See the examples directory for complete implementations:

  • Database Integration: PostgreSQL, MongoDB, Redis adapters
  • API Integration: REST, GraphQL client adapters
  • Message Queues: RabbitMQ, Kafka event bus adapters
  • File Storage: S3, local filesystem adapters
  • Authentication: JWT, OAuth, API key adapters

Documentation

Related Packages

TypeScript-EDA Infrastructure is part of the TypeScript-EDA ecosystem:

Contributing

We welcome contributions! Please see our Contributing Guide for details.

License

This project is licensed under the GPL-3.0 License - see the LICENSE file for details.

Support