180 lines · 5.1 KB · TypeScript
RawDownload
import * as Net from "node:net"; import { Data, Effect, Layer, ServiceMap } from "effect"; export class NetError extends Data.TaggedError("NetError")<{  readonly message: string;  readonly cause?: unknown;}> {} function isErrnoExceptionWithCode(cause: unknown): cause is {  readonly code: string;} {  return (    typeof cause === "object" &&    cause !== null &&    "code" in cause &&    typeof (cause as { readonly code: unknown }).code === "string"  );} const closeServer = (server: Net.Server) => {  try {    server.close();  } catch {    // Ignore close failures during cleanup.  }}; const tryReservePort = (port: number): Effect.Effect<number, NetError> =>  Effect.callback<number, NetError>((resume) => {    const server = Net.createServer();    let settled = false;     const settle = (effect: Effect.Effect<number, NetError>) => {      if (settled) return;      settled = true;      resume(effect);    };     server.unref();     server.once("error", (cause) => {      settle(Effect.fail(new NetError({ message: "Could not find an available port.", cause })));    });     server.listen(port, () => {      const address = server.address();      const resolved = typeof address === "object" && address !== null ? address.port : 0;      server.close(() => {        if (resolved > 0) {          settle(Effect.succeed(resolved));          return;        }        settle(Effect.fail(new NetError({ message: "Could not find an available port." })));      });    });     return Effect.sync(() => {      closeServer(server);    });  }); export interface NetServiceShape {  /**   * Returns true when a TCP server can bind to {host, port}.   */  readonly canListenOnHost: (port: number, host: string) => Effect.Effect<boolean>;   /**   * Checks loopback availability on both IPv4 and IPv6 localhost addresses.   */  readonly isPortAvailableOnLoopback: (port: number) => Effect.Effect<boolean>;   /**   * Reserve an ephemeral loopback port and release it immediately.   */  readonly reserveLoopbackPort: (host?: string) => Effect.Effect<number, NetError>;   /**   * Resolve an available listening port, preferring the provided port first.   */  readonly findAvailablePort: (preferred: number) => Effect.Effect<number, NetError>;} /** * NetService - Service tag for startup networking helpers. */export class NetService extends ServiceMap.Service<NetService, NetServiceShape>()(  "@t3tools/shared/Net/NetService",) {  static readonly layer = Layer.sync(NetService, () => {    /**     * Returns true when a TCP server can bind to {host, port}.     * `EADDRNOTAVAIL` is treated as available so IPv6-absent hosts don't fail     * loopback availability checks.     */    const canListenOnHost = (port: number, host: string): Effect.Effect<boolean> =>      Effect.callback<boolean>((resume) => {        const server = Net.createServer();        let settled = false;         const settle = (value: boolean) => {          if (settled) return;          settled = true;          resume(Effect.succeed(value));        };         server.unref();         server.once("error", (cause) => {          if (isErrnoExceptionWithCode(cause) && cause.code === "EADDRNOTAVAIL") {            settle(true);            return;          }          settle(false);        });         server.once("listening", () => {          server.close(() => {            settle(true);          });        });         server.listen({ host, port });         return Effect.sync(() => {          closeServer(server);        });      });     /**     * Reserve an ephemeral loopback port and release it immediately.     * Returns the reserved port number.     */    const reserveLoopbackPort = (host = "127.0.0.1"): Effect.Effect<number, NetError> =>      Effect.callback<number, NetError>((resume) => {        const probe = Net.createServer();        let settled = false;         const settle = (effect: Effect.Effect<number, NetError>) => {          if (settled) return;          settled = true;          resume(effect);        };         probe.once("error", (cause) => {          settle(Effect.fail(new NetError({ message: "Failed to reserve loopback port", cause })));        });         probe.listen(0, host, () => {          const address = probe.address();          const port = typeof address === "object" && address !== null ? address.port : 0;          probe.close(() => {            if (port > 0) {              settle(Effect.succeed(port));              return;            }            settle(Effect.fail(new NetError({ message: "Failed to reserve loopback port" })));          });        });         return Effect.sync(() => {          closeServer(probe);        });      });     return {      canListenOnHost,      isPortAvailableOnLoopback: (port) =>        Effect.zipWith(          canListenOnHost(port, "127.0.0.1"),          canListenOnHost(port, "::1"),          (ipv4, ipv6) => ipv4 && ipv6,        ),      reserveLoopbackPort,      findAvailablePort: (preferred) =>        Effect.catch(tryReservePort(preferred), () => tryReservePort(0)),    } satisfies NetServiceShape;  });}