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

@hornjs/fest

v0.0.5

Published

Fetch-style server primitives with middleware, invocation context, lifecycle events, and runtime adapters.

Readme

@hornjs/fest

基于 Fetch API 的服务端基础库,提供中间件、调用上下文,以及 Bun、Deno、Node.js、流式事件宿主的运行时适配器。

English

特性

  • 以标准 Request / Response 为中心的请求处理模型
  • 声明式路由表,支持 Bun 风格优先级与 fetch 兜底
  • 有顺序、可短路、支持中止信号的中间件链
  • 基于 InvocationContext 的调用级上下文
  • 基于 @hornjs/evt 的类型安全生命周期事件
  • 通过 waitUntil() 跟踪后台任务并在关闭时等待完成
  • 支持 Bun.serve()Deno.serve()、Node HTTP/HTTPS/HTTP2、异步事件流
  • 提供静态文件中间件,支持 HTML fallback 与 gzip / Brotli 压缩

安装

pnpm add @hornjs/fest

导出入口

import { Server, StreamRuntimeAdapter, buildRouteHandler, buildRouteTree, match } from "@hornjs/fest";
import { BunRuntimeAdapter } from "@hornjs/fest/bun";
import { DenoRuntimeAdapter } from "@hornjs/fest/deno";
import { NodeRuntimeAdapter } from "@hornjs/fest/node";
import { serveStatic } from "@hornjs/fest/static";

核心概念

Server

Server 是运行时无关的请求内核,负责:

  • 组合中间件
  • 初始化调用上下文
  • 跟踪 waitUntil() 注册的后台任务
  • 协调 runtime adapter 的启动与关闭

adapter 是可选的。在 Bun、Deno、Node.js 环境下,通常可以不显式传入; Server 会根据当前运行时自动检测,并通过 import("@hornjs/fest/bun")import("@hornjs/fest/deno")import("@hornjs/fest/node") 这类动态导入懒加载内置 adapter。

一般只有这些场景才需要手动指定 adapter

  • 你需要显式定制 Bun、Deno、Node 的原生 adapter 参数
  • 你想强制使用某个特定 adapter,而不是依赖自动检测
  • 你运行在内置 Bun、Deno、Node 之外的宿主环境中,比如异步事件流

中间件

中间件签名为 (request, next),可以:

  • 直接返回一个 Response
  • 调用 next(request) 并返回下游结果
  • 修改请求后再继续向下传递

路由与兜底处理

routes 是主路由表,会在 fetch 之前按 Bun 风格优先级进行匹配:

  • 精确路由
  • 参数路由
  • wildcard 路由
  • catch-all 路由

