1/**
2 * Async (non-Effect) Tail Worker fixture for `TailConsumers.test.ts` and
3 * `TailConsumers.local.test.ts`.
4 *
5 * Cloudflare invokes the exported `tail()` handler with the trace items of
6 * each invocation of any producer Worker that lists this script in its
7 * `tail_consumers`. Every batch is persisted to the bound KV namespace under
8 * a key prefixed with the *producing* script's name, so the live test
9 * verifies delivery out-of-band (distilled KV reads) without depending on
10 * which isolate served which request. The `/events` fetch route returns every
11 * recorded batch for the local test, where no out-of-band KV API exists —
12 * the local simulator is only reachable through the worker's own binding.
13 */
14
15interface TailEventsKV {
16 put(key: string, value: string): Promise<void>;
17 get(key: string): Promise<string | null>;
18 list(options?: { prefix?: string }): Promise<{ keys: { name: string }[] }>;
19}
20
21interface TraceItemLite {
22 scriptName?: string | null;
23 outcome?: string;
24 logs?: { message?: unknown[]; level?: string; timestamp?: number }[];
25}
26
27export default {
28 async fetch(
29 request: Request,
30 env: { EVENTS: TailEventsKV },
31 ): Promise<Response> {
32 const url = new URL(request.url);
33 if (url.pathname === "/events") {
34 const list = await env.EVENTS.list({ prefix: "evt:" });
35 const batches = await Promise.all(
36 list.keys.map((key) => env.EVENTS.get(key.name)),
37 );
38 return Response.json({
39 keys: list.keys.map((key) => key.name),
40 batches: batches.filter((batch): batch is string => batch !== null),
41 });
42 }
43 return new Response("tail-consumer-ok");
44 },
45 async tail(
46 events: TraceItemLite[],
47 env: { EVENTS: TailEventsKV },
48 ): Promise<void> {
49 const producer = events[0]?.scriptName ?? "unknown";
50 await env.EVENTS.put(
51 `evt:${producer}:${Date.now()}`,
52 JSON.stringify(events),
53 );
54 },
55};