API index and search · Build metadata
Supporting declarations
packages/remote/src/optimistic-source.ts. These declarations explain referenced types. Only package-page symbols are package exports.
TransportFactory
/** Build a transport to a follower's public ws endpoint (READ-ROUTER-DESIGN.md §2.3). Default
* `(endpoint) => new WsTransport(endpoint)`. */
export type TransportFactory = (endpoint: string) => Transport;RemoteOptimisticConnection
/** How the source obtains its transport:
* - a pre-built {@link Transport} (or `{ transport }`) — FIXED: no endpoint migration, `wsEndpoint`
* on leases is ignored (in-process / tests / a single static daemon);
* - `{ factory, endpoint? }` — REPLACEABLE: transports are built on demand. An initial `endpoint`
* (a static `wsUrl` or an SSR-injected bootstrap) opens eagerly; otherwise the first lease's
* `wsEndpoint` opens it lazily, and a later lease naming a DIFFERENT endpoint migrates the whole
* session there. */
export type RemoteOptimisticConnection = Transport | {
transport: Transport;
} | {
factory: TransportFactory;
endpoint?: string;
};RemoteOptimisticSourceOptions
export interface RemoteOptimisticSourceOptions {
/** Resolve the upstream subscribe target. Defaults to embedded-server `{name,args}`. */
resolveSubscribe?: SubscribeResolver;
/** Override named-mutator delivery, e.g. POST envelopes to the app API server. */
pushMutation?: MutationEnvelopeSender;
/** Follower-affinity mode (FOLLOWER-AFFINITY-DESIGN.md §3): the shared ticket store. When set, the
* source records the follower's minted ticket from the `{t:"affinity"}` frame and CLEARS it on a
* sustained outage so the next (ticketless) reconnect anycasts to a live follower and re-pins
* (§8). Absent ⇒ affinity off (today's behavior). */
affinity?: AffinityTicketStore | (() => AffinityTicketStore | undefined);
}RemoteOptimisticSource
export declare class RemoteOptimisticSource implements OptimisticSource {
/** The CURRENT transport (undefined in pure-lazy mode until the first lease opens one). */
private transport;
/** The ws endpoint the current transport points at (undefined for a fixed transport). */
private currentEndpoint;
/** Builds a transport for an endpoint; undefined ⇒ fixed transport (no migration). */
private readonly transportFactory;
private readonly clientID;
private readonly resolveSubscribe;
private readonly pushMutationSender?;
/** Resolve the current affinity ticket store. A thunk lets the one-call client turn affinity on
* after its first pure-lazy lease returns a placement ticket, before it opens the socket. */
private readonly affinityStore;
private handler;
private progressHandler;
private restartHandler;
private outcomeHandler;
private resyncHandler;
/** Set by {@link resync} when this is a LEASE-AUTH session (some sub presented a `leaseToken`):
* transport pushes queue in {@link pendingPushes} until the first authenticated re-subscribe's
* hello re-establishes the socket's subject, then flush (see QState.authed). Never set on a
* token-less (embedded/rindled) session — its pushes need no subject and go straight out. */
private awaitingAuthedHello;
/** Envelopes held while {@link awaitingAuthedHello} (H-v §7.5 rule 3). Every entry corresponds
* to a still-pending backend mutation (the re-send reconstructs from pending entries; app
* invokes in the window are pending by definition), so a superseding resync may CLEAR this —
* its own re-send regenerates whatever still matters. */
private pendingPushes;
private readonly subs;
/** Queries whose subscribe is waiting for a transport to exist — endpoint-less subscribes issued
* in pure-lazy mode before any lease opens a transport (the lmid system query is registered by
* the backend at construction). Flushed when a transport comes up. */
private readonly deferred;
/** One warning per source when an APP lease resolves without a `wsEndpoint` in pure-lazy mode —
* nothing will ever open the transport, which is otherwise silent (views just stay empty). */
private warnedEndpointlessLease;
/** The daemon's boot id (from each `nhello`); a change means it restarted. */
private lastBootId;
/** The client's own typed per-table schemas, for hello validation (CRIT#4); set by the backend. */
private clientTables;
/** Once true (set by {@link close}), in-flight lease resolutions are inert — they must not open a
* new transport or send after teardown. */
private closed;
/** True once any lease has carried a routed `wsEndpoint`. Gates onDown re-leasing so a single
* UNROUTED daemon keeps its pre-router behavior (recover via reconnect→resync only), not an extra
* lease POST during an outage. */
private sawRoutedEndpoint;
/** Bumped at the start of every re-subscribe-all pass. A migrate triggered mid-pass starts a new
* pass (higher generation); the outer pass then aborts instead of re-subscribing queries twice. */
private resubscribeGen;
constructor(connection: RemoteOptimisticConnection, clientID: string, opts?: RemoteOptimisticSourceOptions);
/** Wire a transport's handlers (no `init`). */
private attach;
/** Make `transport` the current one, announce identity, and (re)subscribe anything deferred. */
private bringUp;
/** Build + bring up a fresh transport to `endpoint` (replaceable mode only). */
private openEndpoint;
/** Migrate the whole session to a new follower (§2.3): build the new transport, tear the old one
* down, and re-subscribe EVERY active query there (re-leasing — the old tokens are
* follower-local and invalid on the new node). */
private migrate;
/** Re-subscribe every live query on the current transport (each re-resolves its lease). A
* re-subscribe can synchronously trigger a `migrate` (lease names a new endpoint), whose own
* re-subscribe pass supersedes this one — the generation check then aborts this pass so a query
* is never re-subscribed (and re-leased) twice. */
private resubscribeAll;
/** Flush subscribes deferred until a transport existed (e.g. the lmid query in pure-lazy mode). */
private flushDeferred;
/** The current follower's ws is sustainedly down — re-lease every query. The router returns a
* (possibly new) `wsEndpoint`: a changed one migrates the session; an unchanged one re-subscribes
* over the reconnecting transport (READ-ROUTER-DESIGN.md §3). No-op for an UNROUTED daemon (no
* lease ever carried a `wsEndpoint`) — there is nowhere to move, so we keep the pre-router
* behavior and let the transport's own reconnect→resync recover. */
private onDown;
/** Tear down the current transport and make any in-flight lease resolution inert (a late lease
* must NOT open a new transport after the consumer closed the client). */
close(): void;
/** Register a handler fired when the DAEMON restarts (a new boot id) — the backend resets its
* `cv` watermark so the new daemon's reset `cv` sequence is accepted instead of dropped. */
onRestart(handler: () => void): void;
expectClientSchema(tables: NormalizedTableSchema[]): void;
registerQuery(qid: QueryId, remote: RemoteQuery): void;
unregisterQuery(qid: QueryId): void;
pushMutation(envelope: MutationEnvelope): Promise<void>;
onNormalized(handler: (qid: QueryId, ev: NormalizedEvent) => void): void;
onProgress(handler: (frame: ProgressFrame) => void): void;
/** The room deopt handshake's verdict stream (H-v). Dispatched OUT-OF-BAND on arrival — see
* {@link onServerMsg}'s `mutationOutcome` arm for why it must never wait behind the cv buffer. */
onMutationOutcome(handler: (frame: MutationOutcomeFrame) => void): void;
/** Fired once per re-established session, SYNCHRONOUSLY inside {@link resync} — before any
* post-reconnect frame can release (the §7.5 rule-3 window: a replayed lmid snapshot must not
* retire an entry whose outcome frame died with the old socket before the re-send captured
* it). The backend re-sends the domain's unconfirmed pending envelopes with their original
* mids; on a lease-auth session their DELIVERY is deferred until the first token hello
* re-authenticates the socket ({@link pendingPushes}). */
onResync(handler: () => void): void;
private subscribe;
private onServerMsg;
/** On reconnect: re-announce identity, fire the `onResync` re-send, and re-subscribe every live
* query (each re-resolves its lease, so a restarted daemon re-materializes + re-leases on the
* transiently). The re-send fires HERE — synchronously, before any post-reconnect frame can be
* processed — because the §7.5 rule-3 window closes fast: the re-subscribed lmid stream's
* fresh snapshot may cover a mid whose outcome frame died with the OLD socket, and once the
* release retires that entry as an apparent success there is nothing left to re-send (the
* lost-deopt write would silently vanish). Firing now captures the in-flight set intact; on a
* lease-auth session the envelopes themselves are HELD ({@link pendingPushes}) until the first
* token re-subscribe's hello re-authenticates the socket, then flush in order — so the shell's
* subject gate never refuses them, and its re-answer (a recorded outcome for any non-applied
* mid) resolves even an already-retired entry via the handshake's not-found arm. */
private resync;
/** Track the daemon's boot id; a change (after the first) means it restarted — fire onRestart. */
private observeBootId;
/** Route a `queryError` by its 101 §5 classification: retryable ⇒ keep the QState (and the
* rows already folded downstream — 101 §6) and re-subscribe after a jittered backoff
* honoring `retryAfterMs`; terminal (or pre-classification servers) ⇒ drop the
* subscription, exactly as before. Fixes the stranded-client gap: a worker fault's or a
* shedding follower's error now heals end-to-end (FOLLOWER-LAG-SHED §6.3). */
private onQueryError;
private openSubscriber;
private applyBatch;
}createRemoteOptimisticSource
/** Convenience: a `RemoteOptimisticSource` over a ws URL or a custom transport. A URL becomes a
* replaceable connection seeded at that endpoint (so a routed lease can still migrate it); a
* pre-built transport stays fixed. */
export declare function createRemoteOptimisticSource(urlOrTransport: string | Transport, clientID: string, opts?: RemoteOptimisticSourceOptions): RemoteOptimisticSource;