HYPERJS.AI

AN API FRAMEWORK FOR BUN, DISTRIBUTED AS SOURCE

Hyper is an HTTP framework for Bun. There is no @hyper/core in your package.json. The CLI copies the components you want into src/hyper/ as plain TypeScript — yours to read, edit, fork, or delete. hyper diff and hyper update keep the upgrade path intact.

Declare a route once. Hyper emits the runtime, an OpenAPI 3.1 document, a typed RPC client, and an MCP server from the same source.

> bun create hyper my-app
> bun run dev
Hyper listening on http://localhost:3000

THE REGISTRY

The framework is the registry. Every part — router, plugins, test helpers, OpenAPI and MCP adapters — is a folder of source files. Install what you need; the rest never enters your repo. The registry stays an installer, not a runtime dependency.

Each install is recorded per-file in hyper.lock.json. Local edits don't block upgrades — hyper update merges only the deltas you accept, the rest of the file stays exactly as you wrote it.

> bun create hyper my-app --template api

> hyper add cors auth-jwt session
  + src/hyper/cors/index.ts
  + src/hyper/auth-jwt/index.ts
  + src/hyper/auth-jwt/jwt.ts
  + src/hyper/session/index.ts
  ~ .env.local  (1 secret added: JWT_SECRET)

run:  bun add zod jose

> hyper diff cors
  ok    src/hyper/cors/index.ts
  drift src/hyper/cors/options.ts  (3 local edits)
installed cors, auth-jwt, session + transitive deps. updated hyper.lock.json.

ONE DEFINITION, MANY OUTPUTS

A route is one declaration: path, schemas, errors, handler. From it Hyper emits the runtime, an OpenAPI 3.1 document, a typed RPC client, and an MCP server. No parallel schema file, no decorator metadata, no second source of types.

Schemas use any Standard Schema library — Zod, Valibot, Arktype — directly. Errors declared on the route appear as response codes in OpenAPI, a discriminated union in the client, and assertions in the test runner.

import { Hyper, ok } from "@hyper/core"
import { z } from "zod"

export default new Hyper()
  .get("/health", "OK")
  .post(
    "/users",
    {
      body: z.object({
        name: z.string().min(1),
        email: z.email(),
      }),
      throws: { 409: z.object({ taken: z.string() }) },
    },
    async ({ body }) => {
      return ok({ id: crypto.randomUUID(), ...body })
    },
  )
  .listen(3000)
> hyper openapi > openapi.json     # 3.1 spec
> hyper client ./client.gen.ts     # typed RPC client
> hyper mcp                        # MCP server, no extra config
openapi 3.1 written -> openapi.json (12 routes, 5 schemas)

PLUGINS AND SUB-APPS

One composition primitive: .use(). It takes a plugin, a sub-app with its own prefix, an imported route file, or a function that decorates the request. The router flattens at build time — nesting is organisational, free at runtime.

In the registry today:

  • cors — CORS, with strict wildcard rejection when credentials are present
  • auth-jwt — JWT auth, EdDSA / RS256 / HS256, 32-byte secret floor
  • session — encrypted cookie sessions, CSRF double-submit
  • log — structured logging, header / body redaction
  • rate-limit — sliding window and token bucket, per-route override
  • csp — Content-Security-Policy, report-only mode, nonces
  • cache — RFC 9111-aware route caching with conditional revalidation
  • idempotency — idempotency-key handling for write methods
  • otel — OpenTelemetry traces and metrics
import { Hyper } from "@hyper/core"
import { corsPlugin } from "@hyper/cors"
import { logPlugin } from "@hyper/log"
import { authJwtPlugin } from "@hyper/auth-jwt"
import { rateLimitPlugin } from "@hyper/rate-limit"

import users from "./routes/users.ts"
import posts from "./routes/posts.ts"

export default new Hyper()
  .use(logPlugin())
  .use(corsPlugin({ origin: ["https://example.com"] }))
  .use(rateLimitPlugin({ window: "1m", max: 60 }))
  .use(authJwtPlugin())
  .use(users)              // honors its own prefix -> /users/*
  .use("/v1", posts)       // re-prefixed       -> /v1/posts/*
  .listen(3000)

BUN-NATIVE

Built for Bun directly — HTTP server, cookie map, password and hash APIs, file primitives. A thin layer on a fast runtime, not an abstraction over runtimes you don't ship to.

Handlers pay only for what they read. Static responses bypass the handler path. Security defaults — HSTS, body limits, strict CORS, secret-length floors, CSRF double-submit — are audited by hyper security --check in CI.

> hyper security --check

  ok  HSTS enabled in production
  ok  body limit <= 1 MB
  ok  prototype-pollution guards active
  ok  CORS rejects wildcard with credentials
  ok  JWT secret >= 32 bytes
  ok  session secret >= 32 bytes
  ok  CSRF double-submit on state-changing methods
  ... 5 more
ok - secure-by-default posture intact (12/12 checks).

ONE BINARY

One CLI for the whole loop: scaffold, dev (hot reload + type-check), test (.example() contracts + bun:test), bench, build, and the registry (add, diff, update, list).

Every command emits JSON with --json. Editor integrations, CI, and agents read the same surface you do.

hyper init [template]      scaffold + auto-install core
hyper dev  [entry]         hot reload + tsgo --watch
hyper build [entry]        bundle + route graph
hyper test                 .example() contracts + bun:test
hyper bench [entry]        per-route latency p50/p99
hyper openapi [out]        OpenAPI 3.1 spec
hyper client  <out>        typed RPC client
hyper mcp     [entry]      MCP server (--audit)
hyper routes  [entry]      print the route graph
hyper security --check     audit the secure-by-default posture

# registry
hyper add <name>...        install components into your repo
hyper diff <name>          show drift vs the registry
hyper update [...names]    bump installed components
hyper list  [query]        browse the catalog

MCP AS A PEER OF HTTP

Any route can opt into MCP exposure. The same schema, validation, and errors run whether the call arrives over HTTP or MCP — no second layer of glue.

The registry runs an MCP endpoint too. From an editor that speaks MCP, installing a Hyper component is the same operation as calling a route.

import { Hyper, ok } from "@hyper/core"
import { z } from "zod"

export default new Hyper({ prefix: "/users" })
  .post(
    "/",
    {
      body: z.object({ name: z.string(), email: z.email() }),
      mcp: {
        title: "Create user",
        description: "Provision a new tenant user. Sends a welcome email.",
      },
    },
    async ({ body }) => ok({ id: crypto.randomUUID(), ...body }),
  )
{
  "mcpServers": {
    "my-api":  { "url": "http://localhost:3000/mcp" },
    "hyper":   { "url": "https://hyperjs.ai/mcp" }
  }
}