1import * as cache from "@distilled.cloud/cloudflare/cache";
2import * as Effect from "effect/Effect";
3import * as Predicate from "effect/Predicate";
4import * as Schedule from "effect/Schedule";
5
6import * as Provider from "../../Provider.ts";
7import { Resource } from "../../Resource.ts";
8import { CloudflareEnvironment } from "../CloudflareEnvironment.ts";
9import type { Providers } from "../Providers.ts";
10import { listAllZones } from "../Zone/lookup.ts";
11
12const TypeId = "Cloudflare.Cache.RegionalTieredCache" as const;
13type TypeId = typeof TypeId;
14
15export interface RegionalTieredCacheProps {
16 /**
17 * Zone whose Regional Tiered Cache setting is managed. Stable —
18 * changing the zone triggers a replacement (the old zone's setting is
19 * restored to the value it had before Alchemy managed it).
20 */
21 zoneId: string;
22 /**
23 * Whether Regional Tiered Cache is enabled on the zone (`value: "on"`)
24 * or disabled (`value: "off"`). Mutable — patched in place.
25 * @default true
26 */
27 enabled?: boolean;
28}
29
30export interface RegionalTieredCacheAttributes {
31 /** Zone the setting belongs to. */
32 zoneId: string;
33 /** Resolved current value of the setting (`"on"` or `"off"`). */
34 value: string;
35 /**
36 * Whether the setting can be modified on the zone's current plan
37 * (`false` means the setting is plan-gated).
38 */
39 editable: boolean;
40 /** When the setting was last modified, if Cloudflare reports it. */
41 modifiedOn: string | undefined;
42 /**
43 * The value the setting had before Alchemy first patched it. Restored
44 * on destroy, so deleting the resource puts the zone back the way it
45 * was found.
46 */
47 initialValue: string;
48}
49
50export type RegionalTieredCache = Resource<
51 TypeId,
52 RegionalTieredCacheProps,
53 RegionalTieredCacheAttributes,
54 never,
55 Providers
56>;
57
58/**
59 * The Regional Tiered Cache setting of a Cloudflare zone
60 * (`/zones/{zone_id}/cache/regional_tiered_cache`).
61 *
62 * Regional Tiered Cache adds a regional layer between Cloudflare's lower
63 * tiers and the upper-tier data center, so cache misses in a region only
64 * travel to the regional hub instead of crossing the globe. The setting is
65 * a zone **singleton** — it always exists on entitled zones (default
66 * `off`), so this resource never creates or deletes anything physical.
67 * Reconcile patches the setting when the observed value differs from the
68 * desired one; destroy restores the value the setting had before Alchemy
69 * first managed it (captured as `initialValue`).
70 *
71 * **Plan-gated**: Regional Tiered Cache requires an Enterprise zone. On
72 * lower plans both reads and writes fail with Cloudflare error code 1135
73 * ("this zone setting is not available for your plan type"), surfaced as
74 * the typed `SettingUnavailableForPlan` error.
75 *
76 * Only one `RegionalTieredCache` resource per zone makes sense — two
77 * instances managing the same zone would fight over the singleton.
78 * ### Managing Regional Tiered Cache
79 * **Example:** Enable Regional Tiered Cache on an Enterprise zone
80 * ```typescript
81 * const zone = yield* Cloudflare.Zone.Zone("Site", { name: "example.com" });
82 *
83 * yield* Cloudflare.Cache.RegionalTieredCache("RegionalCache", {
84 * zoneId: zone.zoneId,
85 * });
86 * ```
87 *
88 * **Example:** Explicitly disable Regional Tiered Cache
89 * ```typescript
90 * yield* Cloudflare.Cache.RegionalTieredCache("RegionalCache", {
91 * zoneId: zone.zoneId,
92 * enabled: false,
93 * });
94 * ```
95 *
96 * @see https://developers.cloudflare.com/cache/how-to/tiered-cache/#regional-tiered-cache
97 *
98 * @resource
99 * @product Cache
100 * @category Performance & Reliability
101 */
102export const RegionalTieredCache = Resource<RegionalTieredCache>(TypeId);
103
104/**
105 * Returns true if the given value is a RegionalTieredCache resource.
106 */
107export const isRegionalTieredCache = (
108 value: unknown,
109): value is RegionalTieredCache =>
110 Predicate.hasProperty(value, "Type") && value.Type === TypeId;
111
112const desiredValue = (props: RegionalTieredCacheProps): "on" | "off" =>
113 (props.enabled ?? true) ? "on" : "off";
114
115// Cloudflare can transiently fail to authenticate a valid token, surfacing as
116// `Unauthorized`/`Forbidden`. Retry with exponential backoff capped at 5s,
117// bounded to ~8 attempts so a persistently-failing call still fails fast.
118const transientAuthRetrySchedule = Schedule.max([
119 Schedule.min([
120 Schedule.exponential("500 millis"),
121 Schedule.spaced("5 seconds"),
122 ]),
123 Schedule.recurs(8),
124]);
125
126export const RegionalTieredCacheProvider = () =>
127 Provider.succeed(RegionalTieredCache, {
128 stables: ["zoneId", "initialValue"],
129
130 list: Effect.fn(function* () {
131 const { accountId } = yield* yield* CloudflareEnvironment;
132 // No account-wide API for this zone singleton — enumerate every
133 // zone in the account and read its setting. Regional Tiered Cache
134 // is Enterprise-only, so non-entitled zones reject the route with
135 // the typed `SettingUnavailableForPlan` error and are skipped.
136 const allZones = yield* listAllZones(accountId);
137 const rows = yield* Effect.forEach(
138 allZones.map((zone) => zone.id),
139 (zoneId) =>
140 cache.getRegionalTieredCache({ zoneId }).pipe(
141 // Cloudflare intermittently rejects a *valid* token with
142 // `Unauthorized`/`Forbidden` (a transient edge auth failure).
143 // Retry with capped backoff rather than dropping the zone, so a
144 // genuinely accessible zone never falls out of the enumeration on
145 // a blip.
146 Effect.retry({
147 while: (e) => e._tag === "Unauthorized" || e._tag === "Forbidden",
148 schedule: transientAuthRetrySchedule,
149 }),
150 Effect.map((observed) =>
151 toAttributes(zoneId, observed, observed.value),
152 ),
153 // Zone deleted out-of-band or plan-gated: skip it.
154 Effect.catchTag("InvalidRoute", () => Effect.succeed(undefined)),
155 Effect.catchTag("SettingUnavailableForPlan", () =>
156 Effect.succeed(undefined),
157 ),
158 ),
159 { concurrency: 10 },
160 );
161 return rows.filter(
162 (row): row is RegionalTieredCacheAttributes => row !== undefined,
163 );
164 }),
165
166 diff: Effect.fn(function* ({ olds = {}, news, output }) {
167 const o = olds as RegionalTieredCacheProps;
168 const n = news as RegionalTieredCacheProps;
169 // zoneId is Input<string>; compare only once both sides are concrete.
170 const oldZoneId =
171 output?.zoneId ?? (typeof o.zoneId === "string" ? o.zoneId : undefined);
172 if (
173 oldZoneId !== undefined &&
174 typeof n.zoneId === "string" &&
175 oldZoneId !== n.zoneId
176 ) {
177 return { action: "replace" } as const;
178 }
179 return undefined;
180 }),
181
182 read: Effect.fn(function* ({ output, olds }) {
183 const zoneId = output?.zoneId ?? (olds?.zoneId as string | undefined);
184 if (!zoneId) return undefined;
185 const observed = yield* cache.getRegionalTieredCache({ zoneId }).pipe(
186 // Zone deleted out-of-band — the setting is gone with it.
187 Effect.catchTag("InvalidRoute", () => Effect.succeed(undefined)),
188 );
189 if (observed === undefined) return undefined;
190 // The setting is a singleton that always exists with a Cloudflare
191 // default — there is nothing to "own", so a cold read adopts
192 // freely (never `Unowned`). The observed value at adoption time
193 // becomes the `initialValue` restored on destroy.
194 const initialValue =
195 output !== undefined ? output.initialValue : observed.value;
196 return toAttributes(zoneId, observed, initialValue);
197 }),
198
199 reconcile: Effect.fn(function* ({ news, output }) {
200 // Inputs have been resolved to concrete strings by Plan.
201 const zoneId = news.zoneId as string;
202
203 // 1. Observe — the setting always exists (on entitled zones); read
204 // its live value. Plan-gated zones fail here with the typed
205 // `SettingUnavailableForPlan` error.
206 const observed = yield* cache.getRegionalTieredCache({ zoneId });
207
208 // 2. Capture — the pre-management value, restored on destroy.
209 // `output` (including an adoption read) already carries it;
210 // otherwise this is our first touch and the observed value is
211 // the zone's original.
212 const initialValue =
213 output !== undefined ? output.initialValue : observed.value;
214
215 // 3. Sync — patch only when the observed value differs.
216 const desired = desiredValue(news);
217 if (observed.value === desired) {
218 return toAttributes(zoneId, observed, initialValue);
219 }
220 const patched = yield* cache.patchRegionalTieredCache({
221 zoneId,
222 value: desired,
223 });
224 return toAttributes(zoneId, patched, initialValue);
225 }),
226
227 delete: Effect.fn(function* ({ output }) {
228 const { zoneId, initialValue } = output;
229 // Observe — if the zone itself is gone (or the plan was downgraded
230 // so the setting no longer exists for the zone), nothing to restore.
231 const observed = yield* cache.getRegionalTieredCache({ zoneId }).pipe(
232 Effect.catchTag("InvalidRoute", () => Effect.succeed(undefined)),
233 Effect.catchTag("SettingUnavailableForPlan", () =>
234 Effect.succeed(undefined),
235 ),
236 );
237 if (observed === undefined) return;
238 // Restore the pre-management value; skip the call when it already
239 // matches (idempotent re-delete after a crashed run).
240 if (observed.value === initialValue) return;
241 yield* cache
242 .patchRegionalTieredCache({ zoneId, value: initialValue })
243 .pipe(Effect.catchTag("InvalidRoute", () => Effect.void));
244 }),
245 });
246
247const toAttributes = (
248 zoneId: string,
249 setting:
250 | cache.GetRegionalTieredCacheResponse
251 | cache.PatchRegionalTieredCacheResponse,
252 initialValue: string,
253): RegionalTieredCacheAttributes => ({
254 zoneId,
255 value: setting.value,
256 editable: setting.editable,
257 modifiedOn: setting.modifiedOn ?? undefined,
258 initialValue,
259});