Something went wrong. Try again.
Monorepo for the nibling.run platform
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233// node:async_hooks without engine hooks. A context (the "frame") is an immutable map// from each AsyncLocalStorage to its store; run() and AsyncResource scopes swap it for// the duration of a synchronous call. DESIGN.md's "Async context" stage 1 makes the// frame follow every callback the kernel itself schedules (timers, nextTick,// queueMicrotask, syscall completions, handle events) and every Promise reaction// (installThenProxy, below), by having each entry point capture() the frame when it// learns the one it must run in and runInFrame() it there later. It still does not// follow a native `await` (stage 2, not built) or anything the engine alone drives// without the kernel in the loop (a resolver pool settling a promise from a sibling's// frame, say) — createHook refuses for the same reason: no engine hook exists here.import { refused } from "./refuse";
export type Context = ReadonlyMap<AsyncLocalStorage<unknown>, unknown>;type Fn = (...args: any[]) => any;
let current: Context = new Map();
/** The then() Proxy goes on the first time a frame other than the root is made. Until * then every frame is the root, so every then() would capture the root anyway, and a * worker that never calls run() or enterWith() keeps the engine's own then. */let thenProxyInstalled = false;function leaveRoot(): void { if (thenProxyInstalled) return; thenProxyInstalled = true; installThenProxy();}
function runIn<R>(ctx: Context, fn: () => R): R { const prev = current; current = ctx; try { return fn(); } finally { current = prev; }}
/** Stage 1's two internal primitives (not exported from node:async_hooks). Every * stage 1 entry point (timers, immediates, nextTick, queueMicrotask, syscall * completions, handle events, Promise reactions) calls `capture()` when it learns * the frame it must later run in, and `runInFrame()` to enter it for exactly that * one call, restoring whatever was current before (or after) it, win or throw. */export function capture(): Context { return current;}
export function runInFrame<R>(frame: Context, fn: (...args: any[]) => R, thisArg?: unknown, args: unknown[] = []): R { const prev = current; current = frame; try { return fn.apply(thisArg, args); } finally { current = prev; }}
interface AsyncLocalStorageOptions { name?: string;}
export class AsyncLocalStorage<T> { #enabled = true; /** Node's debugging label (AsyncLocalStorage({ name }), default ""), not used for * lookup — stores are still keyed by instance identity. A prototype getter, as * Node's is (not an own instance property). */ #name: string;
constructor(options: AsyncLocalStorageOptions = {}) { this.#name = typeof options?.name === "string" ? options.name : ""; }
get name(): string { return this.#name; }
static bind<F extends Fn>(fn: F): F { return new AsyncResource("bound-anonymous-fn").bind(fn); }
static snapshot(): <R>(fn: (...args: any[]) => R, ...args: unknown[]) => R { const captured = current; return (fn, ...args) => runIn(captured, () => fn(...args)); }
getStore(): T | undefined { return this.#enabled ? (current.get(this as AsyncLocalStorage<unknown>) as T | undefined) : undefined; }
run<R>(store: T, callback: (...args: any[]) => R, ...args: unknown[]): R { this.#enabled = true; const next = new Map(current); next.set(this as AsyncLocalStorage<unknown>, store); leaveRoot(); return runIn(next, () => callback(...args)); }
exit<R>(callback: (...args: any[]) => R, ...args: unknown[]): R { const next = new Map(current); next.delete(this as AsyncLocalStorage<unknown>); return runIn(next, () => callback(...args)); }
/** Sets the store for the rest of the current synchronous execution. */ enterWith(store: T): void { this.#enabled = true; const next = new Map(current); next.set(this as AsyncLocalStorage<unknown>, store); leaveRoot(); current = next; }
disable(): void { this.#enabled = false; const next = new Map(current); next.delete(this as AsyncLocalStorage<unknown>); current = next; }}
let warnAsyncResourceProperty: () => void = () => {};
export class AsyncResource { readonly #context: Context;
constructor(type: string, _options?: unknown) { if (typeof type !== "string") { throw Object.assign(new TypeError('The "type" argument must be of type string.'), { code: "ERR_INVALID_ARG_TYPE" }); } this.#context = current; }
static bind<F extends Fn>(fn: F, type?: string, thisArg?: unknown): F { return new AsyncResource(type ?? (fn.name || "bound-anonymous-fn")).bind(fn, thisArg); }
runInAsyncScope<R>(fn: (...args: any[]) => R, thisArg?: unknown, ...args: unknown[]): R { return runIn(this.#context, () => fn.apply(thisArg, args)); }
bind<F extends Fn>(fn: F, thisArg?: unknown): F { if (typeof fn !== "function") { throw Object.assign(new TypeError('The "fn" argument must be of type function.'), { code: "ERR_INVALID_ARG_TYPE" }); } const resource = this; const bound = function (this: unknown, ...args: unknown[]) { return resource.runInAsyncScope(fn, thisArg === undefined ? this : thisArg, ...args); }; Object.defineProperties(bound, { length: { configurable: true, enumerable: false, value: fn.length, writable: false }, asyncResource: { configurable: true, enumerable: true, get: () => { warnAsyncResourceProperty(); return resource; }, }, }); return bound as unknown as F; }
emitDestroy(): this { return this; }
asyncId(): number { return 0; }
triggerAsyncId(): number { return 0; }}
const WHY = "async resource lifecycle hooks need engine support the browser does not expose";
export function makeAsyncHooks(emitWarning: (msg: string, type: string, code: string) => void) { let warned = false; warnAsyncResourceProperty = () => { if (warned) return; warned = true; emitWarning("The asyncResource property on bound functions is deprecated", "DeprecationWarning", "DEP0172"); }; return { AsyncLocalStorage, AsyncResource, createHook: refused("async_hooks", "createHook", WHY), executionAsyncResource: refused("async_hooks", "executionAsyncResource", WHY), /** No async ids are tracked; 0 is Node's "no execution context" value. */ executionAsyncId: () => 0, triggerAsyncId: () => 0, };}
/** Stage 1 item 3: `Promise.prototype.then` becomes a callable Proxy whose `apply` * trap wraps `onFulfilled`/`onRejected` (only the arguments that are functions — * `.then(1, 2)` must still reach the native `then` unwrapped and reject as it would) * to run in the frame current at the `then()` call, not whatever frame happens to be * current when the engine settles the promise and runs the reaction. `catch`, * `finally` and the combinators (`all`, `race`, ...) call `then` internally, so they * inherit this for free. Installed by leaveRoot() on the first non-root frame; from * then on it also covers the runtime's own internal promise plumbing (http's `listen()`, * zlib's engines, ...), which is how handle completions built from a `.then()` chain * pick back up the frame that was current when the kernel call that started the * chain was made, with no extra bookkeeping at those call sites. Skips the wrap * entirely when the captured frame is the (empty) root frame, since restoring it * would be a no-op — the fast path real programs mostly hit when nothing in this * worker ever calls AsyncLocalStorage#run(). */function installThenProxy(): void { const native = Promise.prototype.then; function wrap(frame: Context, fn: Fn): Fn { return function (this: unknown, ...args: unknown[]) { return runInFrame(frame, fn, undefined, args); }; } const proxied = new Proxy(native, { apply(target, thisArg, args: unknown[]) { const frame = capture(); const fn = target as unknown as Fn; // The fast path: `fn.call` with the two positional arguments beats // `fn.apply`/`Reflect.apply` with the arguments array by a measurable margin // (bench-proxy2.mjs in the report), and covers the overwhelming majority of // `then()` calls, which pass 0–2 arguments. if (frame.size === 0) return args.length <= 2 ? fn.call(thisArg, args[0], args[1]) : fn.apply(thisArg, args); const wrapped = args.map((a) => (typeof a === "function" ? wrap(frame, a as Fn) : a)); return wrapped.length <= 2 ? fn.call(thisArg, wrapped[0], wrapped[1]) : fn.apply(thisArg, wrapped); }, }); Object.defineProperty(Promise.prototype, "then", { value: proxied, writable: true, enumerable: false, configurable: true, });}