@dynamic-entity/server 2.5.0 → 3.0.0-rc.1

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/express.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/bytes.ts","../src/limits.ts","../src/multipart.ts","../src/guard-zip.ts","../src/xlsx-source.ts","../src/read-sheet.ts","../src/run-import.ts","../src/write-template.ts","../src/express.ts"],"names":["ExcelJS","deriveImportColumns"],"mappings":"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","file":"express.mjs","sourcesContent":["/**\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 * 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 * multipart.ts — get one file and a few small fields off a request, under bounds.\n *\n * Every limit busboy takes is set, because busboy's defaults are \"no limit\" and a parser with\n * no limits on an attacker-reachable endpoint is a parse loop waiting to be pointed at. Field\n * count, field-name length, field value length, and file count are all attacker-chosen, and\n * none of them are the file.\n *\n * **One import is one file, whatever `maxFiles` says.** That limit is what busboy is told, so\n * it decides when the *parser* gives up; this reader takes the first file part and refuses a\n * second either way. A request carrying two files is a request whose author and whose reader\n * disagree about which one was imported, and guessing is the wrong way to resolve that.\n *\n * **The fields must precede the file, and that is a real constraint rather than an oversight.**\n * The whole point of this package is that the file is never held, so the import has to start\n * while the file is still arriving — which means the `plan` has to have arrived already. A\n * client that sends them the other way round gets told so, in as many words. `FormData` in\n * every browser preserves append order, so the client half simply appends the plan first.\n */\n\nimport Busboy from 'busboy';\nimport type { IncomingMessage } from 'node:http';\nimport type { Readable } from 'node:stream';\nimport { ImportError } from './errors';\nimport type { ImportLimits } from './limits';\n\nexport interface Upload {\n /** Non-file parts that arrived before the file. */\n fields: Record<string, string>;\n /** Display only. It never reaches a header and never decides a format. */\n filename: string;\n /** The file's bytes. Reading it is what drives the rest of the request. */\n stream: Readable;\n}\n\n/**\n * Wait for the first file part, with whatever fields came before it.\n *\n * Resolves as soon as the file *starts*, not when it finishes — the caller then streams it.\n * Rejects if the body has no file, has more than one, or breaches a bound on the way.\n */\nexport function receiveUpload(request: IncomingMessage, limits: ImportLimits): Promise<Upload> {\n return new Promise((resolve, reject) => {\n let settled = false;\n let file: Readable | null = null;\n\n /**\n * Refuse — before the file has been handed over by rejecting, and after it by failing the\n * stream the reader is already pulling from.\n *\n * The second half matters: `limits.files` makes busboy *drop* a second file part rather\n * than complain about it, so without this a request carrying two files would be read as\n * though it carried one and answered with a cheerful 200.\n */\n const fail = (error: ImportError): void => {\n if (settled) {\n file?.destroy(error);\n return;\n }\n settled = true;\n request.unpipe(parser);\n reject(error);\n };\n\n let parser: Busboy.Busboy;\n try {\n parser = Busboy({\n headers: request.headers,\n limits: {\n fileSize: limits.maxBytes,\n files: limits.maxFiles,\n fields: limits.maxFields,\n fieldSize: limits.maxFieldBytes,\n fieldNameSize: limits.maxFieldNameBytes,\n // A part that is neither a field nor a file still costs a parse.\n parts: limits.maxFields + limits.maxFiles,\n },\n });\n } catch (cause) {\n // A missing or unparseable Content-Type. Busboy throws synchronously for it.\n reject(new ImportError('MALFORMED_FILE', 'This is not a multipart upload.', { cause }));\n return;\n }\n\n const fields: Record<string, string> = {};\n let sawFile = false;\n\n parser.on('field', (name, value, info) => {\n // Truncation is busboy's way of enforcing a limit, and a silently shortened JSON plan\n // would fail to parse later with a message about JSON rather than about a limit.\n if (info.nameTruncated || info.valueTruncated) {\n fail(new ImportError('TOO_LARGE', `The \"${safeFieldName(name)}\" field is too long.`));\n return;\n }\n // A repeated part is refused rather than resolved. Last-one-wins is a rule this code\n // would be choosing on a client's behalf, and anything upstream that read the *first*\n // `plan` would then be acting on a different plan from the one imported.\n if (Object.prototype.hasOwnProperty.call(fields, name)) {\n fail(new ImportError('MALFORMED_FILE', `The upload repeats the \"${safeFieldName(name)}\" field.`));\n return;\n }\n fields[name] = value;\n });\n\n parser.on('file', (_name, stream, info) => {\n if (sawFile) {\n stream.resume();\n fail(new ImportError('TOO_MANY_FILES', 'Send one file at a time.'));\n return;\n }\n sawFile = true;\n file = stream;\n\n // A stream that emits `error` with nobody listening takes the process down, and there is\n // a window where exactly that happens: the reader stops first — on its own byte limit,\n // say — and busboy's own limit then fires on a stream nothing is pulling from any more.\n stream.on('error', () => undefined);\n\n // busboy truncates at `fileSize` rather than failing, so the truncation is turned back\n // into the refusal it was meant to be — and into one the reader will see, because it\n // arrives as an error on the stream it is already reading.\n stream.on('limit', () => {\n stream.destroy(\n new ImportError('TOO_LARGE', `The file is larger than the ${limits.maxBytes} byte limit.`),\n );\n });\n\n if (settled) {\n stream.resume();\n return;\n }\n settled = true;\n resolve({ fields, filename: String(info.filename ?? ''), stream });\n });\n\n parser.on('filesLimit', () => fail(new ImportError('TOO_MANY_FILES', 'Send one file at a time.')));\n parser.on('fieldsLimit', () => fail(new ImportError('TOO_LARGE', 'The upload has too many fields.')));\n parser.on('partsLimit', () => fail(new ImportError('TOO_LARGE', 'The upload has too many parts.')));\n\n parser.on('error', cause =>\n fail(new ImportError('MALFORMED_FILE', 'The upload could not be read.', { cause })),\n );\n parser.on('close', () => {\n if (!sawFile) fail(new ImportError('NO_FILE', 'No file was uploaded.'));\n });\n\n // `'close'` and `complete`, not `'aborted'`. The latter is deprecated on Node 18+ and may\n // never fire, which makes it protection that reads as real and is not; `complete` is false\n // exactly when the body stopped arriving before it ended.\n request.on('close', () => {\n if (!request.complete) fail(new ImportError('TIMEOUT', 'The upload did not finish.'));\n });\n request.pipe(parser);\n });\n}\n\n/**\n * A field name, fit to appear in a message.\n *\n * The name is attacker-chosen and the message goes back to them, which is harmless on its own\n * — but a message is also a log line, and a log line with a newline in it is two log lines.\n */\nexport function safeFieldName(name: string): string {\n return String(name).replace(/[^\\w.-]/g, '').slice(0, 40) || 'unnamed';\n}\n\n/** Parse the JSON `plan` field, saying which of the several ways it can be wrong it was. */\nexport function readPlanField(fields: Record<string, string>): unknown {\n const raw = fields['plan'];\n if (raw === undefined) {\n throw new ImportError(\n 'INVALID_PLAN',\n 'No mapping plan was sent. The \"plan\" field must come before the file part.',\n );\n }\n try {\n return JSON.parse(raw);\n } catch (cause) {\n throw new ImportError('INVALID_PLAN', 'The mapping plan is not valid JSON.', { cause });\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. The name is consulted only after the bytes have said\n * the file is text, and only for its separator: `.tsv` means tab, as it does in the browser.\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 /**\n * The client's name for the upload. It never decides the format — the bytes do. Once they\n * have said the file is text, a name ending in `.tsv` fixes the separator as a tab, the way\n * the browser reads one; any other name leaves the separator to `detectDelimiter`. A wrong\n * name can at worst split a text file oddly, which the preview shows before anything imports.\n */\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 /** A separator the filename settled (`.tsv`), which detection then does not override. */\n forced?: 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 = forced ?? 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 // Decided after `detectFormat`, so the name can never make a zip text or text a zip.\n const forced: CsvDelimiter | undefined = /\\.tsv$/i.test(options.filename ?? '') ? '\\t' : undefined;\n const rows = format === 'xlsx' ? xlsxRows(stream, limits) : csvRows(stream, limits, found, forced);\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 DateOrder,\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 /**\n * The order of a date written in digits, such as `05/09/2026`. Without it, one that reads\n * two ways fails its row: it is never guessed. `MDY` reads it month first, as every release\n * before 2.5.0 did without saying so.\n */\n dateOrder?: DateOrder;\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 dateOrder: options.dateOrder,\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","/**\n * express.ts — four routes over the framework-neutral core, and no privileges.\n *\n * Everything this file does, a Fastify, Nest or Next route could do in the same few lines:\n * find the config, read the upload, call `previewSheet` / `runImport` / `writeTemplate`, and\n * answer. It is one caller of the package rather than the package's shape.\n *\n * **Not this router's job, said out loud.** Concurrency limiting, authentication and\n * authorization are the consumer's. The router is mounted inside their app, behind their\n * middleware, and nothing here checks who is asking. SECURITY.md says so rather than leaving\n * a reader to assume the router protects them.\n *\n * This module is published as `@dynamic-entity/server/express` rather than from the package\n * root, so that it — and its `express` import — is reached only by someone who wants it. A\n * consumer wiring Fastify imports the root and never pays for a framework they do not use.\n */\n\nimport {\n buildTemplateSpec,\n CORE_VERSION,\n deriveImportColumns,\n type EntityFormConfig,\n type FormRule,\n type ImportCommitResponse,\n type DateOrder,\n type ImportLookups,\n type ImportPreviewResponse,\n} from '@dynamic-entity/core';\nimport express, { type Request, type Response, type Router } from 'express';\nimport { destroySource } from './bytes';\nimport { ImportError, toErrorBody } from './errors';\nimport { resolveLimits, type ImportLimits } from './limits';\nimport { receiveUpload, readPlanField } from './multipart';\nimport { previewSheet, runImport, type BatchInfo, type ImportRunResult } from './run-import';\nimport {\n writeTemplate,\n TEMPLATE_EXTENSION,\n TEMPLATE_MEDIA_TYPE,\n type TemplateFormat,\n} from './write-template';\n\n/** What the consumer does with a batch of records. Called once per batch, and awaited. */\nexport type OnImport = (\n records: Record<string, unknown>[],\n context: BatchInfo & {\n entity: string;\n config: EntityFormConfig;\n /** For a tenant id, a user, a transaction — whatever the consumer's writer needs. */\n request: Request;\n },\n) => void | Promise<void>;\n\n/**\n * Where the configs come from.\n *\n * A map **or** a function, because a real app keeps its configs in a database and should not\n * need a restart to add an entity. Returning `undefined` is how a loader says \"no such\n * entity\"; throwing is how it says something went wrong.\n */\nexport type ConfigSource =\n | Record<string, EntityFormConfig>\n | ((entity: string, request: Request) => EntityFormConfig | undefined | Promise<EntityFormConfig | undefined>);\n\nexport interface ImportRouterOptions {\n configs: ConfigSource;\n /** Rules per entity. Without them an import checks validators only, which the form does not. */\n rules?: Record<string, readonly FormRule[]>;\n /** Values for every `listName` any config mentions. A server has no `LOOKUP_REGISTRY`. */\n lookups?: ImportLookups;\n /**\n * The order of a date written in digits, for every import and validation this router runs:\n * `DMY` for day first, `MDY` for month first. Without it, a date that reads two ways\n * (`05/09/2026`) fails its row. See `RunImportOptions.dateOrder`.\n */\n dateOrder?: DateOrder;\n /**\n * Where records go.\n *\n * **It must be idempotent.** A stream that fails at row 30,000 has written 29,999 records,\n * and over HTTP a retry is likely rather than possible — a client, a proxy or a user will\n * send the same file again. This package does not deduplicate: the config has no natural-key\n * concept to deduplicate on. See SECURITY.md.\n */\n onImport: OnImport;\n limits?: Partial<ImportLimits>;\n /** Which language error text is produced in. Default `en`. */\n lang?: string | ((request: Request) => string);\n}\n\n/**\n * Every `listName` the importable columns of a config mention.\n *\n * Derived from the same column walk the import uses, so the set checked is exactly the set\n * that will be asked for — a field the sheet cannot carry cannot need a list.\n */\nexport function listNamesOf(config: EntityFormConfig): string[] {\n const names = new Set<string>();\n const { columns } = deriveImportColumns(config, {\n includeReadonly: true,\n includeSystemDefault: true,\n });\n for (const column of columns) {\n if (column.field.listName) names.add(column.field.listName);\n }\n return [...names];\n}\n\n/**\n * Refuse to start rather than store the wrong value at row 30,000.\n *\n * The browser resolves a `listName` field from `LOOKUP_REGISTRY`; a server has no such\n * registry, so an unsupplied list means `coerceCell` finds no options to match against and\n * passes the raw text straight through. The record then looks right and compares wrong against\n * every rule that names the option.\n *\n * So it is not left to vigilance. A silent wrong value found in a database next quarter is the\n * expensive failure; an app that will not boot is the cheap one.\n */\nfunction checkLookups(entity: string, config: EntityFormConfig, lookups: ImportLookups | undefined): void {\n const missing = listNamesOf(config).filter(name => !lookups?.[name]);\n if (!missing.length) return;\n\n throw new Error(\n `@dynamic-entity/server: config \"${entity}\" has fields resolving against ` +\n `${missing.map(name => `\"${name}\"`).join(', ')}, and no such list was supplied.\\n` +\n 'Pass them as `lookups` to createImportRouter. Without them those columns would store ' +\n 'the raw cell text, which renders correctly and matches nothing.',\n );\n}\n\n/** A map's own entry for `key`, never one inherited from `Object.prototype`. */\nfunction ownValue<T>(map: Readonly<Record<string, T>>, key: string): T | undefined {\n return Object.hasOwn(map, key) ? map[key] : undefined;\n}\n\n/** `csv` unless a valid `xlsx` was asked for; anything else is a refusal rather than a guess. */\nfunction readFormat(value: unknown): TemplateFormat {\n if (value === undefined || value === 'csv') return 'csv';\n if (value === 'xlsx') return 'xlsx';\n throw new ImportError('UNSUPPORTED_FORMAT', 'Supported template formats are csv and xlsx.');\n}\n\n/**\n * `?fields=a,b&fields=c` and `?fields=a&fields=b` both mean the same thing, and **no `fields`\n * at all means every column** — which is what a client sends when the user picked everything,\n * rather than spelling out a selection long enough to outgrow the URL.\n *\n * A ref with its row numbers stripped selects every row of a repeating field, because\n * `buildTemplateSpec` matches on both spellings. That is the shape the picker works in.\n */\nfunction readFields(value: unknown): string[] | undefined {\n const raw = value === undefined ? [] : Array.isArray(value) ? value : [value];\n const fields = raw.flatMap(item => String(item).split(',')).map(item => item.trim()).filter(Boolean);\n return fields.length ? fields : undefined;\n}\n\n/**\n * A download filename derived from the config, never from the client.\n *\n * A filename that reaches a `Content-Disposition` having come from a request is header\n * injection with extra steps: a newline in it ends the header and starts another. This takes\n * the entity and keeps only characters that cannot mean anything in a header.\n *\n * The caller passes `config.entity`, not the route segment. They are usually the same string,\n * and when they are not — a consumer mounting a plural route, `{ employees: employeeConfig }` —\n * the config is the one that names the data. Reading the route key instead made the map key\n * carry meaning it was never given, which is a footgun with no upside: the key is a route\n * segment and nothing else.\n */\nexport function templateFilename(entity: string, format: TemplateFormat): string {\n const safe = String(entity).replace(/[^A-Za-z0-9._-]/g, '-').replace(/^[-.]+/, '').slice(0, 64);\n return `${safe || 'import'}-template.${TEMPLATE_EXTENSION[format]}`;\n}\n\n/**\n * Give a request a clock.\n *\n * Two of them, because they answer different attacks: an idle timeout ends a connection that\n * has gone quiet mid-body — a byte a minute holds a socket indefinitely otherwise — and a\n * total timeout ends one that is perfectly chatty and never finishes.\n */\nfunction applyTimeouts(request: Request, response: Response, limits: ImportLimits): void {\n // The connection is closed rather than answered. A client that has stopped sending is not\n // waiting for a status code, and an attacker holding a socket open is the case being\n // answered — the reply they want least is the connection going away.\n const abandon = (message: string) => (): void => {\n request.destroy(new ImportError('TIMEOUT', message));\n };\n\n // `setTimeout` on a request sets it on the *socket*, which a keep-alive connection reuses\n // for whatever request comes next — so an import's thirty seconds would quietly become the\n // idle bound for every later request on that connection, including ones outside this router.\n // The previous value goes back when the response closes.\n const socket = request.socket;\n const previous = socket?.timeout;\n request.setTimeout(limits.idleTimeoutMs, abandon('The upload stalled.'));\n\n const total = setTimeout(abandon('The upload took too long.'), limits.totalTimeoutMs);\n // `unref` so a pending timer never holds a process open past its work.\n total.unref?.();\n response.on('close', () => {\n clearTimeout(total);\n if (socket && !socket.destroyed) socket.setTimeout(previous ?? 0);\n });\n}\n\n/**\n * Send whatever went wrong as the envelope, unless the response has already started.\n *\n * Exported so it can be tested against a response that has already begun: a template that\n * failed part-way is already partly on the wire, and the only honest thing left is to end the\n * connection abruptly. A 200 with a truncated body is a corrupt file that looks like a good\n * one, which is the worst of the available outcomes.\n */\nexport function sendFailure(response: Response, error: unknown): void {\n if (response.headersSent) {\n // A template that failed part-way through is already partly on the wire, and there is no\n // way to take it back. Ending the response abruptly is what tells the client the file is\n // not whole; a 200 with a truncated body would not.\n response.destroy();\n return;\n }\n\n // **The download's headers have to come off first.** The template route sets `Content-Type`\n // and `Content-Disposition` before it starts writing, and `res.json` only sets a content type\n // when one is *not* already there — so a failure after that point answered 413 with a JSON\n // body wearing `text/csv; charset=utf-8` and `attachment; filename=\"employee-template.csv\"`,\n // which a browser dutifully saves as the template. Measured, not theorised.\n for (const header of ['Content-Type', 'Content-Disposition', 'Content-Length']) {\n response.removeHeader(header);\n }\n\n const { status, body } = toErrorBody(error);\n response.status(status).json(body);\n}\n\nexport function createImportRouter(options: ImportRouterOptions): Router {\n const limits = resolveLimits(options.limits);\n const router = express.Router();\n\n // At construction, not at first use: a map is knowable now, and an app that refuses to start\n // is cheaper than a column that silently stored the wrong thing.\n if (typeof options.configs !== 'function') {\n for (const [entity, config] of Object.entries(options.configs)) {\n checkLookups(entity, config, options.lookups);\n }\n }\n\n const languageOf = (request: Request): string =>\n typeof options.lang === 'function' ? options.lang(request) : options.lang ?? 'en';\n\n /**\n * Resolve `:entity`, or 404 without echoing what was asked for.\n *\n * The key is a lookup into a map the consumer supplied — never a path segment, never\n * interpolated into anything. Reflecting it back would turn a 404 into a way to probe which\n * entities exist and, if a browser ever rendered the message, into a way to inject into it.\n *\n * An own-property check, not `configs[entity]`: `constructor`, `__proto__` or `toString`\n * would otherwise resolve to a built-in and run an import against it.\n */\n const configFor = async (request: Request): Promise<{ entity: string; config: EntityFormConfig }> => {\n const entity = String(request.params['entity'] ?? '');\n const config =\n typeof options.configs === 'function'\n ? await options.configs(entity, request)\n : ownValue(options.configs, entity);\n\n if (!config) throw new ImportError('UNKNOWN_ENTITY', 'No import is configured for that entity.');\n // A loader can return a config the router never saw at construction, so the boot-time\n // check gets a second chance here — before a row is read, which is the point of it.\n if (typeof options.configs === 'function') checkLookups(entity, config, options.lookups);\n return { entity, config };\n };\n\n const respond = (result: ImportRunResult, written: boolean): ImportCommitResponse => ({\n written,\n imported: result.imported,\n skipped: result.skipped,\n failed: result.failed,\n rowsRead: result.rowsRead,\n errors: result.errors,\n errorCount: result.errorCount,\n truncated: result.truncated,\n planProblems: result.planProblems,\n engineVersion: CORE_VERSION,\n });\n\n /**\n * Wrap a handler so a rejection becomes the envelope rather than Express's default page.\n *\n * The error is answered here and **not** passed to `next`. Doing both would run the\n * consumer's error middleware over a response that has already been sent, which in Express\n * is a warning in their logs about a problem they did not cause.\n */\n const route =\n (handler: (request: Request, response: Response) => Promise<void>) =>\n (request: Request, response: Response): void => {\n applyTimeouts(request, response, limits);\n handler(request, response).catch(error => sendFailure(response, error));\n };\n\n router.get(\n '/:entity/template',\n route(async (request, response) => {\n const { entity, config } = await configFor(request);\n const format = readFormat(request.query['format']);\n const spec = buildTemplateSpec(config, {\n lang: languageOf(request),\n fields: readFields(request.query['fields']),\n });\n\n response.setHeader('Content-Type', TEMPLATE_MEDIA_TYPE[format]);\n response.setHeader(\n 'Content-Disposition',\n `attachment; filename=\"${templateFilename(config.entity || entity, format)}\"`,\n );\n await writeTemplate({ spec, format, out: response, limits });\n }),\n );\n\n router.post(\n '/:entity/preview',\n route(async (request, response) => {\n const { config } = await configFor(request);\n const upload = await receiveUpload(request, limits);\n\n const preview = await previewSheet({\n stream: upload.stream,\n filename: upload.filename,\n config,\n lang: languageOf(request),\n limits,\n });\n\n const body: ImportPreviewResponse = {\n headers: preview.headers,\n sample: preview.sample,\n suggestion: preview.suggestion,\n rowCount: preview.rowCount,\n arrayBound: preview.arrayBound,\n ...(preview.delimiter ? { delimiter: preview.delimiter } : {}),\n // The order every import through this router is read in, so the client reads its\n // sample the same way and can refuse to commit when it was told another.\n ...(options.dateOrder ? { dateOrder: options.dateOrder } : {}),\n engineVersion: CORE_VERSION,\n };\n response.json(body);\n }),\n );\n\n /**\n * The identical pipeline with no `onBatch`: every row read, every error found, nothing\n * written.\n *\n * This is what makes a non-transactional import liveable. A user can see every problem in\n * the file before anything at all is stored, which is the answer to \"what if it fails\n * halfway\" that does not require a transaction.\n */\n router.post(\n '/:entity/validate',\n route(async (request, response) => {\n response.json(await execute(request, options, limits, languageOf, undefined));\n }),\n );\n\n router.post(\n '/:entity/import',\n route(async (request, response) => {\n const onBatch = async (\n records: Record<string, unknown>[],\n info: BatchInfo,\n entity: string,\n config: EntityFormConfig,\n ): Promise<void> => {\n await options.onImport(records, { ...info, entity, config, request });\n };\n response.json(await execute(request, options, limits, languageOf, onBatch));\n }),\n );\n\n /** Shared by `/validate` and `/import`, which differ only in whether anything is written. */\n async function execute(\n request: Request,\n routerOptions: ImportRouterOptions,\n resolved: ImportLimits,\n language: (request: Request) => string,\n write:\n | ((\n records: Record<string, unknown>[],\n info: BatchInfo,\n entity: string,\n config: EntityFormConfig,\n ) => Promise<void>)\n | undefined,\n ): Promise<ImportCommitResponse> {\n const { entity, config } = await configFor(request);\n const upload = await receiveUpload(request, resolved);\n\n let plan: ReturnType<typeof readPlanField>;\n try {\n plan = readPlanField(upload.fields);\n } catch (error) {\n // The plan is wrong and the file is still arriving. Draining it would be reading a body\n // there is no longer any reason to read.\n destroySource(upload.stream);\n throw error;\n }\n\n const result = await runImport({\n stream: upload.stream,\n filename: upload.filename,\n plan: plan as never,\n config,\n rules: routerOptions.rules && ownValue(routerOptions.rules, entity),\n lookups: routerOptions.lookups,\n dateOrder: routerOptions.dateOrder,\n lang: language(request),\n limits: resolved,\n onBatch: write && ((records, info) => write(records, info, entity, config)),\n });\n\n return respond(result, write !== undefined);\n }\n\n return router;\n}\n"]}
1
+ {"version":3,"sources":["../src/run-import.ts","../src/write-template.ts","../src/express.ts"],"names":["deriveImportColumns","importerTemplateFilename","sendImportFailure","entityFormConfigAdapter","planForConfig","createRouterOverAdapters"],"mappings":"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","file":"express.mjs","sourcesContent":["/**\n * run-import.ts — a file in, records out, with memory bounded by `batchSize` and not by the file.\n *\n * Since 3.0 the runner is sheet-to-schema's (`sheet-to-schema/node`), which takes a schema\n * adapter. These are Dynamic Entity's signatures over it: a config, its rules and its lookups\n * in, and the same result out. The config becomes `entityFormConfigAdapter`, which is also\n * what a browser import runs on, so the two cannot disagree about what a file produces.\n *\n * Nothing here decides anything core already decides, and nothing here reads a row.\n */\n\nimport {\n arrayBoundFor,\n deriveImportColumns,\n entityFormConfigAdapter,\n planForConfig,\n suggestMapping,\n type AnyMappingPlan,\n type ConfigProblem,\n type CsvDelimiter,\n type DateOrder,\n type EntityFormConfig,\n type FormRule,\n type ImportLookups,\n type ImportRowError,\n type MappingPlan,\n} from '@dynamic-entity/core';\nimport {\n previewSheet as previewOverAdapter,\n runImport as runOverAdapter,\n type BatchInfo as ImporterBatchInfo,\n type OnBatch as ImporterOnBatch,\n} from 'sheet-to-schema/node';\nimport type { ByteSource } from './bytes';\nimport type { ImportLimits } from './limits';\nimport 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 type BatchInfo = ImporterBatchInfo;\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 = ImporterOnBatch;\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 /** In either shape: plan v2, or a stored 2.x plan. */\n plan: AnyMappingPlan;\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 /**\n * The order of a date written in digits, such as `05/09/2026`, for every column whose plan\n * entry states none. Without either, one that reads two ways fails its row: it is never\n * guessed. `MDY` reads it month first, as every release before 2.5.0 did without saying so.\n */\n dateOrder?: DateOrder;\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 * which is capped: the distinct rows in it say how many fitted in the cap, not how many\n * failed. The number a user acts on is \"which rows do I fix\", so it is the exact one.\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**. A guessed\n * field is worse than a missing one: it is indistinguishable from a measured one.\n */\n format?: SheetFormat;\n /** Since 3.0: sheet rows the imported records came from. Equal to `imported` while no plan groups rows. */\n rowsInImported?: number;\n /** Since 3.0: a sample of the warnings, capped like `errors`. A record with a warning was imported. */\n warnings?: ImportRowError[];\n /** Since 3.0: every warning found, whether or not it was retained. */\n warningCount?: number;\n}\n\n/**\n * Run an import, or with no `onBatch` a validation: every row read, every error found.\n *\n * The plan is checked before a single row is read. A plan that groups rows is refused with\n * `INVALID_PLAN`: the streaming runner does not group yet, and one that ignored `group` would\n * import a record per row and report success. The browser import does group.\n */\nexport function runImport(options: RunImportOptions): Promise<ImportRunResult> {\n const { config } = options;\n return runOverAdapter({\n stream: options.stream,\n filename: options.filename,\n // A 2.x plan that names no entity is read against this config, as 2.x read it.\n plan: planForConfig(options.plan, config),\n adapter: entityFormConfigAdapter(config, { rules: options.rules, lookups: options.lookups }),\n ctx: { lang: options.lang },\n decimal: options.decimal,\n dateOrder: options.dateOrder,\n onBatch: options.onBatch,\n limits: options.limits,\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`. Plan v2 since 3.0. */\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 * The suggestion for a sheet's headers, and the rows of each repeating field it was sized for.\n *\n * **The same derivation the browser transport uses**, so the two suggest the same mapping for\n * the same file: sized from the headers, so a sheet with `Phone 6 Number` is offered six\n * rows, and without readonly and system-default fields, because nobody maps a column onto a\n * field the form fills in itself.\n */\nexport function suggestionFor(\n headers: readonly string[],\n config: EntityFormConfig,\n lang?: string,\n): { suggestion: MappingPlan; arrayBound: number } {\n const arrayBound = arrayBoundFor(headers, config, null, lang);\n const { columns } = deriveImportColumns(config, { lang, maxArrayRows: arrayBound });\n return { suggestion: suggestMapping(headers, columns, config?.entity ?? ''), arrayBound };\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 preview = await previewOverAdapter({\n stream: options.stream,\n filename: options.filename,\n adapter: entityFormConfigAdapter(options.config, { includeReadonly: false, includeSystemDefault: false }),\n lang: options.lang,\n limits: options.limits,\n });\n return {\n headers: preview.headers,\n sample: preview.sample,\n rowCount: preview.rowCount,\n format: preview.format,\n ...(preview.delimiter ? { delimiter: preview.delimiter } : {}),\n ...suggestionFor(preview.headers, options.config, options.lang),\n };\n}\n","/**\n * write-template.ts — a template a user downloads, fills in, and uploads again.\n *\n * Since 3.0 the writer is sheet-to-schema's, which takes a sheet name, headers and notes. This\n * is Dynamic Entity's signature over it: a `TemplateSpec`, as `buildTemplateSpec` makes one.\n * The bytes are the same bytes.\n */\n\nimport type { TemplateSpec } from '@dynamic-entity/core';\nimport type { Writable } from 'node:stream';\nimport {\n TEMPLATE_EXTENSION as IMPORTER_TEMPLATE_EXTENSION,\n TEMPLATE_MEDIA_TYPE as IMPORTER_TEMPLATE_MEDIA_TYPE,\n writeTemplate as writeSheetTemplate,\n type TemplateFormat as ImporterTemplateFormat,\n type TemplateSheet,\n} from 'sheet-to-schema/node';\nimport type { ImportLimits } from './limits';\n\n/** @deprecated Import `TemplateFormat` from `sheet-to-schema/node`. Removed in 4.0. */\nexport type TemplateFormat = ImporterTemplateFormat;\n/** @deprecated Import `TEMPLATE_EXTENSION` from `sheet-to-schema/node`. Removed in 4.0. */\nexport const TEMPLATE_EXTENSION = IMPORTER_TEMPLATE_EXTENSION;\n/** @deprecated Import `TEMPLATE_MEDIA_TYPE` from `sheet-to-schema/node`. Removed in 4.0. */\nexport const TEMPLATE_MEDIA_TYPE = IMPORTER_TEMPLATE_MEDIA_TYPE;\n\nexport interface WriteTemplateOptions {\n spec: TemplateSpec;\n format: TemplateFormat;\n /** Where the bytes go. An HTTP response, a file stream, anything writable. */\n out: Writable;\n limits?: Partial<ImportLimits>;\n}\n\n/** A `TemplateSpec` as the sheet the writer takes: its name, its headers, a note per header. */\nexport function templateSheetOf(spec: TemplateSpec): TemplateSheet {\n return { sheetName: spec.sheetName, headers: spec.columns.map(column => column.header), notes: spec.notes };\n}\n\n/**\n * Write a blank template: the header row, and for a workbook a note on each header cell.\n *\n * A CSV carries no notes, because it has nowhere to put them. Either way the file holds\n * headers and no formulas, and a header that would run as one is neutralised.\n */\nexport function writeTemplate(options: WriteTemplateOptions): Promise<void> {\n return writeSheetTemplate({ sheet: templateSheetOf(options.spec), format: options.format, out: options.out, limits: options.limits });\n}\n","/**\n * express.ts — four routes, and no privileges.\n *\n * Since 3.0 the routes are sheet-to-schema's (`createImportRouter` in `sheet-to-schema/node`),\n * which resolves a schema adapter by the name in the path. This is that router with a config\n * behind each name: it finds the config, checks its lookups were supplied, turns it into\n * `entityFormConfigAdapter` with the entity's rules, and answers a preview with the same\n * suggestion the browser makes. Reading the upload, the limits, the timeouts and the failure\n * envelope are the router's own, and are the code this file had until 3.0.\n *\n * **Not this router's job, said out loud.** Concurrency limiting, authentication and\n * authorization are the consumer's. The router is mounted inside their app, behind their\n * middleware, and nothing here checks who is asking. SECURITY.md says so rather than leaving\n * a reader to assume the router protects them.\n *\n * This module is published as `@dynamic-entity/server/express` rather than from the package\n * root, so that it — and its `express` import — is reached only by someone who wants it. A\n * consumer wiring Fastify imports the root and never pays for a framework they do not use.\n */\n\nimport {\n buildTemplateSpec,\n CORE_VERSION,\n deriveImportColumns,\n entityFormConfigAdapter,\n planForConfig,\n type DateOrder,\n type EntityFormConfig,\n type FormRule,\n type ImportLookups,\n} from '@dynamic-entity/core';\nimport express, { type Request, type Router } from 'express';\nimport {\n createImportRouter as createRouterOverAdapters,\n ImportError,\n sendFailure as sendImportFailure,\n templateFilename as importerTemplateFilename,\n type ImportBinding,\n} from 'sheet-to-schema/node';\nimport type { ImportLimits } from './limits';\nimport { suggestionFor, type BatchInfo } from './run-import';\nimport { templateSheetOf } from './write-template';\n\n/** What the consumer does with a batch of records. Called once per batch, and awaited. */\nexport type OnImport = (\n records: Record<string, unknown>[],\n context: BatchInfo & {\n entity: string;\n config: EntityFormConfig;\n /** For a tenant id, a user, a transaction — whatever the consumer's writer needs. */\n request: Request;\n },\n) => void | Promise<void>;\n\n/**\n * Where the configs come from.\n *\n * A map **or** a function, because a real app keeps its configs in a database and should not\n * need a restart to add an entity. Returning `undefined` is how a loader says \"no such\n * entity\"; throwing is how it says something went wrong.\n */\nexport type ConfigSource =\n | Record<string, EntityFormConfig>\n | ((entity: string, request: Request) => EntityFormConfig | undefined | Promise<EntityFormConfig | undefined>);\n\nexport interface ImportRouterOptions {\n configs: ConfigSource;\n /** Rules per entity. Without them an import checks validators only, which the form does not. */\n rules?: Record<string, readonly FormRule[]>;\n /** Values for every `listName` any config mentions. A server has no `LOOKUP_REGISTRY`. */\n lookups?: ImportLookups;\n /**\n * The order of a date written in digits, for every import and validation this router runs:\n * `DMY` for day first, `MDY` for month first. Without it, a date that reads two ways\n * (`05/09/2026`) fails its row. See `RunImportOptions.dateOrder`.\n */\n dateOrder?: DateOrder;\n /**\n * Where records go.\n *\n * **It must be idempotent.** A stream that fails at row 30,000 has written 29,999 records,\n * and over HTTP a retry is likely rather than possible — a client, a proxy or a user will\n * send the same file again. This package does not deduplicate: the config has no natural-key\n * concept to deduplicate on. See SECURITY.md.\n */\n onImport: OnImport;\n limits?: Partial<ImportLimits>;\n /** Which language error text is produced in. Default `en`. */\n lang?: string | ((request: Request) => string);\n}\n\n/**\n * Every `listName` the importable columns of a config mention.\n *\n * Derived from the same column walk the import uses, so the set checked is exactly the set\n * that will be asked for — a field the sheet cannot carry cannot need a list.\n */\nexport function listNamesOf(config: EntityFormConfig): string[] {\n const names = new Set<string>();\n const { columns } = deriveImportColumns(config, {\n includeReadonly: true,\n includeSystemDefault: true,\n });\n for (const column of columns) {\n if (column.field.listName) names.add(column.field.listName);\n }\n return [...names];\n}\n\n/**\n * Refuse to start rather than store the wrong value at row 30,000.\n *\n * The browser resolves a `listName` field from `LOOKUP_REGISTRY`; a server has no such\n * registry, so an unsupplied list means `coerceCell` finds no options to match against and\n * passes the raw text straight through. The record then looks right and compares wrong against\n * every rule that names the option.\n *\n * So it is not left to vigilance. A silent wrong value found in a database next quarter is the\n * expensive failure; an app that will not boot is the cheap one.\n */\nfunction checkLookups(entity: string, config: EntityFormConfig, lookups: ImportLookups | undefined): void {\n const missing = listNamesOf(config).filter(name => !lookups?.[name]);\n if (!missing.length) return;\n\n throw new Error(\n `@dynamic-entity/server: config \"${entity}\" has fields resolving against ` +\n `${missing.map(name => `\"${name}\"`).join(', ')}, and no such list was supplied.\\n` +\n 'Pass them as `lookups` to createImportRouter. Without them those columns would store ' +\n 'the raw cell text, which renders correctly and matches nothing.',\n );\n}\n\n/** A map's own entry for `key`, never one inherited from `Object.prototype`. */\nfunction ownValue<T>(map: Readonly<Record<string, T>>, key: string): T | undefined {\n return Object.hasOwn(map, key) ? map[key] : undefined;\n}\n\n/**\n * A download filename derived from the config, never from the client.\n *\n * A filename that reaches a `Content-Disposition` having come from a request is header\n * injection with extra steps: a newline in it ends the header and starts another. This takes\n * the entity and keeps only characters that cannot mean anything in a header.\n *\n * The caller passes `config.entity`, not the route segment. They are usually the same string,\n * and when they are not — a consumer mounting a plural route, `{ employees: employeeConfig }` —\n * the config is the one that names the data.\n */\nexport const templateFilename = importerTemplateFilename;\n\n/**\n * Send whatever went wrong as the envelope, unless the response has already started.\n *\n * A template that failed part-way is already partly on the wire, and the only honest thing\n * left is to end the connection abruptly. A 200 with a truncated body is a corrupt file that\n * looks like a good one, which is the worst of the available outcomes.\n */\nexport const sendFailure = sendImportFailure;\n\n/** The config a binding was made from, for the writer, which is handed the config and not the binding. */\ntype ConfigBinding = ImportBinding & { config: EntityFormConfig };\n\nexport function createImportRouter(options: ImportRouterOptions): Router {\n // At construction, not at first use: a map is knowable now, and an app that refuses to start\n // is cheaper than a column that silently stored the wrong thing.\n if (typeof options.configs !== 'function') {\n for (const [entity, config] of Object.entries(options.configs)) {\n checkLookups(entity, config, options.lookups);\n }\n }\n\n const languageOf = (request: Request): string =>\n typeof options.lang === 'function' ? options.lang(request) : options.lang ?? 'en';\n\n /**\n * Resolve `:entity` to what it imports into, or to nothing, which the router answers 404\n * without echoing what was asked for.\n *\n * The key is a lookup into a map the consumer supplied — never a path segment, never\n * interpolated into anything. An own-property check, not `configs[entity]`: `constructor`,\n * `__proto__` or `toString` would otherwise resolve to a built-in and run an import against it.\n */\n const bindingFor = async (entity: string, request: Request): Promise<ConfigBinding | undefined> => {\n const config =\n typeof options.configs === 'function' ? await options.configs(entity, request) : ownValue(options.configs, entity);\n if (!config) throw new ImportError('UNKNOWN_ENTITY', 'No import is configured for that entity.');\n // A loader can return a config the router never saw at construction, so the boot-time\n // check gets a second chance here — before a row is read, which is the point of it.\n if (typeof options.configs === 'function') checkLookups(entity, config, options.lookups);\n\n const lang = languageOf(request);\n const adapter = entityFormConfigAdapter(config, {\n rules: options.rules && ownValue(options.rules, entity),\n lookups: options.lookups,\n });\n return {\n adapter: adapter as ImportBinding['adapter'],\n ctx: { lang },\n kind: 'dynamic-entity',\n config,\n // Named from the config. The route segment only when the config names nothing.\n templateName: config.entity || entity,\n template: ({ lang: language, fields }) => templateSheetOf(buildTemplateSpec(config, { lang: language, fields })),\n // What the browser suggests for the same headers, and the one number it was sized with.\n previewExtras: preview => suggestionFor(preview.headers, config, lang),\n // A 2.x plan that names no entity is read against this config, as 2.x read it.\n plan: plan => planForConfig(plan, config),\n };\n };\n\n return createRouterOverAdapters(express.Router(), {\n bindings: bindingFor,\n param: 'entity',\n // What a Dynamic Entity client compares with its own `CORE_VERSION`, as it always has.\n engineVersion: CORE_VERSION,\n dateOrder: options.dateOrder,\n limits: options.limits,\n lang: options.lang,\n onImport: (records, { index, firstRow, imported, name, binding, request }) =>\n options.onImport(records, { index, firstRow, imported, entity: name, config: (binding as ConfigBinding).config, request }),\n });\n}\n"]}