import { setImmediate } from 'node:timers/promises'; import { getHostOperationPayload, type OperationDeliveryRow } from './index.js'; // Even non-ASCII content stays below 64 KiB per encoded chunk. const CHUNK_CHARACTERS = 16 * 1024; const CHUNK_BYTES = 64 * 1024; const encoder = new TextEncoder(); function* jsonParts(operations: OperationDeliveryRow[], serverTime: string): Generator { yield '{"operations":['; for (const [index, operation] of operations.entries()) { if (index) yield ','; const metadata = JSON.stringify({ id: operation.id, hostId: operation.host_id, kind: operation.kind, instanceId: operation.instance_id, generation: operation.generation, state: operation.state, error: operation.error, createdAt: operation.created_at, updatedAt: operation.updated_at }); yield metadata.slice(0, -1) + ',"payload":'; // Load only the immutable payload currently being sent, after the // stream has yielded its metadata and had a chance to serve health work. yield getHostOperationPayload(operation.host_id, operation.id); yield ',"result":'; yield operation.result ?? 'null'; yield '}'; } yield '],"serverTime":' + JSON.stringify(serverTime) + '}'; } function* encodedChunks(operations: OperationDeliveryRow[], serverTime: string): Generator { for (const part of jsonParts(operations, serverTime)) { if (Buffer.isBuffer(part)) { for (let start = 0; start < part.length; start += CHUNK_BYTES) yield part.subarray(start, start + CHUNK_BYTES); continue; } for (let start = 0; start < part.length;) { let end = Math.min(start + CHUNK_CHARACTERS, part.length); // A UTF-16 surrogate pair must be encoded together, including when // user-authored configuration text falls on a chunk boundary. const last = part.charCodeAt(end - 1); if (end < part.length && last >= 0xd800 && last <= 0xdbff) end--; yield encoder.encode(part.slice(start, end)); start = end; } } } /** Bound encoding work and yield between chunks so health requests keep flowing. */ export function hostOperationsResponse(operations: OperationDeliveryRow[], serverTime = new Date().toISOString()): Response { const chunks = encodedChunks(operations, serverTime); let cancelled = false; const stream = new ReadableStream({ async pull(controller) { await setImmediate(); if (cancelled) return; const next = chunks.next(); if (next.done) controller.close(); else controller.enqueue(next.value); }, cancel() { cancelled = true; chunks.return(undefined); } }); return new Response(stream, { headers: { 'Content-Type': 'application/json; charset=utf-8', 'Cache-Control': 'no-store' } }); }