Hyperlinkv0.8.0-beta.28

Node

<Self, ROut = never>(
  name: string,
  options?: PrototypeOptions
): Context.ServiceClass<Self, string, Record<string, never>> & {
  isPrototype: true
  kind: ProtocolKind | undefined
  ipc: "unix" | "nPipe"
  onConflict: OnConflict
  url: undefined
  path: undefined
  [catalogSym]: ROut | undefined
  make: {
    (
      cloneName: string,
      target: {
        readonly path: string
        readonly kind?: "IpcSocket"
        readonly onConflict?: OnConflict
      }
    ): IpcNodeTagClass<Self, ROut>
    (
      cloneName: string,
      target: {
        readonly url: `ws://${string}` | `wss://${string}`
        readonly kind?: "WebSocket"
        readonly onConflict?: OnConflict
      }
    ): WsNodeTagClass<Self, ROut>
    (
      cloneName: string,
      target: {
        readonly url: string
        readonly kind: "WebSocket"
        readonly onConflict?: OnConflict
      }
    ): WsNodeTagClass<Self, ROut>
    (
      cloneName: string,
      target: {
        readonly url: string
        readonly kind: "Http"
        readonly onConflict?: OnConflict
      }
    ): HttpNodeTagClass<Self, ROut>
    (
      cloneName: string,
      target: {
        readonly url: string
        readonly kind?: ProtocolKind
        readonly onConflict?: OnConflict
      }
    ): UrlNodeTagClass<Self, ROut>
  }
  instance: (
    suffix?: string
  ) => NodeTagClass<
    Self,
    ROut,
    | BareAddress
    | IpcAddress
    | HttpAddress
    | WsAddress
    | UrlAddressLoose
    | MultiAddress<ProtocolKind>
  > & {
    instanceSuffix?: string | undefined
    isDynamicInstance: true
    dynamicPrototypeKey: string
  }
  listen: <Serves extends ServeLayerList>(
    serves: Serves & ServesForCatalog<Exclude<ROut, undefined>, Serves>,
    listenOptions?: NamelessListenOptions
  ) => (
    suffix?: string
  ) => Layer.Layer<
    Layer.Success<Serves[number]> | ListenNode,
    never,
    Layer.Services<Serves[number]>
  >
}

Prototype Node template (D7) — a Node kind, nested on Node as Prototype. No address until cloned:

class MailWorker extends Prototype<MailWorker, Mail>("app/MailWorker") {}
// Named clone with a fixed address (class ctor):
class East extends MailWorker.make("East", { path: "/tmp/east.sock" }) {}
// Dynamic instances — same protocol siblings + Lookup pipe as unix/http/ws/nPipe:
const mailWorker = MailWorker.listen([Hyperlink.serve(Mail, impl)])
mailWorker().pipe(Layer.provide(Lookup.layer))
mailWorker("w1").pipe(Layer.provide(Lookup.layer))

Lookup is not baked in — pipe Lookup.layer / layerOptions / client like the protocol listen siblings.

