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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@xlr8-nest/core

v2.1.0

Published

NestJS utility library with DDD, CQRS, TypeORM extensions, OpenAPI decorators, and Zod validation

Downloads

955

Readme

@xlr8-nest/core

A comprehensive NestJS utility library with DDD, CQRS, TypeORM extensions, OpenAPI decorators, and Zod validation.

npm version License: MIT TypeScript NestJS

✨ Features

  • 🏗️ Event Module & CQRS: Domain events, aggregate roots, and Command/Query buses
  • 📦 Unit of Work: TypeORM extensions with UoW pattern using AsyncLocalStorage
  • 🔄 Event-Driven: Domain events with Saga pattern support
  • 📬 Outbox & Messaging: Transactional outbox pattern with background worker, retries, and pluggable brokers
  • 📝 OpenAPI: Pre-configured Swagger decorators for standardized API documentation
  • Validation: Seamless Zod integration with NestJS pipes
  • 🗄️ Database: Migration & Seeder services with CLI commands
  • 🎯 Type-Safe: Full TypeScript support with comprehensive type definitions
  • 🔧 Modular: Tree-shakeable exports, import only what you need

📦 Installation

# npm
npm install @xlr8-nest/core

# yarn
yarn add @xlr8-nest/core

# pnpm
pnpm add @xlr8-nest/core

🔄 Version Compatibility

This library supports a wide range of NestJS and dependency versions for maximum flexibility:

| Package | Supported Versions | | ----------------------- | -------------------------------------------------------------------- | | @nestjs/common | ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0 | | @nestjs/core | ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0 | | @nestjs/swagger | ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 | | @nestjs/typeorm | ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0 | | @nestjs/event-emitter | ^2.0.0 || ^3.0.0 || ^4.0.0 | | rxjs | ^7.0.0 || ^8.0.0 | | typeorm | ^0.3.0 || ^0.4.0 | | zod | ^3.0.0 || ^4.0.0 || ^5.0.0 | | reflect-metadata | ^0.1.13 || ^0.2.0 || ^0.3.0 |

Tested with latest versions: NestJS 11, Swagger 11, Zod 4, Event Emitter 3

🧩 Standalone Imports

Use standalone subpath imports when you only need constants or utilities:

import { StatusCode, CommonErrors } from '@xlr8-nest/core/constants';
import { validateInput } from '@xlr8-nest/core/utils';

@xlr8-nest/core/constants has no runtime peer dependencies. @xlr8-nest/core/utils currently contains Zod/Nest validation helpers, so install @nestjs/common and zod when using it.

🚀 Quick Start

1. Event Module

import { Module } from '@nestjs/common';
import { EventModule } from '@xlr8-nest/core/ddd';

@Module({
  imports: [
    EventModule.forRoot({
      wildcard: false,
      delimiter: '.',
      maxListeners: 10,
    }),
  ],
})
export class AppModule {}

2. Database Module with TypeORM

import { DatabaseExtensionModule } from '@xlr8-nest/core/database';

@Module({
  imports: [
    DatabaseExtensionModule.register({
      connection: {
        type: 'postgres',
        host: 'localhost',
        port: 5432,
        username: 'user',
        password: 'pass',
        database: 'mydb',
      },
      migration: {
        enabled: true,
        autoRun: false,
        migrationsPath: 'src/database/migrations',
      },
      seeder: {
        enabled: true,
        seedersPath: 'src/database/seeders',
      },
    }),
  ],
})
export class AppModule {}

3. OpenAPI Decorators

import { Controller, Get, Post, Body } from '@nestjs/common';
import { ApiGet, ApiPost } from '@xlr8-nest/core/openapi';
import { CreateUserDto, UserDto } from './dto';

@Controller('users')
export class UserController {
  @Post()
  @ApiPost(UserDto, { summary: 'Create a new user' })
  async create(@Body() dto: CreateUserDto) {
    return this.userService.create(dto);
  }

