Skip to content
Meeko
Type to search the guides and the API reference. Open it from any page with / or Ctrl K; move with the arrow keys and open a result with Enter.
Get started

Contents

On this page

LocalSocketPubSub

Socket broadcast broker within a single process, used by the hub by default in Node, Bun and Deno; does not reach other instances.

server Since 1.0.0 @meekojs/meeko/server Source code
ts
import { LocalSocketPubSub } from "@meekojs/meeko/server";

LocalSocketPubSub distributes Socket broadcasts among peers connected to this process: each channel/room pair is a set of in-memory listeners, and publish calls them and waits for their local delivery attempts. It is the hub's default broker in Node, Bun and Deno when server/runtime.ts does not declare socket.pubsub.

With a single instance, nothing else is needed. With multiple replicas or processes, a broadcast in one does not reach peers in the others: use RedisSocketPubSub. It is not supported in Cloudflare Workers. Since 1.7, if a production application has channels and does not declare socket.pubsub, Meeko warns once that it is using this broker; explicitly declaring it (new LocalSocketPubSub()) indicates a single-instance deployment and removes the warning.

Usage#

ts
// server/runtime.ts
import { defineServerRuntime, LocalSocketPubSub } from "@meekojs/meeko/server";

export default defineServerRuntime(() => ({
  socket: {
    pubsub: new LocalSocketPubSub({ onListenerError: (error) => console.warn("[socket] listener", error) }),
  },
}));

Without socket.pubsub, the hub creates an identical one with console.error as observer, and warns once in production (since 1.7).

Type#

ts
class LocalSocketPubSub implements SocketPubSub {
  constructor(options?: LocalSocketPubSubOptions);
  publish(channel: string, room: string, message: SocketMessage): Promise<void>;
  subscribe(
    channel: string,
    room: string,
    listener: (message: SocketMessage) => void | Promise<void>,
  ): Unsubscribe;
}

LocalSocketPubSubOptions

ts
interface LocalSocketPubSubOptions {
	onListenerError?(error: unknown): void
}

SocketPubSub

ts
interface SocketPubSub {
  publish(channel: string, room: string, message: SocketMessage): Promise<void>;
  subscribe(
    channel: string,
    room: string,
    listener: (message: SocketMessage) => void | Promise<void>,
  ): Unsubscribe | Promise<Unsubscribe>;
}

SocketMessage

ts
interface SocketMessage {
	readonly event: string
	readonly payload: unknown
}

Parameters#

new LocalSocketPubSub(options?):

Option Type Default Description
onListenerError (error: unknown) => void console.error("[meeko] Socket listener failed", error) Receives a listener failure. Hub listeners never fail; this covers custom listeners. An observer that throws does not stop delivery to others.

Return value#

A SocketPubSub:

Method Description
publish(channel, room, { event, payload }) Calls all listeners for the channel/room pair and resolves when their delivery attempts finish.
subscribe(channel, room, listener) Adds a listener and synchronously returns the function to remove it.

Examples#

Broadcast from an API#

ts
// server/api/orders/[id]/ready.post.ts
export default defineApiRoute({
  params: orderParams,
  response: orderSchema,
  handler: async ({ params }) => {
    const order = await markReady(params.id);
    // Validates the event and payload against the channel contract and publishes to the hub's broker.
    await socket.operations.broadcast(order.storeId, "kitchenChanged", { orderId: order.id, status: "ready" });
    return order;
  },
});

broadcast uses the runtime's broker: this same code works with LocalSocketPubSub, RedisSocketPubSub or the Workers Durable Object.

Listen to a channel/room pair in a test#

ts
import { LocalSocketPubSub } from "@meekojs/meeko/server";

const pubsub = new LocalSocketPubSub();
const received: unknown[] = [];
const stop = pubsub.subscribe("chat", "general", (message) => { received.push(message.payload); });
await pubsub.publish("chat", "general", { event: "message", payload: { text: "Hello" } });
await pubsub.publish("chat", "otra", { event: "message", payload: { text: "No llega" } });
stop();
console.log(received); // [{ text: "Hello" }]

Choose the broker according to the deployment#

ts
// server/runtime.ts
export default defineServerRuntime(async ({ env }) => {
  if (!env.MEEKO_REDIS_URL) return { socket: { pubsub: new LocalSocketPubSub() } }; // a single instance
  const { publisher, subscriber } = await connectRedis(env.MEEKO_REDIS_URL);
  return {
    socket: { pubsub: new RedisSocketPubSub({ publisher, subscriber, prefix: "app:prod:" }) },
    close: async () => { await subscriber.quit(); await publisher.quit(); },
  };
});

Common mistakes#

Multiple instances with the local broker#

Incorrecttexto
2 replicas behind a load balancer, without socket.pubsub (LocalSocketPubSub by default)

broadcast resolves, but only peers connected to the replica that published it receive the message: users on the other replica do not see the change. Since 1.7, a production application with channels and without socket.pubsub warns once per process at startup, like the in-memory rateLimit counter: [meeko] Los canales de Socket usan LocalSocketPubSub, el broker en memoria de cada instancia: …. Meeko cannot count replicas, so the warning also appears with a single instance; declare new LocalSocketPubSub() to remove it.

Correctts
socket: { pubsub: new RedisSocketPubSub({ publisher: redis, subscriber, prefix: "app:prod:" }) }

Use it in Cloudflare Workers#

Incorrectts
socket: { pubsub: new LocalSocketPubSub() } // in a Worker

Connections live in each room's Durable Object, not in the isolate. The Worker rejects the application on creation: Socket channels on Cloudflare Workers require DurableObjectSocketPubSub: leave runtime.socket.pubsub unset and bind MEEKO_SOCKET, or set it to new DurableObjectSocketPubSub(env.MEEKO_SOCKET)….

Correctts
socket: env.MEEKO_SOCKET ? { pubsub: new DurableObjectSocketPubSub(env.MEEKO_SOCKET) } : {}

Notes#

  • Delivery. At most once per peer, in publication order; a peer whose queue exceeds maxPendingDeliveries closes with 1013.
  • Validation. The hub validates the event and payload before publishing and again on delivery; the broker only transports them.
  • Memory. A room's listeners are released when its last peer leaves.
  • Development. In meeko dev, recreating the application (creating or deleting a route) closes open connections and clients reconnect.

Was this page helpful?

Add a comment

It is sent when you choose “Yes” or “No”. Only the page, your answer and the comment are stored: no cookies and no personal data.

0 of 2000 characters

Edit on GitHub: docs/en/reference/server/localsocketpubsub.md