Fixes: 1. media.ts: wrap placeholder generation in try-catch 2. toolbar.ts: check r.ok, display error message in popover
63 lines
2.4 KiB
TypeScript
63 lines
2.4 KiB
TypeScript
/**
|
|
* 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<unknown>) => 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<WaitUntilFn | null> = (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>): 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;
|
|
});
|
|
}
|