  @Get()
  @ApiGet(UserDto, { summary: 'Get paginated users', paginated: true })
  async findAll() {
    return this.userService.findAll();
  }
}

4. Zod Validation

import { Validate } from '@xlr8-nest/core/validator';
import { z } from 'zod';

const createUserSchema = z.object({
  email: z.string().email(),
  name: z.string().min(2),
  age: z.number().min(18).optional(),
});

@Controller('users')
export class UserController {
  @Post()
  @Validate(createUserSchema)
  async create(@Body() dto: z.infer<typeof createUserSchema>) {
    return this.userService.create(dto);
  }
}

📚 Modules

This package provides multiple modules that can be imported individually:

🏗️ Event Module & CQRS (@xlr8-nest/core/ddd)

Domain model primitives, EventEmitter-backed domain event handlers, and optional CQRS buses for NestJS.

import {
  AggregateRoot,
  Entity,
  ValueObject,
  DomainEvent,
  CommandBus,
  QueryBus,
  EventBus,
  EventModule,
  CqrsModule,
} from '@xlr8-nest/core/ddd';

Features:

  • EventModule - EventEmitter infrastructure for domain event handlers
  • AggregateRoot - Base class that records domain events and exposes pullEvents() for publishing
  • Entity - Base entity class with identity management
  • ValueObject - Abstract value object base class
  • EventBus - Publishes pulled aggregate events to @EventHandler() handlers and sagas
  • CommandBus / QueryBus - CQRS buses with automatic handler discovery
  • Decorators: @Event(), @EventHandler(), @CommandHandler(), @QueryHandler(), @Saga()

Aggregate roots only collect events. Use protected addEvent() inside aggregate methods, then publish with EventBus by calling public pullEvents() after the aggregate has been persisted. pullEvents() returns the recorded events and clears the aggregate event list.

Example:

// Define domain event
@Event()
export class UserCreatedEvent implements DomainEvent {
  constructor(
    public readonly userId: string,
    public readonly occurredOn = new Date(),
  ) {}
  get eventName() {
    return getEventName(this);
  }
}

@Event()
export class UserEmailChangedEvent implements DomainEvent {
  constructor(
    public readonly userId: string,
    public readonly email: string,
    public readonly occurredOn = new Date(),
  ) {}
  get eventName() {
    return getEventName(this);
  }
}

// Create aggregate root
export class User extends AggregateRoot<string> {
  constructor(
    id: string,
    private name: string,
    private email: string,
  ) {
    super(id);
  }

  static create(name: string, email: string): User {
    const user = new User(uuid(), name, email);
    user.addEvent(new UserCreatedEvent(user.id));
    return user;
  }

  changeEmail(email: string): void {
    this.email = email;
    this.addEvent(new UserEmailChangedEvent(this.id, email));
  }
}

// Command handler
@CommandHandler(CreateUserCommand)
export class CreateUserHandler implements ICommandHandler<CreateUserCommand> {
  constructor(
    private readonly repository: UserRepository,
    private readonly eventBus: EventBus,
  ) {}

  async execute(command: CreateUserCommand) {
    const user = User.create(command.name, command.email);
    await this.repository.save(user);
    await this.eventBus.publishAll(user.pullEvents());
    return user;
  }
}

// Event handler
@Injectable()
export class UserEventHandlers {
  @EventHandler(UserCreatedEvent)
  async onUserCreated(event: UserCreatedEvent) {
    console.log('User created:', event.userId);
  }
}

🗄️ Database (@xlr8-nest/core/database)

TypeORM extension module with Unit of Work pattern.

import {
  DatabaseExtensionModule,
  TypeOrmClient,
  IUnitOfWork,
  IUnitOfWorkToken,
  InjectUnitOfWork,
  UnitOfWork,
  MigrationService,
  SeederService,
  BaseSeeder,
  BaseFactory,
  BaseOrm,
} from '@xlr8-nest/core/database';

