diff --git a/bun.lock b/bun.lock index d387f03..abfbc9a 100644 --- a/bun.lock +++ b/bun.lock @@ -22,6 +22,8 @@ "dependencies": { "@effect/platform": "^0.90.8", "@effect/platform-bun": "^0.79.0", + "@effect/rpc": "^0.69.2", + "@website/common": "workspace:*", "effect": "^3.17.13", }, }, @@ -95,7 +97,7 @@ "@effect/language-service": ["@effect/language-service@0.40.0", "", { "bin": { "effect-language-service": "cli.js" } }, "sha512-VHikOhYXm+ECy+mLSszHgCfP9YyKQyB9GLFGBKY2nbnru9xwgCnar8pBLA0AkSUjAgn3hc/mdcFdb/XL9uywLQ=="], - "@effect/platform": ["@effect/platform@0.90.8", "", { "dependencies": { "find-my-way-ts": "^0.1.6", "msgpackr": "^1.11.4", "multipasta": "^0.2.7" }, "peerDependencies": { "effect": "^3.17.13" } }, "sha512-lQejPI2VbUAwPIU6oG12sAhqMar8+KkBjxgBVqQTERzHz4vnG9zaiDDytJM+IGWE40MKbCG3pc2wK6nw2Ugk8A=="], + "@effect/platform": ["@effect/platform@0.90.9", "", { "dependencies": { "find-my-way-ts": "^0.1.6", "msgpackr": "^1.11.4", "multipasta": "^0.2.7" }, "peerDependencies": { "effect": "^3.17.13" } }, "sha512-ZLyGMenOXGERsaJ7urioWwugkluWjhrcQgr0/vfgxqe/TCumL3CK93YZKSlJtohDrFSfWjaXCbXyhqz3iypFUg=="], "@effect/platform-browser": ["@effect/platform-browser@0.70.0", "", { "dependencies": { "multipasta": "^0.2.7" }, "peerDependencies": { "@effect/platform": "^0.90.0", "effect": "^3.17.0" } }, "sha512-dq2ukUralavQIipSsQVuIOLLxBlFWL5Mkg1Fnr8esYPuPv0BXAI39nwNNi75X2tPAgak8OCSD0qh9qhmNj2gPA=="], @@ -611,8 +613,6 @@ "@typescript-eslint/typescript-estree/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], - "@website/webapp/@effect/platform": ["@effect/platform@0.90.9", "", { "dependencies": { "find-my-way-ts": "^0.1.6", "msgpackr": "^1.11.4", "multipasta": "^0.2.7" }, "peerDependencies": { "effect": "^3.17.13" } }, "sha512-ZLyGMenOXGERsaJ7urioWwugkluWjhrcQgr0/vfgxqe/TCumL3CK93YZKSlJtohDrFSfWjaXCbXyhqz3iypFUg=="], - "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], diff --git a/packages/common/src/webrpc/WebRpcSerialization.ts b/packages/common/src/webrpc/WebRpcSerialization.ts new file mode 100644 index 0000000..38d18a1 --- /dev/null +++ b/packages/common/src/webrpc/WebRpcSerialization.ts @@ -0,0 +1,5 @@ +import { RpcSerialization } from "@effect/rpc" + + +export const WebRpcSerializationDevelopment = RpcSerialization.layerJson +export const WebRpcSerializationProduction = RpcSerialization.layerNdjson diff --git a/packages/common/src/webrpc/index.ts b/packages/common/src/webrpc/index.ts index b9d6bc2..3698a07 100644 --- a/packages/common/src/webrpc/index.ts +++ b/packages/common/src/webrpc/index.ts @@ -1,2 +1,3 @@ export * as WebRpc from "./WebRpc" +export * as WebRpcSerialization from "./WebRpcSerialization" export * as WebRpcTest from "./WebRpcTest" diff --git a/packages/server/package.json b/packages/server/package.json index 955c9a2..28886d3 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -6,6 +6,7 @@ "dependencies": { "@effect/platform": "^0.90.8", "@effect/platform-bun": "^0.79.0", + "@effect/rpc": "^0.69.2", "@website/common": "workspace:*", "effect": "^3.17.13" } diff --git a/packages/server/src/entrypoint.bun.ts b/packages/server/src/entrypoint.bun.ts index 6624e09..aac88ed 100644 --- a/packages/server/src/entrypoint.bun.ts +++ b/packages/server/src/entrypoint.bun.ts @@ -5,10 +5,13 @@ import { Server } from "./server" Layer.launch(Server).pipe( - Effect.provide(ServerConfig.httpPort.pipe( - Effect.map(port => BunHttpServer.layer({ port })), - Layer.unwrapEffect, + Effect.provide(Layer.empty.pipe( + Layer.provideMerge(ServerConfig.httpPort.pipe( + Effect.map(port => BunHttpServer.layer({ port })), + Layer.unwrapEffect, + )), + Layer.provideMerge(BunContext.layer), )), - Effect.provide(BunContext.layer), + BunRuntime.runMain, ) diff --git a/packages/server/src/http.ts b/packages/server/src/http.ts index 55153ce..2f69d8b 100644 --- a/packages/server/src/http.ts +++ b/packages/server/src/http.ts @@ -1,39 +1,66 @@ import { FileSystem, HttpMiddleware, HttpRouter, HttpServer, HttpServerRequest, HttpServerResponse, Path } from "@effect/platform" -import { Duration, Effect, flow } from "effect" +import { RpcServer } from "@effect/rpc" +import { CommonConfig } from "@website/common/config" +import { WebRpc, WebRpcSerialization } from "@website/common/webrpc" +import { Duration, Effect, flow, Layer } from "effect" +import { WebRpcLive } from "./webrpc" const router = HttpRouter.empty -export const HttpAppDevelopment = router.pipe( - HttpServer.serve(flow( - HttpMiddleware.logger, - HttpMiddleware.xForwardedHeaders, - )), - HttpServer.withLogAddress, +const makeWebRpcRoute = Effect.all([ + CommonConfig.webRpcHttpPath, + RpcServer.toHttpApp(WebRpc.WebRpc), +]).pipe( + Effect.map(([path, app]) => HttpRouter.mountApp(path, app)), + Effect.provide(WebRpcLive.WebRpcLive), ) - -const serveProductionWebapp = HttpMiddleware.make(() => Effect.gen(function*() { +const makeProductionWebappMiddleware = Effect.gen(function*() { const path = yield* Path.Path const fs = yield* FileSystem.FileSystem - const req = yield* HttpServerRequest.HttpServerRequest - const dist = path.join(yield* path.fromFileUrl(new URL(".", import.meta.resolve("@website/webapp"))), "dist") - const source = path.join(dist, req.url) - const isValid = yield* fs.stat(source).pipe( - Effect.andThen(stat => stat.type === "File"), - Effect.catchAll(() => Effect.succeed(false)), - ) - return yield* HttpServerResponse.setHeader( - yield* HttpServerResponse.file(isValid ? source : path.join(dist, "index.html")), - "Cache-Control", - `public, max-age=${Duration.toSeconds("365 days")}, immutable` - ) -})) + return () => Effect.gen(function*() { + const req = yield* HttpServerRequest.HttpServerRequest + const source = path.join(dist, req.url) + const isValid = yield* fs.stat(source).pipe( + Effect.andThen(stat => stat.type === "File"), + Effect.catchAll(() => Effect.succeed(false)), + ) -export const HttpAppProduction = router.pipe( - serveProductionWebapp, - HttpServer.serve(HttpMiddleware.xForwardedHeaders), - HttpServer.withLogAddress, + return yield* HttpServerResponse.setHeader( + yield* HttpServerResponse.file(isValid ? source : path.join(dist, "index.html")), + "Cache-Control", + `public, max-age=${Duration.toSeconds("365 days")}, immutable` + ) + }) +}) + + +export const HttpAppDevelopment = Effect.provide(makeWebRpcRoute, WebRpcSerialization.WebRpcSerializationDevelopment).pipe( + Effect.map(serveWebRpc => router.pipe( + serveWebRpc, + HttpServer.serve(flow( + HttpMiddleware.logger, + HttpMiddleware.xForwardedHeaders, + )), + HttpServer.withLogAddress, + )), + + Layer.unwrapScoped, +) + +export const HttpAppProduction = Effect.all([ + Effect.provide(makeWebRpcRoute, WebRpcSerialization.WebRpcSerializationProduction), + makeProductionWebappMiddleware, +]).pipe( + Effect.map(([serveWebRpc, serveProductionWebapp]) => router.pipe( + serveWebRpc, + serveProductionWebapp, + HttpServer.serve(HttpMiddleware.xForwardedHeaders), + HttpServer.withLogAddress, + )), + + Layer.unwrapScoped, )