API Reference
This page documents the public socket-store API exported from the package entrypoint:
import {
SocketStore,
createMessageHandler,
SocketStoreError,
} from "socket-store";The claims below are backed by src/index.ts, src/SocketStore.ts, src/createMessageHandler.ts, src/types.ts, src/SocketStore.test.ts, and src/schema.test-d.ts.
Topic Schemas
Type:
type SocketSchema = {
[topic: string]: {
state: unknown;
payload: unknown;
};
};Usage notes:
- A schema maps each topic key to the stored state type and incoming payload type for that topic.
- Passing a finite schema to
SocketStore<Schema>constrains handler keys,getState,subscribe,subscribeAll, andsend. - Omitting the generic keeps loose legacy usage where topic names and payloads are not type-checked.
Example:
type ChatMessage = { author: string; text: string };
type AppSchema = {
chat: { state: ChatMessage[]; payload: ChatMessage };
price: { state: number | null; payload: { symbol: string; value: number } };
};Useful exported schema types:
type ChatKey = TopicKey<AppSchema>;
type ChatState = TopicState<AppSchema, "chat">;
type ChatPayload = TopicPayload<AppSchema, "chat">;
type ChatHandler = TopicHandler<AppSchema, "chat">;
type AppUpdate = TopicUpdate<AppSchema>;Adapter Types
Import path:
import type {
SocketStoreAdapterContract,
SocketStoreSender,
SocketStoreStatusGetter,
SocketStoreStatusSubscriber,
SocketStoreStateGetter,
SocketStoreSubscriber,
TopicStateListener,
} from "socket-store";Signatures:
type SocketStoreStateGetter<Schema> = <K extends TopicKey<Schema>>(
key: K
) => TopicState<Schema, K>;
type SocketStoreSender<Schema> = <K extends TopicKey<Schema>>(message: {
key: K;
data: TopicPayload<Schema, K>;
}) => void;
type SocketStoreSubscriber<Schema> = <K extends TopicKey<Schema>>(
key: K,
listener: TopicStateListener<Schema, K>
) => Unsubscribe;
interface SocketStoreAdapterContract<Schema> {
send: SocketStoreSender<Schema>;
getState: SocketStoreStateGetter<Schema>;
subscribe: SocketStoreSubscriber<Schema>;
getStatus: SocketStoreStatusGetter;
subscribeStatus: SocketStoreStatusSubscriber;
}Usage notes:
- These types are the public framework-adapter surface for reading topic state, reading connection status, sending typed topic payloads, and subscribing to topic or status snapshots.
- They intentionally exclude socket lifecycle methods, raw message listeners, all-topic listeners, unhandled listeners, custom protocol configuration, and internal store storage.
- Framework adapters should import these types from the package root instead of deep-importing source or generated
distfiles.
createMessageHandler
Import path:
import { createMessageHandler } from "socket-store";Type:
function createMessageHandler<S, D, K extends string>(
key: K,
callback: (state: S, data: D) => S,
state: S
): MessageHandler<S, D, K>;Usage notes:
keyis the topic name routed bySocketStore.stateis the initial snapshot for that topic.callbackreceives the current state and incoming payload, then returns the next state. Returningvoidis rejected by TypeScript when the state type is explicit.- Duplicate handler keys fail during
SocketStoreconstruction before socket listeners are attached.
Example:
const chatHandler = createMessageHandler(
"chat",
(state: ChatMessage[], payload: ChatMessage) => [...state, payload],
[] as ChatMessage[]
);SocketStore
Import path:
import { SocketStore } from "socket-store";Constructor signature:
new SocketStore<Schema>(
socket: WebSocket,
messageHandlers: SocketStoreMessageHandlers<Schema>,
options?: ISocketStoreOptions<Schema>
);Usage notes:
- The store accepts an existing
WebSocketinstance and registersopen,message,error, andcloselisteners. - Each handler creates one topic snapshot in the internal store.
- Handler keys named like prototype properties, such as
toStringand__proto__, are supported as first-time topic keys. - Duplicate handler keys throw
Error.
Example:
const socket = new WebSocket("wss://example.com/realtime");
const store = new SocketStore<AppSchema>(socket, [chatHandler]);getState
Type:
store.getState<K extends TopicKey<Schema>>(key: K): TopicState<Schema, K>;Usage notes:
- Returns the current snapshot for a registered topic.
- The initial value is the handler's initial
state. - The snapshot remains readable after
dispose(). - Unknown keys are rejected by TypeScript when using a finite schema. Runtime unknown-key reads are not guaranteed.
Example:
const messages = store.getState("chat");getStatus
Type:
store.getStatus(): SocketStoreConnectionStatus;Usage notes:
- Returns the current public connection status snapshot.
- Initial status is derived from the socket's native
readyState:0becomesconnecting,1becomesopen,2becomesclosing, and3becomesclosed. - Native
openevents move the status toopen. - Native
closeevents move the status toclosed. - Native
errorevents continue to reportERR_SOCKET_ERRORthroughonErrorand do not change status by themselves. - The status snapshot remains readable after
dispose().
Example:
const status = store.getStatus();subscribeStatus
Type:
store.subscribeStatus(listener: SocketStoreStatusListener): Unsubscribe;Usage notes:
- Registers a listener for future status changes.
- The listener receives the next
SocketStoreConnectionStatusvalue. - The returned unsubscribe function is idempotent.
- Unsubscribing stops future notifications for that listener.
- Duplicate subscriptions are independent.
- Notification uses a stable listener snapshot.
- New status subscriptions after
dispose()throwError.
Example:
const stopStatus = store.subscribeStatus((status) => {
console.log(status);
});
stopStatus();subscribe
Type:
store.subscribe<K extends TopicKey<Schema>>(
key: K,
listener: (state: TopicState<Schema, K>) => void
): Unsubscribe;Usage notes:
- Registers a listener for successful updates to one topic.
- The listener receives the next topic state after the handler returns.
- The returned unsubscribe function is idempotent.
- Unsubscribing stops future notifications for that listener but does not stop the topic state from updating.
- Duplicate subscriptions are independent.
- Notification uses a stable listener snapshot, so unsubscribing another listener during notification does not skip it for the current message.
- New subscriptions after
dispose()throwError.
Example:
const unsubscribe = store.subscribe("chat", (messages) => {
console.log(messages.at(-1));
});
unsubscribe();
unsubscribe();subscribeRaw
Type:
store.subscribeRaw(listener: RawMessageListener): Unsubscribe;Usage notes:
- Fires before protocol parsing.
- Receives
{ data, event }, wheredatais the originalMessageEvent.data. - The returned unsubscribe function is idempotent.
- New raw subscriptions after
dispose()throwError.
Example:
const stopRaw = store.subscribeRaw(({ data, event }) => {
console.log(data, event.type);
});subscribeAll
Type:
store.subscribeAll(listener: TopicUpdateListener<Schema>): Unsubscribe;Usage notes:
- Fires after any successful registered topic update.
- Receives
{ key, data, state }for the updated topic. - Does not fire for ignored, malformed, unhandled, unknown-topic, or failed handler messages.
- The returned unsubscribe function is idempotent.
- New all-topic subscriptions after
dispose()throwError.
Example:
const stopAll = store.subscribeAll((update) => {
console.log(update.key, update.state);
});subscribeUnhandled
Type:
store.subscribeUnhandled(listener: UnhandledMessageListener): Unsubscribe;Usage notes:
- Fires when a parsed default-protocol message has no registered handler.
- Fires when a custom parser returns
{ type: "unhandled", data, key? }. - Unknown default-protocol topics also report
ERR_UNKNOWN_TOPICthroughonError. - Custom
unhandledparser results do not report routing errors. - The returned unsubscribe function is idempotent.
- New unhandled subscriptions after
dispose()throwError.
Example:
const stopUnhandled = store.subscribeUnhandled(({ key, data }) => {
console.log(key, data);
});send
Type:
store.send<K extends TopicKey<Schema>>({
key,
data,
}: {
key: K;
data: TopicPayload<Schema, K>;
}): void;Usage notes:
- Throws
SocketStoreErrorwith codeERR_SOCKET_NOT_OPENbefore sending whensocket.readyState !== 1. - Connecting, closing, and closed sockets reject sends with
ERR_SOCKET_NOT_OPEN; messages are not queued for later delivery. - Uses the custom protocol serializer when provided.
- Otherwise sends
JSON.stringify({ key, data }). - Serializer or socket-send failures are reported through
onErrorasERR_PROTOCOL_SERIALIZE_FAILEDand rethrown. - Sending after
dispose()throwsError.
Example:
store.send({
key: "chat",
data: { author: "Ada", text: "Hello" },
});dispose
Type:
store.dispose(): void;Usage notes:
- Idempotently removes native socket listeners.
- Clears topic, raw, all-topic, and unhandled subscriptions.
- Prevents future
sendand subscription calls. - Does not erase readable topic snapshots.
Example:
store.dispose();
store.dispose();Default Protocol
Incoming default-protocol messages must be string JSON envelopes:
type SocketStoreEnvelope = {
key: string;
data: unknown;
};Usage notes:
keyselects a registered handler.datais passed to the handler callback.- Non-string message data reports
ERR_UNSUPPORTED_MESSAGE_DATA. - Invalid JSON reports
ERR_INVALID_JSON. - JSON values without a string
keyreportERR_MALFORMED_ENVELOPE. - The exported
SocketStoreEnvelopetype modelsdataas present, but runtime handling for envelopes that omitdatais undecided and should not be relied on. - Unknown topic keys notify unhandled subscribers, report
ERR_UNKNOWN_TOPIC, and do not update topic state.
Example:
socket.dispatchEvent(
new MessageEvent("message", {
data: JSON.stringify({
key: "chat",
data: { author: "Ada", text: "Hello" },
}),
})
);Custom Protocols
Import path:
import type { SocketStoreProtocol } from "socket-store";Signature:
type SocketStoreProtocol<Schema> = {
parse?: (event: MessageEvent) => SocketStoreProtocolResult;
serialize?: (message: SocketStoreOutgoingMessage<Schema>) => SocketStoreSendData;
};Parser results:
type SocketStoreProtocolResult =
| { type: "topic"; key: string; data: unknown }
| { type: "unhandled"; key?: string; data: unknown }
| { type: "ignore" };Usage notes:
parsereceives the originalMessageEvent.- Parser and serializer methods are called with the protocol object as
this. - Custom parsers may map non-string message data, such as
ArrayBuffer. topicresults route through handlers.ignoreresults do not update state and do not report errors.unhandledresults notify unhandled subscribers and do not report routing errors.- Parser throws are reported as
ERR_PROTOCOL_PARSE_FAILED. - Invalid parser results are reported as
ERR_INVALID_PROTOCOL_RESULT.
Example:
const protocol: SocketStoreProtocol<AppSchema> = {
parse(event) {
const message = JSON.parse(event.data as string);
if (message.type === "heartbeat") {
return { type: "ignore" };
}
return {
type: "topic",
key: message.topic,
data: message.payload,
};
},
serialize({ key, data }) {
return JSON.stringify({ topic: key, payload: data });
},
};Lifecycle And Errors
Options signature:
type ISocketStoreOptions<Schema> = {
onConnect?: () => void;
onClose?: (event: CloseEvent) => void;
onError?: (error: SocketStoreError) => void;
protocol?: SocketStoreProtocol<Schema>;
};Usage notes:
onConnectruns for nativeopenevents before disposal.onClosereceives nativeCloseEventvalues before disposal.- Native socket
errorevents are wrapped asSocketStoreErrorwith codeERR_SOCKET_ERROR. - Incoming parse, validation, route, and handler failures are reported through
onErrorand do not update topic state. - If
onErroris not provided, asynchronous message and socket error events do not throw to the caller.
SocketStoreError signature:
class SocketStoreError extends Error {
name: "SocketStoreError";
code: SocketStoreErrorCode;
context: SocketStoreErrorContext;
}Error codes:
ERR_SOCKET_ERRORERR_UNSUPPORTED_MESSAGE_DATAERR_INVALID_JSONERR_MALFORMED_ENVELOPEERR_INVALID_PROTOCOL_RESULTERR_PROTOCOL_PARSE_FAILEDERR_PROTOCOL_SERIALIZE_FAILEDERR_UNKNOWN_TOPICERR_HANDLER_FAILEDERR_SOCKET_NOT_OPEN
Example:
const store = new SocketStore(socket, [chatHandler], {
onError(error) {
console.error(error.code, error.context.phase);
},
});Current Limitations And Edge Cases
socket-store does not currently provide runtime support for:
- Reconnection, backoff, or offline send queues. The proposed future opt-in reconnect shape is documented in Reconnect Configuration Design.
- React hooks or render behavior.
- Persistence.
- RPC, CRDT, or collaborative editing semantics.
- Byte-level stream parsing across multiple WebSocket messages.
- A2A protocol support.
Keep these edge cases in mind:
- Runtime
getStatefor unknown keys is not supported. - Runtime handling for default envelopes that omit
datais not supported. - Listener exception handling is not wrapped by
SocketStore; do not rely on listener failures being converted toSocketStoreError. - The package accepts any
WebSocket-compatible object at runtime, but the supported API is the standardWebSocketinterface.