/** * Defer work past the HTTP response. * * Use for bookkeeping that doesn't need to complete before the client * gets bytes — writes that record state, maintenance queries, cache * refreshes. `after()` hands the promise to the host's lifetime * extender when one is available (Cloudflare's `waitUntil` under * workerd), or fires-and-forgets on Node (the process lives for the * next request anyway). * * Host binding is resolved lazily via a dynamic import of the * `virtual:emdash/wait-until` virtual module. Lazy — rather than a * static top-level import — so tools that walk the dist in a plain * Node loader (`astro check`, Vitest, etc.) don't trip over the * `virtual:` scheme: they'd only fail if they actually called * `after()`, which they don't during type-checking. */ export type WaitUntilFn = (promise: Promise) => void; // Resolves to the host's waitUntil if the adapter provided one, or // null otherwise. Kicked off once at module load; subsequent `after()` // calls see the cached result without re-importing. const waitUntilReady: Promise = (async () => { try { // @ts-ignore - virtual module, generated by the Astro integration const mod = (await import("virtual:emdash/wait-until")) as { waitUntil?: WaitUntilFn; }; return mod.waitUntil ?? null; } catch { // No virtual module available (Node-side tooling, tests without the // integration in scope). Fire-and-forget is the safe fallback. return null; } })(); // Surface rejections without making the module-load fail. waitUntilReady.catch(() => {}); /** * Schedule `fn` to run without blocking the response. * * Errors are caught and logged — a deferred task should never surface * as an unhandled rejection because the response is long gone. Callers * that care about errors should handle them inside `fn`. */ export function after(fn: () => void | Promise): void { const promise = Promise.resolve() .then(fn) .catch((error) => { console.error("[emdash] deferred task failed:", error); }); // Defer the lifetime-extender handoff to the microtask that resolves // waitUntilReady. On workerd this is effectively instant (the virtual // module is already loaded in the bundle); on Node the promise // resolves to null, so this is just one extra microtask and no-op. void waitUntilReady.then((waitUntil) => { if (waitUntil) waitUntil(promise); return null; }); }