The handler
The fetch handler contract, and what JavaScript actually runs in production.
src/index.js must default-export an object literal with a fetch method. Durable Object classes and helper functions may be declared above it. It may import from other files in the project and from your own node_modules: the CLI bundles with Bun before Porffor sees anything.
export default {
fetch(request) {
const url = new URL(request.url);
if (url.pathname === "/health") return new Response("ok");
return Response.json({ method: request.method, path: url.pathname });
},
};
Extra handlers
The same default export can also carry scheduled and queue:
export default {
fetch(request) { /* ... */ },
scheduled(event) { /* event.cron, event.scheduledTime */ },
queue(batch) { /* batch.queue, batch.messages, message.ack(), message.retry() */ },
};
What works
Standard synchronous JavaScript and Web platform pieces: URL, Request, Response, Response.json, Headers, TextEncoder / TextDecoder, JSON, Math, String / Array methods, regular expressions, Date.now() and new Date().toISOString(), atob / btoa, structuredClone.
Crypto: crypto.randomUUID, crypto.getRandomValues, and a subset of crypto.subtle: digest (SHA-256/384/512) and HMAC (importKey, sign, verify). Enough for JWTs (HS256) and hand-rolled sessions; no ECDSA or AES yet. crypto.scryptVerify(password, salt, expected, { N, r, p }) is a Sproutboat extension for checking scrypt hashes made elsewhere (Node/Bun scrypt) during a migration.
ctx.waitUntil(promise), the second argument to fetch/scheduled/queue: work that outlives the response, drained in-process and capped at 25s.
What does not
| Not available | Notes |
|---|---|
Dynamic import(), require() |
Static imports only: everything has to resolve at build time. |
process, Bun, Deno, Buffer, node:* |
Rejected at check time, in your code and in dependencies alike. |
new Proxy |
Porffor compiles it and then ignores every trap, so a trapped property reads back undefined with no error. Rejected at build time, so it fails loudly instead of silently. This is why itty-router and other Proxy-based routers do not work. |
| Streams | ReadableStream / WritableStream are missing upstream, so a response body is one whole string. No SSE or chunked responses; poll instead. |
Most of crypto.subtle |
digest and HMAC work (see What works); ECDSA, AES-GCM and key wrapping do not, so most JWT libraries beyond HS256 and better-auth still fall short. |
WebSocket, XMLHttpRequest |
Not in the http-sync-v0 capability profile. |
fetch() to arbitrary hosts |
Allowed only for hosts listed in outbound — see Outbound fetch. |
| Filesystem | No fs. Static files go through the assets binding. |
| Parsing date strings | new Date("2024-01-02") is unreliable on the current Porffor pin. Use timestamps. |
npm packages work, within those limits. Small pure-JavaScript libraries are fine. Anything that reaches for a platform API, ships native code, or uses a Proxy is not. Note that zod does not currently run, which rules out most libraries that depend on it.