@nubjs/nub-linux-arm64 0.1.14 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/nub CHANGED
Binary file
package/bin/nubx CHANGED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nubjs/nub-linux-arm64",
3
- "version": "0.1.14",
3
+ "version": "0.2.1",
4
4
  "description": "Nub binary for linux-arm64",
5
5
  "license": "MIT",
6
6
  "repository": "https://github.com/nubjs/nub",
Binary file
@@ -218,6 +218,17 @@ function installLazyEsmPolyfills() {
218
218
  return;
219
219
  }
220
220
 
221
+ // Main thread: install blob: worker support EAGERLY. It only wraps
222
+ // URL.createObjectURL + subclasses Blob (touches no node:worker_threads, so it's
223
+ // cold-start-cheap) and MUST be live before user code calls createObjectURL —
224
+ // which happens before the first `new Worker`, so it cannot wait for the lazy
225
+ // Worker load below. See runtime/worker-blob-url.cjs.
226
+ try {
227
+ __require("./worker-blob-url.cjs").installBlobUrlSupport();
228
+ } catch {
229
+ // blob: worker support is best-effort; never block startup on it.
230
+ }
231
+
221
232
  // Main thread: lazy Worker global. Defined NON-ENUMERABLE so it stays invisible
222
233
  // to `Object.keys(globalThis)` / for-in — the additive contract — matching how
223
234
  // worker-polyfill.mjs defines the real one.
@@ -9,4 +9,4 @@
9
9
  // previously lived as a literal inside preload.mjs, which `make version` patched,
10
10
  // while the worker carried a hand-maintained "…-compat" copy that `make version`
11
11
  // never touched — a latent staleness bug this module closes.)
