@nubjs/nub-linux-arm64 0.1.14 → 0.2.0
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 +0 -0
- package/bin/nubx +0 -0
- package/package.json +1 -1
- package/runtime/addons/nub-native.node +0 -0
- package/runtime/preload.cjs +11 -0
- package/runtime/version.mjs +1 -1
- package/runtime/worker-blob-url.cjs +116 -0
- package/runtime/worker-polyfill.mjs +241 -38
package/bin/nub
CHANGED
|
Binary file
|
package/bin/nubx
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
Binary file
|
package/runtime/preload.cjs
CHANGED
|
@@ -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.
|
package/runtime/version.mjs
CHANGED
|
@@ -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.
|
|
12
|
+
export const NUB_VERSION = "0.2.0";
|
|
@@ -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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
181
|
+
spawnTarget = asUrlString;
|
|
109
182
|
}
|
|
110
183
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
//
|
|
114
|
-
//
|
|
115
|
-
//
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
//
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
|
|
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
|
|
132
|
-
|
|
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
|
-
|
|
141
|
-
|
|
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
|
-
|
|
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
|
-
|
|
150
|
-
|
|
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
|
-
|
|
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
|