Files
windmill/backend/windmill-runtime-nativets/src/runtime.js
T
Ruben Fiszel 6d1e12d5e9 feat(nativets): expose the standard web-platform globals deno_web provides (#10112)
* feat(nativets): expose standard web-platform globals for bun parity

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(nativets): wire bun-present Event subclasses and add construction smoke test

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(nativets): seed performance.timeOrigin per isolate, drop broken reportError

Addresses CI Codex review on #10112:
- performance.timeOrigin was undefined (setTimeOrigin never called); seed it
  per isolate via __wmInitPerIsolate executed from create_nativets_runtime.
- reportError needs a global EventTarget this runtime never installs; drop it.
- reword the namespace-import comment to not describe drafting history.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(nativets): wire DOMException global + broad edge-case smoke sweep

DOMException is present in bun and, more importantly, deno_web references it as
a global: AbortController.abort() with no reason constructs a
DOMException("...", "AbortError"), so the already-wired AbortController/
AbortSignal threw "DOMException is not defined" on abort. Surfaced by a new
functional edge-case sweep (smoke_web_globals_edge_cases) that exercises every
wired global for real (not just presence) — DOMException/abort, AbortSignal.timeout,
EventTarget dispatch, stream tee/reader/writer, all 3 compression formats,
structuredClone Map/Set/Date/circular/reject-function, performance mark/measure,
MessagePort delivery — plus a check that the merged Web Crypto globals still work.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(nativets): restore arg-default smoke tests dropped in merge, drop history comments

Addresses CI Codex/Pi review on the merge commit:
- Merge conflict resolution (checkout --ours) dropped smoke_missing_optional_arg_uses_default
  and smoke_explicit_null_arg_is_preserved (added on main by #10111); restore them.
- Reword edge-case-sweep comments to state the constraint, not how the gaps were found.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(nativets): give reportException a global dispatch target; wire stream reader/controller globals

Addresses CI Codex review on #10112:
- P1: a throwing EventTarget listener (and reportError) is routed through
  deno_web's reportException, which dispatches on a saved global reference.
  With none set, dispatchEvent threw a masking error that hid the original.
  Wire a dedicated EventTarget as that target so the ORIGINAL error is reported
  (async unhandled, matching bun). Does NOT make globalThis an EventTarget (bun's
  isn't either). Re-adds reportError, now functional. Regression test asserts the
  original error is surfaced, not a masking one.
- P2: wire the stream reader/controller globals bun also exposes
  (ReadableStreamDefaultReader/BYOBReader, ReadableStreamDefault/ByteStreamController,
  ReadableStreamBYOBRequest, WritableStreamDefaultWriter/Controller,
  TransformStreamDefaultController) for instanceof parity; sweep verifies via real
  reader/writer/controller instances.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(nativets): make globalThis an EventTarget so globalThis.reportError() works

Addresses follow-up CI Codex review on #10112:
- P1: the prior fix saved a *separate* EventTarget as the global reference, so
  globalThis.reportError() still failed its receiver check (this === globalThis_)
  with 'Illegal invocation'. Make globalThis itself the saved reference by turning
  it into a functional EventTarget (setPrototypeOf to DedicatedWorkerGlobalScope +
  setEventTargetData + webidl brand + saveGlobalThisReference), per isolate in
  __wmInitPerIsolate. Both reportError(e) and globalThis.reportError(e) now surface
  the original error (async, matching bun) instead of throwing. New test
  smoke_report_error_both_call_forms covers both call forms.
- P2: reword the regression-test comment to state the invariant, not the patch history.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(nativets): wire performance constructor globals for bun parity

Addresses the P2 nit in the CI Codex review: bun exposes Performance,
PerformanceEntry, PerformanceMark, and PerformanceMeasure as globals (deno_web
exports all four), so wire them alongside the performance singleton. The
edge-case sweep verifies instanceof against real mark/measure entries.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(nativets): state global-wiring comment as a constraint, not patch history

Addresses the P2 in the CI Codex review: reword the block comment to describe
the current bun-parity constraint and the deliberate EventSource/ImageData
exclusions, without narrating what was or wasn't wired before (per AGENTS.md).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 09:47:54 +02:00

159 lines
7.7 KiB
JavaScript

import * as abortSignal from "ext:deno_web/03_abort_signal.js";
import * as domException from "ext:deno_web/01_dom_exception.js";
import * as base64 from "ext:deno_web/05_base64.js";
import * as console from "ext:deno_console/01_console.js";
import * as encoding from "ext:deno_web/08_text_encoding.js";
import * as event from "ext:deno_web/02_event.js";
import * as fetch from "ext:deno_fetch/26_fetch.js";
import * as file from "ext:deno_web/09_file.js";
import * as fileReader from "ext:deno_web/10_filereader.js";
import * as formData from "ext:deno_fetch/21_formdata.js";
import * as headers from "ext:deno_fetch/20_headers.js";
import * as streams from "ext:deno_web/06_streams.js";
import * as timers from "ext:deno_web/02_timers.js";
import * as url from "ext:deno_url/00_url.js";
import * as net from "ext:deno_net/01_net.js";
import * as tls from "ext:deno_net/02_tls.js";
import * as urlPattern from "ext:deno_url/01_urlpattern.js";
import * as webidl from "ext:deno_webidl/00_webidl.js";
import * as crypto from "ext:deno_crypto/00_crypto.js";
import * as response from "ext:deno_fetch/23_response.js";
import * as request from "ext:deno_fetch/23_request.js";
import "ext:deno_web/02_structured_clone.js";
import * as globalInterfaces from "ext:deno_web/04_global_interfaces.js";
// Namespace imports (not side-effect-only) so their constructors are reachable
// for the globalThis wiring below. The module bodies still execute on
// evaluation, so their side effects apply.
import * as messagePort from "ext:deno_web/13_message_port.js";
import * as compression from "ext:deno_web/14_compression.js";
import * as performance from "ext:deno_web/15_performance.js";
import "ext:deno_web/16_image_data.js";
import "ext:deno_fetch/27_eventsource.js";
globalThis.atob = base64.atob;
globalThis.btoa = base64.btoa;
globalThis.fetch = fetch.fetch;
globalThis.Request = request.Request;
globalThis.Response = response.Response;
globalThis.Blob = file.Blob;
globalThis.URL = url.URL;
globalThis.FormData = formData.FormData;
globalThis.URLSearchParams = url.URLSearchParams;
globalThis.Headers = headers.Headers;
globalThis.FileReader = fileReader.FileReader;
globalThis.console = new console.Console((msg, level) =>
globalThis.Deno.core.ops.op_log(msg)
);
globalThis.AbortController = abortSignal.AbortController;
globalThis.AbortSignal = abortSignal.AbortSignal;
globalThis.crypto = crypto.crypto;
globalThis.Crypto = crypto.Crypto;
globalThis.CryptoKey = crypto.CryptoKey;
globalThis.SubtleCrypto = crypto.SubtleCrypto;
Object.assign(globalThis, {
clearInterval: timers.clearInterval,
clearTimeout: timers.clearTimeout,
setInterval: timers.setInterval,
setTimeout: timers.setTimeout,
});
// Standard web-platform globals from the deno_web / deno_url extensions,
// exposed to match the bun runner's global surface. Every name below is present
// in bun; names bun lacks (EventSource, ImageData) are deliberately excluded.
Object.assign(globalThis, {
// DOMException. Beyond bun parity, deno_web modules reference it as a global:
// AbortController.abort() with no reason constructs `new DOMException(...)`, so
// without this the already-wired AbortController/AbortSignal throw on abort.
DOMException: domException.DOMException,
// Text encoding + encoding streams.
TextEncoder: encoding.TextEncoder,
TextDecoder: encoding.TextDecoder,
TextEncoderStream: encoding.TextEncoderStream,
TextDecoderStream: encoding.TextDecoderStream,
// File (Blob is already wired above).
File: file.File,
// Events (AbortSignal, already wired, extends EventTarget). MessageEvent is
// the companion to MessagePort/MessageChannel below. Only the event types
// bun exposes are wired (ProgressEvent / PromiseRejectionEvent are not).
// reportError works because __wmInitPerIsolate makes globalThis an EventTarget.
Event: event.Event,
EventTarget: event.EventTarget,
CustomEvent: event.CustomEvent,
MessageEvent: event.MessageEvent,
CloseEvent: event.CloseEvent,
ErrorEvent: event.ErrorEvent,
reportError: event.reportError,
// Streams + queuing strategies + the reader/controller constructors bun also
// exposes as globals (used for `x instanceof ReadableStreamDefaultReader` etc.;
// the controllers throw on direct construction, matching the spec).
ReadableStream: streams.ReadableStream,
ReadableStreamDefaultReader: streams.ReadableStreamDefaultReader,
ReadableStreamBYOBReader: streams.ReadableStreamBYOBReader,
ReadableStreamDefaultController: streams.ReadableStreamDefaultController,
ReadableByteStreamController: streams.ReadableByteStreamController,
ReadableStreamBYOBRequest: streams.ReadableStreamBYOBRequest,
WritableStream: streams.WritableStream,
WritableStreamDefaultWriter: streams.WritableStreamDefaultWriter,
WritableStreamDefaultController: streams.WritableStreamDefaultController,
TransformStream: streams.TransformStream,
TransformStreamDefaultController: streams.TransformStreamDefaultController,
ByteLengthQueuingStrategy: streams.ByteLengthQueuingStrategy,
CountQueuingStrategy: streams.CountQueuingStrategy,
// URL pattern matching.
URLPattern: urlPattern.URLPattern,
// Compression streams.
CompressionStream: compression.CompressionStream,
DecompressionStream: compression.DecompressionStream,
// Message channel / port.
MessageChannel: messagePort.MessageChannel,
MessagePort: messagePort.MessagePort,
// High-resolution timing: the `performance` singleton and its constructor
// globals (bun exposes all of these; PerformanceObserver is not in deno_web).
performance: performance.performance,
Performance: performance.Performance,
PerformanceEntry: performance.PerformanceEntry,
PerformanceMark: performance.PerformanceMark,
PerformanceMeasure: performance.PerformanceMeasure,
// Spec structuredClone (validates args + honors the options bag), from the
// message-port module rather than the single-arg internal helper in
// 02_structured_clone.js.
structuredClone: messagePort.structuredClone,
});
// Per-isolate init, invoked from Rust after the snapshot is restored (this
// module body runs at snapshot-build time, not per isolate).
globalThis.__wmInitPerIsolate = () => {
// setTimeOrigin() seeds performance.timeOrigin from the isolate's wall clock;
// without it timeOrigin is undefined and `timeOrigin + performance.now()` is NaN.
performance.setTimeOrigin();
// Make globalThis a functional EventTarget, as Deno's bootstrap does. deno_web
// routes uncaught EventTarget-listener errors and reportError through
// reportException, which dispatches an error event on the saved global
// reference; reportError also requires its receiver to equal that reference.
// Both need the reference to be globalThis and globalThis to be an EventTarget,
// otherwise dispatch throws a masking error and globalThis.reportError() throws
// "Illegal invocation". Set up per isolate so the reference is the live global.
// The prototype + brand are what webidl.assertBranded checks in the methods.
Object.setPrototypeOf(
globalThis,
globalInterfaces.DedicatedWorkerGlobalScope.prototype,
);
event.setEventTargetData(globalThis);
globalThis[webidl.brand] = webidl.brand;
event.saveGlobalThisReference(globalThis);
};
// Expose bootstrapOtel globally so it can be called from Rust after runtime creation.
// We use dynamic import so deno_telemetry isn't loaded during snapshot creation.
// Config: [tracingEnabled, metricsEnabled, consoleConfig, deterministic]
// consoleConfig: 0=ignore, 1=capture, 2=replace
globalThis.__bootstrapOtel = () => {
import("ext:deno_telemetry/telemetry.ts").then(({ bootstrap, enterSpan }) => {
bootstrap([1, 0, 1, 0]);
// Expose enterSpan for setting parent trace context
globalThis.__enterSpan = enterSpan;
});
};