/** * Copyright (c) 2026 AzerothJS. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ import type { ApiRegistration } from './api/registry.ts'; /** * Captures the ambient async context; the returned function re-enters it around a call. * Re-entry restores the ENTIRE capture-time frame - an ambient context present at call * time but not at capture is replaced for the call's duration - which outside a request * root means restoring the empty frame. Lives here so this file stays the kernel's ONE * `node:async_hooks` seam. * * @internal */ export declare function captureRequestContext(): (fn: () => R) => R; /** * Registers teardown for the CURRENT work unit: closing a transaction, releasing a lock, * returning a connection. Runs when the unit settles - success, error, or disconnect - * in LIFO order (later acquisitions release first). Throws outside a unit: teardown * registered nowhere is a leak wearing a seatbelt, and loud beats leaking. */ export declare function onWorkUnitCleanup(fn: () => void | Promise): void; /** How a unit's throwing cleanup is reported; threaded from the App for requests. */ export interface WorkUnitOptions { onCleanupError?: ((error: unknown) => void) | undefined; /** * Bounds how long the unit may take to PRODUCE its result, answering with `answer()` when * it does not. Opt-in; absent means no bound, which is the shipped default. * * `answer` is called only on fire and its value is returned in the unit's place, so the * caller decides the wire shape (the App sends a 503). See {@link raceDeadline} for what * this deliberately does NOT do to the scope. */ responseDeadline?: { ms: number; answer: (arg: unknown) => unknown; } | undefined; /** * Hears a STREAMING body's producer failing after the response was already committed. * * Such a failure cannot become a status - the headers left long ago - so without this it * reaches nobody: the consumer gets a truncated body and the server records the clean 2xx * it already sent. On h2c the truncation is byte-identical to a normal end, so neither side * can see it. * * The second argument is the unit's own `arg`, typed `unknown` on purpose: {@link * runInRequestRoot} is generic and public, and is called in-repo with a non-Request arg, so * a reporter must narrow rather than assume. */ onStreamError?: ((error: unknown, arg: unknown) => void) | undefined; /** * @internal How the unit finds the api registered on the App that is dispatching it. A * lookup rather than a value: the App builds its root options once and `register` may run * either side of that. */ api?: (() => ApiRegistration | undefined) | undefined; } /** * @internal The api reachable in process from the CURRENT unit - the dispatching App's own * registration, or the enclosing root's when it registered none. Undefined outside a request * root and inside every work unit. */ export declare function currentApiRegistration(): ApiRegistration | undefined; /** * Runs `fn` inside a fresh request root. The App wraps every dispatch in this; adapters and * user code never call it directly. Cleanups ALWAYS run when the request settles: a throw or a * buffered response runs them immediately; a STREAMING response (SSE, static file, multipart, * any `new Response(stream)`) defers them to the body's end, so teardown that releases a pooled * connection/transaction/lock cannot fire while the stream is still pulling through it. * * A root opened INSIDE another root (an in-process api call made while a page renders) is a * real root with its own scope, cleanups and cache, but it does not get a fresh budget: its * deadline is the tighter of its own and what the enclosing root has left. It also inherits * that root's api when the App dispatching it registered none of its own. */ export declare function runInRequestRoot(fn: (arg: A) => T | Promise, arg: A, options?: WorkUnitOptions): Promise; /** * Runs `fn` as ONE WORK UNIT: the unit owns a fresh store scope - so its `cached()` reads * get a real per-unit cache on a server whose default scope fails closed - and a cleanup * registry ({@link onWorkUnitCleanup}), and both die when the unit settles. The HTTP * request root is this plus the HTTP settle policy (streaming deferral, abort-as-settle). * Wrap the units no interceptor reaches: a ws `onConnection`/`onClose` body, background * regeneration, an app-started timer or queue consumer. */ export declare function runInWorkUnit(fn: () => T | Promise, options?: WorkUnitOptions): Promise; /** * The constructor-supplied seam ws and cron accept: one unit in, the host's contained * reporter alongside. Both hosts declare it structurally so their zero-dependency * contract holds; a type spec pins mutual assignability. */ export type WorkUnitInterceptor = (unit: () => unknown, report: (error: unknown) => void) => unknown; /** * Builds the interceptor a socket server or scheduler wires at construction - one factory * call per host, each carrying its own options. Every intercepted unit (one ws * application message, one cron run) owns a work-unit scope: per-unit caching, cleanups, * release at settle - the fail-closed cost goes away with no cross-identity sharing. The * unit runs synchronously inside its scope; a thenable it returns is what the settle * waits on, a sync throw reports and settles, and neither ever escapes to the caller. * * `deadlineMs` (opt-in, NO default) bounds a unit that never settles: on fire the unit's * cache scope is RELEASED and a timeout error reports through the host's reporter. The * unit itself cannot be cancelled - it continues at the released scope, and its cleanups * still run at the eventual settle, reading a released cache rather than settled entries. * Set it for untrusted-input-driven units (ws); leave cron's legitimate long runs unbounded. * * WHAT A READ SEES ACROSS THE RELEASE, which decides whether a continuing unit may write. * A read STARTED AFTER the release fetches directly and gets correct data. A read already * IN FLIGHT resolves `undefined`: releasing aborts the in-flight fetch and resolves its * waiters, and that prompt, total resolution is what keeps teardown bounded - letting those * readers re-fetch instead would leave the deadline bounding nothing, which is the one job * it has. So a unit that continues past its deadline MUST NOT PERSIST on a value it read * across the release; it has already been reported as over deadline, and `undefined` there * is indistinguishable from absent data. {@link raceDeadline} declines to release for this * exact reason - it has a live client, so it can afford to leave the scope alone. */ export declare function createWorkUnitInterceptor(options?: { deadlineMs?: number; }): WorkUnitInterceptor;