actions/peer_request.ts

Server→client peer requests (ActionPeer) — the request/response direction a backend initiates to a connected WebSocket client and awaits a typed reply.

PendingPeerRequests is the correlation registry: it owns the in-flight map (nested connection_id → request id — the nesting is the per-connection isolation guarantee), id allocation (s{n}-namespaced so a client's own request ids can't collide), the per-request deadline, and the per-connection in-flight cap. It deliberately does not touch the socket — the caller (BackendWebsocketTransport.request_connection) does the send and threads the reply back via resolve. This mirrors the Rust fuz_realtime::peer split (the PendingPeerRequests registry vs the transport that composes it), and keeps the registry pure data so it unit-tests without a live socket.

view source

Declarations
#

9 declarations

audit_unmatched_peer_response
#

actions/peer_request.ts view source

(log: Logger, connection_id: string & $brand<"Uuid">, id: string | number | null): void import {audit_unmatched_peer_response} from '@fuzdev/fuz_app/actions/peer_request.js';

Sampled, bounded audit for an inbound response that matched no pending request on its connection — an unsolicited {id, result}, a cross-connection id echo, or a late/duplicate reply for an already-settled id. Auditing every rejected frame would let a junk flood turn the log into the DoS target, so this warns on the first few then samples 1-in-256. Twin of the Rust fuz_realtime::peer::audit_unmatched_response.

log

the WS dispatcher's logger

type Logger

connection_id

the socket the unmatched reply arrived on

type string & $brand<"Uuid">

id

the reply's echoed id (null when absent)

type string | number | null

returns

void

DEFAULT_PEER_REQUEST_TIMEOUT
#

actions/peer_request.ts view source

10000 import {DEFAULT_PEER_REQUEST_TIMEOUT} from '@fuzdev/fuz_app/actions/peer_request.js';

Default deadline (ms) for a server→client peer request before it resolves timeout. Twin of the Rust spine's DEFAULT_PEER_TIMEOUT.

MAX_IN_FLIGHT_PEER_REQUESTS_PER_CONNECTION
#

actions/peer_request.ts view source

256 import {MAX_IN_FLIGHT_PEER_REQUESTS_PER_CONNECTION} from '@fuzdev/fuz_app/actions/peer_request.js';

Per-connection cap on concurrent in-flight server→client requests. A caller past it gets too_many_in_flight instead of growing the pending map unbounded. Twin of the Rust spine's DEFAULT_MAX_IN_FLIGHT_PER_CONN.

PeerRequestError
#

actions/peer_request.ts view source

PeerRequestError import type {PeerRequestError} from '@fuzdev/fuz_app/actions/peer_request.js';

Why a server→client peer request did not yield a client success reply.

  • timeout — the client did not answer within the deadline.
  • connection_gone — the socket closed (or was never registered) before a reply.
  • too_many_in_flight — the per-connection in-flight cap was hit.
  • client_error — the client answered with a JSON-RPC error; error is forwarded verbatim so the initiating handler can surface the client's own code / message / data.

PeerRequestOptions
#

actions/peer_request.ts view source

PeerRequestOptions import type {PeerRequestOptions} from '@fuzdev/fuz_app/actions/peer_request.js';

Per-call options for a server→client peer request.

timeout_ms?

Deadline (ms) before the request resolves timeout. Defaults to the registry's default_timeout_ms (DEFAULT_PEER_REQUEST_TIMEOUT). Untrusted remote-supplied values should be clamped shorten-only (never lengthen the server's hold on a pooled connection) — see actions/peer_ping.ts.

type number

PeerRequestOutcome
#

PendingPeerRequests
#

actions/peer_request.ts view source

import {PendingPeerRequests} from '@fuzdev/fuz_app/actions/peer_request.js';

Correlation registry for in-flight server→client requests, nested by connection then by the server-issued request id.

The per-connection nesting makes the in-flight count and the close-time drain O(1) and is the isolation boundary — a reply on connection B can never settle a request issued on connection A (it lands in a different inner map). An inner map is removed as soon as it empties, so an idle connection holds no entry. The registry never sends on the socket; the caller does the send between register and the first await, and routes inbound replies back via resolve.

constructor

type new (options?: PendingPeerRequestsOptions | undefined): PendingPeerRequests

options?

type PendingPeerRequestsOptions | undefined
optional

register

Register a pending request for connection_id with a deadline, returning its server-issued s{n} id and the outcome promise (settled by a reply, the deadline, or drain). Returns null when the connection is at its in-flight cap — the caller maps that to too_many_in_flight. The promise executor runs synchronously, so the entry is registered before this returns.

type (connection_id: string & $brand<"Uuid">, timeout_ms?: number | undefined): { id: string | number; outcome: Promise<PeerRequestOutcome>; } | null

connection_id

type string & $brand<"Uuid">

timeout_ms?

type number | undefined
optional
returns { id: string | number; outcome: Promise<PeerRequestOutcome>; } | null

the allocated id + outcome promise, or null at the cap

resolve

Settle the pending request matching an inbound reply. A success response resolves {ok: true, value: result}; an error response resolves {ok: false, error: {kind: 'client_error', error}} (forwarded verbatim).

Returns false when no entry matches — an unsolicited, cross-connection, or already-settled reply — so the caller drops it.

type (connection_id: string & $brand<"Uuid">, response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { ...; }; }): boolean

connection_id

type string & $brand<"Uuid">

response

type { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<...>); message: string; data?: unknown; }; }
returns boolean

whether a pending request was settled

settle

Force-settle one pending request with an explicit outcome (the send-failure path uses this for connection_gone). Clears the timer, drops the entry, resolves the promise. Idempotent — a no-op if the entry is already gone.

type (connection_id: string & $brand<"Uuid">, id: string | number, outcome: PeerRequestOutcome): void

connection_id

type string & $brand<"Uuid">

id

type string | number

outcome

returns void

drain

Settle every pending request on a closing connection as connection_gone. O(1) — drops the connection's inner map in one hop.

type (connection_id: string & $brand<"Uuid">): void

connection_id

type string & $brand<"Uuid">
returns void

size

In-flight request count — for connection_id when given, else across all connections. Telemetry / tests.

type (connection_id?: (string & $brand<"Uuid">) | undefined): number

connection_id?

type (string & $brand<"Uuid">) | undefined
optional
returns number

PendingPeerRequestsOptions
#

RequestClient
#

actions/peer_request.ts view source

RequestClient import type {RequestClient} from '@fuzdev/fuz_app/actions/peer_request.js';

Initiate a JSON-RPC request to the connected client and await its reply — the server→client direction of ActionPeer. Threaded onto ActionContext.request_client for WebSocket handlers (absent on HTTP RPC, where there is no return socket). Returns a PeerRequestOutcome; never throws.

(call)

type (method: string, params: { [x: string]: unknown; } | undefined, options?: PeerRequestOptions | undefined): Promise<PeerRequestOutcome>

method

type string

params

type { [x: string]: unknown; } | undefined

options?

type PeerRequestOptions | undefined
optional
returns Promise<PeerRequestOutcome>

Depends on
#

Imported by
#