@volter/tabnode 0.4.0 → 0.5.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/dist/__sw__.js +259 -5
- package/dist/assets/{runtime-worker-DoV8lqBn.js → runtime-worker-B9buriZs.js} +4021 -2961
- package/dist/fetch-transport.d.ts +20 -0
- package/dist/guest-timers.d.ts +15 -0
- package/dist/http-client-transport.d.ts +19 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.mjs +5204 -3057
- package/dist/native-stream-binding.d.ts +57 -0
- package/dist/native-stream-owner.d.ts +121 -0
- package/dist/node-lib/binding/duplex_stream.d.ts +26 -0
- package/dist/node-lib/binding/fds.d.ts +8 -0
- package/dist/node-lib/binding/fs.d.ts +311 -78
- package/dist/node-lib/binding/misc.d.ts +1 -0
- package/dist/node-lib/binding/pipe_wrap.d.ts +4 -4
- package/dist/node-lib/binding/process_wrap.d.ts +13 -2
- package/dist/node-lib/binding/stream_wrap.d.ts +27 -18
- package/dist/node-lib/binding/tcp_wrap.d.ts +0 -1
- package/dist/node-lib/binding/tty_wrap.d.ts +43 -2
- package/dist/node-lib/binding/util.d.ts +83 -0
- package/dist/node-lib/binding/uv.d.ts +4 -0
- package/dist/node-lib/fs-module.d.ts +1 -0
- package/dist/node-lib/internals/buffer-and-streams.d.ts +30 -17
- package/dist/node-lib/internals/index.d.ts +1 -1
- package/dist/node-lib/load.d.ts +17 -3
- package/dist/node-lib/module-hooks.d.ts +1 -0
- package/dist/node-lib/public-modules.d.ts +1 -1
- package/dist/node-lib/timers.d.ts +38 -0
- package/dist/node-process-host.d.ts +41 -0
- package/dist/process-registry.d.ts +39 -0
- package/dist/process-tokens.d.ts +16 -0
- package/dist/promise-ownership.d.ts +3 -0
- package/dist/runtime.d.ts +10 -9
- package/dist/shims/child_process.d.ts +2 -0
- package/dist/shims/crypto.d.ts +660 -119
- package/dist/shims/dns.d.ts +76 -2
- package/dist/shims/process.d.ts +1 -0
- package/dist/shims/string_decoder.d.ts +19 -3
- package/dist/shims/tls.d.ts +853 -31
- package/dist/source-edits.d.ts +7 -0
- package/package.json +1 -1
- package/src/code-transforms.ts +2 -3
- package/src/fetch-transport.ts +98 -0
- package/src/guest-timers.ts +89 -0
- package/src/http-client-transport.ts +169 -0
- package/src/index.ts +12 -0
- package/src/native-stream-binding.ts +393 -0
- package/src/native-stream-owner.ts +317 -0
- package/src/node-lib/binding/duplex_stream.ts +95 -0
- package/src/node-lib/binding/fds.ts +18 -1
- package/src/node-lib/binding/fs.ts +45 -17
- package/src/node-lib/binding/misc.ts +2 -0
- package/src/node-lib/binding/pipe_wrap.ts +35 -12
- package/src/node-lib/binding/process_wrap.ts +72 -18
- package/src/node-lib/binding/stream_wrap.ts +196 -72
- package/src/node-lib/binding/tcp_wrap.ts +43 -8
- package/src/node-lib/binding/tty_wrap.ts +135 -8
- package/src/node-lib/binding/util.ts +24 -15
- package/src/node-lib/binding/uv.ts +4 -0
- package/src/node-lib/fs-module.ts +2 -18
- package/src/node-lib/internal/navigator.js +149 -0
- package/src/node-lib/internals/buffer-and-streams.ts +50 -11
- package/src/node-lib/internals/events-util.ts +5 -0
- package/src/node-lib/internals/index.ts +12 -2
- package/src/node-lib/load.ts +170 -62
- package/src/node-lib/module-hooks.ts +5 -2
- package/src/node-lib/public-modules.ts +12 -48
- package/src/node-lib/sources.ts +4 -0
- package/src/node-lib/timers.ts +93 -0
- package/src/node-process-host.ts +124 -0
- package/src/process-registry.ts +144 -0
- package/src/process-tokens.ts +60 -6
- package/src/promise-ownership.ts +28 -0
- package/src/runtime.ts +90 -214
- package/src/shims/child_process.ts +313 -80
- package/src/shims/crypto.ts +115 -100
- package/src/shims/dns.ts +17 -1
- package/src/shims/process.ts +33 -11
- package/src/shims/string_decoder.ts +10 -6
- package/src/shims/tls.ts +49 -19
- package/src/source-edits.ts +20 -0
package/src/node-lib/load.ts
CHANGED
|
@@ -29,6 +29,8 @@ import { nodeLibBinding } from './binding';
|
|
|
29
29
|
import { nodeLibPublic } from './public-modules';
|
|
30
30
|
import { setLibRequire } from './require-hook';
|
|
31
31
|
import { NODE_LTS_VERSION, nodeVersions } from './node-versions';
|
|
32
|
+
import { createUtilBinding } from './binding/util';
|
|
33
|
+
import { createFsBindings } from './binding/fs';
|
|
32
34
|
|
|
33
35
|
/**
|
|
34
36
|
* The realm's built-ins, captured as Node captures them: before a program can
|
|
@@ -206,9 +208,16 @@ export function loadNodeLib(name: string): unknown {
|
|
|
206
208
|
* Spread of a lazy proxy copied nothing; `Object.getOwnPropertyNames` reads
|
|
207
209
|
* the proxy's ownKeys trap, which is the module's keys.
|
|
208
210
|
*/
|
|
209
|
-
|
|
211
|
+
type BuiltinNamespace = { cjs: object; ns: Record<string, unknown> };
|
|
212
|
+
const builtinNamespaces = new WeakMap<object, BuiltinNamespace[]>();
|
|
213
|
+
const hostNamespaceOwner = {};
|
|
214
|
+
function namespacesFor(owner: object): BuiltinNamespace[] {
|
|
215
|
+
let held = builtinNamespaces.get(owner);
|
|
216
|
+
if (!held) { held = []; builtinNamespaces.set(owner, held); }
|
|
217
|
+
return held;
|
|
218
|
+
}
|
|
210
219
|
|
|
211
|
-
export function esmNamespaceOf(mod: unknown): Record<string, unknown> {
|
|
220
|
+
export function esmNamespaceOf(mod: unknown, owner: object = hostNamespaceOwner): Record<string, unknown> {
|
|
212
221
|
const ns: Record<string, unknown> = { default: mod };
|
|
213
222
|
if (mod !== null && (typeof mod === 'object' || typeof mod === 'function')) {
|
|
214
223
|
const cjs = mod as object;
|
|
@@ -221,14 +230,14 @@ export function esmNamespaceOf(mod: unknown): Record<string, unknown> {
|
|
|
221
230
|
set(value: unknown) { (cjs as Record<string, unknown>)[key] = value; },
|
|
222
231
|
});
|
|
223
232
|
}
|
|
224
|
-
|
|
233
|
+
namespacesFor(owner).push({ cjs, ns });
|
|
225
234
|
}
|
|
226
235
|
return ns;
|
|
227
236
|
}
|
|
228
237
|
|
|
229
238
|
/** Node's `module.syncBuiltinESMExports`: newly assigned CJS keys appear on the namespace. */
|
|
230
|
-
export function syncBuiltinESMExports(): void {
|
|
231
|
-
for (const { cjs, ns } of
|
|
239
|
+
export function syncBuiltinESMExports(owner: object = hostNamespaceOwner): void {
|
|
240
|
+
for (const { cjs, ns } of namespacesFor(owner)) {
|
|
232
241
|
for (const key of Object.getOwnPropertyNames(cjs)) {
|
|
233
242
|
if (key === 'default' || Object.prototype.hasOwnProperty.call(ns, key)) continue;
|
|
234
243
|
Object.defineProperty(ns, key, {
|
|
@@ -248,35 +257,144 @@ export function loadNodeLibInstance(name: string): unknown {
|
|
|
248
257
|
}
|
|
249
258
|
|
|
250
259
|
/**
|
|
251
|
-
*
|
|
252
|
-
*
|
|
253
|
-
*
|
|
254
|
-
* one realm, and the realm cache is keyed only by name, so a second
|
|
255
|
-
* `Runtime`'s `require('fs')` was the first's module object: a `Symbol.for`
|
|
256
|
-
* tag written by one guest was readable by the other. The first instance
|
|
257
|
-
* still uses {@link loadNodeLib}; a later instance asks here, and the file
|
|
258
|
-
* is compiled once for that owner. Its `require` of other vendored files
|
|
259
|
-
* still hits the realm cache, so `net` and `events` stay one class.
|
|
260
|
+
* Each guest owns one recursively resolved builtin graph. The default loader
|
|
261
|
+
* is private to the engine; guest monkey patches never reach it or siblings.
|
|
262
|
+
* Native bindings retain the shared OS registries beneath those graphs.
|
|
260
263
|
*/
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
264
|
+
export type NodeLibRequire = (name: string) => any;
|
|
265
|
+
type ModuleInitializer = (name: string, value: any, require: NodeLibRequire) => void;
|
|
266
|
+
const moduleInitializers = new Set<ModuleInitializer>();
|
|
267
|
+
|
|
268
|
+
/** Host bootstrap adapters are applied to each newly compiled builtin. */
|
|
269
|
+
export function onNodeLibLoaded(initialize: ModuleInitializer): () => void {
|
|
270
|
+
moduleInitializers.add(initialize);
|
|
271
|
+
return () => { moduleInitializers.delete(initialize); };
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** Native implementations can be shared; their exported module records cannot. */
|
|
275
|
+
export function moduleSurface(value: any, seen = new Map<object, any>()): any {
|
|
276
|
+
if (value === null || typeof value !== 'object') return value;
|
|
277
|
+
if (seen.has(value)) return seen.get(value);
|
|
278
|
+
const prototype = Object.getPrototypeOf(value);
|
|
279
|
+
if (prototype !== null && prototype !== Object.prototype && !Array.isArray(value)) return value;
|
|
280
|
+
const surface = Array.isArray(value) ? [] : Object.create(prototype === null ? null : Object.prototype);
|
|
281
|
+
seen.set(value, surface);
|
|
282
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
283
|
+
for (const [key, descriptor] of Object.entries(descriptors)) {
|
|
284
|
+
if (Array.isArray(value) && key === 'length') continue;
|
|
285
|
+
descriptor.configurable = true;
|
|
286
|
+
if ('value' in descriptor) {
|
|
287
|
+
descriptor.value = moduleSurface(descriptor.value, seen);
|
|
288
|
+
descriptor.writable = true;
|
|
289
|
+
}
|
|
290
|
+
Object.defineProperty(surface, key, descriptor);
|
|
268
291
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
292
|
+
return surface;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/** Native class implementation and OS registries are shared, JS prototypes are not. */
|
|
296
|
+
function bindingSurface(value: unknown): unknown {
|
|
297
|
+
const surface = moduleSurface(value);
|
|
298
|
+
for (const [key, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(surface))) {
|
|
299
|
+
const implementation = descriptor.value;
|
|
300
|
+
// ECMAScript classes have a non-writable own prototype. Ordinary native
|
|
301
|
+
// functions stay callable functions; class facades retain their static API.
|
|
302
|
+
if (typeof implementation !== 'function'
|
|
303
|
+
|| Object.getOwnPropertyDescriptor(implementation, 'prototype')?.writable !== false) continue;
|
|
304
|
+
const ScopedBinding = class extends implementation {};
|
|
305
|
+
Object.defineProperty(ScopedBinding, 'name', { value: implementation.name, configurable: true });
|
|
306
|
+
// IPC transfers native handles within the shared OS. Node's native-brand
|
|
307
|
+
// checks must recognize the received handle even when its JS facade was
|
|
308
|
+
// allocated by another process. This does not share guest module exports.
|
|
309
|
+
Object.defineProperty(ScopedBinding, Symbol.hasInstance, {
|
|
310
|
+
value: (instance: unknown) => Function.prototype[Symbol.hasInstance].call(implementation, instance),
|
|
311
|
+
});
|
|
312
|
+
Object.defineProperty(surface, key, { ...descriptor, value: ScopedBinding });
|
|
313
|
+
}
|
|
314
|
+
return surface;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
class NodeLibScope {
|
|
318
|
+
private readonly modules = new Map<string, NodeLibModule>();
|
|
319
|
+
private readonly internals = new Map<string, unknown>();
|
|
320
|
+
private readonly bindings = new Map<string, unknown>();
|
|
321
|
+
private readonly surfaces = new Map<string, unknown>();
|
|
322
|
+
constructor(readonly process: object) {}
|
|
323
|
+
|
|
324
|
+
readonly require: NodeLibRequire = (specifier) => {
|
|
325
|
+
const name = specifier.startsWith('node:') ? specifier.slice(5) : specifier;
|
|
326
|
+
if (name === 'process') return this.process;
|
|
327
|
+
if (name === 'fs/promises') return this.require('internal/fs/promises').exports;
|
|
328
|
+
if (name === 'timers/promises') return this.require('timers').promises;
|
|
329
|
+
if (name === 'dns/promises') return this.require('dns').promises;
|
|
330
|
+
if (name === 'path/posix') return this.require('path').posix;
|
|
331
|
+
if (name === 'path/win32') return this.require('path').win32;
|
|
332
|
+
if (name === 'assert/strict') return this.require('assert').strict;
|
|
333
|
+
if (name === 'util/types') return this.require('internal/util/types');
|
|
334
|
+
if (NODE_LIB_SOURCES[name] !== undefined) return this.load(name);
|
|
335
|
+
const internal = nodeLibInternal(name, this.require, this.process);
|
|
336
|
+
if (internal) {
|
|
337
|
+
if (!this.internals.has(name)) this.internals.set(name, moduleSurface(internal()));
|
|
338
|
+
return this.internals.get(name);
|
|
339
|
+
}
|
|
340
|
+
const native = nodeLibPublic(name, this.require, this.process);
|
|
341
|
+
if (native) return this.surface(name, native);
|
|
342
|
+
throw new Error(`node-lib: no builtin '${specifier}' in this process`);
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
surface(name: string, create: () => unknown): unknown {
|
|
346
|
+
if (!this.surfaces.has(name)) this.surfaces.set(name, moduleSurface(create()));
|
|
347
|
+
return this.surfaces.get(name);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
private readonly binding = (name: string): unknown => {
|
|
351
|
+
if (!this.bindings.has(name)) {
|
|
352
|
+
if (['fs', 'fs_dir', 'fs_event_wrap'].includes(name)) {
|
|
353
|
+
// Exported handles and encoded names belong to this graph; the fd
|
|
354
|
+
// allocator, open-file descriptions and filesystem remain shared OS state.
|
|
355
|
+
const fs = createFsBindings(() => this.require('buffer').Buffer);
|
|
356
|
+
this.bindings.set('fs', fs.fsBinding);
|
|
357
|
+
this.bindings.set('fs_dir', fs.fsDirBinding);
|
|
358
|
+
this.bindings.set('fs_event_wrap', fs.fsEventWrapBinding);
|
|
359
|
+
} else {
|
|
360
|
+
this.bindings.set(name, name === 'util' ? createUtilBinding(this.require) : bindingSurface(internalBinding(name)));
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return this.bindings.get(name);
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
private load(name: string): unknown {
|
|
367
|
+
const cached = this.modules.get(name);
|
|
368
|
+
if (cached) return cached.exports;
|
|
369
|
+
const record = newNodeLibRecord(name);
|
|
370
|
+
// Insert before evaluation: circular dependencies see this graph's record.
|
|
371
|
+
this.modules.set(name, record);
|
|
372
|
+
try {
|
|
373
|
+
compileNodeLib(name, record, this.require, this.process, this.binding);
|
|
374
|
+
for (const initialize of moduleInitializers) initialize(name, record.exports, this.require);
|
|
375
|
+
} catch (error) {
|
|
376
|
+
this.modules.delete(name);
|
|
377
|
+
throw error;
|
|
378
|
+
}
|
|
379
|
+
return record.exports;
|
|
278
380
|
}
|
|
279
|
-
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const byOwner = new WeakMap<object, NodeLibScope>();
|
|
384
|
+
function scopeFor(owner: object): NodeLibScope {
|
|
385
|
+
let scope = byOwner.get(owner);
|
|
386
|
+
if (!scope) { scope = new NodeLibScope(owner); byOwner.set(owner, scope); }
|
|
387
|
+
return scope;
|
|
388
|
+
}
|
|
389
|
+
export function loadNodeLibFor(owner: object, name: string): any {
|
|
390
|
+
return scopeFor(owner).require(name);
|
|
391
|
+
}
|
|
392
|
+
export function hasNodeLibModule(name: string): boolean {
|
|
393
|
+
return NODE_LIB_SOURCES[name] !== undefined || nodeLibPublic(name) !== undefined
|
|
394
|
+
|| ['path/posix', 'path/win32', 'assert/strict', 'util/types', 'fs/promises', 'timers/promises', 'dns/promises'].includes(name);
|
|
395
|
+
}
|
|
396
|
+
export function nativeModuleFor(owner: object, name: string, create: () => unknown): unknown {
|
|
397
|
+
return scopeFor(owner).surface(name, create);
|
|
280
398
|
}
|
|
281
399
|
|
|
282
400
|
function newNodeLibRecord(name: string): NodeLibModule {
|
|
@@ -285,15 +403,16 @@ function newNodeLibRecord(name: string): NodeLibModule {
|
|
|
285
403
|
}
|
|
286
404
|
|
|
287
405
|
/** The scope Node's `BuiltinModule.compileForInternalLoader` gives a builtin. */
|
|
288
|
-
function compileNodeLib(name: string, record: NodeLibModule
|
|
406
|
+
function compileNodeLib(name: string, record: NodeLibModule, require: NodeLibRequire = nodeLibRequire,
|
|
407
|
+
process: object = nodeLibProcessOf(), binding: (name: string) => unknown = internalBinding): void {
|
|
289
408
|
// eslint-disable-next-line no-new-func
|
|
290
409
|
const compiled = new Function(
|
|
291
410
|
'exports', 'require', 'module', 'process', 'internalBinding', 'primordials',
|
|
292
411
|
`${NODE_LIB_SOURCES[name]}\n//# sourceURL=node:${name}`,
|
|
293
412
|
);
|
|
294
|
-
compiled(record.exports,
|
|
413
|
+
compiled(record.exports, require, record, process, binding, primordialsOf());
|
|
295
414
|
record.loaded = true;
|
|
296
|
-
bootstrapNodeLib(name, record.exports);
|
|
415
|
+
bootstrapNodeLib(name, record.exports, process);
|
|
297
416
|
}
|
|
298
417
|
|
|
299
418
|
/**
|
|
@@ -307,38 +426,27 @@ function compileNodeLib(name: string, record: NodeLibModule): void {
|
|
|
307
426
|
* `testEnabled`. The engine has no bootstrap file, so the step lives here,
|
|
308
427
|
* beside the compile it belongs to.
|
|
309
428
|
*/
|
|
310
|
-
function bootstrapNodeLib(name: string, exports: unknown): void {
|
|
429
|
+
function bootstrapNodeLib(name: string, exports: unknown, process: object): void {
|
|
311
430
|
if (name === 'events') {
|
|
312
|
-
//
|
|
313
|
-
//
|
|
314
|
-
//
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
for (const [key, fallback] of defaults) {
|
|
328
|
-
if (!key || key in target.prototype) continue;
|
|
329
|
-
Object.defineProperty(target.prototype, key, {
|
|
330
|
-
configurable: true,
|
|
331
|
-
get(): unknown { return fallback; },
|
|
332
|
-
set(this: object, value: unknown) {
|
|
333
|
-
Object.defineProperty(this, key, { value, writable: true, configurable: true });
|
|
334
|
-
},
|
|
335
|
-
});
|
|
336
|
-
}
|
|
337
|
-
}
|
|
431
|
+
// Browser EventTargets do not carry Node's process-local listener symbol.
|
|
432
|
+
// Adapt that native boundary without installing a new pair of symbols on
|
|
433
|
+
// the shared EventTarget prototype for every short-lived guest process.
|
|
434
|
+
const events = exports as {
|
|
435
|
+
kMaxEventTargetListeners: symbol;
|
|
436
|
+
defaultMaxListeners: number;
|
|
437
|
+
getMaxListeners: (target: any) => number;
|
|
438
|
+
};
|
|
439
|
+
const original = events.getMaxListeners;
|
|
440
|
+
events.getMaxListeners = (target) => {
|
|
441
|
+
if (typeof EventTarget !== 'undefined' && target instanceof EventTarget
|
|
442
|
+
&& typeof (target as any).getMaxListeners !== 'function'
|
|
443
|
+
&& (target as any)[events.kMaxEventTargetListeners] === undefined) return events.defaultMaxListeners;
|
|
444
|
+
return original(target);
|
|
445
|
+
};
|
|
338
446
|
return;
|
|
339
447
|
}
|
|
340
448
|
if (name === 'internal/util/debuglog') {
|
|
341
|
-
const env = (
|
|
449
|
+
const env = (process as { env?: Record<string, string | undefined> }).env;
|
|
342
450
|
(exports as { initializeDebugEnv?: (value: string) => void }).initializeDebugEnv?.(env?.NODE_DEBUG ?? '');
|
|
343
451
|
return;
|
|
344
452
|
}
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
* - `initialize` is awaited before the first resolution the chain serves,
|
|
33
33
|
* which is where Node's synchronous `register()` has already blocked for it.
|
|
34
34
|
*/
|
|
35
|
-
import {
|
|
35
|
+
import { loadNodeLibFor } from './load';
|
|
36
36
|
|
|
37
37
|
/** A resolve hook's answer, as `internal/modules/esm/hooks.js` validates one. */
|
|
38
38
|
export interface ResolveResult {
|
|
@@ -96,7 +96,10 @@ const ESM_CONDITIONS = ['node', 'import'];
|
|
|
96
96
|
|
|
97
97
|
export class RunModuleHooks {
|
|
98
98
|
/** The vendored file's own instance, this run's alone. */
|
|
99
|
-
readonly sync: CustomizationHooks
|
|
99
|
+
readonly sync: CustomizationHooks;
|
|
100
|
+
constructor(process: object) {
|
|
101
|
+
this.sync = loadNodeLibFor(process, 'internal/modules/customization_hooks') as CustomizationHooks;
|
|
102
|
+
}
|
|
100
103
|
#resolveChain: Link<AsyncResolveHook>[] = [];
|
|
101
104
|
#loadChain: Link<AsyncLoadHook>[] = [];
|
|
102
105
|
#initializing: Promise<unknown>[] = [];
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Node's own files `require('dns')`, `require('timers')` and the rest as any
|
|
5
5
|
* program does, and get the runtime's modules. Here they get the engine's
|
|
6
|
-
* shims
|
|
6
|
+
* shims through the requesting process's module graph, so a
|
|
7
7
|
* `net.Socket` is a `stream.Duplex` of the same `stream` the guest imports,
|
|
8
8
|
* and an `instanceof` across the two holds. A name that is itself a vendored
|
|
9
9
|
* file — `events`, `util`, `buffer`, `stream` — is never asked of this table:
|
|
@@ -13,11 +13,11 @@
|
|
|
13
13
|
* this module is loaded, so a cycle between a shim and the loader settles
|
|
14
14
|
* before either is used.
|
|
15
15
|
*/
|
|
16
|
-
import
|
|
16
|
+
import { createDnsModule } from '../shims/dns';
|
|
17
17
|
import * as clusterShim from '../shims/cluster';
|
|
18
18
|
import * as dgramShim from '../shims/dgram';
|
|
19
19
|
import asyncHooksShim from '../shims/async_hooks';
|
|
20
|
-
import stringDecoderShim from '../shims/string_decoder';
|
|
20
|
+
import stringDecoderShim, { createStringDecoderModule } from '../shims/string_decoder';
|
|
21
21
|
import v8Shim from '../shims/v8';
|
|
22
22
|
import * as pathShim from '../shims/path';
|
|
23
23
|
import * as tlsShim from '../shims/tls';
|
|
@@ -28,57 +28,21 @@ import * as http2Shim from '../shims/http2';
|
|
|
28
28
|
import * as inspectorShim from '../shims/inspector';
|
|
29
29
|
import * as perfHooksShim from '../shims/perf_hooks';
|
|
30
30
|
import { nodeLibInternalRequire } from './load';
|
|
31
|
-
import {
|
|
31
|
+
import { createTimersModule } from './timers';
|
|
32
|
+
import { guestTimerFunctions } from '../guest-timers';
|
|
32
33
|
import { AsyncResource } from './internals/runtime';
|
|
33
34
|
|
|
34
|
-
/**
|
|
35
|
-
* `timers`: the realm's own, called on the realm and answering Node's shape.
|
|
36
|
-
*
|
|
37
|
-
* This is the module Node's own files take their timers from --
|
|
38
|
-
* `_http_server.js` opens with `const { setInterval, clearInterval } =
|
|
39
|
-
* require('timers')` and then writes `setInterval(...).unref()` the moment a
|
|
40
|
-
* server listens -- so what it answers has to be Node's `Timeout`
|
|
41
|
-
* ({@link nodeTimeout}), not a browser's number. It handed back the realm's
|
|
42
|
-
* raw function, and in the tab every guest that called `listen` died with
|
|
43
|
-
* `setInterval(...).unref is not a function`, openvscode-server among them.
|
|
44
|
-
* The function is also called on the realm here: a worker's `setInterval`
|
|
45
|
-
* invoked with this module as its receiver throws `Illegal invocation`, which
|
|
46
|
-
* is what a guest's own `require('timers').setInterval` did.
|
|
47
|
-
*
|
|
48
|
-
* A guest's global timers are the same shape, built per run in
|
|
49
|
-
* `src/runtime.ts` where they are counted against that run's loop; a timer
|
|
50
|
-
* made through either is cleared through either, which is why the shape has
|
|
51
|
-
* one home.
|
|
52
|
-
*/
|
|
53
|
-
const timersModule = {
|
|
54
|
-
setTimeout(fn: (...a: unknown[]) => void, ms?: number, ...rest: unknown[]) {
|
|
55
|
-
return nodeTimeout(globalThis.setTimeout(fn as () => void, ms, ...rest));
|
|
56
|
-
},
|
|
57
|
-
clearTimeout(id: unknown) { return globalThis.clearTimeout(timerHandleOf(id) as number); },
|
|
58
|
-
setInterval(fn: (...a: unknown[]) => void, ms?: number, ...rest: unknown[]) {
|
|
59
|
-
return nodeTimeout(globalThis.setInterval(fn as () => void, ms, ...rest));
|
|
60
|
-
},
|
|
61
|
-
clearInterval(id: unknown) { return globalThis.clearInterval(timerHandleOf(id) as number); },
|
|
62
|
-
setImmediate(fn: (...a: unknown[]) => void, ...args: unknown[]) {
|
|
63
|
-
const realm = globalThis as unknown as { setImmediate?: (fn: (...a: unknown[]) => void, ...args: unknown[]) => unknown };
|
|
64
|
-
return nodeTimeout(realm.setImmediate
|
|
65
|
-
? realm.setImmediate(fn, ...args)
|
|
66
|
-
: globalThis.setTimeout(fn as () => void, 0, ...args));
|
|
67
|
-
},
|
|
68
|
-
clearImmediate(id: unknown) {
|
|
69
|
-
const realm = globalThis as unknown as { clearImmediate?: (id: unknown) => void };
|
|
70
|
-
const handle = timerHandleOf(id);
|
|
71
|
-
return realm.clearImmediate ? realm.clearImmediate(handle) : globalThis.clearTimeout(handle as number);
|
|
72
|
-
},
|
|
73
|
-
};
|
|
74
|
-
|
|
75
35
|
/** Built on the first ask, for the reason `./binding/index.ts` gives. */
|
|
76
36
|
// eslint-disable-next-line no-var, vars-on-top
|
|
77
37
|
var __table: Record<string, () => unknown> | undefined;
|
|
78
|
-
export function nodeLibPublic(name: string): (() => unknown) | undefined {
|
|
38
|
+
export function nodeLibPublic(name: string, require?: (name: string) => any, _process?: object): (() => unknown) | undefined {
|
|
39
|
+
if (_process && name === 'timers') return () => createTimersModule(guestTimerFunctions(_process, globalThis as unknown as Record<string, unknown>));
|
|
40
|
+
if (require && name === 'crypto') return () => cryptoShim.createCryptoModule(require);
|
|
41
|
+
if (require && name === 'tls') return () => tlsShim.createTlsModule(require);
|
|
42
|
+
if (require && name === 'string_decoder') return () => createStringDecoderModule(() => require('buffer').Buffer);
|
|
79
43
|
__table ??= {
|
|
80
|
-
timers:
|
|
81
|
-
dns:
|
|
44
|
+
timers: createTimersModule,
|
|
45
|
+
dns: createDnsModule,
|
|
82
46
|
cluster: () => clusterShim,
|
|
83
47
|
string_decoder: () => stringDecoderShim,
|
|
84
48
|
v8: () => v8Shim,
|
package/src/node-lib/sources.ts
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
* text; `load.ts` evaluates it in the scope Node's `BuiltinModule` gives a
|
|
9
9
|
* builtin.
|
|
10
10
|
*/
|
|
11
|
+
import NAVIGATOR from './internal/navigator.js?raw';
|
|
12
|
+
import PATH from './path.js?raw';
|
|
11
13
|
import NET from './net.js?raw';
|
|
12
14
|
import BUFFER from './buffer.js?raw';
|
|
13
15
|
import INTERNAL_BUFFER from './internal/buffer.js?raw';
|
|
@@ -105,6 +107,8 @@ import INTERNAL_EVENTS_SYMBOLS from './internal/events/symbols.js?raw';
|
|
|
105
107
|
export const NODE_LIB_VERSION = 'v22.18.0';
|
|
106
108
|
|
|
107
109
|
export const NODE_LIB_SOURCES: Record<string, string> = {
|
|
110
|
+
'internal/navigator': NAVIGATOR,
|
|
111
|
+
path: PATH,
|
|
108
112
|
'net': NET,
|
|
109
113
|
'buffer': BUFFER,
|
|
110
114
|
'internal/buffer': INTERNAL_BUFFER,
|
package/src/node-lib/timers.ts
CHANGED
|
@@ -31,3 +31,96 @@ export function nodeTimeout(id: unknown): unknown {
|
|
|
31
31
|
export function timerHandleOf(id: unknown): unknown {
|
|
32
32
|
return id && typeof id === "object" && "_id" in (id as object) ? (id as { _id: unknown })._id : id;
|
|
33
33
|
}
|
|
34
|
+
|
|
35
|
+
/** Native timers surface shared by both builtin loaders, fresh for each graph. */
|
|
36
|
+
export function createTimersModule(owned?: {
|
|
37
|
+
setTimeout(fn: (...args: unknown[]) => void, ms?: number, ...args: unknown[]): unknown;
|
|
38
|
+
clearTimeout(id: unknown): void;
|
|
39
|
+
setInterval(fn: (...args: unknown[]) => void, ms?: number, ...args: unknown[]): unknown;
|
|
40
|
+
clearInterval(id: unknown): void;
|
|
41
|
+
}) {
|
|
42
|
+
const schedule = owned?.setTimeout ?? globalThis.setTimeout.bind(globalThis);
|
|
43
|
+
const cancel = owned?.clearTimeout ?? ((id: unknown) => globalThis.clearTimeout(timerHandleOf(id) as number));
|
|
44
|
+
// Node's legacy timer list API, deprecated (DEP0095) and still exported:
|
|
45
|
+
// an object with `_onTimeout` is enrolled for a duration, made active to
|
|
46
|
+
// start or restart its countdown, and unenrolled to stop it.
|
|
47
|
+
type Enrolled = { _onTimeout?: () => void; _idleTimeout?: number; _idleTimeoutId?: unknown };
|
|
48
|
+
const unenroll = (item: Enrolled): void => {
|
|
49
|
+
if (item._idleTimeoutId) cancel(item._idleTimeoutId);
|
|
50
|
+
item._idleTimeoutId = null;
|
|
51
|
+
item._idleTimeout = -1;
|
|
52
|
+
};
|
|
53
|
+
const enroll = (item: Enrolled, msecs: number): void => {
|
|
54
|
+
if (item._idleTimeoutId) cancel(item._idleTimeoutId);
|
|
55
|
+
item._idleTimeoutId = null;
|
|
56
|
+
item._idleTimeout = msecs;
|
|
57
|
+
};
|
|
58
|
+
const active = (item: Enrolled): void => {
|
|
59
|
+
const msecs = item._idleTimeout;
|
|
60
|
+
if (typeof msecs !== 'number' || msecs < 0) return;
|
|
61
|
+
if (item._idleTimeoutId) cancel(item._idleTimeoutId);
|
|
62
|
+
item._idleTimeoutId = schedule(() => { item._idleTimeoutId = null; if (typeof item._onTimeout === 'function') item._onTimeout(); }, msecs);
|
|
63
|
+
};
|
|
64
|
+
const abortError = () => {
|
|
65
|
+
const error = new Error("The operation was aborted") as Error & { code?: string };
|
|
66
|
+
error.name = "AbortError";
|
|
67
|
+
error.code = "ABORT_ERR";
|
|
68
|
+
return error;
|
|
69
|
+
};
|
|
70
|
+
// timers/promises.setInterval was the global setInterval, which answers a
|
|
71
|
+
// handle rather than the async generator Node does, so "for await" over it
|
|
72
|
+
// never yielded; setTimeout dropped the value it was given, and neither took
|
|
73
|
+
// a signal.
|
|
74
|
+
const timersPromises = {
|
|
75
|
+
setTimeout: (delay: any, value: any, options: any) => new Promise((resolve, reject) => {
|
|
76
|
+
const signal = options && options.signal;
|
|
77
|
+
if (signal && signal.aborted) { reject(abortError()); return; }
|
|
78
|
+
const onAbort = () => { cancel(handle); reject(abortError()); };
|
|
79
|
+
const handle = schedule(() => {
|
|
80
|
+
if (signal) signal.removeEventListener("abort", onAbort);
|
|
81
|
+
resolve(value);
|
|
82
|
+
}, delay);
|
|
83
|
+
if (signal) signal.addEventListener("abort", onAbort, { once: true });
|
|
84
|
+
}),
|
|
85
|
+
setImmediate: (value: any, options: any) => timersPromises.setTimeout(0, value, options),
|
|
86
|
+
setInterval: async function* setInterval(delay: any, value: any, options: any) {
|
|
87
|
+
const signal = options && options.signal;
|
|
88
|
+
for (;;) {
|
|
89
|
+
await timersPromises.setTimeout(delay, void 0, options);
|
|
90
|
+
if (signal && signal.aborted) return;
|
|
91
|
+
yield value;
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
scheduler: {
|
|
95
|
+
wait: (delay: any, options: any) => timersPromises.setTimeout(delay, void 0, options),
|
|
96
|
+
yield: () => timersPromises.setTimeout(0, void 0, void 0)
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
return {
|
|
100
|
+
setTimeout(fn: (...a: unknown[]) => void, ms?: number, ...rest: unknown[]) {
|
|
101
|
+
return nodeTimeout(globalThis.setTimeout(fn as () => void, ms, ...rest));
|
|
102
|
+
},
|
|
103
|
+
clearTimeout(id: unknown) { return globalThis.clearTimeout(timerHandleOf(id) as number); },
|
|
104
|
+
setInterval(fn: (...a: unknown[]) => void, ms?: number, ...rest: unknown[]) {
|
|
105
|
+
return nodeTimeout(globalThis.setInterval(fn as () => void, ms, ...rest));
|
|
106
|
+
},
|
|
107
|
+
clearInterval(id: unknown) { return globalThis.clearInterval(timerHandleOf(id) as number); },
|
|
108
|
+
setImmediate(fn: (...a: unknown[]) => void, ...args: unknown[]) {
|
|
109
|
+
const realm = globalThis as unknown as { setImmediate?: (fn: (...a: unknown[]) => void, ...args: unknown[]) => unknown };
|
|
110
|
+
return nodeTimeout(realm.setImmediate
|
|
111
|
+
? realm.setImmediate(fn, ...args)
|
|
112
|
+
: globalThis.setTimeout(fn as () => void, 0, ...args));
|
|
113
|
+
},
|
|
114
|
+
clearImmediate(id: unknown) {
|
|
115
|
+
const realm = globalThis as unknown as { clearImmediate?: (id: unknown) => void };
|
|
116
|
+
const handle = timerHandleOf(id);
|
|
117
|
+
return realm.clearImmediate ? realm.clearImmediate(handle) : globalThis.clearTimeout(handle as number);
|
|
118
|
+
},
|
|
119
|
+
...(owned ? {
|
|
120
|
+
...owned,
|
|
121
|
+
setImmediate: (fn: (...args: unknown[]) => void, ...args: unknown[]) => owned.setTimeout(fn, 0, ...args),
|
|
122
|
+
clearImmediate: owned.clearTimeout,
|
|
123
|
+
} : {}),
|
|
124
|
+
active, enroll, unenroll, promises: timersPromises,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type { ProcessIdentity } from './process-registry';
|
|
2
|
+
import type { NativeStreamDescriptor } from './native-stream-owner';
|
|
3
|
+
import type { RunStreams } from './shims/child_process';
|
|
4
|
+
import type { VirtualFS } from './virtual-fs';
|
|
5
|
+
|
|
6
|
+
/** Trusted launch data, before Node options or the script execute in a realm. */
|
|
7
|
+
export interface NodeProcessLaunch {
|
|
8
|
+
token: string;
|
|
9
|
+
identity: ProcessIdentity;
|
|
10
|
+
/** Arguments after the Node executable, preserving each parsed word. */
|
|
11
|
+
argv: readonly string[];
|
|
12
|
+
cwd: string;
|
|
13
|
+
/** This entry's filesystem view, including any host-prepared entry wrapper. */
|
|
14
|
+
filesystem: VirtualFS;
|
|
15
|
+
env: Record<string, string>;
|
|
16
|
+
stdin?: string;
|
|
17
|
+
stdinStream?: AsyncIterable<Uint8Array>;
|
|
18
|
+
streams?: RunStreams;
|
|
19
|
+
inherited: readonly { fd: number; handle: NativeStreamDescriptor }[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface NodeProcessHost {
|
|
23
|
+
run(launch: NodeProcessLaunch): Promise<{ stdout: string; stderr: string; exitCode: number }>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let host: NodeProcessHost | undefined;
|
|
27
|
+
let installed = false;
|
|
28
|
+
let started = false;
|
|
29
|
+
let localToken: string | undefined;
|
|
30
|
+
let localStarted = false;
|
|
31
|
+
|
|
32
|
+
export function nodeProcessHostInstalled(): boolean { return installed; }
|
|
33
|
+
|
|
34
|
+
/** Only a worker with one admitted local entry has an unambiguous realm owner. */
|
|
35
|
+
export function nodeProcessRealmToken(): string | null { return localStarted ? localToken ?? null : null; }
|
|
36
|
+
|
|
37
|
+
const HostTransformStream = globalThis.TransformStream;
|
|
38
|
+
const HostTextEncoder = globalThis.TextEncoder;
|
|
39
|
+
|
|
40
|
+
/** Adapt the engine's push sink to the worker bridge's pull-based input. */
|
|
41
|
+
export function nodeProcessInput(streams: RunStreams | undefined): {
|
|
42
|
+
stream?: AsyncIterable<Uint8Array>;
|
|
43
|
+
dispose(): void;
|
|
44
|
+
} {
|
|
45
|
+
if (streams?.stdinStream) return { stream: streams.stdinStream, dispose() {} };
|
|
46
|
+
if (!streams || (!streams.stdinOpen && !streams.held)) return { dispose() {} };
|
|
47
|
+
let inputController!: TransformStreamDefaultController<Uint8Array>;
|
|
48
|
+
const pipe = new HostTransformStream<Uint8Array, Uint8Array>({
|
|
49
|
+
start(controller) { inputController = controller; },
|
|
50
|
+
});
|
|
51
|
+
const writer = pipe.writable.getWriter();
|
|
52
|
+
// Cancellation is delivered through the readable side and pending writes.
|
|
53
|
+
// The host's bookkeeping promise must not become a guest rejection.
|
|
54
|
+
void writer.closed.catch(() => {});
|
|
55
|
+
const encoder = new HostTextEncoder();
|
|
56
|
+
let disposed = false;
|
|
57
|
+
let ended = false;
|
|
58
|
+
const write = (chunk: string | Uint8Array | null): Promise<void> => {
|
|
59
|
+
if (chunk === null) {
|
|
60
|
+
if (ended) return Promise.resolve();
|
|
61
|
+
ended = true;
|
|
62
|
+
return writer.close();
|
|
63
|
+
}
|
|
64
|
+
if (ended || disposed) return Promise.reject(new Error('Node process input is closed.'));
|
|
65
|
+
const bytes = typeof chunk === 'string' ? encoder.encode(chunk) : new Uint8Array(chunk);
|
|
66
|
+
return writer.write(bytes);
|
|
67
|
+
};
|
|
68
|
+
const dispose = (): void => {
|
|
69
|
+
if (disposed) return;
|
|
70
|
+
disposed = true;
|
|
71
|
+
streams.signal?.removeEventListener('abort', dispose);
|
|
72
|
+
// Error both sides, which also releases a write waiting for readable
|
|
73
|
+
// demand. Aborting only the writer can wait behind that blocked write.
|
|
74
|
+
inputController.error(streams.signal?.reason ?? new Error('Node process input stopped.'));
|
|
75
|
+
};
|
|
76
|
+
streams.signal?.addEventListener('abort', dispose, { once: true });
|
|
77
|
+
if (streams.signal?.aborted) dispose();
|
|
78
|
+
streams.stdin = {
|
|
79
|
+
emit() {},
|
|
80
|
+
// The host's legacy sendInput is fire-and-forget. Forked pipes use the
|
|
81
|
+
// acknowledged door below and stop reading while its promise is pending.
|
|
82
|
+
push(chunk) { void write(chunk).catch(dispose); return (writer.desiredSize ?? 0) > 0; },
|
|
83
|
+
writeAsync: write,
|
|
84
|
+
};
|
|
85
|
+
return {
|
|
86
|
+
stream: {
|
|
87
|
+
async *[Symbol.asyncIterator]() {
|
|
88
|
+
const reader = pipe.readable.getReader();
|
|
89
|
+
void reader.closed.catch(() => {});
|
|
90
|
+
let completed = false;
|
|
91
|
+
try {
|
|
92
|
+
for (;;) {
|
|
93
|
+
const next = await reader.read();
|
|
94
|
+
if (next.done) { completed = true; return; }
|
|
95
|
+
yield next.value;
|
|
96
|
+
}
|
|
97
|
+
} finally { reader.releaseLock(); if (!completed) dispose(); }
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
dispose,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Host bootstrap only. A process worker admits exactly one local Node entry. */
|
|
105
|
+
export function installNodeProcessHost(next: NodeProcessHost, token?: string): void {
|
|
106
|
+
if (installed || started) throw new Error('Node process host must be installed once before Node execution.');
|
|
107
|
+
if (typeof next?.run !== 'function' || (token !== undefined && (typeof token !== 'string' || !token.length))) {
|
|
108
|
+
throw new Error('Invalid Node process host.');
|
|
109
|
+
}
|
|
110
|
+
host = next;
|
|
111
|
+
localToken = token;
|
|
112
|
+
installed = true;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Called by the common Node command entry, including engine-first forks. */
|
|
116
|
+
export function nodeProcessHostFor(token: string | null): NodeProcessHost | undefined {
|
|
117
|
+
started = true;
|
|
118
|
+
if (!host) return undefined;
|
|
119
|
+
if (token === null) throw new Error('Isolated Node execution requires a named run.');
|
|
120
|
+
if (token !== localToken) return host;
|
|
121
|
+
if (localStarted) throw new Error('This worker has already executed its admitted Node process.');
|
|
122
|
+
localStarted = true;
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|