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

@virid/express

v0.1.2

Published

Add express functionality to virid

Downloads

225

Readme

@virid/express

@virid/express express 的适配器,负责来自http请求的消息转换为virid中的HttpRequestMessage。并且提供了一组类似Nestjs的依赖注入功能。使得处理http请求可以像处理本地Message一样简单。

🌟 核心设计理念

@virid/express 中,express仅充当普通的io层,负责将http请求转换为对应的Message并投递进入virid调度中。@virid/express还提供了一组类Nestjs的装饰器,使得http请求体的bodyquery等也可以被注入到system中。

  • http请求转Message:所有的http请求将被实例化为特定的Message并投递进入调度器。使用装饰器实现Message声明即API定义。
  • 语义返回值:可以使用Ok()NotFound()StreamFile()等工厂函数从System中返回,即可自动转化为相应的HttpCode响应并结束http请求,或者返回一个新的HttpRequestMessage实现请求的内部转发。
  • 生命周期计数:为了支持消息的内部转发与重定向,@virid/express为每个http请求实现了引用计数的生命周期跟踪功能。当所有的System执行完成后如果http请求还未响应,@virid/express将自动关闭连接并抛出错误,防止请求挂起。

🔌启用插件

import { createVirid } from '@virid/core'
import { ExpressPlugin } from '@virid/express'
import express from 'express'

const server = express()
const virid = createVirid()
  .use(ExpressPlugin, { server: server })

🛠️ @virid/express 核心 API 概览

HttpContext

  • 功能@virid/express将会每一个http请求维护一个HttpContext,包含一些重要信息,每当对应的System执行完,rc将会自动-1,当rc减为0时若http请求还未响应,@virid/express将自动关闭连接并打印错误信息。
  • 逻辑:你可以使用@Ctx()装饰器在System中以获得当前请求的HttpContext
  • 示例:
export class HttpContext {
  public rc: number = 0;
  public isClosed: boolean = false;

  constructor(
    public readonly id: number,
    public readonly req: Request,
    public readonly res: Response,
    public readonly timestamp: number,
    public readonly route: string,
  ) {}
}

HttpRequestMessage与@HttpRoute()

  • 功能HttpRequestMessage是一种特殊的Message类型,每个http请求将被自动转变为一个HttpRequestMessage类型的消息。HttpRoute则标记了当前HttpRequestMessage的路由元信息,包含pathmethod等。
  • 逻辑HttpRouteHttpRequestMessage是成对使用的,HttpRoute只能在HttpRequestMessage上使用。每个HttpRequestMessage将自带一个RequestId类型的Id,用以作为当前请求的唯一标记。
  • 示例:
import { HttpRoute, HttpRequestMessage } from '@virid/express'
@HttpRoute({
  path: '/api/login/qr/check',
  method: 'post'
})
export class LoginQrCheckRequestMessage extends HttpRequestMessage {}

@HttpSystem()

  • 功能@HttpSystem()@virid/core中的@System()不同。@HttpSystem()只能被HttpRequestMessage类型的小消息触发,且支持@Body@Query等装饰器。
  • 逻辑@HttpSystem()类型的System中抛出错误后,将自动转换为InternalServerError()。且@HttpSystem()支持直接返回OK()NotFound()等等直接来设置响应头和HttpCode,也支持返回新的HttpRequestMessage来实现请求接力。
  • 示例:
import { Body, Cookies, Headers, HttpSystem, Ok } from '@virid/express'

export class LoginQrCheckSystem {
  @HttpSystem({
    messageClass: LoginQrCheckRequestMessage
  })
  // 可以直接在system中注入各种http参数
  public static async checkQrStatus(
    @Body() body: LoginQrCheckRequest,
    @Cookies() cookies: Record<string, string>,
    @Headers() headers: Record<string, string>
  ) {
    const { unikey } = body

    const answer = await createRequest({
      url: '/login/qrcode/client/login',
      data: {
        key: unikey,
        type: 3
      },
      cookies,
      headers
    })
    return Ok(answer.data as LoginQrCheckResponse, {
      'Set-Cookie': answer.cookies
    })
  }
}
// 这个示例展示了http请求如何在不同的HttpSystem之间流转
// 内部的两个用于流转的消息类型
class DataFromLocalMessage extends HttpRequestMessage {
  constructor(
    requestId: RequestId,
    public songId: number
  ) {
    super(requestId)
  }
}
class DataFromCacheMessage extends HttpRequestMessage {
  constructor(
    requestId: RequestId,
    public cachePath: string
  ) {
    super(requestId)
  }
}
// 注册路由
@HttpRoute({
  path: '/cache/songs/data',
  method: 'get'
})
class CacheSongDataRequestMessage extends HttpRequestMessage {}