Features:

  • Unit of Work pattern with AsyncLocalStorage
  • Migration & Seeder services with CLI commands
  • Base classes for seeders, factories, and ORM entities (BaseOrm for partial-constructor TypeORM entities)
  • Support for PostgreSQL, MySQL, MariaDB, SQLite, MSSQL
  • Auto-run migrations and seeders
  • Transaction isolation per request

Example:

// Unit of Work usage
@Injectable()
export class UserService {
  constructor(@UnitOfWork() private readonly uow: IUnitOfWork) {}

  async createUser(dto: CreateUserDto) {
    const user = this.uow.getRepository(User).create(dto);
    await this.uow.getRepository(User).save(user);

    // Transaction is automatically managed
    await this.uow.commit();
    return user;
  }
}

// Create seeder
export class UserSeeder extends BaseSeeder {
  async run() {
    const users = [
      { name: 'John', email: '[email protected]' },
      { name: 'Jane', email: '[email protected]' },
    ];

    await this.manager.save(User, users);
  }
}

// Use BaseOrm for partial-constructor TypeORM entities
@Entity('users')
export class UserOrm extends BaseOrm<UserOrm> {
  @PrimaryColumn({ type: 'uuid' })
  id: string;

  @Column()
  email: string;

  @Column()
  name: string;
}

// Construct with any subset of fields — useful for adapter mapping
const orm = new UserOrm({ id, email });

📬 Messaging (@xlr8-nest/core/messaging)

Transactional outbox pattern for reliable cross-service event delivery. Domain events raised by your aggregates are translated into versioned integration events and persisted to an outbox table inside the same DB transaction as the aggregate state — guaranteeing at-least-once delivery to downstream services.

import {
  MessagingModule,
  OutboxPublisher,
  IntegrationEvent,
  IDomainEventTranslator,
  IMessagePublisher,
  OutboxEventRecord,
  ConsoleMessagePublisher,
} from '@xlr8-nest/core/messaging';

Features:

  • Two clearly separated event types:
    • Domain events (in-process, transient) — already provided by @xlr8-nest/core/ddd
    • Integration events (cross-service, durable) — new contracts versioned per consumer
  • IntegrationEvent base class with stable id, eventName, aggregateType, aggregateId, occurredAt
  • IDomainEventTranslator pattern to map domain events → 0+ integration events
  • OutboxPublisher: pulls events from an aggregate, translates, and atomically stashes them inside the active UnitOfWork.transaction()
  • OutboxEventOrm mapping for the outbox_events table (you write the migration)
  • OutboxWorker: background poller with exponential backoff retry (configurable interval, batch size, backoff)
  • Concurrent worker support via SELECT ... FOR UPDATE SKIP LOCKED
  • Pluggable IMessagePublisher — default ConsoleMessagePublisher logs to stdout; swap for Kafka, RabbitMQ, SNS, NATS, etc.
  • One-line module wiring via MessagingModule.forRoot({ translators, messagePublisher, worker })

The flow:

Application handler:
  uow.transaction(async () => {
    await repo.save(aggregate);          // step 1: persist aggregate
    await outbox.publishFrom(aggregate); // step 2: pull events → translate → stash to outbox
  });                                    // step 3: COMMIT (atomic)

Background:
  OutboxWorker polls outbox → MessagePublisher.publish() → mark PUBLISHED
                                                     └── on failure → exponential backoff retry

Example:

import { Injectable, Inject } from '@nestjs/common';
import {
  IntegrationEvent,
  IDomainEventTranslator,
  OutboxPublisher,
  MessagingModule,
} from '@xlr8-nest/core/messaging';
import {
  IUnitOfWork,
  IUnitOfWorkToken,
} from '@xlr8-nest/core/database';
import { CommandHandler, EventBus, DomainEvent } from '@xlr8-nest/core/ddd';

// 1. Define an integration event (the cross-service contract)
export class UserRegisteredIntegrationEvent extends IntegrationEvent {
  readonly eventName = 'user.registered.v1';
  readonly aggregateType = 'user';
  readonly aggregateId: string;

