1import { fromDurableObjectState } from "@/Cloudflare/Workers/DurableObjectState.ts";
2import { fromDurableObjectStorage } from "@/Cloudflare/Workers/DurableObjectStorage.ts";
3import { RuntimeContext } from "@/RuntimeContext.ts";
4import { describe, expect, it } from "alchemy-test";
5import * as Context from "effect/Context";
6import * as Effect from "effect/Effect";
7
8/**
9 * `waitUntil` captures the caller's context and provides it to the Effect it
10 * runs. `blockConcurrencyWhile` and `storage.transaction` run their closures
11 * through a bare `Effect.runPromise` on the raw platform promise, so a service
12 * provided to the calling fiber (a request-scoped deadline, a tracer, an alarm
13 * floor) must reach those closures the same way.
14 */
15class Marker extends Context.Service<Marker, { readonly value: string }>()(
16 "test/Marker",
17) {}
18
19const readMarker = Effect.map(Marker, (marker) => marker.value);
20
21describe(
22 "Durable Object callbacks run with the caller's context",
23 {
24 tags: [
25 "unit",
26 "provider:cloudflare",
27 "provider:cloudflare:worker",
28 "local",
29 ],
30 },
31 () => {
32 it.effect(
33 "blockConcurrencyWhile provides the calling fiber's services",
34 () =>
35 Effect.gen(function* () {
36 const state = fromDurableObjectState({
37 id: { toString: () => "id" },
38 storage: { sql: {} },
39 blockConcurrencyWhile: <T>(callback: () => Promise<T>) =>
40 callback(),
41 } as never);
42
43 const value = yield* state.blockConcurrencyWhile(() => readMarker);
44
45 expect(value).toBe("inside the gate");
46 }).pipe(
47 Effect.provideService(Marker, { value: "inside the gate" }),
48 Effect.provide(RuntimeContext.phantom),
49 ),
50 );
51
52 it.effect("storage.transaction provides the calling fiber's services", () =>
53 Effect.gen(function* () {
54 const txn = { getAlarm: () => Promise.resolve(42) };
55 const storage = fromDurableObjectStorage({
56 sql: {},
57 transaction: <T>(closure: (txn: unknown) => Promise<T>) =>
58 closure(txn),
59 } as never);
60
61 const value = yield* storage.transaction((transaction) =>
62 Effect.all([readMarker, transaction.getAlarm()]),
63 );
64
65 expect(value).toEqual(["inside the transaction", 42]);
66 }).pipe(
67 Effect.provideService(Marker, { value: "inside the transaction" }),
68 Effect.provide(RuntimeContext.phantom),
69 ),
70 );
71 },
72);