1import * as Config from "effect/Config";
2import * as Effect from "effect/Effect";
3import * as Redacted from "effect/Redacted";
4import * as Binding from "../Binding.ts";
5import * as Output from "../Output.ts";
6import type { Resource, ResourceLike } from "../Resource.ts";
7import type { RuntimeContext } from "../RuntimeContext.ts";
8
9export const backendEnvKey = (id: string, field: string) =>
10 `NEON_BINDING_${Array.from(id, (char) => char.codePointAt(0)!.toString(16)).join("_")}_${field}`;
11
12type EnvValue = string | Redacted.Redacted<string>;
13type EnvHost = Resource<
14 string,
15 object | undefined,
16 object,
17 { env?: Record<string, Output.Output<EnvValue>> }
18>;
19type TextBinding = {
20 type: "plain_text" | "secret_text";
21 name: string;
22 text: string;
23};
24type WorkerHost = Resource<
25 "Cloudflare.Worker",
26 object | undefined,
27 object,
28 { bindings?: TextBinding[] }
29>;
30
31const isEnvHost = (host: ResourceLike | undefined): host is EnvHost =>
32 host?.Type === "Neon.Function" ||
33 host?.Type === "AWS.Lambda.Function" ||
34 host?.Type === "Prisma.Compute" ||
35 host?.Type === "Cloudflare.Container";
36const isWorkerHost = (host: ResourceLike | undefined): host is WorkerHost =>
37 host?.Type === "Cloudflare.Worker";
38
39export const bindBackendEnvironment = Effect.fn(function* (
40 id: string,
41 env: Record<string, Output.Output<EnvValue>>,
42) {
43 if (globalThis.__ALCHEMY_RUNTIME__) return;
44 const host = yield* Binding.Host;
45 if (isEnvHost(host)) {
46 yield* host.bind(id, { env });
47 } else if (isWorkerHost(host)) {
48 yield* host.bind(id, {
49 bindings: Object.entries(env).map(([name, value]) =>
50 value.pipe(
51 Output.map((value): TextBinding => ({
52 name,
53 type: Redacted.isRedacted(value) ? "secret_text" : "plain_text",
54 text: Redacted.isRedacted(value) ? Redacted.value(value) : value,
55 })),
56 ),
57 ),
58 });
59 } else {
60 return yield* Effect.die(
61 new Error(
62 `Neon connection binding does not support host ${host?.Type ?? "none"}`,
63 ),
64 );
65 }
66});
67
68export const backendString = (
69 key: string,
70): Effect.Effect<string, never, RuntimeContext> =>
71 Config.String(key).pipe(Effect.orDie);
72export const backendSecret = (
73 key: string,
74): Effect.Effect<Redacted.Redacted<string>, never, RuntimeContext> =>
75 Config.Redacted(key).pipe(Effect.orDie);