Skip to main content
Version: v8.0.0

Socket SDK

The Socket SDK brings the same proxy-based, type-safe SDK pattern from @hyper-fetch/core to real-time sockets. Instead of HTTP methods ($get, $post), you use $listener and $emitter as leaf keys to create listener and emitter instances on the fly.


Purpose
  1. Type-Safe Sockets: Full TypeScript coverage for topics, responses, and payloads.
  2. tRPC-like Syntax: Access listeners and emitters with dot-notation (e.g., sdk.chat.messages.$listener).
  3. Zero Performance Overhead: Generates instances on-demand using proxies.
  4. Dynamic Topic Support: Handle parameterized topics like chat/:roomId.
  5. Centralized Configuration: Configure all listeners/emitters from one place with $configure.

Initialization

import { Socket, createSocketSdk } from "@hyper-fetch/sockets";

const socket = new Socket({ url: "ws://localhost:3000" });

type MyChatSchema = {
// ... schema definition
};

const sdk = createSocketSdk<typeof socket, MyChatSchema>(socket);
Default topic naming

createSocketSdk defaults to camelCaseToKebabCase: true. This means property names in your SDK schema are automatically converted to kebab-case topic strings (e.g., chatMessages becomes chat-messages). You can disable this by passing { camelCaseToKebabCase: false } as the third argument.


Defining the Schema

The schema maps your socket topics as a nested type. Leaf nodes use $listener for receiving events and $emitter for sending events. Dynamic topic segments are prefixed with $ (e.g., $roomId becomes /:roomId).

Use ListenerModel / EmitterModel for schema leaves — not the *Instance types

When defining a schema you are describing one specific topic. Use ListenerModel<{...}> / EmitterModel<{...}> — omitted fields stay strict (unknown, undefined, string, false) so the type system catches mismatches instead of silently collapsing to any. The mental model below explains when to reach for each type.

import type { ListenerModel, EmitterModel } from "@hyper-fetch/sockets";

// Schemas describe shape, response, payload and topic only. The socket (and its adapter)
// is injected automatically by createSocketSdk(socket) - no need to repeat per leaf.
type MyChatSchema = {
chat: {
messages: {
$listener: ListenerModel<{
response: { text: string; user: string };
topic: "chat/messages";
}>;
$emitter: EmitterModel<{
payload: { text: string };
topic: "chat/messages";
}>;
};
$roomId: {
$listener: ListenerModel<{
response: { text: string; user: string };
topic: "chat/:roomId";
}>;
};
};
notifications: {
$listener: ListenerModel<{
response: { message: string };
topic: "notifications";
}>;
};
};

*Model vs *Instance — two mindsets

Hyper Fetch ships two complementary type families for describing listeners and emitters. They look similar but solve opposite problems. Picking the right one is the difference between strict end-to-end safety and accidental any leaks.

ListenerModel<{...}> / EmitterModel<{...}>ListenerInstance<{...}> / EmitterInstance<{...}>
Mindset"This specific topic.""Any listener/emitter that matches this partial shape."
Use forSocket SDK schema leaves, single-topic definitions, listener / emitter factories.Generic constraints (<T extends ...>), reusable hooks / wrappers / utilities that accept any listener or emitter.
Omitted fields default toStrict, non-any (unknown, undefined, string, false).any — on purpose, so any concrete Listener / Emitter satisfies the constraint.
Why those defaults?Absence means "there is no payload / no params". The type should reject mismatches.Absence means "I don't care about that field". The type should accept anything.
// MODEL — defining the chat/messages topic exactly:
// response is { text, user }
// topic is the literal string
// strict types mean unrelated payloads / topics are rejected.
type ChatMessages = ListenerModel<{
response: { text: string; user: string };
topic: "chat/messages";
}>;

// INSTANCE — accepting any listener whose response has a `text` field:
// response is constrained
// topic, params etc. can be ANY shape — caller picks.
function MessageBubble<T extends ListenerInstance<{ response: { text: string } }>>(props: { listener: T }) {
// ...
}

