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

@hyperttp/types

v0.4.2

Published

> Universal, protocol-agnostic types for the Hyperttp ecosystem.

Readme

@hyperttp/types

Universal, protocol-agnostic types for the Hyperttp ecosystem.

@hyperttp/types is a declaration-only package. It defines the contracts shared by the Hyperttp runtime, protocol packages, and transports without depending on REST, gRPC, HTTP, or any other concrete protocol.

Architecture

@hyperttp/types
  Universal contracts: IHyperCore, HyperSender, HyperReceiver, HyperProtocol,
  SendRequest, UniversalResponse, RequestContext, HyperTransport, and registries
          ▲
          │ module augmentation
          │
Protocol packages
  @hyperttp/rest, @hyperttp/grpc, ...
  Protocol semantics, input/output types, and convenience methods
          ▲
          │ use HyperTransport
          │
Transport packages
  Node, Bun, browser, or custom transports
  Protocol-agnostic network I/O

The package has three conceptual layers:

  1. Core types — universal dispatch, lifecycle, plugin, retry, metrics, error, and transport contracts.
  2. Protocol packages — protocol-specific senders, receivers, input/output types, and optional IHyperCore methods.
  3. Transports — low-level I/O implementations. A transport executes normalized requests and returns raw responses; it does not parse protocol payloads.

Installation

npm install @hyperttp/types

Runtime applications usually install this package transitively through @hyperttp/core. Protocol and transport packages should declare it as a peer dependency when appropriate.

Core contracts

Client dispatch

IHyperCore exposes protocol-neutral dispatch and registry operations:

import type {
  IHyperCore,
  SendRequest,
  UniversalResponse,
} from "@hyperttp/types";

async function request(core: IHyperCore) {
  const response: UniversalResponse = await core.send({
    protocol: "custom",
    input: { value: 42 },
  });

  return response.data;
}

The core also exposes runtime introspection methods:

await core.getProtocolName();
await core.getSenderName();
await core.getReceiverName();
await core.getTransportName();

These methods are part of the universal interface and return names supplied or resolved by the runtime implementation.

Protocol input inference

Protocol packages extend ProtocolInputMap and, optionally, ProtocolMetadataMap:

// inside a protocol package
import type { RestInput } from "./rest-input.js";
import type { RestMetadata } from "./rest-metadata.js";

declare module "@hyperttp/types" {
  interface ProtocolInputMap {
    rest: RestInput;
  }

  interface ProtocolMetadataMap {
    rest: RestMetadata;
  }
}

After the augmentation is in scope, known protocol names can infer their input type when calling send():

core.send({
  protocol: "rest",
  input: {
    method: "GET",
    url: "/users",
  },
});

The generic fallback overload remains available for custom or dynamically loaded protocols. Use InferProtocolInput<P> when a reusable helper needs to expose the same behavior.

Server request/response inference

Server protocol packages use the corresponding server maps:

import type { RestServerInput, RestServerResponse } from "./server-types.js";

declare module "@hyperttp/types" {
  interface ProtocolServerRequestMap {
    rest: RestServerInput;
  }

  interface ProtocolServerResponseMap {
    rest: RestServerResponse;
  }
}

HyperServerListenOptions uses these maps as its defaults, so a known protocol can provide typed server handlers without repeating generic parameters:

core.listen({
  protocol: "rest",
  handler(request, context) {
    request.path;
    request.query;
    request.body;

    return { status: 200, body: { ok: true } };
  },
});

The maps are intentionally empty in this package. Concrete request and response types belong to the protocol package that owns their semantics.

Senders and receivers

A protocol sender implements the three-phase client lifecycle:

prepare → send → parse

A protocol receiver implements the three-phase server lifecycle:

receive → handle → respond

A sender can optionally expose a dynamic method surface for protocol convenience methods:

const sender = {
  protocol: "rest",
  methods: {
    get,
    post,
    put,
  },
  prepare(request, context) {
    // normalize protocol input
  },
  send(prepared, transport, context) {
    // execute through HyperTransport
  },
  parse(raw, context) {
    // normalize the transport response
  },
};

HyperMethod and HyperMethodSurface describe this dynamic plugin boundary. The actual method signatures remain protocol-specific and should be declared by the protocol package.

Transport contract

HyperTransport is deliberately protocol-neutral:

import type {
  HyperTransport,
  TransportRequest,
  TransportResponse,
} from "@hyperttp/types";

