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

@standard-server/core

v0.9.2

Published

The Standard Server contract: transport-agnostic request, response, body, and streaming types with body parsing rules, runtime validators, and SSE helpers shared by every adapter

Readme

Standard Server

@standard-server/core is the shared contract package for Standard Server — a unified interface for client-server communication across HTTP and message-based transports. It lets you keep handler and client code transport-agnostic by working with the same request, response, body, and streaming abstractions whether the transport is Fetch, Node.js HTTP, or a peer-style message channel.

Standard Server ships as a small ecosystem of packages:

| Package | Description | | --------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | | @standard-server/core | The shared contract: types, body parsing rules, validators, and SSE helpers | | @standard-server/fetch | Fetch API adapter for browsers, workers, and other Fetch-based runtimes | | @standard-server/node | Node.js HTTP and HTTP/2 adapter | | @standard-server/fastify | Fastify adapter built on the Node.js adapter | | @standard-server/aws-lambda | AWS Lambda adapter with response streaming | | @standard-server/peer | Message-based adapter for WebSocket, MessagePort, and custom transports | | @standard-server/shared | Internal utilities shared across the ecosystem |

This package is the foundation of that model. It defines the request and response types every adapter converts to and from, the body parsing rules they all share, runtime validators, header and URL utilities, and event stream (SSE) helpers.

Request and response types

The package exposes four transport-agnostic shapes:

| Export | Description | | ---------------------- | --------------------------------------------------------------- | | StandardRequest | Eager request object with a parsed body | | StandardLazyRequest | Request object with resolveBody(hint?) for lazy body parsing | | StandardResponse | Eager response object with a parsed body | | StandardLazyResponse | Response object with resolveBody(hint?) for lazy body parsing |

Supporting primitives:

| Export | Description | | ------------------ | ------------------------------------------------------------- | | StandardMethod | Common HTTP verbs plus any custom string value | | StandardUrl | A request URL that must start with / and exclude the origin | | StandardHeaders | Record<string, string \| string[] \| undefined> | | StandardBodyHint | Parsing hint for lazy body resolution | | StandardBody | Shared body union used by requests and responses |

By convention, adapters normalize headers to lowercase keys. signal is part of StandardRequest only and is used to propagate request cancellation.

import type { StandardLazyRequest, StandardResponse } from '@standard-server/core'

export async function handle(request: StandardLazyRequest): Promise<StandardResponse> {
  const body = await request.resolveBody()

  return {
    status: 200,
    headers: { 'content-type': 'application/json' },
    body: {
      ok: true,
      method: request.method,
      url: request.url,
      received: body,
    },
  }
}

Body types

StandardBodyHint and StandardBody describe the shared body contract used across adapters:

| Hint | StandardBody value | Typical content type | Notes | | ------------------- | ------------------------------ | ----------------------------------- | ----------------------------------------------------- | | json | unknown | application/json | Primitives, objects, and arrays | | form-data | FormData | multipart/form-data | Multipart form submissions | | url-search-params | URLSearchParams | application/x-www-form-urlencoded | URL-encoded forms | | event-stream | AsyncIteratorObject<unknown> | text/event-stream | Server-Sent Events (SSE) | | octet-stream | ReadableStream<Uint8Array> | any | Binary streaming | | file | File | any | Fixed-size binary payloads for both File and Blob | | none | undefined | | Empty body |

JSON body

Standard Server treats primitive values, objects, and arrays as JSON.

import type { StandardRequest } from '@standard-server/core'

const request: StandardRequest = {
  method: 'POST',
  url: '/submit',
  headers: {},
  body: { name: 'John Doe', email: '[email protected]' },
}

FormData and URLSearchParams body

Standard Server treats FormData and URLSearchParams as form submissions.

import type { StandardRequest } from '@standard-server/core'

