| 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 | */ |
| 11 | interface 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 | |
| 17 | interface TraceItemLite { |
| 18 | scriptName?: string | null; |
| 19 | outcome?: string; |
| 20 | logs?: { message?: unknown[]; level?: string; timestamp?: number }[]; |
| 21 | } |
| 22 | |
| 23 | export 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 | }; |