1import * as Cloudflare from "alchemy/Cloudflare";
2import * as Effect from "effect/Effect";
3import * as Schedule from "effect/Schedule";
4import { EffectfulContainer } from "./effectful-container.ts";
5
6/**
7 * Durable Object backing one effectful container instance. `boot()` blocks
8 * until the container is accepting RPC (`ping()` answers). NOTE: the
9 * authoritative cold-start clock runs in the Worker AROUND the whole DO call —
10 * the container layer eagerly starts the container during DO construction, so
11 * a clock started here would miss part of the start.
12 *
13 * Each distinct `getByName(name)` is a distinct DO instance and therefore a
14 * distinct container instance, which is how the benchmark spins up N of them.
15 */
16export class EffectfulObject extends Cloudflare.DurableObject<EffectfulObject>()(
17 "BenchEffectfulObject",
18 Effect.gen(function* () {
19 const container = yield* EffectfulContainer;
20
21 return Effect.gen(function* () {
22 return {
23 // Time container cold-start → reachable (RPC answers). Leaves the
24 // container running so the benchmark can `shutdown()` it separately —
25 // the timing must not include teardown. A freshly-built image is still
26 // distributing to the edge metal on the first boots, so retry until the
27 // RPC port answers.
28 boot: () =>
29 Effect.gen(function* () {
30 const start = yield* Effect.sync(() => Date.now());
31 yield* container.ping().pipe(
32 Effect.retry({
33 schedule: Schedule.min([Schedule.exponential("1 second"), Schedule.spaced("5 seconds")]),
34 times: 40,
35 }),
36 );
37 const readyMs = (yield* Effect.sync(() => Date.now())) - start;
38 return { readyMs };
39 }),
40 shutdown: () => container.destroy().pipe(Effect.ignore),
41 };
42 });
43 }).pipe(
44 Effect.provide(
45 Cloudflare.Containers.layer(EffectfulContainer, {
46 enableInternet: true,
47 }),
48 ),
49 ),
50) {}