1/**
2 * Tail consumer for {@link AsyncWorker}. Listed in the producer's
3 * `tailConsumers`, so every invocation of the producer delivers a trace
4 * batch to `tail()`. Batches are recorded into a KV namespace and exposed
5 * over `GET /events` so the integ test can assert the producer's
6 * `console.log` marker arrived end-to-end.
7 *
8 * NOTE: the default export must be this module's ONLY export — extra named
9 * exports become workerd top-level exports and fail startup validation.
10 */
11interface TailEventsKV {
12 put(key: string, value: string): Promise<void>;
13 get(key: string): Promise<string | null>;
14 list(options?: { prefix?: string }): Promise<{ keys: { name: string }[] }>;
15}
16
17interface TraceItemLite {
18 scriptName?: string | null;
19 outcome?: string;
20 logs?: { message?: unknown[]; level?: string; timestamp?: number }[];
21}
22
23export default {
24 async fetch(
25 request: Request,
26 env: { EVENTS: TailEventsKV },
27 ): Promise<Response> {
28 const url = new URL(request.url);
29 if (url.pathname === "/events") {
30 const list = await env.EVENTS.list({ prefix: "evt:" });
31 const batches = await Promise.all(
32 list.keys.map((key) => env.EVENTS.get(key.name)),
33 );
34 return Response.json({
35 keys: list.keys.map((key) => key.name),
36 batches: batches.filter((batch): batch is string => batch !== null),
37 });
38 }
39 return new Response("tail-consumer-ok");
40 },
41 async tail(
42 events: TraceItemLite[],
43 env: { EVENTS: TailEventsKV },
44 ): Promise<void> {
45 const producer = events[0]?.scriptName ?? "unknown";
46 await env.EVENTS.put(
47 `evt:${producer}:${Date.now()}`,
48 JSON.stringify(events),
49 );
50 },
51};