| 1 | import * as Binding from "@/Binding.ts"; |
| 2 | import type { Bucket } from "@/Cloudflare/R2/Bucket.ts"; |
| 3 | import type { Resource, ResourceLike } from "@/Resource.ts"; |
| 4 | import type { RuntimeContext } from "@/RuntimeContext.ts"; |
| 5 | import * as Effect from "effect/Effect"; |
| 6 | import * as Layer from "effect/Layer"; |
| 7 | |
| 8 | /** |
| 9 | * The env var the binding injects into whichever host it is provided on. |
| 10 | */ |
| 11 | export 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 | */ |
| 25 | export 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 | |
| 33 | export const ProbeEnv = Binding.Service<ProbeEnv>("Test.ProbeEnv"); |
| 34 | |
| 35 | type EnvBindingHost = Resource< |
| 36 | string, |
| 37 | object | undefined, |
| 38 | object, |
| 39 | { env?: Record<string, any> } |
| 40 | >; |
| 41 | |
| 42 | const acceptsEnvBinding = ( |
| 43 | host: ResourceLike | undefined, |
| 44 | ): host is EnvBindingHost => host?.Type === "Cloudflare.Container"; |
| 45 | |
| 46 | export 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 | ); |