1import * as Effect from "effect/Effect";
2import * as Layer from "effect/Layer";
3import * as CoreBinding from "../../Binding.ts";
4import type { RuntimeContext } from "../../RuntimeContext.ts";
5import type { Binding } from "./Binding.ts";
6import { isWorker, WorkerEnvironment } from "./Worker.ts";
7
8/**
9 * Native runtime layer for a Worker-only {@link Binding}. Registers the native
10 * binding on the host Worker at deploy time (via `binding.toWorkerBinding()`)
11 * and builds the Effect-native client from the lazy `env[name]` accessor.
12 *
13 * Factored out of `Binding.ts` (which must stay free of `Worker.ts` to avoid an
14 * import cycle through the contract files). Each binding's layer is a one-liner:
15 * `export const XBinding = makeBindingLayer(X, makeXClient)`.
16 */
17export const makeBindingLayer = <Self, Runtime, Client>(
18 tag: Self,
19 makeClient: (
20 raw: Effect.Effect<Runtime, never, RuntimeContext>,
21 binding: Binding<string, Client, Self>,
22 ) => Client,
23): Layer.Layer<Self, never, WorkerEnvironment> =>
24 Layer.effect(
25 tag as never,
26 Effect.gen(function* () {
27 const env = yield* WorkerEnvironment;
28 return Effect.fn(function* (binding: Binding<string, Client>) {
29 // Deploy-time only: register the native binding on the host Worker.
30 if (!globalThis.__ALCHEMY_RUNTIME__) {
31 const host = yield* CoreBinding.Host;
32 if (isWorker(host)) {
33 yield* host.bind(binding.name, {
34 bindings: [binding.toWorkerBinding()],
35 // Dev-only local-emulation opt-out, contributed as a parallel
36 // channel (like `hyperdrives`) so the wire binding stays pure.
37 ...(binding.devRemote
38 ? { devRemote: { [binding.name]: true } }
39 : {}),
40 });
41 }
42 }
43 // Lazy: `WorkerEnvironment` is only populated at exec phase.
44 const raw = Effect.sync(
45 () => (env as Record<string, Runtime>)[binding.name]!,
46 ) as Effect.Effect<Runtime, never, RuntimeContext>;
47 return makeClient(raw, binding as Binding<string, Client, Self>);
48 });
49 }),
50 ) as Layer.Layer<Self, never, WorkerEnvironment>;