12
- export const NUB_VERSION = "0.1.14";
12
+ export const NUB_VERSION = "0.2.1";
@@ -0,0 +1,116 @@
1
+ // Blob-URL worker source capture — the SYNC half of WHATWG `blob:` worker support.
2
+ //
3
+ // A `new Worker(blobUrl)` must read the Blob's source SYNCHRONOUSLY in the
4
+ // constructor, but a Blob's bytes are only readable via the async Blob.text /
5
+ // arrayBuffer. We close that gap by snapshotting the source at
6
+ // `URL.createObjectURL(blob)` time — which always runs BEFORE the Worker is
7
+ // constructed — into a registry keyed by the minted URL.
8
+ //
9
+ // This lives in its OWN tiny CJS module (no node:worker_threads dependency) so the
10
+ // main-thread preload can install the wrap EAGERLY: it must be live before user
11
+ // code calls createObjectURL, whereas worker-polyfill.mjs (which pulls
12
+ // worker_threads + the whole streams/worker-io builtin set) is loaded LAZILY on
13
+ // first `new Worker` to protect cold start. The Worker class imports THIS module to
14
+ // read `blobUrlSources`. Touches only URL / Blob / Buffer — all already-realized
15
+ // core globals — so requiring it adds nothing to the main-thread bootstrap set.
16
+
17
+ // blob: URL → source text. Shared with worker-polyfill.mjs's Worker constructor.
18
+ const blobUrlSources = new Map();
19
+ // Blob construction parts, remembered so createObjectURL can assemble source sync.
20
+ const blobParts = new WeakMap();
21
+
22
+ function decode(parts) {
23
+ let src = "";
24
+ for (const p of parts ?? []) {
25
+ if (typeof p === "string") src += p;
26
+ else if (typeof Buffer !== "undefined" && Buffer.isBuffer(p)) src += p.toString("utf8");
27
+ else if (ArrayBuffer.isView(p)) src += Buffer.from(p.buffer, p.byteOffset, p.byteLength).toString("utf8");
28
+ else if (p instanceof ArrayBuffer) src += Buffer.from(p).toString("utf8");
29
+ else if (typeof p === "object" && p && typeof p.size === "number") {
30
+ const nested = blobParts.get(p); // a nested Blob made via our wrapper
31
+ if (nested) src += decode(nested);
32
+ }
33
+ }
34
+ return src;
35
+ }
36
+
37
+ // Wrap URL.createObjectURL/revokeObjectURL and Proxy Blob to remember parts.
38
+ // Idempotent + transparent for every non-worker use. No-op when URL has no
39
+ // createObjectURL (older floors) — blob: workers are then simply unavailable.
40
+ //
41
+ // The install-marker is a Symbol, not a string property: it sits on the native
42
+ // Blob constructor (a shared global), so a string key would show up in
43
+ // `Reflect.ownKeys(Blob)` / `"x" in Blob` — a (cosmetic) divergence from vanilla
44
+ // Node. A Symbol keeps the marker invisible to those reflective surfaces.
45
+ const INSTALLED = Symbol.for("nub.blobUrlSupport.installed");
46
+ function installBlobUrlSupport() {
47
+ if (typeof URL === "undefined" || typeof URL.createObjectURL !== "function") return;
48
+ if (URL.createObjectURL[INSTALLED]) return;
49
+
50
+ const NativeBlob = globalThis.Blob;
51
+ if (typeof NativeBlob === "function" && !NativeBlob[INSTALLED]) {
52
+ // Record construction parts WITHOUT changing Blob's identity. The earlier
53
+ // approach — a `class extends NativeBlob` swapped onto globalThis.Blob — broke
54
+ // brand identity in two ways that a structured-clone exposes:
55
+ // 1. A deserialized Blob (postMessage / structuredClone) is a NATIVE Blob, so
56
+ // `cloned instanceof globalThis.Blob` was FALSE (subclass.prototype is not
57
+ // in a native instance's chain) — a spec violation; native Node passes.
58
+ // 2. WORSE, undici's webidl `is.Blob` uses the ORDINARY `[Symbol.hasInstance]`
59
+ // (a prototype-chain check that ignores a custom hasInstance) against
60
+ // `globalThis.Blob`, so `new Response(clonedBlob).arrayBuffer()` failed the
61
+ // brand check and stringified the Blob to "[object Blob]" (13 bytes) instead
62
+ // of reading its bytes. A custom `Symbol.hasInstance` can't fix this — undici
63
+ // bypasses it.
64
+ // The ROOT cause was `globalThis.Blob !== node:buffer.Blob`. A Proxy whose target
65
+ // is the native Blob keeps `globalThis.Blob.prototype === NativeBlob.prototype`,
66
+ // so every native Blob (constructed OR deserialized) passes both `instanceof` and
67
+ // undici's ordinary-hasInstance check exactly as on vanilla Node — while the
68
+ // `construct` trap still records parts for sync blob: worker source assembly. File
69
+ // (which `extends` native Blob) then needs NO re-parenting: its instances already
70
+ // have NativeBlob.prototype in their chain.
71
+ const BlobProxy = new Proxy(NativeBlob, {
72
+ construct(target, args, newTarget) {
73
+ // FORWARD newTarget so a user subclass (`class X extends Blob {}`) gets ITS
74
+ // prototype — passing `target` here would force NativeBlob.prototype and
75
+ // silently break `new X() instanceof X` + the subclass's methods (an
76
+ // additivity violation vs vanilla Node). When `new Blob(...)` is called
77
+ // directly, newTarget IS this Proxy; Reflect.construct(NativeBlob, args, Proxy)
78
+ // resolves the new instance's proto from `Proxy.prototype`, which the Proxy
79
+ // forwards to NativeBlob.prototype — so a direct Blob is byte-identical to a
80
+ // native one (same brand, passes instanceof + undici's webidl check).
81
+ const inst = Reflect.construct(target, args, newTarget);
82
+ if (args[0] != null) blobParts.set(inst, args[0]);
83
+ return inst;
84
+ },
85
+ });
86
+ Object.defineProperty(NativeBlob, INSTALLED, { value: true });
87
+ Object.defineProperty(globalThis, "Blob", {
88
+ value: BlobProxy,
89
+ enumerable: false,
90
+ writable: true,
91
+ configurable: true,
92
+ });
93
+ // File's `extends` link still points at the real NativeBlob (unchanged), so
94
+ // `new File(...) instanceof Blob` holds natively — no re-parenting needed.
95
+ }
96
+
97
+ const nativeCreate = URL.createObjectURL.bind(URL);
98
+ const nativeRevoke =
99
+ typeof URL.revokeObjectURL === "function" ? URL.revokeObjectURL.bind(URL) : null;
100
+ const wrappedCreate = function createObjectURL(obj) {
101
+ const url = nativeCreate(obj);
102
+ const parts = blobParts.get(obj);
103
+ if (parts) blobUrlSources.set(url, decode(parts));
104
+ return url;
105
+ };
106
+ Object.defineProperty(wrappedCreate, INSTALLED, { value: true });
107
+ URL.createObjectURL = wrappedCreate;
108
+ if (nativeRevoke) {
109
+ URL.revokeObjectURL = function revokeObjectURL(url) {
110
+ blobUrlSources.delete(url);
111
+ return nativeRevoke(url);
112
+ };
113
+ }
114
+ }
115
+
116
+ module.exports = { blobUrlSources, installBlobUrlSupport };
@@ -87,76 +87,199 @@ function getErrorEventCtor() {
87
87
  export function installWorkerPolyfill() {
88
88
  const { Worker: NodeWorker, parentPort, isMainThread } = __getBuiltin("node:worker_threads");
89
89
  const { fileURLToPath } = __getBuiltin("node:url");
90
+ // blob: worker source registry, shared with the eager main-thread preload that
91
+ // wraps URL.createObjectURL (worker-blob-url.cjs). Loaded via createRequire so
92
+ // both this lazily-loaded ESM module and the eager CJS preload reference the SAME
93
+ // module instance (Node dedupes by resolved path) — i.e. the SAME blobUrlSources.
94
+ const { blobUrlSources, installBlobUrlSupport } = (
95
+ typeof process.getBuiltinModule === "function"
96
+ ? __getBuiltin("node:module").createRequire(import.meta.url)
97
+ : _bootstrapCreateRequire(import.meta.url)
98
+ )("./worker-blob-url.cjs");
99
+
100
+ // Resolve a worker-error stack frame to {filename,lineno,colno} so the
101
+ // ErrorEvent carries real source location, per WHATWG §10.2.6 (the spec
102
+ // requires these fields populated from where the error was raised). Node's
103
+ // `error` event delivers the thrown Error; we read its first stack frame.
104
+ // Browser-scrubbing of cross-origin frames does not apply here (all worker
105
+ // sources are same-origin local), so we surface the raw location.
106
+ //
107
+ // The frame is anchored at `at ` and consumes the optional `Func (` wrapper so
108
+ // group 1 is JUST the path (a `file://` URL, an absolute POSIX path, or a
109
+ // Windows `C:\…` path — the leading `C:` is NOT mistaken for the location
110
+ // colon because the line:col are the LAST two `:`-segments). Without the anchor
111
+ // + wrapper-consumption the path captured the `at Func (` prefix verbatim.
112
+ const STACK_FRAME = /^at\s+(?:.+?\s+\()?(.+?):(\d+):(\d+)\)?$/;
113
+ function locationFromError(err) {
114
+ let filename = "";
115
+ let lineno = 0;
116
+ let colno = 0;
117
+ const stack = err && typeof err.stack === "string" ? err.stack : "";
118
+ for (const line of stack.split("\n")) {
119
+ const t = line.trim();
120
+ if (!t.startsWith("at ")) continue;
121
+ const m = STACK_FRAME.exec(t);
122
+ if (m) {
123
+ filename = m[1].startsWith("file://") ? fileURLToPath(m[1]) : m[1];
124
+ lineno = Number(m[2]) || 0;
125
+ colno = Number(m[3]) || 0;
126
+ break;
127
+ }
128
+ }
129
+ return { filename, lineno, colno };
130
+ }
90
131
 
91
132
  if (typeof globalThis.Worker === "undefined") {
92
133
  class Worker extends EventTarget {
93
134
  #worker;
135
+ #name;
94
136
 
95
137
  constructor(url, options = {}) {
96
138
  super();
97
139
 
98
- let workerPath;
99
- if (url instanceof URL) {
100
- workerPath = fileURLToPath(url);
101
- } else if (typeof url === "string") {
102
- if (url.startsWith("file://")) {
103
- workerPath = fileURLToPath(url);
104
- } else {
105
- workerPath = url;
140
+ // The WHATWG Worker constructor accepts a script URL. Per §10.2.6.3 the
141
+ // standard inline mechanisms are `blob:` and `data:` URLs (there is no
142
+ // inline-source-string form in the spec). We map each to a Node spawn:
143
+ // - file path / file: URL → spawn the file (transpiled by nub's preload)
144
+ // - data: URL → Node runs it directly (worker_threads v14.9)
145
+ // - blob: URL → resolve the Blob via node:buffer, spawn its
146
+ // source with eval:true (Node can't open blob:)
147
+ let spawnTarget;
148
+
149
+ const asUrlString =
150
+ url instanceof URL ? url.href : typeof url === "string" ? url : null;
151
+ if (asUrlString === null) {
152
+ throw new TypeError("Worker constructor: url must be a string or URL");
153
+ }
154
+
155
+ if (asUrlString.startsWith("blob:")) {
156
+ // A `blob:` worker (WHATWG inline mechanism). Node cannot open a blob:
157
+ // URL as a worker entry, and the Blob's bytes are only readable
158
+ // ASYNCHRONOUSLY (Blob.text/arrayBuffer) while this constructor is sync.
159
+ // We close that gap by snapshotting the source SYNCHRONOUSLY at
160
+ // `URL.createObjectURL(blob)` time (see installBlobUrlSupport) into a
161
+ // module-scope registry keyed by URL, then spawn the source as a `data:`
162
+ // URL. We use data: (NOT eval:true) deliberately: the `--import` preload
163
+ // that installs nub's worker-side scope (self/postMessage) does NOT run in
164
+ // an eval:true worker on the compat-tier FLOOR (Node 18.19 — verified), so
165
+ // an eval-based blob worker has no `self` there; a data: URL worker is a
166
+ // real module load and DOES receive the preload on every supported tier.
167
+ const source = blobUrlSources.get(asUrlString);
168
+ if (source === undefined) {
169
+ throw new TypeError(
170
+ `Worker constructor: blob URL '${asUrlString}' is not a known object URL`
171
+ );
106
172
  }
173
+ spawnTarget = new URL(
174
+ "data:text/javascript;base64," + Buffer.from(source, "utf8").toString("base64")
175
+ );
176
+ } else if (asUrlString.startsWith("data:")) {
177
+ spawnTarget = new URL(asUrlString);
178
+ } else if (asUrlString.startsWith("file://")) {
179
+ spawnTarget = fileURLToPath(asUrlString);
107
180
  } else {
108
- throw new TypeError("Worker constructor: url must be a string or URL");
181
+ spawnTarget = asUrlString;
109
182
  }
110
183
 
111
- // `type: "module" | "classic"` is accepted for web compatibility but not
112
- // enforced: Node decides module-vs-CJS for the worker entry by file
113
- // extension + nearest package.json "type" (the same rule nub applies to
114
- // the main entry), and there is no classic/importScripts mode. Passing
115
- // `type` through to NodeWorker is harmless it ignores unknown options.
116
- // See wiki/research/worker-polyfill.md.
117
- // Node rejects flags in Worker execArgv that imply V8 `--harmony-*`
118
- // staging flags (ERR_WORKER_INVALID_EXEC_ARGV). Two categories to strip:
119
- // 1. `--harmony-*` flags themselves (V8 internals; may land in execArgv
120
- // on some Node versions when the parent injected an `--experimental-*`
121
- // that implies them).
122
- // 2. `--experimental-shadow-realm` implies `--harmony-shadow-realm`
123
- // (Node rejects it for workers even though the flag name is not
124
- // `--harmony-*`). This is the only nub-injected experimental flag
125
- // that has this property; other experimentals nub injects
126
- // (--experimental-vm-modules, --experimental-wasm-modules, etc.) are
127
- // fine in worker execArgv. Extend this filter if Node adds more.
128
- this.#worker = new NodeWorker(workerPath, {
184
+ this.#name = typeof options.name === "string" ? options.name : "";
185
+
186
+ // `type: "module" | "classic"` selects the worker's module system per the
187
+ // spec. The worker-side scope exposes `importScripts` only for classic
188
+ // workers (WHATWG WorkerGlobalScope classic-only) and throws for module
189
+ // workers. nub signals the choice to the worker via the internal
190
+ // NUB_WORKER_TYPE env (internal plumbing var exempt from the brand
191
+ // boundary). Node still decides the entry's actual module/CJS PARSING by
192
+ // file extension + package.json "type"; this env governs only which
193
+ // importScripts surface the polyfill installs.
194
+ const workerType =
195
+ options.type === "classic" ? "classic" : "module";
196
+
197
+ // Node rejects flags in Worker execArgv that imply V8 `--harmony-*` staging
198
+ // flags (ERR_WORKER_INVALID_EXEC_ARGV): `--harmony-*` themselves, and
199
+ // `--experimental-shadow-realm` (implies `--harmony-shadow-realm`). Strip
200
+ // those from whatever execArgv we forward.
201
+ const stripHarmony = (argv) =>
202
+ argv.filter(
203
+ f => !f.startsWith("--harmony") && f !== "--experimental-shadow-realm"
204
+ );
205
+ // execArgv: forward nub's preload-carrying parent execArgv by DEFAULT (so a
206
+ // worker inherits nub's transpile augmentation), but if the user supplied
207
+ // their own execArgv, MERGE rather than clobber — parent flags first, user
208
+ // flags appended so the user's win on conflict.
209
+ const execArgv = stripHarmony(
210
+ Array.isArray(options.execArgv)
211
+ ? [...process.execArgv, ...options.execArgv]
212
+ : process.execArgv
213
+ );
214
+
215
+ const nodeOptions = {
129
216
  ...options,
130
217
  eval: false,
131
- execArgv: process.execArgv.filter(
132
- f => !f.startsWith("--harmony") && f !== "--experimental-shadow-realm"
133
- ),
134
- });
218
+ execArgv,
219
+ };
220
+ // Thread the worker type AND name to the worker via internal env vars
221
+ // (NUB_WORKER_TYPE / NUB_WORKER_NAME — internal plumbing, exempt from the
222
+ // brand boundary). NUB_WORKER_NAME is REQUIRED for self.name across the
223
+ // whole compat tier: worker_threads.threadName (the only native worker-side
224
+ // reader of the {name} option) lands in v24.6.0 / v22.20.0, so it is absent
225
+ // below that and the env is the sole portable carrier. We avoid disturbing
226
+ // the user's env semantics: `worker_threads.SHARE_ENV` is a Symbol
227
+ // (live-shared parent env) — spreading it would destroy the share — so in
228
+ // that case we leave env untouched (self.name then falls back to native
229
+ // threadName/"" and importScripts defaults to the classic form).
230
+ const userEnv = options.env;
231
+ if (typeof userEnv === "symbol") {
232
+ // SHARE_ENV: leave nodeOptions.env as the user gave it; can't inject.
233
+ } else {
234
+ nodeOptions.env = {
235
+ ...(userEnv ?? process.env),
236
+ NUB_WORKER_TYPE: workerType,
237
+ NUB_WORKER_NAME: this.#name,
238
+ };
239
+ }
240
+ if (this.#name) nodeOptions.name = this.#name;
241
+
242
+ this.#worker = new NodeWorker(spawnTarget, nodeOptions);
135
243
 
136
244
  this.#worker.on("message", (data) => {
137
245
  this.dispatchEvent(new MessageEvent("message", { data }));
138
246
  });
139
247
 
140
- this.#worker.on("messageerror", (err) => {
141
- this.dispatchEvent(new MessageEvent("messageerror", { data: err }));
248
+ // WHATWG: messageerror fires when an inbound message fails deserialization;
249
+ // it is a plain MessageEvent with `data: null` (NOT carrying the error).
250
+ this.#worker.on("messageerror", () => {
251
+ this.dispatchEvent(new MessageEvent("messageerror", { data: null }));
142
252
  });
143
253
 
144
254
  this.#worker.on("error", (err) => {
145
255
  const ErrorEventCtor = getErrorEventCtor();
146
- this.dispatchEvent(new ErrorEventCtor("error", { error: err, message: err.message }));
256
+ const { filename, lineno, colno } = locationFromError(err);
257
+ this.dispatchEvent(
258
+ new ErrorEventCtor("error", {
259
+ error: err,
260
+ message: err.message,
261
+ filename,
262
+ lineno,
263
+ colno,
264
+ })
265
+ );
147
266
  });
