Repository navigation
Conversation
A statement-level require() of a package nothing installed threw a plain Error; the expression form already carried Node's MODULE_NOT_FOUND code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@types/node 20 and older declare NodeJS.Timeout and NodeJS.Immediate as classes; 22+ (and scriptc's own declarations) declare interfaces. Accept both as the native timer handle. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…encies unloaded
package-shims.ts registers implementation modules shipped with the
compiler per package and major version (chosen by the importer's
installed version) and reads the project's never-loaded packages from
"scriptc": { "packages": { "<name>": "unloaded" } }. Module edges go
through resolveProjectImport; the npm resolution, the npm-edge walk and
the --npm-static set skip such packages; the tsgo host reads a shimmed
package's index.d.ts as a re-export of its implementation; an unloaded
package's require() throws Node's not-installed MODULE_NOT_FOUND.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A TypeScript implementation over node:net, node:tls and node:events: Dispatcher, Client (HTTP/1.1 with keep-alive), Pool, Agent, buildConnector, request(), errors, the global dispatcher, fetch, Headers and the diagnostics_channel publications, with per-version behavior. Differential npm cases run the vendored real undici under Node. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Additional findings
-
packages/compiler/shims/undici/core/client.ts:852-857— When a response stays paused longer than bodyTimeout, resuming it leaves the timeout disabled, so a stalled server can hold the request and connection indefinitely. -
packages/compiler/shims/undici/core/request.ts:135-143— Diagnostics subscribers calling the declared request.addHeader() method receive a TypeError because the published request view omits that method, breaking tracing-header injection. -
packages/compiler/shims/undici/core/request.ts:230-234— A Host header containing CRLF bypasses value validation and is written verbatim to the socket, allowing header or request injection in programs using undici 7.x.
# Conflicts: # packages/compiler/src/frontend/lowering/builtins/timers.ts # packages/compiler/src/frontend/type-mapper.ts
…s as properties @types/node's fetch declarations (undici-types) spell the Headers methods and the body mixin as function-typed properties (readonly append: (...) => void), where scriptc's own declarations use methods. The receiver call lowering treated such a standard-library property as a stored callback: Headers.forEach was refused at build time and append/get/set/... compiled to a runtime 'is not a function'. Standard-library members now take the shared receiver dispatch either way. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The net unit drained its poller every time the loop came back to the I/O station, including the restarts that drain microtasks. I/O that became ready during the iteration's own callbacks (a loopback connect started by one of them) was delivered before the check phase, so setImmediate callbacks queued earlier ran after it; Node delivers it in the next iteration's poll phase. The poller now drains once per iteration (a full batch keeps draining), reopening after the check phase. Back-to-back undici requests now reuse connections like Node, so the fixtures drop their setImmediate turn between requests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fetch's input under @types/node is string | URL | Request. A union with a checked-dynamic arm collapses to the checked-dynamic representation only when every sibling survives the collapse; URL did not qualify, so the type (and anything typed as typeof globalThis.fetch, such as a custom fetch cast through unknown) did not compile. URL's handle boxes by reference, keeps its accessors and setters, answers instanceof URL and checks back out to the typed value, so URL arms now join the collapse. A fetch() input typed string | URL also converts through the checked-dynamic input path instead of being narrowed to a string, which threw on the URL arm. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The undici5 and undici7 npm cases now also run peers.ts against Node with the vendored undici: header values with CR or LF, chunk-size lines that are valid, malformed, overflowing or 1 MiB long, and fetch bodies that are endless (read once, then cancelled), gzipped, truncated, empty, corrupt, cut off, aborted after the headers, redirected while endless, or carry six content codings. The undici5 case leaves out the Host values, which undici 5.12 writes into the request head, and the two fetch cases where undici 5.12 differs (six codings, and an endless redirect, which fails with a TypeError in 5.12). package-shims.test.ts compiles two programs in copies of the cases: one reads 64 MiB chunk-size lines and 64 MiB plain and gzipped fetch bodies from a separate Node peer and checks that RSS stays under 48 MiB (plain lane), the other checks that the 5.x profile rejects a Host value with CR or LF with undici 5.29.0's error.
The undici cases compile the built-in undici implementation's module graph; a sanitized build of it on a CI runner outlasted the default 120 s per case.
A full batch of 64 events polled again without returning to the event loop. When synchronous socket callbacks kept 64 events ready on every drain, timers and setImmediate callbacks never ran. The poll phase now drains at most 48 full batches, the count libuv's uv__io_poll uses, and the budget carries across the reopen that lets microtasks run, so the remaining events wait for the next loop iteration.
Replaces the single-parent "Merge main" squash (6ab9a54) with a two-parent merge of the same tree. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The implicit constructor forwarded its arguments to EventEmitter's options-taking constructor, which compiles to a deferred runtime fence. Dispatcher takes no options, so it calls super() directly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A redirect fetch followed or rejected was marked to drain: its body was read and dropped until the server ended it, and the abort listener was already removed. A body that never ends kept the connection busy after cancellation and stalled a pool of one connection. Following or rejecting a redirect now aborts its request, so the connection closes. undici 7 leaves that connection open with its body paused, which stalls the same pool; the follow-up request here gets a new connection.
A redirect fetch followed or rejected was marked to drain: its body was read and dropped until the server ended it, and the abort listener was already removed. A body that never ends kept the connection busy after cancellation and stalled a pool of one connection. Following or rejecting a redirect now aborts its request, so the connection closes. undici 7 leaves that connection open with its body paused, which stalls the same pool; the follow-up request here gets a new connection.
… per loop iteration (#1435)
Summary
scriptc now implements
undiciitself and compiles that implementation in place of the package's published JavaScript. It does this through a general mechanism that maps an npm package name to a TypeScript module shipped with the compiler. The same registry also lets a project mark optional dependencies as never loaded.Package shims (
packages/compiler/src/frontend/package-shims.ts)packages/compiler/shims/. undici 5.x maps toshims/undici/v5.tsand 7.x toshims/undici/v7.ts.package.jsonpicks the entry. A program with undici 5.12 in one workspace package and 7.30 in another compiles each against its own version.resolveProjectImport(preflight, module order, the CommonJS link). It runs after provenance and tsconfigpaths, so a project mapping still wins.resolveNpmImport7, the pipeline's npm-edge walk and the--npm-staticpackage set all skip shimmed packages.paths, becausepathsmaps every importer to one file. The installed package'sindex.d.tsreads asexport * from "<shim>", so types resolve per importer, the way the real declarations do.skipLibCheck."scriptc": { "packages": { "encoding": "unloaded" } }in the entry'spackage.jsonmarks a package as never loaded.require("encoding")then throws Node's not-installedMODULE_NOT_FOUND, sotry { require("encoding") } catch {}takes its absent branch, and the package never joins the program. This applies decision D6 to node-fetch'sencoding.importof such a package reports Node'sERR_MODULE_NOT_FOUND.pathsmapping already does this today.files) and in native distributions (declarations/shims).The undici shim (
packages/compiler/shims/undici/)The shim is plain TypeScript over
node:net,node:tlsandnode:events.core/*is shared, and thev5.ts/v7.tsentries subclassClient,PoolandAgentwith a version profile, soinstanceofand default factories match each version.Dispatcheris an EventEmitter with thedispatch(opts, handler)contract. It accepts both the 5.x handler callbacks and 7.x's controller API (onRequestStartand the rest).Clientis HTTP/1.1 over one connection: keep-alive reuse with Keep-Alive hints,headersTimeout/bodyTimeout/idle timeouts, a request queue,maxRequestsPerClient, and back-pressure. 7.x uses undici's 499 ms fast timers for headers and body timeouts.Poolcreates clients up toconnectionsand exposesstats. 7.x drops clients whose connection failed.Agentkeeps one dispatcher per origin, withfactory(origin, opts)andconnectoptions. 7.x closes an origin's dispatcher once its connections are gone.interceptors.Client,.Pooland.Agent. 7.x ignores them, as undici 7 does.buildConnectordials overnet/tls, honorsconnect.lookupfor TCP, and applies the connect timeout.request(). Returns{ statusCode, statusText (7.x), headers, trailers, opaque, context, body }. Abort behaves per version: 5.x rejects withRequestAbortedError, 7.x withsignal.reason. 5.x supportsthrowOnError; 7.x rejects it as undici 7 does.on/once/pause/resume/destroy, plustext/json/arrayBuffer/bytes/blob/dump. It is built on EventEmitter because scriptc does not compilenode:streamsubclasses under @types/node.errorsmodule has undici's classes, names, messages andUND_ERR_*codes, with each version's export set.setGlobalDispatcher/getGlobalDispatcherare provided.fetch. Without a dispatcher it is the nativefetch. Through a dispatcher it follows undici's fetch: default headers, redirect follow/manual/error, bad-port blocking,fetch failederrors with a cause, gzip/deflate decoding, and abort.Headersre-exports the native class.undici:request:*andundici:client:*with a stable boxed request per request, soWeakMapcorrelation works.The rest of undici isn't declared, so using it is a compile-time type error. Known differences are listed in the docs:
for await.fetchthrough a dispatcher returns an emptyurlandredirected: false, offers onlygzip, deflate, and sends only string and byte bodies.CONNECT, stream bodies andsocketPathare not supported.Small compiler fixes found on the way
String(x)and template literals overstring | URLunions now compile. The URL arm converts throughhref.require()of a missing package now throws withcode: "MODULE_NOT_FOUND", as the expression form already did.NodeJS.Timeout/Immediatenow map to the timer handle, as the interface form in @types/node 22+ already did.Evidence
node:httpserver:tests/fixtures/npm/cases/undici5(5.12.0) andundici7(7.30.0). Byte-identical stdout and exit, plain and sanitized, and stable over repeated runs. They cover:request()with text, json, arrayBuffer, multi-value headers, chunked bodies and the default PUT method.Agentsubclass with a per-originPoolfactory, a wrappedbuildConnector, a client interceptor and pool stats.Poolconnection limit with queued and size stats,Client, andconnect.lookup.dispatch.throwOnError(5.x) and its 7.x refusal, protocol, and a destroyed body.fetchthrough an agent (POST, manual and followed redirects, refused, abort) and without one.unloaded-optional-dependency: a node-fetch-style optionalencodingrequire.tests/harness/package-shims.test.tscompiles it withencodinginstalled and asserts the output Node prints when it is absent. The same test covers an undici that isn't installed and an unsupported major version.packages/compiler/test/undici-shim-types.test.ts:6250-string-of-string-or-url. Corpus2195-require-missing-packagenow also prints the error code.2195-require-missing-package, from theMODULE_NOT_FOUNDcode fix (+144 B). Self-compile time +0.4%, within tolerance.tests/harness/node-types-timers.test.ts.pnpm lint,pnpm format:check,pnpm test:ts7 --baselines-only(4 new order baselines recorded, none changed), the docs gate, the self-host IR build (exit 0, no diagnostics), and the npm, npm-static and frontend unit suites.node24-url-authorityfails identically on main in the sandbox.fetchthrough a dispatcher:Headers.forEachhas no lowering under @types/node.🤖 Generated with Claude Code