{"version":3,"file":"after-CBU2vkoO.mjs","names":[],"sources":["../src/deferred-tasks.ts","../src/after.ts"],"sourcesContent":["export interface DeferredTaskTracker {\n\tsettled: Promise<void>;\n\ttrack<T>(promise: Promise<T>): Promise<T>;\n\tsettle(): void;\n}\n\n// Test database teardown uses this process-local registry to drain tasks that\n// run without request middleware. Keep it on globalThis so duplicated SSR\n// chunks and their test helpers observe the same task set.\nconst DEFERRED_TASKS_KEY = Symbol.for(\"emdash:deferred-tasks\");\n\nconst deferredTasks: Set<Promise<unknown>> =\n\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- globalThis singleton pattern\n\t((globalThis as Record<symbol, unknown>)[DEFERRED_TASKS_KEY] as\n\t\t| Set<Promise<unknown>>\n\t\t| undefined) ??\n\t(() => {\n\t\tconst tasks = new Set<Promise<unknown>>();\n\t\t(globalThis as Record<symbol, unknown>)[DEFERRED_TASKS_KEY] = tasks;\n\t\treturn tasks;\n\t})();\n\nexport function trackDeferredTask<T>(promise: Promise<T>): Promise<T> {\n\tlet tracked!: Promise<T>;\n\ttracked = promise.finally(() => deferredTasks.delete(tracked));\n\tdeferredTasks.add(tracked);\n\treturn tracked;\n}\n\nexport async function waitForDeferredTasks(): Promise<void> {\n\twhile (deferredTasks.size > 0) {\n\t\tawait Promise.allSettled(deferredTasks);\n\t}\n}\n\nexport function createDeferredTaskTracker(onSettled: () => void): DeferredTaskTracker {\n\tlet pending = 0;\n\tlet responseSettled = false;\n\tlet completed = false;\n\tlet resolveSettled!: () => void;\n\tconst settled = new Promise<void>((resolve) => {\n\t\tresolveSettled = resolve;\n\t});\n\n\tconst completeIfSettled = () => {\n\t\tif (completed || !responseSettled || pending > 0) return;\n\t\tcompleted = true;\n\t\ttry {\n\t\t\tonSettled();\n\t\t} finally {\n\t\t\tresolveSettled();\n\t\t}\n\t};\n\n\treturn {\n\t\tsettled,\n\t\ttrack<T>(promise: Promise<T>): Promise<T> {\n\t\t\tpending++;\n\t\t\treturn promise.finally(() => {\n\t\t\t\t// A task may register another after() call before it settles. The\n\t\t\t\t// counter therefore reaches zero only after the full task chain ends.\n\t\t\t\tpending--;\n\t\t\t\tcompleteIfSettled();\n\t\t\t});\n\t\t},\n\t\tsettle(): void {\n\t\t\tresponseSettled = true;\n\t\t\tcompleteIfSettled();\n\t\t},\n\t};\n}\n","/**\n * Defer work past the HTTP response.\n *\n * Use for bookkeeping that doesn't need to complete before the client\n * gets bytes — writes that record state, maintenance queries, cache\n * refreshes. `after()` hands the promise to the host's lifetime\n * extender when one is available (Cloudflare's `waitUntil` under\n * workerd), or fires-and-forgets on Node (the process lives for the\n * next request anyway).\n *\n * Host binding is resolved lazily via a dynamic import of the\n * `virtual:emdash/wait-until` virtual module. Lazy — rather than a\n * static top-level import — so tools that walk the dist in a plain\n * Node loader (`astro check`, Vitest, etc.) don't trip over the\n * `virtual:` scheme: they'd only fail if they actually called\n * `after()`, which they don't during type-checking.\n */\n\nimport { trackDeferredTask } from \"./deferred-tasks.js\";\nimport { getRequestContext } from \"./request-context.js\";\n\nexport type WaitUntilFn = (promise: Promise<unknown>) => void;\n\n// Resolves to the host's waitUntil if the adapter provided one, or\n// null otherwise. Kicked off once at module load; subsequent `after()`\n// calls see the cached result without re-importing.\nconst waitUntilReady: Promise<WaitUntilFn | null> = (async () => {\n\ttry {\n\t\t// @ts-ignore - virtual module, generated by the Astro integration\n\t\tconst mod = (await import(\"virtual:emdash/wait-until\")) as {\n\t\t\twaitUntil?: WaitUntilFn;\n\t\t};\n\t\treturn mod.waitUntil ?? null;\n\t} catch {\n\t\t// No virtual module available (Node-side tooling, tests without the\n\t\t// integration in scope). Fire-and-forget is the safe fallback.\n\t\treturn null;\n\t}\n})();\n// Surface rejections without making the module-load fail.\nwaitUntilReady.catch(() => {});\n\n/**\n * Schedule `fn` to run without blocking the response.\n *\n * Errors are caught and logged — a deferred task should never surface\n * as an unhandled rejection because the response is long gone. Callers\n * that care about errors should handle them inside `fn`.\n */\nexport function after(fn: () => void | Promise<void>): void {\n\tconst task = Promise.resolve()\n\t\t.then(fn)\n\t\t.catch((error) => {\n\t\t\tconsole.error(\"[emdash] deferred task failed:\", error);\n\t\t});\n\tconst requestTask = getRequestContext()?.deferredTasks?.track(task) ?? task;\n\tconst promise = trackDeferredTask(requestTask);\n\n\t// Defer the lifetime-extender handoff to the microtask that resolves\n\t// waitUntilReady. On workerd this is effectively instant (the virtual\n\t// module is already loaded in the bundle); on Node the promise\n\t// resolves to null, so this is just one extra microtask and no-op.\n\tvoid waitUntilReady.then((waitUntil) => {\n\t\tif (waitUntil) waitUntil(promise);\n\t\treturn null;\n\t});\n}\n"],"mappings":";;;AASA,MAAM,qBAAqB,OAAO,IAAI,wBAAwB;AAE9D,MAAM,gBAEH,WAAuC,8BAGlC;CACN,MAAM,wBAAQ,IAAI,KAAuB;AACzC,CAAC,WAAuC,sBAAsB;AAC9D,QAAO;IACJ;AAEL,SAAgB,kBAAqB,SAAiC;CACrE,IAAI;AACJ,WAAU,QAAQ,cAAc,cAAc,OAAO,QAAQ,CAAC;AAC9D,eAAc,IAAI,QAAQ;AAC1B,QAAO;;AASR,SAAgB,0BAA0B,WAA4C;CACrF,IAAI,UAAU;CACd,IAAI,kBAAkB;CACtB,IAAI,YAAY;CAChB,IAAI;CACJ,MAAM,UAAU,IAAI,SAAe,YAAY;AAC9C,mBAAiB;GAChB;CAEF,MAAM,0BAA0B;AAC/B,MAAI,aAAa,CAAC,mBAAmB,UAAU,EAAG;AAClD,cAAY;AACZ,MAAI;AACH,cAAW;YACF;AACT,mBAAgB;;;AAIlB,QAAO;EACN;EACA,MAAS,SAAiC;AACzC;AACA,UAAO,QAAQ,cAAc;AAG5B;AACA,uBAAmB;KAClB;;EAEH,SAAe;AACd,qBAAkB;AAClB,sBAAmB;;EAEpB;;;;;;;;;;;;;;;;;;;;;;AC3CF,MAAM,kBAA+C,YAAY;AAChE,KAAI;AAKH,UAHa,MAAM,OAAO,8BAGf,aAAa;SACjB;AAGP,SAAO;;IAEL;AAEJ,eAAe,YAAY,GAAG;;;;;;;;AAS9B,SAAgB,MAAM,IAAsC;CAC3D,MAAM,OAAO,QAAQ,SAAS,CAC5B,KAAK,GAAG,CACR,OAAO,UAAU;AACjB,UAAQ,MAAM,kCAAkC,MAAM;GACrD;CAEH,MAAM,UAAU,kBADI,mBAAmB,EAAE,eAAe,MAAM,KAAK,IAAI,KACzB;AAM9C,CAAK,eAAe,MAAM,cAAc;AACvC,MAAI,UAAW,WAAU,QAAQ;AACjC,SAAO;GACN"}