const transport: HyperTransport = {
  async execute(request: TransportRequest): Promise<TransportResponse> {
    // Perform runtime-specific I/O here.
    throw new Error("not implemented");
  },
};

TransportRequest contains normalized headers and universal transport capabilities such as:

  • stream — request streaming when supported;
  • followRedirects — redirect handling when supported;
  • maxRedirects — redirect limit when redirect handling is enabled.

Flexible protocol-level input should be normalized before it reaches the transport. For example, a REST package may accept Headers, tuples, or a record, while the transport sees the portable normalized record form.

Shutdown lifecycle

Transports may implement both close() and destroy():

  • close() is the graceful shutdown operation and may wait for pending work;
  • destroy() is the immediate shutdown operation.

The graceful argument of IHyperCore.destroy() is forwarded to the runtime lifecycle where supported. Actual request draining is transport-dependent. During forced shutdown, runtimes should prefer destroy() when it is available; during graceful shutdown, they should prefer close().

Module augmentation

Protocol packages extend the core without adding protocol dependencies to this package:

// @hyperttp/rest/src/types.ts
import type { IHyperCore, HyperClientOptions } from "@hyperttp/types";
import type { RestInput, RestResponse } from "./types.js";

declare module "@hyperttp/types" {
  interface IHyperCore {
    get<T = unknown>(url: string): Promise<RestResponse<T>>;
  }

  interface HyperClientOptions {
    rest?: {
      baseUrl?: string;
    };
  }

  interface ProtocolInputMap {
    rest: RestInput;
  }

  interface ProtocolServerRequestMap {
    rest: RestInput;
  }
}

Module augmentation adds compile-time contracts only. The runtime package must provide the actual sender, receiver, and convenience method implementations.

To activate an augmentation, import the protocol package or its augmentation entry point:

import "@hyperttp/core/rest";

Creating a protocol package

  1. Define protocol-specific input, output, metadata, and server request/response types.
  2. Implement HyperSender, HyperReceiver, or both.
  3. Extend ProtocolInputMap and any other applicable maps through module augmentation.
  4. Add optional IHyperCore and HyperClientOptions extensions.
  5. Expose the protocol sender/receiver and ensure the runtime registers them.
  6. Keep protocol parsing and serialization in the protocol package, not in the transport.

What does not belong here

The following remain outside @hyperttp/types:

  • REST request and response models;
  • HTTP methods and REST convenience APIs;
  • fetch-specific or Node-specific transport options;
  • JSON parsing options;
  • protocol-specific error and metadata shapes.

They belong to their protocol or transport packages and can reach the core through module augmentation where needed.

Development

npm install
npm run build
npm run lint
npm run format:check

The package publishes generated declaration files from dist/ and contains no runtime code.

Public API reference

All declarations below are re-exported from @hyperttp/types.

IHyperCore and HyperProtocols

HyperProtocols is an empty augmentation container for protocol namespaces such as core.rest or core.grpc.

IHyperCore extends HyperProtocols exposes:

| Member | Type | Purpose | | --- | --- | --- | | config | HyperClientOptions | Immutable client configuration. | | getProtocolName(protocol?) | <P extends SenderProtocol = SenderProtocol>(protocol?: P) => Promise<string> | Resolve the registered protocol name. | | getSenderName(protocol?) | <P extends SenderProtocol = SenderProtocol>(protocol?: P) => Promise<string> | Resolve the sender name. | | getReceiverName(protocol?) | <P extends SenderProtocol = SenderProtocol>(protocol?: P) => Promise<string> | Resolve the receiver name. | | getTransportName() | () => Promise<string> | Resolve the transport name. | | send(request) | Typed overloads | Dispatch a protocol request. | | getSender(protocol) | <P>(protocol: P) => AnyHyperSender<P> \| undefined | Look up a sender. | | registerSender(sender) | <P>(sender: AnyHyperSender<P>) => this | Register a sender. | | getReceiver(protocol) | <P>(protocol: P) => AnyHyperReceiver<P> \| undefined | Look up a receiver. | | registerReceiver(receiver) | <P>(receiver: AnyHyperReceiver<P>) => this | Register a receiver. | | getProtocol(protocol) | <P>(protocol: P) => AnyHyperProtocol<P> \| undefined | Look up a protocol module. | | registerProtocol(protocol) | <P>(protocol: AnyHyperProtocol<P>) => this | Register a protocol module. | | listen(options) | <P>(options: HyperServerListenOptions<P>) => Promise<TransportServer> | Start a protocol server. | | use(plugin) | (plugin: HyperPlugin) => this | Register a plugin. | | extend(options) | (options: Partial<HyperClientOptions>) => IHyperCore | Create a client with merged options. | | create(options) | (options: Partial<HyperClientOptions>) => IHyperCore | Create a new client. | | destroy(graceful?) | (graceful?: boolean) => Promise<void> | Shut down the client. |

