LAB429/ Cheetah product page ↗

Cheetah / Cheetah documentation

Node roles and factories

The three node factories are service-layer constructors. They expose the protocol roles to a host application without choosing an ASGI server, route layout, process model, or deployment topology.

WebSocket role

The WebSocket role authenticates the initial hello, negotiates the supported protocol, registers the logical client and current runtime, maintains heartbeat evidence, owns the live socket, delivers remote commands, and accepts command ACKs. A Redis registry records where a connection lives, but the actual WebSocket handle remains in its owning process.

The preferred form when a preset carries composition constraints is:

websocket_node = create_websocket_node(
    components=components,
    config=WebSocketNodeConfig(
        rest_message_endpoint="https://api.example.com/cheetah/messages",
    ),
)

The low-level form requires auth, registry, correlator, tracer, and config. dispatcher and node_id are optional in the signature, but they are required for the full coordinated reconnect and cross-node delivery behavior. Do not mix components= with individual dependencies.

WebSocketNodeConfig requires the externally usable REST message endpoint. Its defaults are:

SettingDefaultMeaning
heartbeat_interval_ms25000heartbeat interval advertised to the client
protocol_version"1.0"canonical wire version
supported_protocol_versionsonly the canonical versionexact accepted-version set; not a range
transport_security_modestrictreject insecure or undetermined external posture
server_configNoneno additional advertised client configuration
require_tenantFalseordinary dedicated-principal mode

Strict mode requires a secure advertised return endpoint and trustworthy knowledge of the external scheme. Behind a proxy, configure the adapter and proxy trust boundary correctly; an internal plaintext hop does not by itself tell Cheetah that the public connection was secure. debug_insecure is for explicit controlled development, with a client that also opts in to insecure transport.

REST role

The REST role accepts authenticated messages and client state returning over HTTP. It validates and fences responses, deduplicates accepted deliveries, writes retained evidence, wakes response waiters, resolves qualifying terminal RPCs, and updates optional topology registries.

create_rest_node() requires registry, deduplicator, history, correlator, tracer, and the keyword-only response_notifier. A token signer and the context and browser-window registries are optional. The notifier is not optional composition glue: pass the compatible member from the same component family that supplies the App role.

RestNodeConfig.max_ingest_bytes defaults to 1 MiB and can be set to None to disable the node-level limit, which is not recommended. Align the reverse proxy and web framework limits with this boundary. require_tenant defaults to False and is enabled by the dedicated shared-tenancy composition.

RestNode is not an HTTP server. The host endpoint must establish the trusted user or tenant principal and call the service with that authenticated context. The client-supplied message does not get to choose its storage or identity scope.

App role

The App role is the surface product code calls. It authorizes and addresses commands, checks connection evidence, admits work to the dispatcher, registers terminal correlation, reports delivery evidence, waits for retained responses, reads history and presence, and supports signed payload references when configured.

create_app_node() requires registry, dispatcher, correlator, history, tracer, and the keyword-only response_notifier. A token signer, central policy source, and parser registry are optional. REST and App must use compatible notifier implementations in the same deployment namespace; otherwise newly retained responses cannot reliably wake the intended application waiter.

The important AppNodeConfig defaults are:

SettingDefaultBoundary
default_command_timeout_ms30000terminal observation after a send attempt binds a runtime
default_reconnect_wait_ms45000bounded wait for a recently seen logical client to reconnect
max_reconnect_wait_ms300000maximum caller-selected reconnect wait
default_max_client_staleness_ms180000default age limit for retained sighting evidence
max_client_staleness_ms86400000maximum configurable sighting age
connection_freshness_ms50000age below which an active record is immediately usable
minimum_delivery_budget_ms10000time reserved for durable admission after reconnect waiting
max_connection_waiters1024App-process-wide pre-admission waiter capacity
max_connection_waiters_per_tenant128tenant capacity in shared mode; effective-user capacity otherwise
max_ws_message_bytesNoneno App-side command-size limit unless the host selects one
require_tenantFalseordinary dedicated-principal mode

The staleness ceiling cannot exceed the registry's retained-sighting lifetime. Treat these values as one admission policy, not as interchangeable timeouts.

Complete one-process composition

from servercheetah.presets import create_dev_components
from servercheetah.servers import (
    AppNodeConfig,
    RestNodeConfig,
    WebSocketNodeConfig,
    create_app_node,
    create_rest_node,
    create_websocket_node,
)
from servercheetah.types.messages import TransportSecurityMode

components = create_dev_components(
    node_id="development-1",
    api_keys={"development-key": "development-user"},
    enable_worker_topology=True,
)

websocket_node = create_websocket_node(
    components=components,
    config=WebSocketNodeConfig(
        rest_message_endpoint="http://127.0.0.1:8080/cheetah/messages",
        transport_security_mode=TransportSecurityMode.debug_insecure,
    ),
)

rest_node = create_rest_node(
    registry=components.registry,
    deduplicator=components.deduplicator,
    history=components.history,
    correlator=components.correlator,
    tracer=components.tracer,
    response_notifier=components.response_notifier,
    context_registry=components.context_registry,
    browser_window_registry=components.browser_window_registry,
    config=RestNodeConfig(),
)

app_node = create_app_node(
    registry=components.registry,
    dispatcher=components.dispatcher,
    correlator=components.correlator,
    history=components.history,
    tracer=components.tracer,
    response_notifier=components.response_notifier,
    parser_registry=components.parser_registry,
    config=AppNodeConfig(),
)

This example deliberately uses an insecure local endpoint and a fixed development credential. It is a local composition pattern, not a production security example.

What factories do not provide

The returned services do not mount framework routes, bind sockets, start component listeners, create worker managers, authenticate administrative pages, install TLS, expose readiness, or close caller-owned dependencies. A host may place the three roles together or separately, but every process must still manage the lifecycle of the component set it constructed.

Continue to Lifecycle, readiness, and ownership or use Application API for the product-facing surface.