pi-mono/packages/chord/src/next/remote/path.ts
Mario Zechner 8959ad6e68 feat(chord): Chord v2 slice 10, HTTP and workers
chord/next/http: a Web fetch handler built from the registry Http (routes, wraps,
sockets, hosts, relays, CSP sources) and Authenticators, responses as descriptions,
a security baseline on by default, Chord's own Node listener as a host-lifetime
object, nodeRequest for apps mounting it, links and plugin sockets.
chord/next/workers: a forked worker per address, started on demand, stopped when
idle, never restarted by itself, plugins built by the server and shipped.
Plugins: kinds with platform and host packages, createPluginRuntime, scripts(kind),
rollout results per host, imports of other plugins by name. Kernel: RouteHandle
(retry, suspend), no route backoff, anonymous host.root, origins only from relays.
ws is an optional peer dependency.
2026-10-31 20:40:36 +00:00

94 lines
3.2 KiB
TypeScript

// Paths (§3 recovery): where this host's upstream holds go, the parent link or a route address's link, across links.
// Each new link of a path holds every holder of the path again; when it drops, every holder is told.
import { ChordError } from "../data/index.ts";
import type { HostLink } from "./host-link.ts";
import type { RelayedHold } from "./relay.ts";
import type { RemoteObject } from "./remote-object.ts";
/** What a path holds on its links: one of this host's remote references, or a hold it relays. */
export type Upstream = RemoteObject | RelayedHold;
/**
* What changed on a path without a link, for its route: a holder attached, its link dropped (its holders went
* loading), its last holder left.
*/
export type PathEvent =
| { readonly kind: "attached"; readonly holder: Upstream }
| { readonly kind: "dropped" }
| { readonly kind: "released" };
const RELEASED: PathEvent = { kind: "released" };
const DROPPED: PathEvent = { kind: "dropped" };
/**
* The parent link or one route address: its current link and its holders, held on that link and again on every next
* one. `changed` runs when it gets a holder without a link, loses its link or its last holder (a route decides whether
* to resolve), saying which.
*/
export class Path {
readonly #holders = new Set<Upstream>();
readonly #changed: (event: PathEvent) => void;
#link: HostLink | undefined;
/** Whether it ever had a link: a host never connected to a parent has no parent path to wait on. */
#linked = false;
constructor(changed: (event: PathEvent) => void) {
this.#changed = changed;
}
/** The current link while one is open. */
get link(): HostLink | undefined {
return this.#link;
}
/** Whether it had a link once (it may be down now: its holders wait for the next). */
get linked(): boolean {
return this.#linked;
}
/** Whether anything is held over this path. */
get held(): boolean {
return this.#holders.size > 0;
}
/** Holds `holder` over this path: on the current link now, and on every next one. */
attach(holder: Upstream): void {
this.#holders.add(holder);
if (this.#link === undefined) this.#changed({ kind: "attached", holder });
else holder.attach(this.#link);
}
/** `holder` was released or ended; no later link holds it. */
detach(holder: Upstream): void {
this.#holders.delete(holder);
if (this.#holders.size === 0) this.#changed(RELEASED);
}
/** `link` is the path's link now: every holder is held on it. */
connected(link: HostLink): void {
this.#link = link;
this.#linked = true;
for (const holder of this.#holders) holder.attach(link);
}
/** `link` closed (its sides ended their calls already): every holder goes loading until the next link. */
disconnected(link: HostLink): void {
if (this.#link !== link) return;
this.#link = undefined;
const error = new ChordError({ code: "unavailable", message: "the link closed" });
for (const holder of this.#holders) holder.dropped(error);
this.#changed(DROPPED);
}
/** Its address cannot be reached for now (a route's `resolve` failed, a suspension): every holder fails waiting. */
unreachable(error: ChordError): void {
for (const holder of this.#holders) holder.unreachable(error);
}
/** Every holder, for the host's close. */
holders(): Upstream[] {
return [...this.#holders];
}
}