The primary send() overload is:

send<TOutput = unknown, P extends SenderProtocol = SenderProtocol>(
  request: SendRequest<InferProtocolInput<P>, P>,
): Promise<UniversalResponse<TOutput>>;

A generic fallback is also available:

send<TInput = unknown, TOutput = unknown, P extends SenderProtocol = SenderProtocol>(
  request: SendRequest<TInput, P>,
): Promise<UniversalResponse<TOutput>>;

Sender types

SenderProtocol

type SenderProtocol = keyof ProtocolInputMap | (string & {});

It allows known augmented protocol keys as well as arbitrary custom strings.

ProtocolInputMap and ProtocolMetadataMap

Empty interfaces intended for module augmentation:

declare module "@hyperttp/types" {
  interface ProtocolInputMap {
    custom: CustomInput;
  }

  interface ProtocolMetadataMap {
    custom: CustomMetadata;
  }
}

Utility types:

type InferProtocolInput<P extends string> =
  P extends keyof ProtocolInputMap ? ProtocolInputMap[P] : unknown;

type InferProtocolMetadata<P extends string> =
  P extends keyof ProtocolMetadataMap
    ? ProtocolMetadataMap[P]
    : Record<string, unknown>;

RequestContext

Shared request lifecycle context:

| Property | Type | Description | | --- | --- | --- | | requestId | string | Unique request identifier. | | startTime | number | Request start timestamp. | | signal | AbortSignal \| undefined | Cancellation signal. | | meta | Record<string, unknown> | Shared lifecycle metadata. | | state | Record<string, unknown> | Mutable state shared between phases. |

SendRequest<TInput, P>

Universal request envelope:

| Property | Type | Description | | --- | --- | --- | | protocol | P | Protocol identifier. | | input | TInput or InferProtocolInput<P> | Protocol-specific input. | | signal | AbortSignal \| undefined | Optional cancellation signal. | | metadata | Readonly<InferProtocolMetadata<P>> \| undefined | Protocol metadata. |

UniversalResponse<TOutput, TExtra>

Normalized protocol-neutral response:

| Property | Type | Description | | --- | --- | --- | | protocol | SenderProtocol | Protocol that handled the request. | | ok | boolean | Protocol-defined success flag. | | status | number | Numeric status. | | statusText | string \| undefined | Status description. | | headers | Readonly<Record<string, string \| string[]>> | Normalized response headers. | | url | string \| undefined | Final response URL. | | data | TOutput | Normalized response data. | | metadata | Readonly<TExtra> \| undefined | Additional response metadata. | | raw | unknown \| undefined | Raw response before normalization. |

HyperMethod and HyperMethodSurface

type HyperMethod = (...args: any[]) => unknown;
type HyperMethodSurface = Readonly<Record<string, HyperMethod>>;

These types describe the dynamic method boundary used by protocol senders. Concrete protocol packages should provide the precise method signatures for their own APIs.

HyperSender<TInput, TOutput, TPrepared, TRaw, P>

A sender has the following members:

| Member | Type | Description | | --- | --- | --- | | protocol | P | Protocol served by the sender. | | methods | HyperMethodSurface \| undefined | Optional convenience-method surface. | | prepare | (request: SendRequest<TInput, P>, ctx: RequestContext) => TPrepared | Prepare the request. | | send | (prepared: TPrepared, transport: HyperTransport, ctx: RequestContext) => Promise<TRaw> | Execute through a transport. | | parse | (raw: TRaw, ctx: RequestContext) => UniversalResponse<TOutput> \| Promise<UniversalResponse<TOutput>> | Normalize the raw response. |

AnyHyperSender<P>

type AnyHyperSender<P extends SenderProtocol = SenderProtocol> =
  HyperSender<any, any, any, any, P>;

Use this compatibility alias at registration and configuration boundaries when the sender has concrete, protocol-specific lifecycle types. It retains the protocol identifier while avoiding strict function-variance incompatibilities caused by unknown input and raw types.