267
+ // No `exit` event: WHATWG Workers have no exit event (it is a
268
+ // node:worker_threads concept, not part of the web Worker surface).
269
+ }
148
270
 
149
- this.#worker.on("exit", (code) => {
150
- this.dispatchEvent(new Event("exit"));
151
- });
271
+ get name() {
272
+ return this.#name;
152
273
  }
153
274
 
154
275
  postMessage(data, transfer) {
155
276
  this.#worker.postMessage(data, transfer);
156
277
  }
157
278
 
279
+ // WHATWG terminate() returns void. Node's returns a Promise; we discard it so
280
+ // the surface matches the spec (the underlying termination still proceeds).
158
281
  terminate() {
159
- return this.#worker.terminate();
282
+ this.#worker.terminate();
160
283
  }
161
284
 
162
285
  #onmessageHandler = null;
@@ -194,6 +317,11 @@ export function installWorkerPolyfill() {
194
317
  writable: true,
195
318
  configurable: true,
196
319
  });
320
+
321
+ // Enable blob: workers: wrap URL.createObjectURL so the source is captured
322
+ // synchronously for the constructor's blob: branch. Transparent for all other
323
+ // uses; installs once. Only on the main thread (where blob: URLs are minted).
324
+ if (isMainThread) installBlobUrlSupport();
197
325
  }
