1import * as Cloudflare from "@/Cloudflare";
2import * as Effect from "effect/Effect";
3import * as Layer from "effect/Layer";
4import * as Stream from "effect/Stream";
5import * as RpcSerialization from "effect/rpc/RpcSerialization";
6import * as RpcServer from "effect/rpc/RpcServer";
7import ChatBackendRpc from "./ChatBackendRpc.ts";
8import { ChatRpcs } from "./ChatRpcs.ts";
9
10// Same role as `ChatPersistenceWorker` (host the chat DO, route by
11// thread id) but the whole `fetch` surface is a typed Effect
12// `RpcGroup`. Each procedure proxies straight through the typed
13// `getByName(id)` client returned by `RpcDurableObject`.
14export default class ChatPersistenceRpcWorker extends Cloudflare.RpcWorker<ChatPersistenceRpcWorker>()(
15 "ChatPersistenceRpcWorker",
16 {
17 main: import.meta.url,
18 schema: ChatRpcs,
19 },
20 Effect.gen(function* () {
21 // Yielding the inline DO hosts it on this Worker and hands back the
22 // typed namespace.
23 const chats = yield* ChatBackendRpc;
24
25 const handlers = ChatRpcs.toLayer({
26 send: ({ id, prompt }) =>
27 Effect.flatMap(chats.getByName(id), (client) =>
28 client.send({ prompt }),
29 ).pipe(Effect.orDie),
30 streamMessage: ({ id, prompt }) =>
31 chats.getByName(id).pipe(
32 Effect.map((client) => client.streamMessage({ prompt })),
33 Stream.unwrap,
34 Stream.orDie,
35 ),
36 });
37
38 return RpcServer.toHttpEffect(ChatRpcs).pipe(
39 Effect.provide(Layer.mergeAll(handlers, RpcSerialization.layerNdjson)),
40 );
41 }),
42) {}