Spracherkennung für: .ts vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]
async function readChunkWithIdleTimeout(
reader: ReadableStreamDefaultReader<Uint8Array>,
chunkTimeoutMs: number,
onIdleTimeout?: (params: { chunkTimeoutMs: number }) => Error,
): Promise<Awaited<ReturnType<typeof reader.read>>> {
let timeoutId: ReturnType<typeof setTimeout> | undefined;
let timedOut = false;
return await new Promise((resolve, reject) => {
const clear = () => {
if (timeoutId !== undefined) {
clearTimeout(timeoutId);
timeoutId = undefined;
}
};
timeoutId = setTimeout(() => {
timedOut = true;
clear();
void reader.cancel().catch(() => undefined);
reject(
onIdleTimeout?.({ chunkTimeoutMs }) ??
new Error(`Media download stalled: no data received for ${chunkTimeoutMs}ms`),
);
}, chunkTimeoutMs);
void reader.read().then(
(result) => {
clear();
if (!timedOut) {
resolve(result);
}
},
(err) => {
clear();
if (!timedOut) {
reject(err);
}
},
);
});
}
type ReadResponsePrefixResult = {
buffer: Buffer;
size: number;
truncated: boolean;
};
async function readResponsePrefix(
res: Response,
maxBytes: number,
opts?: {
chunkTimeoutMs?: number;
onIdleTimeout?: (params: { chunkTimeoutMs: number }) => Error;
},
): Promise<ReadResponsePrefixResult> {
const chunkTimeoutMs = opts?.chunkTimeoutMs;
const body = res.body;
if (!body || typeof body.getReader !== "function") {
const fallback = Buffer.from(await res.arrayBuffer());
if (fallback.length > maxBytes) {
return {
buffer: fallback.subarray(0, maxBytes),
size: fallback.length,
truncated: true,
};
}
return { buffer: fallback, size: fallback.length, truncated: false };
}
const reader = body.getReader();
const chunks: Uint8Array[] = [];
let total = 0;
let size = 0;
let truncated = false;
try {
while (true) {
const { done, value } = chunkTimeoutMs
? await readChunkWithIdleTimeout(reader, chunkTimeoutMs, opts?.onIdleTimeout)
: await reader.read();
if (done) {
size = total;
break;
}
if (!value?.length) {
continue;
}
const nextTotal = total + value.length;
if (nextTotal > maxBytes) {
const remaining = maxBytes - total;
if (remaining > 0) {
chunks.push(value.subarray(0, remaining));
total += remaining;
}
size = nextTotal;
truncated = true;
try {
await reader.cancel();
} catch {}
break;
}
chunks.push(value);
total = nextTotal;
size = total;
}
} finally {
try {
reader.releaseLock();
} catch {}
}
return {
buffer: Buffer.concat(
chunks.map((chunk) => Buffer.from(chunk)),
total,
),
size,
truncated,
};
}
export async function readResponseWithLimit(
res: Response,
maxBytes: number,
opts?: {
onOverflow?: (params: { size: number; maxBytes: number; res: Response }) => Error;
chunkTimeoutMs?: number;
onIdleTimeout?: (params: { chunkTimeoutMs: number }) => Error;
},
): Promise<Buffer> {
const onOverflow =
opts?.onOverflow ??
((params: { size: number; maxBytes: number }) =>
new Error(`Content too large: ${params.size} bytes (limit: ${params.maxBytes} bytes)`));
const prefix = await readResponsePrefix(res, maxBytes, {
chunkTimeoutMs: opts?.chunkTimeoutMs,
onIdleTimeout: opts?.onIdleTimeout,
});
if (prefix.truncated) {
throw onOverflow({ size: prefix.size, maxBytes, res });
}
return prefix.buffer;
}
export async function readResponseTextSnippet(
res: Response,
opts?: {
maxBytes?: number;
maxChars?: number;
chunkTimeoutMs?: number;
onIdleTimeout?: (params: { chunkTimeoutMs: number }) => Error;
},
): Promise<string | undefined> {
const maxBytes = opts?.maxBytes ?? 8 * 1024;
const maxChars = opts?.maxChars ?? 200;
const prefix = await readResponsePrefix(res, maxBytes, {
chunkTimeoutMs: opts?.chunkTimeoutMs,
onIdleTimeout: opts?.onIdleTimeout,
});
if (prefix.buffer.length === 0) {
return undefined;
}
const text = new TextDecoder().decode(prefix.buffer);
if (!text) {
return undefined;
}
const collapsed = text.replace(/\s+/g, " ").trim();
if (!collapsed) {
return undefined;
}
if (collapsed.length > maxChars) {
return `${collapsed.slice(0, maxChars)}…`;
}
return prefix.truncated ? `${collapsed}…` : collapsed;
}
¤ Dauer der Verarbeitung: 0.23 Sekunden
(vorverarbeitet am 2026-04-27)
¤
*© Formatika GbR, Deutschland