1import * as Cloudflare from "alchemy/Cloudflare";
2import * as Config from "effect/Config";
3import * as Effect from "effect/Effect";
4import * as Redacted from "effect/Redacted";
5import { KV } from "./KV.ts";
6
7/**
8 * Hard-coded value the integ test asserts on to prove the secret was
9 * bound at plantime and read at runtime through the `Redacted` accessor.
10 */
11export const WORKFLOW_SECRET_VALUE = "wf-secret-abc123";
12
13export default class NotifyWorkflow extends Cloudflare.Workflow<NotifyWorkflow>()(
14 "Notifier",
15 Effect.gen(function* () {
16 // Bind a `secret_text` on the workflow at plantime. Using a literal
17 // (instead of `Config.Redacted("WORKFLOW_SECRET")`) keeps the integ
18 // test self-contained — no `.env` setup required.
19 const secret = yield* Config.Redacted("WORKFLOW_SECRET").pipe(
20 Config.withDefault(Redacted.make(WORKFLOW_SECRET_VALUE)),
21 );
22 // Regression guard for https://github.com/alchemy-run/alchemy/pull/71
23 //
24 // The kv binding internally yields `Cloudflare.Workers.WorkerEnvironment` —
25 // before that PR, accessing `WorkerEnvironment` inside a workflow body
26 // crashed because `provideService(WorkerEnvironment, env)` was applied
27 // to the outer `Effect.succeed(body)` wrapper (a no-op) instead of
28 // `body` itself in `Workflow.ts`. Exercising `kv.put` / `kv.get` from
29 // inside a `task` keeps the integ test catching any future regression.
30 const kv = yield* Cloudflare.KV.ReadWriteNamespace(KV);
31
32 return Effect.fn(function* (input: { roomId: string; message: string }) {
33 const { roomId, message } = input;
34
35 const stored = yield* Cloudflare.Workflows.task(
36 "kv-roundtrip",
37 Effect.gen(function* () {
38 const key = `workflow:smoke:${roomId}`;
39 yield* kv.put(key, message);
40 const got = yield* kv.get(key);
41 if (got !== message) {
42 return yield* Effect.die(
43 new Error(
44 `KV roundtrip mismatch: expected "${message}", got "${got ?? "null"}"`,
45 ),
46 );
47 }
48 return got;
49 }).pipe(Effect.orDie),
50 );
51
52 // Resolve the bound secret inside the workflow body. The accessor
53 // returns `Redacted<string>`; unwrap only where the value needs to
54 // leave the workflow (here, in the broadcast + the returned output
55 // so the integ test can assert end-to-end propagation).
56 const secretValue = Redacted.value(secret);
57
58 const processed = yield* Cloudflare.Workflows.task(
59 "process",
60 Effect.succeed({
61 text: `Processed: ${stored}`,
62 secret: secretValue,
63 ts: Date.now(),
64 }),
65 );
66
67 // const room = rooms.getByName(roomId);
68 // yield* Cloudflare.Workflows.task(
69 // "broadcast",
70 // room.broadcast(`[workflow] ${processed.text} secret=${secretValue}`),
71 // );
72
73 // yield* Cloudflare.Workflows.sleep("cooldown", "2 seconds");
74
75 // yield* Cloudflare.Workflows.task(
76 // "finalize",
77 // room.broadcast(`[workflow] complete for ${roomId}`),
78 // );
79
80 return processed;
81 });
82 }),
83) {}