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

@skapxd/nest

v0.2.0

Published

Layer marker mixins and decorators for NestJS applications checked by @skapxd/eslint-opinionated

Readme

@skapxd/nest

Layer markers for NestJS contracts enforced by @skapxd/eslint-opinionated.

The package exports only the names the linter can verify structurally:

  • Dto() marks request or response DTOs through a mixin with an empty default base.
  • Dto(Base) marks DTOs that must compose with another superclass.
  • @UseCase() marks the application boundary that a controller injects.
  • SKAPXD_LAYER is the shared layer key. UseCase uses it as Nest metadata; Dto uses it as a type-level brand on instances.

The lower layer is intentionally not marked. A repository, provider, or domain service is inferred by discard: it is an @Injectable that is not @UseCase and not @Controller. Adding @Repository, @Provider, or @DomainService now would sell a semantic guarantee that ESLint cannot prove from AST alone. @Repository may still make sense later for a concrete ORM rule, for example enforcing that @InjectModel or Mongoose imports only appear in repository classes.

Why this exists

The model comes from:

Keep imports explicit:

import { Dto, UseCase } from '@skapxd/nest';

Dto

Dto is a mixin, not a decorator. A decorator can write runtime metadata, but with Nest's experimentalDecorators it does not change the TypeScript type of the class. That makes it a weak signal for a rule that needs to validate the actual return type of a controller. A mixin returns a base class, so its members are part of the instance type inherited by the concrete DTO.

Use Dto() for ordinary data DTOs. It uses an empty class as its default base:

import { Dto } from '@skapxd/nest';

export class CreateUserRequestDto extends Dto() {
  email!: string;
}

Use Dto(Base) when the DTO must compose with another base class:

import { StreamableFile } from '@nestjs/common';
import { Dto } from '@skapxd/nest';

export class PdfFileDto extends Dto(StreamableFile) {}

Intention: a DTO is transport structure only. It has no business logic, no mutable domain state, and is not injected. A database entity (@Schema or @Entity) must not be used as a DTO; the explicit marker gives the rule a whitelist instead of a fragile blacklist.

The linter has two redundant detection signals:

  • Brand: the instance type has readonly [SKAPXD_LAYER]: "dto" from @skapxd/nest.
  • Base identity: Dto() and the class returned by Dto(Base) are declared in @skapxd/nest, so a type-aware rule can walk the base-type chain and detect the origin.

Dto() and Dto(Base) also provide data DTO helpers through class-transformer:

import { Expose, Type } from 'class-transformer';
import { Dto } from '@skapxd/nest';

export class AddressDto extends Dto() {
  @Expose()
  street!: string;
}

export class UserDto extends Dto() {
  @Expose()
  name!: string;

  @Expose()
  @Type(() => AddressDto)
  address!: AddressDto;
}

const user = UserDto.fromPrimitives({
  name: 'Ada',
  address: { street: 'Main Street' },
});

user.address instanceof AddressDto; // true
user.toPrimitives(); // { name: 'Ada', address: { street: 'Main Street' } }

toPrimitives() is typed from the DTO instance: methods and the SKAPXD_LAYER brand are omitted, arrays and nested DTO properties are transformed recursively, and Date is preserved as the Date instance that class-transformer returns. It does not depend on @codelytv/primitives-type; that package targets domain entities and Value Objects, while this package keeps the DTO contract local and license-compatible.

fromPrimitives and toPrimitives are for data DTOs. They are not a universal construction contract for DTOs that wrap non-JSON resources. For example, class PdfFileDto extends Dto(StreamableFile) {} is valid for the linter and instanceof StreamableFile, but reconstructing a binary stream from primitives is not a useful API.

Breaking change from 0.1.x: replace @Dto() class X {} with class X extends Dto() {} or class X extends Dto(Base) {}.

@UseCase

@UseCase replaces @Injectable() on application use-cases:

import { UseCase } from '@skapxd/nest';

@UseCase()
export class CreateUserUseCase {
  async execute(input: CreateUserRequestDto): Promise<CreateUserResponseDto> {
    // Consume Result from lower-layer services and throw constructed HttpException on failure.
  }
}

Intention: a use-case orchestrates one business flow, typically one endpoint. It is the application boundary where a lower-layer Result becomes a constructed HttpException; it does not return Result. Controllers inject use-cases. Use-cases inject lower-layer services, repositories, or providers.

@UseCase is composed with Nest's @Injectable() and writes "use-case" under the imported SKAPXD_LAYER key.

Read metadata by importing the exported key, not by using the string manually:

import { SKAPXD_LAYER } from '@skapxd/nest';

Reflect.getMetadata(SKAPXD_LAYER, CreateUserUseCase);

Follow-ups not in this release

  • Validating DTOs with class-validator and deciding whether the boundary throws or lower layers return Result belongs in a separate release. Shipping validation here would couple marking, transformation, and error policy into one API.
  • Runtime guards or runtime brands for Dto, and a type/runtime brand for UseCase, are optional future work. The current linter contract is type-aware for DTOs and decorator-aware for use-cases.

Publishing

This package is configured for npm Trusted Publishing through GitHub Actions OIDC. There is no NPM_TOKEN in the workflow. Publishing runs from .github/workflows/ci.yml when a GitHub Release is published.

The package owner must create the Trusted Publisher in npm for package @skapxd/nest with:

  • GitHub repository: skapxd/nest
  • Workflow: ci.yml

Without that npm-side configuration, npm publish --access public from CI will fail, commonly as a scoped package 404.