Skip to content
tRPC

Publisher backends

Recipes for a Publisher on Redis streams, Postgres LISTEN/NOTIFY and Durable Objects.

On this page

No backend packages ship; these recipes are yours to copy and adapt. Both serialize events with danSON (so Dates survive) and share one helper that replays kept events after lastEventId, then follows live ones:

import { import EffectEffect, import LayerLayer, import StreamStream } from 'effect';
import { function createSerializer(opts?: SerializerOptions): SerializercreateSerializer } from '@trpc12/trpc/serializer';
import {
  function tracked<TData>(id: string, data: TData): TrackedEnvelope<TData>tracked,
  type 
type PublisherEvents = {
    [x: string]: unknown;
}
PublisherEvents
,
type interface PublisherShape<TEvents extends PublisherEvents>PublisherShape, } from '@trpc12/trpc/server'; const const danson: Serializer<DansonForm>danson = function createSerializer(opts?: SerializerOptions): SerializercreateSerializer(); const const encode: (data: unknown) => stringencode = (data: unknowndata: unknown) => var JSON: JSONJSON.JSON.stringify(value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string (+1 overload)stringify(const danson: Serializer<DansonForm>danson.Serializer<DansonForm>.serialize(value: unknown, opts?: SerializeOpts): SerializeReturnserialize(data: unknowndata)); const const decode: (text: string) => unknowndecode = (text: stringtext: string) => const danson: Serializer<DansonForm>danson.Serializer<DansonForm>.deserialize<unknown>(value: SerializeReturn, opts?: DeserializeOpts): unknowndeserialize(var JSON: JSONJSON.JSON.parse(text: string, reviver?: (this: any, key: string, value: any) => any): anyparse(text: stringtext)); const
const resume: <TId>(opts: {
    live: Stream.Stream<[TId, string]>;
    replay: Effect.Effect<[TId, string][]>;
    after: TId | undefined;
    newer: (a: TId, b: TId) => boolean;
    key: (id: TId) => string;
}) => Stream.Stream<TrackedEnvelope<unknown>, never, never>
resume
= <
function (type parameter) TId in <TId>(opts: {
    live: Stream.Stream<[TId, string]>;
    replay: Effect.Effect<[TId, string][]>;
    after: TId | undefined;
    newer: (a: TId, b: TId) => boolean;
    key: (id: TId) => string;
}): Stream.Stream<...>
TId
>(
opts: {
    live: Stream.Stream<[TId, string]>;
    replay: Effect.Effect<[TId, string][]>;
    after: TId | undefined;
    newer: (a: TId, b: TId) => boolean;
    key: (id: TId) => string;
}
opts
: {
live: Stream.Stream<[TId, string], never, never>live: import StreamStream.interface Stream<out A, out E = never, out R = never>Stream<[
function (type parameter) TId in <TId>(opts: {
    live: Stream.Stream<[TId, string]>;
    replay: Effect.Effect<[TId, string][]>;
    after: TId | undefined;
    newer: (a: TId, b: TId) => boolean;
    key: (id: TId) => string;
}): Stream.Stream<...>
TId
, string]>;
replay: Effect.Effect<[TId, string][], never, never>replay: import EffectEffect.interface Effect<out A, out E = never, out R = never>Effect<[
function (type parameter) TId in <TId>(opts: {
    live: Stream.Stream<[TId, string]>;
    replay: Effect.Effect<[TId, string][]>;
    after: TId | undefined;
    newer: (a: TId, b: TId) => boolean;
    key: (id: TId) => string;
}): Stream.Stream<...>
TId
, string][]>;
after: TId | undefinedafter:
function (type parameter) TId in <TId>(opts: {
    live: Stream.Stream<[TId, string]>;
    replay: Effect.Effect<[TId, string][]>;
    after: TId | undefined;
    newer: (a: TId, b: TId) => boolean;
    key: (id: TId) => string;
}): Stream.Stream<...>
TId
| undefined;
newer: (a: TId, b: TId) => booleannewer: (a: TIda:
function (type parameter) TId in <TId>(opts: {
    live: Stream.Stream<[TId, string]>;
    replay: Effect.Effect<[TId, string][]>;
    after: TId | undefined;
    newer: (a: TId, b: TId) => boolean;
    key: (id: TId) => string;
}): Stream.Stream<...>
TId
, b: TIdb:
function (type parameter) TId in <TId>(opts: {
    live: Stream.Stream<[TId, string]>;
    replay: Effect.Effect<[TId, string][]>;
    after: TId | undefined;
    newer: (a: TId, b: TId) => boolean;
    key: (id: TId) => string;
}): Stream.Stream<...>
TId
) => boolean;
key: (id: TId) => stringkey: (id: TIdid:
function (type parameter) TId in <TId>(opts: {
    live: Stream.Stream<[TId, string]>;
    replay: Effect.Effect<[TId, string][]>;
    after: TId | undefined;
    newer: (a: TId, b: TId) => boolean;
    key: (id: TId) => string;
}): Stream.Stream<...>
TId
) => string;
}) => import StreamStream.const unwrap: <TrackedEnvelope<unknown>, never, never, never, never>(effect: Effect.Effect<Stream.Stream<TrackedEnvelope<unknown>, never, never>, never, never>) => Stream.Stream<TrackedEnvelope<unknown>, never, never>unwrap( import EffectEffect.const gen: <Effect.Effect<[TId, string][], never, never>, Stream.Stream<TrackedEnvelope<unknown>, never, never>>(f: () => Generator<Effect.Effect<[TId, string][], never, never>, Stream.Stream<TrackedEnvelope<unknown>, never, never>, never>) => Effect.Effect<Stream.Stream<TrackedEnvelope<unknown>, never, never>, never, never> (+1 overload)gen(function* () { const const replay: [TId, string][]replay =
opts: {
    live: Stream.Stream<[TId, string]>;
    replay: Effect.Effect<[TId, string][]>;
    after: TId | undefined;
    newer: (a: TId, b: TId) => boolean;
    key: (id: TId) => string;
}
opts
.after: TId | undefinedafter === var undefinedundefined ? [] : yield*
opts: {
    live: Stream.Stream<[TId, string]>;
    replay: Effect.Effect<[TId, string][]>;
    after: TId | undefined;
    newer: (a: TId, b: TId) => boolean;
    key: (id: TId) => string;
}
opts
.replay: Effect.Effect<[TId, string][], never, never>replay;
const const seen: TId | undefinedseen = const replay: [TId, string][]replay.Array<[TId, string]>.at(index: number): [TId, string] | undefinedat(-1)?.[0] ??
opts: {
    live: Stream.Stream<[TId, string]>;
    replay: Effect.Effect<[TId, string][]>;
    after: TId | undefined;
    newer: (a: TId, b: TId) => boolean;
    key: (id: TId) => string;
}
opts
.after: TId | undefinedafter;
const const live: Stream.Stream<[TId, string], never, never>live =
opts: {
    live: Stream.Stream<[TId, string]>;
    replay: Effect.Effect<[TId, string][]>;
    after: TId | undefined;
    newer: (a: TId, b: TId) => boolean;
    key: (id: TId) => string;
}
opts
.live: Stream.Stream<[TId, string], never, never>live.Pipeable.pipe<Stream.Stream<[TId, string], never, never>, Stream.Stream<[TId, string], never, never>>(this: Stream.Stream<[TId, string], never, never>, ab: (_: Stream.Stream<[TId, string], never, never>) => Stream.Stream<[TId, string], never, never>): Stream.Stream<[TId, string], never, never> (+21 overloads)pipe(
import StreamStream.const filter: <[TId, string]>(predicate: Predicate<[TId, string]>) => <E, R>(self: Stream.Stream<[TId, string], E, R>) => Stream.Stream<[TId, string], E, R> (+3 overloads)filter(([id: TIdid]) => const seen: TId | undefinedseen === var undefinedundefined ||
opts: {
    live: Stream.Stream<[TId, string]>;
    replay: Effect.Effect<[TId, string][]>;
    after: TId | undefined;
    newer: (a: TId, b: TId) => boolean;
    key: (id: TId) => string;
}
opts
.newer: (a: TId, b: TId) => booleannewer(id: TIdid, const seen: TId & ({} | null)seen)),
); return import StreamStream.const concat: <[TId, string], never, never, [TId, string], never, never>(self: Stream.Stream<[TId, string], never, never>, that: Stream.Stream<[TId, string], never, never>) => Stream.Stream<[TId, string], never, never> (+1 overload)concat(import StreamStream.
const fromIterable: <[TId, string]>(iterable: Iterable<[TId, string]>, options?: {
    readonly chunkSize?: number | undefined;
}) => Stream.Stream<[TId, string], never, never>
fromIterable
(const replay: [TId, string][]replay), const live: Stream.Stream<[TId, string], never, never>live).Pipeable.pipe<Stream.Stream<[TId, string], never, never>, Stream.Stream<TrackedEnvelope<unknown>, never, never>>(this: Stream.Stream<[TId, string], never, never>, ab: (_: Stream.Stream<[TId, string], never, never>) => Stream.Stream<TrackedEnvelope<unknown>, never, never>): Stream.Stream<TrackedEnvelope<unknown>, never, never> (+21 overloads)pipe(
import StreamStream.const map: <[TId, string], TrackedEnvelope<unknown>>(f: (a: [TId, string], i: number) => TrackedEnvelope<unknown>) => <E, R>(self: Stream.Stream<[TId, string], E, R>) => Stream.Stream<TrackedEnvelope<unknown>, E, R> (+1 overload)map(([id: TIdid, payload: stringpayload]) => tracked<unknown>(id: string, data: unknown): TrackedEnvelope<unknown>tracked(
opts: {
    live: Stream.Stream<[TId, string]>;
    replay: Effect.Effect<[TId, string][]>;
    after: TId | undefined;
    newer: (a: TId, b: TId) => boolean;
    key: (id: TId) => string;
}
opts
.key: (id: TId) => stringkey(id: TIdid), const decode: (text: string) => unknowndecode(payload: stringpayload))),
); }), );

Redis

Effect's Redis service (effect/persistence) with NodeRedis.layer from @effect/platform-node. Each channel is a Redis stream, which keeps history and assigns ids, plus a pub/sub channel for live delivery. Subscribing before reading history means no event falls in between.

import { import NodeRedisNodeRedis } from '@effect/platform-node';
import { import RedisRedis } from 'effect/persistence';

const const redisNewer: (a: string, b: string) => booleanredisNewer = (a: stringa: string, b: stringb: string) => {
  const [const am: numberam = 0, const as: numberas = 0] = a: stringa.String.split(separator: string | RegExp, limit?: number): string[] (+1 overload)split('-').Array<string>.map<number>(callbackfn: (value: string, index: number, array: string[]) => number, thisArg?: any): number[]map(var Number: NumberConstructorNumber);
  const [const bm: numberbm = 0, const bs: numberbs = 0] = b: stringb.String.split(separator: string | RegExp, limit?: number): string[] (+1 overload)split('-').Array<string>.map<number>(callbackfn: (value: string, index: number, array: string[]) => number, thisArg?: any): number[]map(var Number: NumberConstructorNumber);
  return const am: numberam !== const bm: numberbm ? const am: numberam > const bm: numberbm : const as: numberas > const bs: numberbs;
};

const 
const redisPublisher: <TEvents extends PublisherEvents>(opts: {
    prefix: string;
    maxLen?: number;
}) => Effect.Effect<PublisherShape<TEvents>, never, Redis.Redis>
redisPublisher
= <
function (type parameter) TEvents in <TEvents extends PublisherEvents>(opts: {
    prefix: string;
    maxLen?: number;
}): Effect.Effect<PublisherShape<TEvents>, never, Redis.Redis>
TEvents
extends
type PublisherEvents = {
    [x: string]: unknown;
}
PublisherEvents
>(
opts: {
    prefix: string;
    maxLen?: number;
}
opts
: {
prefix: stringprefix: string; maxLen?: number | undefinedmaxLen?: number; }) => import EffectEffect.
const gen: <Effect.Effect<{
    readonly send: <A = unknown>(command: string, ...args: ReadonlyArray<string>) => Effect.Effect<A, Redis.RedisError>;
    readonly subscribe: (channel: string) => Effect.Effect<Dequeue<Redis.RedisMessage, Redis.RedisError>, never, Scope>;
    readonly eval: <Config extends {
        readonly params: ReadonlyArray<unknown>;
        readonly result: unknown;
    }>(script: Redis.Script<Config>) => (...params: Config["params"]) => Effect.Effect<Config["result"], Redis.RedisError>;
}, never, Redis.Redis>, PublisherShape<...>>(f: () => Generator<...>) => Effect.Effect<...> (+1 overload)
gen
(function* () {
const
const redis: {
    readonly send: <A = unknown>(command: string, ...args: ReadonlyArray<string>) => Effect.Effect<A, Redis.RedisError>;
    readonly subscribe: (channel: string) => Effect.Effect<Dequeue<Redis.RedisMessage, Redis.RedisError>, never, Scope>;
    readonly eval: <Config extends {
        readonly params: ReadonlyArray<unknown>;
        readonly result: unknown;
    }>(script: Redis.Script<Config>) => (...params: Config["params"]) => Effect.Effect<Config["result"], Redis.RedisError>;
}
redis
= yield* import RedisRedis.class RedisRedis;
const const key: (channel: string) => stringkey = (channel: stringchannel: string) => `${
opts: {
    prefix: string;
    maxLen?: number;
}
opts
.prefix: stringprefix}:${channel: stringchannel}`;
const
const shape: {
    publish: (channel: string, data: unknown) => Effect.Effect<string, never, never>;
    subscribe: (channel: string, o?: {
        readonly lastEventId?: string | undefined;
    }) => Stream.Stream<TrackedEnvelope<unknown>, never, never>;
}
shape
= {
publish: (channel: string, data: unknown) => Effect.Effect<string, never, never>publish: (channel: stringchannel: string, data: unknowndata: unknown) => import EffectEffect.const gen: <Effect.Effect<unknown, Redis.RedisError, never>, string>(f: () => Generator<Effect.Effect<unknown, Redis.RedisError, never>, string, never>) => Effect.Effect<string, Redis.RedisError, never> (+1 overload)gen(function* () { const const payload: stringpayload = const encode: (data: unknown) => stringencode(data: unknowndata); const const id: stringid = yield*
const redis: {
    readonly send: <A = unknown>(command: string, ...args: ReadonlyArray<string>) => Effect.Effect<A, Redis.RedisError>;
    readonly subscribe: (channel: string) => Effect.Effect<Dequeue<Redis.RedisMessage, Redis.RedisError>, never, Scope>;
    readonly eval: <Config extends {
        readonly params: ReadonlyArray<unknown>;
        readonly result: unknown;
    }>(script: Redis.Script<Config>) => (...params: Config["params"]) => Effect.Effect<Config["result"], Redis.RedisError>;
}
redis
.send: <string>(command: string, ...args: ReadonlyArray<string>) => Effect.Effect<string, Redis.RedisError, never>send<string>(
'XADD', const key: (channel: string) => stringkey(channel: stringchannel), 'MAXLEN', '~',
var String: StringConstructor
(value?: any) => string
String
(
opts: {
    prefix: string;
    maxLen?: number;
}
opts
.maxLen?: number | undefinedmaxLen ?? 1000),
'*', 'data', const payload: stringpayload, ); yield*
const redis: {
    readonly send: <A = unknown>(command: string, ...args: ReadonlyArray<string>) => Effect.Effect<A, Redis.RedisError>;
    readonly subscribe: (channel: string) => Effect.Effect<Dequeue<Redis.RedisMessage, Redis.RedisError>, never, Scope>;
    readonly eval: <Config extends {
        readonly params: ReadonlyArray<unknown>;
        readonly result: unknown;
    }>(script: Redis.Script<Config>) => (...params: Config["params"]) => Effect.Effect<Config["result"], Redis.RedisError>;
}
redis
.send: <unknown>(command: string, ...args: ReadonlyArray<string>) => Effect.Effect<unknown, Redis.RedisError, never>send(
'PUBLISH', const key: (channel: string) => stringkey(channel: stringchannel), var JSON: JSONJSON.JSON.stringify(value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string (+1 overload)stringify([const id: stringid, const payload: stringpayload]), ); return const id: stringid; }).Pipeable.pipe<Effect.Effect<string, Redis.RedisError, never>, Effect.Effect<string, never, never>>(this: Effect.Effect<string, Redis.RedisError, never>, ab: (_: Effect.Effect<string, Redis.RedisError, never>) => Effect.Effect<string, never, never>): Effect.Effect<string, never, never> (+21 overloads)pipe(import EffectEffect.const orDie: <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, never, R>orDie),
subscribe: (channel: string, o?: {
    readonly lastEventId?: string | undefined;
}) => Stream.Stream<TrackedEnvelope<unknown>, never, never>
subscribe
: (
channel: stringchannel: string,
o: {
    readonly lastEventId?: string | undefined;
} | undefined
o
?: { readonly lastEventId?: string | undefinedlastEventId?: string | undefined },
) => import StreamStream.const unwrap: <TrackedEnvelope<unknown>, never, never, never, Scope>(effect: Effect.Effect<Stream.Stream<TrackedEnvelope<unknown>, never, never>, never, Scope>) => Stream.Stream<TrackedEnvelope<unknown>, never, never>unwrap( import EffectEffect.const gen: <Effect.Effect<Dequeue<Redis.RedisMessage, Redis.RedisError>, never, Scope>, Stream.Stream<TrackedEnvelope<unknown>, never, never>>(f: () => Generator<Effect.Effect<Dequeue<Redis.RedisMessage, Redis.RedisError>, never, Scope>, Stream.Stream<TrackedEnvelope<unknown>, never, never>, never>) => Effect.Effect<Stream.Stream<TrackedEnvelope<unknown>, never, never>, never, Scope> (+1 overload)gen(function* () { const const messages: Dequeue<Redis.RedisMessage, Redis.RedisError>messages = yield*
const redis: {
    readonly send: <A = unknown>(command: string, ...args: ReadonlyArray<string>) => Effect.Effect<A, Redis.RedisError>;
    readonly subscribe: (channel: string) => Effect.Effect<Dequeue<Redis.RedisMessage, Redis.RedisError>, never, Scope>;
    readonly eval: <Config extends {
        readonly params: ReadonlyArray<unknown>;
        readonly result: unknown;
    }>(script: Redis.Script<Config>) => (...params: Config["params"]) => Effect.Effect<Config["result"], Redis.RedisError>;
}
redis
.subscribe: (channel: string) => Effect.Effect<Dequeue<Redis.RedisMessage, Redis.RedisError>, never, Scope>subscribe(const key: (channel: string) => stringkey(channel: stringchannel));
return
const resume: <string>(opts: {
    live: Stream.Stream<[string, string], never, never>;
    replay: Effect.Effect<[string, string][], never, never>;
    after: string | undefined;
    newer: (a: string, b: string) => boolean;
    key: (id: string) => string;
}) => Stream.Stream<TrackedEnvelope<unknown>, never, never>
resume
({
live: Stream.Stream<[string, string], never, never>live: import StreamStream.const fromQueue: <Redis.RedisMessage, Redis.RedisError>(queue: Dequeue<Redis.RedisMessage, Redis.RedisError>) => Stream.Stream<Redis.RedisMessage, Redis.RedisError, never>fromQueue(const messages: Dequeue<Redis.RedisMessage, Redis.RedisError>messages).Pipeable.pipe<Stream.Stream<Redis.RedisMessage, Redis.RedisError, never>, Stream.Stream<[string, string], Redis.RedisError, never>, Stream.Stream<[string, string], never, never>>(this: Stream.Stream<...>, ab: (_: Stream.Stream<Redis.RedisMessage, Redis.RedisError, never>) => Stream.Stream<[string, string], Redis.RedisError, never>, bc: (_: Stream.Stream<[string, string], Redis.RedisError, never>) => Stream.Stream<[string, string], never, never>): Stream.Stream<...> (+21 overloads)pipe( import StreamStream.const map: <Redis.RedisMessage, [string, string]>(f: (a: Redis.RedisMessage, i: number) => [string, string]) => <E, R>(self: Stream.Stream<Redis.RedisMessage, E, R>) => Stream.Stream<[string, string], E, R> (+1 overload)map((m: Redis.RedisMessagem) => var JSON: JSONJSON.JSON.parse(text: string, reviver?: (this: any, key: string, value: any) => any): anyparse(m: Redis.RedisMessagem.RedisMessage.message: stringmessage) as [string, string]), import StreamStream.const orDie: <A, E, R>(self: Stream.Stream<A, E, R>) => Stream.Stream<A, never, R>orDie, ), replay: Effect.Effect<[string, string][], never, never>replay:
const redis: {
    readonly send: <A = unknown>(command: string, ...args: ReadonlyArray<string>) => Effect.Effect<A, Redis.RedisError>;
    readonly subscribe: (channel: string) => Effect.Effect<Dequeue<Redis.RedisMessage, Redis.RedisError>, never, Scope>;
    readonly eval: <Config extends {
        readonly params: ReadonlyArray<unknown>;
        readonly result: unknown;
    }>(script: Redis.Script<Config>) => (...params: Config["params"]) => Effect.Effect<Config["result"], Redis.RedisError>;
}
redis
.send: <[string, [string, string]][]>(command: string, ...args: ReadonlyArray<string>) => Effect.Effect<[string, [string, string]][], Redis.RedisError, never>send<[string, [string, string]][]>( 'XRANGE', const key: (channel: string) => stringkey(channel: stringchannel), `(${
o: {
    readonly lastEventId?: string | undefined;
} | undefined
o
?.lastEventId?: string | undefinedlastEventId}`,
'+', ) .Pipeable.pipe<Effect.Effect<[string, [string, string]][], Redis.RedisError, never>, Effect.Effect<[string, string][], Redis.RedisError, never>, Effect.Effect<[string, string][], never, never>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<[string, [string, string]][], Redis.RedisError, never>) => Effect.Effect<[string, string][], Redis.RedisError, never>, bc: (_: Effect.Effect<[string, string][], Redis.RedisError, never>) => Effect.Effect<[string, string][], never, never>): Effect.Effect<...> (+21 overloads)pipe( import EffectEffect.const map: <[string, [string, string]][], [string, string][]>(f: (a: [string, [string, string]][]) => [string, string][]) => <E, R>(self: Effect.Effect<[string, [string, string]][], E, R>) => Effect.Effect<[string, string][], E, R> (+1 overload)map((entries: [string, [string, string]][]entries) => entries: [string, [string, string]][]entries.Array<[string, [string, string]]>.map<[string, string]>(callbackfn: (value: [string, [string, string]], index: number, array: [string, [string, string]][]) => [string, string], thisArg?: any): [string, string][]map(([id: stringid, [, payload: stringpayload]]): [string, string] => [ id: stringid, payload: stringpayload, ]), ), import EffectEffect.const orDie: <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, never, R>orDie, ), after: string | undefinedafter:
o: {
    readonly lastEventId?: string | undefined;
} | undefined
o
?.lastEventId?: string | undefinedlastEventId,
newer: (a: string, b: string) => booleannewer: const redisNewer: (a: string, b: string) => booleanredisNewer, key: (id: string) => stringkey: (id: stringid) => id: stringid, }); }), ), }; return
const shape: {
    publish: (channel: string, data: unknown) => Effect.Effect<string, never, never>;
    subscribe: (channel: string, o?: {
        readonly lastEventId?: string | undefined;
    }) => Stream.Stream<TrackedEnvelope<unknown>, never, never>;
}
shape
as interface PublisherShape<TEvents extends PublisherEvents>PublisherShape<
function (type parameter) TEvents in <TEvents extends PublisherEvents>(opts: {
    prefix: string;
    maxLen?: number;
}): Effect.Effect<PublisherShape<TEvents>, never, Redis.Redis>
TEvents
>;
}); const const layer: Layer.Layer<Posts, Redis.RedisError, never>layer = import LayerLayer.const effect: <Posts, PublisherShape<PostEvents>>(service: Key<Posts, PublisherShape<PostEvents>>) => <E, R>(effect: Effect.Effect<PublisherShape<PostEvents>, E, R>) => Layer.Layer<Posts, E, Exclude<R, Scope>> (+1 overload)effect(class PostsPosts)(
const redisPublisher: <PostEvents>(opts: {
    prefix: string;
    maxLen?: number;
}) => Effect.Effect<PublisherShape<PostEvents>, never, Redis.Redis>
redisPublisher
<
type PostEvents = {
    created: Post;
    deleted: {
        id: string;
    };
}
PostEvents
>({ prefix: stringprefix: 'posts' }),
).Pipeable.pipe<Layer.Layer<Posts, never, Redis.Redis>, Layer.Layer<Posts, Redis.RedisError, never>>(this: Layer.Layer<Posts, never, Redis.Redis>, ab: (_: Layer.Layer<Posts, never, Redis.Redis>) => Layer.Layer<Posts, Redis.RedisError, never>): Layer.Layer<Posts, Redis.RedisError, never> (+21 overloads)pipe(import LayerLayer.const provide: <never, Redis.RedisError, Redis.Redis | NodeRedis.NodeRedis>(that: Layer.Layer<Redis.Redis | NodeRedis.NodeRedis, Redis.RedisError, never>) => <RIn2, E2, ROut2>(self: Layer.Layer<ROut2, E2, RIn2>) => Layer.Layer<ROut2, Redis.RedisError | E2, Exclude<RIn2, Redis.Redis | NodeRedis.NodeRedis>> (+3 overloads)provide(import NodeRedisNodeRedis.const layer: (options?: RedisClientOptions<RedisModules, RedisFunctions, RedisScripts, RespVersions, TypeMapping, RedisSocketOptions> | undefined) => Layer.Layer<Redis.Redis | NodeRedis.NodeRedis, Redis.RedisError>layer({ RedisClientOptions<M extends RedisModules = RedisModules, F extends RedisFunctions = RedisFunctions, S extends RedisScripts = RedisScripts, RESP extends RespVersions = 3, TYPE_MAPPING extends TypeMapping = TypeMapping, SocketOptions extends RedisSocketOptions = RedisSocketOptions>.url?: string | undefinedurl: var process: NodeJS.Processprocess.NodeJS.Process.env: NodeJS.ProcessEnvenv.string | undefinedREDIS_URL })));

