rate-limiter-worker/ package is a standalone Cloudflare Worker that provides IP-based rate limiting via a SQLite-backed Durable Object. It must be deployed before the main Astro app.
Architecture
The main Astro Pages app cannot export Durable Object classes — Cloudflare Pages Functions have that restriction. The rate limiter is therefore hosted in a separate Worker and bound to the main app via a Durable Object RPC binding.SlidingWindowRateLimiter
TheSlidingWindowRateLimiter class extends DurableObject and uses SQLite-backed storage (available on all plans). Each unique IP gets its own Durable Object instance — Cloudflare routes requests to the geographically nearest instance automatically.
Rate limit parameters
| Parameter | Value | Description |
|---|---|---|
windowMs | 60,000 ms | 60-second sliding window |
maxRequests | 100 | Maximum requests per window per IP |
| Algorithm | Sliding window | Counts actual timestamps, not fixed buckets |
How it works
The Durable Object maintains arequests table in SQLite:
checkLimit call:
- Expired entries (outside the sliding window) are pruned with a single
DELETE - Current window count is fetched with
COUNT(*) - If count ≥
maxRequests: returns{ allowed: false, remaining: 0, retryAfterMs } - Otherwise: inserts the current timestamp and returns
{ allowed: true, remaining: N }
Response interface
wrangler.jsonc configuration
migrations block with new_sqlite_classes registers SlidingWindowRateLimiter as an SQLite-backed Durable Object. This migration runs once on first deploy.
Main app bindings
The main app (astro-app/wrangler.jsonc) references the rate limiter via a cross-script Durable Object binding:
script_name field tells the Pages Worker to find the Durable Object class in the deployed rate-limiter-worker script.
D1 database (PORTAL_DB)
The main app is bound to a D1 database for the sponsor portal:| Binding | Purpose |
|---|---|
PORTAL_DB | Sponsor portal data: RSVPs, agreements, notification prefs, evaluations |
KV store (SESSION_CACHE)
A KV namespace is bound for session caching:SESSION_CACHE is unavailable — sessions fall back to re-validating the JWT on every request.
Deploy the rate limiter
Deploy the worker
From the monorepo root:Or from the The first deploy runs the
rate-limiter-worker/ directory directly:v1 migration and creates the SlidingWindowRateLimiter SQLite-backed Durable Object class.Local development
For local dev, the main app’swrangler.jsonc references the rate limiter DO binding. Run Wrangler dev in the rate-limiter-worker/ directory first, then run wrangler pages dev in astro-app/:
import.meta.env.DEV check in middleware.ts, so the rate limiter is not exercised in standard astro dev.
Testing
The rate limiter worker has its own Vitest test suite using@cloudflare/vitest-pool-workers:
Observability
Therate-limiter-worker has observability: { enabled: true } in wrangler.jsonc. Metrics (requests, CPU time, errors) are visible in the Cloudflare Dashboard → Workers & Pages → rate-limiter-worker → Metrics tab.