  constructor(
    public readonly userId: string,
    public readonly email: string,
  ) {
    super();
    this.aggregateId = userId;
  }
}

// 2. Define a translator: domain event → integration event(s)
@Injectable()
export class UserEventTranslator implements IDomainEventTranslator {
  supports(eventName: string): boolean {
    return eventName === 'user.created';
  }

  translate(event: DomainEvent): IntegrationEvent[] {
    if (event instanceof UserCreatedEvent) {
      return [new UserRegisteredIntegrationEvent(event.userId, event.email)];
    }
    return [];
  }
}

// 3. Wire up the messaging module
@Module({
  imports: [
    MessagingModule.forRoot({
      translators: [UserEventTranslator],
      // messagePublisher: KafkaMessagePublisher, // optional; defaults to ConsoleMessagePublisher
      // worker: { pollIntervalMs: 2000, batchSize: 25 }, // optional tuning
    }),
  ],
})
export class AppModule {}

// 4. Use the OutboxPublisher in your command handler
@CommandHandler(CreateUserCommand)
export class CreateUserHandler implements ICommandHandler<CreateUserCommand> {
  constructor(
    private readonly userRepo: UserRepository,
    @Inject(IUnitOfWorkToken) private readonly uow: IUnitOfWork,
    private readonly outbox: OutboxPublisher,
    private readonly eventBus: EventBus,
  ) {}

  async execute(command: CreateUserCommand) {
    const user = User.create(command.name, command.email);

    // Single atomic transaction: aggregate + integration events committed together
    const domainEvents = await this.uow.transaction(async () => {
      await this.userRepo.save(user);
      return this.outbox.publishFrom(user);
    });

    // After commit, dispatch domain events to in-process listeners
    // (logging, metrics, projectors). Cross-service delivery is already in the outbox.
    for (const event of domainEvents) {
      this.eventBus.publish(event);
    }
  }
}

Custom message publisher (Kafka example):

@Injectable()
export class KafkaMessagePublisher implements IMessagePublisher {
  constructor(private readonly kafka: KafkaClient) {}

  async publish(record: OutboxEventRecord): Promise<void> {
    await this.kafka.send({
      topic: record.eventName,
      key: record.aggregateId,
      messages: [{
        value: JSON.stringify(record.payload),
        headers: { 'event-id': record.id }, // stable across retries — use as dedupe key
      }],
    });
    // throw on broker rejection so the worker records the failure and schedules a retry
  }
}

Outbox CLI (outbox command):

The messaging module ships an outbox CLI command (powered by nest-commander) that you wire into your existing CLI bootstrap alongside migration and seed:

// cli.ts
import { CommandFactory } from 'nest-commander';
import { CliModule } from './cli.module';

async function bootstrap() {
  await CommandFactory.run(CliModule, ['warn', 'error', 'log']);
}
bootstrap();
// cli.module.ts
@Module({
  imports: [
    ConfigModule.forRoot({ isGlobal: true }),
    DatabaseExtensionModule.registerAsync({ useFactory: () => ({ /* ... */ }) }),
    MessagingModule.forRoot({
      translators: [/* ... */],
      // cli: true is the default — the `outbox` command is registered automatically
    }),
  ],
})
export class CliModule {}

Then in package.json:

{
  "scripts": {
    "cli": "ts-node -r tsconfig-paths/register src/cli.ts"
  }
}

Available outbox commands:

# Generate the outbox_events table migration into the configured migrations path
npm run cli -- outbox migration
npm run cli -- outbox migration --name AddOutboxTable
npm run cli -- outbox migration --path ./src/db/migrations

# Show counts (pending / published / failed / due-now)
npm run cli -- outbox status

# Reset all FAILED events back to PENDING for immediate retry
npm run cli -- outbox requeue-failed

# Help
npm run cli -- outbox

Example output of outbox status:

📊 Outbox Status

  Pending:   3
  Published: 142
  Failed:    1
  Due now:   3  (will be picked up by the next worker tick)