Postgres

@effect/sql-pg's PgClient has listen and notify. Events go into a table for history; NOTIFY carries new ones. NOTIFY payloads are capped at 8000 bytes, so for bigger events send only the id and read the row.

import { import PgClientPgClient } from '@effect/sql-pg';
import { import RedactedRedacted } from 'effect';

// create table trpc_events (id bigserial primary key, channel text not null,
//   payload text not null, created_at timestamptz not null default now());

const const pgPublisher: <TEvents extends PublisherEvents>() => Effect.Effect<PublisherShape<TEvents>, never, PgClient.PgClient>pgPublisher = <function (type parameter) TEvents in <TEvents extends PublisherEvents>(): Effect.Effect<PublisherShape<TEvents>, never, PgClient.PgClient>TEvents extends 
type PublisherEvents = {
    [x: string]: unknown;
}
PublisherEvents
>() =>
import EffectEffect.const gen: <Effect.Effect<PgClient.PgClient, never, PgClient.PgClient>, PublisherShape<TEvents>>(f: () => Generator<Effect.Effect<PgClient.PgClient, never, PgClient.PgClient>, PublisherShape<TEvents>, never>) => Effect.Effect<PublisherShape<TEvents>, never, PgClient.PgClient> (+1 overload)gen(function* () { const const sql: PgClient.PgClientsql = yield* import PgClientPgClient.const PgClient: Service<PgClient.PgClient, PgClient.PgClient>PgClient; const
const shape: {
    publish: (channel: string, data: unknown) => Effect.Effect<string, never, never>;
    subscribe: (channel: string, o?: {
        readonly lastEventId?: string | undefined;
    }) => Stream.Stream<TrackedEnvelope<unknown>, never, never>;
}
shape
= {
publish: (channel: string, data: unknown) => Effect.Effect<string, never, never>publish: (channel: stringchannel: string, data: unknowndata: unknown) => import EffectEffect.const gen: <Effect.Effect<void, SqlError, never>, string>(f: () => Generator<Effect.Effect<void, SqlError, never>, string, never>) => Effect.Effect<string, SqlError, never> (+1 overload)gen(function* () { const const payload: stringpayload = const encode: (data: unknown) => stringencode(data: unknowndata); const [
const row: {
    id: string;
}
row
] = yield* const sql: PgClient.PgClientsql<{ id: stringid: string }>`
insert into trpc_events (channel, payload) values (${channel: stringchannel}, ${const payload: stringpayload}) returning id::text`; yield* const sql: PgClient.PgClientsql.PgClient.notify: (channel: string, payload: string) => Effect.Effect<void, SqlError>notify(channel: stringchannel, var JSON: JSONJSON.JSON.stringify(value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string (+1 overload)stringify([
const row: {
    id: string;
}
row
!.id: stringid, const payload: stringpayload]));
return
const row: {
    id: string;
}
row
!.id: stringid;
}).Pipeable.pipe<Effect.Effect<string, SqlError, never>, Effect.Effect<string, never, never>>(this: Effect.Effect<string, SqlError, never>, ab: (_: Effect.Effect<string, SqlError, never>) => Effect.Effect<string, never, never>): Effect.Effect<string, never, never> (+21 overloads)pipe(import EffectEffect.const orDie: <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, never, R>orDie),
subscribe: (channel: string, o?: {
    readonly lastEventId?: string | undefined;
}) => Stream.Stream<TrackedEnvelope<unknown>, never, never>
subscribe
: (
channel: stringchannel: string,
o: {
    readonly lastEventId?: string | undefined;
} | undefined
o
?: { readonly lastEventId?: string | undefinedlastEventId?: string | undefined },
) => import StreamStream.const unwrap: <TrackedEnvelope<unknown>, never, never, never, Scope>(effect: Effect.Effect<Stream.Stream<TrackedEnvelope<unknown>, never, never>, never, Scope>) => Stream.Stream<TrackedEnvelope<unknown>, never, never>unwrap( import EffectEffect.const gen: <Effect.Effect<Dequeue<Notification, SqlError>, SqlError, Scope>, Stream.Stream<TrackedEnvelope<unknown>, never, never>>(f: () => Generator<Effect.Effect<Dequeue<Notification, SqlError>, SqlError, Scope>, Stream.Stream<TrackedEnvelope<unknown>, never, never>, never>) => Effect.Effect<Stream.Stream<TrackedEnvelope<unknown>, never, never>, SqlError, Scope> (+1 overload)gen(function* () { const const notifications: Dequeue<Notification, SqlError>notifications = yield* const sql: PgClient.PgClientsql.PgClient.listen: (channel: string) => Effect.Effect<Dequeue<Notification, SqlError>, SqlError, Scope>listen(channel: stringchannel); return
const resume: <bigint>(opts: {
    live: Stream.Stream<[bigint, string], never, never>;
    replay: Effect.Effect<[bigint, string][], never, never>;
    after: bigint | undefined;
    newer: (a: bigint, b: bigint) => boolean;
    key: (id: bigint) => string;
}) => Stream.Stream<TrackedEnvelope<unknown>, never, never>
resume
({
live: Stream.Stream<[bigint, string], never, never>live: import StreamStream.const fromQueue: <Notification, SqlError>(queue: Dequeue<Notification, SqlError>) => Stream.Stream<Notification, SqlError, never>fromQueue(const notifications: Dequeue<Notification, SqlError>notifications).Pipeable.pipe<Stream.Stream<Notification, SqlError, never>, Stream.Stream<[bigint, string], SqlError, never>, Stream.Stream<[bigint, string], never, never>>(this: Stream.Stream<Notification, SqlError, never>, ab: (_: Stream.Stream<Notification, SqlError, never>) => Stream.Stream<[bigint, string], SqlError, never>, bc: (_: Stream.Stream<[bigint, string], SqlError, never>) => Stream.Stream<[bigint, string], never, never>): Stream.Stream<...> (+21 overloads)pipe( import StreamStream.const map: <Notification, [bigint, string]>(f: (a: Notification, i: number) => [bigint, string]) => <E, R>(self: Stream.Stream<Notification, E, R>) => Stream.Stream<[bigint, string], E, R> (+1 overload)map((n: Notificationn) => { const [const id: stringid, const payload: stringpayload] = var JSON: JSONJSON.JSON.parse(text: string, reviver?: (this: any, key: string, value: any) => any): anyparse(n: Notificationn.Notification.payload: stringpayload) as [ string, string, ]; return [
var BigInt: BigIntConstructor
(value: bigint | boolean | number | string) => bigint
BigInt
(const id: stringid), const payload: stringpayload] as [bigint, string];
}), import StreamStream.const orDie: <A, E, R>(self: Stream.Stream<A, E, R>) => Stream.Stream<A, never, R>orDie, ), replay: Effect.Effect<[bigint, string][], never, never>replay: const sql: PgClient.PgClientsql<{ id: stringid: string; payload: stringpayload: string }>` select id::text, payload from trpc_events where channel = ${channel: stringchannel} and id > ${
o: {
    readonly lastEventId?: string | undefined;
} | undefined
o
?.lastEventId?: string | undefinedlastEventId ?? '0'} order by id`.
Pipeable.pipe<Statement<{
    id: string;
    payload: string;
}>, Effect.Effect<[bigint, string][], SqlError, never>, Effect.Effect<[bigint, string][], never, never>>(this: Statement<{
    id: string;
    payload: string;
}>, ab: (_: Statement<{
    id: string;
    payload: string;
}>) => Effect.Effect<[bigint, string][], SqlError, never>, bc: (_: Effect.Effect<[bigint, string][], SqlError, never>) => Effect.Effect<[bigint, string][], never, never>): Effect.Effect<[bigint, string][], never, never> (+21 overloads)
pipe
(
import EffectEffect.
const map: <readonly {
    id: string;
    payload: string;
}[], [bigint, string][]>(f: (a: readonly {
    id: string;
    payload: string;
}[]) => [bigint, string][]) => <E, R>(self: Effect.Effect<readonly {
    id: string;
    payload: string;
}[], E, R>) => Effect.Effect<[bigint, string][], E, R> (+1 overload)
map
((
rows: readonly {
    id: string;
    payload: string;
}[]
rows
) =>
rows: readonly {
    id: string;
    payload: string;
}[]
rows
.
ReadonlyArray<{ id: string; payload: string; }>.map<[bigint, string]>(callbackfn: (value: {
    id: string;
    payload: string;
}, index: number, array: readonly {
    id: string;
    payload: string;
}[]) => [bigint, string], thisArg?: any): [bigint, string][]
map
((
r: {
    id: string;
    payload: string;
}
r
): [bigint, string] => [
var BigInt: BigIntConstructor
(value: bigint | boolean | number | string) => bigint
BigInt
(
r: {
    id: string;
    payload: string;
}
r
.id: stringid),
r: {
    id: string;
    payload: string;
}
r
.payload: stringpayload]),
), import EffectEffect.const orDie: <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, never, R>orDie, ), after: bigint | undefinedafter:
o: {
    readonly lastEventId?: string | undefined;
} | undefined
o
?.lastEventId?: string | undefinedlastEventId === var undefinedundefined
? var undefinedundefined :
var BigInt: BigIntConstructor
(value: bigint | boolean | number | string) => bigint
BigInt
(
o: {
    readonly lastEventId?: string | undefined;
}
o
.lastEventId?: stringlastEventId),
newer: (a: bigint, b: bigint) => booleannewer: (a: biginta, b: bigintb) => a: biginta > b: bigintb, key: (id: bigint) => stringkey: var String: StringConstructorString, }); }).Pipeable.pipe<Effect.Effect<Stream.Stream<TrackedEnvelope<unknown>, never, never>, SqlError, Scope>, Effect.Effect<Stream.Stream<TrackedEnvelope<unknown>, never, never>, never, Scope>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<Stream.Stream<TrackedEnvelope<unknown>, never, never>, SqlError, Scope>) => Effect.Effect<Stream.Stream<TrackedEnvelope<unknown>, never, never>, never, Scope>): Effect.Effect<...> (+21 overloads)pipe(import EffectEffect.const orDie: <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, never, R>orDie), ), }; return
const shape: {
    publish: (channel: string, data: unknown) => Effect.Effect<string, never, never>;
    subscribe: (channel: string, o?: {
        readonly lastEventId?: string | undefined;
    }) => Stream.Stream<TrackedEnvelope<unknown>, never, never>;
}
shape
as interface PublisherShape<TEvents extends PublisherEvents>PublisherShape<function (type parameter) TEvents in <TEvents extends PublisherEvents>(): Effect.Effect<PublisherShape<TEvents>, never, PgClient.PgClient>TEvents>;
}); const const layer: Layer.Layer<Posts, SqlError, never>layer = import LayerLayer.const effect: <Posts, PublisherShape<PostEvents>>(service: Key<Posts, PublisherShape<PostEvents>>) => <E, R>(effect: Effect.Effect<PublisherShape<PostEvents>, E, R>) => Layer.Layer<Posts, E, Exclude<R, Scope>> (+1 overload)effect(class PostsPosts)(const pgPublisher: <PostEvents>() => Effect.Effect<PublisherShape<PostEvents>, never, PgClient.PgClient>pgPublisher<
type PostEvents = {
    created: Post;
    deleted: {
        id: string;
    };
}
PostEvents
>()).Pipeable.pipe<Layer.Layer<Posts, never, PgClient.PgClient>, Layer.Layer<Posts, SqlError, never>>(this: Layer.Layer<Posts, never, PgClient.PgClient>, ab: (_: Layer.Layer<Posts, never, PgClient.PgClient>) => Layer.Layer<Posts, SqlError, never>): Layer.Layer<Posts, SqlError, never> (+21 overloads)pipe(
import LayerLayer.const provide: <never, SqlError, PgClient.PgClient | SqlClient>(that: Layer.Layer<PgClient.PgClient | SqlClient, SqlError, never>) => <RIn2, E2, ROut2>(self: Layer.Layer<ROut2, E2, RIn2>) => Layer.Layer<ROut2, SqlError | E2, Exclude<RIn2, PgClient.PgClient | SqlClient>> (+3 overloads)provide( import PgClientPgClient.const layer: (config: PgClient.PgPoolConfig) => Layer.Layer<PgClient.PgClient | SqlClient, SqlError>layer({ PgClientConfig.url?: Redacted.Redacted<string> | undefinedurl: import RedactedRedacted.
const make: <string>(value: string, options?: {
    readonly label?: string | undefined;
}) => Redacted.Redacted<string>
make
(var process: NodeJS.Processprocess.NodeJS.Process.env: NodeJS.ProcessEnvenv.string | undefinedDATABASE_URL!) }),
), );