fetch 是未命中路由时的兜底处理函数。如果省略 fetch,那么 routes 必须包含 /*

调用上下文

使用 createContextKey()request.context 可以安全地在中间件和处理函数之间共享调用级状态。

生命周期事件

Server 继承自 @hornjs/evt 的类型化 EventDispatcher,目前会发出三个生命周期事件:

  • serve:runtime adapter 报告监听地址后触发
  • close:关闭完成且 waitUntil() 后台任务全部结束后触发
  • error:异步初始化 runtime adapter 失败时触发
import { Server, ServerErrorEvent, ServerServeEvent } from "@hornjs/fest";

server.addEventListener("serve", (event: ServerServeEvent) => {
  console.log("server ready", server.url);
});

server.addEventListener("error", (event: ServerErrorEvent) => {
  console.error("server failed", event.error);
});

基础示例

import { Server, createContextKey } from "@hornjs/fest";

const requestIdKey = createContextKey<string>("unknown");

const server = new Server({
  middleware: [
    async (request, next) => {
      request.context.set(requestIdKey, crypto.randomUUID());
      return next(request);
    },
  ],
  routes: {
    "/": async (request) => {
      return Response.json({
        id: request.context.get(requestIdKey),
        pathname: new URL(request.url).pathname,
      });
    },
  },
  fetch: () => new Response("Not Found", { status: 404 }),
});

await server.ready();
console.log(server.url);

路由辅助工具

也可以直接编译和匹配路由表。

import { buildRouteHandler, buildRouteTree, match } from "@hornjs/fest";

const routes = {
  "/users/:id": () => new Response("user"),
  "/*": () => new Response("fallback"),
};

const tree = buildRouteTree(routes);
const result = match(tree, new Request("http://localhost/users/42", { method: "GET" }));
console.log(result.all[0]?.params.id);

const handler = buildRouteHandler(tree);
const response = await handler(new Request("http://localhost/users/42"));

Runtime Adapter

Bun

import { Server } from "@hornjs/fest";
import { BunRuntimeAdapter } from "@hornjs/fest/bun";

const server = new Server({
  adapter: new BunRuntimeAdapter(),
  middleware: [],
  routes: {
    "/": () => new Response("Hello from Bun"),
  },
  fetch: () => new Response("Not Found", { status: 404 }),
});

await server.ready();

Deno

import { Server } from "@hornjs/fest";
import { DenoRuntimeAdapter } from "@hornjs/fest/deno";

const server = new Server({
  adapter: new DenoRuntimeAdapter(),
  middleware: [],
  routes: {
    "/": () => new Response("Hello from Deno"),
  },
  fetch: () => new Response("Not Found", { status: 404 }),
});

await server.ready();

Node.js

import { Server } from "@hornjs/fest";
import { NodeRuntimeAdapter } from "@hornjs/fest/node";

const server = new Server({
  adapter: new NodeRuntimeAdapter(),
  middleware: [],
  routes: {
    "/": () => new Response("Hello from Node"),
  },
  fetch: () => new Response("Not Found", { status: 404 }),
});

await server.ready();

Stream

当宿主环境已经把请求封装成异步事件流时,可以使用 StreamRuntimeAdapter

import { Server, StreamRuntimeAdapter } from "@hornjs/fest";

async function* stream() {
  while (true) {
    const event = await getNextFetchEvent();
    yield event;
  }
}

const server = new Server({
  adapter: new StreamRuntimeAdapter({
    stream: stream(),
    url: "/worker",
  }),
  middleware: [],
  routes: {
    "/": () => new Response("Hello from stream"),
  },
  fetch: () => new Response("Not Found", { status: 404 }),
});

await server.serve();

静态文件

serveStatic() 可以直接作为普通中间件使用。

import { Server } from "@hornjs/fest";
import { NodeRuntimeAdapter } from "@hornjs/fest/node";
import { serveStatic } from "@hornjs/fest/static";

const server = new Server({
  adapter: new NodeRuntimeAdapter(),
  middleware: [
    serveStatic({
      dir: "./public",
      renderHTML: async ({ html, filename }) => {
        return new Response(html.replace("</body>", `<p>${filename}</p></body>`), {
          headers: { "content-type": "text/html" },
        });
      },
    }),
  ],
  fetch: () => new Response("Not Found", { status: 404 }),
});

静态资源解析规则:

  • / -> index.html
  • /about -> 依次尝试 about.htmlabout/index.html
  • 已带扩展名的路径按原样解析

当客户端声明支持压缩时,如果运行时支持,会优先使用 Brotli,否则退回 gzip。

TLS

在 Bun、Deno、Node 中,TLS 证书既可以传 PEM 内容,也可以传文件路径:

const server = new Server({
  adapter: new NodeRuntimeAdapter(),
  protocol: "https",
  tls: {
    cert: "./certs/dev-cert.pem",
    key: "./certs/dev-key.pem",
  },
  middleware: [],
  fetch: () => new Response("secure"),
});

如果显式设置了 protocol: "https",那么 certkey 必须同时提供。

手动控制生命周期

如果你想自己控制启动与关闭,可以设置 manual: true

const server = new Server({
  adapter: new NodeRuntimeAdapter(),
  manual: true,
  middleware: [],
  fetch: () => new Response("ok"),
});

await server.serve();
await server.ready();

server.waitUntil?.(
  Promise.resolve().then(() => {
    console.log("background work");
  }),
);

await server.close();

Server.close() 会等待:

  • runtime adapter 完成关闭
  • 所有通过 waitUntil() 注册的后台任务完成

同样也可以通过事件观察这些阶段:

server.addEventListener("serve", () => {
  console.log("listening");
});

server.addEventListener("close", () => {
  console.log("closed");
});

API 概览

主入口导出:

  • Server
  • ServerServeEvent
  • ServerCloseEvent
  • ServerErrorEvent
  • StreamRuntimeAdapter
  • createContextKey
  • InvocationContext
  • buildRouteTree
  • buildRouteHandler
  • match
  • runMiddleware
  • wrapFetch

子路径导出:

  • @hornjs/fest/bun
  • @hornjs/fest/deno
  • @hornjs/fest/node
  • @hornjs/fest/static

开发

pnpm install
pnpm lint
pnpm test
pnpm build