⚠️  There are failed events. Inspect them, then run:
     <prefix> outbox requeue-failed

Using OutboxAdminService programmatically:

The same logic is exposed via OutboxAdminService for non-CLI use (admin dashboards, health checks, etc.):

@Injectable()
export class AdminService {
  constructor(private readonly outboxAdmin: OutboxAdminService) {}

  async getOutboxHealth() {
    const stats = await this.outboxAdmin.getStats();
    return {
      backlog: stats.pending,
      stuck: stats.failed,
      throughput: stats.published,
    };
  }
}

Required migration in your service:

The CLI command above generates this for you. You can also create it by hand — the schema must match OutboxEventOrm:

CREATE TYPE outbox_events_status_enum AS ENUM ('pending', 'published', 'failed');

CREATE TABLE outbox_events (
  id              uuid PRIMARY KEY,
  event_name      varchar(255) NOT NULL,
  aggregate_type  varchar(100) NOT NULL,
  aggregate_id    varchar(255) NOT NULL,
  payload         jsonb NOT NULL,
  status          outbox_events_status_enum NOT NULL DEFAULT 'pending',
  retry_count     integer NOT NULL DEFAULT 0,
  next_attempt_at timestamptz NOT NULL,
  last_error      text,
  occurred_at     timestamptz NOT NULL,
  published_at    timestamptz,
  created_at      timestamptz NOT NULL DEFAULT now(),
  updated_at      timestamptz NOT NULL DEFAULT now()
);

CREATE INDEX idx_outbox_events_due ON outbox_events (status, next_attempt_at);

Retry strategy:

Failed publishes are retried with exponential backoff (defaults: 30s → 60s → 2m → 4m → ... capped at 1 hour). After 10 failed attempts the row transitions to status='failed' and is no longer retried automatically — surface these for operator inspection via a dashboard query on WHERE status = 'failed'.

🚨 Errors (@xlr8-nest/core/errors)

Standardized error classes that extend NestJS exceptions.

import {
  BadRequestError,
  NotFoundError,
  UnauthorizedError,
  ForbiddenError,
  ConflictError,
  InternalServerError,
} from '@xlr8-nest/core/errors';

Example:

@Injectable()
export class UserService {
  async findById(id: string) {
    const user = await this.repository.findOne({ where: { id } });
    if (!user) {
      throw new NotFoundError({
        code: 'USER_NOT_FOUND',
        message: `User with id ${id} not found`,
      });
    }
    return user;
  }
}

📝 OpenAPI (@xlr8-nest/core/openapi)

Swagger documentation decorators with standardized response format.

import {
  ApiDelete,
  ApiGet,
  ApiMethod,
  ApiPatch,
  ApiPost,
  ApiPut,
  ApiError,
  ApiBadRequest,
  ApiConflict,
  ApiNotFound,
} from '@xlr8-nest/core/openapi';

Features:

  • HTTP method-based decorators with wrapped response format
  • Error response decorators
  • Custom success/error wrapper factories
  • Pagination support via ApiGet(..., { paginated: true })
  • Automatic schema generation

Breaking change migration:

| Removed decorator | Use instead | | ----------------- | ---------------------------------- | | ApiCreate | ApiPost | | ApiGetOne | ApiGet | | ApiGetMany | ApiGet(..., { isArray: true }) | | ApiGetPaginated | ApiGet(..., { paginated: true }) | | ApiUpdate | ApiPatch or ApiPut | | ApiAction | ApiMethod |

ApiDelete is still available, but it now follows the HTTP method-based API and defaults to 200.

Example:

@Controller('users')
@ApiTags('users')
export class UserController {
  @Post()
  @ApiPost(UserDto, {
    summary: 'Create a new user',
    description: 'Creates a new user with the provided data',
  })
  @ApiBadRequest({
    error: {
      code: 'VALIDATION_ERROR',
      message: 'Invalid input data',
    },
    includeErrors: true,
  })
  @ApiConflict({
    error: {
      code: 'USER_EMAIL_EXISTS',
      message: 'Email already registered',
    },
  })
  async create(@Body() dto: CreateUserDto) {
    return this.userService.create(dto);
  }

