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

@ballistix.digital/exception-types

v0.5.0

Published

The exception classes, the ExceptionDto envelope, the detail DTOs and the codes Ballistix NestJS applications throw and return.

Readme

@ballistix.digital/exception-types

The exception classes, the ExceptionDto envelope, the detail DTOs and the codes a Ballistix NestJS application throws and returns. The package holds data and no behaviour: no module, no filter and no provider. @ballistix.digital/exception-mapper turns a thrown value into one of these exceptions and into the envelope.

This file is the consumer guide. The pages that describe how the package is built live in the Ballistix wiki.

Install

npm install @ballistix.digital/exception-types

The package needs Node 22.12 or later. Install these peer dependencies in the application.

| Peer | Range | Required | | --- | --- | --- | | class-validator | ^0.15.1 | yes | | class-transformer | ^0.5.1 | yes | | lodash | ^4.17.21 | yes | | luxon | ^3.7.2 | yes | | reflect-metadata | ^0.2.2 | yes | | @nestjs/common | ^11 | types only, for the . entry | | @nestjs/swagger | ^11 | optional: the . entry uses its decorators when it is installed |

Load reflect-metadata once, before any decorated class is imported.

A browser bundle imports the ./react entry and needs neither Nest package:

import { ExceptionDto, ExceptionCodeEnum } from '@ballistix.digital/exception-types/react';

The envelope

ExceptionDto is the shape an HTTP response body or a logged task failure carries. Every field is optional, so a partial envelope from an older service still validates.

| Field | Type | What it holds | | --- | --- | --- | | type | string | A URI that identifies the kind of problem. | | title | string | The human-readable message of the exception. | | status | number | The HTTP status the exception carries. | | code | ExceptionCodeEnum | The code that names the kind of failure. | | errors | object[] | The details: one entry per thing that went wrong, in the shape of the detail DTO of the code. |

The codes

Every code has one message template, one detail DTO and at least one exception class. BaseException fills the template from the first detail, so a placeholder such as {entity} reads the entity field of errors[0].

| Code | Status | Exception class | Detail DTO | Message template | | --- | --- | --- | --- | --- | | RESOURCE_NOT_FOUND | 404 | ResourceNotFoundException | ResourceNotFoundDetailDto | Could not find {entity} with {searchField} {searchValue} | | REFERENCED_RESOURCE_NOT_FOUND | 400 | ReferencedResourceNotFoundException | ReferencedResourceNotFoundDetailDto | Field {requestField} references {entity} with {searchField} {searchValue}, which does not exist | | VALIDATION_FAILED | 400 | ValidationFailedException | ValidationErrorDetailDto | Validation failed | | DATABASE_ERROR | 500, or the 4xx the caller passes | DatabaseErrorException | GenericErrorDetailDto | {message} | | ACTION_NOT_PERMITTED | 403 | ActionNotPermittedException | ActionNotPermittedDetailDto | Not allowed to {action} a {entity} | | ACCESS_DENIED | 403 | AccessDeniedException | GenericErrorDetailDto | {message} | | GENERIC_ERROR | 500, or 400 from GenericBadRequestException | GenericErrorException, GenericBadRequestException | GenericErrorDetailDto | {message} | | FILE_INVALID | 400 | FileInvalidException | NoDetailDto | The uploaded file cannot be read | | XLSX_CONTENT_NOT_FOUND | 400 | XlsxContentNotFoundException | XlsxContentNotFoundDetailDto | Could not find {entity} with {searchField} {searchValue} | | XLSX_REFERENCED_RESOURCE_NOT_FOUND | 400 | XlsxReferencedResourceNotFoundException | XlsxReferencedResourceNotFoundDetailDto | Could not find {entity} with {searchField} {searchValue} | | XLSX_VALIDATION_FAILED | 400 | XlsxValidationFailedException | XlsxValidationErrorDetailDto | Validation of {inputField} failed ({rule}) | | CONFLICT | 409 | ConflictException | ConflictDetailDto | Declared {field} {declared} conflicts with {found} | | PRECONDITION_FAILED | 409 | PreconditionFailedException | PreconditionFailedDetailDto | Precondition {condition} is not met |

Three groups need a word:

  • The two 403 codes. ACTION_NOT_PERMITTED names the verb and the entity of an authorization denial, so a client can show "you cannot update a valuation". ACCESS_DENIED is the 403 with no such pair, such as a business-state guard, and carries a free-text message.

  • The three XLSX_* codes. Each detail carries an XlsxLocationDto: the sheet, plus the 1-based row and column that the importer knows. The numbers match what Excel itself shows.

  • NoDetailDto. FILE_INVALID points at nothing inside the file, so its errors stays an empty array.

  • PRECONDITION_FAILED. The current state of a resource does not allow the action: a completed valuation that cannot change, a file that is not uploaded yet, a status transition the state machine rejects. The detail is { condition, args? }. The condition is a SCREAMING_SNAKE key that each owner types in its own enum, and args carries the values of that condition under names the condition defines. A client translates on the code and the condition together, and fills the translation from args. The message stays the template, so no throw site writes a sentence.

    throw new PreconditionFailedException('INVALID_STATE_TRANSITION', {
    	entity: 'Valuation',
    	field: 'status',
    	from: 'OPEN',
    	to: 'COMPLETED',
    });