constructorsNode
export const Prototype = <Self, ROut = never>(
  name: string,
  options?: PrototypeOptions,
) => {
  const protoOnConflict = options?.onConflict;
  const instance = (suffix?: string) => {
    const key =
      suffix !== undefined && suffix.length > 0 ? `${name}#${suffix}` : name;
    return Object.assign(
      Tag<Self, ROut>()(
        key,
        protoOnConflict !== undefined
          ? { onConflict: protoOnConflict }
          : undefined,
      ),
      {
        isDynamicInstance: true as const,
        dynamicPrototypeKey: name,
        ...(suffix !== undefined && suffix.length > 0
          ? { instanceSuffix: suffix }
          : {}),
      },
    );
  };
  function make(
    cloneName: string,
    target: {
      readonly path: string;
      readonly kind?: "IpcSocket";
      readonly onConflict?: OnConflict;
    },
  ): IpcNodeTagClass<Self, ROut>;
  function make(
    cloneName: string,
    target: {
      readonly url: `ws://${string}` | `wss://${string}`;
      readonly kind?: "WebSocket";
      readonly onConflict?: OnConflict;
    },
  ): WsNodeTagClass<Self, ROut>;
  function make(
    cloneName: string,
    target: {
      readonly url: string;
      readonly kind: "WebSocket";
      readonly onConflict?: OnConflict;
    },
  ): WsNodeTagClass<Self, ROut>;
  function make(
    cloneName: string,
    target: {
      readonly url: string;
      readonly kind: "Http";
      readonly onConflict?: OnConflict;
    },
  ): HttpNodeTagClass<Self, ROut>;
  function make(
    cloneName: string,
    target: {
      readonly url: string;
      readonly kind?: ProtocolKind;
      readonly onConflict?: OnConflict;
    },
  ): UrlNodeTagClass<Self, ROut>;
  function make(
    cloneName: string,
    target:
      | {
          readonly path: string;
          readonly kind?: "IpcSocket";
          readonly onConflict?: OnConflict;
        }
      | {
          readonly url: string;
          readonly kind?: ProtocolKind;
          readonly onConflict?: OnConflict;
        },
  ): IpcNodeTagClass<Self, ROut> | UrlNodeTagClass<Self, ROut> {
    const onConflict = target.onConflict ?? protoOnConflict;
    // Branch so each Tag call hits a dialable overload (not the loose union catch-all).
    if ("path" in target) {
      return Tag<Self, ROut>()(`${name}#${cloneName}`, {
        path: target.path,
        ...(target.kind !== undefined ? { kind: target.kind } : {}),
        ...(onConflict !== undefined ? { onConflict } : {}),
      });
    }
    return Tag<Self, ROut>()(`${name}#${cloneName}`, {
      url: target.url,
      ...(target.kind !== undefined ? { kind: target.kind } : {}),
      ...(onConflict !== undefined ? { onConflict } : {}),
    });
  }
  return Object.assign(Context.Service<Self, Record<string, never>>()(name), {
    isPrototype: true as const,
    kind: options?.kind,
    ipc: options?.ipc ?? ("unix" as const),
    onConflict: protoOnConflict ?? ("inherit" as const),
    url: undefined as undefined,
    path: undefined as undefined,
    [catalogSym]: undefined as ROut | undefined,
    make,
    /**
     * Dynamic instance Node — wire key `prototypeKey#suffix`, for {@link listen} /
     * {@link peersLayer} `self`. Prefer `.listen(serves)` to spawn.
     * Omit `suffix` to mint one at listen.
     */
    instance,
    /**
     * Curry a serve list into a dynamic-instance factory — sugar over
     * {@link unix} / {@link http} / {@link ws} / {@link nPipe}
     * `(proto.instance(suffix), serves)` by `kind` / `ipc`.
     * Same Lookup story as those siblings: pipe `Lookup.layer` when advertise needs it.
     * Keep in sync with the protocol listen siblings (handoff § Protocol listen siblings).
     * Returns a **Layer** only — after `Layer.build`, the minted Node is
     * {@link ListenNode} in context.
     */
    listen: <Serves extends ServeLayerList>(
      serves: Serves & ServesForCatalog<Exclude<ROut, undefined>, Serves>,
      listenOptions?: NamelessListenOptions,
    ): ((
      suffix?: string,
    ) => Layer.Layer<
      Layer.Success<Serves[number]> | ListenNode,
      never,
      Layer.Services<Serves[number]>
    >) =>
      (suffix?: string) => {
        // Explicit type args — avoid re-inferring `Serves` from the already-proven
        // intersection (nested `ServesForCatalog` would otherwise fail to unify).
        const node = instance(suffix);
        if (options?.kind === "Http") {
          return http<typeof node, Serves>(node, serves, listenOptions);
        }
        if (options?.kind === "WebSocket") {
          return ws<typeof node, Serves>(node, serves, listenOptions);
        }
        if (options?.ipc === "nPipe") {
          return nPipe<typeof node, Serves>(node, serves, listenOptions);
        }
        return unix<typeof node, Serves>(node, serves, listenOptions);
      },
  });
};