Hyperlinkv0.8.0-beta.28

Node

<Self, ROut = never>(): {
  (key: string): NodeTagClass<Self, ROut, BareAddress>
  (
    key: string,
    target: {
      readonly path: string
      readonly kind?: "IpcSocket"
      readonly onConflict?: OnConflict
    }
  ): NodeTagClass<Self, ROut, IpcAddress>
  (key: string, target: number | `:${number}`): NodeTagClass<
    Self,
    ROut,
    HttpAddress
  >
  (
    key: string,
    target: `ws://${string}` | `wss://${string}`
  ): NodeTagClass<Self, ROut, WsAddress>
  (
    key: string,
    target: `http://${string}` | `https://${string}`
  ): NodeTagClass<Self, ROut, HttpAddress>
  (
    key: string,
    target: {
      readonly url: `ws://${string}` | `wss://${string}`
      readonly kind?: "WebSocket"
      readonly onConflict?: OnConflict
    }
  ): NodeTagClass<Self, ROut, WsAddress>
  (
    key: string,
    target: {
      readonly url: string
      readonly kind: "WebSocket"
      readonly onConflict?: OnConflict
    }
  ): NodeTagClass<Self, ROut, WsAddress>
  (
    key: string,
    target: {
      readonly url: string
      readonly kind: "Http"
      readonly onConflict?: OnConflict
    }
  ): NodeTagClass<Self, ROut, HttpAddress>
  <const T extends ShorthandTarget>(key: string, target: T): NodeTagClass<
    Self,
    ROut,
    MultiAddress<KindsOf<T>>
  >
  (
    key: string,
    target:
      | string
      | {
          readonly url: string
          readonly kind?: ProtocolKind
          readonly onConflict?: OnConflict
        }
  ): NodeTagClass<Self, ROut, UrlAddressLoose>
  (key: string, target?: LooseNodeTarget): NodeTagClass<
    Self,
    ROut,
    | BareAddress
    | IpcAddress
    | HttpAddress
    | WsAddress
    | UrlAddressLoose
    | MultiAddress<ProtocolKind>
  >
}

Declare a node — a named transport endpoint a resource connects to. Two-stage and keyed by a string, mirroring Effect's Context.Service<Self, Shape>()(key) (a node is a Context.Key, resolved by its key in the Context map) and every sibling factory (Hyperlink.Tag<Self>(), …). The second call infers the target shape, so the { http, ws } shorthand types its ProtocolKind set precisely. Optional catalog type param ROut (C2) — prefer import type for those handles (C4). Templates (no address until cloned) live on Node.Prototype:

class EdgeNode extends Node.Tag<EdgeNode>()("edge") {}                       // no address yet
class Worker extends Node.Tag<Worker>()("worker", 3001) {}                   // → http://localhost:3001/rpc, kind "Http"
class Mail extends Node.Tag<Mail>()("mail", "https://mail.internal/rpc") {}  // full url, as-is, kind "Http"
class Live extends Node.Tag<Live>()("live", { url: "wss://live/rpc" }) {}    // kind "WebSocket" (inferred from ws url)
class Push extends Node.Tag<Push>()("push", { url: "/rpc", kind: "WebSocket" }) {} // same-origin path, explicit kind
class Local extends Node.Tag<Local>()("local", { path: "/tmp/local.sock" }) {} // kind "IpcSocket" (Unix domain)
class Droplet extends Node.Tag<Droplet>()("droplet", { http: "http://d/rpc", ws: "ws://d/rpc" }) {} // multi-protocol
import type { Jobs, Emails } from "@app/contracts"
class AppWorker extends Node.Tag<AppWorker, Jobs | Emails>()("app/Worker", { path: "/tmp/w.sock" }) {}
class MailWorker extends Node.Prototype<MailWorker, Mail>("app/MailWorker") {}

The key is the service key. The optional address matches clientHttp's target: a port (3001 or ":3001"http://localhost:3001/rpc), a full url (used as-is), { url, kind } for an explicit endpoint, { path } for a Unix-domain socket (kind: "IpcSocket"), or the { http, ws, ipc } multi-protocol shorthand. The node carries ProtocolKind so the topology is self-describing about where AND how: connect(node) derives the transport with no protocol argument.

Dialable targets return an AddressedNode (kind: ProtocolKind) so Hyperlink.client(Tag, Worker) can auto-wire connect. Bare Node.Tag()("x") stays address-less (kind: undefined) — still needs explicit connect / lookup.

