Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
306 changes: 13 additions & 293 deletions README.md

Large diffs are not rendered by default.

19 changes: 3 additions & 16 deletions packages/aws-lambda/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,9 @@ Standard Server provides a unified interface for client-server communication acr

This package is the AWS Lambda adapter for that model. It converts an API Gateway proxy event — payload format version 1.0 or 2.0, the latter also used by Lambda Function URLs — into a `StandardLazyRequest`, and writes a `StandardResponse` back through the stream provided by `awslambda.streamifyResponse`, so streaming bodies such as server-sent events flow to the client as they are produced instead of being buffered.

## Entry Point

The package exports a single entry point:

| Export | Purpose |
| ---------------------------- | ---------------------------------------------------------- |
| `@standardserver/aws-lambda` | AWS Lambda adapter helpers for events and response streams |

## Package overview

The main entry point exposes these helpers:
The package exposes these helpers:

| Group | Exports | Purpose |
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
Expand Down Expand Up @@ -89,12 +81,7 @@ export const handler = awslambda.streamifyResponse(async (event, responseStream,

## Resolving Body

The event carries the request body as a fully buffered, optionally base64-encoded string. `resolveBody(hint?)` decodes it and determines how to parse it using the following priority:

1. If `hint?` is provided, use it as the `StandardBodyHint`.
2. Otherwise, if the `standard-server` header is present, use it as the `StandardBodyHint`.
3. Otherwise, if `content-type` is one of the common types, parse accordingly.
4. Otherwise, if `content-length` exists, treat the body as `file`; if not, treat it as `octet-stream`.
The event carries the request body as a fully buffered, optionally base64-encoded string. `resolveBody(hint?)` decodes it and then follows the shared Standard Server resolution rules: an explicit `hint` wins, then the [`standard-server` header](../core/README.md#the-standard-server-header), then inference from the content headers. See [how body parsing works](../core/README.md#how-body-parsing-works) in the core README for the full algorithm.

> [!TIP]
> For efficient communication, set the `standard-server` header to explicitly hint the body type, especially for file or binary streaming. For example, if you upload a file with a common `content-type` such as `application/json` but omit the `standard-server` header, the server may interpret it as JSON and parse it unexpectedly.
Expand All @@ -111,7 +98,7 @@ The event carries the request body as a fully buffered, optionally base64-encode

For the higher-level project overview, see the root [Standard Server README](../../README.md).

For the Node.js primitives this adapter is built on, see the [Node.js adapter documentation](../node/README.md).
For the Node.js primitives this adapter is built on, see the [Node.js adapter documentation](../node/README.md), and for the shared contract, see the [core documentation](../core/README.md).

## Sponsors

Expand Down
84 changes: 63 additions & 21 deletions packages/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,11 @@

Standard Server provides 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.

This package is the foundation of that model. It defines the core request and response types, shared runtime validators, small utility helpers, and event stream (SSE) helpers.

## Entry Points

| Entry point | Purpose |
| ---------------------- | -------------------------------------------------------- |
| `@standardserver/core` | Shared request/response types, utilities, and validators |
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 main entry point exposes four transport-agnostic shapes:
The package exposes four transport-agnostic shapes:

| Export | Description |
| ---------------------- | --------------------------------------------------------------- |
Expand Down Expand Up @@ -85,35 +79,79 @@ export async function handle(request: StandardLazyRequest): Promise<StandardResp
| `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 payloads |
| `octet-stream` | `ReadableStream<Uint8Array>` | any | Binary streaming |
| `file` | `File` | any | Fixed-size binary payloads for both `File` and `Blob` |
| `none` | `undefined` | | Empty body |

### Resolving Body
> [!NOTE]
> Since `File` extends `Blob`, `resolveBody` always returns a `File` when representing either `File` or `Blob` bodies.

## 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](../peer/README.md), 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

`resolveBody(hint?)` determines how to parse the body using the following priority:
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:

1. If `hint?` is provided, use it as the `StandardBodyHint`.
2. Otherwise, if the `standard-server` header is present, use it as the `StandardBodyHint`.
3. Otherwise, if `content-type` is one of the common types, parse accordingly.
4. Otherwise, if `content-length` exists, treat the body as `file`; if not, treat it as `octet-stream`.
- 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.

For efficient communication, set the `standard-server` header to explicitly hint the body type, especially for file or binary streaming. For example, if you upload a file with a common `content-type` such as `application/json` but omit the `standard-server` header, the server may interpret it as JSON and parse it unexpectedly.
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:

```ts
const response = await fetch('/upload', {
method: 'POST',
headers: {
'content-type': 'application/json',
'standard-server': 'file', // <- hint the body type to avoid misinterpretation
'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 `0` → `none`.
2. A common `content-type` → `application/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-length` → `file`.
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:

```ts
import { resolveStandardBodyHint } from '@standardserver/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 main entry point also exports a small set of helpers for common header and URL operations.
The package also exports a small set of helpers for common header and URL operations.

### Content-Disposition helpers

Expand Down Expand Up @@ -201,7 +239,7 @@ Use Event-Stream Helpers when you need explicit SSE encoding, decoding, or metad

### Message types and codecs

The event-stream entry point exposes:
The event-stream helpers include:

- `EventMeta` for `id`, `retry`, and `comments`
- `EventStreamMessage` for complete SSE messages
Expand Down Expand Up @@ -271,12 +309,14 @@ const response: StandardResponse = {
}
```

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](https://developer.mozilla.org/en-US/docs/Web/API/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.

> [!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 subpath also exports:
The package also exports:

- `EventStreamEncoderError` for invalid outbound SSE messages
- `EventStreamDecoderError` for incomplete or invalid inbound stream decoding
Expand All @@ -300,7 +340,9 @@ error.data

## Learn more

For the higher-level project overview, see the root [Standard Server README](../../README.md).
For the higher-level project overview and adapter quick-starts, see the root [Standard Server README](../../README.md).

Adapter documentation: [Fetch](../fetch/README.md) · [Node.js](../node/README.md) · [Fastify](../fastify/README.md) · [AWS Lambda](../aws-lambda/README.md) · [Peer](../peer/README.md)

## Sponsors

Expand Down
21 changes: 4 additions & 17 deletions packages/fastify/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,13 @@

Standard Server provides a unified interface for client-server communication across HTTP and message-based transports. It lets you write handlers against the same request, response, body, and streaming primitives whether the underlying transport is the Fetch API, Node.js HTTP, HTTP/2, or a peer-style message channel.

This package is the Fastify adapter for that model. It builds on `@standardserver/node`, reusing the same body, URL, and abort-signal primitives, while routing the response back through Fastify's reply lifecycle so hooks, plugins, and serializers keep working. Both `Fastify()` and `Fastify({ http2: true })` instances are supported.

## Entry Point

The package exports a single entry point:

| Export | Purpose |
| ------------------------- | ------------------------------------------------ |
| `@standardserver/fastify` | Fastify adapter helpers for requests and replies |
This package is the Fastify adapter for that model. It builds on [`@standardserver/node`](../node/README.md), reusing the same body, URL, and abort-signal primitives, while routing the response back through Fastify's reply lifecycle so hooks, plugins, and serializers keep working. Both `Fastify()` and `Fastify({ http2: true })` instances are supported.

`fastify` is a peer dependency, so the adapter always uses the Fastify version installed in your project.

## Package overview

The main entry point exposes two helpers and their option shapes:
The package exposes two helpers and their option shapes:

| Group | Exports | Purpose |
| ----------------------- | ------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
Expand Down Expand Up @@ -93,12 +85,7 @@ await fastify.listen({ port: 3000 })

## Resolving Body

`resolveBody(hint?)` returns the body Fastify already parsed with its own content type parsers, if there is one. Otherwise it falls back to `toStandardBody()` from `@standardserver/node`, which determines how to parse the body using the following priority:

1. If `hint?` is provided, use it as the `StandardBodyHint`.
2. Otherwise, if the `standard-server` header is present, use it as the `StandardBodyHint`.
3. Otherwise, if `content-type` is one of the common types, parse accordingly.
4. Otherwise, if `content-length` exists, treat the body as `file`; if not, treat it as `octet-stream`.
`resolveBody(hint?)` returns the body Fastify already parsed with its own content type parsers, if there is one. Otherwise it falls back to `toStandardBody()` from `@standardserver/node`, which follows the shared Standard Server resolution rules: an explicit `hint` wins, then the [`standard-server` header](../core/README.md#the-standard-server-header), then inference from the content headers. See [how body parsing works](../core/README.md#how-body-parsing-works) in the core README for the full algorithm.

Because Fastify's own parsers win, a `hint` only applies to bodies Fastify left unparsed. Fastify ships parsers for `application/json` and `text/plain`, and rejects every other content type with `415 Unsupported Media Type` unless you register one. To let the adapter own body parsing end to end, register a catch-all parser that leaves the body untouched:

Expand Down Expand Up @@ -138,7 +125,7 @@ Fastify owns the reply lifecycle, so a few of its rules apply to the response th

For the higher-level project overview, see the root [Standard Server README](../../README.md).

For the Node.js primitives this adapter is built on, see the [Node.js adapter documentation](../node/README.md).
For the Node.js primitives this adapter is built on, see the [Node.js adapter documentation](../node/README.md), and for the shared contract, see the [core documentation](../core/README.md).

## Sponsors

Expand Down
Loading
Loading