Documentation for agents: an index at /llms.txt, every docs page in one file at /llms-full.txt, and Markdown for any docs page by adding .md to its URL.

Effect KitRedis

Pub/Sub

Subscribe to channels and patterns as Streams, and handle reconnects and overflow explicitly.

RedisPubSub turns a Redis subscription into a scoped value with a Stream of messages. redis.publish sends one. Subscribing needs the RedisPubSub service, which the Node.js and Bun layers provide.

Example (Publishing and Receiving a Message)

import { ,  } from "effect";
import { ,  } from "@effect-kit/redis";
import {  } from "@effect-kit/redis/node";

const  = .({ : "redis://localhost:6379" });

const  = .(function* () {
  const  = yield* ;
  const  = yield* ;
  const  = yield* .(["events"]);

  yield* .("events", "hello");

  const [] = yield* ..(.(1), .);
  return message && new ().(.);
const message: PubSubMessage
}); export const = .(., .());

The subscribe effect resolves only after the server acknowledges the subscription, so a message published right after it is not missed. The subscription lives in the surrounding Scope. Closing the scope unsubscribes and waits for the server's confirmation. You can also call subscription.close().

redis.publish returns the number of subscribers that received the message, as a bigint.

Reading messages

A subscription has two streams over the same queue:

  • messages is a Stream<PubSubMessage>.
  • events is a Stream<SubscriptionEvent>: the same messages plus Ready and DeliveryGap markers.

Read one of them at a time. A second concurrent reader fails with SubscriptionError where reason is "concurrent-consumer".

channel and payload are Uint8Array. Nothing is decoded for you, so a message can carry binary data. Decode explicitly, as the example does with TextDecoder, or with a PayloadCodec.

Subscribing to patterns

psubscribe matches channels by glob pattern. A pattern message has _tag: "PatternMessage" and a pattern field.

Example (Subscribing to Every Order Channel)

import { ,  } from "effect";
import {  } from "@effect-kit/redis";

export const  = .(function* () {
  const  = yield* ;
  const  = yield* .(["orders:*"]);

  return ..(
    .(() => {
      const  = new ().(.);
      return . === "PatternMessage"
        ? { , : . }
        : {  };
    }),
  );
});

ssubscribe subscribes to sharded channels, which pair with redis.spublish. Sharded Pub/Sub needs a server that supports it: Redis 7.0 or later. On older versions the server rejects the command with a ServerError. subscribe, psubscribe and ssubscribe take a non-empty list.

Subscribing twice to the same channel shares one server subscription, and each subscriber gets its own stream.

Reconnects and delivery gaps

When the connection drops, messages published in the meantime are lost. Pub/Sub has no replay. The package makes the loss visible through recovery.

recoveryWhen the connection drops
"fail" (default)The stream fails with SubscriptionError and the subscription ends
"resubscribe"The package reconnects and subscribes again. events emits DeliveryGap, then Ready

Example (Reacting to a Delivery Gap)

import { ,  } from "effect";
import {  } from "@effect-kit/redis";

export const  = .(function* () {
  const  = yield* ;
  const  = yield* .(["events"], { : "resubscribe" });

  yield* ..(
    .(() =>
      . === "DeliveryGap"
        ? .("messages may have been missed; reload state")
        : .,
    ),
  );
});

resubscribe does not replay anything. After a DeliveryGap, reload whatever state the messages were keeping current. If you read messages instead of events, a gap fails the stream with reason: "delivery-gap", so lost delivery is never hidden.

The restore step makes up to subscriptions.reconnectAttempts attempts (default 3, at most 10), waiting subscriptions.reconnectDelayMillis (default 100 ms) between them. If the last one fails, every subscription on the layer fails.

Overflow

Each subscription has a bounded queue. If your code reads more slowly than messages arrive and the queue exceeds maxMessages (default 64) or maxBytes (default 1048576, 1 MiB), the stream fails with SubscriptionError and reason: "overflow". Messages are not dropped silently.

SubscriptionError reasonMeaning
overflowThe queue or a retained-bytes budget was exceeded
delivery-gapmessages hit a gap after a reconnect
concurrent-consumerTwo readers on one subscription
capacityToo many listeners or interests (maxListeners 128, maxInterests 1024)
closedThe subscription or client was closed

Move slow work out of the read loop, for example by forking per message or handing it to a bounded queue, so the reader stays ahead of the queue. All subscriptions on a layer also share a retained budget of maxRetainedBytes (default 16 MiB). Set the limits in subscriptions on the layer.

Pub/Sub on Upstash

Text only on Upstash

Upstash over HTTP does not provide RedisPubSub. Use UpstashEvents.subscribe(channel), which streams text messages over SSE, one channel per call.

Example (Subscribing over HTTP)

import { ,  } from "effect";
import {  } from "@effect-kit/redis/upstash";

export const  = .(function* () {
  const  = yield* ;
  const  = yield* .("events");

  return yield* ..(.(1), .);
}).(.);

Each message is { channel: string, message: string }. Channel names cannot contain commas or line breaks. Publishing is the same redis.publish.