198
326
 
199
327
  // Worker-side bootstrap: emulate the DedicatedWorkerGlobalScope on top of
@@ -221,6 +349,81 @@ if (!isMainThread && parentPort) {
221
349
  });
222
350
  defineGlobal("self", scope);
223
351
 
352
+ // `self.name` — the worker's name from the constructor's {name} option (WHATWG
353
+ // DedicatedWorkerGlobalScope.name). Node only exposes a worker-side reader for
354
+ // the {name} option as `worker_threads.threadName` from v24.6.0 / v22.20.0 — it
355
+ // is ABSENT across nub's whole compat tier (18.19–22.19), so it cannot be the
356
+ // floor mechanism. We THREAD the name in ourselves via the internal
357
+ // NUB_WORKER_NAME env (internal plumbing var — exempt from the brand boundary),
358
+ // set by the main-side constructor.
359
+ //
360
+ // RESOLUTION ORDER — env FIRST (not native threadName): NUB_WORKER_NAME carries
361
+ // the user's EXACT intent including the empty string, whereas native
362
+ // `threadName` defaults to the literal sentinel "WorkerThread" for an UNNAMED
363
+ // worker (it's the thread DISPLAY name, not the WHATWG worker name) — surfacing
364
+ // that as self.name would be a spec divergence (an unnamed worker's name must be
365
+ // ""). So: the injected env wins when present; native threadName is the fallback
366
+ // ONLY on the SHARE_ENV path (where the ctor couldn't inject env), with the
367
+ // "WorkerThread" sentinel filtered to "".
368
+ {
369
+ const wt = __getBuiltin("node:worker_threads");
370
+ let name;
371
+ if (typeof process.env.NUB_WORKER_NAME === "string") {
372
+ name = process.env.NUB_WORKER_NAME;
373
+ } else {
374
+ const tn = wt && typeof wt.threadName === "string" ? wt.threadName : "";
375
+ name = tn === "WorkerThread" ? "" : tn;
376
+ }
377
+ Object.defineProperty(scope, "name", {
378
+ value: name,
379
+ enumerable: false,
380
+ writable: true,
381
+ configurable: true,
382
+ });
383
+ }
384
+
385
+ // `importScripts(...urls)` — WHATWG WorkerGlobalScope, CLASSIC workers only.
386
+ // Synchronously fetches + evaluates each script in the global scope, in order.
387
+ // A module worker MUST throw on importScripts (use `import` instead). nub learns
388
+ // the worker's type from NUB_WORKER_TYPE (set by the main-side constructor).
389
+ // Remote URLs are not supported (no sync network in Node); local file:/relative
390
+ // paths and data: URLs are read synchronously.
391
+ // Default to the classic (working) importScripts when the type is unset — this
392
+ // is the SHARE_ENV edge where the constructor couldn't inject NUB_WORKER_TYPE.
393
+ if (process.env.NUB_WORKER_TYPE !== "module") {
394
+ const fs = __getBuiltin("node:fs");
395
+ const { fileURLToPath: f2p, pathToFileURL } = __getBuiltin("node:url");
396
+ defineGlobal("importScripts", (...urls) => {
397
+ for (const u of urls) {
398
+ const s = String(u);
399
+ let code;
400
+ if (s.startsWith("data:")) {
401
+ const comma = s.indexOf(",");
402
+ const meta = s.slice(5, comma);
403
+ const body = s.slice(comma + 1);
404
+ code = meta.includes("base64")
405
+ ? Buffer.from(body, "base64").toString("utf8")
406
+ : decodeURIComponent(body);
407
+ } else if (/^https?:/.test(s)) {
408
+ throw new TypeError(
409
+ "importScripts: remote URLs are not supported (no synchronous network)"
410
+ );
411
+ } else {
412
+ const path = s.startsWith("file://") ? f2p(s) : s;
413
+ code = fs.readFileSync(path, "utf8");
414
+ }
415
+ // Indirect eval → runs in global scope, matching importScripts semantics.
416
+ (0, eval)(code);
417
+ }
418
+ });
419
+ } else {
420
+ // Module workers: importScripts must throw (spec). Provide the throwing form
421
+ // so the surface exists and the error is the spec-correct one.
422
+ defineGlobal("importScripts", () => {
423
+ throw new TypeError("importScripts is not available in module workers");
424
+ });
425
+ }
426
+
224
427
  // `message`/`messageerror` are DELEGATED straight onto the native `parentPort`
225
428
  // (a real Node MessagePort) so Node's own C++ event-loop ref-counting governs
226
429
  // worker lifetime: a worker that never listens leaves parentPort with no