Hexagonal hierarchical geospatial indexing system

RAW Rules

AGENTS.md

<!-- 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