1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255
<!-- NOTE: Keep this file updated as the project evolves. When making architectural changes, adding new patterns, or discovering important conventions, update the relevant sections. -->
# H3 - Agent Guide
H3 (pronounced /eɪtʃθriː/) is a minimal HTTP framework built for high performance and portability. Currently on **v2** — a major rewrite based on **web standard primitives** (Request, Response, URL, Headers).
## Quick Reference
```bash
# Setup
corepack enable && pnpm install
# Development
pnpm dev # vitest watch mode
pnpm vitest run <path> # run specific test
pnpm test # full suite (lint + typecheck + coverage)
pnpm build # build with obuild
pnpm lint # oxlint + oxfmt --check (lint + typecheck)
pnpm fmt # automd + oxlint --fix + oxfmt
pnpm bench:node # node benchmarks
pnpm bench:bun # bun benchmarks
```
## Architecture
### Core Design
- **Web standards first**: Built on native `Request`, `Response`, `URL`, `Headers`
- **Multi-runtime**: Node.js, Bun, Deno, Cloudflare Workers, Service Workers, browsers
- **Minimal core**: 2 production deps (`rou3` for routing, `srvx` for server abstraction)
- **Handler-based**: Composable handlers + middleware, no class-heavy patterns
- **Type-safe**: Strict TypeScript with generic inference throughout
### Key Classes
| Class | File | Purpose |
| -------------- | ----------------- | --------------------------------------------------------------------------------- |
| `H3` | `src/h3.ts` | Main app class (extends `H3Core`), adds routing methods (get/post/put/delete/...) |
| `H3Event` | `src/event.ts` | Request wrapper — wraps web `Request` with lazy properties (URL, context) |
| `HTTPError` | `src/error.ts` | Structured HTTP error with status, data, headers |
| `HTTPResponse` | `src/response.ts` | Flexible response builder |
### Request Flow
1. Request enters via platform adapter (`src/_entries/*.ts`)
2. `H3.fetch()` creates `H3Event` from `Request`
3. Global `onRequest` hooks run
4. Middleware chain executes (matched by route/method)
5. Route handler processes request, returns a value
6. `toResponse()` converts return value → `Response` (auto-handles JSON, streams, blobs, primitives)
7. Global `onResponse` hooks run
## Project Structure
```
src/
├── index.ts # Public API exports
├── h3.ts # H3Core + H3 classes
├── event.ts # H3Event
├── handler.ts # defineHandler, defineValidatedHandler, etc.
├── middleware.ts # Middleware system
├── response.ts # toResponse, HTTPResponse
├── error.ts # HTTPError
├── adapters.ts # Web/Node handler adapters
├── tracing.ts # Tracing plugin (separate entry point)
├── types/ # Type definitions
│ ├── h3.ts # App types (H3Config, H3Plugin, H3Route, HTTPMethod)
│ ├── handler.ts # Handler types (EventHandler, Middleware)
│ ├── context.ts # H3EventContext
│ ├── route-rules.ts # RouteRules (shared, augmentable: merged rule options on event.context.routeRules)
│ └── _utils.ts # Internal type helpers
├── utils/ # ~30 utility modules (public API)
│ ├── request.ts # getQuery, getRouterParams, getRequestURL, ...
│ ├── response.ts # redirect, noContent, html, iterable, ...
│ ├── body.ts # readBody, readValidatedBody, assertBodySize
│ ├── cookie.ts # getCookie, setCookie, parseCookies, chunked cookies
│ ├── session.ts # getSession, useSession, sealSession, ...
│ ├── auth.ts # requireBasicAuth, basicAuth
│ ├── cors.ts # handleCors, appendCorsHeaders, ...
│ ├── proxy.ts # proxy, proxyRequest, fetchWithEvent
│ ├── ws.ts # defineWebSocketHandler, defineWebSocket
│ ├── json-rpc.ts # defineJsonRpcHandler, defineJsonRpcWebSocketHandler
│ ├── event-stream.ts # createEventStream (SSE)
│ ├── static.ts # serveStatic
│ ├── cache.ts # handleCacheHeaders
│ ├── middleware.ts # onRequest, onResponse, onError, bodyLimit
│ ├── route.ts # defineRoute
│ ├── base.ts # withBase
│ └── internal/ # Internal helpers (not exported)
│ ├── auth.ts, body.ts, cors.ts, encoding.ts, ...
│ ├── iron-crypto.ts # Session sealing crypto
│ ├── standard-schema.ts # Standard schema validation
│ └── validate.ts
├── rules/ # Route rules (h3/rules subpath entries)
│ ├── index.ts # h3/rules — routeRules middleware, matchers, built-in handlers
│ ├── middleware.ts # routeRules() plug-and-play middleware
│ ├── normalize.ts # normalizeRouteRules (config → runtime rules)
│ ├── match.ts # createRouteRulesMatcher, createMatcherFromFind, memoize
│ ├── merge.ts # mergeMatchedRouteRules (layer merge semantics)
│ ├── types.ts # RouteRuleConfig, NormalizedRouteRules, MatchedRouteRule, RuleHandler
│ ├── cache.ts # h3/rules/cache — ocache-backed cache handler (optional peer)
│ ├── proxy.ts # h3/rules/proxy — proxyRequest-backed proxy handler
│ ├── compiler.ts # h3/rules/compiler — build-time codegen
│ ├── handlers/ # Built-in rule handlers (headers, redirect, cors, cache)
│ ├── compiler/ # Codegen internals (compile, codegen, runtime-rules, options)
│ └── internal/ # key parsing, scope checks, node-key bucketing, pre-merge analysis
├── _entries/ # Platform-specific entry points
│ ├── generic.ts # Web Worker / Browser
│ ├── node.ts # Node.js (adds toNodeHandler)
│ ├── bun.ts # Bun
│ ├── deno.ts # Deno
│ ├── cloudflare.ts # Cloudflare Workers
│ ├── service-worker.ts # Service Workers
│ └── _common.ts # Shared entry utilities
└── _deprecated.ts # Deprecated exports (v1 compat)
test/
├── _setup.ts # Test infrastructure (describeMatrix, setupWebTest, setupNodeTest)
├── *.test.ts # ~30 integration test files
├── rules/ # Route rules tests (+ type tests: types.test-d.ts)
├── unit/ # Unit tests (including type tests: types.test-d.ts)
├── bench/ # Benchmarks (mitata)
└── fixture/ # Runtime-specific playground fixtures
```
## Code Conventions
### Style
- **ESM only** — no CommonJS
- **Explicit `.ts` extensions** in all import paths
- **No barrel files** — import directly from specific modules
- **Internal files** use `_` prefix (e.g., `_deprecated.ts`, `_entries/`, `_utils.ts`)
- **Internal helpers** go at the end of files or in `utils/internal/`
- **Short files** — aim for < 200 LoC per file, split when larger
- **Options object** as second param for multi-arg functions
- Formatting: `oxfmt` (no config, uses defaults)
- Linting: `oxlint` with `unicorn`, `typescript`, `oxc` plugins
### Naming
- `k` prefix for symbol constants (`kNotFound`, `kHandled`)
- `~` prefix for private/non-enumerable properties
- `#` for truly private class fields
- `define*()` for factory functions (`defineHandler`, `defineMiddleware`, `defineWebSocketHandler`)
- `to*()` for conversion functions (`toResponse`, `toEventHandler`, `toWebHandler`)
- `from*()` for adapter functions (`fromWebHandler`, `fromNodeHandler`)
### TypeScript
- Strict mode + `isolatedDeclarations` + `verbatimModuleSyntax`
- `erasableSyntaxOnly: true` (no enums, no namespaces)
- Target/module: `ESNext` / `NodeNext`
- Lib: `["ESNext", "WebWorker", "DOM", "DOM.Iterable"]`
- Heavy use of generics for type inference in handlers
### Response Handling
Handlers return values directly — no `res.send()` pattern:
- Return `string` → text response
- Return `object` → JSON response
- Return `Response` / `HTTPResponse` → direct response
- Return `ReadableStream` / `Blob` / `File` → streamed response
- Return `kNotFound` symbol → 404
- Return `kHandled` symbol → already handled (SSE, WebSocket, etc.)
## Testing
### Framework
- **Vitest** v4+ with **v8** coverage
- Matrix testing: every test runs in both `web` and `node` modes
### Writing Tests
```typescript
import { describeMatrix } from "./_setup.ts";
describeMatrix("feature name", (ctx, { it, expect }) => {
it("does something", async () => {
ctx.app.get("/test", () => "hello");
const res = await ctx.fetch("/test");
expect(await res.text()).toBe("hello");
});
});
```
Key patterns:
- Use `describeMatrix` for cross-runtime tests
- `ctx.app` is a fresh `H3` instance per test (via `beforeEach`)
- `ctx.fetch` handles URL resolution for both web/node
- `ctx.errors` tracks unhandled errors (auto-asserted in `afterEach`)
- Use `it.skipIf(ctx.target === "node")` for runtime-specific skips
### Running Tests
```bash
pnpm vitest run test/body.test.ts # single file
pnpm vitest run test/unit/ # unit tests
pnpm dev # watch mode (all)
pnpm test # full: lint + typecheck + coverage
```
### Bug Fix Workflow
1. Write regression test that reproduces the bug
2. Confirm test **fails** before any code changes
3. Fix the implementation (minimal change)
4. Confirm test **passes**
5. Run broader test suite for regressions
## Build
- **obuild** with Rolldown bundler
- 6 platform entries + `tracing.ts` and the 4 `rules/*` entries as separate entries
- Code splitting enabled (`h3-[hash].mjs` chunks)
- Custom plugin strips comments (preserves `#/@` annotations)
- Output: `dist/_entries/*.mjs` + `dist/*.d.mts`
### Package Exports
```
h3 → auto-resolved by runtime (deno/bun/workerd/node/default)
h3/node → Node.js runtime (adds toNodeHandler)
h3/bun → Bun runtime
h3/deno → Deno runtime
h3/cloudflare → Cloudflare Workers
h3/service-worker → Service Workers
h3/generic → Universal web standard
h3/tracing → Tracing plugin
h3/rules → Route rules (routeRules middleware, matchers, built-in handlers)
h3/rules/cache → ocache-backed `cache` rule handler (optional `ocache` peer)
h3/rules/proxy → `proxy` rule handler (pulls in proxyRequest)
h3/rules/compiler → Build-time route rules codegen
```
## Dependencies
| Dep | Purpose |
| --------- | --------------------------------------------------------- |
| `rou3` | Route matching engine |
| `srvx` | Server abstraction (multi-runtime) |
| `crossws` | WebSocket abstraction (optional peer dep) |
| `ocache` | Response caching for `h3/rules/cache` (optional peer dep) |
## Best Practices for Contributing
- Prefer web standard APIs over runtime-specific ones
- Keep the core minimal — add utilities, not core complexity
- Test across runtimes using `describeMatrix`
- Return values from handlers instead of mutating responses
- Use `defineHandler`/`defineMiddleware` for type safety
starting shell…