Edge example
Cloudflare Workers’ fetch handlers are stateless — request → response, no long-lived state, no UI. There’s no reactive work for Para to do there, and forcing it would just be ceremony. Where Para earns its keep on the edge is the Durable Object: a single instance per IP that lives across many requests, holding state Para’s signals are built for.
This page shows that Durable Object — the actual rate limiter logic. The fetch handler that calls into it is plain Workers code; a one-liner version is at the bottom for completeness, but it isn’t where the Para syntax lives.
src/limiter.pjs
Section titled “src/limiter.pjs”export class RateLimiter { signal count = 0; signal windowStart = Date.now(); derived overLimit = count > 100;
constructor(state) { this.state = state; when overLimit { console.log(`rate limit crossed: ${count} requests in window`); } }
async fetch(req) { if (Date.now() - windowStart > 60_000) { count = 0; windowStart = Date.now(); } count++;
return Response.json({ allowed: !overLimit, remaining: Math.max(0, 100 - count), }); }}What’s reactive and what isn’t
Section titled “What’s reactive and what isn’t”signal count+signal windowStart— mutable cells holding per-IP state across many requests. Each request incrementscount; once a minute the window resets.derived overLimit = count > 100— recomputes whenevercountchanges. Read-only; written using thederivedkeyword to make the intent explicit (writingsignalhere would auto-promote to the same shape, butderivedsays “you can’t assign to this” at the declaration site).when overLimit { ... }— fires once per false→true transition. Logs the first request that crosses the threshold; doesn’t re-fire on every subsequent over-limit request untilcountdrops back below 100 and rises again. This is the edge-triggered shape that’s clumsy without language support — the alternative is hand-tracking the previous value or an unconditionaleffectthat does its own diffing.
The fetch method is plain JS — no Para sugar. That’s fine: the per-call work isn’t reactive, the cross-call state is. Putting the right tool at the right layer is the point.
The Worker entry that calls it
Section titled “The Worker entry that calls it”The fetch handler that proxies requests through the Durable Object is plain Workers code; nothing reactive happens here, so it’s plain JavaScript:
export default { async fetch(req, env) { const ip = req.headers.get("cf-connecting-ip") ?? "unknown"; const stub = env.RATE_LIMIT.get(env.RATE_LIMIT.idFromName(ip)); const { allowed, remaining } = await (await stub.fetch("https://internal/check")).json();
if (!allowed) { return new Response("rate limited", { status: 429, headers: { "Cache-Control": "public, max-age=10", "Retry-After": "60" }, }); } return Response.json({ ok: true, remaining }, { headers: { "X-RateLimit-Remaining": String(remaining) }, }); },};wrangler.jsonc
Section titled “wrangler.jsonc”{ "name": "my-worker", "main": "dist/index.js", "compatibility_date": "2026-04-30", "durable_objects": { "bindings": [{ "name": "RATE_LIMIT", "class_name": "RateLimiter" }] }, "migrations": [{ "tag": "v1", "new_classes": ["RateLimiter"] }]}package.json
Section titled “package.json”{ "name": "my-worker", "type": "module", "scripts": { "build": "parabun build src/index.pjs --target browser --outfile dist/index.js", "deploy": "parabun run build && wrangler deploy" }, "dependencies": { "@para/signals": "*" }, "devDependencies": { "wrangler": "^4" }}--target browser is correct for Workers — the V8 isolate runtime has web-platform globals but no Node APIs.
Build and deploy
Section titled “Build and deploy”parabun installparabun run deployThe output is standard JavaScript using import "@para/signals". Wrangler bundles it like any other npm-using Worker.
TypeScript form
Section titled “TypeScript form”If you prefer types, swap src/limiter.pjs for src/limiter.pts, declare the Env interface (RATE_LIMIT: DurableObjectNamespace), and type state: DurableObjectState on the class. Same compiled output either way.