Throw an exception

Every exception class extends BaseException, which extends Error. The constructor arguments are the fields of the detail. The class fills in the code, the status and the message.

import { ResourceNotFoundException } from '@ballistix.digital/exception-types';

const valuation = await this.repository.findOneBy({ id });
if (!valuation) {
	// message: "Could not find Valuation with id 42", status 404
	throw new ResourceNotFoundException('Valuation', id);
}
import { ValidationFailedException, ValidationRuleEnum } from '@ballistix.digital/exception-types';

// message: "Validation failed", status 400, one detail per rejected field
throw new ValidationFailedException([
	{ inputField: 'name', rule: ValidationRuleEnum.IS_STRING },
	{ inputField: 'age', rule: ValidationRuleEnum.MIN, args: { min: 18 } },
]);

An exception exposes message, status, code and errors. errors holds one instance of the detail DTO per detail. A detail that fails its own validation is kept raw instead of dropped, so a constructor never throws.

Build your own exception

A new exception class extends BaseException and passes the detail DTO, the code, the status and the details to super:

import { BaseException, ExceptionCodeEnum, GenericErrorDetailDto } from '@ballistix.digital/exception-types';

export class UpstreamUnavailableException extends BaseException<GenericErrorDetailDto> {
	constructor(service: string) {
		super(GenericErrorDetailDto, ExceptionCodeEnum.GENERIC_ERROR, 503, [{ message: `${service} is unavailable` }]);
	}
}

The code must be a member of ExceptionCodeEnum, because BaseException reads its message template from EXCEPTION_MESSAGES. That map is typed Record<ExceptionCodeEnum, string>, so a code without a template does not compile, and only a change to this package adds one. Reuse GENERIC_ERROR while the new code is not there yet.

Rebuild an exception from an envelope

An exception that crossed a boundary as plain data comes back as an envelope: the processing error a task row stores, or the envelope a worker thread posts. When the code on this side needs an exception again, a subclass passes the envelope to BaseException. The title becomes the message. The type, the status, the code and the details stay as the envelope carries them, so a mapper registry maps the exception the way it mapped the original, and a filter renders the same envelope again.

import { BaseException, ExceptionDto } from '@ballistix.digital/exception-types';

export class WorkerException extends BaseException<object> {
	constructor(envelope: ExceptionDto) {
		super(envelope);
	}
}

// message "Validation failed", status 400, code VALIDATION_FAILED, the details as stored
throw new WorkerException(task.processingError);

This path never fills the message template of the code, because the title is already the filled message. A field the envelope lacks gets a default: an empty message, status 500, GENERIC_ERROR and no details. A code this version of the package does not know also becomes GENERIC_ERROR, so an envelope from a newer producer still rebuilds.

Validation rules

ValidationRuleEnum has one member per class-validator rule constant, named after the constant (IS_STRING, MIN, ARRAY_NOT_EMPTY), plus UNKNOWN. A detail of a validation failure names the rule that rejected the value.

validationRuleByName maps the decorator name that class-validator reports to the member:

import { validationRuleByName, ValidationRuleEnum } from '@ballistix.digital/exception-types';

validationRuleByName['isString']; // ValidationRuleEnum.IS_STRING
validationRuleByName['nope'] ?? ValidationRuleEnum.UNKNOWN; // the fallback

The lookup is exact and has no entry for UNKNOWN. Fall back to ValidationRuleEnum.UNKNOWN for a name it does not carry, so a custom decorator degrades to a generic detail instead of throwing.

convertAndValidate

convertAndValidate(cls, plain) turns a plain object, or an array of them, into instances of cls and validates them with class-validator. When a rule fails, it throws a ValidationException. getValidationErrors() gives back the raw class-validator errors.

import { convertAndValidate, ExceptionDto, ValidationException } from '@ballistix.digital/exception-types';

try {
	const envelope = convertAndValidate<ExceptionDto, unknown>(ExceptionDto, await response.json());
} catch (error) {
	if (error instanceof ValidationException) {
		console.warn(error.getValidationErrors());
	}
}

Three behaviours are worth knowing:

  • Luxon. Every DateTime in the input, at any depth, becomes its ISO string before conversion. class-transformer otherwise rebuilds a DateTime with its default constructor and sets every date to the current time. The function changes the input object in place.
  • Undecorated DTOs. forbidUnknownValues is off, so a DTO that carries no class-validator decorator passes instead of failing.
  • Extraneous values. excludeExtraneousValues is the third argument and defaults to on, so only @Expose()d properties survive the conversion.

The two entries

| Entry | Import | Decorators | Needs @nestjs/* | | --- | --- | --- | --- | | . | @ballistix.digital/exception-types | the real @nestjs/swagger decorators when the package is installed, the shim otherwise | when installed | | ./react | @ballistix.digital/exception-types/react | a shim with the same call signatures and no runtime effect | no |

Both entries compile from the same src/, so the classes, the fields and the codes are identical. Only the decorators differ. A Nest application imports the . entry and gets OpenAPI schemas for the DTOs. A browser bundle imports ./react and gets the same shapes with nothing under @nestjs/ in its graph. See ADR 0003.