Skip to content

worker

import { composeWorker, withEdgeCache, withHealing } from "louise-toolkit/worker";

The Worker entrypoint helpers. Every Louise site’s worker.ts has the same shape: try a few Louise-owned routes, fall through to the framework’s SSR handler, optionally wire queue / scheduled. composeWorker builds that ExportedHandler; withEdgeCache and withHealing wrap the fallback and the routes. No peers.

function composeWorker<Env>(options: ComposeWorkerOptions<Env>): ExportedHandler<Env>;
interface ComposeWorkerOptions<Env> {
routes?: WorkerRoute<Env>[]; // first to return a Response wins
fetch: ExportedHandler<Env>["fetch"]; // SSR fallback when no route matches
queue?: ExportedHandler<Env>["queue"];
scheduled?: ExportedHandler<Env>["scheduled"];
}

Composes an ExportedHandler from ordered WorkerRoutes over an SSR fallback. On fetch, each route runs in order and the first Response short-circuits; if none match, the fetch fallback handles it. A WorkerRoute returns a Response to handle the request, or undefined to pass it to the next route.

export default composeWorker<Env>({
routes: [louiseApiRoute, ogImageRoute],
fetch: ssrHandler, // for example, @astrojs/cloudflare's handle
queue: (batch, env) => processBatch(batch, (m) => handle(m, env)),
});
function withEdgeCache<Env>(
handler: (request, env, ctx) => Response | Promise<Response>,
config?: EdgeCacheConfig,
): (request, env, ctx) => Promise<Response>;
interface EdgeCacheConfig {
bypass?: (request: Request) => boolean; // skip cache, always run fresh
cache?: () => Cache; // defaults to caches.default; injectable for tests
signalHeader?: string; // defaults to cloudflare-cdn-cache-control
}

A cookie-aware edge cache for the SSR fallback. It caches public GETs in the Worker-controlled Cache API (caches.default), keyed by URL, and stores a response only when it carries a cacheable directive in the signal header. Bypassed requests (for example, an authenticated editor) and non-GETs always run the handler. Drop-in for composeWorker’s fetch.

Which header carries that decision is your host’s convention, not this layer’s, so signalHeader is configurable. It defaults to CDN_CACHE_CONTROL (cloudflare-cdn-cache-control), which is what a Cloudflare-targeting SSR adapter emits for a cacheable response—so most sites never set it. A host that signals some other way sets its own name and is not obliged to adopt Cloudflare’s.

The signal header is always stripped from the response, whichever one you use, for the reason in the caution below.

export default composeWorker<Env>({
fetch: withEdgeCache(ssrHandler, { bypass: (req) => hasEditorSession(req) }),
});
  • CDN_CACHE_CONTROL—the response header consumed as the “cache me” signal.
  • isCacheableDirective(directive)—is a Cache-Control value an opt-in (public/unspecified with a positive max-age, not no-store/no-cache/private)?
function withHealing<Env>(route: WorkerRoute<Env>, options: HealingOptions<Env>): WorkerRoute<Env>;
interface HealingOptions<Env> {
rules: Record<string, HealingRule<Env>>; // keyed by LouiseError.code
fallbackRule?: HealingRule<Env>; // for codes with no explicit rule
sleep?: (ms: number) => Promise<void>; // injectable for tests
}

Wraps a route so thrown LouiseErrors are healed by policy instead of surfacing as a 500. A rule (selected by error.code) composes three deterministic strategies: retry (re-run, optional exponential backoffMs), fallback (serve a degraded/stale Response), and escalate (hand the failure off out-of-band via ctx.waitUntil, so recovery never blocks the response). Non-LouiseErrors, and codes with no matching rule, re-throw.

const healed = withHealing(apiRoute, {
rules: {
DB_ERROR: {
retries: 2,
backoffMs: 50,
fallback: ({ request }) => serveStale(request),
escalate: ({ env, ...c }) => enqueue(env.HEAL_QUEUE, describeFailure(c)),
},
},
});

WorkerRoute, ComposeWorkerOptions, EdgeCacheConfig, HealingRule, HealingContext, HealingOptions, FailureReport.