const requestWithURLSearchParams: StandardRequest = {
  method: 'POST',
  url: '/submit',
  headers: {},
  body: new URLSearchParams({ name: 'John Doe', email: '[email protected]' }),
}

const formData = new FormData()
formData.append('name', 'John Doe')
formData.append('file', new Blob(['Hello, World!'], { type: 'text/plain' }), 'hello.txt')

const requestWithFormData: StandardRequest = {
  method: 'POST',
  url: '/submit',
  headers: {},
  body: formData,
}

[!TIP] HTML forms submit data as application/x-www-form-urlencoded or multipart/form-data, so these body types are especially helpful there.

File and Blob body

Standard Server treats File and Blob as fixed-size binary payloads.

[!NOTE] Since File extends Blob, resolveBody always returns a File when representing either File or Blob bodies.

import type { StandardResponse } from '@standard-server/core'

const response: StandardResponse = {
  status: 200,
  headers: {
    'content-disposition': [], // <- remove auto-set header
  },
  body: new File(['Hello, World!'], 'hello.txt', { type: 'text/plain' }),
}

When sending a file or blob body, adapters automatically set the content-length, content-type, content-disposition, and standard-server headers based on the provided body. You can override any of them by explicitly providing a header value, or remove one entirely by assigning an empty array.

Event-stream body

Standard Server uses AsyncIteratorObject to represent an event stream body, and you can use withEventMeta() to attach additional SSE event metadata to each emitted event.

import type { StandardResponse } from '@standard-server/core'
import { ErrorEvent, withEventMeta } from '@standard-server/core'

const response: StandardResponse = {
  status: 200,
  headers: {},
  async* body() {
    yield withEventMeta(
      { message: 'Hello, World!' },
      { id: '1', retry: 3000, comments: ['hidden'] },
    )

    throw new ErrorEvent({ message: 'Something went wrong' })

    return { message: 'This is the end of the stream' }
  },
}

Events are interpreted as follows: yield emits a message, throw emits an error, and return emits a close event. Note that close does not cause EventSource to close the connection because it is not part of the SSE specification. However, when using Standard Server for client-side streaming, close is treated as the end of the stream, so the connection is closed and no reconnection is attempted.

For explicit SSE encoding, decoding, and metadata handling, see the Event-Stream Helpers below.

Octet-stream body

Standard Server uses ReadableStream to represent a binary streaming body.

import type { StandardResponse } from '@standard-server/core'

const response: StandardResponse = {
  status: 200,
  headers: {
    'content-type': 'application/octet-stream',
  },
  body: new ReadableStream<Uint8Array>({
    start(controller) {
      const encoder = new TextEncoder()
      controller.enqueue(encoder.encode('Hello, World!'))
      controller.close()
    },
  }),
}

When sending a binary streaming body, adapters automatically set the content-type and standard-server headers. You can override content-type by providing an explicit header value, or remove it entirely by assigning an empty array.

How body parsing works

[!NOTE] This section applies to the HTTP adapters (Fetch, Node.js, Fastify, AWS Lambda). It does not apply to the peer adapter, which identifies body types through its own message protocol — a different but fairly similar mechanism.

resolveBody(hint?) on StandardLazyRequest and StandardLazyResponse resolves the body lazily — the underlying stream is only consumed once you call it. The StandardBodyHint that decides how the raw body is parsed comes from three places: an explicit hint argument, the standard-server header, or inference from the content headers.

The standard-server header

A StandardBody is richer than what HTTP content headers can describe. content-type tells the receiver the media type of the bytes, but not which StandardBody representation the sender intended:

  • A file upload can legitimately carry content-type: application/json. Without more information, the receiver would parse it into a JSON value when the sender meant a File to be stored as-is.
  • A fixed-size binary payload (file) and a binary stream (octet-stream) can share any content type. Telling them apart otherwise depends on content-length, which proxies may rewrite and some runtimes drop when the payload is empty.

