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 🙏

© 2024 – Pkg Stats / Ryan Hefner

hyper-ts

v0.7.9

Published

Type safe middleware architecture for HTTP servers

Downloads

2,560

Readme

A partial porting of https://github.com/owickstrom/hyper to TypeScript

hyper-ts is an experimental middleware architecture for HTTP servers written in TypeScript.

Its main focus is correctness and type-safety, using type-level information to enforce correct composition and abstraction for web servers.

Goals

The goal of hyper-ts is to make use of type system features in TypeScript to enforce correctly stacked middleware in HTTP server applications. All effects of middleware should be reflected in the types to ensure that common mistakes cannot be made. A few examples of such mistakes could be:

  • Incorrect ordering of header and body writing
  • Writing incomplete responses
  • Writing multiple responses
  • Trying to consume a non-parsed request body
  • Consuming a request body parsed as the wrong type
  • Incorrect ordering of, or missing, error handling middleware
  • Incorrect ordering of middleware for sessions, authentication, authorization
  • Missing authentication and/or authorization checks

TypeScript compatibility

| hyper-ts version | fp-ts version | typescript version | | ------------------ | --------------- | -------------------- | | 0.7.x+ | 2.10.5+ | 4.3+ | | 0.5.x+ | 2.0.5+ | 3.5+ | | 0.4.x+ | 1.15.0+ | 3.0.1+ |

Hello world

import * as express from 'express'
import * as H from 'hyper-ts'
import * as M from 'hyper-ts/Middleware'
import { toRequestHandler } from 'hyper-ts/express'
import { pipe } from 'fp-ts/function'

const hello: M.Middleware<H.StatusOpen, H.ResponseEnded, never, void> = pipe(
  M.status(H.Status.OK), // writes the response status
  M.ichain(() => M.closeHeaders()), // tells hyper-ts that we're done with the headers
  M.ichain(() => M.send('Hello hyper-ts on express!')) // sends the response as text
)

express()
  .get('/', toRequestHandler(hello))
  .listen(3000, () => console.log('Express listening on port 3000. Use: GET /'))

Sending a JSON

const hello = pipe(
  M.status(H.Status.OK),
  M.ichain(() => M.json({ a: 1 }, () => 'error'))
)

Core API

Connection

A Connection models the entirety of a connection between the HTTP server and the user agent, both request and response.

State changes are tracked by the phantom type S.

interface Connection<S> {
  readonly _S: S
  readonly getRequest: () => IncomingMessage
  readonly getBody: () => unknown
  readonly getHeader: (name: string) => unknown
  readonly getParams: () => unknown
  readonly getQuery: () => unknown
  readonly getOriginalUrl: () => string
  readonly getMethod: () => string
  readonly setCookie: (
    this: Connection<HeadersOpen>,
    name: string,
    value: string,
    options: CookieOptions
  ) => Connection<HeadersOpen>
  readonly clearCookie: (this: Connection<HeadersOpen>, name: string, options: CookieOptions) => Connection<HeadersOpen>
  readonly setHeader: (this: Connection<HeadersOpen>, name: string, value: string) => Connection<HeadersOpen>
  readonly setStatus: (this: Connection<StatusOpen>, status: Status) => Connection<HeadersOpen>
  readonly setBody: (this: Connection<BodyOpen>, body: unknown) => Connection<ResponseEnded>
  readonly endResponse: (this: Connection<BodyOpen>) => Connection<ResponseEnded>
}

By default hyper-ts manages the following states:

  • StatusOpen: Type indicating that the status-line is ready to be sent
  • HeadersOpen: Type indicating that headers are ready to be sent, i.e. the body streaming has not been started
  • BodyOpen: Type indicating that headers have already been sent, and that the body is currently streaming
  • ResponseEnded: Type indicating that headers have already been sent, and that the body stream, and thus the response, is finished

During the connection lifecycle the following flow is statically enforced

StatusOpen -> HeadersOpen -> BodyOpen -> ResponseEnded

Note. hyper-ts supports express 4.x by default by exporting a Connection instance from the hyper-ts/express module.

Middleware

A middleware is an indexed monadic action transforming one Connection to another Connection. It operates in the TaskEither monad, and is indexed by I and O, the input and output Connection types of the middleware action.

interface Middleware<I, O, E, A> {
  (c: Connection<I>): TaskEither<E, [A, Connection<O>]>
}

The input and output type parameters are used to ensure that a Connection is transformed, and that side-effects are performed, correctly, throughout the middleware chain.

Middlewares are composed using chain and ichain, the indexed monadic version of chain.

Type safety

Invalid operations are prevented statically

import { Status, status } from 'hyper-ts'

pipe(
  M.status(H.Status.OK),
  M.ichain(() => M.header('name', 'value')),
  M.ichain(() => M.closeHeaders()),
  M.ichain(() => M.send('Hello hyper-ts on express!')),
  // try to write a header after sending the body
  M.ichain(() => M.header('name', 'value')) // static error
)

No more "Can't set headers after they are sent." errors.

Decoding params, query and body

Input validation/decoding is done by defining a decoding function with the following signature

(input: unknown) => Either<L, A>

Example (decoding a param)

import * as H from 'hyper-ts'
import * as M from 'hyper-ts/Middleware'
import * as E from 'fp-ts/Either'

const isUnknownRecord = (u: unknown): u is Record<string, unknown> => typeof u === 'object' && u !== null

// returns a middleware validating `req.param.user_id`
export const middleware: M.Middleware<H.StatusOpen, H.StatusOpen, string, string> = M.decodeParam('user_id', u =>
  isUnknownRecord(u) && typeof u.user_id === 'string' ? E.right(u.user_id) : E.left('cannot read param user_id')
)

You can also use io-ts decoders.

import * as H from 'hyper-ts'
import * as M from 'hyper-ts/Middleware'
import * as t from 'io-ts'

// returns a middleware validating `req.param.user_id`
export const middleware2: M.Middleware<H.StatusOpen, H.StatusOpen, t.Errors, string> = M.decodeParam(
  'user_id',
  t.string.decode
)

Here I'm using t.string but you can pass any io-ts runtime type

import * as H from 'hyper-ts'
import * as M from 'hyper-ts/Middleware'
import { IntFromString } from 'io-ts-types/IntFromString'

// validation succeeds only if `req.param.user_id` can be parsed to an integer
export const middleware3: M.Middleware<
  H.StatusOpen,
  H.StatusOpen,
  t.Errors,
  t.Branded<number, t.IntBrand>
> = M.decodeParam('user_id', IntFromString.decode)

Multiple params

import * as H from 'hyper-ts'
import * as M from 'hyper-ts/Middleware'
import * as t from 'io-ts'

// returns a middleware validating both `req.param.user_id` and `req.param.user_name`
export const middleware = M.decodeParams(
  t.strict({
    user_id: t.string,
    user_name: t.string
  }).decode
)

Query

import * as H from 'hyper-ts'
import * as M from 'hyper-ts/Middleware'
import * as t from 'io-ts'

// return a middleware validating the query "order=desc&shoe[color]=blue&shoe[type]=converse"
export const middleware = M.decodeQuery(
  t.strict({
    order: t.string,
    shoe: t.strict({
      color: t.string,
      type: t.string
    })
  }).decode
)

Body

import * as H from 'hyper-ts'
import * as M from 'hyper-ts/Middleware'
import * as t from 'io-ts'

// return a middleware validating `req.body`
export const middleware = M.decodeBody(t.string.decode)

Here's an example using the standard express.json middleware

Documentation

Ecosystem