esrun's global scope tracks the WinterTC Minimum Common Web Platform API. These are standard Web globals — the same names you would use in a browser or other server runtimes. Host capabilities (filesystem, process, network access) are not globals; they live in runtime: modules.
Core
Global
Notes
globalThis
self
console
full Console Standard set — see below
queueMicrotask
structuredClone
reportError
navigator
userAgent — "ES-Runtime/<version>"
console
Group
Methods
Output
log, info, warn, error, debug, dir, dirxml, trace
Grouping
group, groupCollapsed, groupEnd
Counting
count, countReset
Timing
time, timeLog, timeEnd
Other
assert, table, clear
Format specifiers: %s, %d/%i, %f, %o/%O, %j, %%, %c.
A failure with nothing left to catch it is offered to the global scope first. preventDefault() claims it; anything unclaimed is printed and exits non-zero.
Event
Fired when
Cancelable
error
an exception escapes a timer callback, or reportError()
yes
unhandledrejection
a rejection is unhandled at the end of a tick
yes
rejectionhandled
a handler attaches to an already-reported rejection
no
JavaScript
globalThis.addEventListener("unhandledrejection", (event) => {
logger.warn("unhandled", event.reason);
event.preventDefault(); // mine now — do not fail the process
});
onerror / onunhandledrejection / onrejectionhandled are single-handler slots over the same events. rejectionhandled does not retract a report that has already gone out.
Messaging
One agent — no workers, no second realm — so the other end of a channel is always in this isolate and delivery is a queued task, not a cross-thread hop. Messages are still structured-cloned at postMessage and delivered in order.
Global
Notes
MessageChannel / MessagePort
a port buffers until start(); assigning onmessage starts it
BroadcastChannel
reaches every open channel of the same name except itself
Transferring a MessagePort is a DataCloneError: with one agent there is nowhere to transfer it to.
Data
Global
Notes
Blob
File
FormData
DOMException
Performance
Global
Notes
performance
now(), timeOrigin, User Timing (mark/measure/getEntries*/clear*)
Passing a ReadableStream as the fetch body uploads it as a chunked transfer with backpressure — the body is never fully buffered, so large or open-ended uploads stay memory-bounded. Response bodies stream too.
JavaScript
// A ReadableStream body streams to the server (chunked upload, backpressured).
const body = new ReadableStream({
async pull(c) {
const next = await source.read(); // your data source
if (next.done) c.close();
else c.enqueue(next.value); // Uint8Array chunks
},
});
await fetch("https://example.com/upload", { method: "POST", body });
// Anything else (string, Blob, FormData, Uint8Array) is sent buffered.
Timeouts
Connect (DNS + TCP + TLS)
30s → ERR_TIMED_OUT
TCP keepalive
60s on pooled connections
Whole request
uncapped — a streaming body may be long-lived by design
gzip, br, deflate — off the response's Content-Encoding
Stripped
Content-Encoding, Content-Length
Passed through
any other coding (zstd, …), headers intact
Requests carry User-Agent: ES-Runtime/<version> unless you set your own. Both are properties of the default transport.
Redirects
redirect
Behaviour
"follow" (default)
follows, cap 20; past it ERR_TOO_MANY_REDIRECTS
"manual"
resolves with the 3xx, Location intact
"error"
rejects with TypeError
JavaScript
const r = await fetch(url, { redirect: "manual" });
r.status; // 302
r.headers.get("location"); // where it would have gone
r.redirected; // false — nothing was followed
An unknown mode throws TypeError from new Request. Under "manual" the real response is returned, not the spec's opaque-redirect filtered one — same as Node, Deno and Bun.
Not available
Global
Why not
process / Buffer / require
Node.js globals — not provided (use runtime: modules)