@dynamic-entity/server 2.1.1 → 2.3.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/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/limits.ts","../src/errors.ts","../src/bytes.ts","../src/cell-text.ts","../src/guard-zip.ts","../src/xlsx-source.ts","../src/read-sheet.ts","../src/run-import.ts","../src/write-template.ts"],"names":["zlib","Readable","ExcelJS","createCsvReader","padRow","validateMappingPlan","applyMapping","deriveImportColumns","suggestMapping","toCsv","escapeFormula"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * limits.ts — every bound this package enforces, and its default.\n *\n * **Every limit has a finite default.** A limit that is only a limit when a consumer\n * remembers to set one is documentation, not a guard, and this is the first\n * attacker-reachable code in the repository. A consumer who wants more raises the number;\n * nobody has to know the number exists to be protected by it.\n *\n * Each is enforced **during** streaming rather than after. A check that runs once the file is\n * in memory has already lost the thing it was checking.\n */\n\nexport interface ImportLimits {\n // ── The upload itself ──────────────────────────────────────────────────────\n\n /** Total bytes accepted from the request body. Counted as they arrive. */\n maxBytes: number;\n /** Multipart fields accepted, so a body of ten thousand tiny parts is not a parse loop. */\n maxFields: number;\n /** Bytes accepted in one non-file field — in practice the JSON `plan`. */\n maxFieldBytes: number;\n /** Bytes accepted in a field *name*, which is also attacker-chosen. */\n maxFieldNameBytes: number;\n /** Files accepted. One import is one file; more is either a mistake or an attack. */\n maxFiles: number;\n\n // ── The sheet ─────────────────────────────────────────────────────────────\n\n /** Data rows read. The header is not one of them. */\n maxRows: number;\n /** Columns read from the header row, and the width any row may reach. */\n maxColumns: number;\n /**\n * Characters in one cell.\n *\n * This is also the bound on `validators.pattern`: a config-supplied regex with\n * catastrophic backtracking is CPU-bound in the length of what it is run against, and on a\n * server that input is attacker-chosen. See SECURITY.md.\n */\n maxCellLength: number;\n\n // ── The zip an .xlsx actually is ──────────────────────────────────────────\n\n /** Total inflated bytes across every entry, checked *as they inflate*. */\n maxUncompressedBytes: number;\n /** Inflated-to-compressed ratio at which an entry is refused. */\n maxCompressionRatio: number;\n /** Entries in the archive. A workbook has tens; a bomb has thousands. */\n maxZipEntries: number;\n\n // ── The run ───────────────────────────────────────────────────────────────\n\n /**\n * Rows handed to `applyMapping`, and therefore to `onBatch`, at a time.\n *\n * Peak memory is a function of this number and not of the file's size. That is the entire\n * reason this package exists.\n */\n batchSize: number;\n /**\n * Row errors **retained**. The count and the truncation flag are always exact.\n *\n * A file where every row fails accumulates one error object per failing row, and fifty\n * thousand of those is the same unbounded growth as holding the file — the failure this\n * package exists to avoid, arrived at from the other direction.\n *\n * Lowering it costs a user *reasons*, never *counts*: `errorCount` and `failed` are tallied\n * as rows go past and do not depend on what was kept.\n */\n maxReportedErrors: number;\n /** Rows a preview returns as a sample. */\n sampleRows: number;\n\n // ── The clock ─────────────────────────────────────────────────────────────\n\n /** Milliseconds of silence on the request body before it is abandoned. Slowloris. */\n idleTimeoutMs: number;\n /** Milliseconds a whole request may take, however chatty it is. */\n totalTimeoutMs: number;\n}\n\n/**\n * Sized for \"a large legitimate export\", not for \"the largest thing imaginable\".\n *\n * 10 MB of xlsx is comfortably the fifty-thousand-row workbook that motivated the package;\n * `maxRows` is above it so the byte limit is the one a user meets first, and the row limit is\n * the backstop for a file that is mostly empty cells.\n */\nexport const DEFAULT_LIMITS: ImportLimits = {\n maxBytes: 10 * 1024 * 1024,\n maxFields: 16,\n maxFieldBytes: 1024 * 1024,\n maxFieldNameBytes: 200,\n maxFiles: 1,\n\n maxRows: 200_000,\n maxColumns: 512,\n maxCellLength: 32_768,\n\n maxUncompressedBytes: 200 * 1024 * 1024,\n maxCompressionRatio: 200,\n maxZipEntries: 512,\n\n batchSize: 500,\n maxReportedErrors: 200,\n sampleRows: 5,\n\n idleTimeoutMs: 30_000,\n totalTimeoutMs: 10 * 60_000,\n};\n\n/**\n * Fill in what a caller left out.\n *\n * A non-finite or non-positive override falls back to the default rather than being accepted:\n * `maxBytes: 0` from a mis-parsed environment variable would otherwise disable every upload,\n * and `maxBytes: Infinity` would disable the guard — the two ways a configuration mistake\n * turns a limit off, both of which look deliberate in a diff.\n */\nexport function resolveLimits(overrides?: Partial<ImportLimits>): ImportLimits {\n const out = { ...DEFAULT_LIMITS };\n if (!overrides) return out;\n\n for (const key of Object.keys(DEFAULT_LIMITS) as (keyof ImportLimits)[]) {\n const value = overrides[key];\n if (typeof value === 'number' && Number.isFinite(value) && value > 0) {\n out[key] = Math.floor(value);\n }\n }\n return out;\n}\n","/**\n * errors.ts — one error type, a closed set of codes, and an envelope that says nothing else.\n *\n * **Information disclosure is a threat, so the envelope is the guard.** A stack trace tells an\n * attacker the library versions in use and the filesystem layout of the host; a parser's own\n * message tells them which parser. So a response carries a machine-readable `code` and a\n * message this package wrote, and nothing that came from below it. Whatever the real cause\n * was stays on the server, reachable through `cause` for the consumer's logger.\n */\n\n/**\n * Why a request failed, in a form a client can branch on.\n *\n * Closed on purpose: a client that has to match on message text is a client that breaks when\n * the text improves, and the wire shape is shared with `ngx-dynamic-entity`.\n */\nexport type ImportErrorCode =\n /** No file part in the multipart body. */\n | 'NO_FILE'\n /** More file parts than `limits.maxFiles`. */\n | 'TOO_MANY_FILES'\n /** The body exceeded `limits.maxBytes`, or a field exceeded its own bound. */\n | 'TOO_LARGE'\n /** The bytes are not a format this package reads. Decided by content, not by filename. */\n | 'UNSUPPORTED_FORMAT'\n /** The bytes claim a format and are not valid in it. */\n | 'MALFORMED_FILE'\n /** A zip that inflates out of proportion to its size, or has too many entries. */\n | 'ARCHIVE_REFUSED'\n /** Rows, columns or one cell past the configured bound. */\n | 'SHEET_TOO_LARGE'\n /** No config is registered under the requested entity. */\n | 'UNKNOWN_ENTITY'\n /** The `plan` field is missing, is not JSON, or does not describe this config. */\n | 'INVALID_PLAN'\n /** The request took longer than `limits.totalTimeoutMs`, or stalled past the idle bound. */\n | 'TIMEOUT'\n /** The consumer's `onImport` threw. Nothing about *why* crosses the wire. */\n | 'IMPORT_FAILED'\n /** Anything else. Deliberately the least informative code there is. */\n | 'INTERNAL';\n\n/** The HTTP status each code answers with. Kept here so a route never has to decide. */\nconst STATUS: Record<ImportErrorCode, number> = {\n NO_FILE: 400,\n TOO_MANY_FILES: 400,\n TOO_LARGE: 413,\n UNSUPPORTED_FORMAT: 415,\n MALFORMED_FILE: 400,\n ARCHIVE_REFUSED: 400,\n SHEET_TOO_LARGE: 413,\n UNKNOWN_ENTITY: 404,\n INVALID_PLAN: 400,\n TIMEOUT: 408,\n IMPORT_FAILED: 500,\n INTERNAL: 500,\n};\n\n/**\n * What a failing route sends.\n *\n * `details` is present only for `INVALID_PLAN`, and carries `validateMappingPlan`'s own\n * problems — those describe the client's own plan against a config the client already has,\n * so they disclose nothing the caller did not send.\n */\nexport interface ImportErrorBody {\n error: {\n code: ImportErrorCode;\n message: string;\n details?: unknown;\n };\n}\n\nexport class ImportError extends Error {\n readonly code: ImportErrorCode;\n readonly status: number;\n readonly details?: unknown;\n\n constructor(code: ImportErrorCode, message: string, options: { cause?: unknown; details?: unknown } = {}) {\n super(message);\n this.name = 'ImportError';\n this.code = code;\n this.status = STATUS[code] ?? 500;\n this.details = options.details;\n if (options.cause !== undefined) (this as { cause?: unknown }).cause = options.cause;\n }\n}\n\n/**\n * Turn anything thrown into a response body.\n *\n * An `ImportError` says what it says. **Everything else becomes `INTERNAL` with a fixed\n * message** — that is the whole point, and the reason this is a function rather than a\n * `catch` block per route. A parser that throws `ENOENT: open '/srv/app/tmp/…'` must not be\n * the thing that answers the request.\n */\nexport function toErrorBody(error: unknown): { status: number; body: ImportErrorBody } {\n if (error instanceof ImportError) {\n return {\n status: error.status,\n body: {\n error: {\n code: error.code,\n message: error.message,\n ...(error.details === undefined ? {} : { details: error.details }),\n },\n },\n };\n }\n\n return {\n status: 500,\n body: { error: { code: 'INTERNAL', message: 'The import could not be completed.' } },\n };\n}\n","/**\n * bytes.ts — the three things every reader here needs from a byte stream.\n *\n * Deliberately framework-neutral: a `ByteSource` is anything you can `for await` bytes out of,\n * which a Node `Readable`, a `Buffer`, and a hand-rolled async generator all are. Nothing in\n * this file knows what a request is.\n */\n\nimport { ImportError } from './errors';\n\n/** Anything bytes can be pulled from — a request, a file handle, a buffer, a test fixture. */\nexport type ByteSource = AsyncIterable<Uint8Array> | Iterable<Uint8Array> | Uint8Array;\n\n/** Normalise the three shapes above into one async iterable. */\nexport async function* toByteStream(source: ByteSource): AsyncGenerator<Uint8Array> {\n if (source instanceof Uint8Array) {\n yield source;\n return;\n }\n if (Symbol.asyncIterator in source) {\n for await (const chunk of source as AsyncIterable<Uint8Array>) yield asBytes(chunk);\n return;\n }\n for (const chunk of source as Iterable<Uint8Array>) yield asBytes(chunk);\n}\n\n/** A Node stream in object-ish mode can hand back a string; treat it as UTF-8 bytes. */\nfunction asBytes(chunk: Uint8Array | string): Uint8Array {\n return typeof chunk === 'string' ? Buffer.from(chunk, 'utf8') : chunk;\n}\n\n/**\n * Refuse the upload at the byte that crosses the line, not once it has all arrived.\n *\n * Counting after the body is buffered is the guard that has already lost: the memory it was\n * protecting is spent by the time it runs. This throws mid-stream, and the caller destroys\n * the source — so a hostile 4 GB body costs `maxBytes` plus one chunk.\n */\nexport async function* limitBytes(\n source: AsyncIterable<Uint8Array>,\n maxBytes: number,\n): AsyncGenerator<Uint8Array> {\n let seen = 0;\n for await (const chunk of source) {\n seen += chunk.byteLength;\n if (seen > maxBytes) {\n throw new ImportError('TOO_LARGE', `The file is larger than the ${maxBytes} byte limit.`);\n }\n yield chunk;\n }\n}\n\n/**\n * Take the first `n` bytes without consuming them.\n *\n * Format is decided by what a file *is* rather than by what its name claims, which means\n * looking at its first four bytes before choosing a reader — and then handing those same\n * bytes to the reader that was chosen. Returning the head separately and re-yielding it is\n * the whole trick; buffering the file to seek back to zero is the thing being avoided.\n */\nexport async function peek(\n source: AsyncIterable<Uint8Array>,\n n: number,\n): Promise<{ head: Uint8Array; stream: AsyncGenerator<Uint8Array> }> {\n const iterator = source[Symbol.asyncIterator]();\n const taken: Uint8Array[] = [];\n let size = 0;\n\n while (size < n) {\n const next = await iterator.next();\n if (next.done) break;\n taken.push(next.value);\n size += next.value.byteLength;\n }\n\n const head = Buffer.concat(taken.map(c => Buffer.from(c.buffer, c.byteOffset, c.byteLength)));\n\n async function* replay(): AsyncGenerator<Uint8Array> {\n for (const chunk of taken) yield chunk;\n // `iterator`, not `source`: an async iterator is consumed once, and starting a second\n // `for await` over the source would either replay nothing or start a second read.\n for (let next = await iterator.next(); !next.done; next = await iterator.next()) {\n yield next.value;\n }\n }\n\n return { head: head.subarray(0, n), stream: replay() };\n}\n\n/** Best-effort close of whatever the bytes came from, once a guard has decided to stop. */\nexport function destroySource(source: unknown): void {\n const candidate = source as { destroy?: (error?: Error) => void; return?: () => unknown };\n try {\n if (typeof candidate?.destroy === 'function') candidate.destroy();\n else if (typeof candidate?.return === 'function') candidate.return();\n } catch {\n // Already closed, or never closeable. There is nothing useful to do about it here, and\n // throwing from a cleanup path would replace the real error with this one.\n }\n}\n","/**\n * cell-text.ts — how a typed cell is rendered when text is what has to cross the wire.\n *\n * Its own module because both the reader and the preview need it, and a second copy would be\n * a second place for the date rule to be wrong.\n */\n\n/**\n * Render a cell as the text a mapping screen shows.\n *\n * It mirrors `coerceTypedCell`'s date rule on purpose. A preview sample crosses the wire as\n * text and the browser then runs `coerceCell` over that text, so if this rendered a `Date`\n * with `String()` the preview would show — and the browser would coerce — a different day\n * from the one the import will store. That is a preview lying about the only thing it exists\n * to show, and it would be wrong by exactly one day, silently, only for users at a negative\n * UTC offset. `run-import.spec.ts` asserts the two agree rather than trusting this comment.\n */\nexport function sampleText(raw: unknown): string {\n if (raw === null || raw === undefined) return '';\n if (raw instanceof Date) {\n if (Number.isNaN(raw.getTime())) return '';\n const iso = raw.toISOString();\n // Midnight UTC is how a spreadsheet stores a calendar date, so nothing is lost by\n // dropping a time that was never meant. Any other instant keeps its whole ISO form.\n return raw.getTime() % 86_400_000 === 0 ? iso.slice(0, 10) : iso;\n }\n return typeof raw === 'string' ? raw : String(raw);\n}\n","/**\n * guard-zip.ts — open the archive ourselves, and hand the parser only what it should see.\n *\n * An `.xlsx` is a zip, and a zip is the one upload format where the bytes on the wire say\n * nothing about the work they cause. Ten kilobytes can inflate to ten gigabytes. `maxBytes`\n * bounds what arrives and is therefore the wrong guard entirely for this.\n *\n * **Why the archive is rebuilt rather than passed through.** Two reasons, and the second was a\n * surprise:\n *\n * 1. exceljs inflates internally and reports nothing while it does, so a limit applied to its\n * output is applied after the memory it was protecting has been spent. The inflated size is\n * knowable early only here, so this inflates every entry to *measure* it and refuses at the\n * byte that crosses a bound.\n * 2. **exceljs's streaming reader loses entries when a worksheet precedes the shared strings.**\n * It defers such a worksheet to a temp file, and the archive's entry stream then ends at\n * that entry — `xl/workbook.xml`, which exceljs's own writer puts *last*, never arrives,\n * and the reader throws `Cannot read properties of undefined (reading 'sheets')`. Verified\n * by replicating its parse loop: sixteen entries became eight, on two runs out of three.\n * Reordering the archive so the strings, the relationships and the workbook all precede the\n * worksheet avoids the deferral entirely — thirty-two runs, no failures, chunked and whole.\n *\n * Rebuilding also means the parser sees **only the parts an import needs**. The theme, the\n * document properties, the drawings, the printer settings and every worksheet after the first\n * are dropped rather than parsed, and a part that is never parsed cannot be hostile.\n *\n * What this costs is the retained archive, held **compressed** and therefore bounded by\n * `maxBytes` — ten megabytes by default, against a fifty-thousand-row workbook's few. Entry\n * data is re-packed exactly as it arrived; nothing is inflated into memory to be kept.\n */\n\nimport zlib from 'node:zlib';\nimport { ImportError } from './errors';\nimport type { ImportLimits } from './limits';\n\n/** `PK\\x03\\x04` — the start of a local file header. */\nconst LOCAL_HEADER = 0x04034b50;\n/** `PK\\x01\\x02` — a central directory record. */\nconst CENTRAL_HEADER = 0x02014b50;\n/** `PK\\x05\\x06` — the end-of-central-directory record. */\nconst END_OF_CENTRAL = 0x06054b50;\n/** `PK\\x07\\x08` — the optional signature on a data descriptor. */\nconst DATA_DESCRIPTOR = 0x08074b50;\n/** Bytes in a local file header before the name. */\nconst HEADER_SIZE = 30;\n/** Bit 3 of the general-purpose flags: sizes follow the data rather than preceding it. */\nconst HAS_DATA_DESCRIPTOR = 0x08;\n/** A size of exactly this means \"see the zip64 extra field\", which is a size we do not trust. */\nconst ZIP64_SENTINEL = 0xffffffff;\n/** How much is handed to the inflater at once. Bounds the push-back after a stream ends. */\nconst SLICE = 64 * 1024;\n\nconst STORED = 0;\nconst DEFLATED = 8;\n\n/** The largest size a non-zip64 header can express. */\nconst ZIP32_MAX = 0xffffffff;\n\n/**\n * The parts an import reads, in the order exceljs must meet them.\n *\n * Relationships, the workbook and the shared strings before any worksheet — that ordering is\n * what keeps exceljs off its deferral path. Everything not named here, and every worksheet\n * after the first, is dropped.\n */\nconst WANTED = [\n '[Content_Types].xml',\n '_rels/.rels',\n 'xl/_rels/workbook.xml.rels',\n 'xl/workbook.xml',\n 'xl/styles.xml',\n 'xl/sharedStrings.xml',\n];\n\nconst WORKSHEET = /^xl\\/worksheets\\/sheet(\\d+)\\.xml$/;\n\n/**\n * The number in `sheetN.xml`, or `null` for anything that is not a worksheet.\n *\n * **Which worksheet an import reads is never decided by byte order.** A user means the first\n * *tab*, and an archive may list `sheet2.xml` before `sheet1.xml`.\n *\n * The lowest number is not *quite* tab order either: the real order lives in `workbook.xml`'s\n * `<sheets>` and the relationship map, which this does not parse. It is a great deal closer\n * than byte order, and it is the same number exceljs matches a worksheet by.\n */\nfunction sheetNumber(name: string): number | null {\n const match = WORKSHEET.exec(name);\n return match ? Number(match[1]) : null;\n}\n\nconst refuse = (message: string): never => {\n throw new ImportError('ARCHIVE_REFUSED', message);\n};\n\n// ─── CRC-32, because a rebuilt entry needs a checksum the reader will accept ──────────────\n\nconst CRC_TABLE = (() => {\n const table = new Int32Array(256);\n for (let i = 0; i < 256; i++) {\n let c = i;\n for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;\n table[i] = c;\n }\n return table;\n})();\n\nfunction crc32(current: number, bytes: Uint8Array): number {\n let c = ~current;\n for (let i = 0; i < bytes.length; i++) c = CRC_TABLE[(c ^ bytes[i]) & 0xff] ^ (c >>> 8);\n return ~c >>> 0;\n}\n\n// ─── Reading ──────────────────────────────────────────────────────────────────────────────\n\ninterface Entry {\n name: string;\n method: number;\n /** The entry's data exactly as it arrived, still compressed. */\n data: Buffer;\n inflated: number;\n crc: number;\n}\n\n/**\n * A pull-reader over the incoming bytes.\n *\n * `pushBack` exists for one case: a deflate stream that ends part-way through the slice last\n * handed to the inflater. The tail of that slice belongs to the data descriptor and has to be\n * read again as structure rather than as data.\n */\nclass ZipScanner {\n private buffer: Buffer = Buffer.alloc(0);\n private eof = false;\n\n constructor(private readonly iterator: AsyncIterator<Uint8Array>) {}\n\n get available(): number {\n return this.buffer.length;\n }\n\n /** Pull until at least `n` bytes are buffered. False means the stream ended first. */\n async need(n: number): Promise<boolean> {\n while (this.buffer.length < n && !this.eof) {\n const next = await this.iterator.next();\n if (next.done) {\n this.eof = true;\n break;\n }\n const chunk = Buffer.from(next.value.buffer, next.value.byteOffset, next.value.byteLength);\n this.buffer = this.buffer.length ? Buffer.concat([this.buffer, chunk]) : chunk;\n }\n return this.buffer.length >= n;\n }\n\n peek(n: number): Buffer {\n return this.buffer.subarray(0, Math.min(n, this.buffer.length));\n }\n\n take(n: number): Buffer {\n const chunk = this.buffer.subarray(0, n);\n this.buffer = this.buffer.subarray(n);\n return chunk;\n }\n\n pushBack(bytes: Buffer): void {\n if (bytes.length) this.buffer = Buffer.concat([bytes, this.buffer]);\n }\n}\n\n/**\n * Resolve when the inflater has processed this chunk, or reject if it could not.\n *\n * The `error` listener is not belt and braces. When zlib rejects the data outright — which is\n * what happens the moment an entry claims to be deflated and is not — it emits `error` and\n * **never calls the write callback**, so a promise built only on that callback never settles.\n * Verified: a sixteen-byte entry of plain text hung a sixty-second test instead of failing it.\n */\nfunction writeChunk(inflater: zlib.InflateRaw, chunk: Buffer): Promise<void> {\n return new Promise((resolve, reject) => {\n const onError = (error: Error): void => {\n inflater.removeListener('error', onError);\n reject(error);\n };\n inflater.once('error', onError);\n inflater.write(chunk, error => {\n inflater.removeListener('error', onError);\n if (error) reject(error);\n else resolve();\n });\n });\n}\n\n/** Give the event loop a turn, so `data` and `end` handlers run before the next decision. */\nconst settle = (): Promise<void> => new Promise(resolve => setImmediate(resolve));\n\ninterface Budget {\n /** Inflated bytes across the whole archive so far. */\n total: number;\n /** Retained compressed bytes so far. */\n retained: number;\n /** Entries seen so far. */\n entries: number;\n}\n\n/**\n * Read one deflated entry, measuring what it inflates to and keeping its bytes if wanted.\n *\n * `declared` is the compressed length when the header gave one, and `null` when it did not —\n * a streaming writer (exceljs's own included) writes the sizes *after* the data and leaves the\n * header's at zero, and a hostile file would do exactly the same on purpose. Neither path\n * trusts a declared size for the accounting: the numbers checked are the bytes that came out.\n */\nasync function readDeflated(\n scanner: ZipScanner,\n budget: Budget,\n limits: ImportLimits,\n name: string,\n declared: number | null,\n keep: boolean,\n): Promise<{ data: Buffer; inflated: number; crc: number }> {\n /**\n * Retention counted as it grows, not once the entry is complete.\n *\n * Checking afterwards is too late: by then the memory the limit protects has been spent.\n * `limitBytes` caps the stream upstream too, but this guard must not depend on that.\n */\n const retain = (slice: Buffer): void => {\n budget.retained += slice.length;\n if (budget.retained > limits.maxBytes) {\n refuse(`The workbook is larger than the ${limits.maxBytes} byte limit.`);\n }\n };\n const inflater = zlib.createInflateRaw();\n const kept: Buffer[] = [];\n let produced = 0;\n let consumed = 0;\n let crc = 0;\n let ended = false;\n let failure: Error | null = null;\n let breach: ImportError | null = null;\n\n /** Both bounds, checked on every chunk that comes out rather than on the entry's total. */\n const check = (): ImportError | null => {\n if (produced > limits.maxUncompressedBytes || budget.total + produced > limits.maxUncompressedBytes) {\n return new ImportError(\n 'ARCHIVE_REFUSED',\n `The workbook inflates to more than the ${limits.maxUncompressedBytes} byte limit.`,\n );\n }\n // Measured against what has actually been read, so an unfinished entry cannot hide behind\n // a ratio that would only be computed at the end. The floor keeps a tiny entry, where the\n // deflate header alone dominates, from tripping it.\n if (produced > 64 * 1024 && produced / Math.max(consumed, 1) > limits.maxCompressionRatio) {\n return new ImportError(\n 'ARCHIVE_REFUSED',\n `An entry in the workbook inflates more than ${limits.maxCompressionRatio} times — refusing it.`,\n );\n }\n return null;\n };\n\n inflater.on('data', chunk => {\n produced += chunk.length;\n crc = crc32(crc, chunk);\n if (!breach) breach = check();\n });\n inflater.on('end', () => {\n ended = true;\n });\n inflater.on('error', error => {\n failure = error as Error;\n });\n\n try {\n for (;;) {\n const remaining = declared === null ? Number.POSITIVE_INFINITY : declared - consumed;\n if (remaining <= 0) break;\n if (ended || failure || breach) break;\n\n if (!(await scanner.need(1))) refuse(`The workbook ends part-way through \"${name}\".`);\n\n const slice = Buffer.from(scanner.take(Math.min(SLICE, remaining, scanner.available)));\n // Counted *before* the write, because the inflater emits while the write is awaited and\n // the ratio check runs in that handler. Counting after left `consumed` at zero for the\n // whole of the first slice, so every entry over 64 KB looked like an infinite ratio —\n // a guard that refused a perfectly ordinary fifty-thousand-row workbook.\n consumed += slice.length;\n await writeChunk(inflater, slice);\n if (keep) {\n retain(slice);\n kept.push(slice);\n }\n await settle();\n\n if (ended && declared === null) {\n // `bytesWritten` is the input the engine actually consumed, which is exactly the\n // length of the deflate stream. Anything past it belongs to the data descriptor.\n const overshoot = consumed - inflater.bytesWritten;\n if (overshoot > 0) {\n scanner.pushBack(slice.subarray(slice.length - overshoot));\n consumed -= overshoot;\n if (keep) {\n kept[kept.length - 1] = slice.subarray(0, slice.length - overshoot);\n budget.retained -= overshoot;\n }\n }\n }\n }\n\n // A declared length longer than the deflate stream: the rest of the region is still this\n // entry's, so it is consumed rather than re-parsed. Guessing where the next header starts\n // is how a scanner desynchronises.\n while (declared !== null && consumed < declared && !breach) {\n if (!(await scanner.need(1))) refuse(`The workbook ends part-way through \"${name}\".`);\n const slice = Buffer.from(scanner.take(Math.min(declared - consumed, SLICE, scanner.available)));\n consumed += slice.length;\n if (keep) {\n retain(slice);\n kept.push(slice);\n }\n }\n } catch (error) {\n if (error instanceof ImportError) throw error;\n // zlib's own complaint, which names zlib. What a response carries instead is this.\n throw new ImportError('MALFORMED_FILE', 'The workbook could not be read.', { cause: error });\n } finally {\n inflater.destroy();\n }\n\n if (breach) throw breach;\n if (failure) {\n throw new ImportError('MALFORMED_FILE', 'The workbook could not be read.', { cause: failure });\n }\n\n budget.total += produced;\n return { data: keep ? Buffer.concat(kept) : Buffer.alloc(0), inflated: produced, crc };\n}\n\n/** Consume a stored entry, which inflates to exactly itself. */\nasync function readStored(\n scanner: ZipScanner,\n budget: Budget,\n limits: ImportLimits,\n name: string,\n size: number,\n keep: boolean,\n): Promise<{ data: Buffer; inflated: number; crc: number }> {\n const kept: Buffer[] = [];\n let crc = 0;\n let left = size;\n\n while (left > 0) {\n if (!(await scanner.need(1))) refuse(`The workbook ends part-way through \"${name}\".`);\n const slice = Buffer.from(scanner.take(Math.min(left, SLICE, scanner.available)));\n crc = crc32(crc, slice);\n if (keep) {\n // Counted as it grows, for the reason given in `readDeflated`.\n budget.retained += slice.length;\n if (budget.retained > limits.maxBytes) {\n refuse(`The workbook is larger than the ${limits.maxBytes} byte limit.`);\n }\n kept.push(slice);\n }\n left -= slice.length;\n budget.total += slice.length;\n if (budget.total > limits.maxUncompressedBytes) {\n refuse(`The workbook inflates to more than the ${limits.maxUncompressedBytes} byte limit.`);\n }\n }\n\n return { data: keep ? Buffer.concat(kept) : Buffer.alloc(0), inflated: size, crc };\n}\n\n/** Consume the data descriptor that follows an entry whose header declared no sizes. */\nasync function skipDescriptor(scanner: ZipScanner): Promise<void> {\n if (!(await scanner.need(4))) return;\n const signed = scanner.peek(4).readUInt32LE(0) === DATA_DESCRIPTOR;\n const base = signed ? 4 : 0;\n\n // crc32 plus two sizes, four bytes each — or eight each under zip64. What follows the\n // descriptor is the thing that says which, so both lengths are tried in order.\n for (const width of [base + 12, base + 20]) {\n if (!(await scanner.need(width + 4))) break;\n const next = scanner.peek(width + 4).readUInt32LE(width);\n if (next === LOCAL_HEADER || next === CENTRAL_HEADER) {\n scanner.take(width);\n return;\n }\n }\n\n if (await scanner.need(base + 12)) scanner.take(base + 12);\n}\n\n// ─── Writing ──────────────────────────────────────────────────────────────────────────────\n\n/** Repack the kept entries as a zip, in the order the parser needs to meet them. */\nfunction repack(entries: Entry[]): Buffer {\n const parts: Buffer[] = [];\n const directory: Buffer[] = [];\n let offset = 0;\n\n for (const entry of entries) {\n const name = Buffer.from(entry.name, 'utf8');\n\n const local = Buffer.alloc(HEADER_SIZE);\n local.writeUInt32LE(LOCAL_HEADER, 0);\n local.writeUInt16LE(20, 4); // version needed\n local.writeUInt16LE(0, 6); // flags: sizes are right here, so no data descriptor\n local.writeUInt16LE(entry.method, 8);\n local.writeUInt32LE(entry.crc, 14);\n local.writeUInt32LE(entry.data.length, 18);\n local.writeUInt32LE(entry.inflated, 22);\n local.writeUInt16LE(name.length, 26);\n parts.push(local, name, entry.data);\n\n const central = Buffer.alloc(46);\n central.writeUInt32LE(CENTRAL_HEADER, 0);\n central.writeUInt16LE(20, 4); // version made by\n central.writeUInt16LE(20, 6); // version needed\n central.writeUInt16LE(entry.method, 10);\n central.writeUInt32LE(entry.crc, 16);\n central.writeUInt32LE(entry.data.length, 20);\n central.writeUInt32LE(entry.inflated, 24);\n central.writeUInt16LE(name.length, 28);\n central.writeUInt32LE(offset, 42);\n directory.push(central, name);\n\n offset += local.length + name.length + entry.data.length;\n }\n\n const directoryBytes = Buffer.concat(directory);\n const end = Buffer.alloc(22);\n end.writeUInt32LE(END_OF_CENTRAL, 0);\n end.writeUInt16LE(entries.length, 8);\n end.writeUInt16LE(entries.length, 10);\n end.writeUInt32LE(directoryBytes.length, 12);\n end.writeUInt32LE(offset, 16);\n\n return Buffer.concat([...parts, directoryBytes, end]);\n}\n\n/**\n * Walk an archive under every archive-shaped bound, and return the parts an import needs.\n *\n * Parsing stops at the first thing that is not a local file header — the central directory —\n * because nothing after the last entry inflates and there is nothing there to guard.\n */\nexport async function guardZip(\n source: AsyncIterable<Uint8Array>,\n limits: ImportLimits,\n): Promise<Buffer> {\n const scanner = new ZipScanner(source[Symbol.asyncIterator]());\n const budget: Budget = { total: 0, retained: 0, entries: 0 };\n const kept = new Map<string, Entry>();\n /** The lowest-numbered worksheet seen so far, which is the one an import reads. */\n let sheet: { name: string; number: number } | null = null;\n\n for (;;) {\n if (!(await scanner.need(4))) break;\n if (scanner.peek(4).readUInt32LE(0) !== LOCAL_HEADER) break;\n\n if (!(await scanner.need(HEADER_SIZE))) {\n refuse('The workbook ends part-way through an entry header.');\n }\n\n const header = Buffer.from(scanner.peek(HEADER_SIZE));\n const flags = header.readUInt16LE(6);\n const method = header.readUInt16LE(8);\n const declaredCompressed = header.readUInt32LE(18);\n const nameLength = header.readUInt16LE(26);\n const extraLength = header.readUInt16LE(28);\n\n if (++budget.entries > limits.maxZipEntries) {\n refuse(`The workbook has more than ${limits.maxZipEntries} entries.`);\n }\n\n if (!(await scanner.need(HEADER_SIZE + nameLength + extraLength))) {\n refuse('The workbook ends part-way through an entry header.');\n }\n const name = scanner.peek(HEADER_SIZE + nameLength).subarray(HEADER_SIZE).toString('utf8');\n scanner.take(HEADER_SIZE + nameLength + extraLength);\n\n // One worksheet and no other: a mapping plan addresses one sheet's columns, and carrying\n // the rest would be holding data nothing reads. Which one is decided by `sheetNumber`\n // rather than by the order the archive happens to list them in — see that function.\n const number = sheetNumber(name);\n const keep =\n number === null\n ? WANTED.includes(name) && !kept.has(name)\n : sheet === null || number < sheet.number;\n\n // The superseded sheet goes **before** the new one is read, not after. Releasing it\n // afterwards means holding two at once at the moment of changeover, which makes the\n // retention bound depend on the order the archive lists its sheets in: the same workbook\n // sails through ascending and is refused descending.\n if (keep && number !== null && sheet) {\n budget.retained -= kept.get(sheet.name)?.data.length ?? 0;\n kept.delete(sheet.name);\n sheet = null;\n }\n\n const streamed = (flags & HAS_DATA_DESCRIPTOR) !== 0 || declaredCompressed === ZIP64_SENTINEL;\n let read: { data: Buffer; inflated: number; crc: number };\n\n if (method === STORED) {\n if (streamed) {\n // A stored entry whose length is written only afterwards cannot be found the end of\n // without seeking, and seeking is what streaming means not doing. No real writer\n // produces one; a file that does is either broken or trying something.\n refuse(`Entry \"${name}\" is stored without a declared size.`);\n }\n read = await readStored(scanner, budget, limits, name, declaredCompressed, keep);\n } else if (method === DEFLATED) {\n read = await readDeflated(\n scanner,\n budget,\n limits,\n name,\n streamed ? null : declaredCompressed,\n keep,\n );\n if (streamed) await skipDescriptor(scanner);\n } else {\n return refuse(`Entry \"${name}\" uses compression method ${method}, which is not supported.`);\n }\n\n if (keep) {\n // The rebuilt archive is written without zip64, so a size a 32-bit header cannot express\n // would be silently truncated. `maxUncompressedBytes` keeps this out of reach at its\n // default; it is a consumer-settable limit, so the ceiling is checked rather than assumed.\n if (read.data.length > ZIP32_MAX || read.inflated > ZIP32_MAX) {\n refuse(`Entry \"${name}\" is too large to repack without zip64.`);\n }\n kept.set(name, { name, method, data: read.data, inflated: read.inflated, crc: read.crc });\n if (number !== null) sheet = { name, number };\n }\n }\n\n if (!sheet) {\n throw new ImportError('MALFORMED_FILE', 'This workbook has no worksheet.');\n }\n\n const ordered = [...WANTED, sheet.name]\n .map(wanted => kept.get(wanted))\n .filter((entry): entry is Entry => entry !== undefined);\n\n return repack(ordered);\n}\n","/**\n * xlsx-source.ts — a workbook, one row at a time.\n *\n * exceljs's streaming reader does the XML; this file does the three things around it that\n * decide whether a server-side xlsx import matches a browser one.\n *\n * 1. **Every byte goes through `guardZip` first.** exceljs inflates internally and reports\n * nothing while it does, so the only place an archive's real size is knowable early is\n * before it gets there.\n * 2. **Blank rows are put back.** exceljs skips them — a sheet with a gap emits row 3 then\n * row 5 — and a reader that passed that through would shift every later row number in\n * every error message, and disagree with the CSV path about `skipped`. The row numbers a\n * user is shown are the ones in their spreadsheet's gutter, so the gaps are synthesised.\n * 3. **Cells keep their types.** A date cell arrives as a `Date` at UTC midnight and is\n * handed on as one, because `coerceCell` reads a typed cell as its type. Stringifying here\n * to tidy the signature is the exact defect this feature already fixed once.\n *\n * **What this holds, stated rather than discovered:** the archive's wanted parts, kept\n * **compressed** and therefore bounded by `maxBytes` — ten megabytes by default, against a\n * fifty-thousand-row workbook's few. That is the cost of `guardZip` rebuilding the archive\n * rather than forwarding it, and the reasons it does are in that file. Row data is never\n * accumulated, which is the part that scales with the file.\n */\n\nimport ExcelJS from 'exceljs';\nimport { Readable } from 'node:stream';\nimport { ImportError } from './errors';\nimport { guardZip } from './guard-zip';\nimport type { ImportLimits } from './limits';\n\n/**\n * Turn one exceljs cell value into the value a record should be built from.\n *\n * A formula cell yields its **result**, never its formula: a sheet someone uploaded may\n * contain anything, and importing the text `=SUM(A1:A9)` into a field is importing a string\n * the user never saw. An error cell yields its error text, which fails coercion loudly rather\n * than importing as blank.\n */\nexport function cellValue(raw: unknown): unknown {\n if (raw === null || raw === undefined) return '';\n if (raw instanceof Date) return raw;\n if (typeof raw !== 'object') return raw;\n\n const cell = raw as Record<string, unknown>;\n if (Array.isArray(cell['richText'])) {\n return (cell['richText'] as { text?: unknown }[]).map(part => String(part?.text ?? '')).join('');\n }\n if ('formula' in cell || 'sharedFormula' in cell) return cellValue(cell['result']);\n if ('error' in cell) return String(cell['error']);\n if ('text' in cell) return String(cell['text']);\n return '';\n}\n\n/** `row.values` is one-based with a hole at index 0, and sparse wherever a cell was empty. */\nfunction rowCells(values: unknown): unknown[] {\n if (!Array.isArray(values)) return [];\n const out: unknown[] = [];\n for (let i = 1; i < values.length; i++) out.push(cellValue(values[i]));\n return out;\n}\n\n/**\n * Rows of the first worksheet, starting at spreadsheet row 1.\n *\n * The first worksheet and no other: a mapping plan addresses one sheet's columns, and picking\n * a second one silently would import a different file from the one the user previewed.\n */\nexport async function* xlsxRows(\n source: AsyncIterable<Uint8Array>,\n limits: ImportLimits,\n): AsyncGenerator<unknown[]> {\n const archive = await guardZip(source, limits);\n const guarded = Readable.from([archive]);\n const reader = new ExcelJS.stream.xlsx.WorkbookReader(guarded, {\n // Nothing decorative is parsed. Each of these is XML this import has no use for, and\n // every part not parsed is a part that cannot be hostile.\n entries: 'ignore',\n hyperlinks: 'ignore',\n // Cached because a string cell is a reference into this table and an unresolved reference\n // is not a value. See the note at the top about what that costs.\n sharedStrings: 'cache',\n // Cached because it is what tells a numeric cell it is a date.\n styles: 'cache',\n worksheets: 'emit',\n });\n\n /**\n * The next spreadsheet row number expected, so a gap can be filled rather than skipped.\n *\n * **Do not race each pull against a stream-error promise.** `guardZip` validates the whole\n * archive before the parser is constructed, so a malformed zip cannot stall this loop — and\n * racing every row against one long-lived promise attaches a reaction per row that is never\n * released (about 1.9 KB a row, 75 MB over fifty thousand rows). A request that hangs for any\n * other reason is the Express adapter's `totalTimeoutMs` to answer; that layer owns a clock.\n */\n let expected = 1;\n let yielded = false;\n\n try {\n // The first worksheet and no other: a mapping plan addresses one sheet's columns, and\n // picking up a second silently would import a different file from the one the user\n // previewed.\n for await (const worksheet of reader) {\n for await (const row of worksheet) {\n const number = typeof row.number === 'number' ? row.number : expected;\n\n // **Refused before the gap is generated, not after it is counted.** A sheet holding two\n // cells and `r=\"500000\"` is under two kilobytes, and synthesising its way to the row\n // limit cost a quarter of a second of CPU per request before the row guard noticed —\n // the same \"enforce the limit after doing the work\" shape this package keeps finding in\n // itself. The row number says how big the sheet claims to be, so it can say it first.\n if (number - 1 > limits.maxRows) {\n throw new ImportError('SHEET_TOO_LARGE', `The sheet has more than ${limits.maxRows} rows.`);\n }\n\n // A blank row is a row. `applyMapping` counts it as skipped, and every row after it\n // keeps the number the user sees.\n while (expected < number) {\n yield [];\n expected++;\n }\n yield rowCells(row.values);\n yielded = true;\n expected = number + 1;\n }\n break;\n }\n\n if (!yielded) {\n // A worksheet that produced not one row, or no worksheet at all. Either the sheet XML is\n // not readable — a SAX parser given rubbish inside a well-formed zip stops rather than\n // complains — or the sheet is genuinely empty. Neither is a file anyone meant to import,\n // and \"imported 0 rows\" is the wrong thing to tell someone whose file plainly has data.\n throw new ImportError('MALFORMED_FILE', 'This workbook has no readable rows.');\n }\n } catch (error) {\n // A guard that already decided says what it decided. Anything else is the parser refusing\n // the file, and what it says about itself is not for a response to carry — exceljs's SAX\n // parser refuses an undefined entity, which is how a billion-laughs sheet arrives here.\n if (error instanceof ImportError) throw error;\n throw new ImportError('MALFORMED_FILE', 'This is not a readable workbook.', { cause: error });\n } finally {\n guarded.destroy();\n }\n}\n","/**\n * read-sheet.ts — bytes in, a header row and an async stream of rows out.\n *\n * Two properties this file exists to hold:\n *\n * 1. **Format is decided by content, never by the filename.** `.csv` is client-controlled and\n * an attacker who wants a zip parsed will name their zip `.csv`. The first four bytes are\n * not. Both readers then meet the same row guards, so a workbook cannot buy itself a\n * larger sheet by being a workbook.\n * 2. **Rows arrive one at a time.** Nothing here ever holds the sheet. `rows` is an\n * `AsyncIterable`, so a consumer that stops pulling stops the read.\n *\n * Cells are `unknown[]`, never `string[]`. An xlsx cell is a number, a boolean or a `Date`,\n * and core's `coerceCell` reads a typed cell as its type — stringifying here to make the\n * signature tidier is exactly the bug that would reintroduce.\n */\n\nimport { createCsvReader, padRow } from '@dynamic-entity/core';\nimport { destroySource, limitBytes, peek, toByteStream, type ByteSource } from './bytes';\nimport { sampleText } from './cell-text';\nimport { ImportError } from './errors';\nimport { resolveLimits, type ImportLimits } from './limits';\nimport { xlsxRows } from './xlsx-source';\n\n/** The formats this package reads. Both are decided by magic bytes. */\nexport type SheetFormat = 'csv' | 'xlsx';\n\nexport interface SheetSource {\n /** What the bytes turned out to be, which may not be what the filename claimed. */\n format: SheetFormat;\n headers: string[];\n /** Data rows, one at a time. Pulling stops the read; not pulling stops it too. */\n rows: AsyncIterable<unknown[]>;\n}\n\nexport interface ReadSheetOptions {\n stream: ByteSource;\n /** Display only — it names the file in an error message and decides nothing. */\n filename?: string;\n limits?: Partial<ImportLimits>;\n}\n\n/** Enough bytes to tell a zip from text, with room for a UTF-16 BOM. */\nconst MAGIC_BYTES = 8;\n\nconst ZIP = [0x50, 0x4b]; // \"PK\"\nconst OLE2 = [0xd0, 0xcf, 0x11, 0xe0]; // a pre-2007 .xls, which is a compound file, not a zip\nconst UTF16_LE = [0xff, 0xfe];\nconst UTF16_BE = [0xfe, 0xff];\n\nconst startsWith = (bytes: Uint8Array, signature: number[]): boolean =>\n signature.every((byte, i) => bytes[i] === byte);\n\n/**\n * What these bytes actually are.\n *\n * Exported because the multipart layer wants the same answer for the same reason, and because\n * a second copy of a magic-byte table is a second table to be wrong.\n */\nexport function detectFormat(head: Uint8Array): SheetFormat {\n if (startsWith(head, ZIP)) return 'xlsx';\n\n if (startsWith(head, OLE2)) {\n throw new ImportError(\n 'UNSUPPORTED_FORMAT',\n 'This is a pre-2007 Excel file (.xls). Save it as .xlsx or .csv and try again.',\n );\n }\n if (startsWith(head, UTF16_LE) || startsWith(head, UTF16_BE)) {\n throw new ImportError(\n 'UNSUPPORTED_FORMAT',\n 'This file is UTF-16 encoded. Save it as UTF-8 CSV and try again.',\n );\n }\n\n // Anything else is read as delimited text. A binary file that reaches here produces a sheet\n // of nonsense rather than an error, which is the right trade: refusing everything that does\n // not look like text would refuse legitimately unusual encodings too, and nonsense rows are\n // reported per row by the mapping rather than as a mysterious rejection.\n return 'csv';\n}\n\n/** Decode UTF-8 across chunk boundaries and feed core's incremental reader. */\nasync function* csvRows(stream: AsyncIterable<Uint8Array>): AsyncGenerator<string[]> {\n // Not `fatal`: a sheet is user data from somewhere else, and one bad byte replaced with\n // U+FFFD is a cell the importer can report on. Rejecting the whole file for it is a worse\n // answer to a problem the user can see in exactly one cell.\n const decoder = new TextDecoder('utf-8');\n const reader = createCsvReader();\n\n for await (const chunk of stream) {\n // `stream: true` is what holds a multi-byte character split across a 64 KB boundary.\n for (const row of reader.push(decoder.decode(chunk, { stream: true }))) yield row;\n }\n\n for (const row of reader.push(decoder.decode())) yield row;\n for (const row of reader.end()) yield row;\n}\n\n/**\n * Enforce the row, column and cell bounds as rows go past, and pad short rows.\n *\n * Padding matches `parseCsv`, through the same `padRow` core exports, so a file imported on a\n * server and the same file imported in a browser produce the same rows. A second padding rule\n * here would be a place for the two halves to disagree, silently, about a row that ended early.\n */\nexport async function* guardRows(\n rows: AsyncIterable<unknown[]>,\n width: number,\n limits: ImportLimits,\n): AsyncGenerator<unknown[]> {\n let seen = 0;\n\n for await (const row of rows) {\n if (++seen > limits.maxRows) {\n throw new ImportError(\n 'SHEET_TOO_LARGE',\n `The sheet has more than ${limits.maxRows} rows.`,\n );\n }\n if (row.length > limits.maxColumns) {\n throw new ImportError(\n 'SHEET_TOO_LARGE',\n `Row ${seen + 1} has more than ${limits.maxColumns} columns.`,\n );\n }\n for (const cell of row) {\n if (typeof cell === 'string' && cell.length > limits.maxCellLength) {\n throw new ImportError(\n 'SHEET_TOO_LARGE',\n `A cell in row ${seen + 1} is longer than ${limits.maxCellLength} characters.`,\n );\n }\n }\n yield padRow(row, width);\n }\n}\n\n/**\n * Open a sheet: read its header row, and hand back the rest as a stream.\n *\n * The header row is pulled eagerly because everything downstream — the column count, the\n * mapping suggestion, the padding width — is a function of it. Exactly one row is held.\n */\nexport async function readSheet(options: ReadSheetOptions): Promise<SheetSource> {\n const limits = resolveLimits(options.limits);\n\n try {\n const counted = limitBytes(toByteStream(options.stream), limits.maxBytes);\n const { head, stream } = await peek(counted, MAGIC_BYTES);\n const format = detectFormat(head);\n const rows = format === 'xlsx' ? xlsxRows(stream, limits) : csvRows(stream);\n\n const iterator = rows[Symbol.asyncIterator]();\n const first = await iterator.next();\n // Headers are text whichever format they came from — a column heading is a label, and a\n // plan addresses a column by index anyway. `sampleText` rather than `String` so a header\n // cell that is somehow a date reads the same on both sides.\n const headers = first.done ? [] : first.value.map(sampleText);\n\n if (headers.length > limits.maxColumns) {\n throw new ImportError(\n 'SHEET_TOO_LARGE',\n `The sheet has more than ${limits.maxColumns} columns.`,\n );\n }\n\n const remaining = drain(iterator);\n return {\n format,\n headers,\n rows: closing(guardRows(remaining, headers.length, limits), options.stream),\n };\n } catch (error) {\n // Every exit from this function closes the request body, including the ones that are a\n // refusal rather than a fault. Refusing an upload and then leaving the socket half-read is\n // how a rejected upload still costs a handle.\n destroySource(options.stream);\n throw error;\n }\n}\n\n/** Everything the iterator has left, after the header row has been taken off the front. */\nasync function* drain(\n iterator: AsyncIterator<unknown[]>,\n): AsyncGenerator<unknown[]> {\n for (let next = await iterator.next(); !next.done; next = await iterator.next()) {\n yield next.value;\n }\n}\n\n/**\n * Close the source if the row stream stops early — by a guard firing, or by the caller\n * abandoning it.\n *\n * `readSheet` returns before a single data row is read, so its own `try` is long finished by\n * the time a row-level limit fires. Without this, the only thing standing between a refused\n * sheet and a leaked socket would be every caller remembering to destroy it, and \"every caller\n * remembers\" is the assumption this package keeps declining to make.\n */\nasync function* closing(\n rows: AsyncIterable<unknown[]>,\n source: unknown,\n): AsyncGenerator<unknown[]> {\n let drained = false;\n try {\n yield* rows;\n drained = true;\n } finally {\n // `finally` rather than `catch`, so an early `break` in the caller's loop closes it too.\n //\n // But **only when the stream did not finish**. On a clean read the source is already at\n // its end, and destroying it anyway would be actively wrong under Express: the file part\n // of a multipart body shares a socket with the response being written back, and tearing\n // that down after a successful import would abort the reply it was about to send.\n if (!drained) destroySource(source);\n }\n}\n","/**\n * run-import.ts — the package, really. Everything else is plumbing around this file.\n *\n * `runImport` pulls rows off the reader in batches, hands each batch to core's `applyMapping`,\n * **awaits** `onBatch`, and drops the batch. Peak memory is a function of `batchSize`, not of\n * the file's size — which is the one reason this package exists, and the claim every decision\n * below is answerable against.\n *\n * Nothing here decides anything core already decides. The mapping rules, the coercion, the\n * validation and the record shape all come from `applyMapping`; this file's entire job is to\n * feed it and to not accumulate.\n */\n\nimport {\n applyMapping,\n deriveImportColumns,\n suggestMapping,\n validateMappingPlan,\n type ConfigProblem,\n type EntityFormConfig,\n type FormRule,\n type ImportLookups,\n type ImportRowError,\n type MappingPlan,\n} from '@dynamic-entity/core';\nimport { destroySource, type ByteSource } from './bytes';\nimport { sampleText } from './cell-text';\nimport { ImportError } from './errors';\nimport { resolveLimits, type ImportLimits } from './limits';\nimport { readSheet, type SheetFormat } from './read-sheet';\n\n/** Where a batch sits in the user's spreadsheet, for a consumer that logs or reports progress. */\nexport interface BatchInfo {\n /** Zero-based batch number. */\n index: number;\n /** Spreadsheet row number of the first row this batch was built from. */\n firstRow: number;\n /** Records handed over before this batch, so `imported + records.length` is the running total. */\n imported: number;\n}\n\n/**\n * Write a batch of records.\n *\n * **It is awaited, and that is load-bearing.** A consumer writing to a database is slower than\n * a parser reading a file; not awaiting would turn bounded memory into an unbounded queue of\n * pending writes — the exact failure this package exists to avoid, reached from the other\n * direction.\n *\n * **It must be idempotent.** A stream that fails at row 30,000 has already written 29,999\n * records, and an HTTP client — or a proxy, or a user — will retry. See SECURITY.md.\n */\nexport type OnBatch = (\n records: Record<string, unknown>[],\n info: BatchInfo,\n) => void | Promise<void>;\n\nexport interface RunImportOptions {\n stream: ByteSource;\n /** Display only. The format is decided by the file's first bytes, never by its name. */\n filename?: string;\n plan: MappingPlan;\n config: EntityFormConfig;\n rules?: readonly FormRule[];\n /** Values for every `listName` the config mentions. A server has no `LOOKUP_REGISTRY`. */\n lookups?: ImportLookups;\n lang?: string;\n /** Omit to validate without writing: the identical pipeline, every error, nothing stored. */\n onBatch?: OnBatch;\n limits?: Partial<ImportLimits>;\n}\n\nexport interface ImportRunResult {\n /**\n * Records the run produced — and therefore, when `onBatch` was supplied, how many were\n * handed over to be written. The records themselves are deliberately not here: returning\n * fifty thousand of them would undo the streaming this whole file is for.\n */\n imported: number;\n /** Rows that held no values at all. A blank line is not an error and is not a record. */\n skipped: number;\n /**\n * Rows that produced at least one error, counted exactly — **not** derived from `errors`.\n *\n * `errors` is capped, so counting the distinct rows in it answers how many rows fitted in the\n * cap rather than how many failed. Measured on a run of a thousand rows where two hundred\n * failed: the sample held seven of them. The number a user acts on is \"which rows do I fix\",\n * so it is the one that has to be exact.\n *\n * Row numbers never span a batch, so summing the per-batch distinct counts is exact rather\n * than approximate.\n */\n failed: number;\n /** A **sample**, capped at `limits.maxReportedErrors`. `errorCount` is the true total. */\n errors: ImportRowError[];\n /** Every problem found, whether or not it was retained. */\n errorCount: number;\n /** `errors` is shorter than `errorCount`. */\n truncated: boolean;\n /** What is wrong with the plan itself. An `error` here means no row was read at all. */\n planProblems: ConfigProblem[];\n /** Data rows read from the sheet, blank ones included. */\n rowsRead: number;\n /**\n * What the file turned out to be — **absent when no byte of it was ever read**.\n *\n * A plan refused before the stream is touched has nothing to report here, and it used to\n * report `'csv'` anyway. A guessed field is worse than a missing one: it is indistinguishable\n * from a measured one.\n */\n format?: SheetFormat;\n}\n\n/**\n * Check the plan before a single row is read.\n *\n * A plan naming a field the config does not have is wrong about every row, so discovering it\n * at row 30,000 of a file that has already written 29,999 records is the worst available\n * moment. `applyMapping` checks it too — per batch, on its own account — and this is not that\n * check moved but that check brought forward.\n */\nfunction checkPlan(plan: MappingPlan, config: EntityFormConfig, lang?: string): ConfigProblem[] {\n return validateMappingPlan(plan, config, {\n lang,\n // The same derivation `applyMapping` uses, or the two would disagree about which refs\n // exist and the pre-flight would pass a plan the run then rejects.\n includeReadonly: true,\n includeSystemDefault: true,\n });\n}\n\nexport async function runImport(options: RunImportOptions): Promise<ImportRunResult> {\n const limits = resolveLimits(options.limits);\n const { plan, config, onBatch } = options;\n\n const planProblems = checkPlan(plan, config, options.lang);\n if (planProblems.some(problem => problem.level === 'error')) {\n // Nothing was read, so nothing needs unwinding — but the request body is still open, and\n // an undrained socket is a leaked handle.\n destroySource(options.stream);\n return {\n imported: 0,\n skipped: 0,\n failed: 0,\n errors: [],\n errorCount: 0,\n truncated: false,\n planProblems,\n rowsRead: 0,\n };\n }\n\n const sheet = await readSheet({\n stream: options.stream,\n filename: options.filename,\n limits,\n });\n\n const errors: ImportRowError[] = [];\n let errorCount = 0;\n let imported = 0;\n let skipped = 0;\n let failed = 0;\n let rowsRead = 0;\n let batchIndex = 0;\n\n let batch: unknown[][] = [];\n /** The spreadsheet row number of `batch[0]`. The header is row 1. */\n let firstRow = 2;\n\n const flush = async (): Promise<void> => {\n if (!batch.length) return;\n const rows = batch;\n batch = [];\n\n const result = applyMapping(rows, plan, config, {\n lang: options.lang,\n rules: options.rules,\n lookups: options.lookups,\n firstRowNumber: firstRow,\n });\n\n skipped += result.skipped;\n errorCount += result.errors.length;\n // Distinct rows, not problems: one row failing two validators is one row to go and fix.\n failed += new Set(result.errors.map(error => error.row)).size;\n // Retained, not accumulated. A file where every row fails is fifty thousand error objects\n // otherwise, which is the same unbounded growth as holding the file.\n for (const error of result.errors) {\n if (errors.length >= limits.maxReportedErrors) break;\n errors.push(error);\n }\n\n if (result.records.length && onBatch) {\n const info: BatchInfo = { index: batchIndex, firstRow, imported };\n try {\n // Awaited before the next batch is pulled. This is the backpressure.\n await onBatch(result.records, info);\n } catch (cause) {\n throw new ImportError(\n 'IMPORT_FAILED',\n 'The import stopped partway through. Rows before the failure may already have ' +\n 'been written — this import is not transactional.',\n { cause },\n );\n }\n }\n\n imported += result.records.length;\n firstRow += rows.length;\n batchIndex++;\n };\n\n try {\n for await (const row of sheet.rows) {\n rowsRead++;\n batch.push(row as unknown[]);\n if (batch.length >= limits.batchSize) await flush();\n }\n await flush();\n } catch (error) {\n // A failure anywhere — a limit, a malformed file, the consumer's own writer — leaves the\n // request body half-read. Destroying it is what stops a hostile uploader from holding a\n // socket open after the guard that refused them has already fired.\n destroySource(options.stream);\n throw error;\n }\n\n return {\n imported,\n skipped,\n failed,\n errors,\n errorCount,\n truncated: errorCount > errors.length,\n planProblems,\n rowsRead,\n format: sheet.format,\n };\n}\n\n// ─── Preview ──────────────────────────────────────────────────────────────────\n\nexport interface PreviewSheetOptions {\n stream: ByteSource;\n filename?: string;\n config: EntityFormConfig;\n lang?: string;\n limits?: Partial<ImportLimits>;\n}\n\n/** What a first look at an uploaded file yields. Matches the renderer's `ImportPreview`. */\nexport interface SheetPreview {\n headers: string[];\n /** The first few rows as text, for the mapping screen to show under each column. */\n sample: string[][];\n /** Every inferred match is tagged `guess`. */\n suggestion: MappingPlan;\n /** Data rows in the file, which the whole stream is read to count. */\n rowCount: number;\n format: SheetFormat;\n}\n\n/**\n * Headers, a sample, and a suggested mapping.\n *\n * The whole file is read, because `rowCount` is a number the user is shown before they commit\n * and \"about this many\" is not a useful thing to tell someone about their own data. Reading it\n * also means every limit applies to a preview exactly as it applies to an import — a preview\n * endpoint that skipped them would be the cheapest way in.\n */\nexport async function previewSheet(options: PreviewSheetOptions): Promise<SheetPreview> {\n const limits = resolveLimits(options.limits);\n const sheet = await readSheet({\n stream: options.stream,\n filename: options.filename,\n limits,\n });\n\n const sample: string[][] = [];\n let rowCount = 0;\n\n try {\n for await (const row of sheet.rows) {\n rowCount++;\n if (sample.length < limits.sampleRows) sample.push(row.map(sampleText));\n }\n } catch (error) {\n destroySource(options.stream);\n throw error;\n }\n\n // The same derivation the browser transport uses, so the two suggest the same mapping for\n // the same file. Readonly and system-default fields stay out of a *suggestion* for the same\n // reason they are marked: nobody maps a column onto a field the form fills in itself.\n const { columns } = deriveImportColumns(options.config, { lang: options.lang });\n\n return {\n headers: sheet.headers,\n sample,\n rowCount,\n suggestion: suggestMapping(sheet.headers, columns, options.config?.entity ?? ''),\n format: sheet.format,\n };\n}\n","/**\n * write-template.ts — the sheet to hand a user so their data arrives already in the right shape.\n *\n * **This is the one place in the library that writes a file another program executes.** A\n * template is opened in Excel, and `=cmd|'/C calc'!A0` in a cell is a command, not text. So\n * every cell goes through core's `escapeFormula` on the way in.\n *\n * The cell's *type* is the other half, and it cannot be set: exceljs derives it from the value\n * and exposes `type` read-only. Handing it a string is therefore what makes a string cell —\n * which is a claim about a library rather than a line of code here, so `write-template.spec.ts`\n * reads the written XML back and asserts no `<f>` element appears in it. A guard that is an\n * assertion in a test is a guard; a comment saying \"exceljs writes this as text\" is not.\n *\n * The header text itself is derived from config labels, which is authored data like any other\n * and therefore not trusted here.\n */\n\nimport { escapeFormula, toCsv, type TemplateSpec } from '@dynamic-entity/core';\nimport ExcelJS from 'exceljs';\nimport type { Writable } from 'node:stream';\nimport { ImportError } from './errors';\nimport { resolveLimits, type ImportLimits } from './limits';\n\n/** The formats a generated template can be written in. */\nexport type TemplateFormat = 'csv' | 'xlsx';\n\nexport interface WriteTemplateOptions {\n spec: TemplateSpec;\n format: TemplateFormat;\n /** Written into as the file is produced; an HTTP response is the intended one. */\n out: Writable;\n limits?: Partial<ImportLimits>;\n}\n\n/** The extension a written template should be offered under. */\nexport const TEMPLATE_EXTENSION: Record<TemplateFormat, string> = { csv: 'csv', xlsx: 'xlsx' };\n\n/** The media type a written template should be served as. */\nexport const TEMPLATE_MEDIA_TYPE: Record<TemplateFormat, string> = {\n csv: 'text/csv; charset=utf-8',\n xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n};\n\n/**\n * Write a template into `out`.\n *\n * Resolves when the file is complete and the stream has been ended. A caller that owns the\n * stream for other reasons — an Express response, say — gets it back finished, which is what\n * a download handler wants.\n */\nexport async function writeTemplate(options: WriteTemplateOptions): Promise<void> {\n const limits = resolveLimits(options.limits);\n const columns = options.spec.columns;\n\n if (columns.length > limits.maxColumns) {\n throw new ImportError(\n 'SHEET_TOO_LARGE',\n `A template of ${columns.length} columns is past the ${limits.maxColumns} column limit.`,\n );\n }\n\n if (options.format === 'csv') {\n await writeCsv(options.spec, options.out);\n return;\n }\n await writeXlsx(options.spec, options.out);\n}\n\n/**\n * Headers only, and `spec.notes` deliberately left out of the file.\n *\n * The same decision the browser transport makes, for the same reason: CSV has one header row\n * and no concept of a second, so a guidance row would be read back as a record and report a\n * failed row 2 on every re-import of the template the user just filled in.\n */\nfunction writeCsv(spec: TemplateSpec, out: Writable): Promise<void> {\n // Byte-for-byte what `LocalImportTransport.template` produces, through the same `toCsv` —\n // which escapes every cell — so a template downloaded from a server and one generated in the\n // browser are the same file.\n const csv = toCsv(spec.columns.map(column => column.header));\n return new Promise((resolve, reject) => {\n out.end(csv, () => resolve());\n out.on('error', reject);\n });\n}\n\n/**\n * The same headers, plus the guidance CSV had nowhere to put.\n *\n * An xlsx cell has a note attached to it, which is somewhere that is *not* data: the hint\n * reads in Excel and does not come back as a row on re-import. That is the distinction the\n * CSV writer could not make.\n */\nasync function writeXlsx(spec: TemplateSpec, out: Writable): Promise<void> {\n const workbook = new ExcelJS.stream.xlsx.WorkbookWriter({ stream: out, useStyles: true });\n const sheet = workbook.addWorksheet(sheetName(spec.sheetName));\n const { columns, notes } = spec;\n\n const row = sheet.addRow(columns.map(column => escapeFormula(column.header)));\n row.eachCell((cell, index) => {\n cell.font = { bold: true };\n const note = notes[index - 1];\n if (note) cell.note = note;\n });\n row.commit();\n\n sheet.commit();\n await workbook.commit();\n}\n\n/** Excel rejects a worksheet name over 31 characters or containing any of `[]:*?/\\`. */\nfunction sheetName(name: string): string {\n const cleaned = String(name).replace(/[[\\]:*?/\\\\]/g, ' ').trim();\n return (cleaned || 'Import').slice(0, 31);\n}\n"]}
1
+ {"version":3,"sources":["../src/limits.ts","../src/errors.ts","../src/bytes.ts","../src/guard-zip.ts","../src/xlsx-source.ts","../src/read-sheet.ts","../src/run-import.ts","../src/write-template.ts"],"names":["zlib","Readable","ExcelJS","detectDelimiter","createCsvReader","completeFirstLine","padRow","cellText","validateMappingPlan","decimalMarkFor","applyMapping","arrayBoundFor","deriveImportColumns","suggestMapping","toCsv","escapeFormula"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * limits.ts — every bound this package enforces, and its default.\n *\n * **Every limit has a finite default.** A limit that is only a limit when a consumer\n * remembers to set one is documentation, not a guard, and this is the first\n * attacker-reachable code in the repository. A consumer who wants more raises the number;\n * nobody has to know the number exists to be protected by it.\n *\n * Each is enforced **during** streaming rather than after. A check that runs once the file is\n * in memory has already lost the thing it was checking.\n */\n\nexport interface ImportLimits {\n // ── The upload itself ──────────────────────────────────────────────────────\n\n /** Total bytes accepted from the request body. Counted as they arrive. */\n maxBytes: number;\n /** Multipart fields accepted, so a body of ten thousand tiny parts is not a parse loop. */\n maxFields: number;\n /** Bytes accepted in one non-file field — in practice the JSON `plan`. */\n maxFieldBytes: number;\n /** Bytes accepted in a field *name*, which is also attacker-chosen. */\n maxFieldNameBytes: number;\n /** Files accepted. One import is one file; more is either a mistake or an attack. */\n maxFiles: number;\n\n // ── The sheet ─────────────────────────────────────────────────────────────\n\n /** Data rows read. The header is not one of them. */\n maxRows: number;\n /** Columns read from the header row, and the width any row may reach. */\n maxColumns: number;\n /**\n * Characters in one cell.\n *\n * This is also the bound on `validators.pattern`: a config-supplied regex with\n * catastrophic backtracking is CPU-bound in the length of what it is run against, and on a\n * server that input is attacker-chosen. See SECURITY.md.\n */\n maxCellLength: number;\n\n // ── The zip an .xlsx actually is ──────────────────────────────────────────\n\n /** Total inflated bytes across every entry, checked *as they inflate*. */\n maxUncompressedBytes: number;\n /** Inflated-to-compressed ratio at which an entry is refused. */\n maxCompressionRatio: number;\n /** Entries in the archive. A workbook has tens; a bomb has thousands. */\n maxZipEntries: number;\n\n // ── The run ───────────────────────────────────────────────────────────────\n\n /**\n * Rows handed to `applyMapping`, and therefore to `onBatch`, at a time.\n *\n * Peak memory is a function of this number and not of the file's size. That is the entire\n * reason this package exists.\n */\n batchSize: number;\n /**\n * Row errors **retained**. The count and the truncation flag are always exact.\n *\n * A file where every row fails accumulates one error object per failing row, and fifty\n * thousand of those is the same unbounded growth as holding the file — the failure this\n * package exists to avoid, arrived at from the other direction.\n *\n * Lowering it costs a user *reasons*, never *counts*: `errorCount` and `failed` are tallied\n * as rows go past and do not depend on what was kept.\n */\n maxReportedErrors: number;\n /** Rows a preview returns as a sample. */\n sampleRows: number;\n\n // ── The clock ─────────────────────────────────────────────────────────────\n\n /** Milliseconds of silence on the request body before it is abandoned. Slowloris. */\n idleTimeoutMs: number;\n /** Milliseconds a whole request may take, however chatty it is. */\n totalTimeoutMs: number;\n}\n\n/**\n * Sized for \"a large legitimate export\", not for \"the largest thing imaginable\".\n *\n * 10 MB of xlsx is comfortably the fifty-thousand-row workbook that motivated the package;\n * `maxRows` is above it so the byte limit is the one a user meets first, and the row limit is\n * the backstop for a file that is mostly empty cells.\n */\nexport const DEFAULT_LIMITS: ImportLimits = {\n maxBytes: 10 * 1024 * 1024,\n maxFields: 16,\n maxFieldBytes: 1024 * 1024,\n maxFieldNameBytes: 200,\n maxFiles: 1,\n\n maxRows: 200_000,\n maxColumns: 512,\n maxCellLength: 32_768,\n\n maxUncompressedBytes: 200 * 1024 * 1024,\n maxCompressionRatio: 200,\n maxZipEntries: 512,\n\n batchSize: 500,\n maxReportedErrors: 200,\n sampleRows: 5,\n\n idleTimeoutMs: 30_000,\n totalTimeoutMs: 10 * 60_000,\n};\n\n/**\n * Fill in what a caller left out.\n *\n * A non-finite or non-positive override falls back to the default rather than being accepted:\n * `maxBytes: 0` from a mis-parsed environment variable would otherwise disable every upload,\n * and `maxBytes: Infinity` would disable the guard — the two ways a configuration mistake\n * turns a limit off, both of which look deliberate in a diff.\n */\nexport function resolveLimits(overrides?: Partial<ImportLimits>): ImportLimits {\n const out = { ...DEFAULT_LIMITS };\n if (!overrides) return out;\n\n for (const key of Object.keys(DEFAULT_LIMITS) as (keyof ImportLimits)[]) {\n const value = overrides[key];\n if (typeof value === 'number' && Number.isFinite(value) && value > 0) {\n out[key] = Math.floor(value);\n }\n }\n return out;\n}\n","/**\n * errors.ts — one error type, a closed set of codes, and an envelope that says nothing else.\n *\n * **Information disclosure is a threat, so the envelope is the guard.** A stack trace tells an\n * attacker the library versions in use and the filesystem layout of the host; a parser's own\n * message tells them which parser. So a response carries a machine-readable `code` and a\n * message this package wrote, and nothing that came from below it. Whatever the real cause\n * was stays on the server, reachable through `cause` for the consumer's logger.\n */\n\n/**\n * Why a request failed, in a form a client can branch on.\n *\n * Closed on purpose: a client that has to match on message text is a client that breaks when\n * the text improves, and the wire shape is shared with `ngx-dynamic-entity`.\n */\nexport type ImportErrorCode =\n /** No file part in the multipart body. */\n | 'NO_FILE'\n /** More file parts than `limits.maxFiles`. */\n | 'TOO_MANY_FILES'\n /** The body exceeded `limits.maxBytes`, or a field exceeded its own bound. */\n | 'TOO_LARGE'\n /** The bytes are not a format this package reads. Decided by content, not by filename. */\n | 'UNSUPPORTED_FORMAT'\n /** The bytes claim a format and are not valid in it. */\n | 'MALFORMED_FILE'\n /** A zip that inflates out of proportion to its size, or has too many entries. */\n | 'ARCHIVE_REFUSED'\n /** Rows, columns or one cell past the configured bound. */\n | 'SHEET_TOO_LARGE'\n /** No config is registered under the requested entity. */\n | 'UNKNOWN_ENTITY'\n /** The `plan` field is missing, is not JSON, or does not describe this config. */\n | 'INVALID_PLAN'\n /** The request took longer than `limits.totalTimeoutMs`, or stalled past the idle bound. */\n | 'TIMEOUT'\n /** The consumer's `onImport` threw. Nothing about *why* crosses the wire. */\n | 'IMPORT_FAILED'\n /** Anything else. Deliberately the least informative code there is. */\n | 'INTERNAL';\n\n/** The HTTP status each code answers with. Kept here so a route never has to decide. */\nconst STATUS: Record<ImportErrorCode, number> = {\n NO_FILE: 400,\n TOO_MANY_FILES: 400,\n TOO_LARGE: 413,\n UNSUPPORTED_FORMAT: 415,\n MALFORMED_FILE: 400,\n ARCHIVE_REFUSED: 400,\n SHEET_TOO_LARGE: 413,\n UNKNOWN_ENTITY: 404,\n INVALID_PLAN: 400,\n TIMEOUT: 408,\n IMPORT_FAILED: 500,\n INTERNAL: 500,\n};\n\n/**\n * What a failing route sends.\n *\n * `details` is present only for `INVALID_PLAN`, and carries `validateMappingPlan`'s own\n * problems — those describe the client's own plan against a config the client already has,\n * so they disclose nothing the caller did not send.\n */\nexport interface ImportErrorBody {\n error: {\n code: ImportErrorCode;\n message: string;\n details?: unknown;\n };\n}\n\nexport class ImportError extends Error {\n readonly code: ImportErrorCode;\n readonly status: number;\n readonly details?: unknown;\n\n constructor(code: ImportErrorCode, message: string, options: { cause?: unknown; details?: unknown } = {}) {\n super(message);\n this.name = 'ImportError';\n this.code = code;\n this.status = STATUS[code] ?? 500;\n this.details = options.details;\n if (options.cause !== undefined) (this as { cause?: unknown }).cause = options.cause;\n }\n}\n\n/**\n * Turn anything thrown into a response body.\n *\n * An `ImportError` says what it says. **Everything else becomes `INTERNAL` with a fixed\n * message** — that is the whole point, and the reason this is a function rather than a\n * `catch` block per route. A parser that throws `ENOENT: open '/srv/app/tmp/…'` must not be\n * the thing that answers the request.\n */\nexport function toErrorBody(error: unknown): { status: number; body: ImportErrorBody } {\n if (error instanceof ImportError) {\n return {\n status: error.status,\n body: {\n error: {\n code: error.code,\n message: error.message,\n ...(error.details === undefined ? {} : { details: error.details }),\n },\n },\n };\n }\n\n return {\n status: 500,\n body: { error: { code: 'INTERNAL', message: 'The import could not be completed.' } },\n };\n}\n","/**\n * bytes.ts — the three things every reader here needs from a byte stream.\n *\n * Deliberately framework-neutral: a `ByteSource` is anything you can `for await` bytes out of,\n * which a Node `Readable`, a `Buffer`, and a hand-rolled async generator all are. Nothing in\n * this file knows what a request is.\n */\n\nimport { ImportError } from './errors';\n\n/** Anything bytes can be pulled from — a request, a file handle, a buffer, a test fixture. */\nexport type ByteSource = AsyncIterable<Uint8Array> | Iterable<Uint8Array> | Uint8Array;\n\n/** Normalise the three shapes above into one async iterable. */\nexport async function* toByteStream(source: ByteSource): AsyncGenerator<Uint8Array> {\n if (source instanceof Uint8Array) {\n yield source;\n return;\n }\n if (Symbol.asyncIterator in source) {\n for await (const chunk of source as AsyncIterable<Uint8Array>) yield asBytes(chunk);\n return;\n }\n for (const chunk of source as Iterable<Uint8Array>) yield asBytes(chunk);\n}\n\n/** A Node stream in object-ish mode can hand back a string; treat it as UTF-8 bytes. */\nfunction asBytes(chunk: Uint8Array | string): Uint8Array {\n return typeof chunk === 'string' ? Buffer.from(chunk, 'utf8') : chunk;\n}\n\n/**\n * Refuse the upload at the byte that crosses the line, not once it has all arrived.\n *\n * Counting after the body is buffered is the guard that has already lost: the memory it was\n * protecting is spent by the time it runs. This throws mid-stream, and the caller destroys\n * the source — so a hostile 4 GB body costs `maxBytes` plus one chunk.\n */\nexport async function* limitBytes(\n source: AsyncIterable<Uint8Array>,\n maxBytes: number,\n): AsyncGenerator<Uint8Array> {\n let seen = 0;\n for await (const chunk of source) {\n seen += chunk.byteLength;\n if (seen > maxBytes) {\n throw new ImportError('TOO_LARGE', `The file is larger than the ${maxBytes} byte limit.`);\n }\n yield chunk;\n }\n}\n\n/**\n * Take the first `n` bytes without consuming them.\n *\n * Format is decided by what a file *is* rather than by what its name claims, which means\n * looking at its first four bytes before choosing a reader — and then handing those same\n * bytes to the reader that was chosen. Returning the head separately and re-yielding it is\n * the whole trick; buffering the file to seek back to zero is the thing being avoided.\n */\nexport async function peek(\n source: AsyncIterable<Uint8Array>,\n n: number,\n): Promise<{ head: Uint8Array; stream: AsyncGenerator<Uint8Array> }> {\n const iterator = source[Symbol.asyncIterator]();\n const taken: Uint8Array[] = [];\n let size = 0;\n\n while (size < n) {\n const next = await iterator.next();\n if (next.done) break;\n taken.push(next.value);\n size += next.value.byteLength;\n }\n\n const head = Buffer.concat(taken.map(c => Buffer.from(c.buffer, c.byteOffset, c.byteLength)));\n\n async function* replay(): AsyncGenerator<Uint8Array> {\n for (const chunk of taken) yield chunk;\n // `iterator`, not `source`: an async iterator is consumed once, and starting a second\n // `for await` over the source would either replay nothing or start a second read.\n for (let next = await iterator.next(); !next.done; next = await iterator.next()) {\n yield next.value;\n }\n }\n\n return { head: head.subarray(0, n), stream: replay() };\n}\n\n/** Best-effort close of whatever the bytes came from, once a guard has decided to stop. */\nexport function destroySource(source: unknown): void {\n const candidate = source as { destroy?: (error?: Error) => void; return?: () => unknown };\n try {\n if (typeof candidate?.destroy === 'function') candidate.destroy();\n else if (typeof candidate?.return === 'function') candidate.return();\n } catch {\n // Already closed, or never closeable. There is nothing useful to do about it here, and\n // throwing from a cleanup path would replace the real error with this one.\n }\n}\n","/**\n * guard-zip.ts — open the archive ourselves, and hand the parser only what it should see.\n *\n * An `.xlsx` is a zip, and a zip is the one upload format where the bytes on the wire say\n * nothing about the work they cause. Ten kilobytes can inflate to ten gigabytes. `maxBytes`\n * bounds what arrives and is therefore the wrong guard entirely for this.\n *\n * **Why the archive is rebuilt rather than passed through.** Two reasons, and the second was a\n * surprise:\n *\n * 1. exceljs inflates internally and reports nothing while it does, so a limit applied to its\n * output is applied after the memory it was protecting has been spent. The inflated size is\n * knowable early only here, so this inflates every entry to *measure* it and refuses at the\n * byte that crosses a bound.\n * 2. **exceljs's streaming reader loses entries when a worksheet precedes the shared strings.**\n * It defers such a worksheet to a temp file, and the archive's entry stream then ends at\n * that entry — `xl/workbook.xml`, which exceljs's own writer puts *last*, never arrives,\n * and the reader throws `Cannot read properties of undefined (reading 'sheets')`. Verified\n * by replicating its parse loop: sixteen entries became eight, on two runs out of three.\n * Reordering the archive so the strings, the relationships and the workbook all precede the\n * worksheet avoids the deferral entirely — thirty-two runs, no failures, chunked and whole.\n *\n * Rebuilding also means the parser sees **only the parts an import needs**. The theme, the\n * document properties, the drawings, the printer settings and every worksheet after the first\n * are dropped rather than parsed, and a part that is never parsed cannot be hostile.\n *\n * What this costs is the retained archive, held **compressed** and therefore bounded by\n * `maxBytes` — ten megabytes by default, against a fifty-thousand-row workbook's few. Entry\n * data is re-packed exactly as it arrived; nothing is inflated into memory to be kept.\n */\n\nimport zlib from 'node:zlib';\nimport { ImportError } from './errors';\nimport type { ImportLimits } from './limits';\n\n/** `PK\\x03\\x04` — the start of a local file header. */\nconst LOCAL_HEADER = 0x04034b50;\n/** `PK\\x01\\x02` — a central directory record. */\nconst CENTRAL_HEADER = 0x02014b50;\n/** `PK\\x05\\x06` — the end-of-central-directory record. */\nconst END_OF_CENTRAL = 0x06054b50;\n/** `PK\\x07\\x08` — the optional signature on a data descriptor. */\nconst DATA_DESCRIPTOR = 0x08074b50;\n/** Bytes in a local file header before the name. */\nconst HEADER_SIZE = 30;\n/** Bit 3 of the general-purpose flags: sizes follow the data rather than preceding it. */\nconst HAS_DATA_DESCRIPTOR = 0x08;\n/** A size of exactly this means \"see the zip64 extra field\", which is a size we do not trust. */\nconst ZIP64_SENTINEL = 0xffffffff;\n/** How much is handed to the inflater at once. Bounds the push-back after a stream ends. */\nconst SLICE = 64 * 1024;\n\nconst STORED = 0;\nconst DEFLATED = 8;\n\n/** The largest size a non-zip64 header can express. */\nconst ZIP32_MAX = 0xffffffff;\n\n/**\n * The parts an import reads, in the order exceljs must meet them.\n *\n * Relationships, the workbook and the shared strings before any worksheet — that ordering is\n * what keeps exceljs off its deferral path. Everything not named here, and every worksheet\n * after the first, is dropped.\n */\nconst WANTED = [\n '[Content_Types].xml',\n '_rels/.rels',\n 'xl/_rels/workbook.xml.rels',\n 'xl/workbook.xml',\n 'xl/styles.xml',\n 'xl/sharedStrings.xml',\n];\n\nconst WORKSHEET = /^xl\\/worksheets\\/sheet(\\d+)\\.xml$/;\n\n/**\n * The number in `sheetN.xml`, or `null` for anything that is not a worksheet.\n *\n * **Which worksheet an import reads is never decided by byte order.** A user means the first\n * *tab*, and an archive may list `sheet2.xml` before `sheet1.xml`.\n *\n * The lowest number is not *quite* tab order either: the real order lives in `workbook.xml`'s\n * `<sheets>` and the relationship map, which this does not parse. It is a great deal closer\n * than byte order, and it is the same number exceljs matches a worksheet by.\n */\nfunction sheetNumber(name: string): number | null {\n const match = WORKSHEET.exec(name);\n return match ? Number(match[1]) : null;\n}\n\nconst refuse = (message: string): never => {\n throw new ImportError('ARCHIVE_REFUSED', message);\n};\n\n// ─── CRC-32, because a rebuilt entry needs a checksum the reader will accept ──────────────\n\nconst CRC_TABLE = (() => {\n const table = new Int32Array(256);\n for (let i = 0; i < 256; i++) {\n let c = i;\n for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;\n table[i] = c;\n }\n return table;\n})();\n\nfunction crc32(current: number, bytes: Uint8Array): number {\n let c = ~current;\n for (let i = 0; i < bytes.length; i++) c = CRC_TABLE[(c ^ bytes[i]) & 0xff] ^ (c >>> 8);\n return ~c >>> 0;\n}\n\n// ─── Reading ──────────────────────────────────────────────────────────────────────────────\n\ninterface Entry {\n name: string;\n method: number;\n /** The entry's data exactly as it arrived, still compressed. */\n data: Buffer;\n inflated: number;\n crc: number;\n}\n\n/**\n * A pull-reader over the incoming bytes.\n *\n * `pushBack` exists for one case: a deflate stream that ends part-way through the slice last\n * handed to the inflater. The tail of that slice belongs to the data descriptor and has to be\n * read again as structure rather than as data.\n */\nclass ZipScanner {\n private buffer: Buffer = Buffer.alloc(0);\n private eof = false;\n\n constructor(private readonly iterator: AsyncIterator<Uint8Array>) {}\n\n get available(): number {\n return this.buffer.length;\n }\n\n /** Pull until at least `n` bytes are buffered. False means the stream ended first. */\n async need(n: number): Promise<boolean> {\n while (this.buffer.length < n && !this.eof) {\n const next = await this.iterator.next();\n if (next.done) {\n this.eof = true;\n break;\n }\n const chunk = Buffer.from(next.value.buffer, next.value.byteOffset, next.value.byteLength);\n this.buffer = this.buffer.length ? Buffer.concat([this.buffer, chunk]) : chunk;\n }\n return this.buffer.length >= n;\n }\n\n peek(n: number): Buffer {\n return this.buffer.subarray(0, Math.min(n, this.buffer.length));\n }\n\n take(n: number): Buffer {\n const chunk = this.buffer.subarray(0, n);\n this.buffer = this.buffer.subarray(n);\n return chunk;\n }\n\n pushBack(bytes: Buffer): void {\n if (bytes.length) this.buffer = Buffer.concat([bytes, this.buffer]);\n }\n}\n\n/**\n * Resolve when the inflater has processed this chunk, or reject if it could not.\n *\n * The `error` listener is not belt and braces. When zlib rejects the data outright — which is\n * what happens the moment an entry claims to be deflated and is not — it emits `error` and\n * **never calls the write callback**, so a promise built only on that callback never settles.\n * Verified: a sixteen-byte entry of plain text hung a sixty-second test instead of failing it.\n */\nfunction writeChunk(inflater: zlib.InflateRaw, chunk: Buffer): Promise<void> {\n return new Promise((resolve, reject) => {\n const onError = (error: Error): void => {\n inflater.removeListener('error', onError);\n reject(error);\n };\n inflater.once('error', onError);\n inflater.write(chunk, error => {\n inflater.removeListener('error', onError);\n if (error) reject(error);\n else resolve();\n });\n });\n}\n\n/** Give the event loop a turn, so `data` and `end` handlers run before the next decision. */\nconst settle = (): Promise<void> => new Promise(resolve => setImmediate(resolve));\n\ninterface Budget {\n /** Inflated bytes across the whole archive so far. */\n total: number;\n /** Retained compressed bytes so far. */\n retained: number;\n /** Entries seen so far. */\n entries: number;\n}\n\n/**\n * Read one deflated entry, measuring what it inflates to and keeping its bytes if wanted.\n *\n * `declared` is the compressed length when the header gave one, and `null` when it did not —\n * a streaming writer (exceljs's own included) writes the sizes *after* the data and leaves the\n * header's at zero, and a hostile file would do exactly the same on purpose. Neither path\n * trusts a declared size for the accounting: the numbers checked are the bytes that came out.\n */\nasync function readDeflated(\n scanner: ZipScanner,\n budget: Budget,\n limits: ImportLimits,\n name: string,\n declared: number | null,\n keep: boolean,\n): Promise<{ data: Buffer; inflated: number; crc: number }> {\n /**\n * Retention counted as it grows, not once the entry is complete.\n *\n * Checking afterwards is too late: by then the memory the limit protects has been spent.\n * `limitBytes` caps the stream upstream too, but this guard must not depend on that.\n */\n const retain = (slice: Buffer): void => {\n budget.retained += slice.length;\n if (budget.retained > limits.maxBytes) {\n refuse(`The workbook is larger than the ${limits.maxBytes} byte limit.`);\n }\n };\n const inflater = zlib.createInflateRaw();\n const kept: Buffer[] = [];\n let produced = 0;\n let consumed = 0;\n let crc = 0;\n let ended = false;\n let failure: Error | null = null;\n let breach: ImportError | null = null;\n\n /** Both bounds, checked on every chunk that comes out rather than on the entry's total. */\n const check = (): ImportError | null => {\n if (produced > limits.maxUncompressedBytes || budget.total + produced > limits.maxUncompressedBytes) {\n return new ImportError(\n 'ARCHIVE_REFUSED',\n `The workbook inflates to more than the ${limits.maxUncompressedBytes} byte limit.`,\n );\n }\n // Measured against what has actually been read, so an unfinished entry cannot hide behind\n // a ratio that would only be computed at the end. The floor keeps a tiny entry, where the\n // deflate header alone dominates, from tripping it.\n if (produced > 64 * 1024 && produced / Math.max(consumed, 1) > limits.maxCompressionRatio) {\n return new ImportError(\n 'ARCHIVE_REFUSED',\n `An entry in the workbook inflates more than ${limits.maxCompressionRatio} times — refusing it.`,\n );\n }\n return null;\n };\n\n inflater.on('data', chunk => {\n produced += chunk.length;\n crc = crc32(crc, chunk);\n if (!breach) breach = check();\n });\n inflater.on('end', () => {\n ended = true;\n });\n inflater.on('error', error => {\n failure = error as Error;\n });\n\n try {\n for (;;) {\n const remaining = declared === null ? Number.POSITIVE_INFINITY : declared - consumed;\n if (remaining <= 0) break;\n if (ended || failure || breach) break;\n\n if (!(await scanner.need(1))) refuse(`The workbook ends part-way through \"${name}\".`);\n\n const slice = Buffer.from(scanner.take(Math.min(SLICE, remaining, scanner.available)));\n // Counted *before* the write, because the inflater emits while the write is awaited and\n // the ratio check runs in that handler. Counting after left `consumed` at zero for the\n // whole of the first slice, so every entry over 64 KB looked like an infinite ratio —\n // a guard that refused a perfectly ordinary fifty-thousand-row workbook.\n consumed += slice.length;\n await writeChunk(inflater, slice);\n if (keep) {\n retain(slice);\n kept.push(slice);\n }\n await settle();\n\n if (ended && declared === null) {\n // `bytesWritten` is the input the engine actually consumed, which is exactly the\n // length of the deflate stream. Anything past it belongs to the data descriptor.\n const overshoot = consumed - inflater.bytesWritten;\n if (overshoot > 0) {\n scanner.pushBack(slice.subarray(slice.length - overshoot));\n consumed -= overshoot;\n if (keep) {\n kept[kept.length - 1] = slice.subarray(0, slice.length - overshoot);\n budget.retained -= overshoot;\n }\n }\n }\n }\n\n // A declared length longer than the deflate stream: the rest of the region is still this\n // entry's, so it is consumed rather than re-parsed. Guessing where the next header starts\n // is how a scanner desynchronises.\n while (declared !== null && consumed < declared && !breach) {\n if (!(await scanner.need(1))) refuse(`The workbook ends part-way through \"${name}\".`);\n const slice = Buffer.from(scanner.take(Math.min(declared - consumed, SLICE, scanner.available)));\n consumed += slice.length;\n if (keep) {\n retain(slice);\n kept.push(slice);\n }\n }\n } catch (error) {\n if (error instanceof ImportError) throw error;\n // zlib's own complaint, which names zlib. What a response carries instead is this.\n throw new ImportError('MALFORMED_FILE', 'The workbook could not be read.', { cause: error });\n } finally {\n inflater.destroy();\n }\n\n if (breach) throw breach;\n if (failure) {\n throw new ImportError('MALFORMED_FILE', 'The workbook could not be read.', { cause: failure });\n }\n\n budget.total += produced;\n return { data: keep ? Buffer.concat(kept) : Buffer.alloc(0), inflated: produced, crc };\n}\n\n/** Consume a stored entry, which inflates to exactly itself. */\nasync function readStored(\n scanner: ZipScanner,\n budget: Budget,\n limits: ImportLimits,\n name: string,\n size: number,\n keep: boolean,\n): Promise<{ data: Buffer; inflated: number; crc: number }> {\n const kept: Buffer[] = [];\n let crc = 0;\n let left = size;\n\n while (left > 0) {\n if (!(await scanner.need(1))) refuse(`The workbook ends part-way through \"${name}\".`);\n const slice = Buffer.from(scanner.take(Math.min(left, SLICE, scanner.available)));\n crc = crc32(crc, slice);\n if (keep) {\n // Counted as it grows, for the reason given in `readDeflated`.\n budget.retained += slice.length;\n if (budget.retained > limits.maxBytes) {\n refuse(`The workbook is larger than the ${limits.maxBytes} byte limit.`);\n }\n kept.push(slice);\n }\n left -= slice.length;\n budget.total += slice.length;\n if (budget.total > limits.maxUncompressedBytes) {\n refuse(`The workbook inflates to more than the ${limits.maxUncompressedBytes} byte limit.`);\n }\n }\n\n return { data: keep ? Buffer.concat(kept) : Buffer.alloc(0), inflated: size, crc };\n}\n\n/** Consume the data descriptor that follows an entry whose header declared no sizes. */\nasync function skipDescriptor(scanner: ZipScanner): Promise<void> {\n if (!(await scanner.need(4))) return;\n const signed = scanner.peek(4).readUInt32LE(0) === DATA_DESCRIPTOR;\n const base = signed ? 4 : 0;\n\n // crc32 plus two sizes, four bytes each — or eight each under zip64. What follows the\n // descriptor is the thing that says which, so both lengths are tried in order.\n for (const width of [base + 12, base + 20]) {\n if (!(await scanner.need(width + 4))) break;\n const next = scanner.peek(width + 4).readUInt32LE(width);\n if (next === LOCAL_HEADER || next === CENTRAL_HEADER) {\n scanner.take(width);\n return;\n }\n }\n\n if (await scanner.need(base + 12)) scanner.take(base + 12);\n}\n\n// ─── Writing ──────────────────────────────────────────────────────────────────────────────\n\n/** Repack the kept entries as a zip, in the order the parser needs to meet them. */\nfunction repack(entries: Entry[]): Buffer {\n const parts: Buffer[] = [];\n const directory: Buffer[] = [];\n let offset = 0;\n\n for (const entry of entries) {\n const name = Buffer.from(entry.name, 'utf8');\n\n const local = Buffer.alloc(HEADER_SIZE);\n local.writeUInt32LE(LOCAL_HEADER, 0);\n local.writeUInt16LE(20, 4); // version needed\n local.writeUInt16LE(0, 6); // flags: sizes are right here, so no data descriptor\n local.writeUInt16LE(entry.method, 8);\n local.writeUInt32LE(entry.crc, 14);\n local.writeUInt32LE(entry.data.length, 18);\n local.writeUInt32LE(entry.inflated, 22);\n local.writeUInt16LE(name.length, 26);\n parts.push(local, name, entry.data);\n\n const central = Buffer.alloc(46);\n central.writeUInt32LE(CENTRAL_HEADER, 0);\n central.writeUInt16LE(20, 4); // version made by\n central.writeUInt16LE(20, 6); // version needed\n central.writeUInt16LE(entry.method, 10);\n central.writeUInt32LE(entry.crc, 16);\n central.writeUInt32LE(entry.data.length, 20);\n central.writeUInt32LE(entry.inflated, 24);\n central.writeUInt16LE(name.length, 28);\n central.writeUInt32LE(offset, 42);\n directory.push(central, name);\n\n offset += local.length + name.length + entry.data.length;\n }\n\n const directoryBytes = Buffer.concat(directory);\n const end = Buffer.alloc(22);\n end.writeUInt32LE(END_OF_CENTRAL, 0);\n end.writeUInt16LE(entries.length, 8);\n end.writeUInt16LE(entries.length, 10);\n end.writeUInt32LE(directoryBytes.length, 12);\n end.writeUInt32LE(offset, 16);\n\n return Buffer.concat([...parts, directoryBytes, end]);\n}\n\n/**\n * Walk an archive under every archive-shaped bound, and return the parts an import needs.\n *\n * Parsing stops at the first thing that is not a local file header — the central directory —\n * because nothing after the last entry inflates and there is nothing there to guard.\n */\nexport async function guardZip(\n source: AsyncIterable<Uint8Array>,\n limits: ImportLimits,\n): Promise<Buffer> {\n const scanner = new ZipScanner(source[Symbol.asyncIterator]());\n const budget: Budget = { total: 0, retained: 0, entries: 0 };\n const kept = new Map<string, Entry>();\n /** The lowest-numbered worksheet seen so far, which is the one an import reads. */\n let sheet: { name: string; number: number } | null = null;\n\n for (;;) {\n if (!(await scanner.need(4))) break;\n if (scanner.peek(4).readUInt32LE(0) !== LOCAL_HEADER) break;\n\n if (!(await scanner.need(HEADER_SIZE))) {\n refuse('The workbook ends part-way through an entry header.');\n }\n\n const header = Buffer.from(scanner.peek(HEADER_SIZE));\n const flags = header.readUInt16LE(6);\n const method = header.readUInt16LE(8);\n const declaredCompressed = header.readUInt32LE(18);\n const nameLength = header.readUInt16LE(26);\n const extraLength = header.readUInt16LE(28);\n\n if (++budget.entries > limits.maxZipEntries) {\n refuse(`The workbook has more than ${limits.maxZipEntries} entries.`);\n }\n\n if (!(await scanner.need(HEADER_SIZE + nameLength + extraLength))) {\n refuse('The workbook ends part-way through an entry header.');\n }\n const name = scanner.peek(HEADER_SIZE + nameLength).subarray(HEADER_SIZE).toString('utf8');\n scanner.take(HEADER_SIZE + nameLength + extraLength);\n\n // One worksheet and no other: a mapping plan addresses one sheet's columns, and carrying\n // the rest would be holding data nothing reads. Which one is decided by `sheetNumber`\n // rather than by the order the archive happens to list them in — see that function.\n const number = sheetNumber(name);\n const keep =\n number === null\n ? WANTED.includes(name) && !kept.has(name)\n : sheet === null || number < sheet.number;\n\n // The superseded sheet goes **before** the new one is read, not after. Releasing it\n // afterwards means holding two at once at the moment of changeover, which makes the\n // retention bound depend on the order the archive lists its sheets in: the same workbook\n // sails through ascending and is refused descending.\n if (keep && number !== null && sheet) {\n budget.retained -= kept.get(sheet.name)?.data.length ?? 0;\n kept.delete(sheet.name);\n sheet = null;\n }\n\n const streamed = (flags & HAS_DATA_DESCRIPTOR) !== 0 || declaredCompressed === ZIP64_SENTINEL;\n let read: { data: Buffer; inflated: number; crc: number };\n\n if (method === STORED) {\n if (streamed) {\n // A stored entry whose length is written only afterwards cannot be found the end of\n // without seeking, and seeking is what streaming means not doing. No real writer\n // produces one; a file that does is either broken or trying something.\n refuse(`Entry \"${name}\" is stored without a declared size.`);\n }\n read = await readStored(scanner, budget, limits, name, declaredCompressed, keep);\n } else if (method === DEFLATED) {\n read = await readDeflated(\n scanner,\n budget,\n limits,\n name,\n streamed ? null : declaredCompressed,\n keep,\n );\n if (streamed) await skipDescriptor(scanner);\n } else {\n return refuse(`Entry \"${name}\" uses compression method ${method}, which is not supported.`);\n }\n\n if (keep) {\n // The rebuilt archive is written without zip64, so a size a 32-bit header cannot express\n // would be silently truncated. `maxUncompressedBytes` keeps this out of reach at its\n // default; it is a consumer-settable limit, so the ceiling is checked rather than assumed.\n if (read.data.length > ZIP32_MAX || read.inflated > ZIP32_MAX) {\n refuse(`Entry \"${name}\" is too large to repack without zip64.`);\n }\n kept.set(name, { name, method, data: read.data, inflated: read.inflated, crc: read.crc });\n if (number !== null) sheet = { name, number };\n }\n }\n\n if (!sheet) {\n // Any zip starts `PK`, so a `.docx` or a plain archive reaches here too. Without a workbook\n // part it is not a broken workbook but a different kind of file, and saying so is what\n // tells the user to export the right thing rather than to repair this one.\n if (!kept.has('xl/workbook.xml')) {\n throw new ImportError(\n 'UNSUPPORTED_FORMAT',\n 'This is a zip archive that is not an Excel workbook. Upload an .xlsx or .csv file.',\n );\n }\n throw new ImportError('MALFORMED_FILE', 'This workbook has no worksheet.');\n }\n\n const ordered = [...WANTED, sheet.name]\n .map(wanted => kept.get(wanted))\n .filter((entry): entry is Entry => entry !== undefined);\n\n return repack(ordered);\n}\n","/**\n * xlsx-source.ts — a workbook, one row at a time.\n *\n * exceljs's streaming reader does the XML; this file does the three things around it that\n * decide whether a server-side xlsx import matches a browser one.\n *\n * 1. **Every byte goes through `guardZip` first.** exceljs inflates internally and reports\n * nothing while it does, so the only place an archive's real size is knowable early is\n * before it gets there.\n * 2. **Blank rows are put back.** exceljs skips them — a sheet with a gap emits row 3 then\n * row 5 — and a reader that passed that through would shift every later row number in\n * every error message, and disagree with the CSV path about `skipped`. The row numbers a\n * user is shown are the ones in their spreadsheet's gutter, so the gaps are synthesised.\n * 3. **Cells keep their types.** A date cell arrives as a `Date` at UTC midnight and is\n * handed on as one, because `coerceCell` reads a typed cell as its type. Stringifying here\n * to tidy the signature is the exact defect this feature already fixed once.\n *\n * **What this holds, stated rather than discovered:** the archive's wanted parts, kept\n * **compressed** and therefore bounded by `maxBytes` — ten megabytes by default, against a\n * fifty-thousand-row workbook's few. That is the cost of `guardZip` rebuilding the archive\n * rather than forwarding it, and the reasons it does are in that file. Row data is never\n * accumulated, which is the part that scales with the file.\n */\n\nimport ExcelJS from 'exceljs';\nimport { Readable } from 'node:stream';\nimport { ImportError } from './errors';\nimport { guardZip } from './guard-zip';\nimport type { ImportLimits } from './limits';\n\n/**\n * Turn one exceljs cell value into the value a record should be built from.\n *\n * A formula cell yields its **result**, never its formula: a sheet someone uploaded may\n * contain anything, and importing the text `=SUM(A1:A9)` into a field is importing a string\n * the user never saw. An error cell yields its error text, which fails coercion loudly rather\n * than importing as blank.\n */\nexport function cellValue(raw: unknown): unknown {\n if (raw === null || raw === undefined) return '';\n if (raw instanceof Date) return raw;\n if (typeof raw !== 'object') return raw;\n\n const cell = raw as Record<string, unknown>;\n if (Array.isArray(cell['richText'])) {\n return (cell['richText'] as { text?: unknown }[]).map(part => String(part?.text ?? '')).join('');\n }\n if ('formula' in cell || 'sharedFormula' in cell) return cellValue(cell['result']);\n if ('error' in cell) return String(cell['error']);\n if ('text' in cell) return String(cell['text']);\n return '';\n}\n\n/** `row.values` is one-based with a hole at index 0, and sparse wherever a cell was empty. */\nfunction rowCells(values: unknown): unknown[] {\n if (!Array.isArray(values)) return [];\n const out: unknown[] = [];\n for (let i = 1; i < values.length; i++) out.push(cellValue(values[i]));\n return out;\n}\n\n/**\n * Rows of the first worksheet, starting at spreadsheet row 1.\n *\n * The first worksheet and no other: a mapping plan addresses one sheet's columns, and picking\n * a second one silently would import a different file from the one the user previewed.\n */\nexport async function* xlsxRows(\n source: AsyncIterable<Uint8Array>,\n limits: ImportLimits,\n): AsyncGenerator<unknown[]> {\n const archive = await guardZip(source, limits);\n const guarded = Readable.from([archive]);\n const reader = new ExcelJS.stream.xlsx.WorkbookReader(guarded, {\n // Nothing decorative is parsed. Each of these is XML this import has no use for, and\n // every part not parsed is a part that cannot be hostile.\n entries: 'ignore',\n hyperlinks: 'ignore',\n // Cached because a string cell is a reference into this table and an unresolved reference\n // is not a value. See the note at the top about what that costs.\n sharedStrings: 'cache',\n // Cached because it is what tells a numeric cell it is a date.\n styles: 'cache',\n worksheets: 'emit',\n });\n\n /**\n * The next spreadsheet row number expected, so a gap can be filled rather than skipped.\n *\n * **Do not race each pull against a stream-error promise.** `guardZip` validates the whole\n * archive before the parser is constructed, so a malformed zip cannot stall this loop — and\n * racing every row against one long-lived promise attaches a reaction per row that is never\n * released (about 1.9 KB a row, 75 MB over fifty thousand rows). A request that hangs for any\n * other reason is the Express adapter's `totalTimeoutMs` to answer; that layer owns a clock.\n */\n let expected = 1;\n let yielded = false;\n\n try {\n // The first worksheet and no other: a mapping plan addresses one sheet's columns, and\n // picking up a second silently would import a different file from the one the user\n // previewed.\n for await (const worksheet of reader) {\n for await (const row of worksheet) {\n const number = typeof row.number === 'number' ? row.number : expected;\n\n // **Refused before the gap is generated, not after it is counted.** A sheet holding two\n // cells and `r=\"500000\"` is under two kilobytes, and synthesising its way to the row\n // limit cost a quarter of a second of CPU per request before the row guard noticed —\n // the same \"enforce the limit after doing the work\" shape this package keeps finding in\n // itself. The row number says how big the sheet claims to be, so it can say it first.\n if (number - 1 > limits.maxRows) {\n throw new ImportError('SHEET_TOO_LARGE', `The sheet has more than ${limits.maxRows} rows.`);\n }\n\n // A blank row is a row. `applyMapping` counts it as skipped, and every row after it\n // keeps the number the user sees.\n while (expected < number) {\n yield [];\n expected++;\n }\n yield rowCells(row.values);\n yielded = true;\n expected = number + 1;\n }\n break;\n }\n\n if (!yielded) {\n // A worksheet that produced not one row, or no worksheet at all. Either the sheet XML is\n // not readable — a SAX parser given rubbish inside a well-formed zip stops rather than\n // complains — or the sheet is genuinely empty. Neither is a file anyone meant to import,\n // and \"imported 0 rows\" is the wrong thing to tell someone whose file plainly has data.\n throw new ImportError('MALFORMED_FILE', 'This workbook has no readable rows.');\n }\n } catch (error) {\n // A guard that already decided says what it decided. Anything else is the parser refusing\n // the file, and what it says about itself is not for a response to carry — exceljs's SAX\n // parser refuses an undefined entity, which is how a billion-laughs sheet arrives here.\n if (error instanceof ImportError) throw error;\n throw new ImportError('MALFORMED_FILE', 'This is not a readable workbook.', { cause: error });\n } finally {\n guarded.destroy();\n }\n}\n","/**\n * read-sheet.ts — bytes in, a header row and an async stream of rows out.\n *\n * Two properties this file exists to hold:\n *\n * 1. **Format is decided by content, never by the filename.** `.csv` is client-controlled and\n * an attacker who wants a zip parsed will name their zip `.csv`. The first four bytes are\n * not. Both readers then meet the same row guards, so a workbook cannot buy itself a\n * larger sheet by being a workbook.\n * 2. **Rows arrive one at a time.** Nothing here ever holds the sheet. `rows` is an\n * `AsyncIterable`, so a consumer that stops pulling stops the read.\n *\n * Cells are `unknown[]`, never `string[]`. An xlsx cell is a number, a boolean or a `Date`,\n * and core's `coerceCell` reads a typed cell as its type — stringifying here to make the\n * signature tidier is exactly the bug that would reintroduce.\n */\n\nimport {\n completeFirstLine,\n createCsvReader,\n detectDelimiter,\n padRow,\n type CsvDelimiter,\n type CsvReader,\n} from '@dynamic-entity/core';\nimport { destroySource, limitBytes, peek, toByteStream, type ByteSource } from './bytes';\nimport { sampleText } from './cell-text';\nimport { ImportError } from './errors';\nimport { resolveLimits, type ImportLimits } from './limits';\nimport { xlsxRows } from './xlsx-source';\n\n/** The formats this package reads. Both are decided by magic bytes. */\nexport type SheetFormat = 'csv' | 'xlsx';\n\nexport interface SheetSource {\n /** What the bytes turned out to be, which may not be what the filename claimed. */\n format: SheetFormat;\n headers: string[];\n /** Data rows, one at a time. Pulling stops the read; not pulling stops it too. */\n rows: AsyncIterable<unknown[]>;\n /** The field separator a CSV turned out to use. Absent for a workbook. */\n delimiter?: CsvDelimiter;\n}\n\nexport interface ReadSheetOptions {\n stream: ByteSource;\n /** Display only — it names the file in an error message and decides nothing. */\n filename?: string;\n limits?: Partial<ImportLimits>;\n}\n\n/** Enough bytes to tell a zip from text, with room for a UTF-16 BOM. */\nconst MAGIC_BYTES = 8;\n\nconst ZIP = [0x50, 0x4b]; // \"PK\"\nconst OLE2 = [0xd0, 0xcf, 0x11, 0xe0]; // a pre-2007 .xls, which is a compound file, not a zip\nconst UTF16_LE = [0xff, 0xfe];\nconst UTF16_BE = [0xfe, 0xff];\n\nconst startsWith = (bytes: Uint8Array, signature: number[]): boolean =>\n signature.every((byte, i) => bytes[i] === byte);\n\n/**\n * What these bytes actually are.\n *\n * Exported because the multipart layer wants the same answer for the same reason, and because\n * a second copy of a magic-byte table is a second table to be wrong.\n */\nexport function detectFormat(head: Uint8Array): SheetFormat {\n if (startsWith(head, ZIP)) return 'xlsx';\n\n if (startsWith(head, OLE2)) {\n throw new ImportError(\n 'UNSUPPORTED_FORMAT',\n 'This is a pre-2007 Excel file (.xls). Save it as .xlsx or .csv and try again.',\n );\n }\n if (startsWith(head, UTF16_LE) || startsWith(head, UTF16_BE)) {\n throw new ImportError(\n 'UNSUPPORTED_FORMAT',\n 'This file is UTF-16 encoded. Save it as UTF-8 CSV and try again.',\n );\n }\n\n // Anything else is read as delimited text. A binary file that reaches here produces a sheet\n // of nonsense rather than an error, which is the right trade: refusing everything that does\n // not look like text would refuse legitimately unusual encodings too, and nonsense rows are\n // reported per row by the mapping rather than as a mysterious rejection.\n return 'csv';\n}\n\n/**\n * Decode UTF-8 across chunk boundaries and feed core's incremental reader.\n *\n * The separator is not known until the header line has been seen, so text is held until the\n * first line break outside quotes — the same line `parseCsv` detects from in a browser, so\n * both read one file the same way. Holding it is bounded: a header longer than the longest\n * legal row is refused rather than buffered without end.\n */\nasync function* csvRows(\n stream: AsyncIterable<Uint8Array>,\n limits: ImportLimits,\n found: { delimiter?: CsvDelimiter },\n): AsyncGenerator<string[]> {\n // Not `fatal`: a sheet is user data from somewhere else, and one bad byte replaced with\n // U+FFFD is a cell the importer can report on. Rejecting the whole file for it is a worse\n // answer to a problem the user can see in exactly one cell.\n const decoder = new TextDecoder('utf-8');\n const maxHeader = limits.maxCellLength * limits.maxColumns + limits.maxColumns;\n let held = '';\n let reader: CsvReader | null = null;\n\n const open = (headerLine: string): CsvReader => {\n found.delimiter = detectDelimiter(headerLine);\n return createCsvReader({ delimiter: found.delimiter });\n };\n\n for await (const chunk of stream) {\n // `stream: true` is what holds a multi-byte character split across a 64 KB boundary.\n const text = decoder.decode(chunk, { stream: true });\n if (!reader) {\n held += text;\n const line = completeFirstLine(held);\n if (line === null) {\n if (held.length > maxHeader) {\n throw new ImportError('SHEET_TOO_LARGE', 'The header row is longer than any row may be.');\n }\n continue;\n }\n reader = open(line);\n for (const row of reader.push(held)) yield row;\n held = '';\n continue;\n }\n for (const row of reader.push(text)) yield row;\n }\n\n const rest = held + decoder.decode();\n reader ??= open(completeFirstLine(rest) ?? rest);\n for (const row of reader.push(rest)) yield row;\n for (const row of reader.end()) yield row;\n}\n\n/**\n * Enforce the row, column and cell bounds as rows go past, and pad short rows.\n *\n * Padding matches `parseCsv`, through the same `padRow` core exports, so a file imported on a\n * server and the same file imported in a browser produce the same rows. A second padding rule\n * here would be a place for the two halves to disagree, silently, about a row that ended early.\n */\nexport async function* guardRows(\n rows: AsyncIterable<unknown[]>,\n width: number,\n limits: ImportLimits,\n): AsyncGenerator<unknown[]> {\n let seen = 0;\n\n for await (const row of rows) {\n if (++seen > limits.maxRows) {\n throw new ImportError(\n 'SHEET_TOO_LARGE',\n `The sheet has more than ${limits.maxRows} rows.`,\n );\n }\n if (row.length > limits.maxColumns) {\n throw new ImportError(\n 'SHEET_TOO_LARGE',\n `Row ${seen + 1} has more than ${limits.maxColumns} columns.`,\n );\n }\n for (const cell of row) {\n if (typeof cell === 'string' && cell.length > limits.maxCellLength) {\n throw new ImportError(\n 'SHEET_TOO_LARGE',\n `A cell in row ${seen + 1} is longer than ${limits.maxCellLength} characters.`,\n );\n }\n }\n yield padRow(row, width);\n }\n}\n\n/**\n * Open a sheet: read its header row, and hand back the rest as a stream.\n *\n * The header row is pulled eagerly because everything downstream — the column count, the\n * mapping suggestion, the padding width — is a function of it. Exactly one row is held.\n */\nexport async function readSheet(options: ReadSheetOptions): Promise<SheetSource> {\n const limits = resolveLimits(options.limits);\n\n try {\n const counted = limitBytes(toByteStream(options.stream), limits.maxBytes);\n const { head, stream } = await peek(counted, MAGIC_BYTES);\n const format = detectFormat(head);\n const found: { delimiter?: CsvDelimiter } = {};\n const rows = format === 'xlsx' ? xlsxRows(stream, limits) : csvRows(stream, limits, found);\n\n const iterator = rows[Symbol.asyncIterator]();\n const first = await iterator.next();\n // Headers are text whichever format they came from — a column heading is a label, and a\n // plan addresses a column by index anyway. `sampleText` rather than `String` so a header\n // cell that is somehow a date reads the same on both sides.\n const headers = first.done ? [] : first.value.map(sampleText);\n\n if (headers.length > limits.maxColumns) {\n throw new ImportError(\n 'SHEET_TOO_LARGE',\n `The sheet has more than ${limits.maxColumns} columns.`,\n );\n }\n\n const remaining = drain(iterator);\n return {\n format,\n headers,\n rows: closing(guardRows(remaining, headers.length, limits), options.stream),\n ...(found.delimiter ? { delimiter: found.delimiter } : {}),\n };\n } catch (error) {\n // Every exit from this function closes the request body, including the ones that are a\n // refusal rather than a fault. Refusing an upload and then leaving the socket half-read is\n // how a rejected upload still costs a handle.\n destroySource(options.stream);\n throw error;\n }\n}\n\n/** Everything the iterator has left, after the header row has been taken off the front. */\nasync function* drain(\n iterator: AsyncIterator<unknown[]>,\n): AsyncGenerator<unknown[]> {\n for (let next = await iterator.next(); !next.done; next = await iterator.next()) {\n yield next.value;\n }\n}\n\n/**\n * Close the source if the row stream stops early — by a guard firing, or by the caller\n * abandoning it.\n *\n * `readSheet` returns before a single data row is read, so its own `try` is long finished by\n * the time a row-level limit fires. Without this, the only thing standing between a refused\n * sheet and a leaked socket would be every caller remembering to destroy it, and \"every caller\n * remembers\" is the assumption this package keeps declining to make.\n */\nasync function* closing(\n rows: AsyncIterable<unknown[]>,\n source: unknown,\n): AsyncGenerator<unknown[]> {\n let drained = false;\n try {\n yield* rows;\n drained = true;\n } finally {\n // `finally` rather than `catch`, so an early `break` in the caller's loop closes it too.\n //\n // But **only when the stream did not finish**. On a clean read the source is already at\n // its end, and destroying it anyway would be actively wrong under Express: the file part\n // of a multipart body shares a socket with the response being written back, and tearing\n // that down after a successful import would abort the reply it was about to send.\n if (!drained) destroySource(source);\n }\n}\n","/**\n * run-import.ts — the package, really. Everything else is plumbing around this file.\n *\n * `runImport` pulls rows off the reader in batches, hands each batch to core's `applyMapping`,\n * **awaits** `onBatch`, and drops the batch. Peak memory is a function of `batchSize`, not of\n * the file's size — which is the one reason this package exists, and the claim every decision\n * below is answerable against.\n *\n * Nothing here decides anything core already decides. The mapping rules, the coercion, the\n * validation and the record shape all come from `applyMapping`; this file's entire job is to\n * feed it and to not accumulate.\n */\n\nimport {\n applyMapping,\n arrayBoundFor,\n decimalMarkFor,\n deriveImportColumns,\n suggestMapping,\n validateMappingPlan,\n type ConfigProblem,\n type CsvDelimiter,\n type EntityFormConfig,\n type FormRule,\n type ImportLookups,\n type ImportRowError,\n type MappingPlan,\n} from '@dynamic-entity/core';\nimport { destroySource, type ByteSource } from './bytes';\nimport { sampleText } from './cell-text';\nimport { ImportError } from './errors';\nimport { resolveLimits, type ImportLimits } from './limits';\nimport { readSheet, type SheetFormat } from './read-sheet';\n\n/** Where a batch sits in the user's spreadsheet, for a consumer that logs or reports progress. */\nexport interface BatchInfo {\n /** Zero-based batch number. */\n index: number;\n /** Spreadsheet row number of the first row this batch was built from. */\n firstRow: number;\n /** Records handed over before this batch, so `imported + records.length` is the running total. */\n imported: number;\n}\n\n/**\n * Write a batch of records.\n *\n * **It is awaited, and that is load-bearing.** A consumer writing to a database is slower than\n * a parser reading a file; not awaiting would turn bounded memory into an unbounded queue of\n * pending writes — the exact failure this package exists to avoid, reached from the other\n * direction.\n *\n * **It must be idempotent.** A stream that fails at row 30,000 has already written 29,999\n * records, and an HTTP client — or a proxy, or a user — will retry. See SECURITY.md.\n */\nexport type OnBatch = (\n records: Record<string, unknown>[],\n info: BatchInfo,\n) => void | Promise<void>;\n\nexport interface RunImportOptions {\n stream: ByteSource;\n /** Display only. The format is decided by the file's first bytes, never by its name. */\n filename?: string;\n plan: MappingPlan;\n config: EntityFormConfig;\n rules?: readonly FormRule[];\n /** Values for every `listName` the config mentions. A server has no `LOOKUP_REGISTRY`. */\n lookups?: ImportLookups;\n lang?: string;\n /**\n * The decimal mark in number cells. Defaults to `,` for a CSV that uses `;` between fields,\n * because that is what Excel writes in exactly those locales, and `.` otherwise.\n */\n decimal?: '.' | ',';\n /** Omit to validate without writing: the identical pipeline, every error, nothing stored. */\n onBatch?: OnBatch;\n limits?: Partial<ImportLimits>;\n}\n\nexport interface ImportRunResult {\n /**\n * Records the run produced — and therefore, when `onBatch` was supplied, how many were\n * handed over to be written. The records themselves are deliberately not here: returning\n * fifty thousand of them would undo the streaming this whole file is for.\n */\n imported: number;\n /** Rows that held no values at all. A blank line is not an error and is not a record. */\n skipped: number;\n /**\n * Rows that produced at least one error, counted exactly — **not** derived from `errors`.\n *\n * `errors` is capped, so counting the distinct rows in it answers how many rows fitted in the\n * cap rather than how many failed. Measured on a run of a thousand rows where two hundred\n * failed: the sample held seven of them. The number a user acts on is \"which rows do I fix\",\n * so it is the one that has to be exact.\n *\n * Row numbers never span a batch, so summing the per-batch distinct counts is exact rather\n * than approximate.\n */\n failed: number;\n /** A **sample**, capped at `limits.maxReportedErrors`. `errorCount` is the true total. */\n errors: ImportRowError[];\n /** Every problem found, whether or not it was retained. */\n errorCount: number;\n /** `errors` is shorter than `errorCount`. */\n truncated: boolean;\n /** What is wrong with the plan itself. An `error` here means no row was read at all. */\n planProblems: ConfigProblem[];\n /** Data rows read from the sheet, blank ones included. */\n rowsRead: number;\n /**\n * What the file turned out to be — **absent when no byte of it was ever read**.\n *\n * A plan refused before the stream is touched has nothing to report here, and it used to\n * report `'csv'` anyway. A guessed field is worse than a missing one: it is indistinguishable\n * from a measured one.\n */\n format?: SheetFormat;\n}\n\n/**\n * Check the plan before a single row is read.\n *\n * A plan naming a field the config does not have is wrong about every row, so discovering it\n * at row 30,000 of a file that has already written 29,999 records is the worst available\n * moment. `applyMapping` checks it too — per batch, on its own account — and this is not that\n * check moved but that check brought forward.\n */\nfunction checkPlan(plan: MappingPlan, config: EntityFormConfig, lang?: string): ConfigProblem[] {\n return validateMappingPlan(plan, config, {\n lang,\n // The same derivation `applyMapping` uses, or the two would disagree about which refs\n // exist and the pre-flight would pass a plan the run then rejects.\n includeReadonly: true,\n includeSystemDefault: true,\n });\n}\n\nexport async function runImport(options: RunImportOptions): Promise<ImportRunResult> {\n const limits = resolveLimits(options.limits);\n const { plan, config, onBatch } = options;\n\n const planProblems = checkPlan(plan, config, options.lang);\n if (planProblems.some(problem => problem.level === 'error')) {\n // Nothing was read, so nothing needs unwinding — but the request body is still open, and\n // an undrained socket is a leaked handle.\n destroySource(options.stream);\n return {\n imported: 0,\n skipped: 0,\n failed: 0,\n errors: [],\n errorCount: 0,\n truncated: false,\n planProblems,\n rowsRead: 0,\n };\n }\n\n const sheet = await readSheet({\n stream: options.stream,\n filename: options.filename,\n limits,\n });\n\n const decimal = options.decimal ?? decimalMarkFor(sheet.delimiter);\n const errors: ImportRowError[] = [];\n let errorCount = 0;\n let imported = 0;\n let skipped = 0;\n let failed = 0;\n let rowsRead = 0;\n let batchIndex = 0;\n\n let batch: unknown[][] = [];\n /** The spreadsheet row number of `batch[0]`. The header is row 1. */\n let firstRow = 2;\n\n const flush = async (): Promise<void> => {\n if (!batch.length) return;\n const rows = batch;\n batch = [];\n\n const result = applyMapping(rows, plan, config, {\n lang: options.lang,\n decimal,\n rules: options.rules,\n lookups: options.lookups,\n firstRowNumber: firstRow,\n });\n\n skipped += result.skipped;\n errorCount += result.errors.length;\n // Distinct rows, not problems: one row failing two validators is one row to go and fix.\n failed += new Set(result.errors.map(error => error.row)).size;\n // Retained, not accumulated. A file where every row fails is fifty thousand error objects\n // otherwise, which is the same unbounded growth as holding the file.\n for (const error of result.errors) {\n if (errors.length >= limits.maxReportedErrors) break;\n errors.push(error);\n }\n\n if (result.records.length && onBatch) {\n const info: BatchInfo = { index: batchIndex, firstRow, imported };\n try {\n // Awaited before the next batch is pulled. This is the backpressure.\n await onBatch(result.records, info);\n } catch (cause) {\n throw new ImportError(\n 'IMPORT_FAILED',\n 'The import stopped partway through. Rows before the failure may already have ' +\n 'been written — this import is not transactional.',\n { cause },\n );\n }\n }\n\n imported += result.records.length;\n firstRow += rows.length;\n batchIndex++;\n };\n\n try {\n for await (const row of sheet.rows) {\n rowsRead++;\n batch.push(row as unknown[]);\n if (batch.length >= limits.batchSize) await flush();\n }\n await flush();\n } catch (error) {\n // A failure anywhere — a limit, a malformed file, the consumer's own writer — leaves the\n // request body half-read. Destroying it is what stops a hostile uploader from holding a\n // socket open after the guard that refused them has already fired.\n destroySource(options.stream);\n throw error;\n }\n\n return {\n imported,\n skipped,\n failed,\n errors,\n errorCount,\n truncated: errorCount > errors.length,\n planProblems,\n rowsRead,\n format: sheet.format,\n };\n}\n\n// ─── Preview ──────────────────────────────────────────────────────────────────\n\nexport interface PreviewSheetOptions {\n stream: ByteSource;\n filename?: string;\n config: EntityFormConfig;\n lang?: string;\n limits?: Partial<ImportLimits>;\n}\n\n/** What a first look at an uploaded file yields. Matches the renderer's `ImportPreview`. */\nexport interface SheetPreview {\n headers: string[];\n /** The first few rows as text, for the mapping screen to show under each column. */\n sample: string[][];\n /** Every inferred match is tagged `guess`. */\n suggestion: MappingPlan;\n /** Data rows in the file, which the whole stream is read to count. */\n rowCount: number;\n format: SheetFormat;\n /** The field separator a CSV uses. Absent for a workbook. */\n delimiter?: CsvDelimiter;\n /** Rows of each repeating field the suggestion was sized for — see `arrayBoundFor`. */\n arrayBound: number;\n}\n\n/**\n * Headers, a sample, and a suggested mapping.\n *\n * The whole file is read, because `rowCount` is a number the user is shown before they commit\n * and \"about this many\" is not a useful thing to tell someone about their own data. Reading it\n * also means every limit applies to a preview exactly as it applies to an import — a preview\n * endpoint that skipped them would be the cheapest way in.\n */\nexport async function previewSheet(options: PreviewSheetOptions): Promise<SheetPreview> {\n const limits = resolveLimits(options.limits);\n const sheet = await readSheet({\n stream: options.stream,\n filename: options.filename,\n limits,\n });\n\n const sample: string[][] = [];\n let rowCount = 0;\n\n try {\n for await (const row of sheet.rows) {\n rowCount++;\n if (sample.length < limits.sampleRows) sample.push(row.map(sampleText));\n }\n } catch (error) {\n destroySource(options.stream);\n throw error;\n }\n\n // The same derivation the browser transport uses, so the two suggest the same mapping for\n // the same file. Readonly and system-default fields stay out of a *suggestion* for the same\n // reason they are marked: nobody maps a column onto a field the form fills in itself.\n // Sized from the headers, so a sheet with `Phone 6 Number` is offered six rows.\n const arrayBound = arrayBoundFor(sheet.headers, options.config, null, options.lang);\n const { columns } = deriveImportColumns(options.config, { lang: options.lang, maxArrayRows: arrayBound });\n\n return {\n headers: sheet.headers,\n sample,\n rowCount,\n suggestion: suggestMapping(sheet.headers, columns, options.config?.entity ?? ''),\n format: sheet.format,\n ...(sheet.delimiter ? { delimiter: sheet.delimiter } : {}),\n arrayBound,\n };\n}\n","/**\n * write-template.ts — the sheet to hand a user so their data arrives already in the right shape.\n *\n * **This is the one place in the library that writes a file another program executes.** A\n * template is opened in Excel, and `=cmd|'/C calc'!A0` in a cell is a command, not text. So\n * every cell goes through core's `escapeFormula` on the way in.\n *\n * The cell's *type* is the other half, and it cannot be set: exceljs derives it from the value\n * and exposes `type` read-only. Handing it a string is therefore what makes a string cell —\n * which is a claim about a library rather than a line of code here, so `write-template.spec.ts`\n * reads the written XML back and asserts no `<f>` element appears in it. A guard that is an\n * assertion in a test is a guard; a comment saying \"exceljs writes this as text\" is not.\n *\n * The header text itself is derived from config labels, which is authored data like any other\n * and therefore not trusted here.\n */\n\nimport { escapeFormula, toCsv, type TemplateSpec } from '@dynamic-entity/core';\nimport ExcelJS from 'exceljs';\nimport type { Writable } from 'node:stream';\nimport { ImportError } from './errors';\nimport { resolveLimits, type ImportLimits } from './limits';\n\n/** The formats a generated template can be written in. */\nexport type TemplateFormat = 'csv' | 'xlsx';\n\nexport interface WriteTemplateOptions {\n spec: TemplateSpec;\n format: TemplateFormat;\n /** Written into as the file is produced; an HTTP response is the intended one. */\n out: Writable;\n limits?: Partial<ImportLimits>;\n}\n\n/** The extension a written template should be offered under. */\nexport const TEMPLATE_EXTENSION: Record<TemplateFormat, string> = { csv: 'csv', xlsx: 'xlsx' };\n\n/** The media type a written template should be served as. */\nexport const TEMPLATE_MEDIA_TYPE: Record<TemplateFormat, string> = {\n csv: 'text/csv; charset=utf-8',\n xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n};\n\n/**\n * Write a template into `out`.\n *\n * Resolves when the file is complete and the stream has been ended. A caller that owns the\n * stream for other reasons — an Express response, say — gets it back finished, which is what\n * a download handler wants.\n */\nexport async function writeTemplate(options: WriteTemplateOptions): Promise<void> {\n const limits = resolveLimits(options.limits);\n const columns = options.spec.columns;\n\n if (columns.length > limits.maxColumns) {\n throw new ImportError(\n 'SHEET_TOO_LARGE',\n `A template of ${columns.length} columns is past the ${limits.maxColumns} column limit.`,\n );\n }\n\n if (options.format === 'csv') {\n await writeCsv(options.spec, options.out);\n return;\n }\n await writeXlsx(options.spec, options.out);\n}\n\n/**\n * Headers only, and `spec.notes` deliberately left out of the file.\n *\n * The same decision the browser transport makes, for the same reason: CSV has one header row\n * and no concept of a second, so a guidance row would be read back as a record and report a\n * failed row 2 on every re-import of the template the user just filled in.\n */\nfunction writeCsv(spec: TemplateSpec, out: Writable): Promise<void> {\n // Byte-for-byte what `LocalImportTransport.template` produces, through the same `toCsv` —\n // which escapes every cell — so a template downloaded from a server and one generated in the\n // browser are the same file.\n const csv = toCsv(spec.columns.map(column => column.header));\n return new Promise((resolve, reject) => {\n out.end(csv, () => resolve());\n out.on('error', reject);\n });\n}\n\n/**\n * The same headers, plus the guidance CSV had nowhere to put.\n *\n * An xlsx cell has a note attached to it, which is somewhere that is *not* data: the hint\n * reads in Excel and does not come back as a row on re-import. That is the distinction the\n * CSV writer could not make.\n */\nasync function writeXlsx(spec: TemplateSpec, out: Writable): Promise<void> {\n const workbook = new ExcelJS.stream.xlsx.WorkbookWriter({ stream: out, useStyles: true });\n const sheet = workbook.addWorksheet(sheetName(spec.sheetName));\n const { columns, notes } = spec;\n\n const row = sheet.addRow(columns.map(column => escapeFormula(column.header)));\n row.eachCell((cell, index) => {\n cell.font = { bold: true };\n const note = notes[index - 1];\n if (note) cell.note = note;\n });\n row.commit();\n\n sheet.commit();\n await workbook.commit();\n}\n\n/** Excel rejects a worksheet name over 31 characters or containing any of `[]:*?/\\`. */\nfunction sheetName(name: string): string {\n const cleaned = String(name).replace(/[[\\]:*?/\\\\]/g, ' ').trim();\n return (cleaned || 'Import').slice(0, 31);\n}\n"]}