SenderRegistry

interface SenderRegistry {
  register<P extends SenderProtocol>(sender: HyperSender<unknown, unknown, unknown, unknown, P>): void;
  get<P extends SenderProtocol>(protocol: P): HyperSender<unknown, unknown, unknown, unknown, P> | undefined;
  has(protocol: SenderProtocol): boolean;
  readonly size: number;
}

Receiver and server types

Server protocol maps

ProtocolServerRequestMap and ProtocolServerResponseMap are empty augmentation points. Their utility types resolve known entries or fall back to unknown:

type InferProtocolServerRequest<P extends string> =
  P extends keyof ProtocolServerRequestMap
    ? ProtocolServerRequestMap[P]
    : unknown;

type InferProtocolServerResponse<P extends string> =
  P extends keyof ProtocolServerResponseMap
    ? ProtocolServerResponseMap[P]
    : unknown;

ServerRequestContext

Extends RequestContext with transport-dependent server information:

| Property | Type | | --- | --- | | connection | Readonly<Record<string, unknown>> \| undefined | | peer | Readonly<Record<string, unknown>> \| undefined |

HyperServerListenOptions<P, TRequest, TResponse>

| Property | Type | | --- | --- | | protocol | P | | host | string \| undefined | | port | number \| undefined | | signal | AbortSignal \| undefined | | handler | (request: TRequest, ctx: ServerRequestContext) => TResponse \| Promise<TResponse> \| undefined |

TRequest and TResponse default to the corresponding server protocol map entries.

HyperReceiver<TRequest, TResponse, TRawRequest, TRawResponse, P>

| Member | Type | Description | | --- | --- | --- | | protocol | P | Protocol served by the receiver. | | receive | (request: TRawRequest, ctx: ServerRequestContext) => TRequest \| Promise<TRequest> | Parse raw transport input. | | handle | (request: TRequest, ctx: ServerRequestContext) => TResponse \| Promise<TResponse> | Run application logic. | | respond | (response: TResponse, ctx: ServerRequestContext) => TRawResponse \| Promise<TRawResponse> | Serialize the protocol response. |

AnyHyperReceiver<P>

type AnyHyperReceiver<P extends SenderProtocol = SenderProtocol> =
  HyperReceiver<any, any, any, any, P>;

IHyperCore.registerReceiver() and BaseHyperClientOptions.receivers use this alias to accept receivers with concrete protocol request and response types.

ReceiverRegistry

ReceiverRegistry mirrors SenderRegistry with register, get, has, and readonly size.

Protocol modules

HyperProtocol<TInput, TOutput, TReq, TRes, P>

A protocol module contains:

  • protocol: P;
  • an optional display name;
  • a sender, a receiver, or both.

The generic module types connect the client sender and server receiver to the same protocol identifier.

AnyHyperProtocol<P>

type AnyHyperProtocol<P extends SenderProtocol = SenderProtocol> =
  HyperProtocol<any, any, any, any, P>;

This compatibility alias is used by IHyperCore registration and lookup methods, BaseHyperClientOptions.protocols, and ProtocolRegistry. ProtocolRegistry provides register, get, has, and readonly size methods.

Client options

LogLevel

type LogLevel = "debug" | "info" | "warn" | "error";

BaseHyperClientOptions / HyperClientOptions

| Option | Type | Description | | --- | --- | --- | | protocols | AnyHyperProtocol[] \| undefined | Protocol modules to register. | | senders | AnyHyperSender[] \| undefined | Senders to register. | | receivers | AnyHyperReceiver[] \| undefined | Receivers to register. | | customTransport | HyperTransport \| undefined | Custom low-level transport. | | customSender | AnyHyperSender \| undefined | Custom sender. | | retry | Partial<RetryOptions> \| undefined | Retry configuration overrides. | | logger | (level: LogLevel, message: string, meta?: unknown) => void | Custom logger. | | verbose | boolean \| undefined | Enable verbose logging. | | pluginDirs | string[] \| undefined | Plugin directories. | | plugins | (HyperPlugin \| string)[] \| undefined | Plugin instances or module paths. | | trackMetrics | boolean \| undefined | Enable metrics collection. |

HyperClientOptions currently extends BaseHyperClientOptions and is an augmentation point for protocol-specific configuration.

Plugins

PluginPhase and PluginExecutionMode

