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

@velajs/vela

v1.30.0

Published

NestJS-compatible framework for edge runtimes, powered by Hono

Readme

@velajs/vela

npm version CI License: MIT

Nest-style modules, controllers, and dependency injection for edge runtimes, powered by Hono.

Install

pnpm add @velajs/vela

See the documentation index for module authoring, runtime types, security, WebSockets, and live queries.

Quick Start

import { VelaFactory, Controller, Get, Module, Injectable } from '@velajs/vela';

@Injectable()
class AppService {
  getHello() {
    return { message: 'Hello from the edge!' };
  }
}

@Controller('/app')
class AppController {
  constructor(private appService: AppService) {}

  @Get('/')
  hello() {
    return this.appService.getHello();
  }
}

@Module({
  controllers: [AppController],
  providers: [AppService],
})
class AppModule {}

const app = await VelaFactory.create(AppModule);
export default app; // Fetch-compatible application

This example serves GET /app/ and returns { "message": "Hello from the edge!" }.

Build decorated TypeScript with legacy decorators and emitted decorator metadata, for example with Vite 8, whose Oxc transformer emits both. See the tooling guide and API starter for working compiler and runtime configuration. For native Workers bindings, use createCloudflareWorker from @velajs/cloudflare.

HTTP requests have a 1 MiB body ceiling plus bounded query size/count/depth by default, enforced before application middleware, signed-body capture, guards, and parameter parsing. Configure global and narrow streaming limits with VelaFactory.create(AppModule, { security: { body: ..., query: ... } }). Guards run before parameter decorators and pipes, and malformed JSON passed to @Body() produces a 400 response. See the security guide for caching, signed URLs, browser headers, client identity, and WebSockets.

Rate limiting prefers identity explicitly published by trusted authentication through setTrustedRequestIdentity() (principal plus verified tenant), then a configured tracker, then the runtime-attested client address. Core never derives a tracker from forwarding headers. Authentication guards must be registered before ThrottlerModule.

Features

  • Decorator-based controllers@Controller, @Get, @Post, @Put, @Patch, @Delete
  • Dependency injection@Injectable, @Inject, InjectionToken, Scope.DEFAULT (singleton), Scope.TRANSIENT and Scope.REQUEST scopes
  • Modules@Module with imports, exports, controllers, providers
  • Guards@UseGuards with CanActivate interface
  • Pipes@UsePipes, built-in ParseIntPipe, ParseBoolPipe, etc., and ValidationPipe from @velajs/vela/validation
  • Schema-validated parameters@Body(schema), @Query('page', schema), @Param('id', schema) return 400 on invalid input and document the schema in OpenAPI
  • Interceptors@UseInterceptors with NestInterceptor interface
  • Exception filters@UseFilters, @Catch, built-in HTTP exceptions
  • Middleware@UseMiddleware for Hono-native middleware
  • Custom metadata@SetMetadata + Reflector
  • Custom param decoratorscreateParamDecorator
  • Route versioning@Controller({ path: '/users', version: 1 }) + @Version(2) (serves /v1/users and /v2/users)
  • Global prefixVelaFactory.create(AppModule, { globalPrefix: '/api' }), read back with app.getGlobalPrefix()
  • Lifecycle hooksOnModuleInit, OnApplicationBootstrap, OnModuleDestroy
  • CRUD integration — Optional @velajs/crud package

Import paths

Each name has exactly one import path.

| Entry | Contents | |---|---| | @velajs/vela | The application kit: VelaFactory, modules and DI, controllers and route/param decorators, guards, pipes, interceptors, filters, HTTP exceptions, ConfigModule, Logger, lifecycle types | | @velajs/vela/module-kit | Seams for module, integration and adapter authors: Container, MetadataRegistry, DiscoveryService, entrypoint kinds and execution scopes, PipelineRunner, route contributors, invokeScheduledJob, module-authoring helpers | | @velajs/vela/cache, /throttler, /schedule, /events, /health, /logging, /http-client | Optional feature modules | | @velajs/vela/openapi | @Endpoint/defineEndpoint, createOpenApiDocument, @ApiDoc/@ApiTags/@ApiResponse | | @velajs/vela/security | SecurityModule, CorsModule, Secret, signed-URL primitives, the nonce store | | @velajs/vela/dispatch | Signed internal dispatch (InternalDispatcher, @SignedInvocation) | | @velajs/vela/validation, /websocket, /queue, /live, /i18n, /seeder, /storage, /streaming, /observability | Validation and the other feature subsystems | | @velajs/vela/schedule-node, /websocket-node | Node/Bun adapters | | @velajs/vela/internal | Bootstrap plumbing for first-party tooling such as @velajs/testing |

Edge Runtime Compatibility

Vela runs on any runtime that supports the Web Standards API:

  • Cloudflare Workers
  • Deno Deploy
  • Bun
  • Node.js 24+
  • Vercel Edge Functions

