1import * as Data from "effect/Data";
2import type * as Duration from "effect/Duration";
3import * as Effect from "effect/Effect";
4import * as Schedule from "effect/Schedule";
5import * as HttpClient from "effect/http/HttpClient";
6import type * as HttpClientRequest from "effect/http/HttpClientRequest";
7
8class WorkerNotPropagated extends Data.TaggedError("WorkerNotPropagated")<{
9 readonly url: string;
10}> {}
11
12const isWorkerPlaceholder = (status: number, body: string) =>
13 (status === 404 &&
14 body.includes("<title>Page not found</title>") &&
15 body.includes("There is nothing here yet")) ||
16 (status === 500 &&
17 body.includes("<title>Script not found |") &&
18 body.includes(" | Cloudflare</title>") &&
19 body.includes("/cdn-cgi/styles/cf.errors.css"));
20
21/** Retry only Cloudflare's pre-invocation placeholder, never application errors. */
22export const requestWorker = (
23 request: HttpClientRequest.HttpClientRequest,
24 options: { retryDelay?: Duration.Input } = {},
25) =>
26 HttpClient.execute(request).pipe(
27 Effect.flatMap((response) =>
28 response.status !== 404 && response.status !== 500
29 ? Effect.succeed(response)
30 : response.text.pipe(
31 Effect.flatMap((body) =>
32 isWorkerPlaceholder(response.status, body)
33 ? Effect.fail(new WorkerNotPropagated({ url: request.url }))
34 : Effect.succeed(response),
35 ),
36 ),
37 ),
38 Effect.retry({
39 while: (error) => error._tag === "WorkerNotPropagated",
40 schedule: Schedule.spaced(options.retryDelay ?? "1 second"),
41 times: 8,
42 }),
43 );