@volter/tabnode 0.5.15 → 0.5.17

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.
@@ -3,6 +3,16 @@ import { Buffer as NodeBuffer } from '../buffer-module';
3
3
  /** The key the engine hangs a run's filesystem off its guest process. */
4
4
  export declare const kRunFilesystem: unique symbol;
5
5
  export declare function withFilesystem<T>(tree: VirtualFS, work: () => T): T;
6
+ /**
7
+ * Where a child's output goes when its stdio names a descriptor of the
8
+ * parent's (`stdio: ['ignore', fd, fd]` with `fd` from `openSync`): the file
9
+ * that descriptor has open, as the child's own duplicate of it, so it keeps
10
+ * writing after the parent closes its copy. Appending descriptors append;
11
+ * others write on from the position the parent's copy had. A descriptor that
12
+ * is a stream (a socket, a pipe) is written as a stream. Anything else is
13
+ * not a sink, and the child's output there goes nowhere, as `ignore` does.
14
+ */
15
+ export declare function descriptorWriter(fd: number): ((text: string) => void) | null;
6
16
  /** A request object `fs.js` hands an asynchronous call, answered on a tick. */
7
17
  interface FSReq {
8
18
  oncomplete?: (error: Error | null, ...rest: unknown[]) => void;
@@ -25,10 +25,23 @@ export declare class Zlib {
25
25
  /** Node asks a handle for these two after a `Z_STREAM_END`. */
26
26
  get ended(): boolean;
27
27
  }
28
+ declare class BrotliDecoderHandle {
29
+ #private;
30
+ readonly mode: number;
31
+ onerror: ((message: string, errno: number, code?: string) => void) | null;
32
+ constructor(mode: number);
33
+ init(_params: Uint32Array, writeState: Uint32Array, processCallback: ProcessCallback): boolean;
34
+ write(flush: number, inBuf: Uint8Array | null, inOff: number, inLen: number, outBuf: Uint8Array, outOff: number, outLen: number): this;
35
+ writeSync(flush: number, inBuf: Uint8Array | null, inOff: number, inLen: number, outBuf: Uint8Array, outOff: number, outLen: number): this;
36
+ params(): void;
37
+ reset(): void;
38
+ close(): void;
39
+ getAvailableOutput(): number;
40
+ }
28
41
  export declare const zlibBinding: {
29
42
  Zlib: typeof Zlib;
30
43
  BrotliEncoder: new (...args: unknown[]) => never;
31
- BrotliDecoder: new (...args: unknown[]) => never;
44
+ BrotliDecoder: typeof BrotliDecoderHandle;
32
45
  ZstdCompress: new (...args: unknown[]) => never;
33
46
  ZstdDecompress: new (...args: unknown[]) => never;
34
47
  /** `zlib.crc32`, which is zlib's own table-driven one. */
@@ -1,7 +1,3 @@
1
- /**
2
- * perf_hooks shim - Performance measurement APIs
3
- * Wraps browser Performance API
4
- */
5
1
  export declare const performance: Performance;
6
2
  export declare class PerformanceObserver {
7
3
  private callback;
@@ -291,6 +291,7 @@ export declare function createTlsModule(require?: (name: string) => any): {
291
291
  };
292
292
  createSecureContext: (_options?: unknown) => {};
293
293
  getCiphers: () => string[];
294
+ getCACertificates: (type?: string) => string[];
294
295
  DEFAULT_ECDH_CURVE: string;
295
296
  DEFAULT_MAX_VERSION: string;
296
297
  DEFAULT_MIN_VERSION: string;
@@ -583,6 +584,7 @@ declare const tls: {
583
584
  };
584
585
  createSecureContext: (_options?: unknown) => {};
585
586
  getCiphers: () => string[];
587
+ getCACertificates: (type?: string) => string[];
586
588
  DEFAULT_ECDH_CURVE: string;
587
589
  DEFAULT_MAX_VERSION: string;
588
590
  DEFAULT_MIN_VERSION: string;
@@ -868,7 +870,7 @@ export declare const TLSSocket: {
868
870
  write(chunk: unknown, encoding?: unknown, callback?: unknown): boolean;
869
871
  end(chunk?: unknown, encoding?: unknown, callback?: unknown): /*elided*/ any;
870
872
  writable: boolean;
871
- }, createSecureContext: (_options?: unknown) => {}, getCiphers: () => string[], DEFAULT_ECDH_CURVE: string, DEFAULT_MAX_VERSION: string, DEFAULT_MIN_VERSION: string, rootCertificates: string[];
873
+ }, createSecureContext: (_options?: unknown) => {}, getCiphers: () => string[], getCACertificates: (type?: string) => string[], DEFAULT_ECDH_CURVE: string, DEFAULT_MAX_VERSION: string, DEFAULT_MIN_VERSION: string, rootCertificates: string[];
872
874
  export type TLSSocket = InstanceType<typeof TLSSocket>;
873
875
  export type Server = InstanceType<typeof Server>;
874
876
  export default tls;
@@ -44,6 +44,8 @@ export declare function receiveMessageOnPort(port: MessagePort): {
44
44
  } | undefined;
45
45
  export declare const SHARE_ENV: unique symbol;
46
46
  export declare function markAsUntransferable(object: unknown): void;
47
+ export declare function markAsUncloneable(object: unknown): void;
48
+ export declare function isMarkedAsUncloneable(object: unknown): boolean;
47
49
  export declare function getEnvironmentData(key: unknown): unknown;
48
50
  export declare function setEnvironmentData(key: unknown, value: unknown): void;
49
51
  declare const _default: {
@@ -59,6 +61,8 @@ declare const _default: {
59
61
  receiveMessageOnPort: typeof receiveMessageOnPort;
60
62
  SHARE_ENV: symbol;
61
63
  markAsUntransferable: typeof markAsUntransferable;
64
+ markAsUncloneable: typeof markAsUncloneable;
65
+ isMarkedAsUncloneable: typeof isMarkedAsUncloneable;
62
66
  getEnvironmentData: typeof getEnvironmentData;
63
67
  setEnvironmentData: typeof setEnvironmentData;
64
68
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/tabnode",
3
- "version": "0.5.15",
3
+ "version": "0.5.17",
4
4
  "description": "Node's own library, in a browser tab.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -26,6 +26,14 @@ let __nodeLowering = false;
26
26
  const __cjsExports = () => __nodeLowering ? "$exports" : "exports";
27
27
  const __cjsModule = () => __nodeLowering ? "$module" : "module";
28
28
  const __cjsRequire = () => __nodeLowering ? "$require" : "require";
29
+ // The realm's own `Object` and `Symbol`, by names a module cannot declare over.
30
+ // Lowered code calls them before the module's own body has run -- its exports
31
+ // are defined at the top -- and a module that imports a binding called `Object`
32
+ // (typebox does, its object type) had the lowering's `Object.defineProperty`
33
+ // read that import in its dead zone. The dev server's lowering of a browser
34
+ // module keeps the plain names, which exist where that module runs.
35
+ const __cjsObject = () => __nodeLowering ? "__browserRuntimeObject" : "Object";
36
+ const __cjsSymbol = () => __nodeLowering ? "__browserRuntimeSymbol" : "Symbol";
29
37
  const __cjsDefault = (expression: string) => __nodeLowering ? "__browserRuntimeInteropDefault(" + expression + ")" : expression;
30
38
  // The helper lowered code calls by bare name. It is a name on the realm, so
31
39
  // it goes up when a guest first exists, not when this module is loaded.
@@ -33,8 +41,12 @@ forGuestRealm(() => {
33
41
  if ((globalThis as Record<string, unknown>).__browserRuntimeInteropDefault) return;
34
42
  takeFromHost(globalThis, '__browserRuntimeInteropDefault', (imported: any) => imported && imported.__esModule && "default" in imported ? imported.default : imported);
35
43
  });
44
+ forGuestRealm(() => {
45
+ if (!(globalThis as Record<string, unknown>).__browserRuntimeObject) takeFromHost(globalThis, '__browserRuntimeObject', Object);
46
+ if (!(globalThis as Record<string, unknown>).__browserRuntimeSymbol) takeFromHost(globalThis, '__browserRuntimeSymbol', Symbol);
47
+ });
36
48
  export const setNodeLowering = (value: boolean): void => { __nodeLowering = value; };
37
- export { __cjsExports };
49
+ export { __cjsExports, __cjsObject };
38
50
 
39
51
  /**
40
52
  * Whether a module is ESM is a fact of its syntax, not of its text. Asking
@@ -175,7 +187,7 @@ function __substrateLiveImportBindings(code: any) {
175
187
  // or in what it imports; an importer waits for it, as a module graph
176
188
  // evaluates in order. The marker becomes `await` when the loader runs
177
189
  // this body as an async function, and reads nothing otherwise.
178
- parts.push("/*__substrate_await__*/(" + namespace + " && " + namespace + "[Symbol.for(\"substrate.pending\")]);");
190
+ parts.push("/*__substrate_await__*/(" + namespace + " && " + namespace + "[" + __cjsSymbol() + ".for(\"substrate.pending\")]);");
179
191
  }
180
192
  for (const imported of declaration.specifiers) {
181
193
  const read = imported.type === "ImportDefaultSpecifier" ? __cjsDefault(namespace)
@@ -210,7 +222,10 @@ function __substrateLiveImportBindings(code: any) {
210
222
  if (typeof node.type !== "string") return;
211
223
  switch (node.type) {
212
224
  case "ImportDeclaration": case "ImportSpecifier": case "ImportDefaultSpecifier":
213
- case "ImportNamespaceSpecifier": case "ExportSpecifier": case "MetaProperty":
225
+ // `export * as X from "…"` holds a name and a source, no reference: an
226
+ // imported binding also called X was rewritten into the exported name,
227
+ // and typebox's `export * as Guard` became `__substrateImport1`.
228
+ case "ImportNamespaceSpecifier": case "ExportSpecifier": case "ExportAllDeclaration": case "MetaProperty":
214
229
  case "BreakStatement": case "ContinueStatement": case "PrivateIdentifier":
215
230
  return;
216
231
  case "Identifier":
@@ -368,7 +383,7 @@ function transformEsmToCjsAst(code: string): string {
368
383
  // for any import (the marker becomes `await` in an async body).
369
384
  sideEffectImports += 1;
370
385
  const namespace = `__substrateImportSide${sideEffectImports}`;
371
- replacements.push([node.start, node.end, `const ${namespace} = ${__cjsRequire()}(${JSON.stringify(source)}); /*__substrate_await__*/(${namespace} && ${namespace}[Symbol.for("substrate.pending")]);`]);
386
+ replacements.push([node.start, node.end, `const ${namespace} = ${__cjsRequire()}(${JSON.stringify(source)}); /*__substrate_await__*/(${namespace} && ${namespace}[${__cjsSymbol()}.for("substrate.pending")]);`]);
372
387
  } else {
373
388
  const defaultSpec = specs.find((s: any) => s.type === 'ImportDefaultSpecifier');
374
389
  const nsSpec = specs.find((s: any) => s.type === 'ImportNamespaceSpecifier');
@@ -404,7 +419,7 @@ function transformEsmToCjsAst(code: string): string {
404
419
  // `module.exports = X`, which threw away every named export defined
405
420
  // before it (readdirp exports `readdirp` by name and as the default;
406
421
  // chokidar imports the name and found the module was the function).
407
- const defineDefault = (value: string): string => `Object.defineProperty(${__cjsExports()}, "default", { enumerable: true, configurable: true, get: function () { return ${value}; } });`;
422
+ const defineDefault = (value: string): string => `${__cjsObject()}.defineProperty(${__cjsExports()}, "default", { enumerable: true, configurable: true, get: function () { return ${value}; } });`;
408
423
  if (decl.type === 'FunctionDeclaration') {
409
424
  const funcCode = code.slice(decl.start, node.end);
410
425
  replacements.push([node.start, node.end, decl.id ? `${funcCode}\n${defineDefault(decl.id.name)}` : `${defineDefault(`(${funcCode})`)}`]);
@@ -423,11 +438,11 @@ function transformEsmToCjsAst(code: string): string {
423
438
  const name = decl.id.name;
424
439
  const funcCode = code.slice(decl.start, node.end);
425
440
  replacements.push([node.start, node.end, funcCode]);
426
- hoisted.push(`Object.defineProperty(${__cjsExports()}, "${name}", { enumerable: true, get: function () { return ${name}; } });`);
441
+ hoisted.push(`${__cjsObject()}.defineProperty(${__cjsExports()}, "${name}", { enumerable: true, get: function () { return ${name}; } });`);
427
442
  } else if (decl.type === 'ClassDeclaration') {
428
443
  const name = decl.id.name;
429
444
  const classCode = code.slice(decl.start, node.end);
430
- replacements.push([node.start, node.end, `${classCode}\nObject.defineProperty(${__cjsExports()}, "${name}", { enumerable: true, get: function () { return ${name}; } });`]);
445
+ replacements.push([node.start, node.end, `${classCode}\n${__cjsObject()}.defineProperty(${__cjsExports()}, "${name}", { enumerable: true, get: function () { return ${name}; } });`]);
431
446
  } else if (decl.type === 'VariableDeclaration') {
432
447
  // The declaration stays as written, so the module keeps a binding of
433
448
  // its own; what follows is an export that reads that binding when
@@ -446,7 +461,7 @@ function transformEsmToCjsAst(code: string): string {
446
461
  else if (pattern.type === 'RestElement') collect(pattern.argument);
447
462
  };
448
463
  for (const declarator of decl.declarations) collect(declarator.id);
449
- for (const name of names) parts.push(`Object.defineProperty(${__cjsExports()}, "${name}", { enumerable: true, get: function () { return ${name}; } });`);
464
+ for (const name of names) parts.push(`${__cjsObject()}.defineProperty(${__cjsExports()}, "${name}", { enumerable: true, get: function () { return ${name}; } });`);
450
465
  replacements.push([node.start, node.end, parts.join('\n')]);
451
466
  }
452
467
  } else if (node.source) {
@@ -462,9 +477,9 @@ function transformEsmToCjsAst(code: string): string {
462
477
  const tmpVar = `__reexport_${node.start}`;
463
478
  parts.push(`const ${tmpVar} = ${__cjsRequire()}(${JSON.stringify(source)})`);
464
479
  // The re-exported module may still be settling; wait for it as for an import.
465
- parts.push(`/*__substrate_await__*/(${tmpVar} && ${tmpVar}[Symbol.for("substrate.pending")])`);
480
+ parts.push(`/*__substrate_await__*/(${tmpVar} && ${tmpVar}[${__cjsSymbol()}.for("substrate.pending")])`);
466
481
  for (const spec of node.specifiers) {
467
- parts.push(`Object.defineProperty(${__cjsExports()}, "${spec.exported.name}", { enumerable: true, configurable: true, get: function () { return ${tmpVar}.${spec.local.name}; } })`);
482
+ parts.push(`${__cjsObject()}.defineProperty(${__cjsExports()}, "${spec.exported.name}", { enumerable: true, configurable: true, get: function () { return ${tmpVar}.${spec.local.name}; } })`);
468
483
  }
469
484
  replacements.push([node.start, node.end, parts.join(';\n')]);
470
485
  } else {
@@ -472,7 +487,7 @@ function transformEsmToCjsAst(code: string): string {
472
487
  // an explicit export that replaces a star's getter of the same name.
473
488
  const parts: string[] = [];
474
489
  for (const spec of node.specifiers) {
475
- parts.push(`Object.defineProperty(${__cjsExports()}, "${spec.exported.name}", { enumerable: true, configurable: true, get: function () { return ${spec.local.name}; } })`);
490
+ parts.push(`${__cjsObject()}.defineProperty(${__cjsExports()}, "${spec.exported.name}", { enumerable: true, configurable: true, get: function () { return ${spec.local.name}; } })`);
476
491
  }
477
492
  replacements.push([node.start, node.end, parts.join(';\n')]);
478
493
  }
@@ -485,7 +500,7 @@ function transformEsmToCjsAst(code: string): string {
485
500
  const source = node.source.value;
486
501
  const name = node.exported.type === 'Identifier' ? node.exported.name : node.exported.value;
487
502
  const tmpVar = `__reexport_${node.start}`;
488
- replacements.push([node.start, node.end, `const ${tmpVar} = ${__cjsRequire()}(${JSON.stringify(source)}); /*__substrate_await__*/(${tmpVar} && ${tmpVar}[Symbol.for("substrate.pending")]); Object.defineProperty(${__cjsExports()}, ${JSON.stringify(name)}, { enumerable: true, configurable: true, get: function () { return ${tmpVar}; } })`]);
503
+ replacements.push([node.start, node.end, `const ${tmpVar} = ${__cjsRequire()}(${JSON.stringify(source)}); /*__substrate_await__*/(${tmpVar} && ${tmpVar}[${__cjsSymbol()}.for("substrate.pending")]); ${__cjsObject()}.defineProperty(${__cjsExports()}, ${JSON.stringify(name)}, { enumerable: true, configurable: true, get: function () { return ${tmpVar}; } })`]);
489
504
  } else if (node.type === 'ExportAllDeclaration') {
490
505
  // export * from './helpers'
491
506
  const source = node.source.value;
@@ -498,7 +513,7 @@ function transformEsmToCjsAst(code: string): string {
498
513
  // module does not already export; a local export shadows a star's, as the
499
514
  // language says.
500
515
  const tmpVar = `__reexport_${node.start}`;
501
- replacements.push([node.start, node.end, `const ${tmpVar} = ${__cjsRequire()}(${JSON.stringify(source)}); /*__substrate_await__*/(${tmpVar} && ${tmpVar}[Symbol.for("substrate.pending")]); (function (__from, __into) { for (const __key of Object.keys(__from)) { if (__key === "default" || __key === "__esModule" || Object.prototype.hasOwnProperty.call(__into, __key)) continue; Object.defineProperty(__into, __key, { enumerable: true, configurable: true, get: function () { return __from[__key]; } }); } })(${tmpVar}, ${__cjsExports()})`]);
516
+ replacements.push([node.start, node.end, `const ${tmpVar} = ${__cjsRequire()}(${JSON.stringify(source)}); /*__substrate_await__*/(${tmpVar} && ${tmpVar}[${__cjsSymbol()}.for("substrate.pending")]); (function (__from, __into) { for (const __key of ${__cjsObject()}.keys(__from)) { if (__key === "default" || __key === "__esModule" || ${__cjsObject()}.prototype.hasOwnProperty.call(__into, __key)) continue; ${__cjsObject()}.defineProperty(__into, __key, { enumerable: true, configurable: true, get: function () { return __from[__key]; } }); } })(${tmpVar}, ${__cjsExports()})`]);
502
517
  }
503
518
  }
504
519
 
@@ -168,6 +168,40 @@ function fileFor(fd: number): OpenFile {
168
168
  return file;
169
169
  }
170
170
 
171
+ /**
172
+ * Where a child's output goes when its stdio names a descriptor of the
173
+ * parent's (`stdio: ['ignore', fd, fd]` with `fd` from `openSync`): the file
174
+ * that descriptor has open, as the child's own duplicate of it, so it keeps
175
+ * writing after the parent closes its copy. Appending descriptors append;
176
+ * others write on from the position the parent's copy had. A descriptor that
177
+ * is a stream (a socket, a pipe) is written as a stream. Anything else is
178
+ * not a sink, and the child's output there goes nowhere, as `ignore` does.
179
+ */
180
+ export function descriptorWriter(fd: number): ((text: string) => void) | null {
181
+ const stream = handleForFd(fd);
182
+ if (stream instanceof LibuvStreamWrap) {
183
+ return (text) => { try { stream.writeBuffer(new WriteWrap(), new TextEncoder().encode(text)); } catch { /* a closed stream drops the child's output, as a closed pipe does */ } };
184
+ }
185
+ const file = openFiles.get(fd);
186
+ if (!file || file.directory || (file.flags & 3) === 0) return null;
187
+ const { tree, path } = file;
188
+ const append = (file.flags & flagBits().append) !== 0;
189
+ let position = file.position;
190
+ return (text) => {
191
+ const bytes = new TextEncoder().encode(text);
192
+ // the parent's copy, while it is open, holds the file's bytes as it last wrote them
193
+ const shared = openFiles.get(fd) === file ? file : null;
194
+ const existing = shared?.cached ?? (tree.existsSync(path) ? tree.readFileSync(path) as Uint8Array : new Uint8Array(0));
195
+ const at = append ? existing.length : position;
196
+ const next = new Uint8Array(Math.max(existing.length, at + bytes.length));
197
+ next.set(existing, 0);
198
+ next.set(bytes, at);
199
+ tree.writeFileSync(path, next);
200
+ position = at + bytes.length;
201
+ if (shared) shared.cached = next;
202
+ };
203
+ }
204
+
171
205
  /** Node's stat array: eighteen numbers, with each time a second and a nanosecond. */
172
206
  function statArray(stats: VfsStats, bigint: boolean, path?: string): Float64Array | BigInt64Array {
173
207
  const mtime = stats.mtime instanceof Date ? stats.mtime.getTime() : Number(stats.mtimeMs ?? 0);
@@ -29,6 +29,7 @@ import {
29
29
  } from './handles';
30
30
  import { __runFor, mintPid, type ProcessToken } from '../../process-tokens';
31
31
  import { handleForFd } from './fds';
32
+ import { descriptorWriter } from './fs';
32
33
  import { TTY, type TerminalState } from './tty_wrap';
33
34
 
34
35
  /** One entry of Node's `options.stdio`, as `getValidStdio` builds it. */
@@ -178,7 +179,8 @@ function spawningDirectory(token: ProcessToken | null): string | undefined {
178
179
 
179
180
  /** Where an `inherit` entry's bytes go: the spawning program's own stream. */
180
181
  function inheritedWriter(fd: number, token: ProcessToken | null): ((text: string) => void) | null {
181
- if (fd !== 1 && fd !== 2) return null;
182
+ // a descriptor of the parent's own (a log file it opened) is written as the file
183
+ if (fd !== 1 && fd !== 2) return descriptorWriter(fd);
182
184
  // Inherit the descriptor's original sink, not a guest replacement of
183
185
  // process.stderr.write (a child commonly redirects its console over IPC).
184
186
  if (token !== null) {
@@ -28,6 +28,9 @@
28
28
  * which this product does not do. The row in BUILTINS.md says the same.
29
29
  */
30
30
  import { zstream, inflateModule, deflateModule, zlibConstants } from './zlib-pako';
31
+ // @ts-expect-error `brotli` ships no types; its decoder takes and returns bytes.
32
+ import brotliDecompressUntyped from 'brotli/decompress';
33
+ const brotliDecompress = brotliDecompressUntyped as (input: Uint8Array) => Uint8Array;
31
34
 
32
35
  /** Node's `zlib` modes, by the numbers `zlib.js` passes. */
33
36
  const DEFLATE = 1;
@@ -236,9 +239,83 @@ function absentCodec(name: string, reason: string): new (...args: unknown[]) =>
236
239
  } as unknown as new (...args: unknown[]) => never;
237
240
  }
238
241
 
242
+ /**
243
+ * `BrotliDecoder`, over the engine's own Brotli decoder (`brotli`'s, pure
244
+ * JavaScript). That decoder takes a whole stream, so this handle collects
245
+ * the input it is given and decodes when `zlib.js` sends the finishing
246
+ * flush, then hands the output back as the caller's buffers take it: a
247
+ * stream is inflated, it is not inflated incrementally. A server reading a
248
+ * pre-compressed file through `createBrotliDecompress` is the case it serves.
249
+ */
250
+ const BROTLI_OPERATION_FINISH = 2;
251
+ class BrotliDecoderHandle {
252
+ #writeState: Uint32Array | null = null;
253
+ #callback: ProcessCallback | null = null;
254
+ #input: Uint8Array[] = [];
255
+ #output: Uint8Array | null = null;
256
+ #outputAt = 0;
257
+ onerror: ((message: string, errno: number, code?: string) => void) | null = null;
258
+
259
+ constructor(public readonly mode: number) {}
260
+
261
+ init(_params: Uint32Array, writeState: Uint32Array, processCallback: ProcessCallback): boolean {
262
+ this.#writeState = writeState;
263
+ this.#callback = processCallback;
264
+ return true;
265
+ }
266
+
267
+ #run(flush: number, inBuf: Uint8Array | null, inOff: number, inLen: number, outBuf: Uint8Array, outOff: number, outLen: number): void {
268
+ if (inBuf && inLen > 0) this.#input.push(inBuf.slice(inOff, inOff + inLen));
269
+ if (flush === BROTLI_OPERATION_FINISH && this.#output === null) {
270
+ const total = this.#input.reduce((sum, part) => sum + part.byteLength, 0);
271
+ const whole = new Uint8Array(total);
272
+ let at = 0;
273
+ for (const part of this.#input) { whole.set(part, at); at += part.byteLength; }
274
+ this.#input = [];
275
+ try {
276
+ this.#output = brotliDecompress(whole);
277
+ } catch (error) {
278
+ this.onerror?.(`Decompression failed: ${error instanceof Error ? error.message : String(error)}`, -1, 'ERR_BROTLI_DECOMPRESSION_FAILED');
279
+ return;
280
+ }
281
+ this.#outputAt = 0;
282
+ }
283
+ let written = 0;
284
+ if (this.#output) {
285
+ written = Math.min(outLen, this.#output.byteLength - this.#outputAt);
286
+ outBuf.set(this.#output.subarray(this.#outputAt, this.#outputAt + written), outOff);
287
+ this.#outputAt += written;
288
+ }
289
+ if (this.#writeState) {
290
+ this.#writeState[0] = outLen - written;
291
+ this.#writeState[1] = 0;
292
+ }
293
+ }
294
+
295
+ write(flush: number, inBuf: Uint8Array | null, inOff: number, inLen: number, outBuf: Uint8Array, outOff: number, outLen: number): this {
296
+ const tick = (globalThis as { process?: { nextTick?: (fn: () => void) => void } }).process?.nextTick
297
+ ?? ((fn: () => void) => { queueMicrotask(fn); });
298
+ tick(() => {
299
+ this.#run(flush, inBuf, inOff, inLen, outBuf, outOff, outLen);
300
+ this.#callback?.();
301
+ });
302
+ return this;
303
+ }
304
+
305
+ writeSync(flush: number, inBuf: Uint8Array | null, inOff: number, inLen: number, outBuf: Uint8Array, outOff: number, outLen: number): this {
306
+ this.#run(flush, inBuf, inOff, inLen, outBuf, outOff, outLen);
307
+ return this;
308
+ }
309
+
310
+ params(): void {}
311
+ reset(): void { this.#input = []; this.#output = null; this.#outputAt = 0; }
312
+ close(): void { this.reset(); this.#callback = null; }
313
+ getAvailableOutput(): number { return this.#output ? this.#output.byteLength - this.#outputAt : 0; }
314
+ }
315
+
239
316
  const BROTLI_REASON =
240
- 'Brotli is a codec of its own, not zlib; the engine has a wasm build of it as a dependency ' +
241
- 'but no binding for Node\'s Brotli handle yet. `zlib.gzip`, `deflate` and `inflate` are Node\'s own here.';
317
+ 'Brotli is a codec of its own, not zlib; the engine decodes it (`brotli`, pure JavaScript) but has no ' +
318
+ 'encoder for Node\'s Brotli handle yet. `zlib.gzip`, `deflate` and `inflate` are Node\'s own here.';
242
319
  const ZSTD_REASON =
243
320
  'Zstandard is a codec of its own and this engine has no implementation of it at all. ' +
244
321
  '`zlib.gzip`, `deflate` and `inflate` are Node\'s own here.';
@@ -246,7 +323,7 @@ const ZSTD_REASON =
246
323
  export const zlibBinding = {
247
324
  Zlib,
248
325
  BrotliEncoder: absentCodec('Brotli compression', BROTLI_REASON),
249
- BrotliDecoder: absentCodec('Brotli decompression', BROTLI_REASON),
326
+ BrotliDecoder: BrotliDecoderHandle,
250
327
  ZstdCompress: absentCodec('Zstandard compression', ZSTD_REASON),
251
328
  ZstdDecompress: absentCodec('Zstandard decompression', ZSTD_REASON),
252
329
  /** `zlib.crc32`, which is zlib's own table-driven one. */
@@ -219,8 +219,9 @@ export const internalEventTarget = {
219
219
  };
220
220
 
221
221
  /**
222
- * `internal/options`: the command-line options Node's `net` reads. The engine
223
- * takes no command line of its own, so each is Node's own default.
222
+ * `internal/options`: the command-line options Node's `net`, `http` and
223
+ * `util` read. The engine takes no command line of its own, so each is
224
+ * Node's own default.
224
225
  */
225
226
  const optionValues: Record<string, unknown> = {
226
227
  '--network-family-autoselection': true,
@@ -231,6 +232,9 @@ const optionValues: Record<string, unknown> = {
231
232
  '--pending-deprecation': false,
232
233
  '--report-uncaught-exception': false,
233
234
  '--experimental-print-required-tla': false,
235
+ '--max-http-header-size': 16 * 1024,
236
+ '--insecure-http-parser': false,
237
+ '--enable-source-maps': false,
234
238
  };
235
239
 
236
240
  export const internalOptions = {
package/src/runtime.ts CHANGED
@@ -66,7 +66,7 @@ import * as domainShim from './shims/domain';
66
66
  import { createWasiModule, type WasiHostFs, type WasiModule } from './shims/wasi';
67
67
 
68
68
  import { resolve as resolveExports, imports as resolveImports } from 'resolve.exports';
69
- import { transformEsmToCjsSimple, setNodeLowering, __cjsExports, __substrateHasEsmSyntax } from './code-transforms';
69
+ import { transformEsmToCjsSimple, setNodeLowering, __cjsExports, __cjsObject, __substrateHasEsmSyntax } from './code-transforms';
70
70
  import { applySourceEdits } from './source-edits';
71
71
  import * as acorn from 'acorn';
72
72
 
@@ -263,7 +263,12 @@ function __substrateGuestGlobal(process: Process): Record<string, unknown> {
263
263
  if (key === "fetch") { fetch = value; return true; }
264
264
  if (key === "Buffer") { buffer = value; return true; }
265
265
  if (shadowed(target, key)) return Reflect.set(target, key, value, target);
266
- return Reflect.set(host, key, value, host);
266
+ if (Reflect.set(host, key, value, host)) return true;
267
+ // A global the host holds read-only (a confined realm's \`WebSocket\`) is
268
+ // still the guest's to replace, as Node's globals are: undici's
269
+ // \`install()\` assigns \`globalThis.WebSocket\` and Pi died on the refusal.
270
+ // The value becomes the guest's own binding; the host's is untouched.
271
+ return Reflect.defineProperty(target, key, { value, writable: true, configurable: true, enumerable: true });
267
272
  },
268
273
  has(target, key) { return isLocalGlobal(key) ? Reflect.has(target, key) : key === "global" || shadowed(target, key) || key in host; },
269
274
  ownKeys(target) {
@@ -448,7 +453,7 @@ function transformEsmToCjsAst(code: string, filename: string): string {
448
453
  transformed = transformEsmToCjsSimple(transformed);
449
454
 
450
455
  if (hasExportDecl) {
451
- transformed = `Object.defineProperty(${__cjsExports()}, "__esModule", { value: true });\n` + transformed;
456
+ transformed = `${__cjsObject()}.defineProperty(${__cjsExports()}, "__esModule", { value: true });\n` + transformed;
452
457
  }
453
458
  }
454
459
 
@@ -476,7 +481,7 @@ function transformEsmToCjsRegexFallback(code: string, filename: string): string
476
481
  if (hasImport || hasExport) {
477
482
  transformed = transformEsmToCjsSimple(transformed);
478
483
  if (hasExport) {
479
- transformed = `Object.defineProperty(${__cjsExports()}, "__esModule", { value: true });\n` + transformed;
484
+ transformed = `${__cjsObject()}.defineProperty(${__cjsExports()}, "__esModule", { value: true });\n` + transformed;
480
485
  }
481
486
  }
482
487
 
@@ -2680,6 +2685,56 @@ forGuestRealm(() => {
2680
2685
  }
2681
2686
  });
2682
2687
 
2688
+ forGuestRealm(() => {
2689
+ // Node's `stream.finished()` on a web stream waits on a promise Node's own
2690
+ // web streams carry (`nodejs.webstream.isClosedPromise`), settled when the
2691
+ // stream closes or errors, and undici's fetch finalizes every response that
2692
+ // way. The worker's streams are the browser's and carry none, so a stream a
2693
+ // guest constructs is given one, settled by its own controller. The
2694
+ // constructor stays the browser's (a proxy of it), so a stream the platform
2695
+ // made is still `instanceof ReadableStream`.
2696
+ const kIsClosedPromise = Symbol.for('nodejs.webstream.isClosedPromise');
2697
+ const Native = globalThis.ReadableStream;
2698
+ if (typeof Native !== 'function' || (Native as unknown as { __substrateClosedPromise?: boolean }).__substrateClosedPromise) return;
2699
+ type Source = { start?: (controller: unknown) => unknown; pull?: (controller: unknown) => unknown; cancel?: (reason: unknown) => unknown };
2700
+ const Tracked: typeof ReadableStream = new Proxy(Native, {
2701
+ construct(target, args, newTarget): object {
2702
+ let resolve!: () => void;
2703
+ let reject!: (error: unknown) => void;
2704
+ const promise = new Promise<void>((yes, no) => { resolve = yes; reject = no; });
2705
+ promise.then(undefined, () => {});
2706
+ const source = args[0] as Source | null | undefined;
2707
+ const track = (controller: { close?: (...rest: unknown[]) => unknown; error?: (...rest: unknown[]) => unknown }): void => {
2708
+ const close = controller.close;
2709
+ const error = controller.error;
2710
+ if (typeof close === 'function') controller.close = function (this: unknown, ...rest: unknown[]) { const result = close.apply(this, rest); resolve(); return result; };
2711
+ if (typeof error === 'function') controller.error = function (this: unknown, ...rest: unknown[]) { reject(rest[0]); return error.apply(this, rest); };
2712
+ };
2713
+ const failed = (cause: unknown): never => { reject(cause); throw cause; };
2714
+ const wrapped: Source = Object.create(source ?? null);
2715
+ wrapped.start = (controller) => {
2716
+ track(controller as Parameters<typeof track>[0]);
2717
+ try { return source?.start?.call(source, controller); } catch (cause) { return failed(cause); }
2718
+ };
2719
+ if (typeof source?.pull === 'function') {
2720
+ const pull = source.pull;
2721
+ wrapped.pull = (controller) => {
2722
+ try { return Promise.resolve(pull.call(source, controller)).catch(failed); } catch (cause) { return failed(cause); }
2723
+ };
2724
+ }
2725
+ wrapped.cancel = (reason) => {
2726
+ resolve();
2727
+ return source?.cancel?.call(source, reason);
2728
+ };
2729
+ const stream: object = Reflect.construct(target, [wrapped, ...args.slice(1)], newTarget === Tracked ? target : newTarget);
2730
+ Object.defineProperty(stream, kIsClosedPromise, { value: { promise, resolve, reject }, configurable: true });
2731
+ return stream;
2732
+ },
2733
+ });
2734
+ Object.defineProperty(Tracked, '__substrateClosedPromise', { value: true });
2735
+ takeFromHost(globalThis, 'ReadableStream', Tracked);
2736
+ });
2737
+
2683
2738
  // Polyfill Error.captureStackTrace/prepareStackTrace for Safari/WebKit
2684
2739
  // (V8-specific API used by Express's depd and other npm packages)
2685
2740
  forGuestRealm(__substrateStackTracePolyfill);