@chubbyts/chubbyts-http-types

中文文档
4.2.2发布于 2026/11/04MITTypeScriptES Module零依赖近 30 天下载 2347

Http Request/Response types with factories, helpers, middleware and handler, PSR-7, PSR-15 and PSR-17 inspired.

原始使用文档
文档目录13 节
  1. Description
  2. Requirements
  3. Installation
  4. Usage
  5. Messages
  6. Factories
  7. Helpers
  8. Handler
  9. Middleware
  10. Pipe
  11. Typed attributes
  12. Upgrade from 3.x
  13. Copyright

chubbyts-http-types

CI Coverage Status Mutation testing badge npm-version

bugs code_smells coverage duplicated_lines_density ncloc sqale_rating alert_status reliability_rating security_rating sqale_index vulnerabilities

Description

TypeScript types for HTTP messages, handlers and middlewares, inspired by PSR-7, PSR-15 and PSR-17.

A contract only: types (plus pipe), no implementation. Frameworks, routers and middlewares depend on it and stay interchangeable, any implementation of the factories and helpers creates and changes the messages. A body is an AsyncIterable<Uint8Array>, so Node.js streams and web ReadableStreams need no conversion.

ModuleExports
messageUri, Query, Headers, Body, Message, Method, Request, Attributes, ServerRequest<A>, Response
message-factoryUriFactory, BodyFactory, BodyFromFileFactory, RequestFactory, ServerRequestFactory, ResponseFactory
message-helperHeaderValue, MessageHelpers, RequestHelpers, ServerRequestHelpers, ResponseHelpers, UriHelpers, CookieOptions
handlerHandler<A>: (request: ServerRequest<A>) => Promise<Response>
middlewareMiddleware<I, O = I>: (request: ServerRequest<I>, handler: Handler<O>) => Promise<Response>

A, I and O extend and default to Attributes (Record<string, unknown>).

Requirements

  • node: 22

Installation

Through NPM as @chubbyts/chubbyts-http-types.

npm i @chubbyts/chubbyts-http-types@^4.2.2

Usage

Messages

import { Readable } from 'node:stream';
import type { ServerRequest } from '@chubbyts/chubbyts-http-types/message';

// GET https://user:password@localhost:10443/api?key1=value1&key2[]=value2&key3[key31]=value3#fragment
const request: ServerRequest = {
  method: 'GET',
  uri: {
    scheme: 'https',
    userInfo: 'user:password',
    host: 'localhost',
    port: 10443,
    path: '/api',
    query: 'key1=value1&key2[]=value2&key3[key31]=value3',
    fragment: 'fragment',
  },
  protocolVersion: '1.1',
  headers: { accept: ['application/json'] },
  body: Readable.from([]),
  signal: new AbortController().signal,
  attributes: {},
};
  • method: the known methods get autocompleted, any extension method (PROPFIND) is allowed.
  • uri.port: undefined without an explicit port. uri.query: raw and percent-encoded, without ?.
  • headers: lowercase names, each with a list of values.
  • body: read once by iterating it (e.g. text(body) from node:stream/consumers), the chunks are bytes, never objects.
  • signal: aborted when the request is no longer needed (e.g. the client disconnected). The server bridge owns it, handlers pass it on, e.g. fetch(url, { signal: request.signal }).
  • attributes: values derived from the request (route parameters, the user), see Typed attributes.

A Request is the same without attributes, a Response has status, reasonPhrase, protocolVersion, headers and body.

Factories

TypeSignature
UriFactory(uri: string) => Uri
BodyFactory(content: string) => Body
BodyFromFileFactory(filename: string) => Body
RequestFactory(method: Method, uri: string | Uri) => Request
ServerRequestFactory(method: Method, uri: string | Uri) => ServerRequest
ResponseFactory(status: number, reasonPhrase?: string) => Response

Created messages have the protocol version 1.1, no headers and an empty body. Each request gets a signal of its own (a shared one would pile up the listeners), a given Uri is used as is, a server request starts without attributes. The reason phrase falls back to STATUS_CODES (an empty string if unknown).

Helpers

The helpers read and change messages and uris, split like the PSR-7 interfaces. Every implementation has to follow these rules, so middlewares work with whichever gets injected:

  • header names are case-insensitive and stored lowercase, a name has to be a token (RFC 9110 5.1), a value must not contain CR, LF or NUL, otherwise the with* helpers throw
  • the with* helpers return a new message or uri, the given one stays unchanged
  • a query gets parsed with nested (key[sub]=value, key[]=value) and repeated keys into objects and arrays, + and percent-encodings decoded, __proto__ never changes the prototype, the limits (depth, array size, parameters) are up to the implementation
  • query params get serialized with indexed keys (key[sub]=value, key[0]=value), percent-encoded (RFC 3986, space as %20), empty arrays and objects dropped, so parsing them returns them again
  • the port of a uri is undefined if it is the standard one of its scheme (80 for http, 443 for https)

MessageHelpers (PSR-7 MessageInterface):

