UNPKG

alepha

Version:

Easy-to-use modern TypeScript framework for building many kind of applications.

71 lines (62 loc) 2.44 kB
import { $hook, $inject, Alepha } from "alepha"; import { $logger } from "alepha/logger"; /** * Runs fire-and-forget work that should outlive the request that scheduled it * (sending a welcome email, an audit write, a webhook) without blocking the * response. * * On a long-lived runtime (Node, Vercel) the event loop keeps the promise * alive, so the only job here is to detach the task from the caller and never * leak an unhandled rejection. On serverless/edge runtimes that freeze the * isolate once the response is returned (Cloudflare Workers) the * {@link WorkerdBackgroundTaskProvider} variant additionally wraps the task in * `executionCtx.waitUntil` — call sites stay platform-agnostic and only ever * call {@link defer}. * * In-flight tasks are tracked and awaited on shutdown (`flush`), so graceful * stop, `run({ once })`, and unit tests don't silently drop work. */ export class BackgroundTaskProvider { protected readonly alepha = $inject(Alepha); protected readonly log = $logger(); /** In-flight deferred tasks, awaited on stop. */ protected readonly pending = new Set<Promise<void>>(); /** * Schedule `task` to run in the background. Returns immediately — the task is * detached onto a microtask, so the caller (e.g. an HTTP handler) is never * blocked and a synchronous throw inside `task` can't escape into it. Errors * are caught and logged. */ public defer(task: () => unknown | Promise<unknown>): void { const promise = Promise.resolve() .then(task) .then( () => undefined, (error: unknown) => { this.log.warn("Background task failed", error as Error); }, ); this.pending.add(promise); void promise.finally(() => { this.pending.delete(promise); }); this.keepAlive(promise); } /** * Hook for platform variants to keep the runtime alive until `promise` * settles. No-op on long-lived runtimes (the event loop already does). The * Cloudflare variant overrides this with `executionCtx.waitUntil`. */ protected keepAlive(_promise: Promise<void>): void {} /** * Await all in-flight tasks (best-effort). Called on `stop` so shutdown and * tests don't drop deferred work. */ public async flush(): Promise<void> { await Promise.allSettled([...this.pending]); } protected readonly onStop = $hook({ on: "stop", handler: () => this.flush(), }); }