1import * as Cloudflare from "@/Cloudflare";
2import * as Output from "@/Output";
3import * as Config from "effect/Config";
4import * as Effect from "effect/Effect";
5import * as Redacted from "effect/Redacted";
6import { HttpServerRequest } from "effect/http/HttpServerRequest";
7import * as HttpServerResponse from "effect/http/HttpServerResponse";
8
9/**
10 * Effect-native Worker fixture that exercises every supported
11 * `WorkerBindingResource` shape and echoes the resolved values back as
12 * JSON so the test can assert the deploy → runtime round-trip.
13 *
14 * Mixes the two declaration styles:
15 * - `env: { ... }` literal/Redacted/Config values declared on the
16 * resource (resolved via `WorkerEnvironment` at runtime).
17 * - `yield* Config.xxx(...)` resolved during Init — Alchemy captures
18 * the binding automatically and the same `Config` re-resolves from
19 * the binding at runtime.
20 */
21export default class EnvEffectWorker extends Cloudflare.Worker<EnvEffectWorker>()(
22 "EnvEffectWorker",
23 {
24 main: import.meta.url,
25 env: {
26 STR: "hello",
27 NUM: 42,
28 BOOL: true,
29 NULL: null,
30 OBJ: { nested: { value: "ok" }, count: 7 },
31 ARR: [1, 2, 3],
32 OUTPUT_STR: Output.literal("output-str"),
33 SECRET_STR: Redacted.make("shh"),
34 SECRET_JSON: Redacted.make({ token: "abc", scopes: ["read", "write"] }),
35 // Config declared statically on `env` — Alchemy resolves at deploy
36 // time and binds it as `secret_text` on the Worker.
37 CONFIG_REDACTED: Config.Redacted("CONFIG_REDACTED"),
38 },
39 },
40 Effect.gen(function* () {
41 // Captured during Init — Alchemy binds these onto the Worker and the
42 // runtime ConfigProvider (backed by `env`) re-resolves them here.
43 const configStr = yield* Config.String("CONFIG_STR");
44 const configNum = yield* Config.Number("CONFIG_NUM");
45 const configRedactedInit = yield* Config.Redacted("CONFIG_REDACTED_INIT");
46 // Composite forms — each member is bound individually, so the whole
47 // shape must survive the deploy → runtime round-trip.
48 const configAllObj = yield* Config.all({
49 str: Config.String("CONFIG_STR"),
50 num: Config.Number("CONFIG_NUM"),
51 redacted: Config.Redacted("CONFIG_REDACTED_INIT"),
52 });
53 const configAllTuple = yield* Config.all([
54 Config.String("CONFIG_STR"),
55 Config.Number("CONFIG_NUM"),
56 ]);
57 // Nested prefix — bound under the flattened `CONFIG_NESTED_HOST` key.
58 const configNested = yield* Config.String("HOST").pipe(
59 Config.nested("CONFIG_NESTED"),
60 );
61
62 // Yieldable binding form — attaches the `version_metadata` binding to
63 // this Worker and returns a deferred accessor resolved at runtime.
64 const versionMetadata = yield* Cloudflare.Workers.VersionMetadata();
65
66 return {
67 fetch: Effect.gen(function* () {
68 const request = yield* HttpServerRequest;
69 const env = yield* Cloudflare.Workers.WorkerEnvironment;
70 const pathname = new URL(request.originalUrl).pathname;
71
72 if (pathname === "/env") {
73 return yield* HttpServerResponse.json({
74 STR: env.STR,
75 NUM: env.NUM,
76 BOOL: env.BOOL,
77 NULL: env.NULL,
78 OBJ: env.OBJ,
79 ARR: env.ARR,
80 OUTPUT_STR: env.OUTPUT_STR,
81 SECRET_STR: env.SECRET_STR,
82 SECRET_JSON:
83 typeof env.SECRET_JSON === "string"
84 ? JSON.parse(env.SECRET_JSON)
85 : env.SECRET_JSON,
86 });
87 }
88
89 if (pathname === "/version") {
90 const { id, tag, timestamp } = yield* versionMetadata;
91 return yield* HttpServerResponse.json({ id, tag, timestamp });
92 }
93
94 if (pathname === "/config") {
95 return yield* HttpServerResponse.json({
96 CONFIG_STR: configStr,
97 CONFIG_NUM: configNum,
98 CONFIG_REDACTED: env.CONFIG_REDACTED,
99 CONFIG_REDACTED_INIT: Redacted.value(configRedactedInit),
100 CONFIG_REDACTED_INIT_IS_REDACTED:
101 Redacted.isRedacted(configRedactedInit),
102 CONFIG_ALL_OBJ: {
103 str: configAllObj.str,
104 num: configAllObj.num,
105 redacted: Redacted.value(configAllObj.redacted),
106 redactedIsRedacted: Redacted.isRedacted(configAllObj.redacted),
107 },
108 CONFIG_ALL_TUPLE: configAllTuple,
109 CONFIG_NESTED_HOST: configNested,
110 });
111 }
112
113 if (pathname === "/config-runtime") {
114 // Re-resolve the same Configs captured during Init — this time at
115 // request time, deep inside a nested effect, where the env-backed
116 // runtime ConfigProvider (not the Init interceptor) answers the
117 // read. The bound values arrive in `env` as
118 // `{"_tag":"Redacted","value":...}` markers and must be reified
119 // transparently: `Config.Number` must decode the raw source value,
120 // not the marker JSON.
121 const nested = yield* Effect.gen(function* () {
122 const allObj = yield* Config.all({
123 str: Config.String("CONFIG_STR"),
124 num: Config.Number("CONFIG_NUM"),
125 redacted: Config.Redacted("CONFIG_REDACTED_INIT"),
126 });
127 return {
128 CONFIG_STR: yield* Config.String("CONFIG_STR"),
129 CONFIG_NUM: yield* Config.Number("CONFIG_NUM"),
130 // Combinators re-apply at runtime against the bound source.
131 CONFIG_NUM_WITH_DEFAULT: yield* Config.Number("CONFIG_NUM").pipe(
132 Config.withDefault(999),
133 ),
134 // Never read during Init, so never bound — the default applies.
135 CONFIG_UNSET_WITH_DEFAULT: yield* Config.Number(
136 "CONFIG_UNSET",
137 ).pipe(Config.withDefault(3000)),
138 CONFIG_ALL_OBJ: {
139 str: allObj.str,
140 num: allObj.num,
141 redacted: Redacted.value(allObj.redacted),
142 redactedIsRedacted: Redacted.isRedacted(allObj.redacted),
143 },
144 CONFIG_ALL_TUPLE: yield* Config.all([
145 Config.String("CONFIG_STR"),
146 Config.Number("CONFIG_NUM"),
147 ]),
148 CONFIG_NESTED_HOST: yield* Config.String("HOST").pipe(
149 Config.nested("CONFIG_NESTED"),
150 ),
151 };
152 });
153 const redactedAtRuntime = yield* Config.Redacted(
154 "CONFIG_REDACTED_INIT",
155 );
156 return yield* HttpServerResponse.json({
157 ...nested,
158 CONFIG_REDACTED_INIT: Redacted.value(redactedAtRuntime),
159 CONFIG_REDACTED_INIT_IS_REDACTED:
160 Redacted.isRedacted(redactedAtRuntime),
161 });
162 }
163
164 return HttpServerResponse.text("ok");
165 // The request-time Config reads carry ConfigError; a failed read is
166 // a test bug, so die (surfaces as a 500 the test assertions catch).
167 }).pipe(Effect.orDie),
168 };
169 }).pipe(Effect.provide(Cloudflare.Workers.VersionMetadataBinding)),
170) {}