@nubjs/nub-darwin-x64 0.2.1 → 0.2.3
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/navigator-locks.mjs +188 -81
- package/runtime/navigator-shim.mjs +144 -0
- package/runtime/polyfills.cjs +624 -18
- package/runtime/preload-async-hooks.mjs +16 -3
- package/runtime/preload-common.cjs +49 -0
- package/runtime/preload.cjs +15 -0
- package/runtime/preload.mjs +8 -0
- package/runtime/transform-core.mjs +86 -14
- package/runtime/version.mjs +1 -1
package/runtime/polyfills.cjs
CHANGED
|
@@ -39,29 +39,33 @@ function installSyncPolyfills(preloaded) {
|
|
|
39
39
|
// `typeof localStorage` throws, so feature-detection is impossible and the throw
|
|
40
40
|
// can surface before user code expects it. The spawn layer signals this case via
|
|
41
41
|
// the internal `__NUB_NEUTRALIZE_LOCALSTORAGE` env var (set iff unflagged ∧
|
|
42
|
-
// no user file).
|
|
43
|
-
// matching Node
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
//
|
|
42
|
+
// no user file). DELETE the throwing getter so the global becomes ABSENT —
|
|
43
|
+
// matching vanilla Node 24's shape on this band (`'localStorage' in globalThis
|
|
44
|
+
// === false`), not present-but-undefined. Absent is the additive choice: a bare
|
|
45
|
+
// `localStorage` read throws ReferenceError exactly as on vanilla Node 24, and
|
|
46
|
+
// `typeof localStorage === "undefined"` stays true with no throw. The earlier
|
|
47
|
+
// present-undefined define matched Node 25+'s native shape, but that broke isomorphic
|
|
48
|
+
// libraries that gate on `'localStorage' in window/globalThis` (e.g. vitest's
|
|
49
|
+
// happy-dom `getWindowKeys`): a present property made them SKIP installing their
|
|
50
|
+
// own store, so user code then read nub's `undefined` and crashed (#166). This
|
|
51
|
+
// runs in the preload BEFORE any user code, so the throwing getter is never
|
|
52
|
+
// observed. When the user passes `--localstorage-file`, the env var is absent and
|
|
53
|
+
// `localStorage` works normally (we do not touch it). We deliberately KEEP the
|
|
54
|
+
// env var set so it inherits to the whole process subtree: a `node`- or
|
|
49
55
|
// `nub`-spawned grandchild re-inherits the webstorage flag via NODE_OPTIONS and
|
|
50
56
|
// would otherwise re-install the throwing getter with no neutralize signal. It's
|
|
51
57
|
// an internal `__NUB_*` plumbing var that's explicitly fine to leak to children.
|
|
52
|
-
// Neutralization is idempotent — a descendant re-running this preload
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
//
|
|
58
|
+
// Neutralization is idempotent — a descendant re-running this preload deletes an
|
|
59
|
+
// already-absent or re-installed `localStorage` again, which is harmless. The
|
|
60
|
+
// property is a configurable own accessor (the define that replaced it before
|
|
61
|
+
// already proved that), so `delete` removes it cleanly. This file is sloppy-mode
|
|
62
|
+
// CJS, where `delete` never throws (a non-configurable property would just make
|
|
63
|
+
// it return false and leave Node's getter in place); the try/catch is belt-and-
|
|
64
|
+
// suspenders for any future strict/ESM move.
|
|
56
65
|
if (process.env.__NUB_NEUTRALIZE_LOCALSTORAGE) {
|
|
57
66
|
try {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
configurable: true,
|
|
61
|
-
writable: true,
|
|
62
|
-
enumerable: false,
|
|
63
|
-
});
|
|
64
|
-
} catch { /* descriptor non-configurable on this runtime: leave Node's behavior */ }
|
|
67
|
+
delete globalThis.localStorage;
|
|
68
|
+
} catch { /* non-configurable on this runtime: leave Node's behavior */ }
|
|
65
69
|
}
|
|
66
70
|
|
|
67
71
|
// ── reportError (WinterTC min-common-API, not in any Node) ──────────
|
|
@@ -84,6 +88,74 @@ function installSyncPolyfills(preloaded) {
|
|
|
84
88
|
});
|
|
85
89
|
}
|
|
86
90
|
|
|
91
|
+
// ── File (global on Node 20+, missing on the 18.x compat floor) ─────
|
|
92
|
+
// Node exposes the WHATWG `File` as a global from Node 20; on 18.13–18.x it
|
|
93
|
+
// exists only as `node:buffer`'s `File` export. Backfill the global from there
|
|
94
|
+
// so worker/messaging code that constructs `new File(...)` works down to the
|
|
95
|
+
// floor (polyfill-all-the-way-down). Identity is preserved (same constructor as
|
|
96
|
+
// `node:buffer`), so `instanceof` and undici's webidl brand checks hold. Blob is
|
|
97
|
+
// already global on 18.x, but the same backfill guards it for completeness.
|
|
98
|
+
// Non-enumerable to match Node's own global descriptors (the additive contract:
|
|
99
|
+
// invisible to global enumeration).
|
|
100
|
+
//
|
|
101
|
+
// Node 18 emits a one-time `ExperimentalWarning: buffer.File …` on the FIRST
|
|
102
|
+
// `new File(...)` (the constructor, NOT the property read). Without nub the floor
|
|
103
|
+
// simply has no `File` global, so backfilling it would newly surface that warning
|
|
104
|
+
// when user code first constructs a File. To keep the floor backfill silent we
|
|
105
|
+
// force one throwaway construction INSIDE a suppression window: that consumes
|
|
106
|
+
// Node's once-per-feature guard (the warning is dropped here) so the user's later
|
|
107
|
+
// `new File(...)` is silent.
|
|
108
|
+
if (typeof globalThis.File === "undefined" || typeof globalThis.Blob === "undefined") {
|
|
109
|
+
const origEmitWarning = process.emitWarning;
|
|
110
|
+
process.emitWarning = function (warning, ...rest) {
|
|
111
|
+
const opt = rest[0];
|
|
112
|
+
const type = opt && typeof opt === "object" ? opt.type : opt;
|
|
113
|
+
const msg = typeof warning === "string" ? warning : (warning && warning.message) || "";
|
|
114
|
+
if (type === "ExperimentalWarning" && /buffer\.(File|Blob)/.test(msg)) return;
|
|
115
|
+
return origEmitWarning.call(this, warning, ...rest);
|
|
116
|
+
};
|
|
117
|
+
try {
|
|
118
|
+
const buffer = require("node:buffer");
|
|
119
|
+
const sampleArgs = { File: [[], ""], Blob: [[]] };
|
|
120
|
+
for (const name of ["File", "Blob"]) {
|
|
121
|
+
const Ctor = buffer[name];
|
|
122
|
+
if (typeof globalThis[name] === "undefined" && typeof Ctor === "function") {
|
|
123
|
+
Object.defineProperty(globalThis, name, {
|
|
124
|
+
value: Ctor,
|
|
125
|
+
enumerable: false,
|
|
126
|
+
writable: true,
|
|
127
|
+
configurable: true,
|
|
128
|
+
});
|
|
129
|
+
// Trip (and suppress) the experimental-feature warning now, so user code
|
|
130
|
+
// never sees it.
|
|
131
|
+
try { new Ctor(...sampleArgs[name]); } catch { /* construction shape varies; the warning fires regardless */ }
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
} finally {
|
|
135
|
+
process.emitWarning = origEmitWarning;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ── MessageEvent.ports → frozen array (WHATWG read-only requirement) ─
|
|
140
|
+
// The spec mandates `MessageEvent.ports` be a read-only (frozen) array; Node's
|
|
141
|
+
// native MessageEvent returns a mutable array. Wrap the configurable prototype
|
|
142
|
+
// getter so every read yields a frozen array, for both a native MessageChannel's
|
|
143
|
+
// delivery and nub's worker-side MessageEvents. Idempotent (the wrapper is marked
|
|
144
|
+
// so a re-run in the same realm doesn't double-wrap).
|
|
145
|
+
if (typeof globalThis.MessageEvent === "function") {
|
|
146
|
+
const proto = globalThis.MessageEvent.prototype;
|
|
147
|
+
const desc = Object.getOwnPropertyDescriptor(proto, "ports");
|
|
148
|
+
if (desc && typeof desc.get === "function" && desc.configurable && !desc.get.__nubFreezesPorts) {
|
|
149
|
+
const origGet = desc.get;
|
|
150
|
+
const get = function () {
|
|
151
|
+
const ports = origGet.call(this);
|
|
152
|
+
return Array.isArray(ports) ? Object.freeze(ports) : ports;
|
|
153
|
+
};
|
|
154
|
+
get.__nubFreezesPorts = true;
|
|
155
|
+
Object.defineProperty(proto, "ports", { ...desc, get });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
87
159
|
// ── URLPattern (native on Node 24+, missing on 22.x) ───────────────
|
|
88
160
|
if (typeof globalThis.URLPattern === "undefined") {
|
|
89
161
|
const mod = preloaded.urlpattern;
|
|
@@ -191,6 +263,540 @@ function installSyncPolyfills(preloaded) {
|
|
|
191
263
|
}
|
|
192
264
|
}
|
|
193
265
|
}
|
|
266
|
+
|
|
267
|
+
installUint8ArrayBase64();
|
|
268
|
+
installDisposableStacks();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// ── Uint8Array base64/hex (TC39 Stage 3; native Node 25+, absent below) ──
|
|
272
|
+
// Spec-faithful port of the TC39 proposal-arraybuffer-base64 reference polyfill,
|
|
273
|
+
// so the < 25 floor behaves byte-for-byte like native: toBase64/fromBase64 honor
|
|
274
|
+
// the {alphabet, omitPadding} / {alphabet, lastChunkHandling} options,
|
|
275
|
+
// setFromBase64/setFromHex report {read, written} and write the valid prefix before
|
|
276
|
+
// throwing on a malformed tail, and toHex/fromHex round-trip. The methods are
|
|
277
|
+
// defined non-enumerable (the additive contract: invisible to enumeration of the
|
|
278
|
+
// prototype) and feature-detect off `Uint8Array.prototype.toBase64`, so they are a
|
|
279
|
+
// strict no-op where the runtime ships them natively. Verified differentially
|
|
280
|
+
// against Node native across the encode/decode/whitespace/padding/maxLength matrix.
|
|
281
|
+
function installUint8ArrayBase64() {
|
|
282
|
+
if (typeof Uint8Array.prototype.toBase64 === "function") return;
|
|
283
|
+
|
|
284
|
+
const B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
285
|
+
// char code → 6-bit value for the standard alphabet; url chars are remapped to
|
|
286
|
+
// standard before lookup, so a single decode table covers both alphabets.
|
|
287
|
+
const DECODE = new Int16Array(128).fill(-1);
|
|
288
|
+
for (let i = 0; i < B64.length; i++) DECODE[B64.charCodeAt(i)] = i;
|
|
289
|
+
|
|
290
|
+
// %TypedArray%.prototype[@@toStringTag] getter — the brand check native uses: it
|
|
291
|
+
// accepts a Uint8Array (and Buffer, a Uint8Array subclass) and rejects any other
|
|
292
|
+
// TypedArray or non-typed-array with a TypeError.
|
|
293
|
+
const tagGet = Object.getOwnPropertyDescriptor(
|
|
294
|
+
Object.getPrototypeOf(Uint8Array.prototype),
|
|
295
|
+
Symbol.toStringTag,
|
|
296
|
+
).get;
|
|
297
|
+
const checkU8 = (arg) => {
|
|
298
|
+
let kind;
|
|
299
|
+
try {
|
|
300
|
+
kind = tagGet.call(arg);
|
|
301
|
+
} catch {
|
|
302
|
+
throw new TypeError("not a Uint8Array");
|
|
303
|
+
}
|
|
304
|
+
if (kind !== "Uint8Array") throw new TypeError("not a Uint8Array");
|
|
305
|
+
};
|
|
306
|
+
const getOptions = (options) => {
|
|
307
|
+
if (typeof options === "undefined") return Object.create(null);
|
|
308
|
+
if (options && typeof options === "object") return options;
|
|
309
|
+
throw new TypeError("options is not object");
|
|
310
|
+
};
|
|
311
|
+
const isDetached = (arr) => "detached" in arr.buffer && arr.buffer.detached;
|
|
312
|
+
const isWs = (cc) =>
|
|
313
|
+
cc === 0x09 || cc === 0x0a || cc === 0x0c || cc === 0x0d || cc === 0x20;
|
|
314
|
+
const skipWs = (s, i) => {
|
|
315
|
+
while (i < s.length && isWs(s.charCodeAt(i))) i++;
|
|
316
|
+
return i;
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
// chunk is 2–4 standard-alphabet chars; pads to 4 then emits 1–3 bytes. In strict
|
|
320
|
+
// mode the unused low bits of a 2/3-char chunk must be zero.
|
|
321
|
+
const decodeChunk = (chunk, throwOnExtraBits) => {
|
|
322
|
+
const n = chunk.length;
|
|
323
|
+
const padded = n < 4 ? chunk + (n === 2 ? "AA" : "A") : chunk;
|
|
324
|
+
const triplet =
|
|
325
|
+
(DECODE[padded.charCodeAt(0)] << 18) +
|
|
326
|
+
(DECODE[padded.charCodeAt(1)] << 12) +
|
|
327
|
+
(DECODE[padded.charCodeAt(2)] << 6) +
|
|
328
|
+
DECODE[padded.charCodeAt(3)];
|
|
329
|
+
const b0 = (triplet >> 16) & 255;
|
|
330
|
+
const b1 = (triplet >> 8) & 255;
|
|
331
|
+
const b2 = triplet & 255;
|
|
332
|
+
if (n === 2) {
|
|
333
|
+
if (throwOnExtraBits && b1 !== 0) throw new SyntaxError("extra bits");
|
|
334
|
+
return [b0];
|
|
335
|
+
}
|
|
336
|
+
if (n === 3) {
|
|
337
|
+
if (throwOnExtraBits && b2 !== 0) throw new SyntaxError("extra bits");
|
|
338
|
+
return [b0, b1];
|
|
339
|
+
}
|
|
340
|
+
return [b0, b1, b2];
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
const u8ToBase64 = (arr, options) => {
|
|
344
|
+
checkU8(arr);
|
|
345
|
+
const opts = getOptions(options);
|
|
346
|
+
let alphabet = opts.alphabet;
|
|
347
|
+
if (typeof alphabet === "undefined") alphabet = "base64";
|
|
348
|
+
if (alphabet !== "base64" && alphabet !== "base64url") {
|
|
349
|
+
throw new TypeError('expected alphabet to be either "base64" or "base64url"');
|
|
350
|
+
}
|
|
351
|
+
const omitPadding = !!opts.omitPadding;
|
|
352
|
+
if (isDetached(arr)) {
|
|
353
|
+
throw new TypeError("toBase64 called on array backed by detached buffer");
|
|
354
|
+
}
|
|
355
|
+
const lookup =
|
|
356
|
+
alphabet === "base64url"
|
|
357
|
+
? "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
|
|
358
|
+
: B64;
|
|
359
|
+
let result = "";
|
|
360
|
+
let i = 0;
|
|
361
|
+
for (; i + 2 < arr.length; i += 3) {
|
|
362
|
+
const triplet = (arr[i] << 16) + (arr[i + 1] << 8) + arr[i + 2];
|
|
363
|
+
result +=
|
|
364
|
+
lookup[(triplet >> 18) & 63] +
|
|
365
|
+
lookup[(triplet >> 12) & 63] +
|
|
366
|
+
lookup[(triplet >> 6) & 63] +
|
|
367
|
+
lookup[triplet & 63];
|
|
368
|
+
}
|
|
369
|
+
if (i + 2 === arr.length) {
|
|
370
|
+
const triplet = (arr[i] << 16) + (arr[i + 1] << 8);
|
|
371
|
+
result +=
|
|
372
|
+
lookup[(triplet >> 18) & 63] +
|
|
373
|
+
lookup[(triplet >> 12) & 63] +
|
|
374
|
+
lookup[(triplet >> 6) & 63] +
|
|
375
|
+
(omitPadding ? "" : "=");
|
|
376
|
+
} else if (i + 1 === arr.length) {
|
|
377
|
+
const triplet = arr[i] << 16;
|
|
378
|
+
result +=
|
|
379
|
+
lookup[(triplet >> 18) & 63] +
|
|
380
|
+
lookup[(triplet >> 12) & 63] +
|
|
381
|
+
(omitPadding ? "" : "==");
|
|
382
|
+
}
|
|
383
|
+
return result;
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
// Core decode shared by fromBase64 and setFromBase64. Returns {bytes, read,
|
|
387
|
+
// error}: a non-null `error` is thrown by the callers AFTER the valid prefix is
|
|
388
|
+
// written (so setFromBase64 partial-writes then throws, matching native).
|
|
389
|
+
const fromBase64 = (string, alphabet, lastChunkHandling, maxLength) => {
|
|
390
|
+
if (maxLength === 0) return { read: 0, bytes: [], error: null };
|
|
391
|
+
let read = 0;
|
|
392
|
+
const bytes = [];
|
|
393
|
+
let chunk = "";
|
|
394
|
+
let index = 0;
|
|
395
|
+
while (true) {
|
|
396
|
+
index = skipWs(string, index);
|
|
397
|
+
if (index === string.length) {
|
|
398
|
+
if (chunk.length > 0) {
|
|
399
|
+
if (lastChunkHandling === "stop-before-partial") {
|
|
400
|
+
return { bytes, read, error: null };
|
|
401
|
+
} else if (lastChunkHandling === "loose") {
|
|
402
|
+
if (chunk.length === 1) {
|
|
403
|
+
return {
|
|
404
|
+
bytes,
|
|
405
|
+
read,
|
|
406
|
+
error: new SyntaxError("malformed padding: exactly one additional character"),
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
bytes.push(...decodeChunk(chunk, false));
|
|
410
|
+
} else {
|
|
411
|
+
return { bytes, read, error: new SyntaxError("missing padding") };
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return { bytes, read: string.length, error: null };
|
|
415
|
+
}
|
|
416
|
+
let char = string[index];
|
|
417
|
+
++index;
|
|
418
|
+
if (char === "=") {
|
|
419
|
+
if (chunk.length < 2) {
|
|
420
|
+
return { bytes, read, error: new SyntaxError("padding is too early") };
|
|
421
|
+
}
|
|
422
|
+
index = skipWs(string, index);
|
|
423
|
+
if (chunk.length === 2) {
|
|
424
|
+
if (index === string.length) {
|
|
425
|
+
if (lastChunkHandling === "stop-before-partial") {
|
|
426
|
+
return { bytes, read, error: null };
|
|
427
|
+
}
|
|
428
|
+
return { bytes, read, error: new SyntaxError("malformed padding - only one =") };
|
|
429
|
+
}
|
|
430
|
+
if (string[index] === "=") {
|
|
431
|
+
++index;
|
|
432
|
+
index = skipWs(string, index);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
if (index < string.length) {
|
|
436
|
+
return { bytes, read, error: new SyntaxError("unexpected character after padding") };
|
|
437
|
+
}
|
|
438
|
+
bytes.push(...decodeChunk(chunk, lastChunkHandling === "strict"));
|
|
439
|
+
return { bytes, read: string.length, error: null };
|
|
440
|
+
}
|
|
441
|
+
if (alphabet === "base64url") {
|
|
442
|
+
if (char === "+" || char === "/") {
|
|
443
|
+
return { bytes, read, error: new SyntaxError("unexpected character " + JSON.stringify(char)) };
|
|
444
|
+
} else if (char === "-") {
|
|
445
|
+
char = "+";
|
|
446
|
+
} else if (char === "_") {
|
|
447
|
+
char = "/";
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
if (!B64.includes(char)) {
|
|
451
|
+
return { bytes, read, error: new SyntaxError("unexpected character " + JSON.stringify(char)) };
|
|
452
|
+
}
|
|
453
|
+
const remainingBytes = maxLength - bytes.length;
|
|
454
|
+
if (
|
|
455
|
+
(remainingBytes === 1 && chunk.length === 2) ||
|
|
456
|
+
(remainingBytes === 2 && chunk.length === 3)
|
|
457
|
+
) {
|
|
458
|
+
// The chunk-in-progress already represents exactly `remainingBytes` bytes;
|
|
459
|
+
// the char we just read would start a group we have no room for. Stop.
|
|
460
|
+
return { bytes, read, error: null };
|
|
461
|
+
}
|
|
462
|
+
chunk += char;
|
|
463
|
+
if (chunk.length === 4) {
|
|
464
|
+
bytes.push(...decodeChunk(chunk, false));
|
|
465
|
+
chunk = "";
|
|
466
|
+
read = index;
|
|
467
|
+
if (bytes.length === maxLength) {
|
|
468
|
+
// maxLength hit (setFromBase64 with a short target): native advances
|
|
469
|
+
// `read` past trailing whitespace only when it runs to end-of-input —
|
|
470
|
+
// if real content follows, `read` stays at the quad boundary.
|
|
471
|
+
const after = skipWs(string, index);
|
|
472
|
+
if (after === string.length) read = after;
|
|
473
|
+
return { bytes, read, error: null };
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
const b64ToU8 = (string, options, into) => {
|
|
480
|
+
if (typeof string !== "string") throw new TypeError("expected input to be a string");
|
|
481
|
+
const opts = getOptions(options);
|
|
482
|
+
let alphabet = opts.alphabet;
|
|
483
|
+
if (typeof alphabet === "undefined") alphabet = "base64";
|
|
484
|
+
if (alphabet !== "base64" && alphabet !== "base64url") {
|
|
485
|
+
throw new TypeError('expected alphabet to be either "base64" or "base64url"');
|
|
486
|
+
}
|
|
487
|
+
let lastChunkHandling = opts.lastChunkHandling;
|
|
488
|
+
if (typeof lastChunkHandling === "undefined") lastChunkHandling = "loose";
|
|
489
|
+
if (
|
|
490
|
+
lastChunkHandling !== "loose" &&
|
|
491
|
+
lastChunkHandling !== "strict" &&
|
|
492
|
+
lastChunkHandling !== "stop-before-partial"
|
|
493
|
+
) {
|
|
494
|
+
throw new TypeError(
|
|
495
|
+
'expected lastChunkHandling to be either "loose", "strict", or "stop-before-partial"',
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
if (into && isDetached(into)) {
|
|
499
|
+
throw new TypeError("setFromBase64 called on array backed by detached buffer");
|
|
500
|
+
}
|
|
501
|
+
const maxLength = into ? into.length : 2 ** 53 - 1;
|
|
502
|
+
let { bytes, read, error } = fromBase64(string, alphabet, lastChunkHandling, maxLength);
|
|
503
|
+
if (error && !into) throw error;
|
|
504
|
+
bytes = new Uint8Array(bytes);
|
|
505
|
+
if (into && bytes.length > 0) into.set(bytes);
|
|
506
|
+
if (error) throw error;
|
|
507
|
+
return { read, bytes };
|
|
508
|
+
};
|
|
509
|
+
|
|
510
|
+
const u8ToHex = (arr) => {
|
|
511
|
+
checkU8(arr);
|
|
512
|
+
if (isDetached(arr)) {
|
|
513
|
+
throw new TypeError("toHex called on array backed by detached buffer");
|
|
514
|
+
}
|
|
515
|
+
let out = "";
|
|
516
|
+
for (let i = 0; i < arr.length; ++i) out += arr[i].toString(16).padStart(2, "0");
|
|
517
|
+
return out;
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
const hexToU8 = (string, into) => {
|
|
521
|
+
if (typeof string !== "string") throw new TypeError("expected string to be a string");
|
|
522
|
+
if (into && isDetached(into)) {
|
|
523
|
+
throw new TypeError("setFromHex called on array backed by detached buffer");
|
|
524
|
+
}
|
|
525
|
+
// Odd-length input is rejected unconditionally — even with an `into` and even
|
|
526
|
+
// when maxLength would cut before the lone trailing hexit (matches native).
|
|
527
|
+
if (string.length % 2 !== 0) {
|
|
528
|
+
throw new SyntaxError("string should be an even number of characters");
|
|
529
|
+
}
|
|
530
|
+
const maxLength = into ? into.length : 2 ** 53 - 1;
|
|
531
|
+
const bytesArr = [];
|
|
532
|
+
let read = 0;
|
|
533
|
+
let error = null;
|
|
534
|
+
if (maxLength > 0) {
|
|
535
|
+
while (read < string.length) {
|
|
536
|
+
const hexits = string.slice(read, read + 2);
|
|
537
|
+
if (/[^0-9a-fA-F]/.test(hexits)) {
|
|
538
|
+
error = new SyntaxError("string should only contain hex characters");
|
|
539
|
+
break;
|
|
540
|
+
}
|
|
541
|
+
bytesArr.push(parseInt(hexits, 16));
|
|
542
|
+
read += 2;
|
|
543
|
+
if (bytesArr.length === maxLength) break;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
if (error && !into) throw error;
|
|
547
|
+
const bytes = new Uint8Array(bytesArr);
|
|
548
|
+
if (into && bytes.length > 0) into.set(bytes);
|
|
549
|
+
if (error) throw error;
|
|
550
|
+
return { read, bytes };
|
|
551
|
+
};
|
|
552
|
+
|
|
553
|
+
const def = (target, name, fn) => {
|
|
554
|
+
Object.defineProperty(target, name, {
|
|
555
|
+
value: fn,
|
|
556
|
+
writable: true,
|
|
557
|
+
enumerable: false,
|
|
558
|
+
configurable: true,
|
|
559
|
+
});
|
|
560
|
+
};
|
|
561
|
+
def(Uint8Array.prototype, "toBase64", function toBase64(options) {
|
|
562
|
+
return u8ToBase64(this, options);
|
|
563
|
+
});
|
|
564
|
+
def(Uint8Array, "fromBase64", function fromBase64(string, options) {
|
|
565
|
+
return b64ToU8(string, options, undefined).bytes;
|
|
566
|
+
});
|
|
567
|
+
def(Uint8Array.prototype, "setFromBase64", function setFromBase64(string, options) {
|
|
568
|
+
checkU8(this);
|
|
569
|
+
const { read, bytes } = b64ToU8(string, options, this);
|
|
570
|
+
return { read, written: bytes.length };
|
|
571
|
+
});
|
|
572
|
+
def(Uint8Array.prototype, "toHex", function toHex() {
|
|
573
|
+
return u8ToHex(this);
|
|
574
|
+
});
|
|
575
|
+
def(Uint8Array, "fromHex", function fromHex(string) {
|
|
576
|
+
return hexToU8(string, undefined).bytes;
|
|
577
|
+
});
|
|
578
|
+
def(Uint8Array.prototype, "setFromHex", function setFromHex(string) {
|
|
579
|
+
checkU8(this);
|
|
580
|
+
const { read, bytes } = hexToU8(string, this);
|
|
581
|
+
return { read, written: bytes.length };
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// ── DisposableStack / AsyncDisposableStack (TC39 Stage 4 Explicit Resource
|
|
586
|
+
// Management; native Node 24+, absent below) ──
|
|
587
|
+
// nub already down-levels the `using` / `await using` SYNTAX; this fills the
|
|
588
|
+
// runtime-CLASS gap so code that references the classes directly (or output from a
|
|
589
|
+
// toolchain that targets the native classes) works across the floor. Disposal is
|
|
590
|
+
// LIFO; a throwing disposer is aggregated into a SuppressedError chain per spec.
|
|
591
|
+
// Symbol.dispose / Symbol.asyncDispose are present on every Node nub supports, but
|
|
592
|
+
// are defined defensively if absent since the classes depend on them. Feature-detect
|
|
593
|
+
// off `globalThis.DisposableStack` / `globalThis.AsyncDisposableStack` — a strict
|
|
594
|
+
// no-op where native.
|
|
595
|
+
function installDisposableStacks() {
|
|
596
|
+
if (typeof Symbol.dispose === "undefined") {
|
|
597
|
+
Object.defineProperty(Symbol, "dispose", { value: Symbol("Symbol.dispose") });
|
|
598
|
+
}
|
|
599
|
+
if (typeof Symbol.asyncDispose === "undefined") {
|
|
600
|
+
Object.defineProperty(Symbol, "asyncDispose", { value: Symbol("Symbol.asyncDispose") });
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
const defGlobal = (name, value) => {
|
|
604
|
+
Object.defineProperty(globalThis, name, {
|
|
605
|
+
value,
|
|
606
|
+
writable: true,
|
|
607
|
+
enumerable: false,
|
|
608
|
+
configurable: true,
|
|
609
|
+
});
|
|
610
|
+
};
|
|
611
|
+
|
|
612
|
+
// ── SuppressedError (TC39 Stage 4, the companion to the Stacks; native Node 24+,
|
|
613
|
+
// absent below) — the error a throwing disposer is aggregated into.
|
|
614
|
+
if (typeof globalThis.SuppressedError === "undefined") {
|
|
615
|
+
class SuppressedError extends Error {
|
|
616
|
+
constructor(error, suppressed, message) {
|
|
617
|
+
super(message);
|
|
618
|
+
// Spec (and native Node 24+) install .error/.suppressed as non-enumerable
|
|
619
|
+
// data props — plain assignment would make them enumerable, so Object.keys()
|
|
620
|
+
// / JSON.stringify() would leak them on the floor but not on native.
|
|
621
|
+
Object.defineProperty(this, "error", {
|
|
622
|
+
value: error,
|
|
623
|
+
writable: true,
|
|
624
|
+
enumerable: false,
|
|
625
|
+
configurable: true,
|
|
626
|
+
});
|
|
627
|
+
Object.defineProperty(this, "suppressed", {
|
|
628
|
+
value: suppressed,
|
|
629
|
+
writable: true,
|
|
630
|
+
enumerable: false,
|
|
631
|
+
configurable: true,
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
Object.defineProperty(SuppressedError.prototype, "name", {
|
|
636
|
+
value: "SuppressedError",
|
|
637
|
+
writable: true,
|
|
638
|
+
enumerable: false,
|
|
639
|
+
configurable: true,
|
|
640
|
+
});
|
|
641
|
+
defGlobal("SuppressedError", SuppressedError);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// new SuppressedError(error, suppressed): .error is the most-recent throw, the
|
|
645
|
+
// accumulated prior chain is nested under .suppressed — matching the spec's
|
|
646
|
+
// DisposeResources fold. Resolved AFTER the polyfill above so the floor gets a
|
|
647
|
+
// real SuppressedError instance, not a bare Error.
|
|
648
|
+
const Suppressed = globalThis.SuppressedError;
|
|
649
|
+
|
|
650
|
+
if (typeof globalThis.DisposableStack === "undefined") {
|
|
651
|
+
class DisposableStack {
|
|
652
|
+
#disposed = false;
|
|
653
|
+
#stack = [];
|
|
654
|
+
get disposed() {
|
|
655
|
+
return this.#disposed;
|
|
656
|
+
}
|
|
657
|
+
dispose() {
|
|
658
|
+
if (this.#disposed) return undefined;
|
|
659
|
+
this.#disposed = true;
|
|
660
|
+
let hasError = false;
|
|
661
|
+
let error;
|
|
662
|
+
const stack = this.#stack;
|
|
663
|
+
this.#stack = [];
|
|
664
|
+
for (let i = stack.length - 1; i >= 0; i--) {
|
|
665
|
+
try {
|
|
666
|
+
stack[i]();
|
|
667
|
+
} catch (e) {
|
|
668
|
+
if (hasError) error = new Suppressed(e, error);
|
|
669
|
+
else {
|
|
670
|
+
hasError = true;
|
|
671
|
+
error = e;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
if (hasError) throw error;
|
|
676
|
+
return undefined;
|
|
677
|
+
}
|
|
678
|
+
use(value) {
|
|
679
|
+
if (this.#disposed) throw new ReferenceError("DisposableStack already disposed");
|
|
680
|
+
if (value !== null && value !== undefined) {
|
|
681
|
+
const method = value[Symbol.dispose];
|
|
682
|
+
if (typeof method !== "function") throw new TypeError("value is not disposable");
|
|
683
|
+
this.#stack.push(() => method.call(value));
|
|
684
|
+
}
|
|
685
|
+
return value;
|
|
686
|
+
}
|
|
687
|
+
adopt(value, onDispose) {
|
|
688
|
+
if (this.#disposed) throw new ReferenceError("DisposableStack already disposed");
|
|
689
|
+
if (typeof onDispose !== "function") throw new TypeError("onDispose is not callable");
|
|
690
|
+
this.#stack.push(() => onDispose(value));
|
|
691
|
+
return value;
|
|
692
|
+
}
|
|
693
|
+
defer(onDispose) {
|
|
694
|
+
if (this.#disposed) throw new ReferenceError("DisposableStack already disposed");
|
|
695
|
+
if (typeof onDispose !== "function") throw new TypeError("onDispose is not callable");
|
|
696
|
+
this.#stack.push(() => onDispose());
|
|
697
|
+
return undefined;
|
|
698
|
+
}
|
|
699
|
+
move() {
|
|
700
|
+
if (this.#disposed) throw new ReferenceError("DisposableStack already disposed");
|
|
701
|
+
const next = new DisposableStack();
|
|
702
|
+
next.#stack = this.#stack;
|
|
703
|
+
this.#stack = [];
|
|
704
|
+
this.#disposed = true;
|
|
705
|
+
return next;
|
|
706
|
+
}
|
|
707
|
+
get [Symbol.toStringTag]() {
|
|
708
|
+
return "DisposableStack";
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
// Spec: @@dispose is the same function object as `dispose`.
|
|
712
|
+
Object.defineProperty(DisposableStack.prototype, Symbol.dispose, {
|
|
713
|
+
value: DisposableStack.prototype.dispose,
|
|
714
|
+
writable: true,
|
|
715
|
+
enumerable: false,
|
|
716
|
+
configurable: true,
|
|
717
|
+
});
|
|
718
|
+
defGlobal("DisposableStack", DisposableStack);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
if (typeof globalThis.AsyncDisposableStack === "undefined") {
|
|
722
|
+
class AsyncDisposableStack {
|
|
723
|
+
#disposed = false;
|
|
724
|
+
#stack = [];
|
|
725
|
+
get disposed() {
|
|
726
|
+
return this.#disposed;
|
|
727
|
+
}
|
|
728
|
+
async disposeAsync() {
|
|
729
|
+
if (this.#disposed) return undefined;
|
|
730
|
+
this.#disposed = true;
|
|
731
|
+
let hasError = false;
|
|
732
|
+
let error;
|
|
733
|
+
const stack = this.#stack;
|
|
734
|
+
this.#stack = [];
|
|
735
|
+
for (let i = stack.length - 1; i >= 0; i--) {
|
|
736
|
+
try {
|
|
737
|
+
await stack[i]();
|
|
738
|
+
} catch (e) {
|
|
739
|
+
if (hasError) error = new Suppressed(e, error);
|
|
740
|
+
else {
|
|
741
|
+
hasError = true;
|
|
742
|
+
error = e;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
if (hasError) throw error;
|
|
747
|
+
return undefined;
|
|
748
|
+
}
|
|
749
|
+
use(value) {
|
|
750
|
+
if (this.#disposed) throw new ReferenceError("AsyncDisposableStack already disposed");
|
|
751
|
+
if (value !== null && value !== undefined) {
|
|
752
|
+
let method = value[Symbol.asyncDispose];
|
|
753
|
+
if (method === undefined || method === null) {
|
|
754
|
+
const sync = value[Symbol.dispose];
|
|
755
|
+
if (typeof sync !== "function") {
|
|
756
|
+
throw new TypeError("value is not async disposable");
|
|
757
|
+
}
|
|
758
|
+
this.#stack.push(() => sync.call(value));
|
|
759
|
+
} else {
|
|
760
|
+
if (typeof method !== "function") {
|
|
761
|
+
throw new TypeError("value is not async disposable");
|
|
762
|
+
}
|
|
763
|
+
this.#stack.push(() => method.call(value));
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
return value;
|
|
767
|
+
}
|
|
768
|
+
adopt(value, onDispose) {
|
|
769
|
+
if (this.#disposed) throw new ReferenceError("AsyncDisposableStack already disposed");
|
|
770
|
+
if (typeof onDispose !== "function") throw new TypeError("onDispose is not callable");
|
|
771
|
+
this.#stack.push(() => onDispose(value));
|
|
772
|
+
return value;
|
|
773
|
+
}
|
|
774
|
+
defer(onDispose) {
|
|
775
|
+
if (this.#disposed) throw new ReferenceError("AsyncDisposableStack already disposed");
|
|
776
|
+
if (typeof onDispose !== "function") throw new TypeError("onDispose is not callable");
|
|
777
|
+
this.#stack.push(() => onDispose());
|
|
778
|
+
return undefined;
|
|
779
|
+
}
|
|
780
|
+
move() {
|
|
781
|
+
if (this.#disposed) throw new ReferenceError("AsyncDisposableStack already disposed");
|
|
782
|
+
const next = new AsyncDisposableStack();
|
|
783
|
+
next.#stack = this.#stack;
|
|
784
|
+
this.#stack = [];
|
|
785
|
+
this.#disposed = true;
|
|
786
|
+
return next;
|
|
787
|
+
}
|
|
788
|
+
get [Symbol.toStringTag]() {
|
|
789
|
+
return "AsyncDisposableStack";
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
Object.defineProperty(AsyncDisposableStack.prototype, Symbol.asyncDispose, {
|
|
793
|
+
value: AsyncDisposableStack.prototype.disposeAsync,
|
|
794
|
+
writable: true,
|
|
795
|
+
enumerable: false,
|
|
796
|
+
configurable: true,
|
|
797
|
+
});
|
|
798
|
+
defGlobal("AsyncDisposableStack", AsyncDisposableStack);
|
|
799
|
+
}
|
|
194
800
|
}
|
|
195
801
|
|
|
196
802
|
// Load the two ESM side-effect modules — Web Locks (navigator.locks) and the
|