From 3ebb7ee796193cf5b09c6186a31d3efd2dfa58a5 Mon Sep 17 00:00:00 2001 From: Max Koon <22125083+k2on@users.noreply.github.com> Date: Fri, 28 Nov 2025 15:04:42 -0500 Subject: [PATCH] feat: add @effect/rpc --- apps/api/package.json | 1 + apps/api/src/index.ts | 14 +++- apps/api/src/middleware/cors.ts | 1 + apps/api/src/rpc/handler.ts | 13 ++++ apps/expo/app/rpc.tsx | 30 +++++++++ apps/expo/package.json | 1 + packages/shared/package.json | 1 + packages/shared/src/rpc.ts | 13 ++++ pnpm-lock.yaml | 112 +++++++++++++++++++++++--------- 9 files changed, 154 insertions(+), 32 deletions(-) create mode 100644 apps/api/src/rpc/handler.ts create mode 100644 apps/expo/app/rpc.tsx create mode 100644 packages/shared/src/rpc.ts diff --git a/apps/api/package.json b/apps/api/package.json index 01709f6..ada9c1c 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -9,6 +9,7 @@ "dependencies": { "@effect/platform": "^0.93.2", "@effect/platform-node": "^0.101.1", + "@effect/rpc": "^0.72.2", "@money/shared": "workspace:*", "better-auth": "^1.3.27", "effect": "^3.19.4", diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index 5c09c22..fe6165a 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -5,11 +5,12 @@ import * as HttpServerResponse from "@effect/platform/HttpServerResponse"; import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer"; import { createServer } from "http"; -import { CorsMiddleware } from "./middleware/cors"; import { AuthRoute } from "./auth/handler"; import { BetterAuthLive } from "./auth/better-auth"; import { WebhookReceiverRoute } from "./webhook"; import { ZeroMutateRoute, ZeroQueryRoute } from "./zero/handler"; +import { RpcRoute } from "./rpc/handler"; +import { BASE_URL } from "@money/shared"; const RootRoute = HttpLayerRouter.add( "GET", @@ -24,13 +25,22 @@ const AllRoutes = Layer.mergeAll( AuthRoute, ZeroQueryRoute, ZeroMutateRoute, + RpcRoute, WebhookReceiverRoute, +).pipe( + Layer.provide( + HttpLayerRouter.cors({ + allowedOrigins: ["https://money.koon.us", `${BASE_URL}:8081`], + allowedMethods: ["POST", "GET", "OPTIONS"], + // allowedHeaders: ["Content-Type", "Authorization", ""], + credentials: true, + }), + ), ); HttpLayerRouter.serve(AllRoutes).pipe( Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })), Layer.provide(BetterAuthLive), - Layer.provide(CorsMiddleware.layer), Layer.launch, NodeRuntime.runMain, ); diff --git a/apps/api/src/middleware/cors.ts b/apps/api/src/middleware/cors.ts index 713c69c..bcec7b9 100644 --- a/apps/api/src/middleware/cors.ts +++ b/apps/api/src/middleware/cors.ts @@ -8,4 +8,5 @@ export const CorsMiddleware = HttpLayerRouter.middleware( allowedHeaders: ["Content-Type", "Authorization"], credentials: true, }), + { global: true }, ); diff --git a/apps/api/src/rpc/handler.ts b/apps/api/src/rpc/handler.ts new file mode 100644 index 0000000..26e52b1 --- /dev/null +++ b/apps/api/src/rpc/handler.ts @@ -0,0 +1,13 @@ +import { RpcSerialization, RpcServer } from "@effect/rpc"; +import { Effect, Layer, Schema } from "effect"; +import { LinkRpcs, Link } from "@money/shared/rpc"; + +const LinkHandlers = LinkRpcs.toLayer({ + CreateLink: () => Effect.succeed(new Link({ href: "hi" })), +}); + +export const RpcRoute = RpcServer.layerHttpRouter({ + group: LinkRpcs, + path: "/rpc", + protocol: "http", +}).pipe(Layer.provide(LinkHandlers), Layer.provide(RpcSerialization.layerJson)); diff --git a/apps/expo/app/rpc.tsx b/apps/expo/app/rpc.tsx new file mode 100644 index 0000000..65ab3ce --- /dev/null +++ b/apps/expo/app/rpc.tsx @@ -0,0 +1,30 @@ +import { Button, Text, View } from "react-native"; +import { AtomRpc, useAtomSet } from "@effect-atom/atom-react"; +import { Layer } from "effect"; +import { LinkRpcs } from "@money/shared/rpc"; +import { FetchHttpClient } from "@effect/platform"; +import { RpcClient, RpcSerialization } from "@effect/rpc"; + +class Client extends AtomRpc.Tag()("RpcClient", { + group: LinkRpcs, + protocol: RpcClient.layerProtocolHttp({ + url: "http://laptop:3000/rpc", + }).pipe(Layer.provide([RpcSerialization.layerJson, FetchHttpClient.layer])), +}) {} + +export default function Page() { + const create = useAtomSet(Client.mutation("CreateLink")); + + const onPress = () => { + create({ + payload: void 0, + }); + }; + + return ( + + RPC Test +