Edge
`@mandujs/edge` ships edge-runtime adapters for Cloudflare Workers (Phase 15.1, shipped). Deno Deploy, Vercel Edge, and Netlify Edge adapters are scaffolded and land in Phase 15.2+.
On this page
Edge
@mandujs/edge is the Mandu package that lets you deploy the same
app to V8-isolate edge runtimes — Cloudflare Workers, Deno Deploy,
Vercel Edge, Netlify Edge. One codebase, multiple deploy targets.
Shipping status. Phase 15.1 ships the Cloudflare Workers adapter. Deno Deploy, Vercel Edge, and Netlify Edge are scaffolded stubs landing in Phase 15.2 / 15.3.
# Install
bun add @mandujs/edge
# Wrangler is a peer dep — install separately
bun add -D wrangler
# Build for Workers
mandu build --target=workers
# Dev + deploy via wrangler
wrangler dev
wrangler deploy
Pages
- Cloudflare Workers — getting started, wrangler.toml, compatibility flags, bindings.
- Deno / Vercel / Netlify — roadmap for Phase 15.2 adapters.
Polyfill mapping
Mandu's runtime paths are already 90% Web Fetch standard. The remaining Bun-specific APIs map to Workers equivalents as follows:
| Bun API | Workers equivalent | Status (15.1) |
|---|---|---|
Bun.serve |
export default { fetch } |
Done |
Bun.CookieMap |
LegacyCookieCodec (WebCrypto) |
Done |
Bun.CSRF |
crypto.subtle HMAC-SHA256 |
Done |
Bun.password |
@noble/hashes/argon2 (planned) |
Coming soon |
Bun.sql |
Neon serverless driver / D1 | Coming soon |
Bun.s3 |
aws4fetch / R2 binding |
Coming soon |
Bun.file |
KV binding / build-time inlining | Coming soon |
Bun.cron |
Workers Cron Triggers | Coming soon |
| SMTP | Permanent skip (use Resend) | Not planned |
Calling an unsupported Bun API from inside a Worker returns a structured 500 response:
{
"error": "BunApiUnsupportedOnEdge",
"api": "Bun.s3",
"correlationId": "019291f0-4a1c-7f2e-8c9a-...",
"message": "Bun.s3 is not yet polyfilled for the Workers runtime.",
"migration_guide": "/docs/edge#unsupported-apis",
"runtime": "workers"
}
No crash, no silent wrong behavior — a clear, machine-readable refusal pointing at the migration guide.
Why a separate package?
@mandujs/core is Bun-native by default — it ships with Bun.serve,
Bun.CookieMap, Bun.password, etc. as the first-class path.
@mandujs/edge is opt-in so projects that never touch edge don't
pay the polyfill overhead.
Typical package.json:
{
"dependencies": {
"@mandujs/core": "^0.24.0"
}
}
Once you add an edge target:
{
"dependencies": {
"@mandujs/core": "^0.24.0",
"@mandujs/edge": "^0.3.0"
},
"devDependencies": {
"wrangler": "^4.0.0"
}
}
Edge vs Pages Functions vs Node serverless
Choose the right runtime for the shape of your app:
| Runtime | Cold start | CPU cap (free) | Use when |
|---|---|---|---|
Cloudflare Workers (@mandujs/edge/workers) |
1–5ms | 10ms | Global latency matters; mostly-dynamic |
Cloudflare Pages + Functions (mandu deploy --target=cf-pages) |
1–5ms | 10ms | Mostly-static site with a sprinkle of dynamic |
Vercel Functions (mandu deploy --target=vercel) |
~900ms first | 60s | Rich Node ecosystem, generous timeouts |
Fly.io machines (mandu deploy --target=fly) |
~850ms | n/a | Long-lived processes, stateful |
Mandu's runtime-agnostic API surface (contracts, fillings, middleware) works across all four — the adapter is the only thing that changes.
Accessing edge runtime primitives
Each adapter exposes typed accessors for the runtime's native context:
// Cloudflare Workers
import { getWorkersEnv, getWorkersCtx } from "@mandujs/edge/workers";
export async function POST(req: Request) {
const env = getWorkersEnv();
const ctx = getWorkersCtx();
ctx?.waitUntil(env?.ANALYTICS_QUEUE.send({ at: Date.now() }));
return Response.json({ ok: true });
}
The accessor pattern is consistent across runtimes — Deno Deploy's
equivalent will be getDenoServeCtx() etc.
Compatibility testing
mandu build --target=workers runs a compatibility guard during
build:
- Scans every module in the client + server bundles.
- Flags imports of
fs,child_process,net,tls, and any Bun-specific API not yet polyfilled. - Emits CLI_E213 as a warning (non-fatal in 15.1; will be
upgradable to error via
--strict-edgein 15.2).
Run it in CI even if you don't yet deploy to edge — it catches inadvertent drift early.
🤖 Agent Prompt
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/edge/index to my project.
Summary of the page:
`@mandujs/edge` is a separate package from core — install with `bun add @mandujs/edge`. Polyfill mapping: Bun.CookieMap → LegacyCookieCodec (WebCrypto); Bun.CSRF → crypto.subtle HMAC-SHA256; Bun.serve → `export default { fetch }`. Unsupported APIs return `BunApiUnsupportedOnEdge` 500 JSON. Cloudflare Workers shipped; Deno/Vercel/Netlify coming.
Required invariants — must hold after your changes:
- `@mandujs/edge` is a separate package — not bundled with `@mandujs/core`
- Cloudflare Workers adapter is shipped (Phase 15.1); Deno/Vercel/Netlify Edge are stubs
- Calling an unsupported Bun API inside a Worker returns a structured 500 with `BunApiUnsupportedOnEdge` payload
- Wrangler is a peer dependency — install with `bun add -D wrangler`
- All edge adapters use Mandu's same `manifest.json` + registered handlers — zero app-code changes required
Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.
Related
- Deploy — Cloudflare Pages — the Pages-Functions variant (different deploy model, same V8 isolate).
- Architect — Prerender — static prerendering reduces the edge CPU you need.
- AGENTS — MCP tools — the
run.teststool understands edge-target builds.
For Agents
{
"schema": "mandu.edge/v0.24",
"package": "@mandujs/edge",
"shipped_adapters": ["workers"],
"roadmap_adapters": ["deno", "vercel-edge", "netlify-edge"],
"peer_dependency": "wrangler (for Workers)",
"polyfill_mapping": {
"Bun.serve": "export default { fetch }",
"Bun.CookieMap": "LegacyCookieCodec (WebCrypto)",
"Bun.CSRF": "crypto.subtle HMAC-SHA256",
"Bun.password": "@noble/hashes/argon2 (planned)",
"Bun.sql": "Neon / D1 (planned)",
"Bun.s3": "aws4fetch / R2 binding (planned)",
"Bun.file": "KV binding / inline (planned)",
"Bun.cron": "Workers Cron Triggers (planned)",
"SMTP": "skip permanently — use Resend"
},
"unsupported_api_response": {
"status": 500,
"error": "BunApiUnsupportedOnEdge"
},
"rules": [
"@mandujs/edge is opt-in — not in core",
"Use `mandu build --target=workers` — compatibility guard flags edge-unsafe imports",
"Use `mandu deploy --target=cf-pages` for Pages+Functions (different deploy model)"
]
}For Agents
`@mandujs/edge` is a separate package from core — install with `bun add @mandujs/edge`. Polyfill mapping: Bun.CookieMap → LegacyCookieCodec (WebCrypto); Bun.CSRF → crypto.subtle HMAC-SHA256; Bun.serve → `export default { fetch }`. Unsupported APIs return `BunApiUnsupportedOnEdge` 500 JSON. Cloudflare Workers shipped; Deno/Vercel/Netlify coming.
- `@mandujs/edge` is a separate package — not bundled with `@mandujs/core`
- Cloudflare Workers adapter is shipped (Phase 15.1); Deno/Vercel/Netlify Edge are stubs
- Calling an unsupported Bun API inside a Worker returns a structured 500 with `BunApiUnsupportedOnEdge` payload
- Wrangler is a peer dependency — install with `bun add -D wrangler`
- All edge adapters use Mandu's same `manifest.json` + registered handlers — zero app-code changes required