  @Get(':id')
  @ApiGet(UserDto, { summary: 'Get user by ID' })
  @ApiNotFound()
  async findOne(@Param('id') id: string) {
    return this.userService.findById(id);
  }

  @Get()
  @ApiGet(UserDto, { summary: 'Get paginated users', paginated: true })
  async findAll(@Query() query: PaginationDto) {
    return this.userService.findAll(query);
  }

  @Delete(':id')
  @ApiDelete(null, { summary: 'Delete user' })
  async delete(@Param('id') id: string) {
    await this.userService.delete(id);
    return null;
  }
}

Use null or undefined as the response data type for endpoints whose wrapped response has data: null.

5. Response Builders

import {
  buildSuccessResponse,
  buildErrorResponse,
  normalizeUnknownException,
  type ApiFailure,
  type ApiSuccess,
  type ErrorType,
} from '@xlr8-nest/core/response';
import { NotFoundError } from '@xlr8-nest/core/errors';

const USER_NOT_FOUND_ERROR: ErrorType<'USER_NOT_FOUND'> = {
  code: 'USER_NOT_FOUND',
  message: 'User not found',
};

@Get(':id')
async findOne(@Param('id') id: string): Promise<ApiSuccess<UserDto>> {
  const user = await this.userService.findById(id);
  return buildSuccessResponse(user, {
    message: 'User retrieved successfully',
  });
}

function buildNotFoundBody(): ApiFailure {
  return buildErrorResponse(new NotFoundError(USER_NOT_FOUND_ERROR));
}

function normalizeException(exception: unknown): ApiFailure {
  return buildErrorResponse(exception, {
    fallbackError: {
      code: 'INTERNAL_SERVER_ERROR',
      message: 'Unexpected error',
    },
  }).body;
}

Custom wrapper example:

const auditWrapper = ({ defaultSchema, defaultExtraModels }) => ({
  schema: {
    ...defaultSchema,
    properties: {
      ...defaultSchema.properties,
      traceId: { type: 'string', example: 'req_123' },
    },
    required: [...(defaultSchema.required ?? []), 'traceId'],
  },
  extraModels: defaultExtraModels,
});

@Post()
@ApiPost(UserDto, {
  summary: 'Create user',
  wrapper: auditWrapper,
})
@ApiBadRequest({
  includeErrors: true,
  wrapper: auditWrapper,
})
async create(@Body() dto: CreateUserDto) {
  return this.userService.create(dto);
}

📐 Types (@xlr8-nest/core/types)

Shared TypeScript types and interfaces.

import {
  Response,
  SuccessResponse,
  ErrorResponse,
  ApiResult,
  ApiSuccess,
  ApiFailure,
  UserIdentity,
  PaginationMeta,
} from '@xlr8-nest/core/types';

📦 Response (@xlr8-nest/core/response)

Response builders for standardized success/error payloads.

import {
  buildSuccessResponse,
  buildErrorResponse,
  normalizeUnknownException,
  type ApiResult,
  type ApiSuccess,
  type ApiFailure,
  type SuccessResponse,
} from '@xlr8-nest/core/response';

Features:

  • Type-safe success response builder for controller/service returns
  • Type-safe error response builder that derives payloads from exceptions
  • Exception normalizer for BaseError, Nest HttpException, and unknown errors
  • Short aliases for controller signatures: ApiSuccess<T>, ApiFailure<T>, ApiResult<T>

Recommended naming for controller return types:

  • Prefer ApiSuccess<T> when the controller method only returns the success body.
  • Prefer ApiResult<T> when you want one shared API contract type across app layers or tests.
  • Avoid using bare Response<T> in controllers if your app also imports Nest/Express Response, because the name can be confusing.
  • For error builders and decorators, prefer passing a single ErrorType object instead of separate code and message.

