Hexagonal hierarchical geospatial indexing system
<!-- 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
β βββ _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
βββ _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
βββ 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` as separate entry
- 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
```
## Dependencies
| Dep | Purpose |
| --------- | ----------------------------------------- |
| `rou3` | Route matching engine |
| `srvx` | Server abstraction (multi-runtime) |
| `crossws` | WebSocket abstraction (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