Open History to see the last commit
1import * as Axiom from "@alchemy.run/axiom";
2import { Observability } from "./observability.ts";
3import * as Cloudflare from "@alchemy.run/cloudflare";
4import * as Config from "effect/Config";
5import * as Effect from "effect/Effect";
6import * as Layer from "effect/Layer";
7import * as Redacted from "effect/Redacted";
8import * as HttpServerResponse from "effect/http/HttpServerResponse";
9import * as Admission from "./Admission.ts";
10import * as Cache from "./Cache.ts";
11import * as Cleanup from "./Cleanup.ts";
12import * as NativeClients from "./NativeClients.ts";
13import * as Savings from "./Savings.ts";
14import type { ProtocolBindings } from "./protocolTypes.ts";
15import * as Resources from "./resources.ts";
16
17export class Worker extends Cloudflare.Worker<Worker, {}>()("Cache") {}
18
19export const layer = Worker.make(
20 Resources.cacheName.pipe(
21 Effect.map((name) => ({
22 name,
23 main: import.meta.filename,
24 compatibility: { date: "2026-09-11", flags: ["nodejs_compat"] },
25 })),
26 ),
27 Effect.gen(function* () {
28 const savings = yield* Cloudflare.D1.QueryDatabase(yield* Resources.Savings);
29 const database = yield* Cloudflare.D1.QueryDatabase(yield* Resources.Index);
30 const artifacts = yield* Cloudflare.R2.ReadWriteBucket(yield* Resources.Artifacts);
31 const nativeClients = yield* Cloudflare.R2.ReadWriteBucket(yield* Resources.NativeClients);
32 const developerKeys = yield* Config.Redacted("DEVELOPER_KEYS");
33 const readerKeys = yield* Config.Redacted("READER_KEYS");
34 const reads = yield* Cloudflare.RateLimit("READ_LIMITER", {
35 namespaceId: "1001",
36 simple: { limit: 600, period: 60 },
37 });
38 const writes = yield* Cloudflare.RateLimit("STORE_LIMITER", {
39 namespaceId: "1002",
40 simple: { limit: 600, period: 60 },
41 });
42 const bindings = Effect.gen(function* () {
43 return Cache.Bindings.of({
44 INDEX: yield* database.raw,
45 ARTIFACTS: (yield* artifacts.raw) as unknown as ProtocolBindings["ARTIFACTS"],
46 GC_BATCH_SIZE: String(Cleanup.batchSize),
47 developerKeys: Redacted.value(developerKeys),
48 readerKeys: Redacted.value(readerKeys),
49 readLimit: yield* reads.raw,
50 storeLimit: yield* writes.raw,
51 });
52 });
53 const nativeRuntime = NativeClients.layer.pipe(
54 Layer.provide(
55 Layer.effect(
56 NativeClients.Bindings,
57 Effect.gen(function* () {
58 return NativeClients.Bindings.of({
59 CLIENTS: yield* nativeClients.raw,
60 developerKeys: Redacted.value(developerKeys),
61 readerKeys: Redacted.value(readerKeys),
62 readLimit: yield* reads.raw,
63 storeLimit: yield* writes.raw,
64 });
65 }),
66 ),
67 ),
68 );
69 const admission = yield* Admission.Admission.pipe(Effect.provide(Admission.layer));
70 const savingsLayer = Savings.layer.pipe(
71 Layer.provide(Layer.effect(Savings.Database, savings.raw)),
72 );
73 const runtime = Cache.layer.pipe(
74 Layer.provide([
75 Layer.succeed(Admission.Admission, admission),
76 Layer.effect(Cache.Bindings, bindings),
77 savingsLayer,
78 ]),
79 );
80 yield* Cloudflare.Workers.cron("* * * * *", () =>
81 Cleanup.run().pipe(Effect.provide(runtime), Effect.orDie),
82 );
83 yield* Cloudflare.Workers.cron("0 * * * *", () =>
84 Savings.Savings.pipe(
85 Effect.flatMap((savings) => savings.cleanup.pipe(Effect.andThen(savings.emitTotals()))),
86 Effect.provide(savingsLayer),
87 Effect.orDie,
88 ),
89 );
90 return {
91 fetch: Effect.gen(function* () {
92 const request = yield* Cloudflare.Workers.Request;
93 if (new URL(request.url).pathname.startsWith(NativeClients.routePrefix)) {
94 const native = yield* NativeClients.NativeClients.pipe(Effect.provide(nativeRuntime));
95 return HttpServerResponse.fromWeb(yield* native.handle(request));
96 }
97 const context = yield* Cloudflare.Workers.WorkerExecutionContext;
98 const cache = yield* Cache.Cache;
99 return HttpServerResponse.fromWeb(yield* cache.handle(request, context.raw));
100 }).pipe(Effect.provide(runtime)),
101 };
102 }).pipe(
103 Effect.provide([
104 Cloudflare.D1.QueryDatabaseBinding,
105 Cloudflare.R2.ReadWriteBucketBinding,
106 Cloudflare.Workers.RateLimitBinding,
107 Cloudflare.Workers.CronEventSourceLive,
108 Layer.unwrap(
109 Effect.map(Observability, ({ traces, token }) =>
110 Axiom.Telemetry({ serviceName: "t3-task-cache", traces, token }),
111 ),
112 ),
113 ]),
114 ),
115);
116// Alchemy's generated Worker entry imports the runtime layer as the default export.
117export default layer;