MiniSearch / server /utils /streamUtils.ts
Nymbo's picture
Sync with upstream felladrin/MiniSearch (2026-10-01)
ff7a361 verified
Raw History Blame Contribute Delete
2.35 kB
import type { ServerResponse } from "node:http";
/**
* Reads at most `maxBytes` of a fetch response body. A hostile or merely huge
* upstream must not be able to pin the server's memory, and callers that only
* need the head of a document (page text, thumbnails) gain nothing from the
* tail anyway.
*/
export async function readCappedBytes(
response: Response,
maxBytes: number,
): Promise<{ bytes: Uint8Array; truncated: boolean }> {
const reader = response.body?.getReader();
if (!reader) return { bytes: new Uint8Array(), truncated: false };
const chunks: Uint8Array[] = [];
let bytesRead = 0;
let reachedEnd = false;
while (bytesRead < maxBytes) {
const { done, value } = await reader.read();
if (done) {
reachedEnd = true;
break;
}
const remaining = maxBytes - bytesRead;
const chunk =
value.byteLength > remaining ? value.subarray(0, remaining) : value;
bytesRead += chunk.byteLength;
chunks.push(chunk);
if (chunk.byteLength < value.byteLength) break;
}
await reader.cancel().catch(() => {});
const bytes = new Uint8Array(bytesRead);
let offset = 0;
for (const chunk of chunks) {
bytes.set(chunk, offset);
offset += chunk.byteLength;
}
return { bytes, truncated: !reachedEnd };
}
/** Calculates the backoff delay for a retry attempt, with jitter. */
export function calculateBackoffTime(
attempt: number,
baseDelayMs = 100,
maxDelayMs = 5000,
): number {
const delay = Math.min(baseDelayMs * 2 ** (attempt - 1), maxDelayMs);
return delay * (0.7 + Math.random() * 0.3);
}
export function isResponseWritable(response: ServerResponse): boolean {
return !response.writableEnded && !response.destroyed;
}
export function safeWriteResponse(
response: ServerResponse,
data: string,
): boolean {
if (!isResponseWritable(response)) return false;
try {
return response.write(data);
} catch (error) {
console.error("Failed to write to response:", error);
return false;
}
}
export function safeEndResponse(response: ServerResponse, data?: string): void {
if (response.writableEnded || response.destroyed) return;
try {
if (data) {
response.end(data);
} else {
response.end();
}
} catch (error) {
console.error("Failed to end response:", error);
response.destroy();
}
}