Web Standards end to end

The feature

June's entire request path is the Web platform's own vocabulary:

fetch(request: Request): Promise<Response>

That signature is the framework. Inside it: URL for routing, Headers for negotiation, ReadableStream for HTML streaming (renderToReadableStream, not the Node-only pipeable variant), Web crypto for IDs, the Cache API where the host offers one. @junejs/core imports zero node:* modules — enforced as an architectural rule, not a habit.

Standards as the portability mechanism

This is why deployment is an adapter instead of a rewrite (Deployment):

  • workerd speaks fetch natively — the built worker's fetch() IS the pipeline, no shim.
  • Bun serves it directly (Bun.serve({ fetch })).
  • Node needs only an edge adapter: node:http requests are converted to Request / from Response at the boundary (Readable.toWeb / fromWeb) — and nothing below the boundary knows.

Same story for data: the db contract is async-first because edge databases (D1) are async — the standard-shaped surface is the one every target can implement.

Try it

The MCP endpoint is a plain standards handler too — mcpHandler(request) takes a Request like everything else. There is no framework-specific request object to learn and no middleware tower to thread:

// a June app is embeddable anywhere a fetch handler runs
const app = createApp({ appDir, config });
export default { fetch: (req: Request) => app.fetch(req) };

Why it matters

Framework-specific request objects are how lock-in starts. When the interface is the platform's own, your knowledge transfers in, your code transfers out, and new runtimes are adapters — for us and for you.