If you find yourself writing ListenerInstance inside a schema, that is a signal you wanted ListenerModel. If you find yourself writing ListenerModel as a generic constraint on a reusable component, that is a signal you wanted ListenerInstance.

Socket injection — you do not pass socket per leaf

You do not have to add socket: AppSocket to every ListenerModel / EmitterModel declaration. createSocketSdk(socket) rewrites the schema type at the boundary so every Listener and Emitter leaf carries the actual socket (and therefore its adapter). The schema describes topic shape; the SDK supplies the runtime context.


Usage

Listening to events

sdk.chat.messages.$listener.listen(({ data }) => {
console.log(data.text, data.user);
});

Emitting events

sdk.chat.messages.$emitter.emit({ payload: { text: "Hello!" } });

Dynamic topics with parameters

sdk.chat.$roomId.$listener
.setParams({ roomId: "general" })
.listen(({ data }) => {
console.log(data);
});

SDK Configuration ($configure)

The $configure method works the same way as the core HTTP SDK. Configuration supports two value formats: plain objects for common settings and functions for full access to every Listener/Emitter setter.

Instance-Specific Keys (Dot-Path)

Use dot-path keys that mirror the SDK accessor chain to target a specific listener or emitter:

const configured = sdk.$configure({
"chat.messages.$listener": (instance) => instance.setOptions({ buffer: true }),
"chat.messages.$emitter": (instance) => instance.setPayloadMapper(myMapper),
});

Topic Group Keys

Use topic-based keys to configure all listeners and emitters on a topic at once:

const configured = sdk.$configure({
"chat/messages": { options: { priority: "high" } },
"chat/*": { options: { reconnect: true } },
});

Function-Based Values

Functions receive the instance and return a modified one, giving access to every setter:

import type { SocketSdkConfigurationValue } from "@hyper-fetch/sockets";

const setReconnect: SocketSdkConfigurationValue = (instance) =>
instance.setOptions({ reconnect: true });

const configured = sdk.$configure({
"*": setReconnect,
"chat.messages.$emitter": (instance) => instance.setPayloadMapper(myMapper),
});

Plain Object Values

For common settings, use a plain object shorthand:

const configured = sdk.$configure({
"*": { options: { reconnect: true } },
"chat/*": { options: { priority: "high" } },
});
KeyTypeDescription
optionsRecord<string, unknown>Adapter-specific options passed to setOptions

Application Order

When multiple keys match, they apply in this order (later overrides earlier):

  1. "*" — Global defaults
  2. Topic groups"chat/messages", "chat/*"
  3. Dot-path specific"chat.messages.$listener"

Configuration Keys

  • "*" — Matches all listeners and emitters (global defaults).
  • Exact topic — e.g. "chat/messages" matches all listeners/emitters on that topic.
  • Wildcard — e.g. "chat/*" matches topics like chat/messages, chat/rooms, etc.
  • Dot-path — e.g. "chat.messages.$listener" targets a specific instance.

Multi-file Configuration

import { createSocketConfiguration } from "@hyper-fetch/sockets";
import type { MyChatSchema } from "./schema";

export const chatConfig = createSocketConfiguration<MyChatSchema>()({
"chat/*": { options: { reconnect: true } },
"chat.messages.$listener": (instance) => instance.setOptions({ buffer: true }),
});

Summary

The Socket SDK provides the same developer experience as the core HTTP SDK, adapted for real-time communication. Define your schema once, access listeners and emitters via dot-notation, and centralize configuration with $configure. Configuration supports plain-object shorthand, function-based values for full setter access, topic group matching with wildcards, and a clear 3-level application order (global -> topic group -> dot-path specific).


See Also

Socket Class
Learn about the Socket class and connection management.
Listeners
Dive deeper into creating and using listeners.
Emitters
Dive deeper into creating and using emitters.
HTTP SDK
See how the same SDK pattern works for HTTP requests.