cluster

js/cluster.ts

fino:cluster - public API for cluster participation.

Cluster membership uses WebTransport over HTTP/3. This module establishes the peer view consumed by cluster services; realm allocation remains owned by the reactor scheduler.

Learn more:

startCluster({ port }) - Start a seed server on the given port AND participate as a worker. The calling node becomes both the coordinator and an execution target. This is the entry point for the first node.

joinCluster({ seed }) - Connect to an existing seed node and join its membership view.

Only one cluster connection per process is supported. Calling either function when already connected throws.

Current release scope uses one trusted seed node. Seed election, cluster authentication, and distributed scheduler coordination remain future work.

import { startCluster, leaveCluster } from 'fino:cluster';
await startCluster({ port: 9999, nodeId: 'seed-a', tls: { cert: './cert.pem', key: './key.pem' } });
leaveCluster();

Interfaces

interface StartClusterOptions {

Options for starting the first cluster seed node.

import { startCluster, type StartClusterOptions } from 'fino:cluster';

const opts: StartClusterOptions = { port: 9999, nodeId: 'seed-a' };
await startCluster(opts);

Properties

port: number

TCP/UDP port the seed HTTP/3 WebTransport server will listen on.

The port must be available on the local host. There is no default because the first cluster node must advertise a stable address to workers.

import { startCluster } from 'fino:cluster';

await startCluster({ port: 9999, tls: { cert: './cert.pem', key: './key.pem' } });
hostname?: string

Interface address the seed server binds.

Defaults to all interfaces (0.0.0.0, or :: when an IPv6 hostname selects the IPv6 family). The seed also joins itself as a worker; for that self-connection wildcard binds (0.0.0.0, ::) are mapped to the matching loopback address, and bare IPv6 literals are bracketed.

nodeId?: string

Optional node identifier.

When omitted, the seed uses seed-{port}. Choose a stable ID if logs or cluster diagnostics need to correlate restarts.

import { startCluster } from 'fino:cluster';

await startCluster({ port: 9999, nodeId: 'primary-seed' });
tls: { cert: string; key: string; }

TLS certificate and key files for the seed's HTTPS/HTTP/3 server.

cert and key are filesystem paths to PEM files, not inline PEM text. TLS is required — WebTransport rides on HTTP/3 over QUIC, so there is no plaintext mode. For certificates not signed by a system-trusted CA, joining workers should trust the issuing CA via tls.ca or pin the certificate via serverCertificateHashes.

import { startCluster } from 'fino:cluster';

await startCluster({ port: 9999, tls: { cert: './cert.pem', key: './key.pem' } });
path?: string

URL path of the cluster WebTransport endpoint on the seed server.

Defaults to /__fino_cluster. A missing leading slash is added. Workers must connect to this exact path; WebTransport sessions requested on any other path are rejected with a 404, and plain HTTP requests receive a placeholder response.

h3?: true | { quic?: Record<string, unknown>; }

HTTP/3 listener configuration forwarded to the underlying server.

HTTP/3 is always enabled — the cluster transport requires it — so the only useful form is an object whose quic field tunes the QUIC transport (timeouts, flow control, and similar low-level knobs). Omitting this or passing true uses the defaults.

interface JoinClusterOptions {

Options for joining an existing cluster seed.

import { joinCluster, type JoinClusterOptions } from 'fino:cluster';

const opts: JoinClusterOptions = { seed: 'https://127.0.0.1:9999/__fino_cluster' };
await joinCluster(opts);

Properties

seed: string | URL

HTTPS WebTransport URL of the seed node.

The URL must include the https:// scheme and a reachable host and port. When the path is omitted, /__fino_cluster is used.

import { joinCluster } from 'fino:cluster';

await joinCluster({ seed: 'https://seed.example.test:9999/__fino_cluster' });
nodeId?: string

Optional worker node identifier.

When omitted, a short random worker-* ID is generated. Provide a stable value for predictable logs or cluster placement diagnostics.

import { joinCluster } from 'fino:cluster';

await joinCluster({ seed: 'https://127.0.0.1:9999/__fino_cluster', nodeId: 'worker-a' });
tls?: { ca?: string; rejectUnauthorized?: boolean; }

TLS verification settings for the connection to the seed.

ca is a filesystem path to a PEM CA bundle trusted for the seed's certificate — use it when the seed's certificate is not signed by a system-trusted CA. rejectUnauthorized: false skips verification entirely; avoid it outside local development, since it permits man-in-the-middle attacks — prefer ca or serverCertificateHashes.

import { joinCluster } from 'fino:cluster';

await joinCluster({
  seed: 'https://seed.example.test:9999/__fino_cluster',
  tls: { ca: './cluster-ca.pem' },
});
quic?: Record<string, unknown>

QUIC transport tuning forwarded to the WebTransport session.

Low-level knobs (timeouts, flow control, and similar) applied to the QUIC connection under the WebTransport session. Most deployments should omit this and use the defaults.

serverCertificateHashes?: readonly WebTransportHash[]

Certificate pinning hashes for the seed's certificate.

Follows the WebTransport serverCertificateHashes model: the connection is accepted when the seed's certificate matches one of the given hashes, bypassing CA-based validation. Useful for self-signed deployments where distributing a CA bundle is impractical.

import { joinCluster } from 'fino:cluster';

const certSha256 = new Uint8Array(32); // SHA-256 digest of the seed certificate
await joinCluster({
  seed: 'https://127.0.0.1:9999/__fino_cluster',
  serverCertificateHashes: [{ algorithm: 'sha-256', value: certSha256 }],
});

Functions

async function startCluster(opts: StartClusterOptions): Promise<void>

Start the cluster seed server and participate as a worker on this node.

The seed is the current membership hub. Realm placement is not performed by this API; the scheduler will consume membership when distributed allocation is added.

This call returns immediately after the seed starts listening. The event loop keeps the server alive as long as there are connected peers.

Throws if this process is already connected to a cluster. The function resolves with void after the seed transport is listening and the local worker client has connected to it.

import { startCluster, leaveCluster } from 'fino:cluster';

await startCluster({ port: 9999, nodeId: 'seed-a' });
leaveCluster();

async function joinCluster(opts: JoinClusterOptions): Promise<void>

Connect to an existing seed and register this node as a worker.

After this call the node receives membership updates from the seed.

Throws if this process is already connected or if the seed URL cannot be reached, and throws a TypeError when the seed URL does not use the https: scheme. The returned promise resolves once the worker transport is connected and the client loop has started.

import { joinCluster, leaveCluster } from 'fino:cluster';

await joinCluster({ seed: 'https://127.0.0.1:9999/__fino_cluster', nodeId: 'worker-a' });
leaveCluster();

function leaveCluster(): void

Disconnect from the cluster.

The call is synchronous and idempotent. It stops the active worker client and seed server, if present, then clears module-level cluster state.

import { startCluster, leaveCluster } from 'fino:cluster';

await startCluster({ port: 9999 });
leaveCluster();