constructorsProtocolKindNodeclientHttpconnectAddressedNode
export const Tag = <Self, ROut = never>() => {
  function build(key: string): NodeTagClass<Self, ROut, BareAddress>;
  function build(
    key: string,
    target: {
      readonly path: string;
      readonly kind?: "IpcSocket";
      readonly onConflict?: OnConflict;
    },
  ): NodeTagClass<Self, ROut, IpcAddress>;
  function build(
    key: string,
    target: number | `:${number}`,
  ): NodeTagClass<Self, ROut, HttpAddress>;
  function build(
    key: string,
    target: `ws://${string}` | `wss://${string}`,
  ): NodeTagClass<Self, ROut, WsAddress>;
  function build(
    key: string,
    target: `http://${string}` | `https://${string}`,
  ): NodeTagClass<Self, ROut, HttpAddress>;
  function build(
    key: string,
    target: {
      readonly url: `ws://${string}` | `wss://${string}`;
      readonly kind?: "WebSocket";
      readonly onConflict?: OnConflict;
    },
  ): NodeTagClass<Self, ROut, WsAddress>;
  function build(
    key: string,
    target: {
      readonly url: string;
      readonly kind: "WebSocket";
      readonly onConflict?: OnConflict;
    },
  ): NodeTagClass<Self, ROut, WsAddress>;
  function build(
    key: string,
    target: {
      readonly url: string;
      readonly kind: "Http";
      readonly onConflict?: OnConflict;
    },
  ): NodeTagClass<Self, ROut, HttpAddress>;
  function build<const T extends ShorthandTarget>(
    key: string,
    target: T,
  ): NodeTagClass<Self, ROut, MultiAddress<KindsOf<T>>>;
  function build(
    key: string,
    target:
      | string
      | {
          readonly url: string;
          readonly kind?: ProtocolKind;
          readonly onConflict?: OnConflict;
        },
  ): NodeTagClass<Self, ROut, UrlAddressLoose>;
  function build(
    key: string,
    target?: LooseNodeTarget,
  ): NodeTagClass<
    Self,
    ROut,
    | BareAddress
    | IpcAddress
    | HttpAddress
    | WsAddress
    | UrlAddressLoose
    | MultiAddress<ProtocolKind>
  >;
  function build(
    key: string,
    target?: LooseNodeTarget,
  ): NodeTagClass<
    Self,
    ROut,
    | BareAddress
    | IpcAddress
    | HttpAddress
    | WsAddress
    | UrlAddressLoose
    | MultiAddress<ProtocolKind>
  > {
  // matches clientHttp's target: a port / ":port" / url resolves to an /rpc url; an explicit
  // `{ url }` is used verbatim. IPC nodes omit `url`. Bad positional strings do **not** throw —
  // stamp {@link InvalidHttpTarget} and leave the node unaddressed (fail on connect / clientHttp).
  let httpPort: number | undefined;
  let url: string | undefined;
  let path: string | undefined;
  let kind: ProtocolKind | undefined;
  let endpoints: Endpoints;
  let invalidTarget: InvalidHttpTarget | undefined;
  if (isShorthandTarget(target)) {
    // Multi-protocol shorthand `{ http?, ws?, ipc? }` — build the transport set; the primary
    // (for single-protocol readers) is http > ws > ipc. `connect` selects from the full set.
    const httpUrl = target.http;
    const wsUrl = target.ws;
    const ipcPath = target.ipc;
    endpoints = {
      ...(httpUrl !== undefined ? { Http: { url: httpUrl } } : {}),
      ...(wsUrl !== undefined ? { WebSocket: { url: wsUrl } } : {}),
      ...(ipcPath !== undefined ? { IpcSocket: { path: ipcPath } } : {}),
    };
    url = httpUrl ?? wsUrl;
    path = ipcPath;
    kind =
      httpUrl !== undefined
        ? "Http"
        : wsUrl !== undefined
          ? "WebSocket"
          : ipcPath !== undefined
            ? "IpcSocket"
            : undefined;
  } else {
    path = typeof target === "object" && target !== null ? target.path : undefined;
    if (path !== undefined || target === undefined) {
      url = undefined;
    } else if (typeof target === "object") {
      url = target.url;
    } else {
      // Bare-**port** shorthand (`3009` / `":3009"`) — carry the port so the DIAL resolves the host via
      // the client Config (`url` below is only a localhost preview). A full url has no port to carry.
      httpPort =
        typeof target === "number"
          ? target
          : /^:\d+$/.test(target)
            ? Number(target.slice(1))
            : undefined;
      const resolved = resolveHttpTarget(target);
      if (Result.isSuccess(resolved)) {
        url = resolved.success;
      } else {
        invalidTarget = resolved.failure;
        url = undefined;
      }
    }
    // `kind` is the SSOT for *how* to reach the node: explicit `{ kind }` wins; else `path` →
    // IpcSocket, `ws(s)://` → WebSocket, any other url → Http. Bare / invalid leave kind undefined.
    kind =
      (typeof target === "object" && target !== null ? target.kind : undefined) ??
      (path !== undefined
        ? "IpcSocket"
        : url === undefined
          ? undefined
          : url.startsWith("ws://") || url.startsWith("wss://")
            ? "WebSocket"
            : "Http");
    // The transport set — one entry from the resolved primary (multi-protocol shorthand / `over*`
    // add more). `connect` selects from it; the server asserts its transport is a member.
    endpoints =
      kind === "IpcSocket" && path !== undefined
        ? { IpcSocket: { path } }
        : kind === "WebSocket" && url !== undefined
          ? { WebSocket: { url } }
          : kind === "Http" && url !== undefined
            ? { Http: { url } }
            : {};
  }
  // Advertise conflict policy — an explicit `{ onConflict }` on the target wins (shorthand included);
  // an ordinary node defaults to `"inherit"` (resolved up the chain at advertise time).
  const onConflict: OnConflict = onConflictFromTarget(target) ?? "inherit";
  return assembleNode<
    Self,
    ROut,
    | BareAddress
    | IpcAddress
    | HttpAddress
    | WsAddress
    | UrlAddressLoose
    | MultiAddress<ProtocolKind>
  >(key, { url, path, kind, endpoints, onConflict, httpPort, invalidTarget });
  }

  return build;
};
Referenced by 2 symbols