/// import { DurableObject } from 'cloudflare:workers' import { layerProtocolDurableObject, toDurableObjectHandler } from '@livestore/common-cf' import { Effect, Layer, Rpc, RpcClient, RpcGroup, RpcSerialization, Schema } from '@livestore/utils/effect' export class TestRpcs extends RpcGroup.make( Rpc.make('Ping', { payload: Schema.Struct({ message: Schema.String }), success: Schema.Struct({ response: Schema.String }), }), Rpc.make('Echo', { payload: Schema.Struct({ text: Schema.String }), success: Schema.Struct({ echo: Schema.String }), }), Rpc.make('Add', { payload: Schema.Struct({ a: Schema.Number, b: Schema.Number }), success: Schema.Struct({ result: Schema.Number }), }), ) {} export interface Env { TEST_RPC_DO: DurableObjectNamespace TEST_RPC_CLIENT_DO: DurableObjectNamespace } export class TestRpcDurableObject extends DurableObject { __DURABLE_OBJECT_BRAND = 'TestRpcDurableObject' as never async rpc(payload: unknown): Promise { const TestRpcsLive = TestRpcs.toLayer({ Ping: ({ message }) => Effect.succeed({ response: `Pong: ${message}` }), Echo: ({ text }) => Effect.succeed({ echo: `Echo: ${text}` }), Add: ({ a, b }) => Effect.succeed({ result: a + b }), }) return toDurableObjectHandler(TestRpcs, { layer: Layer.mergeAll(TestRpcsLive, RpcSerialization.layerJson), })(payload) } } export class TestRpcClientDO extends DurableObject { __DURABLE_OBJECT_BRAND = 'TestRpcClientDO' as never readonly env: Env constructor(state: DurableObjectState, env: Env) { super(state, env) this.env = env } async fetch(request: Request): Promise { try { const url = new URL(request.url) if (url.pathname === '/call-server') { const method = url.searchParams.get('method') const serverDO = this.env.TEST_RPC_DO.get(this.env.TEST_RPC_DO.idFromName('test-server')) // Create protocol layer for DO RPC communication const ProtocolLive = layerProtocolDurableObject((payload) => serverDO.rpc(payload)).pipe( Layer.provide(RpcSerialization.layerJson), ) // Use idiomatic Effect RPC client pattern from README const program = Effect.gen(function* () { const client = yield* RpcClient.make(TestRpcs) // Call RPC methods using clean API switch (method) { case 'ping': { const message = url.searchParams.get('message') || 'Hello' return yield* client.Ping({ message }) } case 'echo': { const text = url.searchParams.get('text') || 'Hello World' return yield* client.Echo({ text }) } case 'add': { const a = Number.parseInt(url.searchParams.get('a') || '5') const b = Number.parseInt(url.searchParams.get('b') || '3') return yield* client.Add({ a, b }) } default: return yield* Effect.fail(new Error(`Unknown method: ${method}`)) } }).pipe(Effect.scoped) const result = await program.pipe(Effect.provide(ProtocolLive), Effect.runPromise) return new Response(JSON.stringify({ success: true, result }), { headers: { 'Content-Type': 'application/json' }, }) } return new Response('Not found', { status: 404 }) } catch (error) { return new Response(JSON.stringify({ success: false, error: String(error) }), { status: 500, headers: { 'Content-Type': 'application/json' }, }) } } } export default { async fetch(request: Request, env: Env): Promise { try { const url = new URL(request.url) // Idiomatic Effect RPC client testing if (url.pathname === '/test-rpc-client') { const clientDO = env.TEST_RPC_CLIENT_DO.get(env.TEST_RPC_CLIENT_DO.idFromName('test-client')) const method = url.searchParams.get('method') ?? 'ping' const clientUrl = new URL('/call-server', request.url) clientUrl.searchParams.set('method', method) // Forward parameters for (const [key, value] of url.searchParams) { if (key !== 'method') { clientUrl.searchParams.set(key, value) } } return clientDO.fetch(clientUrl.toString()) } return new Response('Idiomatic Effect RPC Test\n\nEndpoints:\n- /test-rpc-client?method=ping|echo|add', { headers: { 'Content-Type': 'text/plain' }, }) } catch (error) { return new Response(`Error: ${error}`, { status: 500 }) } }, }