import { NodeHttpClient } from "@effect/platform-node"; import Undici from "@effect/platform-node/Undici"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import type * as HttpClient from "effect/unstable/http/HttpClient"; import { connectTimeoutFor, TransportFailureVisibility } from "@packall/registry-npm"; import { Transport } from "../transport.js"; /** * `connect` is where TLS trust has to land. * * undici verifies against Node's bundled root store and offers no way to say * otherwise per request — the dispatcher is the only place that decision can be * made, which is why the whole `.npmrc` reaches this far down. Without it, * `strict-ssl=false` and `cafile` are read, understood, and then ignored, and a * registry behind an internal CA fails a handshake that npm completes. * * The CA list is *added* to the system store rather than replacing it, and is * left off entirely when empty so the default store stays in place. */ const layerFor = ({ requestTimeoutMs, tls, }: Transport.Options): Layer.Layer => NodeHttpClient.layerUndiciNoDispatcher.pipe( Layer.provide( Layer.effect(NodeHttpClient.Dispatcher)( Effect.acquireRelease( Effect.sync( () => new Undici.Agent({ connect: { // Interrupting an Effect does not close a socket // still mid-handshake, and undici's own connect // timeout is 10s. Against a blackholed host the // run prints "registry unreachable" on schedule // and then sits there, because the pending socket // keeps Node's event loop alive — which is // indistinguishable from a hang. timeout: connectTimeoutFor(requestTimeoutMs), rejectUnauthorized: tls.rejectUnauthorized, ...(tls.ca.length > 0 ? { ca: [...tls.ca] } : {}), }, }), ), (dispatcher) => Effect.promise(() => dispatcher.destroy()), ), ), ), ); /** The undici-backed HTTP backend. */ export const layerTransport: Layer.Layer = Layer.succeed(Transport)({ layerFor, canProbeRoot: true, failureVisibility: TransportFailureVisibility.Detailed, });