Something went wrong. Try again.
Monorepo for the nibling.run platform
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279// CJS module loader for the runtime worker. require() is synchronous:// every loadModule/resolve syscall goes through the sync mailbox (Atomics.wait).// Envelope + error mapping are the C2/C3 grafts recorded in sketches/arena-1/SYNTHESIS.md.// loadCjsByPath is also the backend of the runtime hook's cjs() — the ESM facade// modules served from the SW virtual origin evaluate CJS through the same loader// and the same cache, so a module never evaluates twice per worker.//// The pieces below (wrapper, extensions, resolveFilename) are also node:module's// Module._* surface (src/runtime/module.ts): real hook points, not a shadow API, so// a guest's require.extensions-style patch actually changes what require() does.import type { FsResult, SyscallSpec } from "../shared/protocol";import type { HttpRuntime } from "./http";import type { Loop } from "./loop";import type { Timers } from "./timers";import type { GuestProcess } from "./process";import type { WorkerThreads } from "./worker_threads";import type { ChildProcesses } from "./child_process";import { loadBuiltin } from "./builtins";import { loaderError, requireNotFound } from "./errors";import { esmCycle, esmEvaluating, requireEsm } from "./esm-sync";import { unsupported } from "./refuse";import { SCHEME_ONLY_BUILTINS, isBuiltinId, type BuiltinId } from "../shared/builtin-ids";
export interface ModuleCacheEntry { exports: unknown; loaded: boolean;}
/** The require()-visible module object (Node's `module`): the require cache entry * IS the module instance, so `module.exports = x` from inside a cycle is what a * later require() of the same path reads back. */export interface ModuleLike extends ModuleCacheEntry { id: string; filename: string; children: unknown[]; paths: string[]; /** Node's module.require/_compile: real methods on every module object (not just * `new Module()` instances), so a guest's own Module._extensions handler — which * expects to call `mod._compile(content, filename)` the way Node's own default * handlers do — works on whatever require() handed it. */ require(specifier: string): unknown; _compile(content: string, filename: string): unknown;}
export interface RequireContext { call<K extends keyof SyscallSpec>(op: K, args: SyscallSpec[K]["args"]): Promise<SyscallSpec[K]["ret"]>; callSync<K extends keyof SyscallSpec>(op: K, args: SyscallSpec[K]["args"]): SyscallSpec[K]["ret"]; /** A sync syscall that hands back the kernel's result instead of throwing. */ trySync<K extends keyof SyscallSpec>(op: K, args: SyscallSpec[K]["args"]): FsResult<SyscallSpec[K]["ret"]>; /** An async syscall whose result `done` receives as a poll-phase loop callback. */ submit<K extends keyof SyscallSpec>(op: K, args: SyscallSpec[K]["args"], done: (result: FsResult<SyscallSpec[K]["ret"]>) => void): void; cache: Map<string, ModuleCacheEntry>; dirnameOf(path: string): string; /** Process cwd — relative builtin paths resolve against it (join is lexical). */ readonly cwd: string; resolvePath(p: string): string; /** The process-wide node:http module (one per worker: it owns the port dispatch). */ readonly http: HttpRuntime["module"]; /** The guest timer functions (the same objects as the globals). */ readonly timers: Timers; readonly process: GuestProcess; /** The worker's event loop, for builtins whose work completes outside a syscall. */ readonly loop: Loop; /** This worker's node:worker_threads state (it owns the thread's parent port). */ readonly workerThreads: WorkerThreads; /** This worker's children (node:child_process); their output comes back to it. */ readonly childProcess: ChildProcesses;}
/** Compile a CJS module body the way Node wraps it: the wrapper opens on the body's * first line, so stack traces report the guest's own line numbers. `new Function` * would prepend two header lines and shift every frame by two. Entry-script * execution (worker_threads eval, the process entry) calls this directly with its * own extra `process` parameter; it is not part of Module.wrap's contract below. */export function compileCjs(text: string, path: string, params: string[]): (...args: unknown[]) => unknown { const indirectEval = eval; // A hashbang is only legal as a script's first characters; inside the wrapper it is // not, so it goes, keeping the line (a package's bin entries start with one). const body = text.startsWith("#!") ? text.replace(/^#![^\n]*/, "") : text; return indirectEval(`(function (${params.join(", ")}) {${body}\n})\n//# sourceURL=${path}`);}
/** Module.wrapper / Module.wrap: the require() path's own compile template, kept * mutable and separate from compileCjs's generic one (above) so a guest patching * it — a require hook rewriting the wrapper to inject something — really changes * what require() compiles. Same five params and order as Node. */export const wrapper: [string, string] = [ "(function (exports, require, module, __filename, __dirname) { ", "\n});",];
export function wrap(script: string): string { return `${wrapper[0]}${script}${wrapper[1]}`;}
function compileWrapped(ctx: RequireContext, mod: ModuleLike, text: string, filename: string): unknown { const indirectEval = eval; const body = text.startsWith("#!") ? text.replace(/^#![^\n]*/, "") : text; const fn = indirectEval(`${wrap(body)}\n//# sourceURL=${filename}`) as (...args: unknown[]) => unknown; return fn.call(mod.exports, mod.exports, makeRequire(ctx, filename), mod, filename, ctx.dirnameOf(filename));}
/** Module._extensions: a compile step keyed by file extension, consulted by * module.load(). This is the same object `require("module")._extensions` hands * back (src/runtime/module.ts), so a guest's require hook (ts-node/tsx-style) * that assigns `Module._extensions['.js'] = ...` really changes what runs. An * extension not in the table falls back to `.js`, as Node's own lookup does. */export type ExtensionHandler = (ctx: RequireContext, mod: ModuleLike, filename: string, parent: string | undefined) => void;
function loadSource(ctx: RequireContext, filename: string): { text: string; format: "cjs" | "json" | "esm" } { const { source, format } = ctx.callSync("loadModule", { path: filename }); return { text: new TextDecoder().decode(source), format };}
export const extensions: Record<string, ExtensionHandler> = { ".js": (ctx, mod, filename, parent) => { const { text, format } = loadSource(ctx, filename); if (format === "esm") mod.exports = requireEsm(ctx, filename, parent); else compileWrapped(ctx, mod, text, filename); }, ".cjs": (ctx, mod, filename) => { const { text } = loadSource(ctx, filename); compileWrapped(ctx, mod, text, filename); }, ".json": (ctx, mod, filename) => { const { text } = loadSource(ctx, filename); mod.exports = JSON.parse(text); }, ".node": () => { throw unsupported("module", "require(*.node)", "native addons have no equivalent inside a worker"); },};
function extnameOf(filename: string): string { const base = filename.slice(filename.lastIndexOf("/") + 1); const dot = base.lastIndexOf("."); return dot <= 0 ? "" : base.slice(dot);}
/** Node's Module._nodeModulePaths: purely lexical (every ancestor's node_modules, * skipping one already named node_modules), no fs check — the resolver does that * when a require() actually walks it. */export function nodeModulePaths(from: string): string[] { const paths: string[] = []; let dir = from; for (;;) { if (!dir.endsWith("/node_modules")) paths.push(dir === "/" ? "/node_modules" : `${dir}/node_modules`); const slash = dir.lastIndexOf("/"); if (slash <= 0) break; dir = dir.slice(0, slash); } return paths;}
/** Node's `module` object, which is also its require-cache entry. */export function newModule(ctx: RequireContext, path: string): ModuleLike { const mod: ModuleLike = { exports: {}, id: path, filename: path, loaded: false, children: [], paths: nodeModulePaths(dirnameLexical(path)), require: (specifier) => makeRequire(ctx, mod.filename)(specifier), _compile: (content, filename) => compileWrapped(ctx, mod, content, filename), }; return mod;}
function dirnameLexical(path: string): string { const slash = path.lastIndexOf("/"); return slash <= 0 ? "/" : path.slice(0, slash);}
/** Resolve→load→eval for a KNOWN path, shared by require() and the cjs() runtime * hook. The cache entry is installed before loading so a require() cycle observes * partial exports instead of recursing. Dispatches through `extensions` (above), * so a patched handler for a known extension is what actually compiles the file. */export function loadCjsByPath(ctx: RequireContext, path: string, parent?: string): unknown { // A module still loading is a require() cycle: it answers its exports so far, // except an ES module, which has none until it finishes. const cached = ctx.cache.get(path); if (cached) { if (!cached.loaded && esmEvaluating(path)) throw esmCycle(path); return cached.exports; } const mod = newModule(ctx, path); ctx.cache.set(path, mod); try { const ext = extnameOf(path); const handler = extensions[ext] ?? extensions[".js"]!; handler(ctx, mod, path, parent); } catch (e) { ctx.cache.delete(path); // as Node does, a module that threw loads afresh next time throw e; } mod.loaded = true; return mod.exports;}
/** Module._resolveFilename's default behavior: the specifier resolver, returning * either a builtin's request string back unchanged (Node echoes it: `fs` stays * `fs`, `node:fs` stays `node:fs`) or a file path. */function defaultResolveFilename(ctx: RequireContext, request: string, parentFilename: string | undefined): string { const resolved = ctx.callSync("resolve", { specifier: request, parent: ctx.dirnameOf(parentFilename ?? ctx.cwd), kind: "require", }); if (resolved.kind === "builtin") return request; if (resolved.kind === "missing") { if (resolved.code === "ERR_MODULE_NOT_FOUND") throw requireNotFound(request, parentFilename ? [parentFilename] : []); throw loaderError(resolved.code, request, parentFilename); } return resolved.path;}
export type ResolveFilenameFn = (ctx: RequireContext, request: string, parentFilename: string | undefined, isMain: boolean) => string;
let resolveFilenameOverride: ResolveFilenameFn | null = null;
/** Module._resolveFilename's current implementation — `defaultResolveFilename` * unless a guest (module.ts's Module._resolveFilename setter) installed its own, * in which case require() itself resolves through it too. */export function resolveFilename(ctx: RequireContext, request: string, parentFilename: string | undefined, isMain = false): string { return (resolveFilenameOverride ?? defaultResolveFilename)(ctx, request, parentFilename, isMain);}
export function setResolveFilenameHook(fn: ResolveFilenameFn | null): void { resolveFilenameOverride = fn;}
/** A snapshot of whichever implementation is live right now (default or a guest's * override) — for `Module._resolveFilename`'s getter. Snapshotting matters: it * must NOT re-check the override on every call, or a guest that reads this to * save "the original" and later restores it (`Module._resolveFilename = original`) * would hand its own restored value back to itself as the override and recurse * forever. A snapshot is forever "whatever was running at read time", exactly * what "the original" means to a caller doing a save/patch/restore. */export function currentResolveFilename(): ResolveFilenameFn { return resolveFilenameOverride ?? defaultResolveFilename;}
/** Whether `name` (as `Module._resolveFilename`/`resolve()` handed back) names a * builtin — `node:fs` or bare `fs`, the two forms Node accepts — and the bare id * if so. Shared by require(), require.resolve and Module._load. */export function builtinIdOf(name: string): BuiltinId | null { const id = name.replace(/^node:/, ""); if (!isBuiltinId(id)) return null; if (!name.startsWith("node:") && SCHEME_ONLY_BUILTINS.has(id)) return null; return id;}
/** Node's require.cache IS Module._cache — the exact same object, not a lookalike * (a guest does `delete require.cache[id]` and `Module._cache[id]` interchangeably). * One Proxy per worker, built lazily so modules.ts never needs ctx until a require() * actually happens; module.ts (node:module) hands back this same instance. */let cacheViewSingleton: Record<string, unknown> | null = null;export function cacheView(ctx: RequireContext): Record<string, unknown> { cacheViewSingleton ??= new Proxy({} as Record<string, unknown>, { get: (_t, key) => (typeof key === "string" ? ctx.cache.get(key) : undefined), set: (_t, key, value) => { if (typeof key === "string") ctx.cache.set(key, value as ModuleLike); return true; }, has: (_t, key) => typeof key === "string" && ctx.cache.has(key), deleteProperty: (_t, key) => { if (typeof key === "string") ctx.cache.delete(key); return true; }, ownKeys: () => [...ctx.cache.keys()], getOwnPropertyDescriptor: (_t, key) => (typeof key === "string" && ctx.cache.has(key) ? { enumerable: true, configurable: true, value: ctx.cache.get(key) } : undefined), }); return cacheViewSingleton;}
export function makeRequire(ctx: RequireContext, filename: string): (specifier: string) => unknown { const require = (specifier: string): unknown => { const resolved = resolveFilename(ctx, specifier, filename, false); const builtinId = builtinIdOf(resolved); if (builtinId !== null) return loadBuiltin(builtinId, ctx); return loadCjsByPath(ctx, resolved, filename); }; (require as { resolve?: unknown }).resolve = (specifier: string): string => resolveFilename(ctx, specifier, filename, false); (require as { cache?: unknown }).cache = cacheView(ctx); return require;}