@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
TypeScript
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 };