Keep native collection launches responsive
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
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<string | Buffer> {
|
||||
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<Uint8Array> {
|
||||
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<Uint8Array>({
|
||||
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' } });
|
||||
}
|
||||
Reference in New Issue
Block a user