Cloudflare Durable Objects

A Durable Object is one process that every request for its name reaches, so Posts.memory() is enough inside it. Route the endpoint to the object and serve the router there:

import { class DurableObject<Env = Cloudflare.Env, Props = {}>DurableObject } from 'cloudflare:workers';

export class class PostsRoomPostsRoom extends class DurableObject<Env = Cloudflare.Env, Props = {}>DurableObject {
  PostsRoom.handler: FetchHandler<undefined>handler = 
createFetchHandler<{
    onCreated: BuildProcedure<RootDef<{}, {}>, "subscription", {
        output: TrackedEnvelope<Post>;
        errors: never;
        services: Posts;
    }>;
}, undefined>(opts: FetchHandlerOptions<{
    onCreated: BuildProcedure<RootDef<{}, {}>, "subscription", {
        output: TrackedEnvelope<Post>;
        errors: never;
        services: Posts;
    }>;
}, undefined>): FetchHandler<undefined>
createFetchHandler
({
BaseHandlerOptions<{ onCreated: BuildProcedure<RootDef<{}, {}>, "subscription", { output: TrackedEnvelope<Post>; errors: never; services: Posts; }>; }>.router: {
    onCreated: BuildProcedure<RootDef<{}, {}>, "subscription", {
        output: TrackedEnvelope<Post>;
        errors: never;
        services: Posts;
    }>;
}
router
,
layer: Layer<Posts, any, never>layer: class PostsPosts.PublisherClass<Posts, "Posts", { created: Post; deleted: { id: string; }; }>.memory(opts?: MemoryPublisherOptions): Layer<Posts, never, never>memory({ MemoryPublisherOptions.buffer?: number | undefinedbuffer: 100 }), }); PostsRoom.fetch(request: Request): Promise<Response>fetch(request: Request<unknown, CfProperties<unknown>>request: interface Request<CfHostMetadata = unknown, Cf = CfProperties<CfHostMetadata>>Request) { return this.PostsRoom.handler: FetchHandler<undefined>handler.FetchHandler<undefined>.fetch: (request: Request) => Promise<Response>fetch(request: Request<unknown, CfProperties<unknown>>request); } } export default { ExportedHandler<Env, unknown, unknown, unknown>.fetch?: ExportedHandlerFetchHandler<Env, unknown, unknown> | undefinedfetch: (request: Request<unknown, CfProperties<unknown>>request: interface Request<CfHostMetadata = unknown, Cf = CfProperties<CfHostMetadata>>Request, env: Envenv: Env) => env: Envenv.Env.POSTS_ROOM: DurableObjectNamespace<PostsRoom>POSTS_ROOM.DurableObjectNamespace<PostsRoom>.getByName(name: string, options?: DurableObjectNamespaceGetDurableObjectOptions): DurableObjectStub<PostsRoom>getByName('posts').function fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>fetch(request: Request<unknown, CfProperties<unknown>>request), } satisfies interface ExportedHandler<Env = unknown, QueueHandlerMessage = unknown, CfHostMetadata = unknown, Props = unknown>ExportedHandler<Env>;

Open SSE streams keep the object in memory; kept events are lost when it's evicted. To let it sleep between events, serve WebSockets with the hibernation adapter and provide hibernatingPublisher(Posts, { state: this.ctx }) from @trpc12/trpc/cloudflare instead of Posts.memory() (subscriptions that hibernate).