Example:

@Controller('users')
export class UserController {
  @Get(':id')
  async findOne(@Param('id') id: string): Promise<ApiSuccess<UserDto>> {
    const user = await this.userService.findById(id);

    return buildSuccessResponse(user, {
      message: 'User retrieved successfully',
    });
  }

  @Get()
  async findAll(): Promise<ApiSuccess<UserDto[]>> {
    const users = await this.userService.findAll();

    return buildSuccessResponse(users);
  }
}

Function reference:

import {
  buildSuccessResponse,
  buildErrorResponse,
  normalizeUnknownException,
  type ErrorType,
} from '@xlr8-nest/core/response';
import { NotFoundError } from '@xlr8-nest/core/errors';

const USER_NOT_FOUND: ErrorType<'USER_NOT_FOUND'> = {
  code: 'USER_NOT_FOUND',
  message: 'User not found',
};

const success = buildSuccessResponse({ id: 'u_1' });

const error = buildErrorResponse(new NotFoundError(USER_NOT_FOUND));

const normalized = normalizeUnknownException(exception, {
  fallbackError: {
    code: 'INTERNAL_SERVER_ERROR',
    message: 'Unexpected error',
  },
  customErrorFactory: (error) => {
    if (error instanceof ExternalServiceError) {
      return {
        statusCode: 502,
        error: {
          code: 'EXTERNAL_SERVICE_ERROR',
          message: error.message,
        },
      };
    }

    return undefined;
  },
});

Shared contract example:

import { buildSuccessResponse, type ApiResult } from '@xlr8-nest/core/response';

@Injectable()
export class UserFacade {
  async findOne(id: string): Promise<ApiResult<UserDto>> {
    const user = await this.userService.findById(id);

    return buildSuccessResponse(user, {
      message: 'User retrieved successfully',
    });
  }
}

Exception filter example:

import {
  buildErrorResponse,
  normalizeUnknownException,
  type ApiFailure,
} from '@xlr8-nest/core/response';

@Catch()
export class GlobalExceptionFilter implements ExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    const response = host.switchToHttp().getResponse();
    const options = {
      fallbackError: {
        code: 'INTERNAL_SERVER_ERROR',
        message: 'Unexpected error',
      },
    };
    const { statusCode } = normalizeUnknownException(exception, options);
    const payload: ApiFailure = buildErrorResponse(exception, options);

    response.status(statusCode).json(payload);
  }
}

✅ Validator (@xlr8-nest/core/validator)

Zod validation integration with NestJS pipes.

import { ZodValidationPipe, Validate } from '@xlr8-nest/core/validator';

Example:

const updateUserSchema = z.object({
  name: z.string().min(2).optional(),
  email: z.string().email().optional(),
  age: z.number().min(0).max(150).optional(),
}).refine(data => Object.keys(data).length > 0, {
  message: 'At least one field must be provided'
});

@Patch(':id')
@Validate(updateUserSchema)
async update(
  @Param('id') id: string,
  @Body() dto: z.infer<typeof updateUserSchema>
) {
  return this.userService.update(id, dto);
}

📋 Peer Dependencies

This package requires the following peer dependencies based on which modules you use:

| Module | Required Dependencies | Optional | | ------------- | -------------------------------------------------- | ---------------------------- | | ddd | @nestjs/common, @nestjs/core, rxjs | @nestjs/event-emitter | | database | @nestjs/common, @nestjs/core | @nestjs/typeorm, typeorm | | messaging | @nestjs/common, @nestjs/core, @nestjs/typeorm, typeorm | - | | openapi | @nestjs/common | @nestjs/swagger | | response | @nestjs/common | - | | validator | @nestjs/common | zod | | errors | @nestjs/common | - | | types | - | - | | constants | - | - | | utils | @nestjs/common, zod | - |

All peer dependencies are marked as optional in peerDependenciesMeta, so you only need to install what you use.

🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

📄 License

MIT © [Your Name]

🔗 Links