The standard-server header closes this gap. It carries the sender's StandardBodyHint verbatim — json, form-data, url-search-params, event-stream, octet-stream, file, or none — so the receiver reconstructs exactly the body representation the sender had.

Adapters set the header automatically for the ambiguous body types: a Blob or File body is sent with standard-server: file, and a ReadableStream body with standard-server: octet-stream, alongside the usual content headers. A header you set yourself always wins, and assigning an empty array removes it entirely. For the other body types, the content headers are enough, so adapters clear it.

The header is optional: when it is absent or invalid, the receiver falls back to content-header inference, so plain HTTP clients work as-is. Just set the header yourself whenever the content type alone could be misread:

const response = await fetch('/upload', {
  method: 'POST',
  headers: {
    'content-type': 'application/json',
    'standard-server': 'file', // <- keep the payload a File on the server
  },
  body: new Blob(['{"message": "Hello, world!"}'], { type: 'application/json' }),
})

Resolution order

The hint is chosen in this order:

  1. Explicit hint argument. If you pass a hint to resolveBody(hint), it always wins.
  2. The standard-server header. If present and holding a valid hint value, it is used verbatim. Unknown values are ignored.
  3. Content-header inference:
    1. No content-type, and content-length absent or 0none.
    2. A common content-typeapplication/json parses as json, multipart/form-data as form-data, application/x-www-form-urlencoded as url-search-params, and text/event-stream as event-stream. Media type casing and parameters such as ; charset=utf-8 are ignored.
    3. A content-disposition carrying a filename, or any content-lengthfile.
    4. Anything else → octet-stream.

This resolution is implemented once in this package as resolveStandardBodyHint(headers) and shared by every HTTP adapter, so the same body parses the same way regardless of which HTTP transport carried it:

import { resolveStandardBodyHint } from '@standard-server/core'

resolveStandardBodyHint({ 'content-type': 'application/json' })
// 'json'

resolveStandardBodyHint({
  'content-type': 'application/json',
  'standard-server': 'file',
})
// 'file'

resolveStandardBodyHint({})
// 'none'

Use it when building a custom adapter, or when you need to know how a body will parse without consuming it.

Utilities

The package also exports a small set of helpers for common header and URL operations.

Content-Disposition helpers

Use generateContentDisposition() to produce a safe Content-Disposition value and getFilenameFromContentDisposition() to read a filename back from an existing header.

import {
  generateContentDisposition,
  getFilenameFromContentDisposition,
} from '@standard-server/core'

const disposition = generateContentDisposition('report "Q2".csv')
// inline; filename="report \"Q2\".csv"; filename*=utf-8''report%20%22Q2%22.csv

const filename = getFilenameFromContentDisposition(disposition)
// 'report "Q2".csv'

generateContentDisposition() preserves an ASCII-safe filename="..." value and also emits filename*= for UTF-8 aware clients.

Header helpers

mergeStandardHeaders() combines two StandardHeaders objects while preserving duplicate values, and flattenStandardHeader() turns a single header value into a plain string when needed.

import {
  flattenStandardHeader,
  mergeStandardHeaders,
} from '@standard-server/core'

const headers = mergeStandardHeaders(
  { 'accept': 'application/json', 'set-cookie': ['a=1'] },
  { 'set-cookie': 'b=2', 'vary': 'accept', 'warning': undefined },
)
// {
//   accept: 'application/json',
//   'set-cookie': ['a=1', 'b=2'],
//   vary: 'accept',
// }

const cookieHeader = flattenStandardHeader(headers['set-cookie'])
// 'a=1, b=2'

URL parsing

parseStandardUrl() splits a StandardUrl into [pathname, search, hash] without requiring a full origin.

import { parseStandardUrl } from '@standard-server/core'

const [pathname, search, hash] = parseStandardUrl('/users/123?tab=settings#profile')
// pathname => '/users/123'
// search => '?tab=settings'
// hash => '#profile'

Validators