type PluginPhase = "START" | "PREPARE" | "CONTROL" | "FORMAT" | "NETWORK" | "DATA";
type PluginExecutionMode = "blocking" | "background";

PluginContext

interface PluginContext {
  readonly config: HyperClientOptions;
  readonly core: IHyperCore;
}

PluginOnRequestResult<TInput, TOutput>

type PluginOnRequestResult<TInput = unknown, TOutput = unknown> =
  | UniversalResponse<TOutput>
  | SendRequest<TInput>
  | void;

HyperPlugin<TInput, TOutput>

A plugin has name, optional phase and mode, and optional hooks:

| Hook | Signature | | --- | --- | | enabled | (config: HyperClientOptions) => boolean | | setup | (ctx: PluginContext) => void | | onRequest | (req, ctx?, reqCtx?) => Promise<PluginOnRequestResult<TInput, TOutput>> \| PluginOnRequestResult<TInput, TOutput> | | onResponse | (res, req?, ctx?, reqCtx?) => Promise<UniversalResponse<TOutput> \| void> \| UniversalResponse<TOutput> \| void | | onError | (err, req?, ctx?, reqCtx?) => Promise<UniversalResponse<TOutput> \| void> \| UniversalResponse<TOutput> \| void |

reqCtx is the per-request RequestContext, allowing plugins to share state and meta across lifecycle phases.

Retry and errors

RetryOptions

| Option | Type | | --- | --- | | maxRetries | number \| undefined | | baseDelay | number \| undefined | | maxDelay | number \| undefined | | jitter | boolean \| undefined |

HyperttpError<TReq, TRes>

Extends Error with:

| Property | Type | | --- | --- | | code | string | | cause | unknown \| undefined | | request | TReq \| undefined | | response | TRes \| undefined | | status | number \| undefined | | statusCode | number \| undefined | | meta | { retryCount: number; isRetryable: boolean; duration?: number; timings?: { start: number; end?: number } } \| undefined |

Metrics

HyperStats

Optional built-in counters include inflightRequests, cacheSize, queuedRequests, activeQueue, and rateLimitHits. The interface also has a string index signature for custom metrics: [key: string]: unknown.

RequestMetrics

Required fields:

  • startTime, endTime, and duration — request timing in milliseconds/timestamps;
  • bytesReceived and bytesSent;
  • retries;
  • cached.

Optional fields are statusCode, url, method, bodyHash, plugins, and stages. stages may contain serializationMs, networkMs, and parsingMs.

Adapters

HyperAdapter<T>

interface HyperAdapter<T> {
  readonly name: string;
  readonly version?: string;
  adapt(core: IHyperCore): T;
}

Adapters translate IHyperCore into a third-party client shape such as Axios, Ky, or Got.

Transport types

TransportRequest

| Property | Type | | --- | --- | | method | string | | url | string | | headers | Readonly<Record<string, string \| string[]>> | | body | unknown \| undefined | | signal | AbortSignal \| undefined | | protocol | SenderProtocol | | stream | boolean \| undefined | | followRedirects | boolean \| undefined | | maxRedirects | number \| undefined |

Headers are already normalized at this layer. Protocol packages may accept more flexible header input and normalize it before creating a TransportRequest.

TransportResponse

| Property | Type | | --- | --- | | status | number | | statusText | string \| undefined | | headers | Readonly<Record<string, string \| string[]>> | | url | string \| undefined | | body | unknown |

TransportListenOptions

| Property | Type | | --- | --- | | host | string \| undefined | | port | number \| undefined | | signal | AbortSignal \| undefined | | onRequest | (request: TransportRequest) => Promise<TransportResponse> \| TransportResponse \| undefined |

onError is intentionally not part of this universal contract; transport-specific error callbacks belong in the concrete transport package.

TransportServer

interface TransportServer {
  close(): Promise<void> | void;
}

HyperTransport

| Member | Type | | --- | --- | | protocols | readonly SenderProtocol[] \| undefined | | supports | (protocol: SenderProtocol) => boolean (optional) | | execute | (request: TransportRequest) => Promise<TransportResponse> | | listen | (options: TransportListenOptions) => Promise<TransportServer> (optional) | | close | () => Promise<void> \| void (optional) | | destroy | () => Promise<void> \| void (optional) |

Development

npm install
npm run build
npm run lint
npm run format:check

The package publishes generated declaration files from dist/ and contains no runtime code.

License

MIT