export class CacheSongDataSystem {
  @HttpSystem()
  public static async songData(
    @Message(CacheSongDataRequestMessage) message: CacheSongDataRequestMessage,
    @Query('id') id: number,
    @Query('md5') md5: string,
    @Query('source') source: 'net' | 'local',
    @Headers() headers: Record<string, string>,
  ) {
    // 如果是本地歌曲的url,转发http请求
    const requestId = message.requestId
    if (source == 'local') return new DataFromLocalMessage(requestId, id)
    // ...
   	//  如果有缓存,走缓存
    if (localPath) return new DataFromCacheMessage(requestId, localPath)
	// ....
    // 返回流对象
    return Stream(webStream)
  }

  @HttpSystem()
  public static async songDataFromLocal(
    @Message(DataFromLocalMessage) _message: DataFromLocalMessage,
    dbComponent: DatabaseComponent
  ) {
    // ...
    // 从本地读取文件并返回
    return StreamFile(absolutePath, {
      dotfiles: 'allow'
    })
  }

  @HttpSystem()
  public static async songDataFromCache(
    @Message(DataFromCacheMessage) message: DataFromCacheMessage,
    @Query('id') id: number,
    @Query('md5') md5: string,
    dbComponent: DatabaseComponent
  ) {
   	// ....
    // 从缓存文件中读取并返回
    return StreamFile(absolutePath, {
      dotfiles: 'allow'
    })
  }
}

@Body()/@Query等

  • 功能:自动将请求的各种信息提取并作为HttpSystem的参数注入,与Nestjs的装饰器相似。
  • 逻辑
  • 示例:
export class LoginQrCheckSystem {
  @HttpSystem({
    messageClass: LoginQrCheckRequestMessage
  })
  // 可以直接在system中注入各种http参数
  public static async checkQrStatus(
    @Body() body: LoginQrCheckBody,
    @Cookies() cookies: Record<string, string>,
    @Headers() headers: Record<string, string>,
    @Query() id:number,
    @Params() id:LoginQrCheckParams,
    @Req() req:Request,
    @Res() res:Response, 
    @Ctx() ctx: HttpContext,
  ) {
   	//....
    return Ok()
  }
}

Pipe

  • 功能@virid/express支持简单的pipe处理和自动转换,也支持自定义使用特定类型的pipe转换注册。
  • 逻辑:在使用@Query()时,@virid/express支持一些自动的转换,例如 @Query() id:number将自动转换为number类型,或者也可以为自己的类型设置相应的pipe转换。
  • 示例:
import { addAutoPipe } from '@virid/express'
addAutoPipe(YourType,(data:YourType)=>{return newData})

🛜其他响应类型


/** 200 OK */
export const Ok = (data: any, headers: HttpHeaders = {}) =>
  new OkResponse(data, headers);

/** 201 Created */
export const Created = (data: any, headers: HttpHeaders = {}) =>
  new CreatedResponse(data, headers);

/** 204 No Content */
export const NoContent = () => new NoContentResponse();

/** 400 Bad Request */
export const BadRequest = (msg = "Bad Request") => new BadRequestResponse(msg);

/** 401 Unauthorized */
export const Unauthorized = (msg = "Unauthorized") =>
  new UnauthorizedResponse(msg);

/** 403 Forbidden */
export const Forbidden = (msg = "Forbidden") => new ForbiddenResponse(msg);

/** 404 Not Found */
export const NotFound = (msg = "Not Found") => new NotFoundResponse(msg);

/** 500 Internal Error */
export const InternalServerError = (msg = "Internal Server Error") =>
  new InternalServerErrorResponse(msg);

export const CustomResponse = (
  status: number,
  data: any,
  headers: HttpHeaders = {},
) => new CustomResponseResponse(status, data, headers);

/** 发送本地文件 (自动处理 Range/206) */
export const StreamFile = (path: string, options?: StreamFileOptions) =>
  new StreamFileResponse(path, options);
/**
 * 发送流响应
 */
export const Stream = (stream: Readable, options?: StreamResponse["options"]) =>
  new StreamResponse(stream, options);

export class HttpError extends Error {
  constructor(
    public readonly status: number,
    public readonly message: string,
  ) {
    super(message);
  }
}