Runtime type guards are useful when requests or responses cross process, transport, or message boundaries.

| Export | Checks | | ---------------------- | ------------------------------------------------------ | | isStandardMethod() | Any string value | | isStandardUrl() | A string starting with / | | isStandardHeaders() | Object values are string, string[], or undefined | | isStandardRequest() | | | isStandardResponse() | |

import { isStandardRequest } from '@standard-server/core'

export function expectStandardRequest(input: unknown) {
  if (!isStandardRequest(input)) {
    throw new TypeError('Expected a StandardRequest-compatible value')
  }

  return input
}

Event-Stream Helpers

Use Event-Stream Helpers when you need explicit SSE encoding, decoding, or metadata handling.

Message types and codecs

The event-stream helpers include:

  • EventMeta for id, retry, and comments
  • EventStreamMessage for complete SSE messages
  • encodeEventStreamMessage() and decodeEventStreamMessage() for single-message codec operations
  • EventStreamDecoder and EventStreamDecoderStream for chunked stream decoding
import {
  decodeEventStreamMessage,
  encodeEventStreamMessage,
} from '@standard-server/core'

const encoded = encodeEventStreamMessage({
  comments: ['bootstrap'],
  event: 'message',
  id: '42',
  retry: 3000,
  data: 'hello\nworld',
})

const decoded = decodeEventStreamMessage(encoded)
// {
//   comments: ['bootstrap'],
//   event: 'message',
//   id: '42',
//   retry: 3000,
//   data: 'hello\nworld',
// }

For streaming decode, pipe text chunks through EventStreamDecoderStream:

import { EventStreamDecoderStream } from '@standard-server/core'

const messages = response.body!
  .pipeThrough(new TextDecoderStream())
  .pipeThrough(new EventStreamDecoderStream())

Iterator metadata helpers

StandardBody uses async iterators for event-stream bodies. To attach SSE metadata to a yielded value without changing its visible shape, use withEventMeta().

import type { StandardResponse } from '@standard-server/core'
import { getEventMeta, unwrapEvent, withEventMeta } from '@standard-server/core'

const event = withEventMeta(
  { message: 'hello' },
  { id: '1', retry: 3000, comments: ['bootstrap'] },
)

const [data, meta] = unwrapEvent(event)
// data => { message: 'hello' }
// meta => { id: '1', retry: 3000, comments: ['bootstrap'] }

const extractedMeta = getEventMeta(event)
// { id: '1', retry: 3000, comments: ['bootstrap'] }

const response: StandardResponse = {
  status: 200,
  headers: {},
  async* body() {
    yield event
  },
}

[!WARNING] Metadata is validated before it is attached: id, event, and comments must not contain line breaks, and retry must be a non-negative integer.

Errors and low-level assertions

The package also exports:

  • EventStreamEncoderError for invalid outbound SSE messages
  • EventStreamDecoderError for incomplete or invalid inbound stream decoding
  • ErrorEvent for wrapping structured event-stream error payloads in an Error
  • assertEventStreamMessageId(), assertEventStreamMessageName(), assertEventStreamMessageRetry(), and assertEventStreamMessageComment() for low-level validation when building custom SSE tooling
import { ErrorEvent } from '@standard-server/core'

const error = new ErrorEvent(
  { code: 'E_STREAM', detail: 'Connection lost' },
  { message: 'stream error' },
)

error.message
// 'stream error'

error.data
// { code: 'E_STREAM', detail: 'Connection lost' }

Learn more

For transport-specific quick-starts and options, see the adapter documentation: Fetch · Node.js · Fastify · AWS Lambda · Peer

Sponsors

Like what we build over at middleapi? You can help keep it going through GitHub Sponsors or Open Collective. Every bit helps! 🚀

Special Sponsors

Organization Sponsors

Sponsors

Backers

With thanks to 36 past sponsors who helped get us here.

License

Distributed under the MIT License. See LICENCE for more information.