HelperSignatureRule
getHeader(message, name) => ReadonlyArray<string>empty array if missing
getHeaderLine(message, name) => stringvalues joined with ', ', empty string if missing, throws for set-cookie
hasHeader(message, name) => boolean
withHeader(message, name, value) => messagereplaces the values
withAddedHeader(message, name, value) => messageappends to the existing values
withoutHeader(message, name) => message
withProtocolVersion(message, protocolVersion) => messagethrows for other than 1.0, 1.1, 2 or 3
withBody(message, body) => message

RequestHelpers (PSR-7 RequestInterface), including the MessageHelpers:

HelperSignatureRule
withMethod(request, method) => requestthrows if not a token, case-sensitive
getRequestTarget(request) => stringorigin-form: path (/ if empty) and query
withUri(request, uri, preserveHost?) => requestsets the host header, unless the uri has none or preserveHost and one exists
withSignal(request, signal) => requeste.g. AbortSignal.any([request.signal, AbortSignal.timeout(ms)])
getQueryParams(request) => Queryparses uri.query
withQueryParams(request, queryParams) => requestreplaces uri.query

ServerRequestHelpers (PSR-7 ServerRequestInterface), including the RequestHelpers:

HelperSignatureRule
withAttribute(request, name, value) => requestadds or replaces the attribute, including its type (*)
withoutAttribute(request, name) => requestremoves the attribute, including its type
getCookieParams(request) => Record<string, string>values as sent, surrounding quotes removed, first occurrence wins

(*) With a name only known at runtime (string), any attribute could get replaced, so their types widen to include the type of the value.

ResponseHelpers (PSR-7 ResponseInterface), including the MessageHelpers:

HelperSignatureRule
withStatus(response, status, reasonPhrase?) => responsethrows outside 100-599, reason phrase falls back to STATUS_CODES
withCookie(response, name, value, options?) => responseappends a set-cookie header
withoutCookie(response, name, options?) => responseappends a set-cookie header expiring the cookie

UriHelpers (PSR-7 UriInterface):

HelperSignatureRule
getAuthority(uri) => string[user-info@]host[:port], empty string without host
toString(uri) => string// and the authority only with a host
withScheme(uri, scheme) => urilowercased, throws if invalid, empty allowed
withUserInfo(uri, user, password?) => uripercent-encoded, joined with :
withHost(uri, host) => urilowercased
withPort(uri, port) => urithrows outside 1-65535, undefined removes it
withPath(uri, path) => uripercent-encodes not allowed characters, keeps existing encodings
withQuery(uri, query) => uriwithout ?, percent-encodes like withPath
withFragment(uri, fragment) => uriwithout #, percent-encodes like withPath

The cookie helpers follow RFC 6265 (PSR-7 has no cookie support):

  • a name has to be a token, a value consists of cookie-octets, optionally within double quotes, and gets neither encoded nor decoded
  • path and domain must not contain ; or control characters, an invalid maxAge or expires throws
  • sameSite: 'None' and partitioned require secure, a __Secure- prefix requires secure, a __Host- prefix secure, the path / and no domain
  • attributes get serialized in the order Path, Domain, Max-Age, Expires, Secure, HttpOnly, SameSite, Partitioned
  • withoutCookie sets an empty value, Max-Age=0 and an Expires within 1970, path and domain have to match the ones the cookie got set with

Handler

A handler turns a server request into a response, its dependencies get injected:

import type { Handler } from '@chubbyts/chubbyts-http-types/handler';
import type { BodyFactory, ResponseFactory } from '@chubbyts/chubbyts-http-types/message-factory';
import type { ResponseHelpers } from '@chubbyts/chubbyts-http-types/message-helper';

export const createPingHandler = (
  responseFactory: ResponseFactory,
  bodyFactory: BodyFactory,
  responseHelpers: ResponseHelpers,
): Handler => {
  return async () => {
    const response = responseHelpers.withBody(
      responseFactory(200),
      bodyFactory(JSON.stringify({ datetime: new Date().toISOString() })),
    );

    return responseHelpers.withHeader(response, 'content-type', 'application/json');
  };
};

Middleware

A middleware changes the request before and the response after the next handler, or responds on its own:

import type { Attributes } from '@chubbyts/chubbyts-http-types/message';
import type { ResponseHelpers, ServerRequestHelpers } from '@chubbyts/chubbyts-http-types/message-helper';
import type { Middleware } from '@chubbyts/chubbyts-http-types/middleware';

export const createRequestIdMiddleware = (
  serverRequestHelpers: ServerRequestHelpers,
  responseHelpers: ResponseHelpers,
): Middleware<Attributes, { requestId: string }> => {
  return async (request, handler) => {
    const requestId = crypto.randomUUID();

    const response = await handler(serverRequestHelpers.withAttribute(request, 'requestId', requestId));

    return responseHelpers.withHeader(response, 'x-request-id', requestId);
  };
};

Pipe

pipe composes middlewares into one, the first being the outermost:

import type { Handler } from '@chubbyts/chubbyts-http-types/handler';
import type { Middleware } from '@chubbyts/chubbyts-http-types/middleware';
import { pipe } from '@chubbyts/chubbyts-http-types/pipe';