No Node.js-specific APIs (node:fs, Buffer, process) are used.

Edge-safe contract

The main export (@velajs/vela) is edge-safe by contract — no node:* imports, no Buffer, no process, no setInterval, no Bun.serve. This is enforced in CI by src/__tests__/edge-runtime-audit.test.ts, which fails the build if any file under src/ references a forbidden API.

One subpath, @velajs/vela/schedule-node, is an opt-in Node/Bun adapter for setInterval-based job execution. It uses runtime-specific APIs by design and is excluded from the edge-runtime audit. Edge runtimes (Cloudflare Workers, Deno Deploy, Vercel Edge) should not import it — use platform cron triggers instead (e.g., @velajs/cloudflare dispatches @Cron jobs via the Workers scheduled() handler).

// Node / Bun only — opt-in
import { ScheduleNodeModule } from '@velajs/vela/schedule-node';

WebSocket transports for Node, Bun, and Deno are exposed separately through @velajs/vela/websocket-node. On Workers, use @velajs/cloudflare and its native Durable Object entrypoint. See the WebSocket guide.

Runtime environment and config

ENV is the framework-owned token for the environment a runtime hands the application: bindings, variables and secrets, typed as VelaEnv. Core declares VelaEnv empty and never reads a platform global; @velajs/cloudflare extends it with the Cloudflare.Env that wrangler types generates and seeds ENV for each Worker and Durable Object. Elsewhere, seed it yourself: VelaFactory.create(AppModule, { env }) (a Node entry may pass process.env), or Test.createTestingModule(metadata, { env }) in tests.

import { ConfigModule, Inject, InjectEnv, Injectable, Module, registerAs, type ConfigType, type VelaEnv } from '@velajs/vela';

// Declare what your runtime provides. On Workers, @velajs/cloudflare types
// VelaEnv from `wrangler types` instead.
declare module '@velajs/vela' {
  interface VelaEnv {
    DATABASE_URL?: string;
  }
}

export const database = registerAs('database', (env) => ({
  url: env.DATABASE_URL ?? 'sqlite::memory:',
}));

@Injectable()
class Reports {
  constructor(
    @InjectEnv() private readonly env: VelaEnv,
    @Inject(database.KEY) private readonly db: ConfigType<typeof database>,
  ) {}
}

@Module({ imports: [ConfigModule.forFeature(database)], providers: [Reports] })
class ReportsModule {}

registerAs(namespace, env => config) reads ENV; ConfigModule.forFeature provides one namespace to the importing module, and ConfigModule.forRoot({ load }) registers several; both share one provider per namespace, so its factory runs once. ConfigService<T> checks get/getOrThrow dot paths against the shape you declare. ENV has no default: reading it where no runtime seeded one fails, while framework readers inject it optionally. A string URL_SIGNING_SECRET in ENV signs URLs and invocations when no explicit secret is configured. Values come from outside the program, so validate what you read.

Dynamic modules

Configurable modules use forRoot (sync) and forRootAsync (DI-resolved):

@Module({
  imports: [
    CacheModule.forRoot({ ttl: 60 }),
    HttpModule.forRoot({ baseURL: 'https://api.example.com' }),
    ConfigModule.forRootAsync({
      useFactory: async (loader: ConfigLoader) => loader.load(),
      inject: [ConfigLoader],
    }),
  ],
})
class AppModule {}

Identity model

Each DynamicModule has an optional key?: string that discriminates one instance from another. First-party modules derive key: stableHash(options) automatically inside forRoot — so the same options always dedup, and distinct options register as distinct instances:

// Same options → dedup (one CacheModule instance, ttl: 60)
imports: [
  CacheModule.forRoot({ ttl: 60 }),
  CacheModule.forRoot({ ttl: 60 }),
]

// Different options → two distinct instances coexist
imports: [
  CacheModule.forRoot({ ttl: 60 }),
  CacheModule.forRoot({ ttl: 120 }),
]

When a consumer module imports two instances both exporting the same logical token, the resolver throws MultipleProvidersFoundError with both candidate ids — resolve the ambiguity by importing only one, or use a per-instance accessor exposed by the module. Most apps with a single instance never hit this.

Custom modules can use the same pattern via the public helpers:

import { defineDynamicModule, stableHash } from '@velajs/vela/module-kit';

class MyModule {
  static forRoot(options: MyOptions): DynamicModule {
    return defineDynamicModule({
      module: MyModule,
      key: stableHash(options),    // or pass an explicit key
      providers: [/* ... */],
      exports: [/* ... */],
    });
  }
}

forRootAsync callers should pass key explicitly when the same module needs multiple async instances — factories aren't structurally hashable.

Custom parameter decorators with deferred resolution

