1/**
2 * Runtime helpers consumed by generated bundle entrypoints (Cloudflare
3 * Workers, Cloudflare Containers, AWS Lambda, …).
4 *
5 * Anything exported here runs *inside* the deployed function — keep the
6 * surface tiny and dependency-light.
7 */
8
9import * as ConfigProvider from "effect/ConfigProvider";
10import * as Effect from "effect/Effect";
11import * as Layer from "effect/Layer";
12import { isRedactedMarker, sanitizeKey } from "./RuntimeContext.ts";
13import { asEffect } from "./Util/types.ts";
14
15/**
16 * Resolve the user's default-export entrypoint into a `Layer` for the
17 * bundled runtime.
18 *
19 * `entrypoint` may be any of:
20 * - a `Layer` factory (`{ build: (...) => ... }`) — used as-is
21 * - an Alchemy `Platform`/`Worker` construct (now a real `Effect`)
22 * - a plain `Effect`
23 *
24 * Centralized so the inline ternary doesn't have to be re-emitted into
25 * every bundle template (and accidentally rewritten to `x : x` by a bulk
26 * replace, which silently swaps the class in for the Effect and bricks
27 * every deployed worker/lambda).
28 */
29export const makeEntrypointLayer = (
30 tag: any,
31 entrypoint: any,
32): Layer.Layer<any> => {
33 if (typeof entrypoint?.build === "function") {
34 return entrypoint;
35 }
36 return Layer.effect(tag, asEffect(entrypoint));
37};
38
39/**
40 * Unwrap the `{"_tag":"Redacted","value":...}` marker that the deploy-time
41 * `Config` interceptor (see `Platform.ts`) and `RuntimeContext.set` use to
42 * preserve `Redacted`-ness across the env-var boundary. Returns the inner
43 * source value as a string, or `undefined` when `raw` is not a marker.
44 */
45const parseRedactedMarker = (raw: string): string | undefined => {
46 if (!raw.startsWith("{")) {
47 return undefined;
48 }
49 try {
50 const parsed: unknown = JSON.parse(raw);
51 if (isRedactedMarker(parsed)) {
52 return typeof parsed.value === "string"
53 ? parsed.value
54 : JSON.stringify(parsed.value);
55 }
56 } catch {
57 // not JSON — plain env value, fall through
58 }
59 return undefined;
60};
61
62/**
63 * Reify an env-var string the way `RuntimeContext.get` does: unwrap the
64 * `Redacted` marker, unquote a JSON-stringified string, and pass anything
65 * else through verbatim.
66 */
67const reifyEnvString = (raw: string): string => {
68 const marker = parseRedactedMarker(raw);
69 if (marker !== undefined) {
70 return marker;
71 }
72 if (raw.startsWith('"')) {
73 try {
74 const parsed: unknown = JSON.parse(raw);
75 if (typeof parsed === "string") {
76 return parsed;
77 }
78 } catch {
79 // not JSON — plain env value, fall through
80 }
81 }
82 return raw;
83};
84
85/**
86 * Wrap a runtime's env-backed `ConfigProvider` so values that were
87 * auto-bound by the deploy-time `Config` interceptor decode transparently.
88 *
89 * The engine can't know which config values are sensitive, so the
90 * interceptor binds every `Config` read during Init onto the deploy target
91 * as a secret, serialized as a `{"_tag":"Redacted","value":<source>}`
92 * marker. The interceptor's runtime branch reifies those markers for reads
93 * during Init, but effects that run later (request handlers, nested
94 * layers) resolve `Config` against the raw env-backed provider — without
95 * this wrapper, `Config.Number("PORT")` inside a handler sees the marker
96 * JSON instead of the source value and fails with a schema error.
97 *
98 * Two behaviors:
99 * - Leaf values that carry the marker are unwrapped to the raw source
100 * string before `Config` schemas decode them; everything else passes
101 * through untouched.
102 * - On a miss, falls back to the flat `sanitizeKey`-canonicalized key
103 * (`my.key` → `my_key`) that the interceptor bound the value under, so
104 * config names with non-alphanumeric characters resolve at runtime too.
105 */
106export const reifyBoundConfigProvider = (
107 base: ConfigProvider.ConfigProvider,
108 env: Record<string, unknown>,
109): ConfigProvider.ConfigProvider =>
110 ConfigProvider.make((path) =>
111 base.load(path).pipe(
112 Effect.map((node) => {
113 if (node?._tag === "Value") {
114 const value = parseRedactedMarker(node.value);
115 return value === undefined ? node : ConfigProvider.makeValue(value);
116 }
117 if (node === undefined) {
118 const raw = env[sanitizeKey(path.map((p) => p.toString()).join("_"))];
119 if (typeof raw === "string") {
120 return ConfigProvider.makeValue(reifyEnvString(raw));
121 }
122 }
123 return node;
124 }),
125 ),
126 );