Created
August 8, 2024 10:15
-
-
Save madyanalj/90ae08cd4a9570fa6524b8df7ee76d4c to your computer and use it in GitHub Desktop.
Effect RPC with Cloudflare Workers
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Effect as E, Schema } from "@effect/schema" | |
import { | |
BooleanFromSelfOrString, | |
NonEmptyObject, | |
ObjectValue, | |
} from "@o/shared" | |
import { ConfigProvider, Context, Layer } from "effect" | |
const WorkerBinding = <T extends object>() => | |
NonEmptyObject.pipe( | |
Schema.filter((input): input is T => true, { title: "WorkerBinding" }), | |
) | |
const make = (env: unknown) => { | |
const get = <A, I>(key: string, schema: Schema.Schema<A, I>) => | |
ObjectValue(key, schema) | |
.annotations({ title: "WorkerConfig" }) | |
.pipe(Schema.decodeUnknownSync)(env) | |
return { | |
get: () => {}, | |
serviceBinding: <T extends object>(key: string) => | |
get(key, WorkerBinding<T>()), | |
boolean: (key: string) => get(key, BooleanFromSelfOrString), | |
} | |
} | |
export class WorkerConfig extends Context.Tag("WorkerConfig")< | |
WorkerConfig, | |
ReturnType<typeof make> | |
>() { | |
static Live = (env: unknown) => { | |
const configProvider = ConfigProvider.fromJson(env) | |
const configLayer = Layer.setConfigProvider(configProvider) | |
return Layer.effect(this, E.succeed(make(env))).pipe( | |
Layer.provide(configLayer), | |
) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Statement } from "@effect/sql" | |
import { D1Client } from "@effect/sql-d1" | |
import * as SqliteDrizzle from "@effect/sql-drizzle/Sqlite" | |
import { Config, Effect as E, Layer } from "effect" | |
import { WorkerConfig } from "./config" | |
const D1Live = Layer.unwrapEffect( | |
E.gen(function* () { | |
const config = yield* WorkerConfig | |
const db = config.serviceBinding<D1Database>("DB") | |
const enableDbResultDebug = config.boolean("ENABLE_DB_RESULT_DEBUG") | |
const SqlLogger = Statement.setTransformer((statement) => | |
E.gen(function* () { | |
const [query, params] = statement.compile() | |
if (enableDbResultDebug) | |
yield* E.void.pipe( | |
E.withSpan("db.params", { attributes: { query, params } }), | |
) | |
return statement | |
}), | |
) | |
return D1Client.layer({ | |
db: Config.succeed(db), | |
}).pipe(Layer.provide(SqlLogger)) | |
}), | |
) | |
export const DatabaseLive = SqliteDrizzle.layer.pipe(Layer.provide(D1Live)) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { DevTools } from "@effect/experimental" | |
import { Socket } from "@effect/platform" | |
import { Boolean, Context, Effect as E, Layer } from "effect" | |
import { WorkerConfig } from "./config" | |
const make = (ctx: ExecutionContext) => | |
Layer.unwrapEffect( | |
E.gen(function* () { | |
const config = yield* WorkerConfig | |
const enable = config.boolean("ENABLE_EFFECT_DEV_TOOLS") | |
return Boolean.match(enable, { | |
onTrue: () => { | |
ctx.waitUntil(E.sleep(100).pipe(E.runPromise)) | |
return DevTools.layerWebSocket().pipe( | |
Layer.provide(Socket.layerWebSocketConstructorGlobal), | |
) | |
}, | |
onFalse: () => Layer.empty, | |
}) | |
}), | |
) | |
export class EffectDevTools extends Context.Tag("EffectDevTools")< | |
EffectDevTools, | |
ReturnType<typeof make> | |
>() { | |
static Live = (ctx: ExecutionContext) => | |
Layer.effect(this, E.succeed(make(ctx))) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { HttpApp } from "@effect/platform" | |
import { Effect as E, Layer, Logger, LogLevel } from "effect" | |
import { WorkerConfig } from "./helpers/config" | |
import { DatabaseLive } from "./helpers/db" | |
import { EffectDevTools } from "./helpers/effect-dev-tools" | |
import { httpRouter } from "./rpc/router" | |
const worker: ExportedHandler<Record<string, unknown>> = { | |
fetch: async (request, env, ctx) => { | |
const MainLayer = DatabaseLive.pipe( | |
Layer.tapErrorCause(E.logError), | |
Layer.provide(Logger.minimumLogLevel(LogLevel.All)), | |
Layer.provide(EffectDevTools.Live(ctx)), | |
Layer.provide(WorkerConfig.Live(env)), | |
) | |
return HttpApp.toWebHandlerLayer(httpRouter, MainLayer).handler(request) | |
}, | |
} | |
export default worker |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
HttpMiddleware, | |
HttpRouter, | |
HttpServerResponse, | |
} from "@effect/platform" | |
import { Router } from "@effect/rpc" | |
import { toHttpApp } from "@effect/rpc-http/HttpRouter" | |
import { Effect as E, flow } from "effect" | |
export { Schema as S } from "@effect/schema" | |
class FooRequest extends S.TaggedRequest<FooRequest>()("FooRequest", { | |
payload: { id: S.String }, | |
success: S.String, | |
failure: S.Never, | |
}) {} | |
export const MY_RPCS = [ | |
Rpc.effect(FooRequest, ({ id }) => | |
E.gen(function* () { | |
return "Hello world " + id | |
}), | |
), | |
] as const | |
export const httpRouter = HttpRouter.empty.pipe( | |
HttpRouter.post( | |
"/rpc", | |
Router.make(MY_RPCS).pipe(toHttpApp), | |
), | |
HttpRouter.options("*", HttpServerResponse.empty()), | |
HttpRouter.use( | |
flow( | |
// TODO: enable cors | |
HttpMiddleware.cors(), | |
HttpMiddleware.logger, | |
), | |
), | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment