@noy-db/as-xlsx 0.2.0-pre.3 → 0.2.0-pre.31

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.
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/index.ts","../src/xlsx.ts","../src/read.ts"],"sourcesContent":["/**\n * **@noy-db/as-xlsx** — Excel spreadsheet plaintext export for noy-db.\n *\n * Produces a real `.xlsx` file (Office Open XML / OOXML) from one\n * or more noy-db collections. Opens natively in Excel, Numbers,\n * LibreOffice Calc, Google Sheets, and every modern spreadsheet\n * tool.\n *\n * Zero runtime dependencies — the XLSX encoder builds the required\n * SpreadsheetML parts and assembles them with\n * `@noy-db/as-zip`'s `writeZip()` (STORE method; most xlsx\n * contents are XML text which Excel compresses at open time anyway).\n *\n * Part of the `@noy-db/as-*` portable-artefact family, plaintext\n * tier. See [`docs/patterns/as-exports.md`](https://github.com/vLannaAi/noy-db/blob/main/docs/patterns/as-exports.md).\n *\n * ## Authorisation\n *\n * Every call is gated by `assertCanExport('plaintext', 'xlsx')`.\n *\n * ```ts\n * await db.grant('firm', {\n * userId: 'accountant', role: 'viewer', passphrase: '…',\n * exportCapability: { plaintext: ['xlsx'] },\n * })\n * ```\n *\n * @packageDocumentation\n */\n\nimport type { Vault, DictEntry } from '@noy-db/hub'\nimport { writeXlsx, type XlsxSheet } from './xlsx.js'\nimport { readXlsx } from './read.js'\n\nexport { writeXlsx, colLetter, type XlsxSheet, type XlsxRow } from './xlsx.js'\nexport { readXlsx, type ReadXlsxResult, type ReadXlsxSheet, type ReadXlsxRow } from './read.js'\n\n/** Per-sheet options for the noy-db consumer API. */\nexport interface AsXlsxSheetOptions {\n /**\n * Sheet tab name. Excel caps at 31 chars; longer names are\n * truncated with `…`. Duplicates are suffixed `(2)`, `(3)`.\n */\n readonly name: string\n /** Source collection. Must be in the caller's read ACL. */\n readonly collection: string\n /**\n * Field list + order. When omitted, columns are inferred from\n * the union of keys across all records (first-record-wins order).\n */\n readonly columns?: readonly string[]\n /**\n * Optional predicate against each decrypted record. Runs after\n * decryption; doesn't reduce I/O.\n */\n readonly filter?: (record: unknown) => boolean\n /**\n * Optional per-column character widths (Excel `wch` units). When set,\n * the emitted sheet opens with the requested column widths instead of\n * Excel's default 10-character fallback. Index aligned with\n * `columns` / inferred-column order.\n *\n * Non-finite or non-positive entries are skipped so consumers can\n * mix explicit + auto (pass `undefined` for \"auto\").\n *\n * Length is NOT validated against `columns.length` — extra entries\n * are harmless (no `<col>` is emitted past the column count) and a\n * short array leaves trailing columns at Excel's default. This\n * mirrors `XlsxSheet.widths`, which it threads through.\n */\n readonly widths?: ReadonlyArray<number | undefined>\n}\n\n/** Single-collection convenience — passed where a sheet-list is accepted. */\nexport interface AsXlsxOptions {\n /** One or more sheets. At least one required. */\n readonly sheets: readonly AsXlsxSheetOptions[]\n}\n\n/** Options for `download()` — adds optional filename. */\nexport interface AsXlsxDownloadOptions extends AsXlsxOptions {\n /** Filename offered to the browser. Default `'export.xlsx'`. */\n readonly filename?: string\n}\n\n/** Options for `write()` — requires explicit risk acknowledgement. */\nexport interface AsXlsxWriteOptions extends AsXlsxOptions {\n /** Tier 3 egress — see `docs/patterns/as-exports.md`. */\n readonly acknowledgeRisks: true\n}\n\n/**\n * Convenience — single-collection shorthand. Equivalent to\n * `toBytes(vault, { sheets: [{ name: collectionName, collection: collectionName }] })`.\n */\nexport async function toBytesFromCollection(\n vault: Vault,\n collectionName: string,\n): Promise<Uint8Array> {\n return toBytes(vault, {\n sheets: [{ name: collectionName, collection: collectionName }],\n })\n}\n\n/**\n * Build the `.xlsx` byte stream from one or more sheets. Pure\n * beyond the auth check + store reads.\n */\nexport async function toBytes(vault: Vault, options: AsXlsxOptions): Promise<Uint8Array> {\n vault.assertCanExport('plaintext', 'xlsx')\n\n if (options.sheets.length === 0) {\n throw new Error('as-xlsx: at least one sheet is required')\n }\n\n const materialisedSheets: XlsxSheet[] = []\n for (const sheetOpt of options.sheets) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const collection = vault.collection<any>(sheetOpt.collection)\n const list = await collection.list()\n const records: Record<string, unknown>[] = []\n for (const item of list) {\n const r = item as Record<string, unknown>\n if (sheetOpt.filter && !sheetOpt.filter(r)) continue\n records.push(r)\n }\n const columns = sheetOpt.columns ?? inferColumns(records)\n materialisedSheets.push({\n name: sheetOpt.name,\n header: columns,\n rows: records.map((r) => columns.map((c) => r[c] ?? null)),\n ...(sheetOpt.widths !== undefined ? { widths: sheetOpt.widths } : {}),\n })\n }\n\n return writeXlsx(materialisedSheets)\n}\n\n/**\n * Browser download. Requires a browser-like environment with\n * `URL.createObjectURL` + `document.createElement`.\n */\nexport async function download(vault: Vault, options: AsXlsxDownloadOptions): Promise<void> {\n const bytes = await toBytes(vault, options)\n const filename = options.filename ?? 'export.xlsx'\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const blob = new Blob([bytes as any], {\n type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n })\n const url = URL.createObjectURL(blob)\n const a = document.createElement('a')\n a.href = url\n a.download = filename\n a.click()\n URL.revokeObjectURL(url)\n}\n\n/**\n * Node file-write. Requires `acknowledgeRisks: true` because the\n * plaintext xlsx persists past the process (Tier 3 egress).\n */\nexport async function write(\n vault: Vault,\n path: string,\n options: AsXlsxWriteOptions,\n): Promise<void> {\n if (options.acknowledgeRisks !== true) {\n throw new Error(\n 'as-xlsx.write: acknowledgeRisks: true is required for on-disk plaintext output. ' +\n 'This call creates a persistent plaintext xlsx outside noy-db\\'s encrypted storage — ' +\n 'see docs/patterns/as-exports.md §\"The three tiers of \\\\\"plaintext out\\\\\"\"',\n )\n }\n const bytes = await toBytes(vault, options)\n const { writeFile } = await import('node:fs/promises')\n await writeFile(path, bytes)\n}\n\n// ── internals ─────────────────────────────────────────────────────\n\nfunction inferColumns(records: readonly Record<string, unknown>[]): string[] {\n const seen = new Set<string>()\n const out: string[] = []\n for (const r of records) {\n for (const key of Object.keys(r)) {\n if (!seen.has(key)) {\n seen.add(key)\n out.push(key)\n }\n }\n }\n return out\n}\n\n// ── Reader ──────────────────────────────────────\n\nimport { diffVault, type VaultDiff } from '@noy-db/hub'\n\nexport type ImportPolicy = 'merge' | 'replace' | 'insert-only'\n\n/**\n * Thrown when a dict field contains two different keys whose labels are\n * identical in any locale — making label→key inversion ambiguous.\n *\n * @example\n * dict has: { key: 'a', labels: { en: 'Open' } } and { key: 'b', labels: { th: 'Open' } }\n * → XlsxDictAmbiguityError('status', 'Open')\n */\nexport class XlsxDictAmbiguityError extends Error {\n constructor(\n public readonly column: string,\n public readonly label: string,\n ) {\n super(\n `as-xlsx.fromBytes: dict for column \"${column}\" has ambiguous label \"${label}\" — ` +\n 'it maps to more than one key across locales. ' +\n 'Supply a stricter dict or resolve the label conflict before importing.',\n )\n this.name = 'XlsxDictAmbiguityError'\n }\n}\n\nexport interface AsXlsxImportOptions {\n /** Target collection. xlsx has no native collection grouping. */\n readonly collection: string\n /**\n * Sheet name to read. Defaults to the first sheet in the workbook.\n */\n readonly sheet?: string\n /**\n * 1-based header row index. Default `1` (first row).\n */\n readonly headerRow?: number\n /**\n * Optional field type hints. xlsx cells already have a type\n * (number, boolean, shared-string), so this is for the few cases\n * where the writer's emission rules don't preserve intent —\n * notably ISO-date strings the writer routed through the shared-\n * string path. `'date'` parses the value with `new Date()` and\n * keeps the result as an ISO-8601 string for stable round-tripping.\n */\n readonly fieldTypes?: Record<string, 'string' | 'number' | 'boolean' | 'date'>\n /** Field carrying the record id. Default `'id'`. */\n readonly idKey?: string\n /** Reconciliation policy. Default `'merge'`. */\n readonly policy?: ImportPolicy\n /**\n * Per-field dict definitions for label→key inversion. When a column\n * header matches a key here, cell values are matched against every\n * locale label in the dict entries; matching labels are replaced by\n * their stable key before building the ImportPlan.\n *\n * Takes precedence over any vault dictionary with the same name.\n * For fields not listed here, `fromBytes` automatically tries\n * `vault.dictionary(fieldName).list()` as a fallback.\n *\n * Unknown labels (no match in any locale) pass through as-is.\n */\n readonly dicts?: Readonly<Record<string, readonly DictEntry[]>>\n}\n\nexport interface AsXlsxImportPlan {\n readonly plan: VaultDiff\n readonly policy: ImportPolicy\n apply(): Promise<void>\n}\n\n/**\n * Build an import plan from an `.xlsx` byte stream. Inverts what\n * `toBytes()` writes — the first row is the header, subsequent rows\n * are records keyed by the column letters in the header row.\n *\n * Capability: `assertCanImport('plaintext', 'xlsx')`.\n * Atomicity: `apply()` runs inside `vault.noydb.transaction()`.\n *\n * **Not supported (matches the writer scope):**\n * - Cell styles / number formats / date format codes\n * - Formulas, merged cells, frozen panes\n * - Inline strings → handled defensively (since some upstream tools\n * emit them) but the writer never produces them\n * - Excel date serials → not auto-detected; pass `fieldTypes: { ts:\n * 'date' }` to coerce a numeric serial to ISO. Date round-trip via\n * the writer (which emits ISO strings) works without a hint.\n *\n * **Dict-label inversion** — supply `dicts` per field (or populate vault\n * dictionaries with `withI18n()`) and the reader automatically inverts\n * human labels back to their stable keys. Ambiguous labels throw\n * `XlsxDictAmbiguityError`; unknown labels pass through unchanged.\n */\nexport async function fromBytes(\n vault: Vault,\n bytes: Uint8Array,\n options: AsXlsxImportOptions,\n): Promise<AsXlsxImportPlan> {\n vault.assertCanImport('plaintext', 'xlsx')\n\n const policy: ImportPolicy = options.policy ?? 'merge'\n const idKey = options.idKey ?? 'id'\n const types = options.fieldTypes ?? {}\n const headerRowIdx = (options.headerRow ?? 1) - 1\n if (headerRowIdx < 0) {\n throw new Error('as-xlsx.fromBytes: headerRow must be 1-based and >= 1')\n }\n\n const decoded = await readXlsx(bytes)\n if (decoded.sheets.length === 0) {\n return emptyXlsxPlan(vault, options.collection, policy, idKey)\n }\n const sheet = options.sheet === undefined\n ? decoded.sheets[0]!\n : decoded.sheets.find((s) => s.name === options.sheet)\n if (sheet === undefined) {\n throw new Error(\n `as-xlsx.fromBytes: workbook has no sheet named \"${options.sheet}\". ` +\n `Available: ${decoded.sheets.map((s) => `\"${s.name}\"`).join(', ')}`,\n )\n }\n\n const allRows = sheet.rows\n if (allRows.length <= headerRowIdx) {\n return emptyXlsxPlan(vault, options.collection, policy, idKey)\n }\n const headerRow = allRows[headerRowIdx]!\n // Map column letter → field name. Only columns that have a\n // non-empty header cell contribute fields; blank columns are\n // ignored on read so a half-populated header doesn't synthesise\n // numeric `__EMPTY` keys the way some xlsx libs do.\n const colToField = new Map<string, string>()\n for (const [col, value] of Object.entries(headerRow)) {\n const fieldName = headerCellToField(value)\n if (fieldName === '') continue\n colToField.set(col, fieldName)\n }\n\n // Build per-field inversion maps.\n // Priority: explicit dicts option > vault dictionary lookup.\n // Vault lookup fires for every column not covered by explicit dicts;\n // vault.dictionary(name).list() returns [] when no dict exists — safe to call on any name.\n const invertMaps = new Map<string, Map<string, string>>()\n const allFields = new Set(colToField.values())\n for (const field of allFields) {\n const explicitEntries = options.dicts?.[field]\n if (explicitEntries !== undefined && explicitEntries.length > 0) {\n invertMaps.set(field, buildInversionMap(field, explicitEntries))\n continue\n }\n try {\n const vaultEntries = await vault.dictionary(field).list()\n if (vaultEntries.length > 0) {\n invertMaps.set(field, buildInversionMap(field, vaultEntries))\n }\n } catch {\n // No dictionary for this field — skip silently\n }\n }\n\n const records: Record<string, unknown>[] = []\n for (let i = headerRowIdx + 1; i < allRows.length; i++) {\n const row = allRows[i]!\n const record: Record<string, unknown> = {}\n let hasAny = false\n for (const [col, value] of Object.entries(row)) {\n const field = colToField.get(col)\n if (field === undefined) continue\n const coerced = coerceXlsxCell(value, types[field])\n if (coerced !== undefined) {\n const invMap = invertMaps.get(field)\n if (invMap !== undefined && typeof coerced === 'string') {\n record[field] = invMap.get(coerced) ?? coerced\n } else {\n record[field] = coerced\n }\n hasAny = true\n }\n }\n if (hasAny) records.push(record)\n }\n\n const plan = await diffVault(vault, { [options.collection]: records }, {\n collections: [options.collection],\n idKey,\n })\n\n return {\n plan,\n policy,\n async apply(): Promise<void> {\n // Routes through the txStrategy seam — clear error when\n // withTransactions() isn't opted in.\n await vault.noydb.transaction((tx) => {\n const txVault = tx.vault(vault.name)\n for (const entry of plan.added) {\n txVault.collection(entry.collection).put(entry.id, entry.record, { reason: 'import:xlsx' })\n }\n if (policy !== 'insert-only') {\n for (const entry of plan.modified) {\n txVault.collection(entry.collection).put(entry.id, entry.record, { reason: 'import:xlsx' })\n }\n }\n if (policy === 'replace') {\n for (const entry of plan.deleted) {\n txVault.collection(entry.collection).delete(entry.id)\n }\n }\n })\n },\n }\n}\n\nasync function emptyXlsxPlan(\n vault: Vault,\n collection: string,\n policy: ImportPolicy,\n idKey: string,\n): Promise<AsXlsxImportPlan> {\n const plan = await diffVault(vault, { [collection]: [] }, { collections: [collection], idKey })\n return { plan, policy, async apply() { /* nothing to do */ } }\n}\n\n/**\n * Build a label→key inversion map from a list of DictEntry objects.\n * Scans every locale in every entry's labels map. Throws\n * XlsxDictAmbiguityError if the same label string appears for two\n * different keys across any locale.\n */\nfunction buildInversionMap(column: string, entries: readonly DictEntry[]): Map<string, string> {\n const map = new Map<string, string>()\n for (const entry of entries) {\n for (const label of Object.values(entry.labels)) {\n if (label === '') continue\n const existing = map.get(label)\n if (existing !== undefined && existing !== entry.key) {\n throw new XlsxDictAmbiguityError(column, label)\n }\n map.set(label, entry.key)\n }\n }\n return map\n}\n\nfunction headerCellToField(value: unknown): string {\n // Header cells should be strings; defensively coerce numbers/booleans\n // and reject everything else (objects, undefined). Empty string =\n // \"this column has no header → ignore\".\n if (typeof value === 'string') return value\n if (typeof value === 'number' || typeof value === 'boolean') return String(value)\n return ''\n}\n\nfunction coerceXlsxCell(\n value: unknown,\n type?: 'string' | 'number' | 'boolean' | 'date',\n): unknown {\n if (value === undefined || value === null) return undefined\n if (type === undefined) return value\n if (type === 'string') {\n if (typeof value === 'string') return value\n if (typeof value === 'number' || typeof value === 'boolean') return String(value)\n return undefined\n }\n if (type === 'number') {\n if (typeof value === 'number') return value\n const n = Number(value)\n return Number.isFinite(n) ? n : undefined\n }\n if (type === 'boolean') {\n if (typeof value === 'boolean') return value\n if (value === 'true' || value === 1) return true\n if (value === 'false' || value === 0) return false\n return undefined\n }\n if (type === 'date') {\n // Excel date serial: days since 1900-01-01 with the historical\n // 1900-leap-year quirk. Numbers are converted; strings parsed\n // with `new Date()` and re-emitted as ISO so round-trips are\n // stable. Returning a string keeps the JSON envelope canonical.\n if (typeof value === 'number' && Number.isFinite(value)) {\n const ms = excelSerialToMs(value)\n const d = new Date(ms)\n return d.toISOString()\n }\n if (typeof value === 'string') {\n const d = new Date(value)\n if (!Number.isNaN(d.getTime())) return d.toISOString()\n }\n return undefined\n }\n return value\n}\n\n/**\n * Convert an Excel-style date serial to a JS millisecond timestamp.\n * Excel's \"1900 system\" treats day 1 as 1900-01-01 and includes the\n * non-existent 1900-02-29, so the offset between Excel serial and\n * Unix epoch days is 25569 for any date past 1900-03-01.\n *\n * Pre-1900-03 dates (serial ≤ 60) are uncommon in noy-db's domains\n * and we don't try to compensate for the leap-year bug there — they\n * round-trip with one-day skew, same as Excel itself.\n */\nfunction excelSerialToMs(serial: number): number {\n const EPOCH_OFFSET_DAYS = 25569\n const MS_PER_DAY = 86400_000\n return Math.round((serial - EPOCH_OFFSET_DAYS) * MS_PER_DAY)\n}\n","/**\n * Minimal zero-dependency XLSX writer.\n *\n * An `.xlsx` file is a ZIP archive (Office Open XML / OOXML) with\n * SpreadsheetML inside. This writer emits the six parts needed for\n * a valid worksheet and hands them to `@noy-db/as-zip`'s\n * `writeZip()` to assemble the final `.xlsx` bytes.\n *\n * ## Emitted parts\n *\n * ```\n * [Content_Types].xml # MIME descriptors\n * _rels/.rels # root → workbook pointer\n * xl/workbook.xml # sheet list\n * xl/_rels/workbook.xml.rels # sheet-part pointers\n * xl/worksheets/sheet<N>.xml # cell data\n * xl/sharedStrings.xml # string pool (Unicode-safe)\n * ```\n *\n * Strings route through the shared-string table (`sharedStrings.xml`)\n * rather than being inlined on cells, which is:\n *\n * 1. Slightly more compact when strings repeat (client names,\n * status labels, locale codes).\n * 2. Consistent with how Excel writes its own files — some\n * strict-OOXML readers refuse inline strings.\n *\n * Numbers, booleans, and dates are written as typed cells; strings\n * and everything else fall back to the shared-string path.\n *\n * ## Not supported\n *\n * - Cell styles (fonts, colours, borders, number formats).\n * - Formulas, merged cells, frozen panes, auto-filter.\n * - Charts, images, drawings.\n * - Zip64 / archives > 4 GiB.\n *\n * @module\n */\n\nimport { writeZip, type ZipEntry } from '@noy-db/as-zip'\n\nconst XML_HEADER = '<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>'\nconst ENCODER = new TextEncoder()\n\n/** One row in a sheet. Values are coerced per type at emit time. */\nexport type XlsxRow = ReadonlyArray<unknown>\n\n/** One sheet in a workbook. */\nexport interface XlsxSheet {\n /** Sheet tab name — Excel caps at 31 chars; we truncate with `…`. */\n readonly name: string\n /** Header row, rendered as row 1. Omit to skip the header. */\n readonly header?: readonly string[]\n /** Data rows — each is an array aligned with `header` if present. */\n readonly rows: readonly XlsxRow[]\n /**\n * Optional per-column widths in Excel character units (same scale as\n * SheetJS's `wch`). When set, emits a `<cols>` block so Excel opens\n * the file with the columns sized as specified instead of the default\n * 10-character width. Index aligned with `header` / row cells.\n *\n * Non-finite or non-positive entries are skipped (column falls back\n * to Excel's default width). A consumer typically passes `undefined`\n * for \"auto\" columns and a number for explicit widths.\n */\n readonly widths?: ReadonlyArray<number | undefined>\n}\n\n/**\n * Build a complete `.xlsx` byte stream from the supplied sheet data.\n * Pure — no I/O beyond the internal zip concatenation.\n */\nexport async function writeXlsx(sheets: readonly XlsxSheet[]): Promise<Uint8Array> {\n if (sheets.length === 0) {\n throw new Error('writeXlsx: at least one sheet is required')\n }\n\n // Dedup sheet names (Excel rejects duplicates) + truncate to 31 chars.\n const seen = new Set<string>()\n const safeSheets: XlsxSheet[] = sheets.map((s, i) => {\n let name = truncateSheetName(s.name || `Sheet${i + 1}`)\n let n = 1\n while (seen.has(name)) {\n const suffix = `(${n++})`\n name = truncateSheetName(name.slice(0, 31 - suffix.length) + suffix)\n }\n seen.add(name)\n return { ...s, name }\n })\n\n // Build shared-string table across every sheet. Emit order = insertion order.\n const sharedStrings: string[] = []\n const stringIndex = new Map<string, number>()\n const internString = (s: string): number => {\n const existing = stringIndex.get(s)\n if (existing !== undefined) return existing\n const idx = sharedStrings.length\n sharedStrings.push(s)\n stringIndex.set(s, idx)\n return idx\n }\n\n // Build the worksheet XML for each sheet — coercing values per type.\n const sheetXmls: string[] = safeSheets.map((sheet) => {\n const lines: string[] = [\n XML_HEADER,\n '<worksheet xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\">',\n ]\n // `<cols>` must precede `<sheetData>` per OOXML schema. Emit one\n // `<col>` element per defined width; skip entries that are not\n // positive finite numbers so consumers can mix explicit + auto.\n if (sheet.widths && sheet.widths.length > 0) {\n const colLines: string[] = []\n for (let i = 0; i < sheet.widths.length; i++) {\n const w = sheet.widths[i]\n if (typeof w !== 'number' || !Number.isFinite(w) || w <= 0) continue\n const n = i + 1\n colLines.push(`<col min=\"${n}\" max=\"${n}\" width=\"${w}\" customWidth=\"1\"/>`)\n }\n if (colLines.length > 0) {\n lines.push('<cols>', ...colLines, '</cols>')\n }\n }\n lines.push('<sheetData>')\n let rowNum = 0\n if (sheet.header && sheet.header.length > 0) {\n rowNum++\n const cells = sheet.header\n .map((h, i) => {\n const idx = internString(String(h))\n return `<c r=\"${colLetter(i + 1)}${rowNum}\" t=\"s\"><v>${idx}</v></c>`\n })\n .join('')\n lines.push(`<row r=\"${rowNum}\">${cells}</row>`)\n }\n for (const row of sheet.rows) {\n rowNum++\n const cells = row\n .map((value, i) => cellXml(value, i + 1, rowNum, internString))\n .join('')\n lines.push(`<row r=\"${rowNum}\">${cells}</row>`)\n }\n lines.push('</sheetData>', '</worksheet>')\n return lines.join('')\n })\n\n // ── Fixed parts ─────────────────────────────────────────────────\n\n const sheetEntries = safeSheets.map((s, i) => ({\n index: i + 1,\n id: `rId${i + 1}`,\n name: s.name,\n path: `xl/worksheets/sheet${i + 1}.xml`,\n }))\n\n const contentTypes = [\n XML_HEADER,\n '<Types xmlns=\"http://schemas.openxmlformats.org/package/2006/content-types\">',\n '<Default Extension=\"rels\" ContentType=\"application/vnd.openxmlformats-package.relationships+xml\"/>',\n '<Default Extension=\"xml\" ContentType=\"application/xml\"/>',\n '<Override PartName=\"/xl/workbook.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml\"/>',\n ...sheetEntries.map(\n (s) =>\n `<Override PartName=\"/${s.path}\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml\"/>`,\n ),\n '<Override PartName=\"/xl/sharedStrings.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml\"/>',\n '</Types>',\n ].join('')\n\n const rootRels =\n XML_HEADER +\n '<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">' +\n '<Relationship Id=\"rId1\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument\" Target=\"xl/workbook.xml\"/>' +\n '</Relationships>'\n\n const workbookXml = [\n XML_HEADER,\n '<workbook xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\">',\n '<sheets>',\n ...sheetEntries.map(\n (s) => `<sheet name=\"${escapeXmlAttr(s.name)}\" sheetId=\"${s.index}\" r:id=\"${s.id}\"/>`,\n ),\n '</sheets>',\n '</workbook>',\n ].join('')\n\n const workbookRels = [\n XML_HEADER,\n '<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">',\n ...sheetEntries.map(\n (s) =>\n `<Relationship Id=\"${s.id}\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet\" Target=\"worksheets/sheet${s.index}.xml\"/>`,\n ),\n `<Relationship Id=\"rIdSharedStrings\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings\" Target=\"sharedStrings.xml\"/>`,\n '</Relationships>',\n ].join('')\n\n const sharedStringsXml = [\n XML_HEADER,\n `<sst xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\" count=\"${sharedStrings.length}\" uniqueCount=\"${sharedStrings.length}\">`,\n ...sharedStrings.map((s) => `<si><t xml:space=\"preserve\">${escapeXmlText(s)}</t></si>`),\n '</sst>',\n ].join('')\n\n const entries: ZipEntry[] = [\n { path: '[Content_Types].xml', bytes: ENCODER.encode(contentTypes) },\n { path: '_rels/.rels', bytes: ENCODER.encode(rootRels) },\n { path: 'xl/workbook.xml', bytes: ENCODER.encode(workbookXml) },\n { path: 'xl/_rels/workbook.xml.rels', bytes: ENCODER.encode(workbookRels) },\n { path: 'xl/sharedStrings.xml', bytes: ENCODER.encode(sharedStringsXml) },\n ...sheetEntries.map((s, i) => ({ path: s.path, bytes: ENCODER.encode(sheetXmls[i] ?? '') })),\n ]\n\n return await writeZip(entries)\n}\n\n// ── Cell emission ─────────────────────────────────────────────────\n\nfunction cellXml(\n value: unknown,\n colIdx: number,\n rowNum: number,\n intern: (s: string) => number,\n): string {\n const ref = `${colLetter(colIdx)}${rowNum}`\n if (value === null || value === undefined || value === '') return `<c r=\"${ref}\"/>`\n if (typeof value === 'number' && Number.isFinite(value)) {\n return `<c r=\"${ref}\"><v>${value}</v></c>`\n }\n if (typeof value === 'boolean') {\n return `<c r=\"${ref}\" t=\"b\"><v>${value ? 1 : 0}</v></c>`\n }\n // Date → ISO-8601 string (Excel renders as text unless the cell\n // has a date-format style; styles are out of scope for this\n // minimal writer).\n const s =\n value instanceof Date\n ? value.toISOString()\n : typeof value === 'string'\n ? value\n : JSON.stringify(value)\n const idx = intern(s)\n return `<c r=\"${ref}\" t=\"s\"><v>${idx}</v></c>`\n}\n\n// ── Helpers ───────────────────────────────────────────────────────\n\n/**\n * Convert a 1-based column index to Excel A1 letter notation.\n * 1 → A, 26 → Z, 27 → AA, 702 → ZZ, 703 → AAA.\n */\nexport function colLetter(n: number): string {\n let s = ''\n let x = n\n while (x > 0) {\n const r = (x - 1) % 26\n s = String.fromCharCode(65 + r) + s\n x = Math.floor((x - 1) / 26)\n }\n return s\n}\n\nfunction truncateSheetName(name: string): string {\n // Excel sheet-name rules: max 31 chars, forbid :/\\?*[]\n const cleaned = name.replace(/[:/\\\\?*[\\]]/g, '_')\n if (cleaned.length <= 31) return cleaned\n return cleaned.slice(0, 30) + '…'\n}\n\n/** XML text escaping — `& < > \\r` (quotes only matter in attributes). */\nfunction escapeXmlText(s: string): string {\n return s\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\\r/g, '&#13;')\n}\n\n/** XML attribute escaping. */\nfunction escapeXmlAttr(s: string): string {\n return escapeXmlText(s).replace(/\"/g, '&quot;')\n}\n","/**\n * Minimal OOXML reader. Inverse of `writeXlsx` in `xlsx.ts`.\n *\n * Walks the three parts the writer emits:\n *\n * - `xl/sharedStrings.xml` → string table (idx → string)\n * - `xl/workbook.xml` → sheet name list (with sheetId)\n * - `xl/_rels/workbook.xml.rels` → sheetId → sheet part path\n * - `xl/worksheets/sheet<N>.xml` → cell data\n *\n * Cell types matched to the writer's emission rules:\n * - no `t` attribute → number (`<v>` is parsed as Number)\n * - `t=\"s\"` → shared-string ref\n * - `t=\"b\"` → boolean (`1` ↔ true, `0` ↔ false)\n * - empty `<c />` → undefined\n *\n * Excel date serials are NOT auto-converted — the writer outputs ISO-\n * 8601 strings via the shared-string path, so dates round-trip as\n * strings unless the consumer opts into `dateFields` coercion (handled\n * one layer up in `index.ts:fromBytes`).\n *\n * @module\n */\n\nimport { readZip } from '@noy-db/as-zip'\nimport { XMLParser, XMLValidator } from 'fast-xml-parser'\n\nconst PARSER = new XMLParser({\n ignoreAttributes: false,\n attributeNamePrefix: '@_',\n parseTagValue: false,\n parseAttributeValue: false,\n trimValues: false,\n isArray: (name) => name === 'sheet' || name === 'row' || name === 'c' || name === 'si' || name === 'Relationship',\n})\n\n/** A row of cell values keyed by column letter (`'A' → 'foo'`). */\nexport type ReadXlsxRow = Record<string, unknown>\n\nexport interface ReadXlsxSheet {\n /** Sheet tab name from `xl/workbook.xml`. */\n readonly name: string\n /** All rows in declaration order. Columns indexed by Excel letter. */\n readonly rows: readonly ReadXlsxRow[]\n}\n\nexport interface ReadXlsxResult {\n readonly sheets: readonly ReadXlsxSheet[]\n}\n\n/**\n * Decode an `.xlsx` (OOXML) byte stream into per-sheet row data. The\n * caller decides what to do with the rows (header inference, type\n * coercion, record building) — the reader stays format-only.\n *\n * Throws on malformed XML, missing parts, or sheet-id mismatches.\n */\nexport async function readXlsx(bytes: Uint8Array): Promise<ReadXlsxResult> {\n const entries = await readZip(bytes)\n const partByPath = new Map<string, Uint8Array>()\n for (const e of entries) partByPath.set(e.path, e.bytes)\n\n const sharedStrings = readSharedStrings(partByPath.get('xl/sharedStrings.xml'))\n const sheetMeta = readWorkbook(partByPath.get('xl/workbook.xml'))\n const rels = readWorkbookRels(partByPath.get('xl/_rels/workbook.xml.rels'))\n\n const sheets: ReadXlsxSheet[] = []\n for (const meta of sheetMeta) {\n const target = rels.get(meta.rId)\n if (target === undefined) {\n throw new Error(\n `as-xlsx.readXlsx: workbook references rId=\"${meta.rId}\" but the rels file has no matching target`,\n )\n }\n const sheetPath = `xl/${target}`\n const sheetBytes = partByPath.get(sheetPath)\n if (sheetBytes === undefined) {\n throw new Error(`as-xlsx.readXlsx: missing sheet part ${sheetPath}`)\n }\n sheets.push({\n name: meta.name,\n rows: readSheet(sheetBytes, sharedStrings),\n })\n }\n\n return { sheets }\n}\n\n// ── XML helpers ─────────────────────────────────────────────────────\n\nfunction decodeXml(bytes: Uint8Array): string {\n return new TextDecoder().decode(bytes)\n}\n\nfunction parseStrict(xml: string, where: string): unknown {\n const validation = XMLValidator.validate(xml)\n if (validation !== true) {\n const err = validation.err\n throw new Error(\n `as-xlsx.readXlsx: ${where} is not valid XML (${err.code} at line ${err.line}: ${err.msg})`,\n )\n }\n try {\n return PARSER.parse(xml)\n } catch (err) {\n throw new Error(`as-xlsx.readXlsx: failed to parse ${where} (${(err as Error).message})`)\n }\n}\n\n// ── sharedStrings.xml ───────────────────────────────────────────────\n\nfunction readSharedStrings(bytes: Uint8Array | undefined): readonly string[] {\n // The writer always emits sharedStrings.xml, but a hand-crafted\n // .xlsx might omit it if no string cells are present. Treat missing\n // as an empty table so cells with t=\"s\" surface as a clear error\n // (\"ref out of bounds\") rather than a misleading null.\n if (bytes === undefined) return []\n const parsed = parseStrict(decodeXml(bytes), 'xl/sharedStrings.xml')\n const sst = (parsed as Record<string, unknown>).sst\n if (sst === null || sst === undefined || typeof sst !== 'object') return []\n const items = (sst as { si?: unknown[] }).si ?? []\n if (!Array.isArray(items)) return []\n\n return items.map((si): string => {\n if (si === null || typeof si !== 'object') return ''\n const t = (si as { t?: unknown }).t\n return textValue(t)\n })\n}\n\n// ── workbook.xml ────────────────────────────────────────────────────\n\ninterface WorkbookSheetMeta {\n readonly name: string\n readonly sheetId: string\n readonly rId: string\n}\n\nfunction readWorkbook(bytes: Uint8Array | undefined): readonly WorkbookSheetMeta[] {\n if (bytes === undefined) {\n throw new Error('as-xlsx.readXlsx: missing xl/workbook.xml')\n }\n const parsed = parseStrict(decodeXml(bytes), 'xl/workbook.xml')\n const workbook = (parsed as Record<string, unknown>).workbook\n if (workbook === null || workbook === undefined || typeof workbook !== 'object') {\n throw new Error('as-xlsx.readXlsx: xl/workbook.xml has no <workbook> root')\n }\n const sheetsObj = (workbook as { sheets?: unknown }).sheets\n if (sheetsObj === null || sheetsObj === undefined || typeof sheetsObj !== 'object') {\n return []\n }\n const sheetEntries = (sheetsObj as { sheet?: unknown[] }).sheet ?? []\n if (!Array.isArray(sheetEntries)) return []\n\n return sheetEntries.map((s): WorkbookSheetMeta => {\n const obj = s as Record<string, unknown>\n return {\n name: stringAttr(obj['@_name']),\n sheetId: stringAttr(obj['@_sheetId']),\n rId: stringAttr(obj['@_r:id']) || stringAttr(obj['@_id']),\n }\n })\n}\n\n// ── workbook.xml.rels ───────────────────────────────────────────────\n\nfunction readWorkbookRels(bytes: Uint8Array | undefined): Map<string, string> {\n // Map of relationship id → target path (relative to xl/).\n if (bytes === undefined) {\n throw new Error('as-xlsx.readXlsx: missing xl/_rels/workbook.xml.rels')\n }\n const parsed = parseStrict(decodeXml(bytes), 'xl/_rels/workbook.xml.rels')\n const root = (parsed as Record<string, unknown>).Relationships\n if (root === null || root === undefined || typeof root !== 'object') {\n return new Map()\n }\n const rels = (root as { Relationship?: unknown[] }).Relationship ?? []\n if (!Array.isArray(rels)) return new Map()\n\n const map = new Map<string, string>()\n for (const r of rels) {\n const obj = r as Record<string, unknown>\n const id = stringAttr(obj['@_Id'])\n const target = stringAttr(obj['@_Target'])\n if (id) map.set(id, target)\n }\n return map\n}\n\n// ── sheet<N>.xml ────────────────────────────────────────────────────\n\nfunction readSheet(bytes: Uint8Array, sharedStrings: readonly string[]): ReadXlsxRow[] {\n const parsed = parseStrict(decodeXml(bytes), 'sheet')\n const ws = (parsed as Record<string, unknown>).worksheet\n if (ws === null || ws === undefined || typeof ws !== 'object') return []\n const sheetData = (ws as { sheetData?: unknown }).sheetData\n if (sheetData === null || sheetData === undefined || typeof sheetData !== 'object') return []\n const rowEntries = (sheetData as { row?: unknown[] }).row ?? []\n if (!Array.isArray(rowEntries)) return []\n\n const rows: ReadXlsxRow[] = []\n for (const row of rowEntries) {\n if (row === null || typeof row !== 'object') {\n rows.push({})\n continue\n }\n const cells = (row as { c?: unknown[] }).c ?? []\n if (!Array.isArray(cells)) {\n rows.push({})\n continue\n }\n const out: ReadXlsxRow = {}\n for (const c of cells) {\n if (c === null || typeof c !== 'object') continue\n const cellObj = c as Record<string, unknown>\n const ref = stringAttr(cellObj['@_r'])\n if (!ref) continue\n const col = letterFromRef(ref)\n const t = cellObj['@_t']\n const v = cellObj['v']\n const text = textValue(v)\n if (text === '') {\n if (t === 's') {\n out[col] = ''\n continue\n }\n // Empty <c r=\"A1\"/> — leave undefined (caller can drop).\n continue\n }\n if (t === 's') {\n const idx = Number(text)\n if (!Number.isInteger(idx) || idx < 0 || idx >= sharedStrings.length) {\n throw new Error(\n `as-xlsx.readXlsx: shared-string reference ${idx} out of range ` +\n `(table has ${sharedStrings.length} entries)`,\n )\n }\n out[col] = sharedStrings[idx]\n } else if (t === 'b') {\n out[col] = text === '1'\n } else if (t === 'str' || t === 'inlineStr') {\n // Inline strings — writer never emits these but readers should\n // accept them since some upstream tools default to inline.\n out[col] = text\n } else {\n // Numeric cell (no `t` attribute, or t=\"n\").\n const n = Number(text)\n out[col] = Number.isFinite(n) ? n : text\n }\n }\n rows.push(out)\n }\n return rows\n}\n\n/**\n * Extract column letters from an A1-style reference (`\"BC42\"` → `\"BC\"`).\n * Used to build the sparse `Record<column, value>` row shape.\n */\nfunction letterFromRef(ref: string): string {\n let i = 0\n while (i < ref.length && /[A-Z]/.test(ref[i]!)) i++\n return ref.slice(0, i)\n}\n\n/**\n * fast-xml-parser surfaces text-only elements as primitives or as\n * objects with a `#text` field when other attributes are present.\n * Normalize to a string.\n */\nfunction textValue(raw: unknown): string {\n if (raw === null || raw === undefined) return ''\n if (typeof raw === 'string') return raw\n if (typeof raw === 'number' || typeof raw === 'boolean') return String(raw)\n if (typeof raw === 'object') {\n const txt = (raw as { '#text'?: unknown })['#text']\n if (txt !== undefined) return textValue(txt)\n }\n return ''\n}\n\n/**\n * Stringify a fast-xml-parser attribute value. Attributes come through\n * as primitives; defensively narrow before `String()` so the lint rule\n * (`@typescript-eslint/no-base-to-string`) is satisfied. Anything not\n * a primitive is coerced to the empty string — silent failures here\n * surface as missing parts (e.g. unknown rId) downstream.\n */\nfunction stringAttr(raw: unknown): string {\n if (raw === undefined || raw === null) return ''\n if (typeof raw === 'string') return raw\n if (typeof raw === 'number' || typeof raw === 'boolean') return String(raw)\n return ''\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACwCA,oBAAwC;AAExC,IAAM,aAAa;AACnB,IAAM,UAAU,IAAI,YAAY;AA8BhC,eAAsB,UAAU,QAAmD;AACjF,MAAI,OAAO,WAAW,GAAG;AACvB,UAAM,IAAI,MAAM,2CAA2C;AAAA,EAC7D;AAGA,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,aAA0B,OAAO,IAAI,CAAC,GAAG,MAAM;AACnD,QAAI,OAAO,kBAAkB,EAAE,QAAQ,QAAQ,IAAI,CAAC,EAAE;AACtD,QAAI,IAAI;AACR,WAAO,KAAK,IAAI,IAAI,GAAG;AACrB,YAAM,SAAS,IAAI,GAAG;AACtB,aAAO,kBAAkB,KAAK,MAAM,GAAG,KAAK,OAAO,MAAM,IAAI,MAAM;AAAA,IACrE;AACA,SAAK,IAAI,IAAI;AACb,WAAO,EAAE,GAAG,GAAG,KAAK;AAAA,EACtB,CAAC;AAGD,QAAM,gBAA0B,CAAC;AACjC,QAAM,cAAc,oBAAI,IAAoB;AAC5C,QAAM,eAAe,CAAC,MAAsB;AAC1C,UAAM,WAAW,YAAY,IAAI,CAAC;AAClC,QAAI,aAAa,OAAW,QAAO;AACnC,UAAM,MAAM,cAAc;AAC1B,kBAAc,KAAK,CAAC;AACpB,gBAAY,IAAI,GAAG,GAAG;AACtB,WAAO;AAAA,EACT;AAGA,QAAM,YAAsB,WAAW,IAAI,CAAC,UAAU;AACpD,UAAM,QAAkB;AAAA,MACtB;AAAA,MACA;AAAA,IACF;AAIA,QAAI,MAAM,UAAU,MAAM,OAAO,SAAS,GAAG;AAC3C,YAAM,WAAqB,CAAC;AAC5B,eAAS,IAAI,GAAG,IAAI,MAAM,OAAO,QAAQ,KAAK;AAC5C,cAAM,IAAI,MAAM,OAAO,CAAC;AACxB,YAAI,OAAO,MAAM,YAAY,CAAC,OAAO,SAAS,CAAC,KAAK,KAAK,EAAG;AAC5D,cAAM,IAAI,IAAI;AACd,iBAAS,KAAK,aAAa,CAAC,UAAU,CAAC,YAAY,CAAC,qBAAqB;AAAA,MAC3E;AACA,UAAI,SAAS,SAAS,GAAG;AACvB,cAAM,KAAK,UAAU,GAAG,UAAU,SAAS;AAAA,MAC7C;AAAA,IACF;AACA,UAAM,KAAK,aAAa;AACxB,QAAI,SAAS;AACb,QAAI,MAAM,UAAU,MAAM,OAAO,SAAS,GAAG;AAC3C;AACA,YAAM,QAAQ,MAAM,OACjB,IAAI,CAAC,GAAG,MAAM;AACb,cAAM,MAAM,aAAa,OAAO,CAAC,CAAC;AAClC,eAAO,SAAS,UAAU,IAAI,CAAC,CAAC,GAAG,MAAM,cAAc,GAAG;AAAA,MAC5D,CAAC,EACA,KAAK,EAAE;AACV,YAAM,KAAK,WAAW,MAAM,KAAK,KAAK,QAAQ;AAAA,IAChD;AACA,eAAW,OAAO,MAAM,MAAM;AAC5B;AACA,YAAM,QAAQ,IACX,IAAI,CAAC,OAAO,MAAM,QAAQ,OAAO,IAAI,GAAG,QAAQ,YAAY,CAAC,EAC7D,KAAK,EAAE;AACV,YAAM,KAAK,WAAW,MAAM,KAAK,KAAK,QAAQ;AAAA,IAChD;AACA,UAAM,KAAK,gBAAgB,cAAc;AACzC,WAAO,MAAM,KAAK,EAAE;AAAA,EACtB,CAAC;AAID,QAAM,eAAe,WAAW,IAAI,CAAC,GAAG,OAAO;AAAA,IAC7C,OAAO,IAAI;AAAA,IACX,IAAI,MAAM,IAAI,CAAC;AAAA,IACf,MAAM,EAAE;AAAA,IACR,MAAM,sBAAsB,IAAI,CAAC;AAAA,EACnC,EAAE;AAEF,QAAM,eAAe;AAAA,IACnB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG,aAAa;AAAA,MACd,CAAC,MACC,wBAAwB,EAAE,IAAI;AAAA,IAClC;AAAA,IACA;AAAA,IACA;AAAA,EACF,EAAE,KAAK,EAAE;AAET,QAAM,WACJ,aACA;AAIF,QAAM,cAAc;AAAA,IAClB;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG,aAAa;AAAA,MACd,CAAC,MAAM,gBAAgB,cAAc,EAAE,IAAI,CAAC,cAAc,EAAE,KAAK,WAAW,EAAE,EAAE;AAAA,IAClF;AAAA,IACA;AAAA,IACA;AAAA,EACF,EAAE,KAAK,EAAE;AAET,QAAM,eAAe;AAAA,IACnB;AAAA,IACA;AAAA,IACA,GAAG,aAAa;AAAA,MACd,CAAC,MACC,qBAAqB,EAAE,EAAE,kHAAkH,EAAE,KAAK;AAAA,IACtJ;AAAA,IACA;AAAA,IACA;AAAA,EACF,EAAE,KAAK,EAAE;AAET,QAAM,mBAAmB;AAAA,IACvB;AAAA,IACA,iFAAiF,cAAc,MAAM,kBAAkB,cAAc,MAAM;AAAA,IAC3I,GAAG,cAAc,IAAI,CAAC,MAAM,+BAA+B,cAAc,CAAC,CAAC,WAAW;AAAA,IACtF;AAAA,EACF,EAAE,KAAK,EAAE;AAET,QAAM,UAAsB;AAAA,IAC1B,EAAE,MAAM,uBAAuB,OAAO,QAAQ,OAAO,YAAY,EAAE;AAAA,IACnE,EAAE,MAAM,eAAe,OAAO,QAAQ,OAAO,QAAQ,EAAE;AAAA,IACvD,EAAE,MAAM,mBAAmB,OAAO,QAAQ,OAAO,WAAW,EAAE;AAAA,IAC9D,EAAE,MAAM,8BAA8B,OAAO,QAAQ,OAAO,YAAY,EAAE;AAAA,IAC1E,EAAE,MAAM,wBAAwB,OAAO,QAAQ,OAAO,gBAAgB,EAAE;AAAA,IACxE,GAAG,aAAa,IAAI,CAAC,GAAG,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,QAAQ,OAAO,UAAU,CAAC,KAAK,EAAE,EAAE,EAAE;AAAA,EAC7F;AAEA,SAAO,UAAM,wBAAS,OAAO;AAC/B;AAIA,SAAS,QACP,OACA,QACA,QACA,QACQ;AACR,QAAM,MAAM,GAAG,UAAU,MAAM,CAAC,GAAG,MAAM;AACzC,MAAI,UAAU,QAAQ,UAAU,UAAa,UAAU,GAAI,QAAO,SAAS,GAAG;AAC9E,MAAI,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,GAAG;AACvD,WAAO,SAAS,GAAG,QAAQ,KAAK;AAAA,EAClC;AACA,MAAI,OAAO,UAAU,WAAW;AAC9B,WAAO,SAAS,GAAG,cAAc,QAAQ,IAAI,CAAC;AAAA,EAChD;AAIA,QAAM,IACJ,iBAAiB,OACb,MAAM,YAAY,IAClB,OAAO,UAAU,WACf,QACA,KAAK,UAAU,KAAK;AAC5B,QAAM,MAAM,OAAO,CAAC;AACpB,SAAO,SAAS,GAAG,cAAc,GAAG;AACtC;AAQO,SAAS,UAAU,GAAmB;AAC3C,MAAI,IAAI;AACR,MAAI,IAAI;AACR,SAAO,IAAI,GAAG;AACZ,UAAM,KAAK,IAAI,KAAK;AACpB,QAAI,OAAO,aAAa,KAAK,CAAC,IAAI;AAClC,QAAI,KAAK,OAAO,IAAI,KAAK,EAAE;AAAA,EAC7B;AACA,SAAO;AACT;AAEA,SAAS,kBAAkB,MAAsB;AAE/C,QAAM,UAAU,KAAK,QAAQ,gBAAgB,GAAG;AAChD,MAAI,QAAQ,UAAU,GAAI,QAAO;AACjC,SAAO,QAAQ,MAAM,GAAG,EAAE,IAAI;AAChC;AAGA,SAAS,cAAc,GAAmB;AACxC,SAAO,EACJ,QAAQ,MAAM,OAAO,EACrB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,MAAM,EACpB,QAAQ,OAAO,OAAO;AAC3B;AAGA,SAAS,cAAc,GAAmB;AACxC,SAAO,cAAc,CAAC,EAAE,QAAQ,MAAM,QAAQ;AAChD;;;AClQA,IAAAA,iBAAwB;AACxB,6BAAwC;AAExC,IAAM,SAAS,IAAI,iCAAU;AAAA,EAC3B,kBAAkB;AAAA,EAClB,qBAAqB;AAAA,EACrB,eAAe;AAAA,EACf,qBAAqB;AAAA,EACrB,YAAY;AAAA,EACZ,SAAS,CAAC,SAAS,SAAS,WAAW,SAAS,SAAS,SAAS,OAAO,SAAS,QAAQ,SAAS;AACrG,CAAC;AAuBD,eAAsB,SAAS,OAA4C;AACzE,QAAM,UAAU,UAAM,wBAAQ,KAAK;AACnC,QAAM,aAAa,oBAAI,IAAwB;AAC/C,aAAW,KAAK,QAAS,YAAW,IAAI,EAAE,MAAM,EAAE,KAAK;AAEvD,QAAM,gBAAgB,kBAAkB,WAAW,IAAI,sBAAsB,CAAC;AAC9E,QAAM,YAAY,aAAa,WAAW,IAAI,iBAAiB,CAAC;AAChE,QAAM,OAAO,iBAAiB,WAAW,IAAI,4BAA4B,CAAC;AAE1E,QAAM,SAA0B,CAAC;AACjC,aAAW,QAAQ,WAAW;AAC5B,UAAM,SAAS,KAAK,IAAI,KAAK,GAAG;AAChC,QAAI,WAAW,QAAW;AACxB,YAAM,IAAI;AAAA,QACR,8CAA8C,KAAK,GAAG;AAAA,MACxD;AAAA,IACF;AACA,UAAM,YAAY,MAAM,MAAM;AAC9B,UAAM,aAAa,WAAW,IAAI,SAAS;AAC3C,QAAI,eAAe,QAAW;AAC5B,YAAM,IAAI,MAAM,wCAAwC,SAAS,EAAE;AAAA,IACrE;AACA,WAAO,KAAK;AAAA,MACV,MAAM,KAAK;AAAA,MACX,MAAM,UAAU,YAAY,aAAa;AAAA,IAC3C,CAAC;AAAA,EACH;AAEA,SAAO,EAAE,OAAO;AAClB;AAIA,SAAS,UAAU,OAA2B;AAC5C,SAAO,IAAI,YAAY,EAAE,OAAO,KAAK;AACvC;AAEA,SAAS,YAAY,KAAa,OAAwB;AACxD,QAAM,aAAa,oCAAa,SAAS,GAAG;AAC5C,MAAI,eAAe,MAAM;AACvB,UAAM,MAAM,WAAW;AACvB,UAAM,IAAI;AAAA,MACR,qBAAqB,KAAK,sBAAsB,IAAI,IAAI,YAAY,IAAI,IAAI,KAAK,IAAI,GAAG;AAAA,IAC1F;AAAA,EACF;AACA,MAAI;AACF,WAAO,OAAO,MAAM,GAAG;AAAA,EACzB,SAAS,KAAK;AACZ,UAAM,IAAI,MAAM,qCAAqC,KAAK,KAAM,IAAc,OAAO,GAAG;AAAA,EAC1F;AACF;AAIA,SAAS,kBAAkB,OAAkD;AAK3E,MAAI,UAAU,OAAW,QAAO,CAAC;AACjC,QAAM,SAAS,YAAY,UAAU,KAAK,GAAG,sBAAsB;AACnE,QAAM,MAAO,OAAmC;AAChD,MAAI,QAAQ,QAAQ,QAAQ,UAAa,OAAO,QAAQ,SAAU,QAAO,CAAC;AAC1E,QAAM,QAAS,IAA2B,MAAM,CAAC;AACjD,MAAI,CAAC,MAAM,QAAQ,KAAK,EAAG,QAAO,CAAC;AAEnC,SAAO,MAAM,IAAI,CAAC,OAAe;AAC/B,QAAI,OAAO,QAAQ,OAAO,OAAO,SAAU,QAAO;AAClD,UAAM,IAAK,GAAuB;AAClC,WAAO,UAAU,CAAC;AAAA,EACpB,CAAC;AACH;AAUA,SAAS,aAAa,OAA6D;AACjF,MAAI,UAAU,QAAW;AACvB,UAAM,IAAI,MAAM,2CAA2C;AAAA,EAC7D;AACA,QAAM,SAAS,YAAY,UAAU,KAAK,GAAG,iBAAiB;AAC9D,QAAM,WAAY,OAAmC;AACrD,MAAI,aAAa,QAAQ,aAAa,UAAa,OAAO,aAAa,UAAU;AAC/E,UAAM,IAAI,MAAM,0DAA0D;AAAA,EAC5E;AACA,QAAM,YAAa,SAAkC;AACrD,MAAI,cAAc,QAAQ,cAAc,UAAa,OAAO,cAAc,UAAU;AAClF,WAAO,CAAC;AAAA,EACV;AACA,QAAM,eAAgB,UAAoC,SAAS,CAAC;AACpE,MAAI,CAAC,MAAM,QAAQ,YAAY,EAAG,QAAO,CAAC;AAE1C,SAAO,aAAa,IAAI,CAAC,MAAyB;AAChD,UAAM,MAAM;AACZ,WAAO;AAAA,MACL,MAAM,WAAW,IAAI,QAAQ,CAAC;AAAA,MAC9B,SAAS,WAAW,IAAI,WAAW,CAAC;AAAA,MACpC,KAAK,WAAW,IAAI,QAAQ,CAAC,KAAK,WAAW,IAAI,MAAM,CAAC;AAAA,IAC1D;AAAA,EACF,CAAC;AACH;AAIA,SAAS,iBAAiB,OAAoD;AAE5E,MAAI,UAAU,QAAW;AACvB,UAAM,IAAI,MAAM,sDAAsD;AAAA,EACxE;AACA,QAAM,SAAS,YAAY,UAAU,KAAK,GAAG,4BAA4B;AACzE,QAAM,OAAQ,OAAmC;AACjD,MAAI,SAAS,QAAQ,SAAS,UAAa,OAAO,SAAS,UAAU;AACnE,WAAO,oBAAI,IAAI;AAAA,EACjB;AACA,QAAM,OAAQ,KAAsC,gBAAgB,CAAC;AACrE,MAAI,CAAC,MAAM,QAAQ,IAAI,EAAG,QAAO,oBAAI,IAAI;AAEzC,QAAM,MAAM,oBAAI,IAAoB;AACpC,aAAW,KAAK,MAAM;AACpB,UAAM,MAAM;AACZ,UAAM,KAAK,WAAW,IAAI,MAAM,CAAC;AACjC,UAAM,SAAS,WAAW,IAAI,UAAU,CAAC;AACzC,QAAI,GAAI,KAAI,IAAI,IAAI,MAAM;AAAA,EAC5B;AACA,SAAO;AACT;AAIA,SAAS,UAAU,OAAmB,eAAiD;AACrF,QAAM,SAAS,YAAY,UAAU,KAAK,GAAG,OAAO;AACpD,QAAM,KAAM,OAAmC;AAC/C,MAAI,OAAO,QAAQ,OAAO,UAAa,OAAO,OAAO,SAAU,QAAO,CAAC;AACvE,QAAM,YAAa,GAA+B;AAClD,MAAI,cAAc,QAAQ,cAAc,UAAa,OAAO,cAAc,SAAU,QAAO,CAAC;AAC5F,QAAM,aAAc,UAAkC,OAAO,CAAC;AAC9D,MAAI,CAAC,MAAM,QAAQ,UAAU,EAAG,QAAO,CAAC;AAExC,QAAM,OAAsB,CAAC;AAC7B,aAAW,OAAO,YAAY;AAC5B,QAAI,QAAQ,QAAQ,OAAO,QAAQ,UAAU;AAC3C,WAAK,KAAK,CAAC,CAAC;AACZ;AAAA,IACF;AACA,UAAM,QAAS,IAA0B,KAAK,CAAC;AAC/C,QAAI,CAAC,MAAM,QAAQ,KAAK,GAAG;AACzB,WAAK,KAAK,CAAC,CAAC;AACZ;AAAA,IACF;AACA,UAAM,MAAmB,CAAC;AAC1B,eAAW,KAAK,OAAO;AACrB,UAAI,MAAM,QAAQ,OAAO,MAAM,SAAU;AACzC,YAAM,UAAU;AAChB,YAAM,MAAM,WAAW,QAAQ,KAAK,CAAC;AACrC,UAAI,CAAC,IAAK;AACV,YAAM,MAAM,cAAc,GAAG;AAC7B,YAAM,IAAI,QAAQ,KAAK;AACvB,YAAM,IAAI,QAAQ,GAAG;AACrB,YAAM,OAAO,UAAU,CAAC;AACxB,UAAI,SAAS,IAAI;AACf,YAAI,MAAM,KAAK;AACb,cAAI,GAAG,IAAI;AACX;AAAA,QACF;AAEA;AAAA,MACF;AACA,UAAI,MAAM,KAAK;AACb,cAAM,MAAM,OAAO,IAAI;AACvB,YAAI,CAAC,OAAO,UAAU,GAAG,KAAK,MAAM,KAAK,OAAO,cAAc,QAAQ;AACpE,gBAAM,IAAI;AAAA,YACR,6CAA6C,GAAG,4BAChC,cAAc,MAAM;AAAA,UACtC;AAAA,QACF;AACA,YAAI,GAAG,IAAI,cAAc,GAAG;AAAA,MAC9B,WAAW,MAAM,KAAK;AACpB,YAAI,GAAG,IAAI,SAAS;AAAA,MACtB,WAAW,MAAM,SAAS,MAAM,aAAa;AAG3C,YAAI,GAAG,IAAI;AAAA,MACb,OAAO;AAEL,cAAM,IAAI,OAAO,IAAI;AACrB,YAAI,GAAG,IAAI,OAAO,SAAS,CAAC,IAAI,IAAI;AAAA,MACtC;AAAA,IACF;AACA,SAAK,KAAK,GAAG;AAAA,EACf;AACA,SAAO;AACT;AAMA,SAAS,cAAc,KAAqB;AAC1C,MAAI,IAAI;AACR,SAAO,IAAI,IAAI,UAAU,QAAQ,KAAK,IAAI,CAAC,CAAE,EAAG;AAChD,SAAO,IAAI,MAAM,GAAG,CAAC;AACvB;AAOA,SAAS,UAAU,KAAsB;AACvC,MAAI,QAAQ,QAAQ,QAAQ,OAAW,QAAO;AAC9C,MAAI,OAAO,QAAQ,SAAU,QAAO;AACpC,MAAI,OAAO,QAAQ,YAAY,OAAO,QAAQ,UAAW,QAAO,OAAO,GAAG;AAC1E,MAAI,OAAO,QAAQ,UAAU;AAC3B,UAAM,MAAO,IAA8B,OAAO;AAClD,QAAI,QAAQ,OAAW,QAAO,UAAU,GAAG;AAAA,EAC7C;AACA,SAAO;AACT;AASA,SAAS,WAAW,KAAsB;AACxC,MAAI,QAAQ,UAAa,QAAQ,KAAM,QAAO;AAC9C,MAAI,OAAO,QAAQ,SAAU,QAAO;AACpC,MAAI,OAAO,QAAQ,YAAY,OAAO,QAAQ,UAAW,QAAO,OAAO,GAAG;AAC1E,SAAO;AACT;;;AFjGA,iBAA0C;AArG1C,eAAsB,sBACpB,OACA,gBACqB;AACrB,SAAO,QAAQ,OAAO;AAAA,IACpB,QAAQ,CAAC,EAAE,MAAM,gBAAgB,YAAY,eAAe,CAAC;AAAA,EAC/D,CAAC;AACH;AAMA,eAAsB,QAAQ,OAAc,SAA6C;AACvF,QAAM,gBAAgB,aAAa,MAAM;AAEzC,MAAI,QAAQ,OAAO,WAAW,GAAG;AAC/B,UAAM,IAAI,MAAM,yCAAyC;AAAA,EAC3D;AAEA,QAAM,qBAAkC,CAAC;AACzC,aAAW,YAAY,QAAQ,QAAQ;AAErC,UAAM,aAAa,MAAM,WAAgB,SAAS,UAAU;AAC5D,UAAM,OAAO,MAAM,WAAW,KAAK;AACnC,UAAM,UAAqC,CAAC;AAC5C,eAAW,QAAQ,MAAM;AACvB,YAAM,IAAI;AACV,UAAI,SAAS,UAAU,CAAC,SAAS,OAAO,CAAC,EAAG;AAC5C,cAAQ,KAAK,CAAC;AAAA,IAChB;AACA,UAAM,UAAU,SAAS,WAAW,aAAa,OAAO;AACxD,uBAAmB,KAAK;AAAA,MACtB,MAAM,SAAS;AAAA,MACf,QAAQ;AAAA,MACR,MAAM,QAAQ,IAAI,CAAC,MAAM,QAAQ,IAAI,CAAC,MAAM,EAAE,CAAC,KAAK,IAAI,CAAC;AAAA,MACzD,GAAI,SAAS,WAAW,SAAY,EAAE,QAAQ,SAAS,OAAO,IAAI,CAAC;AAAA,IACrE,CAAC;AAAA,EACH;AAEA,SAAO,UAAU,kBAAkB;AACrC;AAMA,eAAsB,SAAS,OAAc,SAA+C;AAC1F,QAAM,QAAQ,MAAM,QAAQ,OAAO,OAAO;AAC1C,QAAM,WAAW,QAAQ,YAAY;AAErC,QAAM,OAAO,IAAI,KAAK,CAAC,KAAY,GAAG;AAAA,IACpC,MAAM;AAAA,EACR,CAAC;AACD,QAAM,MAAM,IAAI,gBAAgB,IAAI;AACpC,QAAM,IAAI,SAAS,cAAc,GAAG;AACpC,IAAE,OAAO;AACT,IAAE,WAAW;AACb,IAAE,MAAM;AACR,MAAI,gBAAgB,GAAG;AACzB;AAMA,eAAsB,MACpB,OACA,MACA,SACe;AACf,MAAI,QAAQ,qBAAqB,MAAM;AACrC,UAAM,IAAI;AAAA,MACR;AAAA,IAGF;AAAA,EACF;AACA,QAAM,QAAQ,MAAM,QAAQ,OAAO,OAAO;AAC1C,QAAM,EAAE,UAAU,IAAI,MAAM,OAAO,aAAkB;AACrD,QAAM,UAAU,MAAM,KAAK;AAC7B;AAIA,SAAS,aAAa,SAAuD;AAC3E,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,MAAgB,CAAC;AACvB,aAAW,KAAK,SAAS;AACvB,eAAW,OAAO,OAAO,KAAK,CAAC,GAAG;AAChC,UAAI,CAAC,KAAK,IAAI,GAAG,GAAG;AAClB,aAAK,IAAI,GAAG;AACZ,YAAI,KAAK,GAAG;AAAA,MACd;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAgBO,IAAM,yBAAN,cAAqC,MAAM;AAAA,EAChD,YACkB,QACA,OAChB;AACA;AAAA,MACE,uCAAuC,MAAM,0BAA0B,KAAK;AAAA,IAG9E;AAPgB;AACA;AAOhB,SAAK,OAAO;AAAA,EACd;AAAA,EATkB;AAAA,EACA;AASpB;AAqEA,eAAsB,UACpB,OACA,OACA,SAC2B;AAC3B,QAAM,gBAAgB,aAAa,MAAM;AAEzC,QAAM,SAAuB,QAAQ,UAAU;AAC/C,QAAM,QAAQ,QAAQ,SAAS;AAC/B,QAAM,QAAQ,QAAQ,cAAc,CAAC;AACrC,QAAM,gBAAgB,QAAQ,aAAa,KAAK;AAChD,MAAI,eAAe,GAAG;AACpB,UAAM,IAAI,MAAM,uDAAuD;AAAA,EACzE;AAEA,QAAM,UAAU,MAAM,SAAS,KAAK;AACpC,MAAI,QAAQ,OAAO,WAAW,GAAG;AAC/B,WAAO,cAAc,OAAO,QAAQ,YAAY,QAAQ,KAAK;AAAA,EAC/D;AACA,QAAM,QAAQ,QAAQ,UAAU,SAC5B,QAAQ,OAAO,CAAC,IAChB,QAAQ,OAAO,KAAK,CAAC,MAAM,EAAE,SAAS,QAAQ,KAAK;AACvD,MAAI,UAAU,QAAW;AACvB,UAAM,IAAI;AAAA,MACR,mDAAmD,QAAQ,KAAK,iBAChD,QAAQ,OAAO,IAAI,CAAC,MAAM,IAAI,EAAE,IAAI,GAAG,EAAE,KAAK,IAAI,CAAC;AAAA,IACrE;AAAA,EACF;AAEA,QAAM,UAAU,MAAM;AACtB,MAAI,QAAQ,UAAU,cAAc;AAClC,WAAO,cAAc,OAAO,QAAQ,YAAY,QAAQ,KAAK;AAAA,EAC/D;AACA,QAAM,YAAY,QAAQ,YAAY;AAKtC,QAAM,aAAa,oBAAI,IAAoB;AAC3C,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,SAAS,GAAG;AACpD,UAAM,YAAY,kBAAkB,KAAK;AACzC,QAAI,cAAc,GAAI;AACtB,eAAW,IAAI,KAAK,SAAS;AAAA,EAC/B;AAMA,QAAM,aAAa,oBAAI,IAAiC;AACxD,QAAM,YAAY,IAAI,IAAI,WAAW,OAAO,CAAC;AAC7C,aAAW,SAAS,WAAW;AAC7B,UAAM,kBAAkB,QAAQ,QAAQ,KAAK;AAC7C,QAAI,oBAAoB,UAAa,gBAAgB,SAAS,GAAG;AAC/D,iBAAW,IAAI,OAAO,kBAAkB,OAAO,eAAe,CAAC;AAC/D;AAAA,IACF;AACA,QAAI;AACF,YAAM,eAAe,MAAM,MAAM,WAAW,KAAK,EAAE,KAAK;AACxD,UAAI,aAAa,SAAS,GAAG;AAC3B,mBAAW,IAAI,OAAO,kBAAkB,OAAO,YAAY,CAAC;AAAA,MAC9D;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,QAAM,UAAqC,CAAC;AAC5C,WAAS,IAAI,eAAe,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACtD,UAAM,MAAM,QAAQ,CAAC;AACrB,UAAM,SAAkC,CAAC;AACzC,QAAI,SAAS;AACb,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,GAAG,GAAG;AAC9C,YAAM,QAAQ,WAAW,IAAI,GAAG;AAChC,UAAI,UAAU,OAAW;AACzB,YAAM,UAAU,eAAe,OAAO,MAAM,KAAK,CAAC;AAClD,UAAI,YAAY,QAAW;AACzB,cAAM,SAAS,WAAW,IAAI,KAAK;AACnC,YAAI,WAAW,UAAa,OAAO,YAAY,UAAU;AACvD,iBAAO,KAAK,IAAI,OAAO,IAAI,OAAO,KAAK;AAAA,QACzC,OAAO;AACL,iBAAO,KAAK,IAAI;AAAA,QAClB;AACA,iBAAS;AAAA,MACX;AAAA,IACF;AACA,QAAI,OAAQ,SAAQ,KAAK,MAAM;AAAA,EACjC;AAEA,QAAM,OAAO,UAAM,sBAAU,OAAO,EAAE,CAAC,QAAQ,UAAU,GAAG,QAAQ,GAAG;AAAA,IACrE,aAAa,CAAC,QAAQ,UAAU;AAAA,IAChC;AAAA,EACF,CAAC;AAED,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,MAAM,QAAuB;AAG3B,YAAM,MAAM,MAAM,YAAY,CAAC,OAAO;AACpC,cAAM,UAAU,GAAG,MAAM,MAAM,IAAI;AACnC,mBAAW,SAAS,KAAK,OAAO;AAC9B,kBAAQ,WAAW,MAAM,UAAU,EAAE,IAAI,MAAM,IAAI,MAAM,QAAQ,EAAE,QAAQ,cAAc,CAAC;AAAA,QAC5F;AACA,YAAI,WAAW,eAAe;AAC5B,qBAAW,SAAS,KAAK,UAAU;AACjC,oBAAQ,WAAW,MAAM,UAAU,EAAE,IAAI,MAAM,IAAI,MAAM,QAAQ,EAAE,QAAQ,cAAc,CAAC;AAAA,UAC5F;AAAA,QACF;AACA,YAAI,WAAW,WAAW;AACxB,qBAAW,SAAS,KAAK,SAAS;AAChC,oBAAQ,WAAW,MAAM,UAAU,EAAE,OAAO,MAAM,EAAE;AAAA,UACtD;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAEA,eAAe,cACb,OACA,YACA,QACA,OAC2B;AAC3B,QAAM,OAAO,UAAM,sBAAU,OAAO,EAAE,CAAC,UAAU,GAAG,CAAC,EAAE,GAAG,EAAE,aAAa,CAAC,UAAU,GAAG,MAAM,CAAC;AAC9F,SAAO,EAAE,MAAM,QAAQ,MAAM,QAAQ;AAAA,EAAsB,EAAE;AAC/D;AAQA,SAAS,kBAAkB,QAAgB,SAAoD;AAC7F,QAAM,MAAM,oBAAI,IAAoB;AACpC,aAAW,SAAS,SAAS;AAC3B,eAAW,SAAS,OAAO,OAAO,MAAM,MAAM,GAAG;AAC/C,UAAI,UAAU,GAAI;AAClB,YAAM,WAAW,IAAI,IAAI,KAAK;AAC9B,UAAI,aAAa,UAAa,aAAa,MAAM,KAAK;AACpD,cAAM,IAAI,uBAAuB,QAAQ,KAAK;AAAA,MAChD;AACA,UAAI,IAAI,OAAO,MAAM,GAAG;AAAA,IAC1B;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,kBAAkB,OAAwB;AAIjD,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,MAAI,OAAO,UAAU,YAAY,OAAO,UAAU,UAAW,QAAO,OAAO,KAAK;AAChF,SAAO;AACT;AAEA,SAAS,eACP,OACA,MACS;AACT,MAAI,UAAU,UAAa,UAAU,KAAM,QAAO;AAClD,MAAI,SAAS,OAAW,QAAO;AAC/B,MAAI,SAAS,UAAU;AACrB,QAAI,OAAO,UAAU,SAAU,QAAO;AACtC,QAAI,OAAO,UAAU,YAAY,OAAO,UAAU,UAAW,QAAO,OAAO,KAAK;AAChF,WAAO;AAAA,EACT;AACA,MAAI,SAAS,UAAU;AACrB,QAAI,OAAO,UAAU,SAAU,QAAO;AACtC,UAAM,IAAI,OAAO,KAAK;AACtB,WAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAAA,EAClC;AACA,MAAI,SAAS,WAAW;AACtB,QAAI,OAAO,UAAU,UAAW,QAAO;AACvC,QAAI,UAAU,UAAU,UAAU,EAAG,QAAO;AAC5C,QAAI,UAAU,WAAW,UAAU,EAAG,QAAO;AAC7C,WAAO;AAAA,EACT;AACA,MAAI,SAAS,QAAQ;AAKnB,QAAI,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,GAAG;AACvD,YAAM,KAAK,gBAAgB,KAAK;AAChC,YAAM,IAAI,IAAI,KAAK,EAAE;AACrB,aAAO,EAAE,YAAY;AAAA,IACvB;AACA,QAAI,OAAO,UAAU,UAAU;AAC7B,YAAM,IAAI,IAAI,KAAK,KAAK;AACxB,UAAI,CAAC,OAAO,MAAM,EAAE,QAAQ,CAAC,EAAG,QAAO,EAAE,YAAY;AAAA,IACvD;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAYA,SAAS,gBAAgB,QAAwB;AAC/C,QAAM,oBAAoB;AAC1B,QAAM,aAAa;AACnB,SAAO,KAAK,OAAO,SAAS,qBAAqB,UAAU;AAC7D;","names":["import_as_zip"]}
package/dist/index.d.cts DELETED
@@ -1,300 +0,0 @@
1
- import { DictEntry, VaultDiff, Vault } from '@noy-db/hub';
2
-
3
- /**
4
- * Minimal zero-dependency XLSX writer.
5
- *
6
- * An `.xlsx` file is a ZIP archive (Office Open XML / OOXML) with
7
- * SpreadsheetML inside. This writer emits the six parts needed for
8
- * a valid worksheet and hands them to `@noy-db/as-zip`'s
9
- * `writeZip()` to assemble the final `.xlsx` bytes.
10
- *
11
- * ## Emitted parts
12
- *
13
- * ```
14
- * [Content_Types].xml # MIME descriptors
15
- * _rels/.rels # root → workbook pointer
16
- * xl/workbook.xml # sheet list
17
- * xl/_rels/workbook.xml.rels # sheet-part pointers
18
- * xl/worksheets/sheet<N>.xml # cell data
19
- * xl/sharedStrings.xml # string pool (Unicode-safe)
20
- * ```
21
- *
22
- * Strings route through the shared-string table (`sharedStrings.xml`)
23
- * rather than being inlined on cells, which is:
24
- *
25
- * 1. Slightly more compact when strings repeat (client names,
26
- * status labels, locale codes).
27
- * 2. Consistent with how Excel writes its own files — some
28
- * strict-OOXML readers refuse inline strings.
29
- *
30
- * Numbers, booleans, and dates are written as typed cells; strings
31
- * and everything else fall back to the shared-string path.
32
- *
33
- * ## Not supported
34
- *
35
- * - Cell styles (fonts, colours, borders, number formats).
36
- * - Formulas, merged cells, frozen panes, auto-filter.
37
- * - Charts, images, drawings.
38
- * - Zip64 / archives > 4 GiB.
39
- *
40
- * @module
41
- */
42
- /** One row in a sheet. Values are coerced per type at emit time. */
43
- type XlsxRow = ReadonlyArray<unknown>;
44
- /** One sheet in a workbook. */
45
- interface XlsxSheet {
46
- /** Sheet tab name — Excel caps at 31 chars; we truncate with `…`. */
47
- readonly name: string;
48
- /** Header row, rendered as row 1. Omit to skip the header. */
49
- readonly header?: readonly string[];
50
- /** Data rows — each is an array aligned with `header` if present. */
51
- readonly rows: readonly XlsxRow[];
52
- /**
53
- * Optional per-column widths in Excel character units (same scale as
54
- * SheetJS's `wch`). When set, emits a `<cols>` block so Excel opens
55
- * the file with the columns sized as specified instead of the default
56
- * 10-character width. Index aligned with `header` / row cells.
57
- *
58
- * Non-finite or non-positive entries are skipped (column falls back
59
- * to Excel's default width). A consumer typically passes `undefined`
60
- * for "auto" columns and a number for explicit widths.
61
- */
62
- readonly widths?: ReadonlyArray<number | undefined>;
63
- }
64
- /**
65
- * Build a complete `.xlsx` byte stream from the supplied sheet data.
66
- * Pure — no I/O beyond the internal zip concatenation.
67
- */
68
- declare function writeXlsx(sheets: readonly XlsxSheet[]): Promise<Uint8Array>;
69
- /**
70
- * Convert a 1-based column index to Excel A1 letter notation.
71
- * 1 → A, 26 → Z, 27 → AA, 702 → ZZ, 703 → AAA.
72
- */
73
- declare function colLetter(n: number): string;
74
-
75
- /**
76
- * Minimal OOXML reader. Inverse of `writeXlsx` in `xlsx.ts`.
77
- *
78
- * Walks the three parts the writer emits:
79
- *
80
- * - `xl/sharedStrings.xml` → string table (idx → string)
81
- * - `xl/workbook.xml` → sheet name list (with sheetId)
82
- * - `xl/_rels/workbook.xml.rels` → sheetId → sheet part path
83
- * - `xl/worksheets/sheet<N>.xml` → cell data
84
- *
85
- * Cell types matched to the writer's emission rules:
86
- * - no `t` attribute → number (`<v>` is parsed as Number)
87
- * - `t="s"` → shared-string ref
88
- * - `t="b"` → boolean (`1` ↔ true, `0` ↔ false)
89
- * - empty `<c />` → undefined
90
- *
91
- * Excel date serials are NOT auto-converted — the writer outputs ISO-
92
- * 8601 strings via the shared-string path, so dates round-trip as
93
- * strings unless the consumer opts into `dateFields` coercion (handled
94
- * one layer up in `index.ts:fromBytes`).
95
- *
96
- * @module
97
- */
98
- /** A row of cell values keyed by column letter (`'A' → 'foo'`). */
99
- type ReadXlsxRow = Record<string, unknown>;
100
- interface ReadXlsxSheet {
101
- /** Sheet tab name from `xl/workbook.xml`. */
102
- readonly name: string;
103
- /** All rows in declaration order. Columns indexed by Excel letter. */
104
- readonly rows: readonly ReadXlsxRow[];
105
- }
106
- interface ReadXlsxResult {
107
- readonly sheets: readonly ReadXlsxSheet[];
108
- }
109
- /**
110
- * Decode an `.xlsx` (OOXML) byte stream into per-sheet row data. The
111
- * caller decides what to do with the rows (header inference, type
112
- * coercion, record building) — the reader stays format-only.
113
- *
114
- * Throws on malformed XML, missing parts, or sheet-id mismatches.
115
- */
116
- declare function readXlsx(bytes: Uint8Array): Promise<ReadXlsxResult>;
117
-
118
- /**
119
- * **@noy-db/as-xlsx** — Excel spreadsheet plaintext export for noy-db.
120
- *
121
- * Produces a real `.xlsx` file (Office Open XML / OOXML) from one
122
- * or more noy-db collections. Opens natively in Excel, Numbers,
123
- * LibreOffice Calc, Google Sheets, and every modern spreadsheet
124
- * tool.
125
- *
126
- * Zero runtime dependencies — the XLSX encoder builds the required
127
- * SpreadsheetML parts and assembles them with
128
- * `@noy-db/as-zip`'s `writeZip()` (STORE method; most xlsx
129
- * contents are XML text which Excel compresses at open time anyway).
130
- *
131
- * Part of the `@noy-db/as-*` portable-artefact family, plaintext
132
- * tier. See [`docs/patterns/as-exports.md`](https://github.com/vLannaAi/noy-db/blob/main/docs/patterns/as-exports.md).
133
- *
134
- * ## Authorisation
135
- *
136
- * Every call is gated by `assertCanExport('plaintext', 'xlsx')`.
137
- *
138
- * ```ts
139
- * await db.grant('firm', {
140
- * userId: 'accountant', role: 'viewer', passphrase: '…',
141
- * exportCapability: { plaintext: ['xlsx'] },
142
- * })
143
- * ```
144
- *
145
- * @packageDocumentation
146
- */
147
-
148
- /** Per-sheet options for the noy-db consumer API. */
149
- interface AsXlsxSheetOptions {
150
- /**
151
- * Sheet tab name. Excel caps at 31 chars; longer names are
152
- * truncated with `…`. Duplicates are suffixed `(2)`, `(3)`.
153
- */
154
- readonly name: string;
155
- /** Source collection. Must be in the caller's read ACL. */
156
- readonly collection: string;
157
- /**
158
- * Field list + order. When omitted, columns are inferred from
159
- * the union of keys across all records (first-record-wins order).
160
- */
161
- readonly columns?: readonly string[];
162
- /**
163
- * Optional predicate against each decrypted record. Runs after
164
- * decryption; doesn't reduce I/O.
165
- */
166
- readonly filter?: (record: unknown) => boolean;
167
- /**
168
- * Optional per-column character widths (Excel `wch` units). When set,
169
- * the emitted sheet opens with the requested column widths instead of
170
- * Excel's default 10-character fallback. Index aligned with
171
- * `columns` / inferred-column order.
172
- *
173
- * Non-finite or non-positive entries are skipped so consumers can
174
- * mix explicit + auto (pass `undefined` for "auto").
175
- *
176
- * Length is NOT validated against `columns.length` — extra entries
177
- * are harmless (no `<col>` is emitted past the column count) and a
178
- * short array leaves trailing columns at Excel's default. This
179
- * mirrors `XlsxSheet.widths`, which it threads through.
180
- */
181
- readonly widths?: ReadonlyArray<number | undefined>;
182
- }
183
- /** Single-collection convenience — passed where a sheet-list is accepted. */
184
- interface AsXlsxOptions {
185
- /** One or more sheets. At least one required. */
186
- readonly sheets: readonly AsXlsxSheetOptions[];
187
- }
188
- /** Options for `download()` — adds optional filename. */
189
- interface AsXlsxDownloadOptions extends AsXlsxOptions {
190
- /** Filename offered to the browser. Default `'export.xlsx'`. */
191
- readonly filename?: string;
192
- }
193
- /** Options for `write()` — requires explicit risk acknowledgement. */
194
- interface AsXlsxWriteOptions extends AsXlsxOptions {
195
- /** Tier 3 egress — see `docs/patterns/as-exports.md`. */
196
- readonly acknowledgeRisks: true;
197
- }
198
- /**
199
- * Convenience — single-collection shorthand. Equivalent to
200
- * `toBytes(vault, { sheets: [{ name: collectionName, collection: collectionName }] })`.
201
- */
202
- declare function toBytesFromCollection(vault: Vault, collectionName: string): Promise<Uint8Array>;
203
- /**
204
- * Build the `.xlsx` byte stream from one or more sheets. Pure
205
- * beyond the auth check + store reads.
206
- */
207
- declare function toBytes(vault: Vault, options: AsXlsxOptions): Promise<Uint8Array>;
208
- /**
209
- * Browser download. Requires a browser-like environment with
210
- * `URL.createObjectURL` + `document.createElement`.
211
- */
212
- declare function download(vault: Vault, options: AsXlsxDownloadOptions): Promise<void>;
213
- /**
214
- * Node file-write. Requires `acknowledgeRisks: true` because the
215
- * plaintext xlsx persists past the process (Tier 3 egress).
216
- */
217
- declare function write(vault: Vault, path: string, options: AsXlsxWriteOptions): Promise<void>;
218
-
219
- type ImportPolicy = 'merge' | 'replace' | 'insert-only';
220
- /**
221
- * Thrown when a dict field contains two different keys whose labels are
222
- * identical in any locale — making label→key inversion ambiguous.
223
- *
224
- * @example
225
- * dict has: { key: 'a', labels: { en: 'Open' } } and { key: 'b', labels: { th: 'Open' } }
226
- * → XlsxDictAmbiguityError('status', 'Open')
227
- */
228
- declare class XlsxDictAmbiguityError extends Error {
229
- readonly column: string;
230
- readonly label: string;
231
- constructor(column: string, label: string);
232
- }
233
- interface AsXlsxImportOptions {
234
- /** Target collection. xlsx has no native collection grouping. */
235
- readonly collection: string;
236
- /**
237
- * Sheet name to read. Defaults to the first sheet in the workbook.
238
- */
239
- readonly sheet?: string;
240
- /**
241
- * 1-based header row index. Default `1` (first row).
242
- */
243
- readonly headerRow?: number;
244
- /**
245
- * Optional field type hints. xlsx cells already have a type
246
- * (number, boolean, shared-string), so this is for the few cases
247
- * where the writer's emission rules don't preserve intent —
248
- * notably ISO-date strings the writer routed through the shared-
249
- * string path. `'date'` parses the value with `new Date()` and
250
- * keeps the result as an ISO-8601 string for stable round-tripping.
251
- */
252
- readonly fieldTypes?: Record<string, 'string' | 'number' | 'boolean' | 'date'>;
253
- /** Field carrying the record id. Default `'id'`. */
254
- readonly idKey?: string;
255
- /** Reconciliation policy. Default `'merge'`. */
256
- readonly policy?: ImportPolicy;
257
- /**
258
- * Per-field dict definitions for label→key inversion. When a column
259
- * header matches a key here, cell values are matched against every
260
- * locale label in the dict entries; matching labels are replaced by
261
- * their stable key before building the ImportPlan.
262
- *
263
- * Takes precedence over any vault dictionary with the same name.
264
- * For fields not listed here, `fromBytes` automatically tries
265
- * `vault.dictionary(fieldName).list()` as a fallback.
266
- *
267
- * Unknown labels (no match in any locale) pass through as-is.
268
- */
269
- readonly dicts?: Readonly<Record<string, readonly DictEntry[]>>;
270
- }
271
- interface AsXlsxImportPlan {
272
- readonly plan: VaultDiff;
273
- readonly policy: ImportPolicy;
274
- apply(): Promise<void>;
275
- }
276
- /**
277
- * Build an import plan from an `.xlsx` byte stream. Inverts what
278
- * `toBytes()` writes — the first row is the header, subsequent rows
279
- * are records keyed by the column letters in the header row.
280
- *
281
- * Capability: `assertCanImport('plaintext', 'xlsx')`.
282
- * Atomicity: `apply()` runs inside `vault.noydb.transaction()`.
283
- *
284
- * **Not supported (matches the writer scope):**
285
- * - Cell styles / number formats / date format codes
286
- * - Formulas, merged cells, frozen panes
287
- * - Inline strings → handled defensively (since some upstream tools
288
- * emit them) but the writer never produces them
289
- * - Excel date serials → not auto-detected; pass `fieldTypes: { ts:
290
- * 'date' }` to coerce a numeric serial to ISO. Date round-trip via
291
- * the writer (which emits ISO strings) works without a hint.
292
- *
293
- * **Dict-label inversion** — supply `dicts` per field (or populate vault
294
- * dictionaries with `withI18n()`) and the reader automatically inverts
295
- * human labels back to their stable keys. Ambiguous labels throw
296
- * `XlsxDictAmbiguityError`; unknown labels pass through unchanged.
297
- */
298
- declare function fromBytes(vault: Vault, bytes: Uint8Array, options: AsXlsxImportOptions): Promise<AsXlsxImportPlan>;
299
-
300
- export { type AsXlsxDownloadOptions, type AsXlsxImportOptions, type AsXlsxImportPlan, type AsXlsxOptions, type AsXlsxSheetOptions, type AsXlsxWriteOptions, type ImportPolicy, type ReadXlsxResult, type ReadXlsxRow, type ReadXlsxSheet, XlsxDictAmbiguityError, type XlsxRow, type XlsxSheet, colLetter, download, fromBytes, readXlsx, toBytes, toBytesFromCollection, write, writeXlsx };