UNPKG

@firtoz/socka

Version:

Standard Schema–first WebSocket RPC for TypeScript — Bun, Hono, Node ws, Cloudflare Workers, Durable Objects

166 lines (160 loc) • 8.59 kB
import { Context } from 'hono'; import { BaseSession, SessionEnv, BaseWebSocketDO } from '@firtoz/websocket-do'; import { S as SockaContractBound, a as SockaWireFormat, I as InferSockaHandlers, m as SockaReportError, c as InferSockaPushPayload } from '../socka-report-error-BALZPjd1.js'; import { f as SockaPushSession } from '../SockaWebSocketSession-CmEtwVE-.js'; import '@standard-schema/spec'; /** Session data with no fields — `createData` may be omitted (defaults to `{}`). */ type EmptySockaSessionData$2 = Record<string, never>; /** * Durable Object (or test double) that owns the socka contract, shared * {@link SockaDoSession} map, and per-connection config for * {@link SockaDoSession}. */ interface SockaDoHost<TContract extends SockaContractBound, TData = EmptySockaSessionData$2, TEnv extends object = Cloudflare.Env, TSession extends SockaDoSession<TContract, TData, TEnv> = SockaDoSession<TContract, TData, TEnv>> { readonly contract: TContract; readonly sessions: Map<WebSocket, TSession>; buildSockaSessionConfig(ctx: Context<{ Bindings: TEnv; }> | undefined): SockaDoSessionConfigInput<TContract, TData, TEnv>; } /** Session data with no fields — `createData` may be omitted (defaults to `{}`). */ type EmptySockaSessionData$1 = Record<string, never>; type SockaDoOuterSession<TContract extends SockaContractBound, TData, TEnv extends object> = SockaDoSession<TContract, TData, TEnv>; type SockaDoSessionCreateData<TData, TEnv extends object> = [TData] extends [ EmptySockaSessionData$1 ] ? { createData?: (ctx: Context<{ Bindings: TEnv; }>) => TData; } : { createData: (ctx: Context<{ Bindings: TEnv; }>) => TData; }; type SockaDoSessionConfigFields<TContract extends SockaContractBound, TData, TEnv extends object> = { /** Default `"json"`. Use `"msgpack"` for binary frames (must match client). */ wireFormat?: SockaWireFormat; handlers: InferSockaHandlers<TContract, SockaDoOuterSession<TContract, TData, TEnv>>; handleClose: (session: SockaDoOuterSession<TContract, TData, TEnv>) => Promise<void>; onHandlerError?: (error: unknown, rpcName: string, input: unknown, session: SockaDoOuterSession<TContract, TData, TEnv>) => void; onValidationError?: (error: unknown, originalMessage: unknown) => Promise<void>; /** * Optional sink for non-RPC failures (e.g. `onAttached`). Defaults to * `console.error`; see `SockaReportError` in `@firtoz/socka/core`. */ reportError?: (event: SockaReportError) => void; /** * Called after this session is registered in the DO `sessions` map (next * microtask). Use for join broadcasts and other logic that must run only * when peers can see this connection. */ onAttached?: (session: SockaDoOuterSession<TContract, TData, TEnv>) => void | Promise<void>; serializeJson?: (value: unknown) => string; deserializeJson?: (raw: string) => unknown; }; /** {@link SockaDoSessionConfig} without `contract` — supplied by the DO host. */ type SockaDoSessionConfigInput<TContract extends SockaContractBound, TData, TEnv extends object> = SockaDoSessionConfigFields<TContract, TData, TEnv> & SockaDoSessionCreateData<TData, TEnv>; type SockaDoSessionConfig<TContract extends SockaContractBound, TData, TEnv extends object> = { contract: TContract; } & SockaDoSessionConfigInput<TContract, TData, TEnv>; /** * Durable Object WebSocket session driven by a socka contract. * Dispatches client requests to typed handler functions, validates * input/output via Standard Schema, and auto-sends response/error frames. */ declare class SockaDoSession<TContract extends SockaContractBound, TData = EmptySockaSessionData$1, TEnv extends object = Cloudflare.Env> extends BaseSession<TData, unknown, unknown, TEnv> implements SockaPushSession<TContract> { private socka; constructor(websocket: WebSocket, host: SockaDoHost<TContract, TData, TEnv, SockaDoSession<TContract, TData, TEnv>>, attachCtx?: Context<{ Bindings: TEnv; }> | undefined); constructor(websocket: WebSocket, sessions: Map<WebSocket, SockaDoSession<TContract, TData, TEnv>>, config: SockaDoSessionConfig<TContract, TData, TEnv>); handleRawMessage(rawMessage: string): Promise<void>; emitWireEvent(event: string, body: unknown): void; emitPush<K extends keyof TContract["pushes"] & string>(name: K, body: InferSockaPushPayload<TContract, K>): Promise<void>; broadcastPush<K extends keyof TContract["pushes"] & string>(name: K, body: InferSockaPushPayload<TContract, K>, excludeSelf?: boolean): Promise<void>; /** * {@link SockaWebSocketSession.listPeers} for this Durable Object room. */ listPeers(options?: { excludeSelf?: boolean; }): TData[]; /** * Like {@link listPeers} but maps each peer {@link SockaDoSession}. */ listPeersWith<R>(map: (session: SockaDoSession<TContract, TData, TEnv>) => R, options?: { excludeSelf?: boolean; }): R[]; peerCount(options?: { excludeSelf?: boolean; }): number; hasPeers(options?: { excludeSelf?: boolean; }): boolean; } /** Session data with no fields — `createData` may be omitted (defaults to `{}`). */ type EmptySockaSessionData = Record<string, never>; type SockaWebSocketDOOptions = { /** Same as `pairServerWebSocketAcceptOptions` on `BaseWebSocketDOOptions` from `@firtoz/websocket-do`. */ pairServerWebSocketAcceptOptions?: WebSocketAcceptOptions; }; /** * Low-level DO base when {@link createSockaSession} returns a custom * {@link SockaDoSession} subclass. Most apps extend {@link SockaWebSocketDO} * instead (default session type, three type parameters). */ declare abstract class SockaWebSocketDOBase<TContract extends SockaContractBound, TData = EmptySockaSessionData, TSession extends SockaDoSession<TContract, TData, any> = SockaDoSession<TContract, TData, Cloudflare.Env>, TEnv extends SessionEnv<TSession> = SessionEnv<TSession>> extends BaseWebSocketDO<TSession, TEnv> { protected abstract readonly contract: TContract; constructor(ctx: DurableObjectState, env: TEnv, options?: SockaWebSocketDOOptions); /** * Per-connection socka config (handlers, `createData`, lifecycle hooks). * `contract` comes from {@link contract} on this host — do not repeat it here. * * Called on each new attach and on hibernation resume (`ctx` is `undefined` * on resume). **`createData`** runs only on fresh upgrade via * {@link BaseSession.startFresh}, not on {@link BaseSession.resume}. */ protected abstract buildSockaSessionConfig(ctx: Context<{ Bindings: TEnv; }> | undefined): SockaDoSessionConfigInput<TContract, TData, TEnv>; /** * Factory for each connected WebSocket. Default: {@link SockaDoSession} wired * to this host. Override when you need a {@link SockaDoSession} subclass. */ protected createSockaSession(ctx: Context<{ Bindings: TEnv; }> | undefined, websocket: WebSocket): TSession | Promise<TSession>; /** * Broadcast a contract-typed push to **every** connected session in this DO. * * Use from HTTP handlers, alarms, or other code paths with no originating * WebSocket session. No-op when the room is empty. */ protected broadcastPushToAll<K extends keyof TContract["pushes"] & string>(name: K, body: InferSockaPushPayload<TContract, K>): Promise<void>; } /** * Durable Object base class for socka WebSocket rooms. * * Subclasses declare {@link contract} and {@link buildSockaSessionConfig}; the * base wires WebSocket upgrade and hibernation resume to * `new SockaDoSession(websocket, host)`. * * @example * ```ts * class ChatRoomDO extends SockaWebSocketDO<typeof chatContract, SessionData, Env> { * protected readonly contract = chatContract; * protected buildSockaSessionConfig(ctx) { … } * } * ``` */ declare abstract class SockaWebSocketDO<TContract extends SockaContractBound, TData, TEnv extends object> extends SockaWebSocketDOBase<TContract, TData, SockaDoSession<TContract, TData, TEnv>, TEnv> { } /** * Shared error reply shape for correlate-by-id RPC when the handler throws. * Narrow at the call site to your server message union if needed. */ declare function toErrorReply(id: string, error: string): { type: "error"; id: string; error: string; }; export { type SockaDoHost, SockaDoSession, type SockaDoSessionConfig, type SockaDoSessionConfigInput, SockaWebSocketDO, SockaWebSocketDOBase, type SockaWebSocketDOOptions, toErrorReply };