import { TConnectionState, TSendFailedParams, TSendParams } from './types'; type TConnectionStateSource = { addConnectionStateListener: (listener: () => void) => () => void; getConnectionState: () => TConnectionState; }; export declare function useSocketConnectionState(manager: TConnectionStateSource): TConnectionState; /** * Fires on every connection state transition with the new and previous * state. The event counterpart to `useSocketConnectionState`: use that * one to RENDER the state, this one to REACT to a transition without * re-rendering the component — clear ephemeral UI (typing indicators, * live cursors, presence dots) the moment the socket drops, stop timers, * pause animations. * * Does not fire on mount with the current state — only on transitions * after mount. For "reconnected AND subscriptions restored" use * `useSocketReady` instead; this fires on the raw transition, before * subscription replay. * * @example * ```tsx * useSocketConnectionChange(manager, (state) => { * if (state !== "connected") clearTypingIndicators(); * }); * ``` */ export declare function useSocketConnectionChange(manager: TConnectionStateSource, handler: (state: TConnectionState, prev: TConnectionState) => void): void; type TKeyedEventSource = { readonly discriminator: TKey; addEventListener: (value: string, cb: (msg: TServerMsg) => void) => () => void; }; export declare function useSocketEvent, TValue extends TServerMsg[TKey]>(manager: TKeyedEventSource, value: TValue, handler: (msg: Extract>) => void): void; /** * Receive-side backpressure helper. Buffers events matching `value` and * flushes the batch to `handler` on a fixed `flushMs` interval. Use for * high-frequency streams (LLM tokens, presence cursors, telemetry) where * a setState per event would tank UI performance. * * **`idleMs` (optional)**: when set, also flushes after `idleMs` of * silence on the channel. This avoids the trailing-latency artifact * where the last 1–3 events of a stream sit in the buffer waiting for * the next interval tick. Typical use: `flushMs: 16, idleMs: 8` for an * LLM token stream so the final tokens render without a visible stall. * * **Do not use `idleMs` for pure throttling.** Every matching event * resets the idle timer, so any gap longer than `idleMs` triggers an * early flush — including a trickle of 2 events followed by silence, * which will flush at `idleMs` rather than wait for `flushMs`. `idleMs` * is an *opt-in to extra responsiveness on burst tails*, not extra * batching. If your goal is "render at most every X ms regardless of * activity," omit `idleMs` and rely on `flushMs` alone. */ export declare function useSocketEventBatch, TValue extends TServerMsg[TKey]>(manager: TKeyedEventSource, value: TValue, handler: (msgs: Array>>) => void, options: { flushMs: number; idleMs?: number; }): void; type TSubscribable = { subscribe: (key: string, data?: TClientMsg) => void; unsubscribe: (key: string, data?: TClientMsg) => void; }; /** * Declarative ref-counted subscription. The first mount for a given `key` * triggers a single `subscribe` wire send; later mounts increment a * counter without re-sending. Unmount decrements. The wire `unsubscribe` * fires only when the count reaches zero. * * **Recommended: wrap this hook once per resource and call the wrapper * everywhere.** Centralising the `key` + `subscribe` + `unsubscribe` * derivation in a single custom hook (e.g. `useSpaceSubscription`) makes * it impossible for two call sites to drift apart on params. Combined * with first-payload-wins semantics (see below), this turns a whole * class of bugs into a non-issue. * * **Gating with `enabled`**: pass `enabled: false` to opt out entirely * (no wire send, no ref-count touch). Flipping `false → true` subscribes; * `true → false` unsubscribes. Mirrors React Query's `enabled` semantics. * * **Payload timing.** The `subscribe` payload is read once, when the * effect runs (mount, key change, or `enabled` flip), and stored for * replay on reconnect. Changing it between renders without changing * `key`/`enabled` does NOT re-send. The `unsubscribe` payload is read at * unmount time (latest ref), so updates between mount and unmount are * picked up. To push a new subscribe payload, change `key` or toggle * `enabled`. * * **First-payload wins.** When multiple components subscribe to the same * `key`, only the first payload is sent on the wire and stored for replay * on reconnect. Later subscribers only bump the ref count. Combining * `enabled: false → true` with an existing subscription on the same key * means the late joiner's payload never reaches the wire. Wrap in a * custom hook so two call sites cannot drift apart. If your protocol * needs each joiner to identify itself, send a separate `useSocketSend` * message instead of relying on the subscribe payload. * * **Subscribe with no payload** is supported (bookkeeping-only ref count, * no wire send). `useSocketPendingSubscription` will not flip to `true` * for those keys since there is nothing to ack. * * @example * ```tsx * // Centralised wrapper — every consumer uses this, never the raw hook. * export function useSpaceSubscription(spaceId: string | null) { * useSocketSubscription(manager, { * key: spaceId ?? "", * enabled: spaceId !== null, * subscribe: spaceId ? { type: "join", spaceId } : undefined, * unsubscribe: spaceId ? { type: "leave", spaceId } : undefined, * }); * } * ``` */ export declare function useSocketSubscription(manager: TSubscribable, args: { key: string; subscribe?: TClientMsg; unsubscribe?: TClientMsg; enabled?: boolean; }): void; type TPendingSubscriptionSource = { addPendingSubscriptionListener: (listener: () => void) => () => void; hasPendingSubscription: (key: string) => boolean; }; export declare function useSocketPendingSubscription(manager: TPendingSubscriptionSource, key: string): boolean; type TSendable = { send: (params: TSendParams) => boolean; }; type TUseSocketSend = { send: (data: TClientMsg, ackId?: string) => boolean; }; export declare function useSocketSend(manager: TSendable): TUseSocketSend; type TSendIntentSource = { addSendIntentListener: (cb: (params: TSendParams) => void) => () => void; }; /** * Observe every outgoing send the moment `manager.send(...)` is called, * regardless of whether the socket actually delivers it. Fires before * the wire write, so it sees offline sends too. Use this to drive * optimistic UI: append the message to local state immediately, then * pair with `useSocketInFlightDrop` to roll back if the send is dropped * by a disconnect before being acked. * * @example * ```tsx * useSocketSendIntent(manager, ({ data, ackId }) => { * if (data.type === "chat") { * addOptimisticMessage({ ...data, ackId, status: "pending" }); * } * }); * ``` */ export declare function useSocketSendIntent(manager: TSendIntentSource, handler: (params: TSendParams) => void): void; type TSendFailedSource = { addSendFailedListener: (cb: (params: TSendFailedParams) => void) => () => void; }; /** * Fires when `manager.send(...)` returns false — the message never left * the client. `reason` is `"not-connected"` when the socket was down at * send time, `"serialize-error"` when `serialize` threw, or * `"transport-error"` when the transport threw on the wire write. The * outcome counterpart to `useSocketSendIntent`: intent announces the * attempt, this announces the failure. Use it to mark optimistic UI as * failed or enqueue the message for resend via `createUndeliveredSync` — * centrally, without checking the boolean at every call site. * * Messages that DID reach the wire but are dropped unacked by a * disconnect surface through `useSocketInFlightDrop`, not here. * * Note: sends on a disposed manager fail with `"not-connected"`. That * state is terminal — do not auto-retry from this handler without also * observing connection state. * * @example * ```tsx * useSocketSendFailed(manager, ({ data, ackId }) => { * if (ackId) markMessageFailed(ackId); * undeliveredSync.addMessage(channel, data); * }); * ``` */ export declare function useSocketSendFailed(manager: TSendFailedSource, handler: (params: TSendFailedParams) => void): void; type TInFlightDropSource = { addInFlightDropListener: (cb: (messages: { id: string; data: TClientMsg; }[]) => void) => () => void; }; /** * Fires when in-flight messages (those sent with an `ackId`) are dropped * because the socket closed before the server acked them. Use this to * roll back optimistic UI added in `useSocketSendIntent`, or to enqueue * the messages for resend via `createUndeliveredSync`. * * @example * ```tsx * useSocketInFlightDrop(manager, (dropped) => { * for (const { id, data } of dropped) { * markMessageFailed(id); * undeliveredSync.enqueue(data); * } * }); * ``` */ export declare function useSocketInFlightDrop(manager: TInFlightDropSource, handler: (messages: { id: string; data: TClientMsg; }[]) => void): void; type TReadySource = { addReadyListener: (cb: (restoredKeys: string[]) => void) => () => void; }; /** * Fires every time the socket transitions to `connected` AND existing * subscriptions have been replayed to the server. Receives the list of * resubscribed keys. Also fires on the very first connect, with * `restoredKeys = []`. Use this to flush queued offline sends or to * refetch state that may have drifted during the disconnect. * * @example * ```tsx * useSocketReady(manager, (restoredKeys) => { * for (const msg of undeliveredSync.drain()) manager.send({ data: msg }); * if (restoredKeys.length > 0) refetchSpaceStateForKeys(restoredKeys); * }); * ``` */ export declare function useSocketReady(manager: TReadySource, handler: (restoredKeys: string[]) => void): void; type TLastUnsubscribeSource = { addLastUnsubscribeListener: (cb: (key: string, subscribePayload: TClientMsg | undefined) => void) => () => void; }; /** * Fires when the subscription ref count for `key` drops to zero — the * last subscriber left and the wire `unsubscribe` was sent. Use it to * clear cached server state for the key, or to fire app-level cleanup * tied to "no one is watching this channel anymore." * * The second arg is the **original `subscribe` payload** that was stored * when the first subscriber for this key joined (first-payload wins), * not the unsubscribe payload. Useful for cache eviction code that needs * to recover the join context. * * **Strict Mode**: React 18+ Strict Mode double-mounts components in * dev, causing a transient mount→unmount→remount cycle that fires this * listener spuriously. Keep handlers idempotent — write to state, do * not perform irreversible side effects (e.g. POST cleanup, decrement * external counters). Production behavior is unaffected. * * @example * ```tsx * useSocketLastUnsubscribe(manager, (key) => { * queryClient.removeQueries({ queryKey: ["space", key] }); * }); * ``` */ export declare function useSocketLastUnsubscribe(manager: TLastUnsubscribeSource, handler: (key: string, subscribePayload: TClientMsg | undefined) => void): void; export {}; //# sourceMappingURL=hooks.d.ts.map