declare const handler: Handler;
declare const middlewares: Array<Middleware>;

const middleware: Middleware = pipe(middlewares);

export const app: Handler = (request) => middleware(request, handler);

Typed attributes

A handler or middleware relying on attributes set earlier (by a router or an authentication) declares them, one that adds attributes declares the ones it receives and the ones it passes on as Middleware<I, O>:

import type { Handler } from '@chubbyts/chubbyts-http-types/handler';
import type { ResponseFactory } from '@chubbyts/chubbyts-http-types/message-factory';
import type { ResponseHelpers, ServerRequestHelpers } from '@chubbyts/chubbyts-http-types/message-helper';
import type { Middleware } from '@chubbyts/chubbyts-http-types/middleware';

type RequestIdAttributes = { requestId: string };
type UserAttributes = RequestIdAttributes & { userId: string };

export const createAuthenticationMiddleware = (
  serverRequestHelpers: ServerRequestHelpers,
  resolveUserId: (authorization: string) => Promise<string>,
): Middleware<RequestIdAttributes, UserAttributes> => {
  return async (request, handler) => {
    const userId = await resolveUserId(serverRequestHelpers.getHeaderLine(request, 'authorization'));

    return handler(serverRequestHelpers.withAttribute(request, 'userId', userId));
  };
};

export const createUserHandler =
  (responseFactory: ResponseFactory, responseHelpers: ResponseHelpers): Handler<UserAttributes> =>
  async (request) =>
    responseHelpers.withHeader(responseFactory(204), 'x-user-id', request.attributes.userId);

pipe only composes middlewares sharing the same attributes (Middleware<A>). With m1: Middleware<RequestIdAttributes, UserAttributes> and m2: Middleware<UserAttributes, PingAttributes>, pipe([m1, m2]) compiles to a Middleware<UserAttributes>: it requires the userId upfront and hides the ping. Chain such middlewares by hand:

const app: Handler<RequestIdAttributes> = (request) => m1(request, (userRequest) => m2(userRequest, handler));

Upgrade from 3.x

3.x4.x
node >= 18node >= 22
import … from '@chubbyts/chubbyts-http-types/dist/message'import … from '@chubbyts/chubbyts-http-types/message' (without dist/)
uri.schemauri.scheme (like PSR-7 getScheme())
statusMap.get(status)STATUS_CODES[status] from node:http
mutable messagesall properties readonly, e.g. withAttribute(request, 'userId', 'user-1') instead of an assignment
Method limited to CONNECT, DELETE, GET, … TRACEany method, the known ones get autocompleted
Query arrays contain only nested queriesQuery arrays contain strings and nested queries (key[]=value)
uri.query parsed (Query)uri.query raw string, use requestHelpers.getQueryParams and withQueryParams
body is a Duplex, a handler writes into response.bodybody is an AsyncIterable<Uint8Array>, a handler sets it with withBody(response, bodyFactory(…))
StreamFromResourceFactoryremoved, a Node.js stream already is a Body
StreamFactory, StreamFromFileFactoryBodyFactory, BodyFromFileFactory (the old names are deprecated aliases)
no way to notice a disconnected clientrequest.signal, aborted when the request is no longer needed
ServerRequest, Handler, Middleware with untyped attributesServerRequest<A>, Handler<A>, Middleware<I, O> with typed attributes
-new: Headers, Body, Attributes, the helper types and pipe

2026 Dominik Zogg

版本与发布记录

@chubbyts/chubbyts-http-types 共发布 19 个版本,首次发布于 2022/05/26;过去 12 个月发布了 5 个版本。

版本发布日期距今
4.2.2latest2026/11/045 天前
4.2.12026/11/036 天前
4.2.02026/11/027 天前
4.1.02026/11/018 天前
4.0.02026/10/3010 天前
3.0.12025/06/161.4 年前

关于 @chubbyts/chubbyts-http-types 的常见问题

@chubbyts/chubbyts-http-types 是什么?

Http Request/Response types with factories, helpers, middleware and handler, PSR-7, PSR-15 and PSR-17 inspired.

如何安装 @chubbyts/chubbyts-http-types?

在项目目录运行 npm install @chubbyts/chubbyts-http-types,或使用 pnpm add / yarn add / bun add。

@chubbyts/chubbyts-http-types 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@chubbyts/chubbyts-http-types 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@chubbyts/chubbyts-http-types 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@chubbyts/chubbyts-http-types 可以商用吗?

@chubbyts/chubbyts-http-types 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@chubbyts/chubbyts-http-types 的下载量有多少?

根据 npm 官方下载统计,@chubbyts/chubbyts-http-types 在 2026/10/10 – 2026/11/08 这 30 天内被下载 2,347 次,日均约 78 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@chubbyts/chubbyts-http-types 还在维护吗?

最新版本 4.2.2 发布于 2026/11/04(5 天前);至今共发布 19 个版本,其中过去 12 个月发布了 5 个;首次发布于 2022/05/26。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。