@ubercode/multipart-stream 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +261 -0
- package/dist/index.cjs +930 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +785 -0
- package/dist/index.d.ts +785 -0
- package/dist/index.js +913 -0
- package/dist/index.js.map +1 -0
- package/package.json +94 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/internal/format-error-embed.ts","../src/extract-boundary.ts","../src/internal/default-logger.ts","../src/internal/validate-timeout.ts","../src/internal/flatten-headers.ts","../src/internal/normalize-input.ts","../src/internal/queue-notifier.ts","../src/internal/timers.ts","../src/parse-multipart-related.ts","../src/fetch-and-handle-multipart.ts","../src/stream-helpers.ts"],"names":["Readable","dicerMod","PassThrough"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Public error classes (FR-019, NFR-DR-S-001, NFR-DR-S-004, NFR-DR-S-012,\n * NFR-DR-D-007).\n *\n * Every class:\n * - extends `Error`\n * - sets `this.name` literally to its class-name string in the constructor\n * (NFR-DR-D-007 — survives minification AND is the documented fallback\n * when `instanceof` returns false across the ESM/CJS module-format\n * boundary)\n * - supports `cause` plumbing via `super(message, options)`\n * - exposes typed structured property fields for caller-side branching\n *\n * The classes are runtime values (NFR-012); consumers may import them as\n * values for `instanceof` checks AND as types in signatures.\n */\n\n/**\n * Thrown when no source-stream bytes arrive for `idleTimeoutMs` consecutive\n * milliseconds. The source has been destroyed and all listeners removed by\n * the time this surfaces.\n *\n * @example\n * try {\n * await fetchAndHandleMultipart(url, { idleTimeoutMs: 5000, totalTimeoutMs: 60_000, parser });\n * } catch (err) {\n * if (err instanceof MultipartIdleTimeoutError) {\n * metrics.increment('multipart.idle_timeout', { ms: err.idleTimeoutMs });\n * }\n * throw err;\n * }\n */\nexport class MultipartIdleTimeoutError extends Error {\n /** Stable cross-format discriminator (NFR-DR-D-007). */\n override readonly name = 'MultipartIdleTimeoutError';\n /** The configured idle window (ms) that elapsed without source activity. */\n readonly idleTimeoutMs: number;\n\n /**\n * @param idleTimeoutMs - The configured idle window in ms.\n * @param options - Optional `{ cause }` for wrapping a lower-level error.\n */\n constructor(idleTimeoutMs: number, options?: ErrorOptions) {\n super(`multipart: idle timeout (${String(idleTimeoutMs)}ms)`, options);\n this.idleTimeoutMs = idleTimeoutMs;\n }\n}\n\n/**\n * Thrown when the total wallclock budget (`totalTimeoutMs`) elapses,\n * regardless of source activity.\n *\n * @example\n * if (err instanceof MultipartTotalTimeoutError) {\n * metrics.increment('multipart.total_timeout');\n * }\n */\nexport class MultipartTotalTimeoutError extends Error {\n override readonly name = 'MultipartTotalTimeoutError';\n /** The configured total window (ms) that elapsed. */\n readonly totalTimeoutMs: number;\n\n /**\n * @param totalTimeoutMs - The configured total budget in ms.\n * @param options - Optional `{ cause }`.\n */\n constructor(totalTimeoutMs: number, options?: ErrorOptions) {\n super(`multipart: total timeout (${String(totalTimeoutMs)}ms)`, options);\n this.totalTimeoutMs = totalTimeoutMs;\n }\n}\n\n/**\n * Thrown when the caller-provided `AbortSignal` fires (FR-009), or is already\n * aborted at call time. `reason` carries the signal's `reason` verbatim, or\n * is `undefined` if the signal had no reason. The library NEVER synthesizes\n * a server-derived reason (F-S-006).\n *\n * @example\n * const ctrl = new AbortController();\n * setTimeout(() => ctrl.abort(new Error('user cancelled')), 5000);\n * try {\n * await fetchAndHandleMultipart(url, { signal: ctrl.signal, idleTimeoutMs: 5000, totalTimeoutMs: 30000, parser });\n * } catch (err) {\n * if (err instanceof MultipartAbortError) console.warn('aborted because:', err.reason);\n * }\n */\nexport class MultipartAbortError extends Error {\n override readonly name = 'MultipartAbortError';\n /**\n * The signal's `reason` if the caller supplied one, else `undefined`. Per\n * F-S-006 the library never synthesizes a reason that embeds server bytes.\n */\n readonly reason?: unknown;\n\n /**\n * @param reason - Optional caller-supplied abort reason.\n * @param options - Optional `{ cause }`.\n */\n constructor(reason?: unknown, options?: ErrorOptions) {\n // We deliberately do NOT embed `reason` into the message (NFR-DR-S-006:\n // attacker-controlled bytes do not flow into log lines via this path).\n super('multipart: operation aborted', options);\n if (reason !== undefined) this.reason = reason;\n }\n}\n\n/**\n * Thrown when the source stream ends without dicer observing the closing\n * multipart boundary (FR-022) — typically a mid-flight server hangup or\n * transport-layer cut. Cleanup per FR-010 still runs.\n *\n * @example\n * if (err instanceof MultipartTruncatedError) {\n * retryQueue.enqueue({ url, bytesReceived: err.bytesReceived });\n * }\n */\nexport class MultipartTruncatedError extends Error {\n override readonly name = 'MultipartTruncatedError';\n /** Total bytes pulled from the source before it ended prematurely. */\n readonly bytesReceived: number;\n\n /**\n * @param bytesReceived - Cumulative bytes received before the source ended.\n * @param options - Optional `{ cause }`.\n */\n constructor(bytesReceived: number, options?: ErrorOptions) {\n super(\n `multipart: stream ended before closing boundary (${String(\n bytesReceived,\n )}B received)`,\n options,\n );\n this.bytesReceived = bytesReceived;\n }\n}\n\n/**\n * Info-bag for {@link MultipartPartTooLargeError}.\n */\nexport interface MultipartPartTooLargeInfo {\n /** The configured `maxPartBytes` cap that was exceeded. */\n readonly maxPartBytes: number;\n /** Zero-based ordinal of the offending part. */\n readonly partIndex: number;\n /** Bytes observed at the moment the cap tripped. */\n readonly bytesReceived: number;\n}\n\n/**\n * Thrown when a part body's accumulated bytes exceed `maxPartBytes`\n * (NFR-DR-S-001). The offending part body is destroyed and full FR-010\n * cleanup runs before this surfaces.\n *\n * @example\n * if (err instanceof MultipartPartTooLargeError) {\n * metrics.increment('multipart.part_too_large', { partIndex: err.partIndex });\n * }\n */\nexport class MultipartPartTooLargeError extends Error {\n override readonly name = 'MultipartPartTooLargeError';\n readonly maxPartBytes: number;\n readonly partIndex: number;\n readonly bytesReceived: number;\n\n /**\n * @param info - Structured trip info: `{ maxPartBytes, partIndex, bytesReceived }`.\n * @param options - Optional `{ cause }`.\n */\n constructor(info: MultipartPartTooLargeInfo, options?: ErrorOptions) {\n super(\n `multipart: part ${String(info.partIndex)} exceeded maxPartBytes (${String(\n info.maxPartBytes,\n )}) at ${String(info.bytesReceived)}B`,\n options,\n );\n this.maxPartBytes = info.maxPartBytes;\n this.partIndex = info.partIndex;\n this.bytesReceived = info.bytesReceived;\n }\n}\n\n/**\n * Info-bag for {@link MultipartHeadersTooLargeError}.\n */\nexport interface MultipartHeadersTooLargeInfo {\n /** Discriminator: which limit was hit. */\n readonly limit: 'count' | 'bytes';\n /** Zero-based ordinal of the offending part. */\n readonly partIndex: number;\n /** The configured cap. */\n readonly cap: number;\n /** Observed value at trip-time. */\n readonly observed: number;\n}\n\n/**\n * Thrown when a part has more headers than `maxHeadersPerPart` (default 100)\n * OR its header block bytes exceed `maxHeaderBytesPerPart` (default 16 KiB)\n * — NFR-DR-S-004.\n *\n * @example\n * if (err instanceof MultipartHeadersTooLargeError) {\n * log.warn({ limit: err.limit, observed: err.observed }, 'oversized part headers');\n * }\n */\nexport class MultipartHeadersTooLargeError extends Error {\n override readonly name = 'MultipartHeadersTooLargeError';\n readonly limit: 'count' | 'bytes';\n readonly partIndex: number;\n readonly cap: number;\n readonly observed: number;\n\n /**\n * @param info - Structured trip info: `{ limit, partIndex, cap, observed }`.\n * @param options - Optional `{ cause }`.\n */\n constructor(info: MultipartHeadersTooLargeInfo, options?: ErrorOptions) {\n super(\n `multipart: part ${String(info.partIndex)} headers exceeded ${info.limit} cap (${String(\n info.cap,\n )}) at ${String(info.observed)}`,\n options,\n );\n this.limit = info.limit;\n this.partIndex = info.partIndex;\n this.cap = info.cap;\n this.observed = info.observed;\n }\n}\n\n/**\n * Info-bag for {@link MultipartTooManyPartsError}.\n */\nexport interface MultipartTooManyPartsInfo {\n /** The configured `maxParts` cap that was exceeded. */\n readonly maxParts: number;\n /** Observed part count when the cap tripped (== `maxParts + 1`). */\n readonly observed: number;\n}\n\n/**\n * Thrown when the multipart envelope contains more parts than `maxParts`\n * (default `10_000`) — NFR-DR-S-012.\n *\n * @example\n * if (err instanceof MultipartTooManyPartsError) {\n * log.warn({ cap: err.maxParts }, 'too many parts');\n * }\n */\nexport class MultipartTooManyPartsError extends Error {\n override readonly name = 'MultipartTooManyPartsError';\n readonly maxParts: number;\n readonly observed: number;\n\n /**\n * @param info - Structured trip info: `{ maxParts, observed }`.\n * @param options - Optional `{ cause }`.\n */\n constructor(info: MultipartTooManyPartsInfo, options?: ErrorOptions) {\n super(\n `multipart: envelope exceeded maxParts (${String(info.maxParts)}) at part ${String(\n info.observed,\n )}`,\n options,\n );\n this.maxParts = info.maxParts;\n this.observed = info.observed;\n }\n}\n","/**\n * `formatErrorEmbed` — full implementation per NFR-DR-S-006 / F-S-005 / F-S-007.\n *\n * Sprint 7 replaces the Sprint 3 truncate-only stub with the full sanitizer:\n * 1. Replace control chars (0x00-0x1F + 0x7F) with the literal token\n * `[redacted-control]`. This stops attacker-controlled bytes from\n * injecting CR/LF/escape sequences into the caller's logs.\n * 2. Replace ANSI/CSI escape sequences (`\\x1B[...]<final-byte>`) with the\n * same `[redacted-control]` token. CSI is the dominant attacker vector\n * for log-injection (color/cursor manipulation in terminal viewers);\n * other escape forms (DCS `\\x1B P`, OSC `\\x1B ]`, single-shifts) are\n * out of scope for v1 — documented in the Sprint 7 assumptions.\n * 3. `JSON.stringify` the redacted result so embedded quotes are visible\n * and any remaining stray non-printable that snuck past the regexes\n * gets escaped to `\u0000`-style sequences. The wrapping `\"...\"` makes\n * the embed's boundaries unambiguous in the rendered error message.\n * 4. Truncate the JSON-stringified form to 120 chars with an ellipsis\n * suffix when over the limit.\n *\n * Truncation order matters: redact FIRST (so the literal redaction token is\n * preserved verbatim), JSON.stringify SECOND (so the redaction token reads\n * as text rather than getting JSON-escaped), truncate LAST (so the visual\n * length matches the visible rendered embed).\n *\n * @internal\n */\n\nconst FORMAT_ERROR_EMBED_LIMIT = 120;\nconst ELLIPSIS = '…';\nconst REDACTION_TOKEN = '[redacted-control]';\n\n/**\n * CSI (Control Sequence Introducer) escape sequence pattern. Matches\n * `\\x1B[` + zero-or-more parameter bytes (digits and `;` and `?`) +\n * zero-or-more intermediate bytes (`\\x20`-`\\x2F`) + one final byte\n * (`\\x40`-`\\x7E`). Conservative — covers SGR, CUP, EL, ED, DSR, etc.\n *\n * Pattern is anchored character-class only — no quantifier nesting that\n * could enable backtracking.\n *\n * @internal\n */\nconst ANSI_ESCAPE_RE = /\\x1B\\[[0-9;?]*[ -/]*[@-~]/g;\n\n/**\n * C0 + DEL control characters. Matches any single byte in the range\n * 0x00-0x1F or 0x7F. Linear scan — no backtracking concerns.\n *\n * @internal\n */\nconst CONTROL_CHARS_RE = /[\\x00-\\x1F\\x7F]/g;\n\n/**\n * Replace ANSI escape sequences and C0 control chars with the literal\n * `[redacted-control]` token. ANSI replacement runs FIRST so the bytes\n * inside a CSI sequence (which are themselves printable digits / `;` /\n * `?` plus the final byte) don't get redacted twice — they're consumed\n * as a unit by the ANSI regex and replaced once. The C0 pass after that\n * catches stray control bytes outside any escape sequence.\n *\n * @internal\n */\nfunction redactControlAndAnsi(s: string): string {\n return s.replace(ANSI_ESCAPE_RE, REDACTION_TOKEN).replace(CONTROL_CHARS_RE, REDACTION_TOKEN);\n}\n\n/**\n * Sanitize an arbitrary value for safe inclusion in an error message that\n * will be rendered to a terminal or logged to a structured logger.\n *\n * The output is:\n * - JSON-stringified (so embedded quotes are visible and any remaining\n * non-printable bytes are visible as `\u0000`-style escapes),\n * - control-char-redacted (0x00-0x1F + 0x7F → `[redacted-control]`),\n * - ANSI/CSI-redacted (`\\x1B[...]` → `[redacted-control]`),\n * - truncated to 120 chars + ellipsis when over the limit.\n *\n * Non-string inputs are coerced via `String(value)` first; the redaction\n * + stringify + truncate chain runs unconditionally. The function is\n * total — it never throws.\n *\n * @param value - The value to embed. Typically a `Content-Type` header\n * value, an `err.message`, or another attacker-controllable string.\n * Non-strings are coerced.\n * @returns A safe-to-embed string. Always non-empty (`\"\"` produces `'\"\"'`\n * from JSON.stringify).\n *\n * @example\n * truncateForErrorEmbed('hello \"world\"');\n * // '\"hello \\\\\"world\\\\\"\"'\n *\n * @example\n * truncateForErrorEmbed('text\u0000with\u0007control');\n * // '\"text[redacted-control]with[redacted-control]control\"'\n *\n * @example\n * truncateForErrorEmbed('\\x1B[31mred\\x1B[0m');\n * // '\"[redacted-control]red[redacted-control]\"'\n *\n * @internal\n */\nexport function truncateForErrorEmbed(value: unknown): string {\n const s = typeof value === 'string' ? value : String(value);\n // Step 1: redact ANSI + control chars BEFORE JSON.stringify so the\n // redaction token reads as text (otherwise JSON.stringify would render\n // `\\x07` as `\u0007` rather than as our token).\n const redacted = redactControlAndAnsi(s);\n // Step 2: JSON.stringify so embedded quotes are visible and the embed's\n // boundaries are unambiguous.\n const stringified = JSON.stringify(redacted);\n // Step 3: truncate to 120 chars (the JSON-stringified length, not the\n // pre-stringify length — we cap the rendered embed). Sliced strings\n // remain valid UTF-16; JSON's escape sequences won't be split by a\n // 120-char slice unless the input was already longer than 120 chars,\n // in which case the trailing `…` makes the truncation visible to\n // operators reading the log.\n if (stringified.length > FORMAT_ERROR_EMBED_LIMIT) {\n return stringified.slice(0, FORMAT_ERROR_EMBED_LIMIT) + ELLIPSIS;\n }\n return stringified;\n}\n\n/**\n * Reduce an unknown thrown value to a `{ name, message }` pair safe to put\n * in `Logger.meta.errSummary` (NFR-DR-S-008).\n *\n * Non-`Error` throws fall back to `{ name: 'Error', message: <stringified> }`.\n * The message is run through {@link truncateForErrorEmbed} so the meta\n * payload stays bounded even for attacker-controlled error messages.\n *\n * Used at every `logger.warn` `meta.errSummary` site in the library:\n * - Layer A late-emit handler (FR-011 retained dicer 'error' listener),\n * - Layer A FR-017 silent-catch replacements (onProgress, unpipe),\n * - Layer A per-part 'error' bridge (post-cleanup logger branch),\n * - Layer B FR-017 silent-catch replacement (completion-tick onProgress).\n *\n * @param err - Unknown thrown value.\n * @returns `{ name, message }` summary safe for structured logging.\n *\n * @example\n * summarizeError(new TypeError('bang'));\n * // { name: 'TypeError', message: '\"bang\"' }\n *\n * @example\n * summarizeError('plain-string');\n * // { name: 'Error', message: '\"plain-string\"' }\n *\n * @internal\n */\nexport function summarizeError(err: unknown): {\n readonly name: string;\n readonly message: string;\n} {\n if (err instanceof Error) {\n return {\n name: err.name,\n message: truncateForErrorEmbed(err.message),\n };\n }\n return {\n name: 'Error',\n message: truncateForErrorEmbed(err),\n };\n}\n","/**\n * `extractBoundary` — RFC 2046 boundary extractor (FR-016).\n *\n * Public utility. Pure function — no I/O, no allocations beyond the result\n * and a single intermediate buffer for unescaping a quoted string.\n *\n * Implementation note (NFR-DR-S-011, T-080): this is a hand-written,\n * non-backtracking tokenizer. A naive regex with an alternation between the\n * quoted-string and bare-token forms is ReDoS-prone on adversarial input\n * (megabytes of `\\\\\\\"` sequences); the deliberate single-pass tokenizer\n * below runs in O(n) time on any input.\n */\n\nimport { truncateForErrorEmbed } from './internal/format-error-embed.js';\n\n/**\n * Parse the `boundary=` parameter out of an HTTP `Content-Type` header value.\n *\n * Handles RFC 2046 quoted-string and bare-token forms. The first `boundary=`\n * occurrence wins (parameters are scanned left-to-right). Boundary parameter\n * names are matched case-insensitively. The returned token is unquoted and\n * has its backslash escapes resolved.\n *\n * @param contentTypeHeader - The full `Content-Type` header value, e.g.\n * `multipart/related; boundary=\"weird;boundary\"`. May be `null` or\n * `undefined`; both are treated as missing-input errors.\n * @returns The unquoted boundary token.\n * @throws {Error} `multipart: Content-Type header is required to extract\n * boundary` when the input is null, undefined, or empty.\n * @throws {Error} `multipart: boundary parameter missing from Content-Type:\n * <header>` when the header has no `boundary=` parameter.\n * @throws {Error} `multipart: boundary parameter is empty in Content-Type:\n * <header>` when the parameter is present but the value is empty.\n *\n * In every error path that embeds the input header, the embedded value is\n * sanitized via the full sanitizer (truncate to 120 chars, control-char\n * redact, ANSI redact, JSON.stringify per NFR-DR-S-006).\n *\n * @example\n * extractBoundary('multipart/related; boundary=foo'); // 'foo'\n *\n * @example\n * // Quoted-string form with embedded special chars (RFC 2046):\n * extractBoundary('multipart/related; boundary=\"weird;boundary\"');\n * // 'weird;boundary'\n *\n * @example\n * // Multiple parameters in any order; case-insensitive parameter name:\n * extractBoundary('multipart/related; type=\"application/dicom\"; BOUNDARY=BAR; charset=utf-8');\n * // 'BAR'\n */\nexport function extractBoundary(\n contentTypeHeader: string | null | undefined,\n): string {\n if (contentTypeHeader == null || contentTypeHeader === '') {\n throw new Error(\n 'multipart: Content-Type header is required to extract boundary',\n );\n }\n\n const header = contentTypeHeader;\n const len = header.length;\n let i = 0;\n\n // Skip the type/subtype prefix up to the first `;` — we don't validate it\n // here (FR-021 in fetchAndHandleMultipart does the multipart/related\n // assertion). We just hunt for `boundary=` in the parameter list.\n while (i < len && header.charCodeAt(i) !== 0x3b /* ; */) i++;\n\n while (i < len) {\n // Skip the `;` and any whitespace + leading semicolons before the next\n // parameter token.\n while (i < len) {\n const c = header.charCodeAt(i);\n if (c === 0x3b /* ; */ || c === 0x20 /* space */ || c === 0x09 /* tab */) {\n i++;\n continue;\n }\n break;\n }\n if (i >= len) break;\n\n // Parameter name: token chars up to `=`. Per RFC 7230, tokens are a\n // restricted set; we accept any non-`=` non-`;` non-whitespace as the\n // name and lower-case-compare against `boundary`.\n const nameStart = i;\n while (i < len) {\n const c = header.charCodeAt(i);\n if (c === 0x3d /* = */ || c === 0x3b /* ; */) break;\n if (c === 0x20 /* space */ || c === 0x09 /* tab */) break;\n i++;\n }\n const name = header.slice(nameStart, i).toLowerCase();\n\n // Skip whitespace between name and `=`.\n while (i < len) {\n const c = header.charCodeAt(i);\n if (c === 0x20 || c === 0x09) {\n i++;\n continue;\n }\n break;\n }\n\n if (i >= len || header.charCodeAt(i) !== 0x3d /* = */) {\n // Either end-of-string or another `;` — this is a flag-only parameter\n // (not legal per RFC, but tolerated by browsers); skip it.\n // Advance past anything until the next `;`.\n while (i < len && header.charCodeAt(i) !== 0x3b) i++;\n continue;\n }\n\n // Skip the `=` and any whitespace.\n i++;\n while (i < len) {\n const c = header.charCodeAt(i);\n if (c === 0x20 || c === 0x09) {\n i++;\n continue;\n }\n break;\n }\n\n // Value: either a quoted-string (\"...\", with `\\` escapes) or a bare token\n // up to the next `;`/whitespace.\n let value = '';\n if (i < len && header.charCodeAt(i) === 0x22 /* \" */) {\n // Quoted string. Single-pass scan; never backtracks. The cost on a\n // 64 KiB pathological input (T-080) is O(n) regardless of escape\n // density — that's the whole point of NOT using a regex here.\n i++; // consume opening quote\n const buf: string[] = [];\n let closed = false;\n while (i < len) {\n const c = header.charCodeAt(i);\n if (c === 0x5c /* \\ */ && i + 1 < len) {\n // Escaped char — accept the next char verbatim.\n buf.push(header.charAt(i + 1));\n i += 2;\n continue;\n }\n if (c === 0x22 /* \" */) {\n closed = true;\n i++;\n break;\n }\n buf.push(header.charAt(i));\n i++;\n }\n // Per RFC, an unterminated quoted string is malformed. We accept what\n // we got rather than throwing — extractBoundary's failure modes are\n // about missing/empty boundary, not malformed framing. The downstream\n // dicer call will raise on a bad boundary anyway.\n void closed;\n value = buf.join('');\n } else {\n // Bare token — read until `;` / whitespace / EOL.\n const valStart = i;\n while (i < len) {\n const c = header.charCodeAt(i);\n if (c === 0x3b /* ; */ || c === 0x20 || c === 0x09) break;\n i++;\n }\n value = header.slice(valStart, i);\n }\n\n if (name === 'boundary') {\n if (value === '') {\n throw new Error(\n `multipart: boundary parameter is empty in Content-Type: ${truncateForErrorEmbed(\n header,\n )}`,\n );\n }\n return value;\n }\n\n // Not the parameter we're looking for — skip to the next `;`.\n while (i < len && header.charCodeAt(i) !== 0x3b) i++;\n }\n\n throw new Error(\n `multipart: boundary parameter missing from Content-Type: ${truncateForErrorEmbed(\n header,\n )}`,\n );\n}\n","/**\n * Default logger fallback (FR-018).\n *\n * The PUBLIC `Logger` injection-point is event-style (JC-2):\n * `(event: { level: 'warn'; msg: string; meta?: unknown }) => void`\n *\n * — but per FR-018, when the caller omits `logger`, internal warnings MUST\n * fall back to `console.warn(msg, meta)` (POSITIONAL — matches `console`'s\n * signature, NOT the event-style fn). This is the documented contract;\n * tests T-019 (event-style call) and T-020 (positional console.warn) both\n * assert it.\n *\n * The adapter below is therefore intentionally tiny: it accepts an event\n * and re-emits it positionally on `console.warn`. Library code that needs\n * to log MUST go through `defaultLogger` (or a caller-supplied `Logger`)\n * via the `resolveLogger` helper here so the fallback shape is honored\n * uniformly.\n */\n\nimport type { Logger } from '../types.js';\n\n/**\n * Default `Logger` used when the caller did not supply one.\n *\n * Per FR-018, the fallback emits to `console.warn` with the POSITIONAL\n * `(msg, meta)` shape — NOT the event-style payload. This matches the\n * console API a Node operator expects and lets a stack-trace-grepping CI\n * pipeline find these warnings.\n *\n * `meta` is omitted from the call when undefined to avoid `console.warn\n * (msg, undefined)` rendering as `<msg> undefined` under some console\n * implementations.\n *\n * @internal\n */\nexport const defaultLogger: Logger = (event) => {\n if (event.meta === undefined) {\n console.warn(event.msg);\n return;\n }\n console.warn(event.msg, event.meta);\n};\n\n/**\n * Resolve a caller-supplied `Logger` to a defined function. When the caller\n * supplies one, return it as-is; when they omit it, return\n * {@link defaultLogger}. Library code calls the resolved logger uniformly\n * with event-style payloads — the internal `defaultLogger` is responsible\n * for re-shaping to the positional `console.warn` form.\n *\n * @param logger - Caller-supplied logger (or undefined).\n * @returns The resolved logger function.\n *\n * @internal\n */\nexport function resolveLogger(logger: Logger | undefined): Logger {\n return logger ?? defaultLogger;\n}\n","/**\n * `validatePositiveTimeout` — centralized timeout validation for both public\n * entry points (`parseMultipartRelated` and `fetchAndHandleMultipart`) per\n * `kiln/spec/data-model.md` §2.11.\n *\n * Per FR-006 / JC-3 / NFR-DR-S-009, both `idleTimeoutMs` and `totalTimeoutMs`\n * are REQUIRED on every entry point and MUST be positive finite integers in\n * the inclusive range `[1, 2_147_483_647]` (`2^31 - 1`). Values outside this\n * range are rejected synchronously with `TypeError` whose message names the\n * field and explains Node's internal `setTimeout` clamping behavior — any\n * delay greater than `2^31 - 1` ms is silently clamped down to `1` ms, which\n * would be a silent debugging surprise.\n *\n * The function is a TypeScript assertion function so callers can call it on\n * `opts.idleTimeoutMs` and have the type narrow afterward, even though the\n * input declared type is `number` (no run-time narrowing happens — the assert\n * just tells the type system that any execution past this point has the\n * range invariant).\n *\n * @internal\n */\n\n/** Inclusive upper bound (`2^31 - 1`); Node's `setTimeout` clamps above this. */\nconst MAX_SAFE_TIMEOUT_MS = 2_147_483_647;\n\n/**\n * Validate an `idleTimeoutMs` / `totalTimeoutMs` value. Throws `TypeError`\n * synchronously on `NaN`, non-finite, non-integer, `< 1`, or `> 2^31 - 1`.\n *\n * The error message MUST mention Node's `setTimeout` clamping behavior so\n * callers understand why the bound exists (NFR-DR-S-009).\n *\n * @param name - The option name (`idleTimeoutMs` or `totalTimeoutMs`) — used\n * verbatim in the error message so the caller knows which field tripped.\n * @param value - The value to validate. Declared `unknown` so callers who\n * pass an untyped record (e.g. JSON.parse output) get the same validation\n * without a static `as number` cast.\n *\n * @throws {TypeError} when `value` is not a number, not finite, not an\n * integer, less than 1, or greater than `2_147_483_647`.\n *\n * @example\n * validatePositiveTimeout('idleTimeoutMs', opts.idleTimeoutMs);\n * validatePositiveTimeout('totalTimeoutMs', opts.totalTimeoutMs);\n * // After both calls, opts.idleTimeoutMs / totalTimeoutMs are guaranteed\n * // to be valid integers in [1, 2^31 - 1].\n *\n * @internal\n */\nexport function validatePositiveTimeout(\n name: 'idleTimeoutMs' | 'totalTimeoutMs',\n value: unknown,\n): asserts value is number {\n if (\n typeof value !== 'number' ||\n !Number.isFinite(value) ||\n !Number.isInteger(value) ||\n value < 1 ||\n value > MAX_SAFE_TIMEOUT_MS\n ) {\n const observed =\n typeof value === 'number' ? String(value) : typeof value;\n throw new TypeError(\n `multipart: ${name} must be a positive finite integer in [1, 2_147_483_647]; got ${observed}. Note: Node's setTimeout clamps values above 2^31-1 to 1ms, so larger timeouts are silently broken.`,\n );\n }\n}\n","/**\n * Header-bag normalizer (FR-016 internal helper).\n *\n * Dicer's per-part `'header'` event delivers a `Record<string, ...>` whose\n * values vary by dicer version + MIME folding behavior:\n * - dicer 0.3.1's HeaderParser stores `string[]` (latin1-decoded line\n * content), one entry per repeated header name.\n * - The ambient shim at `src/internal/dicer.d.ts` declares\n * `Buffer | Buffer[] | Buffer[][]` defensively.\n *\n * We collapse every shape to a single string per name (joining repeats with\n * `, ` per RFC 7230 §3.2.2) and lowercase the key.\n *\n * Unit-tested via the integration tests for unusual capitalization\n * (T-057).\n *\n * @internal\n */\n\n/**\n * Coerce one header value (whatever shape dicer chose) to a single string.\n *\n * @param v - The raw header value, in any of the shapes documented above.\n * @returns The flattened string. Returns `''` for nullish input — matches the\n * reference impl's contract.\n *\n * @internal\n */\nexport function flattenHeaderValue(v: unknown): string {\n if (v == null) return '';\n if (typeof v === 'string') return v;\n if (Buffer.isBuffer(v)) return v.toString('utf8');\n if (Array.isArray(v)) {\n const parts: string[] = [];\n for (const inner of v) {\n const flat = flattenHeaderValue(inner);\n if (flat !== '') parts.push(flat);\n }\n return parts.join(', ');\n }\n // Fallback for unexpected shapes (e.g. number, boolean) — coerce to a\n // primitive string. Object/null cases are handled above; this branch only\n // catches pure primitives, which `Object.prototype.toString.call` would\n // overwrap, so we use the JSON-stringify of the wrapped primitive value\n // to keep the result log-line-friendly.\n if (typeof v === 'number' || typeof v === 'boolean' || typeof v === 'bigint') {\n return String(v);\n }\n // Truly unknown shape — return empty string rather than '[object Object]'.\n return '';\n}\n\n/**\n * Lowercase keys and run every value through `flattenHeaderValue`.\n *\n * @param raw - Dicer's raw header bag.\n * @returns A flat `Record<string, string | undefined>` with lowercase keys.\n *\n * @internal\n */\nexport function flattenDicerHeaders(\n raw: Record<string, unknown> | undefined,\n): Record<string, string | undefined> {\n if (raw == null) return {};\n const out: Record<string, string | undefined> = {};\n for (const key of Object.keys(raw)) {\n const lower = key.toLowerCase();\n const value = flattenHeaderValue(raw[key]);\n out[lower] = value;\n }\n return out;\n}\n","/**\n * Input normalizer for `parseMultipartRelated`.\n *\n * Per `kiln/spec/data-model.md` §2.4 (`ParseInput` discriminated union), the\n * public `parseMultipartRelated` accepts either a Web `Response` or a Node\n * `Readable`. Internally we always work in a `{ readable, boundary }` shape;\n * this module is the single conversion site.\n *\n * Synchronous validation paths required by FR-004:\n * - Response with `body === null` → throws.\n * - Response missing `Content-Type` → throws (via `extractBoundary`).\n * - Response with Content-Type but no `boundary=` → throws (via\n * `extractBoundary`).\n * - Readable input without explicit `opts.boundary` → throws.\n *\n * For Web `ReadableStream` bodies we call `Readable.fromWeb(body as\n * ReadableStream<Uint8Array>)` per FR-003 — the typed cast is documented\n * inline and is NOT `as never` (per NFR-001 / T-039).\n *\n * @internal\n */\n\nimport { Readable } from 'node:stream';\nimport type { ReadableStream as WebReadableStream } from 'node:stream/web';\n\nimport { extractBoundary } from '../extract-boundary.js';\n\n/** Discriminated union per `kiln/spec/data-model.md` §2.4. @internal */\nexport type NormalizedInput =\n | {\n readonly kind: 'response';\n readonly readable: Readable;\n readonly boundary: string;\n }\n | {\n readonly kind: 'readable';\n readonly readable: Readable;\n readonly boundary: string;\n };\n\n/** @internal */\nexport interface NormalizeOptions {\n readonly boundary?: string | undefined;\n}\n\n/**\n * Detect a Web `Response` without depending on the global `Response`\n * constructor for `instanceof` checks (Node 20.18+ has it, but defensive\n * duck-typing covers test fixtures that emulate the shape).\n *\n * @internal\n */\nfunction looksLikeResponse(input: unknown): input is Response {\n if (typeof Response !== 'undefined' && input instanceof Response) {\n return true;\n }\n if (input == null || typeof input !== 'object') return false;\n const candidate = input as { headers?: { get?: unknown }; body?: unknown };\n return (\n typeof candidate.headers === 'object' &&\n candidate.headers !== null &&\n typeof (candidate.headers).get === 'function' &&\n 'body' in candidate\n );\n}\n\n/**\n * Detect a Node `Readable` without requiring `instanceof Readable` (which is\n * fragile across re-bundled streams modules in mixed ESM+CJS environments).\n *\n * @internal\n */\nfunction looksLikeReadable(input: unknown): input is Readable {\n if (input == null || typeof input !== 'object') return false;\n const candidate = input as {\n pipe?: unknown;\n on?: unknown;\n readable?: unknown;\n };\n return (\n typeof candidate.pipe === 'function' &&\n typeof candidate.on === 'function'\n );\n}\n\n/**\n * Normalize a public `Response | Readable` input plus its options into a\n * `{ readable, boundary }` pair the parser core can consume.\n *\n * Throws synchronously on every FR-004 violation. The thrown errors are\n * intentionally plain `Error` (not the `Multipart*Error` classes) — the\n * spec calls these out as \"fundamentally invalid input\" pre-conditions,\n * not multipart-protocol failures.\n *\n * @internal\n */\nexport function normalizeInput(\n input: unknown,\n opts: NormalizeOptions,\n): NormalizedInput {\n if (looksLikeResponse(input)) {\n if (input.body == null) {\n throw new Error('multipart: response body is null');\n }\n const contentType = input.headers.get('content-type');\n // `extractBoundary` throws with the precise FR-004 / NFR-DR-S-006\n // sanitized error messages on missing/empty/missing-boundary input.\n const boundary = extractBoundary(contentType);\n\n // FR-003: convert the Web ReadableStream to a Node Readable. The cast\n // `body as ReadableStream<Uint8Array>` matches the runtime shape (fetch\n // bodies are byte streams) and satisfies `Readable.fromWeb`'s typings;\n // critically, this is NOT `as never` (per NFR-001 / T-039).\n const webBody = input.body as unknown as WebReadableStream<Uint8Array>;\n const readable = Readable.fromWeb(webBody);\n return { kind: 'response', readable, boundary };\n }\n\n if (looksLikeReadable(input)) {\n if (typeof opts.boundary !== 'string' || opts.boundary === '') {\n throw new Error(\n 'multipart: boundary option is required when input is a Readable',\n );\n }\n return { kind: 'readable', readable: input, boundary: opts.boundary };\n }\n\n throw new Error(\n 'multipart: input must be a Response or a Node Readable',\n );\n}\n","/**\n * Queue+notifier bridge between dicer's event-emitter shape and the async\n * generator returned by `parseMultipartRelated`.\n *\n * Per `kiln/spec/architecture.md` §3 and `kiln/spec/data-model.md` §2.1/§2.2: a\n * single-producer / single-consumer bounded async queue. Dicer's `'part'` /\n * `'finish'` / `'error'` event handlers `push` items synchronously; the\n * generator's main loop awaits `next()` until an item is enqueued.\n *\n * The simplest correct shape: an unbounded array + a single resolver pair.\n * Backpressure is not required (dicer is a `Writable` and handles its own\n * backpressure via the `pipe()` from the source `Readable`).\n *\n * Items are tagged with a discriminated union so callers can distinguish\n * part-events from end-of-stream and error sentinels without ambiguity.\n *\n * @internal\n */\n\nimport type { StreamingMultipartPart } from '../types.js';\n\n/** Discriminated union of items the queue carries. */\nexport type QueueItem =\n | { readonly type: 'part'; readonly part: StreamingMultipartPart }\n | { readonly type: 'end' }\n | { readonly type: 'error'; readonly err: Error };\n\n/**\n * Internal queue+notifier shape. See `src/internal/queue-notifier.ts` for\n * the public surface — note this interface is NOT re-exported.\n *\n * @internal\n */\nexport interface QueueNotifier {\n /**\n * Synchronously enqueue an item. If a consumer is waiting on `next()`, the\n * waiter is resolved on the same tick. If the queue has already been ended\n * or errored, the push is ignored (idempotency guard for the cleanup\n * path).\n */\n push(item: QueueItem): void;\n\n /**\n * Convenience: push an `end` sentinel exactly once. Subsequent calls are\n * ignored.\n */\n signalEnd(): void;\n\n /**\n * Convenience: push an `error` sentinel. The queue accepts multiple error\n * pushes (e.g. dicer + source both fail) — the consumer drains the first\n * one and the rest stay buffered for the cleanup path.\n */\n signalError(err: Error): void;\n\n /**\n * Pull the next item, or wait until one is available. Resolves with the\n * `end` sentinel when `signalEnd` has been called and the buffered items\n * are drained.\n */\n next(): Promise<QueueItem>;\n\n /**\n * Read-only snapshot of pending items (used at cleanup time for\n * draining).\n */\n readonly pending: readonly QueueItem[];\n\n /**\n * Cleanup-path helper: extract every still-pending `'part'` item, remove\n * them from the buffer, and return them so the iterator's finally can\n * destroy each `.body`. Non-part items (`'end'`, `'error'`) are left in\n * place so the consumer's next `.next()` call still sees them.\n *\n * Idempotent: calling twice returns an empty array on the second call.\n *\n * Used by the cleanup path in `parseMultipartRelated` (FR-010).\n */\n drainPendingParts(): readonly StreamingMultipartPart[];\n}\n\n/**\n * Construct a fresh `QueueNotifier`.\n *\n * @internal\n */\nexport function createQueueNotifier(): QueueNotifier {\n const items: QueueItem[] = [];\n let waiter: ((item: QueueItem) => void) | null = null;\n let ended = false;\n\n const push = (item: QueueItem): void => {\n if (ended && item.type !== 'end' && item.type !== 'error') {\n // After an `end` has been delivered, only further error pushes are\n // permitted (cleanup may surface late errors). Drop part pushes\n // silently — the dicer state machine has already declared the\n // stream finished.\n return;\n }\n if (item.type === 'end') {\n if (ended) return;\n ended = true;\n }\n if (waiter) {\n const w = waiter;\n waiter = null;\n w(item);\n return;\n }\n items.push(item);\n };\n\n const signalEnd = (): void => {\n push({ type: 'end' });\n };\n\n const signalError = (err: Error): void => {\n push({ type: 'error', err });\n };\n\n const next = (): Promise<QueueItem> => {\n const buffered = items.shift();\n if (buffered !== undefined) {\n return Promise.resolve(buffered);\n }\n return new Promise<QueueItem>((resolve) => {\n waiter = resolve;\n });\n };\n\n const drainPendingParts = (): readonly StreamingMultipartPart[] => {\n const parts: StreamingMultipartPart[] = [];\n const remaining: QueueItem[] = [];\n for (const item of items) {\n if (item.type === 'part') {\n parts.push(item.part);\n } else {\n remaining.push(item);\n }\n }\n items.length = 0;\n items.push(...remaining);\n return parts;\n };\n\n return {\n push,\n signalEnd,\n signalError,\n next,\n get pending(): readonly QueueItem[] {\n return items;\n },\n drainPendingParts,\n };\n}\n","/**\n * `TimerState` — composite abort plumbing for `parseMultipartRelated`'s\n * idle timeout, total timeout, and caller-provided `AbortSignal`. Per\n * `kiln/spec/data-model.md` §2.3 and `kiln/spec/architecture.md` §3 (Layer A owns the\n * timers per FR-DR-A-026), this module is the single source for all four\n * termination-by-time signals: idle, total, caller abort, parser-throw /\n * source-error.\n *\n * Design:\n * - One internal `AbortController`. The `signal` exposed publicly is its\n * `signal`, which fires on FIRST-fire-wins basis from any of the three\n * sources (idle / total / caller).\n * - `idleTimer` is reset by the per-chunk `'data'` listener attached to the\n * source `Readable` inside `parseMultipartRelated` (FR-DR-A-025 — onProgress\n * is NOT used for this).\n * - `totalTimer` runs once from `setupTimers` entry; never resets.\n * - The caller's `AbortSignal` (when supplied) gets a one-shot `abort` listener\n * that fires `abortInternally(MultipartAbortError(opts.signal.reason))`. If\n * the signal is ALREADY aborted at call time, `setupTimers` synchronously\n * stores the abort error and aborts the controller — `signal.aborted` is\n * `true` by the time the function returns. Caller (parseMultipartRelated)\n * checks this BEFORE entering its for-await loop and rejects the iterator\n * on first `.next()` per the FR-009 already-aborted contract.\n *\n * Idempotency:\n * - `cleanup()` is guarded with a `cleaned` flag — first call cancels both\n * timers and removes the caller-signal listener; subsequent calls are\n * no-ops.\n * - `abortInternally(err)` ignores subsequent calls once aborted (the stored\n * error is the FIRST fire — caller signal racing with idle timer keeps the\n * first-fire error).\n *\n * @internal\n */\n\nimport {\n MultipartAbortError,\n MultipartIdleTimeoutError,\n MultipartTotalTimeoutError,\n} from '../errors.js';\n\n/**\n * Options passed to {@link setupTimers}.\n *\n * @internal\n */\nexport interface TimerStateOptions {\n /** Idle window in ms. Validated upstream; assumed in `[1, 2^31-1]`. */\n readonly idleTimeoutMs: number;\n /** Total wallclock budget in ms. Validated upstream. */\n readonly totalTimeoutMs: number;\n /** Caller's `AbortSignal`, or omitted. Reason is forwarded verbatim. */\n readonly signal?: AbortSignal | undefined;\n}\n\n/**\n * State bundle returned by {@link setupTimers}. The signal aggregates idle +\n * total + caller-signal abort. `resetIdle()` is hot-pathed (called per source\n * chunk); `cleanup()` is idempotent (called from the iterator's `finally`).\n *\n * @internal\n */\nexport interface TimerState {\n /**\n * Combined `AbortSignal` — fires when ANY of: idle timer / total timer /\n * caller signal aborts. After a fire, {@link abortError} returns the\n * specific error class corresponding to the cause.\n */\n readonly signal: AbortSignal;\n\n /**\n * Reset the idle timer. Called from the per-chunk source `'data'` listener\n * inside `parseMultipartRelated` (FR-DR-A-025). Cheap — clears and re-arms\n * the existing timer.\n *\n * No-op once `cleanup()` has run or the signal has already aborted (the\n * combined signal can't un-abort).\n */\n resetIdle(): void;\n\n /**\n * Cancel both timers and detach the caller-signal listener. Idempotent.\n * Called from the iterator's `finally` block — paired with the\n * `cleanup()` function via the `cleaned` flag in\n * `parse-multipart-related.ts`.\n */\n cleanup(): void;\n\n /**\n * The error that caused the abort, when available. Resolved AFTER the\n * combined `signal` fires — discriminates idle / total / caller-abort via\n * the returned class type. Returns `undefined` if the signal has not yet\n * fired (e.g. successful operation, cleanup before any timer expired).\n */\n readonly abortError: () => Error | undefined;\n}\n\n/**\n * Construct a fresh {@link TimerState} for one operation.\n *\n * Side-effects fired immediately on entry:\n * 1. Two `setTimeout` calls (idle + total) — both armed before the function\n * returns so they cover the entire operation lifetime per FR-012's\n * \"before pipe()\" invariant.\n * 2. If `opts.signal` is already aborted, the controller is aborted\n * synchronously and a {@link MultipartAbortError} is stored as the abort\n * reason. `state.signal.aborted` will be `true` on return.\n * 3. If `opts.signal` is provided and not yet aborted, an `'abort'` listener\n * is attached to it. The listener forwards `signal.reason` verbatim into\n * a new `MultipartAbortError` — F-S-006 contract.\n *\n * @param opts - Idle/total timeouts (already validated by caller) and\n * optional caller `AbortSignal`.\n * @param startMs - The wallclock start time of the operation. Reserved for\n * computing `elapsedMs` in future error variants; the current error\n * classes take only the configured cap, so this parameter is currently\n * unused.\n *\n * @internal\n */\nexport function setupTimers(\n opts: TimerStateOptions,\n startMs: number,\n): TimerState {\n const controller = new AbortController();\n const callerSignal = opts.signal;\n\n let storedError: Error | undefined;\n let cleaned = false;\n let idleTimer: ReturnType<typeof setTimeout> | undefined;\n let totalTimer: ReturnType<typeof setTimeout> | undefined;\n\n // Single-shot abort: stores the error, aborts the controller, then runs\n // cleanup so timers don't continue ticking after the first fire. Subsequent\n // calls are no-ops because `controller.signal.aborted` is already true and\n // the stored error stays at its first-fire value.\n const abortInternally = (err: Error): void => {\n if (controller.signal.aborted) return;\n storedError = err;\n controller.abort(err);\n runCleanup();\n };\n\n const onCallerAbort = (): void => {\n // The caller signal owns its own `reason` (could be undefined, a string,\n // or an Error). F-S-006: the library MUST NOT synthesize a reason that\n // embeds server-derived bytes — we forward `callerSignal.reason` verbatim.\n abortInternally(new MultipartAbortError(callerSignal?.reason));\n };\n\n const onIdle = (): void => {\n abortInternally(new MultipartIdleTimeoutError(opts.idleTimeoutMs));\n };\n\n const onTotal = (): void => {\n abortInternally(new MultipartTotalTimeoutError(opts.totalTimeoutMs));\n };\n\n function runCleanup(): void {\n if (cleaned) return;\n cleaned = true;\n if (idleTimer !== undefined) {\n clearTimeout(idleTimer);\n idleTimer = undefined;\n }\n if (totalTimer !== undefined) {\n clearTimeout(totalTimer);\n totalTimer = undefined;\n }\n if (callerSignal !== undefined) {\n callerSignal.removeEventListener('abort', onCallerAbort);\n }\n }\n\n const resetIdle = (): void => {\n if (cleaned || controller.signal.aborted) return;\n if (idleTimer !== undefined) clearTimeout(idleTimer);\n idleTimer = setTimeout(onIdle, opts.idleTimeoutMs);\n };\n\n // FR-009 already-aborted check: if the caller signal is aborted at call\n // time we must short-circuit synchronously. We forward signal.reason\n // verbatim into the MultipartAbortError. The `cleaned` short-circuit in\n // resetIdle/abortInternally guarantees no follow-on timer can race against\n // this.\n if (callerSignal?.aborted) {\n storedError = new MultipartAbortError(callerSignal.reason);\n controller.abort(storedError);\n // Do NOT install the abort listener on an already-aborted signal — Node\n // would deliver the event immediately, but abortInternally would no-op\n // since controller.signal.aborted is already true. Skipping the\n // attach is symmetric with cleanup's removeEventListener.\n } else {\n if (callerSignal !== undefined) {\n callerSignal.addEventListener('abort', onCallerAbort, { once: true });\n }\n idleTimer = setTimeout(onIdle, opts.idleTimeoutMs);\n totalTimer = setTimeout(onTotal, opts.totalTimeoutMs);\n }\n\n // The `startMs` parameter is currently unused by the error classes\n // (the error fields don't embed `elapsedMs`) but threaded through for\n // forward-compat with NFR-DR-S-006's potential expansion.\n void startMs;\n\n return {\n signal: controller.signal,\n resetIdle,\n cleanup: runCleanup,\n abortError: () => storedError,\n };\n}\n","/**\n * `parseMultipartRelated` — Layer A (parser/dicer adapter). FR-001.\n *\n * Resource-cap enforcement (NFR-DR-S-001/004/012):\n * - `maxPartBytes` (NFR-DR-S-001): each per-part body Readable gets a\n * 'data' listener that increments a per-part byte counter; on overflow\n * the listener pushes `MultipartPartTooLargeError` into the queue and\n * destroys the offending part body. No default — when undefined, no\n * cap is enforced.\n * - `maxParts` (NFR-DR-S-012): the dicer 'part' counter is checked at\n * each emit; on overflow, push `MultipartTooManyPartsError`. Default\n * `10_000` when undefined.\n * - `maxHeadersPerPart` + `maxHeaderBytesPerPart` (NFR-DR-S-004): on\n * each per-part 'header' event, count headers and sum the byte length\n * of `name + ': ' + value + '\\r\\n'` framing for every header line; on\n * overflow, push `MultipartHeadersTooLargeError`. Defaults: count=100,\n * bytes=16384 (16 KiB).\n *\n * Timer + abort machinery (FR-006/FR-007/FR-008/FR-009/FR-DR-A-025/\n * FR-DR-A-026):\n * - `validatePositiveTimeout` calls at the top of the generator enforce\n * the FR-006 / JC-3 contract (both timeouts REQUIRED, both validated\n * against `[1, 2^31-1]`).\n * - `setupTimers(...)` constructs the composite abort signal aggregating\n * idle, total, and caller-supplied AbortSignal — one source of truth\n * for all three. The signal listens for any of those firing and pushes\n * the right error class into the queue.\n * - The per-chunk source `'data'` listener resets the idle timer\n * (FR-DR-A-025 — onProgress is NOT used for this).\n * - The cleanup function calls `timers.cleanup()` — same idempotent\n * `cleaned` flag.\n * - Already-aborted callers short-circuit on first `.next()` per FR-009.\n *\n * Cleanup contract: the `finally` cleanup drains unyielded part bodies\n * (FR-010), removes 'data'/'error'/'end' listeners on source and 'part'/\n * 'finish' on dicer, unpipes + destroys source, and KEEPS dicer's 'error'\n * listener for late-emit observability (FR-011). Truncation detection\n * (FR-022) fires when source 'end' arrives without dicer 'finish' having\n * fired.\n */\n\nimport { PassThrough, type Readable } from 'node:stream';\n\nimport type { DicerHeaderBag, DicerPartStream } from 'dicer';\n// FR-DR-A-027: import dicer through the hand-written ambient shim. The\n// FR-DR-A-028 normalization shape lives at the call site below (and must\n// stay this way — the cross-format consumer test asserts the runtime shape\n// works under both ESM and CJS bundles). DO NOT collapse this to\n// `import { default as Dicer } from 'dicer'` — that defeats the\n// normalization that handles the `module.exports = Dicer` CJS shape.\nimport dicerMod from 'dicer';\n\nimport {\n MultipartAbortError,\n MultipartHeadersTooLargeError,\n MultipartPartTooLargeError,\n MultipartTooManyPartsError,\n MultipartTruncatedError,\n} from './errors.js';\nimport { defaultLogger } from './internal/default-logger.js';\nimport { flattenDicerHeaders } from './internal/flatten-headers.js';\nimport { summarizeError } from './internal/format-error-embed.js';\nimport { normalizeInput } from './internal/normalize-input.js';\nimport { createQueueNotifier } from './internal/queue-notifier.js';\nimport { setupTimers } from './internal/timers.js';\nimport { validatePositiveTimeout } from './internal/validate-timeout.js';\nimport type {\n Logger,\n ParseMultipartOptions,\n StreamingMultipartPart,\n} from './types.js';\n\n/** @internal */\ntype DicerDefaultExport = typeof dicerMod;\n\n/**\n * Measure the byte length of one or more header VALUES (without the name\n * or framing). Dicer 0.3.1's HeaderParser delivers each header as\n * `string[]` (latin1-decoded line content per repeated header value). We\n * sum each entry's UTF-8 byte length.\n *\n * For the broader forward-compat shapes documented in the ambient shim\n * (`Buffer`, `Buffer[]`, `Buffer[][]`), the function recurses through\n * arrays and falls back to 0 for shapes it can't measure. The cap is\n * SOFT — under-counting at worst means the cap doesn't trip on a\n * forward-incompatible dicer version, NOT that the parser crashes.\n *\n * NOT exported. Used only by the maxHeaderBytesPerPart cap inside the\n * `'header'` event listener.\n *\n * @internal\n */\nfunction measureHeaderValueBytes(value: unknown): number {\n if (typeof value === 'string') return Buffer.byteLength(value);\n if (Array.isArray(value)) {\n let total = 0;\n for (const inner of value) {\n total += measureHeaderValueBytes(inner);\n }\n return total;\n }\n // Forward-compat: Buffer / Buffer[] would arrive via the array\n // branch above (Buffer is recursed-into) or directly here. Defensive\n // — returns 0 for unknown shapes, which is safe under the SOFT-cap\n // contract documented above.\n if (Buffer.isBuffer(value)) return value.length;\n return 0;\n}\n\n/**\n * FR-DR-A-028 default-export normalization shape (typed; no `as any`).\n *\n * `dicer` is a CJS module whose `module.exports = Dicer` is the constructor\n * itself. Under ESM, Node wraps it as `{ default: Dicer }`; under CJS the\n * import is the raw constructor. The normalizer below picks the right\n * binding for both module formats. The cross-format consumer test (T-072)\n * proves this works against `dist/index.js` AND `dist/index.cjs`.\n *\n * @internal\n */\nconst Dicer: DicerDefaultExport =\n (dicerMod as { default?: DicerDefaultExport }).default ?? dicerMod;\n\n/**\n * Parse a `multipart/related` envelope as a typed async-iterator of\n * streaming parts (FR-001).\n *\n * @param input - A Web `Response` (boundary auto-extracted from\n * `Content-Type`) or a Node `Readable` (caller supplies `boundary` via\n * options).\n * @param opts - {@link ParseMultipartOptions}. `idleTimeoutMs` and\n * `totalTimeoutMs` are REQUIRED on this entry point too (FR-006 / JC-3) and\n * are validated synchronously via `validatePositiveTimeout` — both must be\n * positive finite integers in `[1, 2_147_483_647]` (NFR-DR-S-009).\n * @returns An `AsyncGenerator<StreamingMultipartPart, void, void>` that\n * yields parts in dicer's emit order.\n *\n * @throws {TypeError} `multipart: idleTimeoutMs must be a positive finite\n * integer in [1, 2_147_483_647]; …` when `idleTimeoutMs` is missing,\n * non-numeric, non-finite, non-integer, `< 1`, or `> 2^31 - 1`. Same for\n * `totalTimeoutMs`.\n * @throws {Error} `multipart: response body is null` when `input` is a\n * `Response` whose `.body` is `null` (FR-004). The first `.next()` rejects.\n * @throws {Error} `multipart: Content-Type header is required to extract\n * boundary` when the Response has no `Content-Type`.\n * @throws {Error} `multipart: boundary parameter missing from Content-Type`\n * when `Content-Type` is present but lacks a `boundary=` parameter.\n * @throws {Error} `multipart: boundary option is required when input is a\n * Readable` when the input is a Node `Readable` and `opts.boundary` is\n * missing or empty.\n * @throws {MultipartIdleTimeoutError} when no source bytes arrive for\n * `idleTimeoutMs` consecutive ms (FR-007). The idle timer resets on every\n * chunk received from the source (FR-DR-A-025).\n * @throws {MultipartTotalTimeoutError} when the total operation wallclock\n * exceeds `totalTimeoutMs` (FR-008).\n * @throws {MultipartAbortError} when `opts.signal` fires (or is already\n * aborted at call time — first `.next()` rejects synchronously per FR-009).\n * `error.reason` is the caller's `signal.reason` verbatim (F-S-006).\n * @throws {MultipartTruncatedError} when the source emits `'end'` before\n * dicer emits `'finish'` (FR-022).\n *\n * @example\n * for await (const part of parseMultipartRelated(res, {\n * idleTimeoutMs: 5000,\n * totalTimeoutMs: 60_000,\n * })) {\n * console.log(part.contentType, part.contentId);\n * }\n */\nexport function parseMultipartRelated(\n input: Response,\n opts: ParseMultipartOptions,\n): AsyncGenerator<StreamingMultipartPart, void, void>;\nexport function parseMultipartRelated(\n input: Readable,\n opts: ParseMultipartOptions & { boundary: string },\n): AsyncGenerator<StreamingMultipartPart, void, void>;\nexport function parseMultipartRelated(\n input: Response | Readable,\n opts: ParseMultipartOptions,\n): AsyncGenerator<StreamingMultipartPart, void, void> {\n return parseMultipartRelatedImpl(input, opts);\n}\n\n// The async generator below is intentionally one function — see the\n// eslint.config.mjs per-file override. It owns three concerns (dicer\n// wiring, listener attachment before pipe per FR-012, and the yield loop)\n// that are deliberately kept together. Splitting into helpers would require\n// shared closure state across helper boundaries and obscure the \"all\n// listeners before pipe\" invariant.\nasync function* parseMultipartRelatedImpl(\n input: Response | Readable,\n opts: ParseMultipartOptions,\n): AsyncGenerator<StreamingMultipartPart, void, void> {\n // 1. Synchronous validation (FR-004 + FR-006/JC-3). Async generators\n // cannot truly throw at construction time per the vitest contract\n // (T-002/T-003/T-025 expectations) — every throw here surfaces from\n // the first `.next()`.\n //\n // Validate timeouts FIRST, before any input normalization, so callers\n // who pass an invalid timeout get the precise NFR-DR-S-009 message\n // even if their input is also malformed. Both timeouts must be present\n // AND in the inclusive range [1, 2^31-1] (validatePositiveTimeout\n // handles the missing-value case via the same TypeError path).\n validatePositiveTimeout('idleTimeoutMs', opts.idleTimeoutMs);\n validatePositiveTimeout('totalTimeoutMs', opts.totalTimeoutMs);\n\n // Resolve resource-cap defaults at validation time so the values stay\n // fixed for the duration of the operation (avoid race conditions where\n // opts mutates between listener fires). Per kiln/spec/api.md §2 and\n // review-security.md F-S-001/004/012:\n // - maxPartBytes: undefined => no cap (NFR-DR-S-001).\n // - maxParts: undefined => default 10_000 (NFR-DR-S-012).\n // - maxHeadersPerPart: undefined => default 100 (NFR-DR-S-004).\n // - maxHeaderBytesPerPart: undefined => default 16_384 (NFR-DR-S-004).\n // The numeric defaults defend against attacker-controlled inputs even\n // when callers omit the fields entirely.\n const maxPartBytes = opts.maxPartBytes;\n const maxParts = opts.maxParts ?? 10_000;\n const maxHeadersPerPart = opts.maxHeadersPerPart ?? 100;\n const maxHeaderBytesPerPart = opts.maxHeaderBytesPerPart ?? 16_384;\n\n const { readable: source, boundary } = normalizeInput(input, {\n boundary: opts.boundary,\n });\n\n // 2. Logger fallback (FR-018 / JC-2). Resolved here so the FR-017 catch\n // sites (onProgress + cleanup unpipe) and the FR-011 late-emit path\n // all share the same logger.\n const logger: Logger = opts.logger ?? defaultLogger;\n\n // 3. Construct dicer via the FR-DR-A-028 normalization at the top of file.\n const dicer = new Dicer({ boundary });\n\n // 4. Queue+notifier bridge (Layer C internal).\n const queue = createQueueNotifier();\n\n // Operation state used by the listeners.\n let bytesReceived = 0;\n let nextPartIndex = 0;\n let dicerFinished = false;\n let cleaned = false;\n // Set true when the abort came from the combined-signal handler so the\n // cleanup function knows the queue already received the discriminated\n // error — avoids double-pushing or racing with idle/total firings.\n let abortPushed = false;\n // Set of every per-part Readable dicer has emitted. We need this in\n // addition to queue.drainPendingParts() because a part may have been\n // emitted on dicer's 'part' event but NOT yet reached the per-part\n // 'header' event (so it never made it into the queue). Cleanup must\n // still destroy it — otherwise dicer's per-part Readable buffers a\n // chunk that is never freed (the silent-leak BRIEF flags).\n const allPartStreams = new Set<DicerPartStream>();\n const startMs = Date.now();\n\n // 4b. Set up the composite abort plumbing (FR-007/FR-008/FR-009/\n // FR-DR-A-025/FR-DR-A-026). The TimerState aggregates idle, total, and\n // caller-supplied AbortSignal into a single signal. It MUST be\n // constructed BEFORE we attach our pre-pipe listeners and BEFORE we\n // pipe — the timers fire wallclock-based, so any listener added later\n // would miss synchronous fires. The combined-signal handler attached\n // below pushes the right error class into the queue.\n const timers = setupTimers(\n {\n idleTimeoutMs: opts.idleTimeoutMs,\n totalTimeoutMs: opts.totalTimeoutMs,\n ...(opts.signal !== undefined ? { signal: opts.signal } : {}),\n },\n startMs,\n );\n\n // FR-009 already-aborted contract: when the caller signal is aborted at\n // call time, the iterator's first `.next()` MUST reject synchronously\n // with MultipartAbortError whose `.reason` is the caller-supplied reason\n // verbatim. Push the error into the queue right now so the for-await\n // loop below picks it up immediately. The MultipartAbortError instance\n // was already constructed by setupTimers — we re-use it via abortError().\n if (timers.signal.aborted) {\n const abortErr =\n timers.abortError() ?? new MultipartAbortError(opts.signal?.reason);\n queue.signalError(abortErr);\n abortPushed = true;\n }\n\n // Combined-signal handler — fires when ANY of idle/total/caller-abort\n // trigger AFTER setupTimers returned. Same path used for source 'error'\n // and dicer 'error': push into queue, the for-await loop's\n // item.type === 'error' branch surfaces it, and the iterator's `finally`\n // runs cleanup() which also calls timers.cleanup(). The `cleaned` flag\n // protects against late-fire pushes after cleanup ran first (e.g.\n // success path that ran cleanup then a stray total-timer fire that\n // setupTimers' own cleanup missed by a microtask).\n const onCombinedAbort = (): void => {\n if (cleaned || abortPushed) return;\n abortPushed = true;\n const abortErr =\n timers.abortError() ?? new MultipartAbortError(opts.signal?.reason);\n queue.signalError(abortErr);\n };\n // `{ once: true }` because the combined signal is one-shot — once it\n // fires the controller can't fire again, but we still want to honor the\n // contract that this listener never re-runs across cleanup boundaries.\n if (!timers.signal.aborted) {\n timers.signal.addEventListener('abort', onCombinedAbort, { once: true });\n }\n\n const fireProgress = (): void => {\n const callback = opts.onProgress;\n if (callback === undefined) return;\n const elapsedMs = Date.now() - startMs;\n const rateBps =\n elapsedMs <= 0 ? 0 : Math.round((bytesReceived * 1000) / elapsedMs);\n try {\n callback({ bytes: bytesReceived, elapsedMs, rateBps });\n } catch (err) {\n // FR-017: NEVER silent-catch. Route through the configured logger.\n logger({\n level: 'warn',\n msg: 'multipart: onProgress threw',\n meta: { errSummary: summarizeError(err) },\n });\n }\n };\n\n // Running counter of dicer 'part' emits. Compared against maxParts on\n // every emit; once observed > cap, push the error and destroy the\n // offending stream. Counts every emit including those past the cap so\n // the surfaced `observed` value is accurate.\n let partsObserved = 0;\n\n // 5. Attach ALL listeners BEFORE pipe() (FR-012 / US-011). This is the\n // critical invariant — synchronous early errors from dicer (e.g. a\n // malformed first byte producing an immediate 'error') must surface\n // via the queue rather than being lost.\n const onPart = (partStream: DicerPartStream): void => {\n // Dicer's per-part stream emits a single 'header' event with the full\n // header bag. dicer 0.3.1's HeaderParser delivers this as\n // Record<string, string[]>; the ambient shim documents the broader\n // shape Buffer | Buffer[] | Buffer[][] for forward-compat. The\n // flattenDicerHeaders helper handles every documented variant.\n allPartStreams.add(partStream);\n const partIndex = nextPartIndex++;\n\n // maxParts (NFR-DR-S-012). Trip when the (1-based) emit count\n // exceeds the cap. We push the error and destroy the offending part\n // stream; cleanup() handles the rest of the FR-010 work via the\n // iterator's finally. We deliberately count BEFORE the cap check so\n // observed === maxParts + 1 on the first overflow (the spec's\n // expected value per `cap, observed` shape).\n partsObserved += 1;\n if (partsObserved > maxParts) {\n if (!partStream.destroyed) partStream.destroy();\n queue.signalError(\n new MultipartTooManyPartsError({\n maxParts,\n observed: partsObserved,\n }),\n );\n return;\n }\n\n const headersAccumulator: { value: Record<string, string | undefined> } = {\n value: {},\n };\n\n const onHeader = (raw: unknown): void => {\n // maxHeadersPerPart (count) + maxHeaderBytesPerPart (bytes)\n // (NFR-DR-S-004). Inspect the raw bag BEFORE flattening so\n // a header with N repeated values counts as N headers (matching how\n // dicer/HTTP semantics see the wire). Bytes are measured as\n // `name + ': ' + value + '\\r\\n'` per header line — a deterministic\n // approximation of the on-the-wire framing. We measure on the\n // RAW values (Buffer | Buffer[] | Buffer[][] | string |\n // string[]) without flattening so the count is conservative\n // (under-counts only on shapes the wire wouldn't actually produce).\n // Each header line's wire framing is `name + \": \" + value + \"\\r\\n\"`.\n // We charge 4 bytes for `\": \"` + `\"\\r\\n\"` per logical line (one\n // line per repeated header value). The byte count is approximate\n // — sufficient for the maxHeaderBytesPerPart cap, which is a\n // soft defense against attacker-bombed envelopes. Dicer 0.3.1\n // always delivers each header value as `string[]` (one entry per\n // repeat); the forward-compat broader shape from the ambient\n // shim is normalized to a uniform array via Array.isArray below.\n const bag = raw as DicerHeaderBag | undefined;\n let headerCount = 0;\n let headerBytes = 0;\n if (bag != null) {\n for (const name of Object.keys(bag)) {\n const value = (bag as Record<string, unknown>)[name];\n const nameBytes = Buffer.byteLength(name);\n const values = Array.isArray(value) ? value : [value];\n for (const inner of values) {\n headerCount += 1;\n headerBytes += nameBytes + 4 + measureHeaderValueBytes(inner);\n }\n }\n }\n if (headerCount > maxHeadersPerPart) {\n if (!partStream.destroyed) partStream.destroy();\n queue.signalError(\n new MultipartHeadersTooLargeError({\n limit: 'count',\n partIndex,\n cap: maxHeadersPerPart,\n observed: headerCount,\n }),\n );\n return;\n }\n if (headerBytes > maxHeaderBytesPerPart) {\n if (!partStream.destroyed) partStream.destroy();\n queue.signalError(\n new MultipartHeadersTooLargeError({\n limit: 'bytes',\n partIndex,\n cap: maxHeaderBytesPerPart,\n observed: headerBytes,\n }),\n );\n return;\n }\n\n headersAccumulator.value = flattenDicerHeaders(\n raw as Record<string, unknown> | undefined,\n );\n const headers = headersAccumulator.value;\n const contentType = headers['content-type'] ?? '';\n const contentId = headers['content-id'];\n const contentLengthRaw = headers['content-length'];\n const parsedLen =\n contentLengthRaw !== undefined\n ? Number.parseInt(contentLengthRaw, 10)\n : Number.NaN;\n const contentLength = Number.isFinite(parsedLen) ? parsedLen : undefined;\n\n // maxPartBytes (NFR-DR-S-001). When a cap is configured, wrap the\n // dicer per-part Readable in a counting PassThrough so the\n // public `body` exposed to consumers can observe every byte without\n // racing the consumer's own listener attach (PassThrough buffers\n // upstream writes until a downstream listener is attached, so the\n // consumer's `streamToString` / for-await drain catches every byte\n // even if it attaches several microtasks after we install our pipe).\n // The PassThrough is tracked in `allPartStreams` so cleanup destroys\n // it on every termination path.\n //\n // On overflow we:\n // (a) destroy the upstream partStream to stop dicer pumping,\n // (b) push the typed error into the queue,\n // (c) END the counter cleanly (NOT destroy) so the consumer's\n // drain on `body` resolves naturally — without this, the\n // consumer sees a \"Premature close\" error from their drain\n // BEFORE our queue.signalError gets a chance to surface, and\n // the parser's throw eats our typed error. By ending the\n // counter cleanly, the consumer's for-await of `body`\n // completes; control returns to the outer for-await of the\n // iterator; that .next() resolves with our error.\n let publicBody: Readable = partStream as unknown as Readable;\n if (maxPartBytes !== undefined) {\n const cap = maxPartBytes;\n let partBytesAccumulated = 0;\n let tripped = false;\n const counter = new PassThrough();\n // PassThrough extends Readable, and DicerPartStream extends\n // Readable per the ambient shim, so structural typing accepts\n // the PassThrough directly. The Set is used only for cleanup-\n // time `.destroy()` calls (Node Readable API), so the structural\n // overlap is safe.\n allPartStreams.add(counter);\n // Counter for upstream data. We use a Transform-style approach:\n // intercept partStream's 'data' events with a counting listener\n // and write each chunk to the counter ourselves (no `pipe()`).\n // This avoids the EventEmitter snapshot race where the pipe's\n // internal data listener fires AFTER our trip+unpipe (because\n // the listener array was snapshotted at the start of emit()).\n // We also forward 'end' / 'error' / 'close' explicitly.\n const onUpstreamData = (chunk: Buffer): void => {\n if (tripped) return;\n partBytesAccumulated += chunk.length;\n if (partBytesAccumulated > cap) {\n tripped = true;\n // Stop upstream dicer.\n if (!partStream.destroyed) partStream.destroy();\n // Push the typed error BEFORE we end the counter so the\n // queue ordering puts our error ahead of any post-end\n // iterator.next() resolutions.\n queue.signalError(\n new MultipartPartTooLargeError({\n maxPartBytes: cap,\n partIndex,\n bytesReceived: partBytesAccumulated,\n }),\n );\n // End the counter cleanly so consumer drain resolves.\n if (!counter.writableEnded) counter.end();\n return;\n }\n // Below cap: forward to counter (only if counter is still\n // writable — destroyed/ended counters reject further writes).\n if (counter.writable && !counter.writableEnded) {\n counter.write(chunk);\n }\n };\n const onUpstreamEnd = (): void => {\n if (tripped) return;\n if (!counter.writableEnded) counter.end();\n };\n const onUpstreamError = (err: Error): void => {\n // Forward upstream errors to the counter so a truncated part\n // propagates to the consumer's body drain. The per-part error\n // bridge ALSO routes the error to the queue; we deliberately\n // destroy the counter so the consumer's drain can complete.\n if (!counter.destroyed) counter.destroy(err);\n };\n partStream.on('data', onUpstreamData);\n partStream.on('end', onUpstreamEnd);\n partStream.on('error', onUpstreamError);\n publicBody = counter;\n }\n\n const part: StreamingMultipartPart = {\n index: partIndex,\n boundary,\n headers,\n rawHeaders: Buffer.alloc(0),\n contentType,\n ...(contentId !== undefined ? { contentId } : {}),\n ...(contentLength !== undefined ? { contentLength } : {}),\n // When maxPartBytes is configured, body is the PassThrough that\n // wraps dicer's per-part Readable; otherwise body is dicer's\n // per-part Readable directly. Both expose Node `Readable`.\n body: publicBody,\n };\n\n queue.push({ type: 'part', part });\n // FR-013: fire onProgress per yielded part. The completion-tick\n // assertion is owned by `fetchAndHandleMultipart`.\n fireProgress();\n };\n partStream.once('header', onHeader);\n // Per-part 'error' bridge. If dicer's per-part stream errors out\n // (e.g. truncated part body — \"Part terminated early due to\n // unexpected end of multipart data\"), the error propagates as an\n // unhandled 'error' event on the body Readable and Node terminates\n // the process. Bridge the error into the queue (pre-cleanup) or the\n // logger (post-cleanup) so it becomes observable instead of fatal.\n partStream.on('error', (err: Error) => {\n if (cleaned) {\n logger({\n level: 'warn',\n msg: 'multipart: late part-stream error after generator close',\n meta: { errSummary: summarizeError(err) },\n });\n return;\n }\n queue.signalError(err);\n });\n };\n\n const onFinish = (): void => {\n dicerFinished = true;\n queue.signalEnd();\n };\n\n // FR-011 contract — this listener is INTENTIONALLY retained through the\n // generator's `finally` block. Before cleanup runs it pushes the error\n // into the queue; AFTER cleanup it routes the error observation through\n // the configured logger so late-tick dicer errors never escape as\n // uncaught process exceptions. The `cleaned` flag is the sole\n // discriminator.\n const onDicerError = (err: Error): void => {\n if (cleaned) {\n logger({\n level: 'warn',\n msg: 'multipart: late parser error after generator close',\n meta: { errSummary: summarizeError(err) },\n });\n return;\n }\n queue.signalError(err);\n };\n\n const onSourceData = (chunk: Buffer): void => {\n bytesReceived += chunk.length;\n // FR-DR-A-025: idle timer resets ON EVERY CHUNK from the source. This\n // is the SOLE idle-reset path — onProgress (which fires per-part per\n // FR-013) is too coarse for slow-loris protection.\n timers.resetIdle();\n };\n\n const onSourceError = (err: Error): void => {\n queue.signalError(err);\n };\n\n // FR-022 truncation detector: when the source emits 'end' but dicer has\n // NOT yet emitted 'finish' on a subsequent tick, the response was cut\n // mid-envelope. Push a MultipartTruncatedError so the consumer's next\n // `.next()` sees it. dicer may also emit its own 'error' if the\n // truncation happens mid-part-header; the queue is first-fire-wins, so\n // whichever path fires first is what surfaces. Either outcome runs the\n // same FR-010 cleanup.\n //\n // We defer the dicerFinished check to `setImmediate` so well-formed\n // envelopes (where dicer's 'finish' fires synchronously after source\n // 'end' through the pipe machinery) do not race-trip the truncation\n // path. setImmediate runs strictly AFTER any pending I/O and any\n // already-scheduled process.nextTick / promise microtasks — long enough\n // for dicer's internal end-of-stream tick to land.\n const onSourceEnd = (): void => {\n setImmediate(() => {\n if (dicerFinished) return;\n if (cleaned) return;\n queue.signalError(new MultipartTruncatedError(bytesReceived));\n });\n };\n\n dicer.on('part', onPart);\n dicer.on('finish', onFinish);\n dicer.on('error', onDicerError);\n source.on('data', onSourceData);\n source.on('error', onSourceError);\n source.on('end', onSourceEnd);\n\n // 6. Pipe AFTER all listeners are attached (FR-012).\n source.pipe(dicer);\n\n // FR-010 cleanup function — idempotent (`cleaned` guard). Runs from the\n // generator's `finally` on every termination path (success, caller\n // `break`, parser/source/dicer error, timeout/abort). The function is\n // intentionally synchronous and total — no awaits, no thrown errors —\n // because the cleanup contract demands it run unconditionally and\n // exactly once.\n const cleanup = (): void => {\n if (cleaned) return;\n cleaned = true;\n\n // (1) Remove the listeners we attached to the source. Removing by\n // reference lets us coexist with any listeners the caller (or\n // downstream layers) may have attached.\n source.off('data', onSourceData);\n source.off('error', onSourceError);\n source.off('end', onSourceEnd);\n\n // (2) Try to unpipe — wrap in a logged catch (FR-017 silent-catch\n // replacement site, architecture.md §7).\n try {\n source.unpipe(dicer);\n } catch (err) {\n logger({\n level: 'warn',\n msg: 'multipart: unpipe failed during cleanup',\n meta: { errSummary: summarizeError(err) },\n });\n }\n\n // (3) Destroy the source if it isn't already done. `destroy()` is\n // idempotent on Node Readable, but the `.destroyed` short-circuit\n // keeps the call site free of redundant work.\n if (!source.destroyed) {\n source.destroy();\n }\n\n // (4) Drain unyielded parts and destroy each one's body. This is the\n // silent-leak BRIEF flags (architecture.md §5.3): dicer's per-part\n // Readables hold buffered chunks and are not GC-eligible until\n // destroyed. If the consumer broke out of the for-await loop,\n // every still-pending part is leaked unless we destroy here.\n for (const part of queue.drainPendingParts()) {\n // Cast: StreamingMultipartPart.body is typed as Node Readable, which\n // exposes `.destroy()` directly.\n part.body.destroy();\n }\n // Belt-and-suspenders: also destroy any per-part Readable that dicer\n // emitted on 'part' but that never made it into the queue (i.e. it\n // never fired 'header' before cleanup ran — happens on synchronous\n // termination paths like an immediate source error).\n for (const partStream of allPartStreams) {\n if (!partStream.destroyed) partStream.destroy();\n }\n allPartStreams.clear();\n\n // (5) Remove dicer 'part' and 'finish' listeners. INTENTIONALLY do NOT\n // remove dicer's 'error' listener — that's the FR-011 contract.\n // The retained listener checks the `cleaned` flag (set above) to\n // route late-tick errors through `logger.warn` instead of trying\n // to push into the now-closed queue.\n dicer.off('part', onPart);\n dicer.off('finish', onFinish);\n // dicer.off('error', onDicerError); ← deliberately left attached\n\n // (6) Cancel idle/total timers and detach the caller-signal listener\n // (Layer C). timers.cleanup() is itself idempotent. We also remove\n // our combined-signal listener so we don't accumulate references\n // across operations (the controller is operation-scoped so this\n // is belt-and-suspenders, but cheap and correct).\n timers.signal.removeEventListener('abort', onCombinedAbort);\n timers.cleanup();\n };\n\n // 7. Yield loop. The `finally` runs on EVERY exit path (success /\n // caller break / yield throw / item-error throw / source destroy)\n // so cleanup is centralized.\n try {\n while (true) {\n const item = await queue.next();\n if (item.type === 'end') return;\n if (item.type === 'error') throw item.err;\n yield item.part;\n }\n } finally {\n cleanup();\n }\n}\n","/**\n * `fetchAndHandleMultipart` — Layer B (fetch orchestration). FR-005.\n *\n * The wrapper:\n * 1. Validates the option bag synchronously: `parser` presence,\n * `validatePositiveTimeout` on both timeouts, FR-024 `fetchInit.signal`\n * ban, and FR-009 already-aborted short-circuit. ALL of these run\n * BEFORE `fetch` is invoked — T-013 spies on `globalThis.fetch` and\n * asserts it was never called when the caller's signal is pre-aborted.\n * 2. Calls `fetch(url, { ...fetchInit, signal: options.signal })` —\n * forwards the caller's `AbortSignal` to fetch directly so a network-\n * time abort cancels the request before any bytes flow.\n * 3. Validates the response Content-Type (FR-021) case-insensitively\n * against `multipart/related` BEFORE constructing dicer. The offending\n * Content-Type is sanitized via `truncateForErrorEmbed` per\n * NFR-DR-S-006. T-035 asserts the dicer-activity harness sees zero\n * Dicer instances on this path.\n * 4. Captures `status` + `headers` BEFORE consuming the body\n * (FR-DR-A-029 — the Response is gone after iteration).\n * 5. Forwards `idleTimeoutMs` / `totalTimeoutMs` / `signal` / `onProgress`\n * / `logger` / cap fields DOWN to `parseMultipartRelated`. Per\n * FR-DR-A-026 timer ownership lives in Layer A; Layer B does NOT\n * construct a TimerState.\n * 6. Drives the for-await loop, awaits `options.parser(part)` per part,\n * collects non-undefined returns into `parts`.\n * 7. Resolves with `{ parts, bytes, elapsedMs, status, headers }` per\n * FR-DR-A-029. The previously-considered `response: Response` field\n * is REMOVED at the type level.\n *\n * Bytes-tracking strategy: the wrapper supplies its own `onProgress`\n * callback to `parseMultipartRelated` regardless of whether the caller\n * supplied one. The internal callback captures `lastBytes` and (when the\n * caller supplied one) forwards the snapshot to the caller — wrapping the\n * caller's call in a try/catch that routes via the resolved logger\n * (FR-017 silent-catch replacement). After the loop ends, the wrapper\n * fires ONE final completion-tick `onProgress` call (T-014 full\n * assertion) with the final `bytes` / `elapsedMs` / `rateBps`.\n */\n\nimport { MultipartAbortError } from './errors.js';\nimport { defaultLogger } from './internal/default-logger.js';\nimport {\n summarizeError,\n truncateForErrorEmbed,\n} from './internal/format-error-embed.js';\nimport { validatePositiveTimeout } from './internal/validate-timeout.js';\nimport { parseMultipartRelated } from './parse-multipart-related.js';\nimport type {\n Logger,\n MultipartFetchResult,\n MultipartHandlerOptions,\n ParseMultipartOptions,\n ProgressSnapshot,\n StreamingMultipartPart,\n} from './types.js';\n\n/**\n * Wrap `fetch` end-to-end: call, validate Content-Type, parse multipart,\n * route each part through the caller's `parser`, and resolve with\n * {@link MultipartFetchResult} (FR-DR-A-029 — `{ parts, bytes, elapsedMs,\n * status, headers }`; NO `response` field).\n *\n * @param url - Forwarded to `fetch`. `URL` is supported for parity with\n * `fetch` itself.\n * @param options - {@link MultipartHandlerOptions}. `parser`,\n * `idleTimeoutMs`, and `totalTimeoutMs` are REQUIRED (FR-005 / FR-006).\n * The static `Omit<RequestInit, 'signal'>` on `options.fetchInit` enforces\n * the FR-024 signal-ban at compile time; the runtime check below catches\n * dynamic spreads / `as` consumers.\n * @returns A `Promise<MultipartFetchResult<T>>` that resolves once every\n * part has been processed.\n *\n * @throws {TypeError} `multipart: options.parser is required` when\n * `options.parser` is missing or not a function.\n * @throws {TypeError} from `validatePositiveTimeout` when either timeout\n * is missing/invalid (FR-006 / NFR-DR-S-009 — message mentions Node's\n * `setTimeout` clamping).\n * @throws {Error} `multipart: pass signal via options.signal — fetchInit.signal\n * is reserved for internal use` when `options.fetchInit.signal` is set\n * (FR-024).\n * @throws {MultipartAbortError} synchronously when `options.signal?.aborted`\n * is `true` at call time (FR-009). `fetch` is NEVER invoked on this path\n * — T-013 spies on `globalThis.fetch` to verify.\n * @throws {Error} `multipart: response Content-Type is not multipart/related;\n * got <actual>` when the response Content-Type doesn't start with\n * `multipart/related` (case-insensitive) (FR-021). Dicer is NEVER\n * constructed on this path — T-035 asserts via the dicer-activity\n * harness.\n * @throws Any error from `parseMultipartRelated` (idle/total timeout,\n * abort mid-stream, truncation, source error, dicer error, cap overflow).\n * @throws Any error from `options.parser`.\n *\n * @example\n * const result = await fetchAndHandleMultipart(url, {\n * idleTimeoutMs: 10_000,\n * totalTimeoutMs: 60_000,\n * parser: async (part) => streamToString(part.body),\n * });\n * console.log(result.parts.length, 'parts in', result.elapsedMs, 'ms');\n */\nexport async function fetchAndHandleMultipart<T>(\n url: string | URL,\n options: MultipartHandlerOptions<T>,\n): Promise<MultipartFetchResult<T>> {\n // 1. Synchronous validation (FR-005 / FR-006 / FR-024 / FR-009).\n // These all run BEFORE fetch is invoked — T-013 spy contract.\n if (typeof options.parser !== 'function') {\n throw new TypeError('multipart: options.parser is required');\n }\n validatePositiveTimeout('idleTimeoutMs', options.idleTimeoutMs);\n validatePositiveTimeout('totalTimeoutMs', options.totalTimeoutMs);\n\n // FR-024 runtime check. The static `Omit<RequestInit, 'signal'>` on\n // MultipartHandlerOptions.fetchInit handles the type-level rejection;\n // the runtime check catches dynamic spreads or `as any` consumers who\n // launder past TypeScript.\n if (\n options.fetchInit !== undefined &&\n 'signal' in options.fetchInit &&\n (options.fetchInit as { signal?: unknown }).signal !== undefined\n ) {\n throw new Error(\n 'multipart: pass signal via options.signal — fetchInit.signal is reserved for internal use',\n );\n }\n\n // FR-009 already-aborted: throw synchronously with the verbatim caller\n // reason (F-S-006). Fetch is NEVER called on this path.\n if (options.signal?.aborted === true) {\n throw new MultipartAbortError(options.signal.reason);\n }\n\n // 2. Logger fallback (FR-018 / JC-2). Resolved once and used at every\n // Layer-B logger site (onProgress catch, completion-tick catch).\n // Note: the SAME logger is forwarded to parseMultipartRelated so\n // Layer A's late-emit / FR-017 sites flow through the caller's\n // pipeline too.\n const logger: Logger = options.logger ?? defaultLogger;\n\n // 3. Wallclock + bytes accumulator. Layer B owns the result wallclock\n // per architecture.md §3 (\"owns wallclock for elapsedMs/bytes\").\n const startMs = Date.now();\n let lastBytes = 0;\n\n // 4. Always supply our own onProgress to parseMultipartRelated so we\n // can capture lastBytes — even when the caller didn't supply one.\n // When they did, forward the snapshot through with a try/catch that\n // routes via the resolved logger (FR-017 silent-catch replacement).\n //\n // NOTE: parse-multipart-related.ts already wraps caller's onProgress\n // in its own try/catch + logger.warn. To avoid a double-log when the\n // caller's callback throws, we DO NOT also catch here — Layer A's\n // catch site is the canonical one. We only forward.\n //\n // Architecture decision: the inner onProgress closure unconditionally\n // captures lastBytes; the call-through to options.onProgress lives\n // inside parseMultipartRelated's fireProgress (which already wraps\n // in try/catch + logger.warn). To get BOTH behaviors (capture bytes\n // AND let Layer A do the catching), we pass a wrapper that captures\n // bytes then defers to options.onProgress directly. Layer A's\n // fireProgress wraps THIS wrapper, so a throw from options.onProgress\n // is caught at the Layer A site and routed through Layer A's logger\n // (which is the same `logger` we resolved above).\n const onProgressForLayerA = (snap: ProgressSnapshot): void => {\n lastBytes = snap.bytes;\n if (options.onProgress !== undefined) {\n options.onProgress(snap);\n }\n };\n\n // 5. fetch — forward fetchInit (without signal — caller's signal is\n // threaded through as our `signal` arg). architecture.md §4.2 step 2\n // explicitly: \"caller signal forwarded raw; timers live in Layer A\".\n //\n // A pre-fetch abort fires `fetch` to reject with an AbortError-\n // flavored DOMException; we surface that as MultipartAbortError if\n // the caller's signal is now aborted (race-safe: the FR-009 check\n // above already short-circuits the synchronously-aborted case).\n let res: Response;\n try {\n // Conditional spread: under exactOptionalPropertyTypes, passing\n // `signal: undefined` to fetch's RequestInit is rejected if the\n // declared type is non-optional. Always include the field — fetch\n // accepts AbortSignal | null | undefined at runtime.\n const init: RequestInit = {\n ...(options.fetchInit ?? {}),\n ...(options.signal !== undefined ? { signal: options.signal } : {}),\n };\n res = await fetch(url, init);\n } catch (err) {\n // If the caller signal aborted during fetch, surface as\n // MultipartAbortError with the caller-verbatim reason (F-S-006).\n // Re-read the signal because TS's narrowing assumes the FR-009\n // already-aborted check above means the signal can't have aborted —\n // but the caller can fire it asynchronously between then and now.\n const sig = options.signal;\n if (sig?.aborted) {\n throw new MultipartAbortError(sig.reason);\n }\n throw err;\n }\n\n // 6. FR-021 — Content-Type validation BEFORE dicer construction. The\n // test-harness asserts `dicer-activity.dicerInstances().length === 0`\n // on this path (T-035). Case-insensitive prefix check per FR-021;\n // the offending value is run through `truncateForErrorEmbed` (the\n // full sanitizer per NFR-DR-S-006).\n const contentType = res.headers.get('content-type') ?? '';\n if (!contentType.toLowerCase().startsWith('multipart/related')) {\n throw new Error(\n `multipart: response Content-Type is not multipart/related; got ${truncateForErrorEmbed(contentType)}`,\n );\n }\n\n // 7. Capture status + headers BEFORE consuming the body (FR-DR-A-029 —\n // the Response is gone after iteration). The `headers` reference is\n // the live Headers object; callers who need a frozen snapshot can\n // construct `new Headers(result.headers)`.\n const status = res.status;\n const headers = res.headers;\n\n // 8. Drive parseMultipartRelated. Forward EVERYTHING down (FR-DR-A-026\n // — Layer A owns timers). Conditional spreads honor\n // exactOptionalPropertyTypes for fields the caller may have omitted.\n const parseOpts: ParseMultipartOptions = {\n idleTimeoutMs: options.idleTimeoutMs,\n totalTimeoutMs: options.totalTimeoutMs,\n onProgress: onProgressForLayerA,\n ...(options.signal !== undefined ? { signal: options.signal } : {}),\n ...(options.logger !== undefined ? { logger: options.logger } : {}),\n ...(options.maxPartBytes !== undefined\n ? { maxPartBytes: options.maxPartBytes }\n : {}),\n ...(options.maxParts !== undefined ? { maxParts: options.maxParts } : {}),\n ...(options.maxHeadersPerPart !== undefined\n ? { maxHeadersPerPart: options.maxHeadersPerPart }\n : {}),\n ...(options.maxHeaderBytesPerPart !== undefined\n ? { maxHeaderBytesPerPart: options.maxHeaderBytesPerPart }\n : {}),\n };\n\n const parts: T[] = [];\n for await (const part of parseMultipartRelated(res, parseOpts)) {\n const value = await invokeParser(options.parser, part);\n if (value !== undefined) {\n parts.push(value);\n }\n // FR-014: when the parser returns undefined OR the parser returned a\n // value without touching part.body, the LIBRARY drains the body so\n // dicer can advance its state machine to the next part. Without this,\n // a parser that always returns undefined hangs the multipart stream\n // (dicer's per-part Readable buffers indefinitely; the closing\n // boundary never makes it through; the truncation detector\n // mis-fires). Draining is a no-op if the parser already drained.\n if (!part.body.destroyed && part.body.readable) {\n // Drain by iterating to end. The for-await is the canonical\n // drain idiom for a Node Readable in flowing mode.\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n for await (const _chunk of part.body) {\n // discard\n }\n }\n }\n\n // 9. Final completion-tick onProgress (T-014 full assertion). Caller\n // exceptions are caught and routed via the resolved logger — same\n // contract as the per-part site in Layer A.\n const elapsedMs = Date.now() - startMs;\n if (options.onProgress !== undefined) {\n const rateBps =\n elapsedMs <= 0 ? 0 : Math.round((lastBytes * 1000) / elapsedMs);\n try {\n options.onProgress({ bytes: lastBytes, elapsedMs, rateBps });\n } catch (err) {\n // FR-017 silent-catch replacement — onProgress completion-tick\n // site lives at Layer B (the per-yielded-part site lives at Layer\n // A's fireProgress). Both flow through the same logger contract.\n logger({\n level: 'warn',\n msg: 'multipart: onProgress threw on completion tick',\n meta: { errSummary: summarizeError(err) },\n });\n }\n }\n\n // 10. Resolve with the FR-DR-A-029 shape — no response field.\n return { parts, bytes: lastBytes, elapsedMs, status, headers };\n}\n\n/**\n * Tiny indirection so the parser invocation is its own statement (helps\n * the line / branch coverage attribution map cleanly to a single site\n * even when the parser returns a non-Promise).\n *\n * @internal\n */\nasync function invokeParser<T>(\n parser: (part: StreamingMultipartPart) => Promise<T | undefined>,\n part: StreamingMultipartPart,\n): Promise<T | undefined> {\n return parser(part);\n}\n","/**\n * Stream-collection helpers (FR-015 + NFR-DR-S-002).\n *\n * Both helpers drain a Node `Readable` to a single value. They are\n * convenience for small parts (text / binary metadata, manifests) — for\n * larger payloads callers SHOULD pipe directly to a sink instead of\n * buffering.\n *\n * The optional `options.maxBytes` cap (NFR-DR-S-002): when set and the\n * accumulated bytes exceed the cap, the source `Readable` is destroyed and\n * the promise rejects with a clear `Error`. Existing callers that pass only\n * `(readable)` or `(readable, encoding)` see no behavior change.\n *\n * Per kiln/spec/api.md §3 + §4, `streamToBuffer`'s `options` parameter is the\n * second positional arg; `streamToString`'s `options` parameter is the\n * THIRD positional arg (after the legacy `encoding?`). The cap-overflow\n * error is a generic `Error` (NOT a custom class) because these are\n * utilities — the parsing-domain error classes are reserved for\n * `parseMultipartRelated`.\n */\n\nimport type { Readable } from 'node:stream';\n\n/**\n * Options bag accepted by both {@link streamToString} and\n * {@link streamToBuffer}. Currently exposes only `maxBytes` (NFR-DR-S-002);\n * forward-compat-shaped as an interface so additional cap fields can land\n * without breaking callers.\n */\nexport interface StreamCollectOptions {\n /**\n * Soft cap on accumulated input bytes. When set and the source produces\n * more than this many bytes total, the source `Readable` is destroyed\n * and the promise rejects with a clear `Error`. Omit (default) for no\n * cap. Must be a positive finite integer when set.\n */\n readonly maxBytes?: number | undefined;\n}\n\n/**\n * Drain a Node `Readable` to a single string.\n *\n * Calls `Buffer.from(chunk).toString(encoding)` for non-Buffer chunks,\n * defending against object-mode-ish streams that emit strings already.\n *\n * @param readable - Source stream. Must end (rejection on `'error'`).\n * @param encoding - Optional `BufferEncoding` (defaults to `'utf8'`).\n * @param options - Optional {@link StreamCollectOptions}. When\n * `options.maxBytes` is set and accumulated bytes exceed the cap, the\n * source is destroyed and the promise rejects (NFR-DR-S-002).\n * @returns A `Promise<string>` resolving to the full decoded contents.\n * @throws Any `'error'` event from `readable` rejects the promise with that\n * error.\n * @throws {Error} `streamToString: input exceeded maxBytes (<n>)` when\n * `options.maxBytes` is set and exceeded. The source `readable` is\n * destroyed before the promise rejects.\n *\n * @example\n * const text = await streamToString(part.body, 'utf8');\n * console.log(JSON.parse(text));\n *\n * @example\n * // With a 1 MiB cap to defend against attacker-controlled part bodies:\n * const text = await streamToString(part.body, 'utf8', { maxBytes: 1_048_576 });\n */\nexport function streamToString(\n readable: Readable,\n encoding: BufferEncoding = 'utf8',\n options: StreamCollectOptions = {},\n): Promise<string> {\n return new Promise<string>((resolve, reject) => {\n const chunks: Buffer[] = [];\n let total = 0;\n const cap = options.maxBytes;\n const onData = (chunk: Buffer | string): void => {\n const buf =\n typeof chunk === 'string' ? Buffer.from(chunk, encoding) : chunk;\n total += buf.length;\n // NFR-DR-S-002 cap check. We compare AFTER accumulating the current\n // chunk's bytes — a cap of N rejects on the chunk that takes the\n // total over N (i.e. strict `>`). The source is destroyed before\n // we reject so any further 'data' events are suppressed.\n if (cap !== undefined && total > cap) {\n cleanup();\n readable.destroy();\n reject(\n new Error(`streamToString: input exceeded maxBytes (${String(cap)})`),\n );\n return;\n }\n chunks.push(buf);\n };\n const onError = (err: Error): void => {\n cleanup();\n reject(err);\n };\n const onEnd = (): void => {\n cleanup();\n resolve(Buffer.concat(chunks).toString(encoding));\n };\n const cleanup = (): void => {\n readable.off('data', onData);\n readable.off('error', onError);\n readable.off('end', onEnd);\n };\n readable.on('data', onData);\n readable.once('error', onError);\n readable.once('end', onEnd);\n });\n}\n\n/**\n * Drain a Node `Readable` to a single `Buffer`.\n *\n * @param readable - Source stream.\n * @param options - Optional {@link StreamCollectOptions}. When\n * `options.maxBytes` is set and accumulated bytes exceed the cap, the\n * source is destroyed and the promise rejects (NFR-DR-S-002).\n * @returns A `Promise<Buffer>`. Zero-byte streams resolve to\n * `Buffer.alloc(0)`.\n * @throws Any `'error'` event from `readable` rejects the promise.\n * @throws {Error} `streamToBuffer: input exceeded maxBytes (<n>)` when\n * `options.maxBytes` is set and exceeded. The source `readable` is\n * destroyed before the promise rejects.\n *\n * @example\n * const buf = await streamToBuffer(part.body);\n * await fs.writeFile(`/tmp/${part.contentId ?? 'part'}.bin`, buf);\n *\n * @example\n * // With a 5 MiB cap to defend against attacker-controlled part bodies:\n * const buf = await streamToBuffer(part.body, { maxBytes: 5_242_880 });\n */\nexport function streamToBuffer(\n readable: Readable,\n options: StreamCollectOptions = {},\n): Promise<Buffer> {\n return new Promise<Buffer>((resolve, reject) => {\n const chunks: Buffer[] = [];\n let total = 0;\n const cap = options.maxBytes;\n const onData = (chunk: Buffer | string): void => {\n const buf = typeof chunk === 'string' ? Buffer.from(chunk) : chunk;\n total += buf.length;\n if (cap !== undefined && total > cap) {\n cleanup();\n readable.destroy();\n reject(\n new Error(`streamToBuffer: input exceeded maxBytes (${String(cap)})`),\n );\n return;\n }\n chunks.push(buf);\n };\n const onError = (err: Error): void => {\n cleanup();\n reject(err);\n };\n const onEnd = (): void => {\n cleanup();\n resolve(chunks.length === 0 ? Buffer.alloc(0) : Buffer.concat(chunks));\n };\n const cleanup = (): void => {\n readable.off('data', onData);\n readable.off('error', onError);\n readable.off('end', onEnd);\n };\n readable.on('data', onData);\n readable.once('error', onError);\n readable.once('end', onEnd);\n });\n}\n"]}
|