Vela runs middleware → guards → extract args/pipes → interceptors → handler. Controller and handler middleware runs only for its matched HTTP method and route, including before/after await next() behavior. HEAD retains Hono's GET fallback; HEAD-only middleware is skipped for ordinary GET requests. Request-scoped controllers are resolved when the handler is invoked, after guards and pipes succeed. Singleton construction and bootstrap lifecycle hooks are unchanged. Pipeline component construction failures are reported before handler exception filters render them; filters are resolved only when an error needs handling. Controller-scoped and handler-scoped middleware, guards, pipes, interceptors, and filters resolve asynchronous providers in their declaring module. Parameter pipes use the same owner. Guard, pipe, interceptor and filter classes that a module's classes reference need no providers entry: the module registers them and builds each once per scope with its dependencies, as in Nest. Application-wide components retain their global scope. Middleware configured by a module resolves in that module, including async providers. Pipes may implement transformAsync; HTTP prefers it at awaited boundaries and otherwise calls transform.

Every HTTP request, including adapter-mounted routes, owns one execution lifetime. Inject EXECUTION_LIFETIME to register defer(() => work()) or waitUntil(promise). Deferred callbacks start after the middleware/handler chain settles; disposal waits for both managed work and response EOF, error, or cancellation. Native Workers waitUntil retains asynchronous cleanup. HEAD responses cancel their untransmitted body before cleanup; WebSocket upgrade responses retain their native fields. REQUEST_CONTEXT.request captures the request after framework body-limit normalization and before application middleware runs. Guards, controllers, and adapters therefore use the same readable request and its trusted identity. The context remains a snapshot; replacing the request later does not transfer identity. Rejected bodies retain their original request context through reporting and cleanup. An ordinary createParamDecorator can read state populated by a guard. Its data argument is required when the factory excludes undefined: a factory accepting string produces @Header('x-id'); a factory accepting undefined supports @CurrentUser().

Use createLazyParamDecorator when a handler may not need an expensive value. It injects a function the handler calls explicitly. The function caches the returned value or Promise, or the thrown error, and never reruns the factory. Primitives, undefined, objects and promises retain their real semantics.

import { Injectable, REQUEST_CONTEXT, RequestContextKey, UseGuards } from '@velajs/vela';
import { createLazyParamDecorator } from '@velajs/vela/module-kit';
import type { CanActivate, ExecutionContext } from '@velajs/vela';

const USER = new RequestContextKey<{ id: string; name: string }>('app.user');

@Injectable()
class AuthGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const request = context.getContainer()?.resolve(REQUEST_CONTEXT);
    if (!request) throw new Error('Missing request context');
    request.set(USER, { id: 'u-1', name: 'ada' });
    return true;
  }
}

const DeferredProfile = createLazyParamDecorator(
  (_data: undefined, context: ExecutionContext) =>
    context.getContainer()?.resolve(REQUEST_CONTEXT).get(USER),
);

@UseGuards(AuthGuard)
@Get('/me')
me(@DeferredProfile() loadProfile: () => { id: string; name: string } | undefined) {
  const profile = loadProfile();
  return { id: profile?.id };
}

For an async factory, annotate the injected parameter as () => Promise<User> and call await loadUser(). Lazy decorators do not accept parameter pipes: validate the produced value in the factory. Ordinary decorators continue to accept pipes after their data argument.

Companion packages

| Package | Purpose | |---|---| | @velajs/cloudflare | Native Workers bindings, HTTP/queue/scheduled handlers, and Durable Object integrations | | @velajs/crud | Schema-bound CRUD controllers with memory and Drizzle adapters | | @velajs/testing | Test.createTestingModule() with overrideProvider/Guard/Pipe/Interceptor/Filter |

pnpm add @velajs/testing -D
pnpm add @velajs/cloudflare @cloudflare/workers-types
pnpm add @velajs/crud @velajs/crud-memory zod

Advanced framework integration

Applications use the root application kit and the feature subpaths. Module, integration and adapter authors also use @velajs/vela/module-kit: Container, MetadataRegistry, DiscoveryService, entrypoint kinds, execution scopes, PipelineRunner and route contributors. ModuleRef and VelaApplication are part of the root. The module authoring guide covers public discovery, entrypoint, and route integration APIs.

@velajs/vela/internal also exposes lower-level bootstrap and routing machinery, such as RouteManager, ModuleLoader, ComponentManager, and bindAppProviders. It is used by framework integrations such as @velajs/testing; these internals can change independently of the public module-authoring contract.

License

MIT

Standard Schema and edge capabilities

See the edge capabilities guide for asynchronous validation, tenant admission, Cedar authorization, compound IDs, scoped cursors, commit hooks, encryption, and backend guarantees.

Asynchronous response caching

Use ResponseCacheModule.forRoot({ namespace, store, scope, invalidation? }) and @CacheResponse({ ttl, tags }) for async memory/tiered/remote response caching. Inject ResponseCacheService for scoped reads and post-commit invalidation. The legacy CacheService API stays synchronous. See the caching guide for trusted partitions, failure behavior, expiry and distributed consistency guarantees.