1import * as Binding from "@/Binding.ts";
2import type { Bucket } from "@/Cloudflare/R2/Bucket.ts";
3import type { Resource, ResourceLike } from "@/Resource.ts";
4import type { RuntimeContext } from "@/RuntimeContext.ts";
5import * as Effect from "effect/Effect";
6import * as Layer from "effect/Layer";
7
8/**
9 * The env var the binding injects into whichever host it is provided on.
10 */
11export const PROBE_ENV_KEY = "ALCHEMY_TEST_BOUND_ENV";
12
13/**
14 * A minimal `Binding.Service` whose entire deploy-time contribution is an
15 * environment variable — the shape `Prisma.Connect` uses for a Prisma
16 * Compute app, a Lambda Function, and (as of this fixture's coverage) a
17 * Cloudflare Container.
18 *
19 * A container is a real process, so it has no workerd bindings: `env` is the
20 * only channel a capability has to reach it. This proves the container
21 * provider actually folds the binding contract's `env` into the deployed
22 * application's environment variables — it used to declare the contract and
23 * silently drop the values.
24 */
25export interface ProbeEnv extends Binding.Service<
26 ProbeEnv,
27 "Test.ProbeEnv",
28 (
29 bucket: Bucket,
30 ) => Effect.Effect<Effect.Effect<string | undefined, never, RuntimeContext>>
31> {}
32
33export const ProbeEnv = Binding.Service<ProbeEnv>("Test.ProbeEnv");
34
35type EnvBindingHost = Resource<
36 string,
37 object | undefined,
38 object,
39 { env?: Record<string, any> }
40>;
41
42const acceptsEnvBinding = (
43 host: ResourceLike | undefined,
44): host is EnvBindingHost => host?.Type === "Cloudflare.Container";
45
46export const ProbeEnvBinding = Layer.effect(
47 ProbeEnv,
48 Effect.succeed(
49 Effect.fn(function* (bucket: Bucket) {
50 if (!globalThis.__ALCHEMY_RUNTIME__) {
51 const host = yield* Binding.Host;
52 if (!acceptsEnvBinding(host)) {
53 return yield* Effect.die(
54 new Error(
55 `Test.ProbeEnv expects a Cloudflare.Container host, got '${host?.Type ?? "no host"}'`,
56 ),
57 );
58 }
59 // The value is an Output of a sibling resource's attribute, so this
60 // also pins that bound env survives Output resolution.
61 yield* host.bind`${bucket}`({
62 env: { [PROBE_ENV_KEY]: bucket.bucketName },
63 });
64 }
65 return Effect.sync(() => process.env[PROBE_ENV_KEY]);
66 }),
67 ),
68);