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

@ts-http/core

v0.0.5

Published

Typed HTTP contract definitions and fetch client

Readme

@ts-http/core

npm GitHub

Contract types and typed fetch client for ts-http.

Define your API once in TypeScript. The types flow to both the client and server — no code generation, no schema files, no runtime validation overhead.

Installation

npm install @ts-http/core
# or
pnpm add @ts-http/core

Requires Node 18+ or a modern browser (native fetch).

Quick start

import { ApiDescription, createRestClient } from '@ts-http/core';

interface UserApi {
  getAll(): Promise<User[]>;
  getById(id: string): Promise<User>;
  create(data: { name: string; email: string }): Promise<User>;
  remove(id: string): Promise<void>;
}

const userApi: ApiDescription<UserApi> = {
  subRoute: '/api/users',
  mapping: {
    getAll:  { method: 'GET',    path: '' },
    getById: { method: 'GET',    path: ':id' },
    create:  { method: 'POST',   path: '' },
    remove:  { method: 'DELETE', path: ':id', resultType: 'NONE' },
  },
};

const client = createRestClient<UserApi>(userApi);

const all  = await client.getAll();        // User[]
const user = await client.getById('123'); // User
await client.remove('123');              // void

Omitting baseUrl sends requests to the current origin — the right default for most browser apps.

Server adapters

The same interface and ApiDescription contract you define for the client can be used on the server too — no duplication, full type safety end-to-end.

  • @ts-http/express — turns the contract into an Express router via createExpressRouter. Handlers receive plain typed arguments; no req/res/next boilerplate.
  • @ts-http/nestjs — use the contract directly in a NestJS controller with the @Action decorator and TypedController type. No extra adapter layer needed.

Client options

const client = createRestClient<MyApi>(api, baseUrl, {
  // swap in a custom fetch (e.g. for tests or environments without global fetch)
  fetch: myFetch,

  // called on every response
  onResponse: async (res, { method, url }) => {
    if (res.status === 401) throw new Error('Unauthorized');
  },

  // called on non-2xx errors or network failures
  onError: (error, context) => {
    console.error(error.message, context.url);
  },

  // pluggable logger — or pass logging: false to silence everything
  logger: myLogger,
  logging: false,

  // fallback result type when the route doesn't specify one
  defaultResultType: 'JSON',

  // type adapters for custom serialization/deserialization
  // default: [dateAdapter]  (ISO strings → Date objects)
  adapters: [dateAdapter, myDecimalAdapter],

  // full override for JSON parsing (e.g. superjson)
  parseJson: (text) => superjson.parse(text),
});

Result types

The resultType on a route entry (or defaultResultType on the client) controls how the response body is consumed:

| Value | What you get | |---|---| | JSON | Parsed object (default) | | TEXT | Raw string | | BLOB | Blob | | ARRAYBUFFER | ArrayBuffer | | STREAM | ReadableStream | | AUTO | Best-effort content-type handling: parse JSON responses automatically, otherwise return text | | NONE | Nothing — response body is ignored (useful for DELETE) |

AUTO is intentionally conservative. For binary downloads or streaming endpoints, prefer the explicit BLOB, ARRAYBUFFER, or STREAM result types.

Type adapters

Adapters hook into both directions: deserialize runs during response parsing, serialize runs during request body stringification.

The built-in dateAdapter converts ISO 8601 strings to Date objects on the way in:

import { dateAdapter } from '@ts-http/core';

const client = createRestClient(api, baseUrl, {
  adapters: [dateAdapter],
});

Pass adapters: [] to disable all transforms.

See the Luxon DateTime adapter and Axios adapter guides for ready-to-use examples.

Streaming

Set resultType: 'STREAM' on a route entry to receive a ReadableStream:

const fileApi: ApiDescription<FileApi> = {
  subRoute: '/files',
  mapping: {
    download: { method: 'GET', path: ':id', resultType: 'STREAM' },
  },
};

const stream = await client.download('report.pdf'); // ReadableStream

Logging

// errors only
const client = createRestClient(api, baseUrl, {
  logger: { error: console.error },
});

// silence everything
const client = createRestClient(api, baseUrl, { logging: false });

License

MIT © 2026 Clemens Meier