{"_id":"hucre","_rev":"12-09fb21abe60ea8a05c272ed86f7ba5e0","name":"hucre","dist-tags":{"latest":"1.1.0"},"versions":{"0.0.1":{"name":"hucre","version":"0.0.1","keywords":["csv","esm","excel","export","import","ods","parse","read","spreadsheet","tree-shakeable","typescript","write","xlsx","zero-dependency"],"author":{"name":"productdevbook"},"license":"MIT","_id":"hucre@0.0.1","maintainers":[{"name":"productdevbook","email":"hi@productdevbook.com"}],"homepage":"https://github.com/productdevbook/hucre","bugs":{"url":"https://github.com/productdevbook/hucre/issues"},"bin":{"hucre":"dist/cli.mjs"},"dist":{"shasum":"41f9b79b796ecd1b50fac686ba219cec465cb03a","tarball":"https://registry.npmjs.org/hucre/-/hucre-0.0.1.tgz","fileCount":27,"integrity":"sha512-Zxtq+/SKfLu9gCGZhm+fSz7uOZ77pKBVzEekO4XDuzUhwyPW4Kx82ViCly9s/6YmSUvJb13jSC4oRsUi6Hv7kA==","signatures":[{"sig":"MEUCIQC9/M48WY3mXKk5G5qbflPrkQoP5u66J/kSi/0X+AJkqwIgAQzuvWNZ4ZjRbC01rhF7r/IZcUJEU8svWrBNDPvW9/0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":363486},"type":"module","types":"./dist/index.d.mts","module":"./dist/index.mjs","exports":{".":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"./csv":{"types":"./dist/csv.d.mts","default":"./dist/csv.mjs"},"./ods":{"types":"./dist/ods.d.mts","default":"./dist/ods.mjs"},"./xlsx":{"types":"./dist/xlsx.d.mts","default":"./dist/xlsx.mjs"}},"funding":"https://github.com/sponsors/productdevbook","gitHead":"17c9618eee08aceb44dfe49548d6c3a0b3f9ead0","scripts":{"dev":"vitest","fmt":"oxfmt .","lint":"oxlint . && oxfmt --check .","test":"pnpm lint && pnpm typecheck && vitest run","build":"obuild","prepack":"pnpm build","release":"pnpm test && pnpm build && bumpp --commit --tag --push --all","lint:fix":"oxlint . --fix && oxfmt .","typecheck":"tsgo --noEmit"},"_npmUser":{"name":"productdevbook","email":"hi@productdevbook.com"},"repository":{"url":"git+https://github.com/productdevbook/hucre.git","type":"git"},"_npmVersion":"11.6.1","description":"Zero-dependency spreadsheet engine. Read & write XLSX, CSV, ODS. Schema validation, streaming, tree-shakeable. Pure TypeScript, works everywhere.","directories":{},"_nodeVersion":"24.11.0","_hasShrinkwrap":false,"devDependencies":{"bumpp":"^11.0.1","citty":"^0.2.1","oxfmt":"^0.42.0","obuild":"^0.4.32","oxlint":"^1.57.0","vitest":"^4.1.1","consola":"^3.4.2","typescript":"^6.0.2","@vitest/coverage-v8":"^4.1.1","@typescript/native-preview":"7.0.0-dev.20260316.1"},"_npmOperationalInternal":{"tmp":"tmp/hucre_0.0.1_1774626602908_0.70954514656911","host":"s3://npm-registry-packages-npm-production"}},"0.1.0":{"name":"hucre","version":"0.1.0","keywords":["csv","esm","excel","export","import","ods","parse","read","spreadsheet","tree-shakeable","typescript","write","xlsx","zero-dependency"],"author":{"name":"productdevbook"},"license":"MIT","_id":"hucre@0.1.0","maintainers":[{"name":"productdevbook","email":"hi@productdevbook.com"}],"homepage":"https://github.com/productdevbook/hucre","bugs":{"url":"https://github.com/productdevbook/hucre/issues"},"bin":{"hucre":"dist/cli.mjs"},"dist":{"shasum":"d6e41fb0937de70ff222c082f26a9b70a67111ed","tarball":"https://registry.npmjs.org/hucre/-/hucre-0.1.0.tgz","fileCount":27,"integrity":"sha512-/3ZQKkGT+EIjbd+ZUrepDLP4xdNhjW+Vlj+VYNTjq8nI6EzVPrC14H6YjITfjm1kTKClP+Bmg0LYwIC4UxExFw==","signatures":[{"sig":"MEUCIEqSrGt650N1ecEPx2CTPltD0k2x/kFo4GUqLXbHI29pAiEAh/R04DInkMqjW9hYtvI9AL4S1Bw260NPMTfBo/JApnM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/hucre@0.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":363456},"type":"module","_from":"file:hucre-0.1.0.tgz","types":"./dist/index.d.mts","module":"./dist/index.mjs","exports":{".":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"./csv":{"types":"./dist/csv.d.mts","default":"./dist/csv.mjs"},"./ods":{"types":"./dist/ods.d.mts","default":"./dist/ods.mjs"},"./xlsx":{"types":"./dist/xlsx.d.mts","default":"./dist/xlsx.mjs"}},"funding":"https://github.com/sponsors/productdevbook","scripts":{"dev":"vitest","fmt":"oxfmt .","lint":"oxlint . && oxfmt --check .","test":"pnpm lint && pnpm typecheck && vitest run","build":"obuild","release":"pnpm test && pnpm build && bumpp --commit --tag --push --all","lint:fix":"oxlint . --fix && oxfmt .","typecheck":"tsgo --noEmit"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:b2fe83d5-d334-4e66-a6d5-55854dffa069"}},"_resolved":"/tmp/fc0d15e1757a373ff0579185c3acec49/hucre-0.1.0.tgz","_integrity":"sha512-/3ZQKkGT+EIjbd+ZUrepDLP4xdNhjW+Vlj+VYNTjq8nI6EzVPrC14H6YjITfjm1kTKClP+Bmg0LYwIC4UxExFw==","repository":{"url":"git+https://github.com/productdevbook/hucre.git","type":"git"},"_npmVersion":"11.9.0","description":"Zero-dependency spreadsheet engine. Read & write XLSX, CSV, ODS. Schema validation, streaming, tree-shakeable. Pure TypeScript, works everywhere.","directories":{},"_nodeVersion":"24.14.0","_hasShrinkwrap":false,"devDependencies":{"bumpp":"^11.0.1","citty":"^0.2.1","oxfmt":"^0.42.0","obuild":"^0.4.32","oxlint":"^1.57.0","vitest":"^4.1.1","consola":"^3.4.2","typescript":"^6.0.2","@vitest/coverage-v8":"^4.1.1","@typescript/native-preview":"7.0.0-dev.20260316.1"},"_npmOperationalInternal":{"tmp":"tmp/hucre_0.1.0_1774627003871_0.5926943178678994","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"hucre","version":"0.2.0","keywords":["csv","esm","excel","export","import","ods","parse","read","spreadsheet","tree-shakeable","typescript","write","xlsx","zero-dependency"],"author":{"name":"productdevbook"},"license":"MIT","_id":"hucre@0.2.0","maintainers":[{"name":"productdevbook","email":"hi@productdevbook.com"}],"homepage":"https://github.com/productdevbook/hucre","bugs":{"url":"https://github.com/productdevbook/hucre/issues"},"bin":{"hucre":"dist/cli.mjs"},"dist":{"shasum":"0a0e91caa8187ba777e8ef4dd51befd1328bc22c","tarball":"https://registry.npmjs.org/hucre/-/hucre-0.2.0.tgz","fileCount":28,"integrity":"sha512-MCeDaEBCzXMr90nlhX8iKMJQpyvnfaICOpCnk8tvCTMn5akrXQxgg6RyVY6qqLdMHl/KXdmV93g4g9wkf3Tfkg==","signatures":[{"sig":"MEUCIDYuq0VITNwRRaWztb6ItfjZZeNeOpzAMvqnlyshpLlxAiEAhVX19xCzbECfL9dqrtxf2MuP6ipPiEv95YOmyuXuWdQ=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/hucre@0.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":389718},"type":"module","_from":"file:hucre-0.2.0.tgz","types":"./dist/index.d.mts","module":"./dist/index.mjs","exports":{".":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"./csv":{"types":"./dist/csv.d.mts","default":"./dist/csv.mjs"},"./ods":{"types":"./dist/ods.d.mts","default":"./dist/ods.mjs"},"./xlsx":{"types":"./dist/xlsx.d.mts","default":"./dist/xlsx.mjs"}},"funding":"https://github.com/sponsors/productdevbook","scripts":{"dev":"vitest","fmt":"oxfmt .","lint":"oxlint . && oxfmt --check .","test":"pnpm lint && pnpm typecheck && vitest run","build":"obuild","release":"pnpm test && pnpm build && bumpp --commit --tag --push --all","lint:fix":"oxlint . --fix && oxfmt .","typecheck":"tsgo --noEmit"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:b2fe83d5-d334-4e66-a6d5-55854dffa069"}},"_resolved":"/tmp/06cc7695cd6f3665fbb4004b2133acc5/hucre-0.2.0.tgz","_integrity":"sha512-MCeDaEBCzXMr90nlhX8iKMJQpyvnfaICOpCnk8tvCTMn5akrXQxgg6RyVY6qqLdMHl/KXdmV93g4g9wkf3Tfkg==","repository":{"url":"git+https://github.com/productdevbook/hucre.git","type":"git"},"_npmVersion":"11.11.0","description":"Zero-dependency spreadsheet engine. Read & write XLSX, CSV, ODS. Schema validation, streaming, tree-shakeable. Pure TypeScript, works everywhere.","directories":{},"_nodeVersion":"24.14.1","_hasShrinkwrap":false,"devDependencies":{"bumpp":"^11.0.1","citty":"^0.2.1","oxfmt":"^0.42.0","obuild":"^0.4.32","oxlint":"^1.57.0","vitest":"^4.1.1","consola":"^3.4.2","typescript":"^6.0.2","@vitest/coverage-v8":"^4.1.1","@typescript/native-preview":"7.0.0-dev.20260316.1"},"_npmOperationalInternal":{"tmp":"tmp/hucre_0.2.0_1775279971959_0.9272586575423742","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"hucre","version":"0.3.0","keywords":["csv","esm","excel","export","import","ods","parse","read","spreadsheet","tree-shakeable","typescript","write","xlsx","zero-dependency"],"author":{"name":"productdevbook"},"license":"MIT","_id":"hucre@0.3.0","maintainers":[{"name":"productdevbook","email":"hi@productdevbook.com"}],"homepage":"https://github.com/productdevbook/hucre","bugs":{"url":"https://github.com/productdevbook/hucre/issues"},"bin":{"hucre":"dist/cli.mjs"},"dist":{"shasum":"88bdb1233d7a89ef6e69090266581c17f93ebe83","tarball":"https://registry.npmjs.org/hucre/-/hucre-0.3.0.tgz","fileCount":27,"integrity":"sha512-gXLYQGcZY/7WwS2We6dV7fsO8SGbKBuiGGntTfOQKh2cbZGg4+aMiCQPsFACngb7/ezoTf+GazxoWISDbv098g==","signatures":[{"sig":"MEUCIQCW4cBEUnPm2Re6ieyO5B6GYd9uFnes8th0cfgaSrrLHwIgO0ADhPybtcvGCKAiKtl5GRQc1kXlOzaZkXdSvfip2Hc=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/hucre@0.3.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":375341},"type":"module","_from":"file:hucre-0.3.0.tgz","types":"./dist/index.d.mts","module":"./dist/index.mjs","exports":{".":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"./csv":{"types":"./dist/csv.d.mts","default":"./dist/csv.mjs"},"./ods":{"types":"./dist/ods.d.mts","default":"./dist/ods.mjs"},"./xlsx":{"types":"./dist/xlsx.d.mts","default":"./dist/xlsx.mjs"}},"funding":"https://github.com/sponsors/productdevbook","scripts":{"dev":"vitest","fmt":"oxfmt .","lint":"oxlint . && oxfmt --check .","test":"pnpm lint && pnpm typecheck && vitest run","build":"obuild","release":"pnpm test && pnpm build && bumpp --commit --tag --push --all","lint:fix":"oxlint . --fix && oxfmt .","typecheck":"tsgo --noEmit"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:b2fe83d5-d334-4e66-a6d5-55854dffa069"}},"_resolved":"/tmp/e6bc43829247d4e6ea55313f317c61c1/hucre-0.3.0.tgz","_integrity":"sha512-gXLYQGcZY/7WwS2We6dV7fsO8SGbKBuiGGntTfOQKh2cbZGg4+aMiCQPsFACngb7/ezoTf+GazxoWISDbv098g==","repository":{"url":"git+https://github.com/productdevbook/hucre.git","type":"git"},"_npmVersion":"11.11.0","description":"Zero-dependency spreadsheet engine. Read & write XLSX, CSV, ODS. Schema validation, streaming, tree-shakeable. Pure TypeScript, works everywhere.","directories":{},"_nodeVersion":"24.14.1","_hasShrinkwrap":false,"devDependencies":{"bumpp":"^11.0.1","citty":"^0.2.1","oxfmt":"^0.42.0","obuild":"^0.4.32","oxlint":"^1.57.0","vitest":"^4.1.1","consola":"^3.4.2","typescript":"^6.0.2","@vitest/coverage-v8":"^4.1.1","@typescript/native-preview":"7.0.0-dev.20260316.1"},"_npmOperationalInternal":{"tmp":"tmp/hucre_0.3.0_1775496438110_0.2509444704089143","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"hucre","version":"0.4.0","keywords":["csv","esm","excel","export","import","ods","parse","read","spreadsheet","tree-shakeable","typescript","write","xlsx","zero-dependency"],"author":{"name":"productdevbook"},"license":"MIT","_id":"hucre@0.4.0","maintainers":[{"name":"productdevbook","email":"hi@productdevbook.com"}],"homepage":"https://github.com/productdevbook/hucre","bugs":{"url":"https://github.com/productdevbook/hucre/issues"},"bin":{"hucre":"dist/cli.mjs"},"dist":{"shasum":"c598e1c538c4badd4b811f25ee3637297cfa6592","tarball":"https://registry.npmjs.org/hucre/-/hucre-0.4.0.tgz","fileCount":157,"integrity":"sha512-O5J0VR5COwAlRMLRitQH6SW7oy5S3UsEKnT9htLisGBwcKkKrLffhsknFz9Zrgndv8qWHLn7Kj7hxbtM45ECFA==","signatures":[{"sig":"MEQCIArec2/qH/Jhw76fefvwT2YbjB1wRIhcm+l51+lUiqFDAiBokxWoFBsMsOh3teAEU2rHWBfUmTVjIzYAphQ6Qe7K9w==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/hucre@0.4.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":690932},"type":"module","_from":"file:hucre-0.4.0.tgz","types":"./dist/index.d.mts","module":"./dist/index.mjs","exports":{".":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"./csv":{"types":"./dist/csv.d.mts","default":"./dist/csv.mjs"},"./ods":{"types":"./dist/ods.d.mts","default":"./dist/ods.mjs"},"./xml":{"types":"./dist/xml.d.mts","default":"./dist/xml.mjs"},"./json":{"types":"./dist/json.d.mts","default":"./dist/json.mjs"},"./xlsx":{"types":"./dist/xlsx.d.mts","default":"./dist/xlsx.mjs"}},"funding":"https://github.com/sponsors/productdevbook","scripts":{"dev":"vitest","fmt":"oxfmt .","lint":"oxlint . && oxfmt --check .","test":"pnpm lint && pnpm typecheck && vitest run","build":"obuild","release":"pnpm test && pnpm build && bumpp --commit --tag --push --all","lint:fix":"oxlint . --fix && oxfmt .","typecheck":"tsgo --noEmit"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:b2fe83d5-d334-4e66-a6d5-55854dffa069"}},"_resolved":"/tmp/2afd3da20e9d5434bfe9fd903c1df66d/hucre-0.4.0.tgz","_integrity":"sha512-O5J0VR5COwAlRMLRitQH6SW7oy5S3UsEKnT9htLisGBwcKkKrLffhsknFz9Zrgndv8qWHLn7Kj7hxbtM45ECFA==","repository":{"url":"git+https://github.com/productdevbook/hucre.git","type":"git"},"_npmVersion":"11.11.0","description":"Zero-dependency spreadsheet engine. Read & write XLSX, CSV, ODS. Schema validation, streaming, tree-shakeable. Pure TypeScript, works everywhere.","directories":{},"_nodeVersion":"24.14.1","_hasShrinkwrap":false,"devDependencies":{"bumpp":"^11.0.1","citty":"^0.2.1","oxfmt":"^0.42.0","obuild":"^0.4.32","oxlint":"^1.57.0","vitest":"^4.1.1","consola":"^3.4.2","typescript":"^6.0.2","@vitest/coverage-v8":"^4.1.1","@typescript/native-preview":"7.0.0-dev.20260316.1"},"_npmOperationalInternal":{"tmp":"tmp/hucre_0.4.0_1777104895052_0.6629333550776042","host":"s3://npm-registry-packages-npm-production"}},"0.5.0":{"name":"hucre","version":"0.5.0","keywords":["csv","esm","excel","export","import","ods","parse","read","spreadsheet","tree-shakeable","typescript","write","xlsx","zero-dependency"],"author":"productdevbook","license":"MIT","_id":"hucre@0.5.0","maintainers":[{"name":"productdevbook","email":"hi@productdevbook.com"}],"homepage":"https://github.com/productdevbook/hucre","bugs":{"url":"https://github.com/productdevbook/hucre/issues"},"bin":{"hucre":"./dist/cli.mjs"},"dist":{"shasum":"48e89b8b9dd5944c49c874f4cb0e2f7bc9c22df4","tarball":"https://registry.npmjs.org/hucre/-/hucre-0.5.0.tgz","fileCount":209,"integrity":"sha512-0/z7GrOLySyT/EUSZ7zWm0ZED14qtUZDw7WjyEjDzxdTe7yl7c9cU0OxELqruN4LykRbwxEef7IK0QEnPmQLPQ==","signatures":[{"sig":"MEQCIFGDvMfX70A98ssvZy+gGJEtCX++X+z+J2D9OEaoH3fuAiBh3gtBcC4mWaZDjkd6nfHORBTLACwX364Ujr34qoYt6A==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/hucre@0.5.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":2641894},"type":"module","types":"./dist/index.d.mts","module":"./dist/index.mjs","exports":{".":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"./csv":{"types":"./dist/csv.d.mts","default":"./dist/csv.mjs"},"./ods":{"types":"./dist/ods.d.mts","default":"./dist/ods.mjs"},"./xml":{"types":"./dist/xml.d.mts","default":"./dist/xml.mjs"},"./json":{"types":"./dist/json.d.mts","default":"./dist/json.mjs"},"./xlsx":{"types":"./dist/xlsx.d.mts","default":"./dist/xlsx.mjs"}},"funding":"https://github.com/sponsors/productdevbook","scripts":{"dev":"vitest","fmt":"oxfmt .","lint":"oxlint . && oxfmt --check .","test":"pnpm lint && pnpm typecheck && vitest run","build":"obuild","release":"pnpm test && pnpm build && bumpp --commit --tag --push --all","lint:fix":"oxlint . --fix && oxfmt .","typecheck":"tsgo --noEmit"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:b2fe83d5-d334-4e66-a6d5-55854dffa069"}},"repository":{"url":"git+https://github.com/productdevbook/hucre.git","type":"git"},"description":"Zero-dependency spreadsheet engine. Read & write XLSX, CSV, ODS. Schema validation, streaming, tree-shakeable. Pure TypeScript, works everywhere.","directories":{},"_nodeVersion":"24.14.1","_hasShrinkwrap":false,"devDependencies":{"bumpp":"^11.0.1","citty":"^0.2.1","oxfmt":"^0.42.0","obuild":"^0.4.32","oxlint":"^1.57.0","vitest":"^4.1.1","consola":"^3.4.2","typescript":"^6.0.2","@vitest/coverage-v8":"^4.1.1","@typescript/native-preview":"7.0.0-dev.20260316.1"},"_npmOperationalInternal":{"tmp":"tmp/hucre_0.5.0_1778271552788_0.15264978696070486","host":"s3://npm-registry-packages-npm-production"}},"0.5.1":{"name":"hucre","version":"0.5.1","keywords":["csv","esm","excel","export","import","ods","parse","read","spreadsheet","tree-shakeable","typescript","write","xlsx","zero-dependency"],"author":"productdevbook","license":"MIT","_id":"hucre@0.5.1","maintainers":[{"name":"productdevbook","email":"hi@productdevbook.com"}],"homepage":"https://github.com/productdevbook/hucre","bugs":{"url":"https://github.com/productdevbook/hucre/issues"},"bin":{"hucre":"./dist/cli.mjs"},"dist":{"shasum":"c5c3514f670610add28e62bc3ef0623ba8d5a98a","tarball":"https://registry.npmjs.org/hucre/-/hucre-0.5.1.tgz","fileCount":213,"integrity":"sha512-ZKTM4V30NNjUKMbJd/CRJgsAj/xBMTfhMa6G7uurbh+ubn5EHLKm81/eGFGEso5wTUEdYK7t6QC1kOmRRDslzg==","signatures":[{"sig":"MEQCIDkKTeC+ea6OkBhZkATQMiLxcEd2ONcM3y2HsDB+fgElAiBEpg2/3jNF6WR+cO8Ic/Sb5zeFe+icegdA9ic8QfD1ag==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/hucre@0.5.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":2647071},"type":"module","types":"./dist/index.d.mts","module":"./dist/index.mjs","exports":{".":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"./csv":{"types":"./dist/csv.d.mts","default":"./dist/csv.mjs"},"./ods":{"types":"./dist/ods.d.mts","default":"./dist/ods.mjs"},"./xml":{"types":"./dist/xml.d.mts","default":"./dist/xml.mjs"},"./json":{"types":"./dist/json.d.mts","default":"./dist/json.mjs"},"./xlsx":{"types":"./dist/xlsx.d.mts","default":"./dist/xlsx.mjs"}},"funding":"https://github.com/sponsors/productdevbook","scripts":{"dev":"vitest","fmt":"oxfmt .","lint":"oxlint . && oxfmt --check .","test":"pnpm lint && pnpm typecheck && vitest run","build":"obuild","release":"pnpm test && pnpm build && bumpp --commit --tag --push --all","lint:fix":"oxlint . --fix && oxfmt .","typecheck":"tsgo --noEmit"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:b2fe83d5-d334-4e66-a6d5-55854dffa069"}},"repository":{"url":"git+https://github.com/productdevbook/hucre.git","type":"git"},"description":"Zero-dependency spreadsheet engine. Read & write XLSX, CSV, ODS. Schema validation, streaming, tree-shakeable. Pure TypeScript, works everywhere.","directories":{},"_nodeVersion":"24.14.1","_hasShrinkwrap":false,"devDependencies":{"bumpp":"^11.0.1","citty":"^0.2.1","oxfmt":"^0.42.0","obuild":"^0.4.32","oxlint":"^1.57.0","vitest":"^4.1.1","consola":"^3.4.2","typescript":"^6.0.2","@vitest/coverage-v8":"^4.1.1","@typescript/native-preview":"7.0.0-dev.20260316.1"},"_npmOperationalInternal":{"tmp":"tmp/hucre_0.5.1_1778482088527_0.7774437438715374","host":"s3://npm-registry-packages-npm-production"}},"0.6.0":{"name":"hucre","version":"0.6.0","keywords":["csv","esm","excel","export","import","ods","parse","read","spreadsheet","tree-shakeable","typescript","write","xlsx","zero-dependency"],"author":"productdevbook","license":"MIT","_id":"hucre@0.6.0","maintainers":[{"name":"productdevbook","email":"hi@productdevbook.com"}],"homepage":"https://github.com/productdevbook/hucre","bugs":{"url":"https://github.com/productdevbook/hucre/issues"},"bin":{"hucre":"./dist/cli.mjs"},"dist":{"shasum":"d75314ff6354fe11f574ae72c64761a530df1711","tarball":"https://registry.npmjs.org/hucre/-/hucre-0.6.0.tgz","fileCount":229,"integrity":"sha512-Osajs1c1Si0TaUIfminbkWkQI370ZCAEq9CUTQtbi7n80Fa6ejCo9NYTKdtqJV/4j+HPwqd7H7GOwkwI1X4gbw==","signatures":[{"sig":"MEUCIQChgyfGQg9M4kksvy0bUt8L1bQ80QApYWkQXVeU7f0I9wIgUKJgUmSLsMwoIn59f753J5u3dekDxJd8XXhj2SV37gU=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/hucre@0.6.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":2788059},"type":"module","types":"./dist/index.d.mts","module":"./dist/index.mjs","exports":{".":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"./csv":{"types":"./dist/csv.d.mts","default":"./dist/csv.mjs"},"./ods":{"types":"./dist/ods.d.mts","default":"./dist/ods.mjs"},"./xml":{"types":"./dist/xml.d.mts","default":"./dist/xml.mjs"},"./json":{"types":"./dist/json.d.mts","default":"./dist/json.mjs"},"./xlsx":{"types":"./dist/xlsx.d.mts","default":"./dist/xlsx.mjs"}},"funding":"https://github.com/sponsors/productdevbook","scripts":{"dev":"vitest","fmt":"oxfmt .","lint":"oxlint . && oxfmt --check .","test":"pnpm lint && pnpm typecheck && vitest run","build":"obuild","release":"pnpm test && pnpm build && bumpp --commit --tag --push --all","lint:fix":"oxlint . --fix && oxfmt .","typecheck":"tsgo --noEmit"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:b2fe83d5-d334-4e66-a6d5-55854dffa069"}},"repository":{"url":"git+https://github.com/productdevbook/hucre.git","type":"git"},"description":"Zero-dependency spreadsheet engine. Read & write XLSX, CSV, ODS. Schema validation, streaming, tree-shakeable. Pure TypeScript, works everywhere.","directories":{},"_nodeVersion":"24.16.0","_hasShrinkwrap":false,"devDependencies":{"bumpp":"^11.0.1","citty":"^0.2.1","oxfmt":"^0.42.0","obuild":"^0.4.32","oxlint":"^1.57.0","vitest":"^4.1.1","consola":"^3.4.2","typescript":"^6.0.2","@vitest/coverage-v8":"^4.1.1","@typescript/native-preview":"7.0.0-dev.20260316.1"},"_npmOperationalInternal":{"tmp":"tmp/hucre_0.6.0_1780006284866_0.6713981899421984","host":"s3://npm-registry-packages-npm-production"}},"0.6.1":{"name":"hucre","version":"0.6.1","keywords":["csv","esm","excel","export","import","ods","parse","read","spreadsheet","tree-shakeable","typescript","write","xlsx","zero-dependency"],"author":"productdevbook","license":"MIT","_id":"hucre@0.6.1","maintainers":[{"name":"productdevbook","email":"hi@productdevbook.com"}],"homepage":"https://github.com/productdevbook/hucre","bugs":{"url":"https://github.com/productdevbook/hucre/issues"},"bin":{"hucre":"./dist/cli.mjs"},"dist":{"shasum":"9fd9e9d1dcb894ee2893b853da41eabe463683a8","tarball":"https://registry.npmjs.org/hucre/-/hucre-0.6.1.tgz","fileCount":231,"integrity":"sha512-E7cDE4eRz09hFa1/8tXrqZsuqzN93F8c//coO7zWBzNN926mJy0YHP5xLdmMNaWk2/F7Rab63QkFTLkh9ZcNvQ==","signatures":[{"sig":"MEYCIQDTN6S9dcnaNcgp1jJJS7rda2c0Vfyf6Ksq/tpDSiKpAAIhAIREZ1kBmV0ZbHPDeWluKQ6t/yCoYNch3rKkHWzwgdlb","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/hucre@0.6.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":2817053},"type":"module","types":"./dist/index.d.mts","module":"./dist/index.mjs","exports":{".":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"./csv":{"types":"./dist/csv.d.mts","default":"./dist/csv.mjs"},"./ods":{"types":"./dist/ods.d.mts","default":"./dist/ods.mjs"},"./xml":{"types":"./dist/xml.d.mts","default":"./dist/xml.mjs"},"./json":{"types":"./dist/json.d.mts","default":"./dist/json.mjs"},"./xlsx":{"types":"./dist/xlsx.d.mts","default":"./dist/xlsx.mjs"}},"funding":"https://github.com/sponsors/productdevbook","scripts":{"dev":"vitest","fmt":"oxfmt .","lint":"oxlint . && oxfmt --check .","test":"pnpm lint && pnpm typecheck && vitest run","build":"obuild","release":"pnpm test && pnpm build && bumpp --commit --tag --push --all","lint:fix":"oxlint . --fix && oxfmt .","typecheck":"tsgo --noEmit"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:b2fe83d5-d334-4e66-a6d5-55854dffa069"}},"repository":{"url":"git+https://github.com/productdevbook/hucre.git","type":"git"},"description":"Zero-dependency spreadsheet engine. Read & write XLSX, CSV, ODS. Schema validation, streaming, tree-shakeable. Pure TypeScript, works everywhere.","directories":{},"_nodeVersion":"24.16.0","dependencies":{"citty":"^0.2.1","consola":"^3.4.2"},"_hasShrinkwrap":false,"devDependencies":{"bumpp":"^11.0.1","oxfmt":"^0.42.0","obuild":"^0.4.32","oxlint":"^1.57.0","vitest":"^4.1.1","typescript":"^6.0.2","@vitest/coverage-v8":"^4.1.1","@typescript/native-preview":"7.0.0-dev.20260316.1"},"_npmOperationalInternal":{"tmp":"tmp/hucre_0.6.1_1781078672262_0.707580686648628","host":"s3://npm-registry-packages-npm-production"}},"0.6.2":{"name":"hucre","version":"0.6.2","keywords":["csv","esm","excel","export","import","ods","parse","read","spreadsheet","tree-shakeable","typescript","write","xlsx","zero-dependency"],"author":"productdevbook","license":"MIT","_id":"hucre@0.6.2","maintainers":[{"name":"productdevbook","email":"hi@productdevbook.com"}],"homepage":"https://github.com/productdevbook/hucre","bugs":{"url":"https://github.com/productdevbook/hucre/issues"},"bin":{"hucre":"./dist/cli.mjs"},"dist":{"shasum":"59ec87e2035562601c55d65b9e2d6d1a5592b274","tarball":"https://registry.npmjs.org/hucre/-/hucre-0.6.2.tgz","fileCount":231,"integrity":"sha512-o1PqTiKfx37tFmiiPElwpEfFvlXcXn5CEgDbt1uYk1bVvzDNEIAIKnxrKhfcHLZx6jc5PdofOy8PuwJcIZsqUg==","signatures":[{"sig":"MEUCIBChVmd88gQ6KJKsjADXFqln6RpUvTaODWXTm1jXP9uXAiEAn4CunnHBaju4mGIYd+W0+2bp/f5Pc97Rc7p6s8AHNkE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/hucre@0.6.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":2072673},"type":"module","types":"./dist/index.d.mts","module":"./dist/index.mjs","exports":{".":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"./csv":{"types":"./dist/csv.d.mts","default":"./dist/csv.mjs"},"./ods":{"types":"./dist/ods.d.mts","default":"./dist/ods.mjs"},"./xml":{"types":"./dist/xml.d.mts","default":"./dist/xml.mjs"},"./json":{"types":"./dist/json.d.mts","default":"./dist/json.mjs"},"./xlsx":{"types":"./dist/xlsx.d.mts","default":"./dist/xlsx.mjs"}},"funding":"https://github.com/sponsors/productdevbook","scripts":{"dev":"vitest","fmt":"oxfmt .","lint":"oxlint . && oxfmt --check .","test":"pnpm lint && pnpm typecheck && vitest run","build":"obuild","release":"pnpm test && pnpm build && bumpp --commit --tag --push --all","lint:fix":"oxlint . --fix && oxfmt .","typecheck":"tsgo --noEmit"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:b2fe83d5-d334-4e66-a6d5-55854dffa069"}},"repository":{"url":"git+https://github.com/productdevbook/hucre.git","type":"git"},"description":"Zero-dependency spreadsheet engine. Read & write XLSX, CSV, ODS. Schema validation, streaming, tree-shakeable. Pure TypeScript, works everywhere.","directories":{},"_nodeVersion":"24.18.0","_hasShrinkwrap":false,"devDependencies":{"bumpp":"^12.0.0","citty":"^0.2.2","oxfmt":"^0.59.0","obuild":"^0.4.38","oxlint":"^1.74.0","vitest":"^4.1.10","consola":"^3.4.2","typescript":"^7.0.2","@vitest/coverage-v8":"^4.1.10","@typescript/native-preview":"7.0.0-dev.20260316.1"},"_npmOperationalInternal":{"tmp":"tmp/hucre_0.6.2_1784657963060_0.8390494844396414","host":"s3://npm-registry-packages-npm-production"}},"1.0.0":{"name":"hucre","version":"1.0.0","keywords":["csv","esm","excel","export","import","ods","parse","read","spreadsheet","tree-shakeable","typescript","write","xlsx","zero-dependency"],"author":"productdevbook","license":"MIT","_id":"hucre@1.0.0","maintainers":[{"name":"productdevbook","email":"hi@productdevbook.com"}],"homepage":"https://github.com/productdevbook/hucre","bugs":{"url":"https://github.com/productdevbook/hucre/issues"},"bin":{"hucre":"./dist/cli.mjs"},"dist":{"shasum":"f0ba191eec21c7942218116c3dd1d3d023e0a63d","tarball":"https://registry.npmjs.org/hucre/-/hucre-1.0.0.tgz","fileCount":254,"integrity":"sha512-+K5H8z3P8/oAxPpHgvzWbTcsae2X+XoeXk9YqxAvYsuuIoXMK8PMAxmsbeRb3MuHA4fgJlBBCY/G016eQkNokA==","signatures":[{"sig":"MEUCIQDy9c6WQqZipyl7CfMQBDkoOZOogADrJy6GhdoesRo00wIgC8Xduwoou9T+F0lbA6Se9c3BmjZ6zAhpKVfMzUUWwvU=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/hucre@1.0.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":2619693},"type":"module","types":"./dist/index.d.mts","module":"./dist/index.mjs","exports":{".":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"./csv":{"types":"./dist/csv.d.mts","default":"./dist/csv.mjs"},"./ods":{"types":"./dist/ods.d.mts","default":"./dist/ods.mjs"},"./xml":{"types":"./dist/xml.d.mts","default":"./dist/xml.mjs"},"./json":{"types":"./dist/json.d.mts","default":"./dist/json.mjs"},"./xlsx":{"types":"./dist/xlsx.d.mts","default":"./dist/xlsx.mjs"},"./ooxml":{"types":"./dist/ooxml.d.mts","default":"./dist/ooxml.mjs"}},"funding":"https://github.com/sponsors/productdevbook","scripts":{"dev":"vitest","fmt":"oxfmt .","lint":"oxlint . && oxfmt --check .","test":"pnpm lint && pnpm typecheck && vitest run","build":"obuild","release":"pnpm test && pnpm build && bumpp --commit --tag --push --all","lint:fix":"oxlint . --fix && oxfmt .","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.cli.json","verify:package":"pnpm build && node scripts/verify-package.mjs"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:b2fe83d5-d334-4e66-a6d5-55854dffa069"}},"repository":{"url":"git+https://github.com/productdevbook/hucre.git","type":"git"},"description":"Zero-dependency spreadsheet engine. Read & write XLSX, CSV, ODS. Schema validation, streaming, tree-shakeable. Pure TypeScript, works everywhere.","directories":{},"_nodeVersion":"24.18.0","_hasShrinkwrap":false,"devDependencies":{"bumpp":"^12.1.1","citty":"^0.2.2","oxfmt":"^0.61.0","obuild":"^0.4.38","oxlint":"^1.76.0","vitest":"^4.1.10","consola":"^3.4.2","typescript":"^7.0.2","@types/node":"^26.1.2","@vitest/coverage-v8":"^4.1.10"},"_npmOperationalInternal":{"tmp":"tmp/hucre_1.0.0_1785835005212_0.3519323329357904","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"hucre","version":"1.1.0","description":"Zero-dependency spreadsheet engine. Read & write XLSX, CSV, ODS. Schema validation, streaming, tree-shakeable. Pure TypeScript, works everywhere.","keywords":["csv","esm","excel","export","import","ods","parse","read","spreadsheet","tree-shakeable","typescript","write","xlsx","zero-dependency"],"homepage":"https://github.com/productdevbook/hucre","bugs":{"url":"https://github.com/productdevbook/hucre/issues"},"license":"MIT","author":"productdevbook","repository":{"type":"git","url":"git+https://github.com/productdevbook/hucre.git"},"funding":"https://github.com/sponsors/productdevbook","bin":{"hucre":"./dist/cli.mjs"},"type":"module","sideEffects":false,"module":"./dist/index.mjs","types":"./dist/index.d.mts","exports":{".":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"./xlsx":{"types":"./dist/xlsx.d.mts","default":"./dist/xlsx.mjs"},"./csv":{"types":"./dist/csv.d.mts","default":"./dist/csv.mjs"},"./ods":{"types":"./dist/ods.d.mts","default":"./dist/ods.mjs"},"./json":{"types":"./dist/json.d.mts","default":"./dist/json.mjs"},"./xml":{"types":"./dist/xml.d.mts","default":"./dist/xml.mjs"},"./ooxml":{"types":"./dist/ooxml.d.mts","default":"./dist/ooxml.mjs"}},"devDependencies":{"@types/node":"^26.1.2","@vitest/coverage-v8":"^4.1.10","bumpp":"^12.1.1","citty":"^0.2.2","consola":"^3.4.2","obuild":"^0.4.38","oxfmt":"^0.61.0","oxlint":"^1.76.0","rolldown":"^1.2.3","typescript":"^7.0.2","vitest":"^4.1.10"},"engines":{"node":">=24"},"scripts":{"bench":"node bench/index.mjs","build":"obuild","verify:package":"pnpm build && node scripts/verify-package.mjs","coverage":"vitest run --coverage","size":"pnpm build && node scripts/size.mjs","size:update":"pnpm build && node scripts/size.mjs --update","dev":"vitest","lint":"oxlint . && oxfmt --check .","lint:fix":"oxlint . --fix && oxfmt .","fmt":"oxfmt .","test":"pnpm lint && pnpm typecheck && vitest run","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.cli.json","release":"pnpm test && pnpm build && bumpp --commit --tag --push --all"},"_nodeVersion":"24.18.0","_id":"hucre@1.1.0","dist":{"integrity":"sha512-3HROYLWzejzhMKdGVrBuU4UWuDleVVQwpjFeYwTHzXLN8o2kptOkPS1Yq04xL5kQVwF6ELM1R2myuWW8WNu1fQ==","shasum":"16c661f475059b2d12fdb41c78ef606ad6ed3e96","tarball":"https://registry.npmjs.org/hucre/-/hucre-1.1.0.tgz","fileCount":281,"unpackedSize":2781985,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/hucre@1.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCrGigX9SbGcXnGCc+tpd7Zl0UqyuyZOGEbclxmGK9uJQIgA251SMZm1NINyxP1NHVVO1L1nRy+A4APmhGqJ4BG6Rk="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:b2fe83d5-d334-4e66-a6d5-55854dffa069"}},"directories":{},"maintainers":[{"name":"productdevbook","email":"hi@productdevbook.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/hucre_1.1.0_1786642132679_0.29987153216290285"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-27T15:50:02.907Z","modified":"2026-08-13T17:28:53.192Z","0.0.1":"2026-03-27T15:50:03.079Z","0.1.0":"2026-03-27T15:56:44.005Z","0.2.0":"2026-04-04T05:19:32.140Z","0.3.0":"2026-04-06T17:27:18.260Z","0.4.0":"2026-04-25T08:14:55.206Z","0.5.0":"2026-05-08T20:19:12.988Z","0.5.1":"2026-05-11T06:48:08.706Z","0.6.0":"2026-05-28T22:11:25.066Z","0.6.1":"2026-06-10T08:04:32.448Z","0.6.2":"2026-07-21T18:19:23.244Z","1.0.0":"2026-08-04T09:16:45.390Z","1.1.0":"2026-08-13T17:28:52.887Z"},"bugs":{"url":"https://github.com/productdevbook/hucre/issues"},"author":"productdevbook","license":"MIT","homepage":"https://github.com/productdevbook/hucre","keywords":["csv","esm","excel","export","import","ods","parse","read","spreadsheet","tree-shakeable","typescript","write","xlsx","zero-dependency"],"repository":{"type":"git","url":"git+https://github.com/productdevbook/hucre.git"},"description":"Zero-dependency spreadsheet engine. Read & write XLSX, CSV, ODS. Schema validation, streaming, tree-shakeable. Pure TypeScript, works everywhere.","maintainers":[{"name":"productdevbook","email":"hi@productdevbook.com"}],"readme":"<p align=\"center\">\n  <br>\n  <img src=\".github/assets/cover.svg\" alt=\"hucre — Zero-dependency spreadsheet engine\" width=\"100%\">\n  <br><br>\n  <b style=\"font-size: 2em;\">hucre</b>\n  <br><br>\n  Zero-dependency spreadsheet engine.\n  <br>\n  Read & write XLSX, CSV, ODS, JSON, NDJSON, XML. Schema validation, streaming, round-trip preservation. Pure TypeScript, works everywhere.\n  <br><br>\n  <a href=\"https://npmjs.com/package/hucre\"><img src=\"https://img.shields.io/npm/v/hucre?style=flat&colorA=18181B&colorB=34d399\" alt=\"npm version\"></a>\n  <a href=\"https://npmjs.com/package/hucre\"><img src=\"https://img.shields.io/npm/dm/hucre?style=flat&colorA=18181B&colorB=34d399\" alt=\"npm downloads\"></a>\n  <a href=\"https://bundlephobia.com/result?p=hucre\"><img src=\"https://img.shields.io/bundlephobia/minzip/hucre?style=flat&colorA=18181B&colorB=34d399\" alt=\"bundle size\"></a>\n  <a href=\"https://github.com/productdevbook/hucre/blob/main/LICENSE\"><img src=\"https://img.shields.io/github/license/productdevbook/hucre?style=flat&colorA=18181B&colorB=34d399\" alt=\"license\"></a>\n</p>\n\n## Quick Start\n\n```sh\nnpm install hucre\n```\n\n```ts\nimport { readXlsx, writeXlsx } from \"hucre\"\n\n// Read an XLSX file\nconst workbook = await readXlsx(buffer)\nconsole.log(workbook.sheets[0].rows)\n\n// Write an XLSX file\nconst xlsx = await writeXlsx({\n  sheets: [\n    {\n      name: \"Products\",\n      columns: [\n        { header: \"Name\", key: \"name\", width: 25 },\n        { header: \"Price\", key: \"price\", width: 12, numFmt: \"$#,##0.00\" },\n        { header: \"Stock\", key: \"stock\", width: 10 },\n      ],\n      data: [\n        { name: \"Widget\", price: 9.99, stock: 142 },\n        { name: \"Gadget\", price: 24.5, stock: 87 },\n      ],\n    },\n  ],\n})\n```\n\n## Tree Shaking\n\nImport only what you need:\n\n```ts\nimport { readXlsx, writeXlsx } from \"hucre/xlsx\" // XLSX only\nimport { parseCsv, writeCsv } from \"hucre/csv\" // CSV only (~2 KB gzipped)\nimport { readOds, writeOds } from \"hucre/ods\" // ODS only\nimport { parseJson, writeNdjson } from \"hucre/json\" // JSON / NDJSON\nimport { readXml, writeXml } from \"hucre/xml\" // Tabular XML\n```\n\n## Why hucre?\n\n### vs JavaScript / TypeScript Libraries\n\n|                         | hucre                | SheetJS CE    | ExcelJS   | xlsx-js-style |\n| ----------------------- | -------------------- | ------------- | --------- | ------------- |\n| **Dependencies**        | 0                    | 0\\*           | 9         | 0\\*           |\n| **Bundle (gzip)**       | 4–129 KB<sup>†</sup> | ~300 KB       | ~500 KB   | ~300 KB       |\n| **ESM native**          | Yes                  | Partial       | No (CJS)  | Partial       |\n| **TypeScript**          | Native               | Bolted-on     | Bolted-on | Bolted-on     |\n| **Edge runtime**        | Yes                  | No            | No        | No            |\n| **CSP compliant**       | Yes                  | Yes           | No (eval) | Yes           |\n| **npm published**       | Yes                  | No (CDN only) | Stale     | Yes           |\n| **Read + Write**        | Yes                  | Yes (Pro $)   | Yes       | Yes           |\n| **Styling**             | Yes                  | No (Pro $)    | Yes       | Yes           |\n| **Cond. formatting**    | 13 types<sup>‡</sup> | No (Pro $)    | Yes       | No            |\n| **Stream read + write** | Yes                  | CSV only      | Yes       | CSV only      |\n| **ODS support**         | Yes<sup>§</sup>      | Yes           | No        | Yes           |\n| **Round-trip**          | Yes                  | Partial       | Partial   | Partial       |\n| **Sparklines**          | Yes                  | No            | No        | No            |\n| **Tables**              | Yes                  | Yes           | Yes       | Yes           |\n| **Images**              | Yes                  | No (Pro $)    | Yes       | No            |\n\n\\* SheetJS removed itself from npm; must install from CDN tarball.\n\n† Depends on what you import — hucre is fully tree-shakeable, so there is no\nsingle number. Minified + gzipped, bundled with rolldown against `dist/`:\n`{ parseCsv, writeCsv }` from `hucre/csv` = **3.7 KB**, `{ readXlsx }` from\n`hucre/xlsx` = **34 KB**, `{ readXlsx, writeXlsx }` = **68 KB**, the entire\nlibrary (`export * from \"hucre\"`) = **129 KB**.\n\nThese four are measured by `pnpm size` and pinned in\n`scripts/size-budget.json`, so CI fails when one grows past its budget.\nThey are in the README because they had already drifted once — the\nprevious figures (2.3 / 32 / 64 / 114 KB) were true when written and were\nenforced by nothing.\n\n‡ `cellIs`, `expression`, `colorScale`, `dataBar`, `iconSet`, `containsText`,\n`notContainsText`, `beginsWith`, `endsWith`, `containsBlanks`,\n`notContainsBlanks`, `duplicateValues`, `uniqueValues` — read and written.\n`top10` and `aboveAverage` round-trip in their default form only (the writer\nemits no `rank` / `percent` / `bottom` or `aboveAverage` / `equalAverage` /\n`stdDev` attributes), and `timePeriod` rules are dropped on read.\n\n§ Values, formulas and merges round-trip in full; cell styling covers six\nfacets (bold, italic, size, font colour, background colour, number format).\nBorders, alignment, column widths, freeze panes, validation, named ranges,\nimages and page setup are not modelled in either direction, so ODS → ODS is\nlossless while XLSX → ODS drops them. See [What ODS carries](#what-ods-carries).\n\n### vs Libraries in Other Languages\n\n|                       | hucre (TS)          | openpyxl (Py) | XlsxWriter (Py) | rust_xlsxwriter | Apache POI (Java) |\n| --------------------- | ------------------- | ------------- | --------------- | --------------- | ----------------- |\n| **Read XLSX**         | Yes                 | Yes           | No              | No              | Yes               |\n| **Write XLSX**        | Yes                 | Yes           | Yes             | Yes             | Yes               |\n| **Streaming**         | Read+Write          | Read+Write    | const_memory    | const_memory    | SXSSF (write)     |\n| **Charts**            | Round-trip          | 15+ types     | 9 types         | 12+ types       | Limited           |\n| **Pivot tables**      | Read + Write (skel) | Read-only     | No              | No              | Limited           |\n| **Cond. formatting**  | 13 types (see ‡)    | Yes           | Yes             | Yes             | Yes               |\n| **Sparklines**        | Yes                 | Yes           | Yes             | Yes             | No                |\n| **Formula eval**      | No                  | No            | No              | No              | Yes               |\n| **Multi-format**      | XLSX/ODS/CSV        | XLSX only     | XLSX only       | XLSX only       | XLS/XLSX          |\n| **Zero dependencies** | Yes                 | lxml optional | No              | Yes             | No                |\n\n\"Read + Write (skel)\" for pivot tables: hucre writes the pivot cache, layout\nand relationships, but not the pre-computed value cells — Excel fills those\nin on first open. See [Pivot Tables](#pivot-tables).\n\n## Features\n\n### Reading\n\n```ts\nimport { readXlsx } from \"hucre/xlsx\"\n\nconst wb = await readXlsx(uint8Array, {\n  sheets: [0, \"Products\"], // Filter sheets by index or name\n  readStyles: true, // Parse cell styles\n  dateSystem: \"auto\", // Auto-detect 1900/1904\n})\n\nfor (const sheet of wb.sheets) {\n  console.log(sheet.name) // \"Products\"\n  console.log(sheet.rows) // CellValue[][]\n  console.log(sheet.merges) // MergeRange[]\n}\n```\n\n`sheets` also accepts a predicate that runs against lightweight metadata\n**before** each worksheet body is parsed — useful for visibility-based\nselection without paying the I/O cost of the full read:\n\n```ts\nconst wb = await readXlsx(buf, {\n  sheets: (info) => !info.hidden && !info.veryHidden,\n})\n// info: { name, index, hidden?, veryHidden? }\n```\n\nSupported cell types: strings, numbers, booleans, dates, formulas, rich text, errors, inline strings.\n\n### Writing\n\n```ts\nimport { writeXlsx } from \"hucre/xlsx\"\n\nconst buffer = await writeXlsx({\n  sheets: [\n    {\n      name: \"Report\",\n      columns: [\n        { header: \"Date\", key: \"date\", width: 15, numFmt: \"yyyy-mm-dd\" },\n        { header: \"Revenue\", key: \"revenue\", width: 15, numFmt: \"$#,##0.00\" },\n        { header: \"Active\", key: \"active\", width: 10 },\n      ],\n      data: [\n        { date: new Date(\"2026-01-15\"), revenue: 12500, active: true },\n        { date: new Date(\"2026-01-16\"), revenue: 8900, active: false },\n      ],\n      freezePane: { rows: 1 },\n      autoFilter: { range: \"A1:C3\" },\n    },\n  ],\n  defaultFont: { name: \"Calibri\", size: 11 },\n})\n```\n\nA row entry may be a value or a cell object, so styling one cell does not\nmean naming its position again in a parallel map:\n\n```ts\nawait writeXlsx({\n  sheets: [\n    {\n      name: \"Report\",\n      rows: [\n        [{ value: \"Region\", style: { font: { bold: true } } }, \"Revenue\"],\n        [\"EU\", { value: 12500, style: { numFmt: \"$#,##0.00\" } }],\n        [\"Total\", { formula: \"SUM(B2:B2)\" }],\n      ],\n    },\n  ],\n})\n```\n\nAnything a cell carries works there — `style`, `formula`, `richText`,\n`hyperlink`, `checkbox`. `cells` still takes a `\"row,col\"` map, and wins\nwhere both describe the same position.\n\nFeatures: cell styles, auto column widths, merged cells, freeze/split panes, auto-filter (with per-column value filters — `<filters><filter val=\"…\"/></filters>`; custom/dynamic/colour criteria are not emitted), data validation, hyperlinks, images (PNG/JPEG/GIF/SVG/WebP), comments, tables, conditional formatting (all 15 rule types, with their dxf styles), named ranges, print settings, page breaks, sheet protection, workbook protection, rich text, shared/array/dynamic formulas, sparklines, textboxes, background images, number formats, hidden sheets, Excel 2024 native checkboxes, HTML/Markdown/JSON/TSV export, template engine.\n\n### Auto Column Width\n\n```ts\nconst buffer = await writeXlsx({\n  sheets: [\n    {\n      name: \"Products\",\n      columns: [\n        { header: \"Name\", key: \"name\", autoWidth: true },\n        { header: \"Price\", key: \"price\", autoWidth: true, numFmt: \"$#,##0.00\" },\n        { header: \"SKU\", key: \"sku\", autoWidth: true },\n      ],\n      data: products,\n    },\n  ],\n})\n```\n\nCalculates optimal column widths from cell content — font-aware, handles CJK double-width characters, number formats, min/max constraints.\n\n### Data Validation\n\n```ts\nconst buffer = await writeXlsx({\n  sheets: [\n    {\n      name: \"Sheet1\",\n      rows: [\n        [\"Status\", \"Quantity\"],\n        [\"active\", 10],\n      ],\n      dataValidations: [\n        {\n          type: \"list\",\n          values: [\"active\", \"inactive\", \"draft\"],\n          range: \"A2:A100\",\n          showErrorMessage: true,\n          errorTitle: \"Invalid\",\n          errorMessage: \"Pick from the list\",\n        },\n        {\n          type: \"whole\",\n          operator: \"between\",\n          formula1: \"0\",\n          formula2: \"1000\",\n          range: \"B2:B100\",\n        },\n      ],\n    },\n  ],\n})\n```\n\n### Hyperlinks\n\n```ts\nconst buffer = await writeXlsx({\n  sheets: [\n    {\n      name: \"Links\",\n      rows: [[\"Visit Google\", \"Go to Sheet2\"]],\n      cells: new Map([\n        [\n          \"0,0\",\n          {\n            value: \"Visit Google\",\n            type: \"string\",\n            hyperlink: { target: \"https://google.com\", tooltip: \"Open Google\" },\n          },\n        ],\n        [\n          \"0,1\",\n          {\n            value: \"Go to Sheet2\",\n            type: \"string\",\n            hyperlink: { target: \"\", location: \"Sheet2!A1\" },\n          },\n        ],\n      ]),\n    },\n  ],\n})\n```\n\nFor tabular reports, put links **inline in `data` rows** instead of a parallel\n`cells` map — keyed by the column's `key`. Use the `link()` helper (or a plain\n`{ text, hyperlink, tooltip? }` object). A `#`-prefixed target is treated as an\ninternal reference (`#Sheet2!A1`).\n\n```ts\nimport { writeXlsx, link } from \"hucre/xlsx\"\n\nawait writeXlsx({\n  sheets: [\n    {\n      name: \"Summary\",\n      columns: [\n        { header: \"Link\", key: \"link\" },\n        { header: \"ID\", key: \"id\" },\n      ],\n      data: [\n        { link: link(\"Open\", \"https://example.com/items/abc-123\"), id: \"abc-123\" },\n        { link: { text: \"Open\", hyperlink: \"https://example.com/items/def-456\" }, id: \"def-456\" },\n      ],\n    },\n  ],\n})\n```\n\n### Streaming\n\nProcess large files row-by-row without loading everything into memory:\n\n```ts\nimport {\n  streamXlsxRows,\n  writeXlsxStream,\n  writeXlsxStreamSheets,\n  XlsxStreamWriter,\n} from \"hucre/xlsx\"\n\n// Stream read — async generator yields rows one at a time\nfor await (const row of streamXlsxRows(buffer)) {\n  console.log(row.index, row.values)\n}\n\n// True streaming from a ReadableStream — the ZIP is parsed front-to-back\n// from local file headers, so the whole archive is never buffered. Only\n// the small metadata parts (content types, rels, workbook, shared strings,\n// styles) are read up front; the target worksheet is piped straight into\n// the SAX parser. (Falls back to buffering only for archives whose layout\n// rules out single-pass streaming — e.g. ZIP data descriptors, or shared\n// strings stored after the worksheet.)\nconst res = await fetch(\"https://example.com/huge.xlsx\")\nfor await (const row of streamXlsxRows(res.body!)) {\n  console.log(row.index, row.values)\n}\n\n// Cap the number of rows yielded (preview / sampling). The underlying\n// ZIP/SAX stream is cancelled once the cap is reached, so very large\n// sheets stay cheap.\nfor await (const row of streamXlsxRows(buffer, { maxRows: 100 })) {\n  console.log(row.index, row.values)\n}\n\n// Filter to an A1 range. Rows outside the row span are skipped; cells\n// outside the column span are masked to `null` (column indexes stay\n// stable). Parsing stops once a row past the end-row is observed.\nfor await (const row of streamXlsxRows(buffer, { range: \"B2:D1000\" })) {\n  // row.values[0] === null (column A is outside)\n  // row.values[1..3] carry B/C/D\n}\n\n// Stream write — the workbook is emitted as bytes while rows are pulled\n// from the source, so peak memory doesn't grow with the row count.\nfunction* rows() {\n  for (let i = 0; i < 5_000_000; i++) yield [i + 1, Math.random()]\n}\n\nreturn new Response(\n  writeXlsxStream(\n    rows(), // any Iterable or AsyncIterable — a DB cursor, another stream, …\n    {\n      name: \"BigData\",\n      columns: [{ header: \"ID\" }, { header: \"Value\" }],\n      freezePane: { rows: 1 },\n    },\n  ),\n  {\n    headers: {\n      \"content-type\": \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\",\n    },\n  },\n)\n\n// A streamed row can carry formatting per cell, not just per column —\n// enough for a title, a subtotal line or a header band. A cell that brings\n// its own style overrides its column's; a bare value still inherits it.\n// Row heights and merges travel alongside, so a report header no longer\n// forces a fall back to writeXlsx.\nwriteXlsxStream(\n  [\n    [{ value: \"Q3 Report\", style: { font: { size: 20, bold: true } } }, null, null],\n    [{ formula: \"SUM(A3:A99)\" }, \"Total\"],\n    ...dataRows,\n  ],\n  {\n    name: \"Report\",\n    rowDefs: new Map([[0, { height: 30 }]]),\n    merges: [\"A1:C1\"], // or [{ startRow: 0, startCol: 0, endRow: 0, endCol: 2 }]\n  },\n)\n\n// Stream write, several sheets — same guarantee across a workbook that\n// holds more than one sheet. Each sheet brings its own name, columns and\n// rows (and its own styles, heights and merges); the row sources are\n// pulled one sheet at a time, in order.\nreturn new Response(\n  writeXlsxStreamSheets([\n    { name: \"Accepted\", rows: accepted(), columns },\n    { name: \"Rejected\", rows: rejected(), columns },\n  ]),\n  {\n    headers: {\n      \"content-type\": \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\",\n    },\n  },\n)\n\n// Incremental write — serializes each row on arrival, but holds the\n// serialized parts until finish() returns one buffer. Use it when you\n// need a Uint8Array anyway; use writeXlsxStream for constant memory.\nconst writer = new XlsxStreamWriter({\n  name: \"BigData\",\n  columns: [{ header: \"ID\" }, { header: \"Value\" }],\n  freezePane: { rows: 1 },\n})\nfor (let i = 0; i < 100_000; i++) {\n  writer.addRow([i + 1, Math.random()])\n}\nconst buffer = await writer.finish()\n```\n\n#### Across the formats\n\n```ts\nimport { writeOdsStream } from \"hucre/ods\"\nimport { writeNdjsonStream } from \"hucre/json\"\nimport { streamXmlRows, writeXmlStream } from \"hucre/xml\"\n\nreturn new Response(writeOdsStream(rowCursor, { name: \"Export\" }), {\n  headers: { \"content-type\": \"application/vnd.oasis.opendocument.spreadsheet\" },\n})\n\nreturn new Response(writeNdjsonStream(rowCursor), {\n  headers: { \"content-type\": \"application/x-ndjson\" },\n})\n\nreturn new Response(writeXmlStream(rowCursor, { rowTag: \"record\" }), {\n  headers: { \"content-type\": \"application/xml; charset=utf-8\" },\n})\n\n// Reading one back, a row at a time.\nfor await (const row of streamXmlRows(bytes, { rowTag: \"record\" })) {\n  console.log(row.index, row.values)\n}\n```\n\n|        | whole read | whole write | stream read | stream write | incremental writer |\n| ------ | :--------: | :---------: | :---------: | :----------: | :----------------: |\n| XLSX   |     ✔      |      ✔      |      ✔      |  ✔ (multi)   |         ✔          |\n| CSV    |     ✔      |      ✔      |      ✔      |      ✔       |         ✔          |\n| NDJSON |     ✔      |      ✔      |      ✔      |      ✔       |         ✔          |\n| ODS    |     ✔      |      ✔      |      ✔      |      ✔       |         ✔          |\n| XML    |     ✔      |      ✔      |      ✔      |      ✔       |         —          |\n\n`writeOdsStream` carries **values, not formatting**, and that follows from\nthe format: ODF puts `<office:automatic-styles>` before the body, so a\nstyle first seen on row 900,000 has nowhere to be declared — the same\nshape as the shared-string table, which the XLSX streaming writer answers\nwith inline strings and ODF has no equivalent for. Column widths and a\nheader row are carried, because `columns` is known before the first row.\n`writeOds` remains the path for a document that needs styling.\n\n`streamXmlRows` differs from `readXml` in one way that follows from\nstreaming rather than from a choice: **it yields the keys each row\nactually has.** `readXml` pads every row to the union of all headers,\nwhich needs the whole document — a streaming reader cannot know a key\nthat appears only in a later row. For the same reason it takes `rowTag`\nfrom the first child of the root rather than from the most frequent one.\n\nODS now has both writers, and the rule for choosing is the same one XLSX\nalready has: `writeOdsStream` for constant memory and values only,\n`OdsStreamWriter` when you want the styles and can afford to buffer.\n\nThat difference is the format's, not a gap: ODF puts\n`<office:automatic-styles>` **before** the body, so a style first seen on\nrow 900,000 has nowhere to be declared once the body has gone out. A\nbuffering writer holds the rows until `finish()` and writes the style\nblock from everything it saw, which is exactly what buys it per-cell\nstyles and column widths.\n\n#### Which writer to use\n\n|             | `writeXlsxStream()`                          | `XlsxStreamWriter`                |\n| ----------- | -------------------------------------------- | --------------------------------- |\n| Output      | `ReadableStream<Uint8Array>`                 | `Promise<Uint8Array>`             |\n| Rows        | pulled from an (async) iterable              | pushed via `addRow` / `addObject` |\n| Peak memory | O(distinct styles) — flat                    | O(data)                           |\n| Strings     | inline by default                            | shared string table               |\n| Sheets      | one, or several with `writeXlsxStreamSheets` | one, plus auto-split parts        |\n\nMeasured with `pnpm bench` — the scenarios are in `bench/`, so these are\nreproducible rather than quoted. 5 columns of mixed text/number/date data,\nwriting to a sink that discards the bytes (Node 24):\n\n|      Rows | `writeXlsxStream` peak heap | `XlsxStreamWriter` peak heap |\n| --------: | --------------------------: | ---------------------------: |\n|   300,000 |                       41 MB |                       328 MB |\n| 1,000,000 |                       67 MB |                     1,037 MB |\n| 3,000,000 |                       70 MB |                   not viable |\n\n#### What the streaming _reader_ costs\n\nThe write table above is the flattering half. `streamXlsxRows` is\nconstant-memory in **rows** and linear in **distinct strings**, because\n`xl/sharedStrings.xml` is a workbook-wide part that has to be read up\nfront. Two fixtures of the same shape and size, 100,000 rows × 12\ncolumns, differing only in how many distinct strings they contain:\n\n|                              | 400,000 distinct strings | 10 distinct strings |\n| ---------------------------- | -----------------------: | ------------------: |\n| `readXlsx`                   |        2,721 ms / 662 MB |   1,896 ms / 392 MB |\n| `readXlsx` + `maxRows: 1000` |        1,953 ms / 656 MB |   1,150 ms / 221 MB |\n| `streamXlsxRows`             |        2,446 ms / 556 MB |    1,592 ms / 90 MB |\n\nTwo things worth knowing before you rely on either:\n\n- An export of names, SKUs or free text is the high-cardinality case, and\n  there the streaming reader is not much better than the buffering one.\n- **`maxRows` bounds the output, not the work.** Asking for 1,000 rows of\n  100,000 saved 28% of the time and 1% of the memory on the\n  high-cardinality fixture.\n\nRun `pnpm bench` to see both on your own machine.\n\n#### One surface across the incremental writers\n\n`XlsxStreamWriter`, `CsvStreamWriter`, `NdjsonStreamWriter` and\n`OdsStreamWriter` all implement `SpreadsheetStreamWriter`, so a\nformat-agnostic export helper can be written once:\n\n| Method            | Behaviour                                                         |\n| ----------------- | ----------------------------------------------------------------- |\n| `addRow(values)`  | Append positional values                                          |\n| `addObject(item)` | Append an object, projected through the writer's column order     |\n| `finish()`        | Close the writer and return its output (`string` or `Uint8Array`) |\n| `toStream()`      | Output as a `ReadableStream<Uint8Array>`                          |\n\n```ts\nimport type { SpreadsheetStreamWriter } from \"hucre\"\n\nasync function exportAll(writer: SpreadsheetStreamWriter, rows: Array<Record<string, CellValue>>) {\n  for (const row of rows) writer.addObject(row)\n  return await writer.finish() // string | Uint8Array — narrow at the call site\n}\n```\n\nThis was a convention until v1.0.1 and nothing enforced it: there was no\nshared interface, so when `XlsxStreamWriter.addRow` widened to accept\nstyled cells, nothing failed and the drift was left for a reader to\nfind. The `implements` is what makes the next divergence a compile error\n(#468).\n\nFour caveats worth stating plainly:\n\n- **`finish()` is not one type.** The text writers return `string`, XLSX\n  returns `Promise<Uint8Array>`, and the interface says so rather than\n  pretending otherwise. `await` covers both; narrow before use.\n- **Construction is not shared.** `XlsxStreamWriter` takes a sheet `name`\n  and `ColumnDef[]`; the two text writers take a plain key list. The\n  helper is written once — building the writer is still per-format.\n- `toStream()` on `XlsxStreamWriter` and `CsvStreamWriter` **does not bound\n  memory**. Both buffer everything until `finish()`; the stream just hands\n  you the finished bytes. `writeXlsxStream` / `writeCsvStream` are the\n  constant-memory paths. `NdjsonStreamWriter.toStream()` is the only live\n  drain — it releases rows as they are enqueued and stays open until\n  `finish()`.\n- `NdjsonStreamWriter.addRow` needs `columns` (`new NdjsonStreamWriter({\ncolumns: [...] })`), because NDJSON rows are objects and positional\n  values have no key names otherwise. It throws rather than guessing.\n  Its old `write()` / `end()` are kept as `@deprecated` aliases.\n\nStreaming trade-offs worth knowing:\n\n- Strings are written inline (`t=\"inlineStr\"`) by default. A shared\n  string table has to live in memory until the workbook closes, which\n  would undo the constant-memory guarantee — pass `inlineStrings: false`\n  when the data is repetitive and file size matters more.\n- Part sizes aren't known when their ZIP headers go out, so entries carry\n  trailing ZIP data descriptors and `[Content_Types].xml` is written last.\n  That's the same layout `archiver`/`zip-stream` emit, which is what\n  ExcelJS's own streaming writer produces. Verified against `hucre`'s\n  reader, ExcelJS, and `unzip`.\n- ZIP64 records are off by default, which caps any single part — and the\n  whole archive — at 4 GiB. Crossing that throws rather than writing a\n  broken file. Pass `zip64: true` to lift it (see below).\n- Compression uses the platform `CompressionStream`. Without it, parts are\n  stored uncompressed rather than buffered.\n\n#### ZIP64 — past 4 GiB\n\nReading ZIP64 archives needs no configuration: `readXlsx`, `openXlsx`, and\n`streamXlsxRows` resolve ZIP64 EOCD records and per-entry extra fields\ntransparently, whether the producer escaped every field or only the ones\nthat actually overflowed.\n\nWriting is opt-in, because part sizes aren't known when their headers go\nout — it can't be decided per entry mid-stream:\n\n```ts\nconst stream = writeXlsxStream(rows, { name: \"Huge\", columns, zip64: true })\n```\n\nTurn it on when one worksheet's XML may cross 4 GiB — very wide sheets\nnear the per-sheet row cap can. ZIP64 archives need a ZIP64-aware\nconsumer; the output here is verified against Info-ZIP's `unzip`,\nExcelJS, and `hucre`'s own reader.\n\n#### Auto-split past Excel's row limit\n\nPass `maxRowsPerSheet` to spill into `{name}_2`, `{name}_3`, … when the\ndata crosses Excel's 1,048,576-row hard limit (default). The captured\nheader row is repeated on every rolled sheet.\n\nBoth writers do this.\n\n```ts\nimport { XlsxStreamWriter, XLSX_MAX_ROWS_PER_SHEET } from \"hucre/xlsx\"\n\nconst writer = new XlsxStreamWriter({\n  name: \"BigData\",\n  columns: [\n    { key: \"id\", header: \"ID\" },\n    { key: \"v\", header: \"Value\" },\n  ],\n  maxRowsPerSheet: 1_000_000, // optional override; default = 1_048_576\n  repeatHeaders: true, // default\n})\n\nfor (let i = 0; i < 3_000_000; i++) writer.addRow([i + 1, Math.random()])\n// → BigData, BigData_2, BigData_3\nconst buf = await writer.finish()\n```\n\n### Password Protection\n\nRead and write password-protected XLSX workbooks (ECMA-376 **Agile**\nencryption — the Excel 2010+ scheme). Encryption is built on the\nplatform's WebCrypto, so it stays zero-dependency and runs in Node, Deno,\nBun, Cloudflare Workers, and browsers.\n\n```ts\nimport { writeXlsx, readXlsx, readObjects, EncryptedFileError, DecryptionError } from \"hucre\"\n\n// Write an encrypted workbook\nconst encrypted = await writeXlsx({\n  sheets: [{ name: \"Secret\", rows: [[\"pin\", 1234]] }],\n  encryption: { password: \"hunter2\" },\n})\n\n// Read it back with the password\nconst wb = await readXlsx(encrypted, { password: \"hunter2\" })\n\n// Works through every read entry point: read(), readObjects(), streamXlsxRows()\nconst { data: rows } = await readObjects(encrypted, { password: \"hunter2\" })\n\n// Without a password an encrypted file throws EncryptedFileError;\n// with the wrong password it throws DecryptionError.\ntry {\n  await readXlsx(encrypted)\n} catch (e) {\n  if (e instanceof EncryptedFileError) console.log(\"needs a password\")\n}\n```\n\nThe roundtrip path takes the same option: `await saveXlsx(wb, { encryption: { password } })`.\nThe key-derivation iteration count defaults to Excel's 100000; lower it via\n`encryption: { password, spinCount }` when the speed/security trade-off calls for it.\n\n### XLSB (Binary Excel) — read\n\nRead `.xlsb` (Excel Binary Workbook) files — the binary package format\nthat's smaller and faster to open than `.xlsx`. `read()` auto-detects it,\nor call `readXlsb` directly:\n\n```ts\nimport { read, readXlsb } from \"hucre\"\n\nconst wb = await readXlsb(bytes) // sheet names + typed cell values\nconst same = await read(bytes) // auto-detected (XLSX vs XLSB vs ODS)\n```\n\nDecodes shared strings, RK / floating-point numbers, inline strings,\nbooleans, error codes, cached formula values, merged cells, and dates (via\nthe binary style table, honouring the workbook's own 1900/1904 date\nsystem). Read-only; password-protected `.xlsb` also decrypts with\n`{ password }`.\n\n### XLS (Legacy Excel 97-2003) — read\n\nRead legacy `.xls` (BIFF8) files — the OLE2/CFB binary format from Excel\n97-2003. `read()` auto-detects it, or call `readXls`:\n\n```ts\nimport { read, readXls } from \"hucre\"\n\nconst wb = await readXls(bytes)\nconst same = await read(bytes) // auto-detected\n```\n\nDecodes the shared-string table (with CONTINUE spanning), RK / MULRK /\nnumber / boolean / error cells, labels, cached formula values, dates, and\nmerged cells. Read-only.\n\nBoth legacy readers surface sheet names, cell values, and merges — and\nnothing else. Formulas arrive as their **cached value**, never as formula\ntext; styles, column widths, row heights, sheet visibility, named ranges\nand workbook properties are not read. Converting `.xls` / `.xlsb` to\n`.xlsx` through hucre is therefore a values-and-sheet-names conversion,\nnot a faithful copy.\n\n### ODS (OpenDocument)\n\n```ts\nimport { readOds, writeOds } from \"hucre/ods\"\n\nconst wb = await readOds(buffer)\nconst ods = await writeOds({ sheets: [{ name: \"Sheet1\", rows: [[\"Hello\", 42]] }] })\n```\n\n#### What ODS carries\n\nThe ODS reader and writer model the same narrow set, so **ODS → ODS is\nlossless**. The loss shows up when converting _into_ ODS from a format\nthat models more, which in practice means XLSX.\n\n|                                                            | ODS                                                                                         |\n| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------- |\n| Cell values — string, number, boolean, date                | yes                                                                                         |\n| Formulas, including the `of:=` translation                 | yes                                                                                         |\n| Merged cells                                               | yes                                                                                         |\n| Cell styles                                                | **six facets only**: bold, italic, font size, font colour, background colour, number format |\n| Document properties                                        | six fields: title, subject, creator, description, keywords, created                         |\n| Borders, alignment, font name, underline, strikethrough    | no                                                                                          |\n| Column widths, row heights, hidden rows and columns        | no                                                                                          |\n| Freeze and split panes, sheet views, tab colour            | no                                                                                          |\n| Data validation, conditional formatting, auto-filter       | no                                                                                          |\n| Named ranges, tables, images, page setup, sheet protection | no                                                                                          |\n| Hidden sheets                                              | no                                                                                          |\n\nSo `readXlsx` → `writeOds` keeps values, formulas, merges and those six\nstyle facets, and drops the rest **silently** — there is no warning,\nbecause from ODS's side nothing was lost.\n\nTwo consequences worth knowing before you rely on it:\n\n- Two cell styles that differ _only_ in an unsupported property (say two\n  different borders) are treated as the same style and share one\n  definition.\n- A cell styled entirely with unsupported properties gets no style\n  reference at all, rather than an empty one.\n\nThe reader reads `content.xml` and `meta.xml`. It does not open\n`styles.xml` or `settings.xml`, which is where LibreOffice puts named and\ndefault cell styles and all page setup — so a LibreOffice-authored file\nreads back with its direct formatting only.\n\nWidening the ODS model is open work; this table is the current contract,\nnot a permanent one.\n\n### Read, edit, write\n\n`readXlsx` returns a `Workbook`; `writeXlsx` takes `WriteOptions`. They\nare different types — `Chart` is not `SheetChart`, `PivotTable` is not\n`WritePivotTable` — so `writeXlsx({ sheets: wb.sheets })` does not\ntypecheck. `toWriteOptions` converts:\n\n```ts\nimport { readXlsx, toWriteOptions, writeXlsx } from \"hucre\"\n\nconst wb = await readXlsx(buffer)\nwb.sheets[0].rows[0][0] = \"edited\"\n\nconst out = await writeXlsx(toWriteOptions(wb))\n```\n\nThis is the **authoring** path, so it carries only what `WriteSheet` and\n`WriteOptions` describe. Pass `onDrop` to see what it could not:\n\n```ts\ntoWriteOptions(wb, {\n  onDrop: ({ field, sheet, reason }) => console.warn(`dropped ${field}`, sheet, reason),\n})\n// dropped charts  Sheet1  the read model (`Chart`) and the write model …\n```\n\nEditing someone else's file rather than producing a new one? Use\n`openXlsx` / `saveXlsx` below instead — that path preserves the parts\nhucre does not regenerate, so nothing is dropped.\n\n### Round-trip Preservation\n\nOpen, modify, save — without losing charts, macros, or features hucre doesn't natively handle:\n\n```ts\nimport { openXlsx, saveXlsx } from \"hucre/xlsx\"\n\nconst workbook = await openXlsx(buffer)\nworkbook.sheets[0].rows[0][0] = \"Updated!\"\nconst output = await saveXlsx(workbook) // Charts, VBA, themes preserved\n```\n\nPreservation is a property of **this** path. `openXlsx`/`saveXlsx` copies\nevery part it does not regenerate byte-for-byte, so anything hucre does\nnot model survives. `readXlsx`/`writeXlsx` is the authoring path: it\nrebuilds the workbook from the model, and only what `WriteSheet` and\n`WriteOptions` describe comes out the other side. Reading an `.xlsm` with\n`readXlsx` and writing it back leaves no `xl/vbaProject.bin` — use\n`openXlsx`/`saveXlsx` to edit a macro-enabled workbook.\n\nTo attach a macro project to a workbook you are authoring, pass the\nbinary to `writeXlsx` — the output becomes macro-enabled:\n\n```ts\nawait writeXlsx({\n  sheets: [{ name: \"Sheet1\", rows }],\n  vbaProject: await readFile(\"vbaProject.bin\"), // output is .xlsm\n})\n```\n\n### External Workbook References\n\n`[N]Sheet!Ref` references to other workbooks are read into a typed\n`workbook.externalLinks` model and re-declared on roundtrip — without\nthis the `<externalReferences>` block and the matching relationship\ndisappear from `xl/workbook.xml.rels`, leaving Excel with orphan\n`externalLinkN.xml` parts that it ignores.\n\n```ts\nimport { readXlsx, parseExternalLink } from \"hucre\"\n\nconst wb = await readXlsx(buf)\nfor (const link of wb.externalLinks ?? []) {\n  console.log(link.target, link.targetMode, link.sheetNames)\n  for (const sheet of link.sheetData) {\n    for (const cell of sheet.cells) {\n      // cell.type ∈ \"n\" | \"s\" | \"b\" | \"e\" | \"str\"\n      console.log(cell.ref, cell.type, cell.value)\n    }\n  }\n}\n\n// Standalone parser when you already have the XML strings\nconst link = parseExternalLink(externalLinkXml, externalLinkRelsXml)\n```\n\nThe 1-based index in `workbook.externalLinks` matches the `[N]` prefix\nused by formulas like `[1]Sheet1!A1`. Cached `t=\"s\"` values stay as\nshared-string indices into the _external_ workbook (which hucre cannot\ndereference); resolved strings live in the linked file.\n\n### Cell-Embedded Images (WPS DISPIMG)\n\nWPS Office (and recent Excel versions) embed images inside cells via a\nworkbook-level `xl/cellimages.xml` registry referenced from\n`=_xlfn.DISPIMG(\"<id>\", 1)` formulas. hucre reads the registry into a\ntyped `workbook.cellImages` array and re-declares the part on\n`saveXlsx` so the DISPIMG link survives round-trips — without this the\nrelationship and content-type override are dropped and the formula\nloses its target.\n\n```ts\nimport { readXlsx } from \"hucre\"\n\nconst wb = await readXlsx(buf)\nfor (const img of wb.cellImages ?? []) {\n  console.log(img.id, img.type, img.description, img.data.byteLength)\n}\n\n// Standalone parsers when you already have the XML strings.\nimport { parseCellImages, assembleCellImages, REL_CELL_IMAGES } from \"hucre\"\nconst refs = parseCellImages(cellImagesXml)\nconst images = assembleCellImages(refs, mediaMap)\n```\n\nSynthesizing a `cellimages.xml` from a model on a fresh `writeXlsx`\ncall (without an existing source file) is a follow-up — for now the\nread + roundtrip-preserve side is in place.\n\n### Slicers & Timeline Filters\n\nSlicers (Excel 2010+) and timeline slicers (Excel 2013+) are read into\ntyped `workbook.slicerCaches` / `workbook.timelineCaches` plus per-sheet\n`sheet.slicers` / `sheet.timelines` arrays. On `saveXlsx` the slicer /\ntimeline parts are re-declared in `[Content_Types].xml`, the workbook\nrels, the workbook `extLst`, and each sheet's rels — without this\nroundtrip Excel saw the cache parts as orphans and dropped the\nslicers / timelines on next open.\n\n```ts\nimport { readXlsx } from \"hucre\"\n\nconst wb = await readXlsx(buf)\n\n// Workbook-level cache definitions.\nconsole.log(wb.slicerCaches) // SlicerCache[] (pivot-table or table source)\nconsole.log(wb.timelineCaches) // TimelineCache[]\n\n// Per-sheet slicer / timeline instances.\nfor (const sheet of wb.sheets) {\n  for (const s of sheet.slicers ?? []) console.log(s.name, s.cache, s.caption)\n  for (const t of sheet.timelines ?? []) console.log(t.name, t.cache, t.level)\n}\n\n// Standalone parsers when you already have the XML strings.\nimport { parseSlicers, parseSlicerCache, parseTimelines, parseTimelineCache } from \"hucre\"\n```\n\nThe worksheet body's `<x14:slicerList>` / `<x15:timelines>` extension\nblocks are not yet re-injected when the worksheet XML is regenerated —\nExcel still sees the parts as wired up via rels and content-types so\nthey survive the roundtrip, but synthesizing slicers from a fresh\nwrite is a follow-up.\n\n### Pivot Tables\n\nPivot tables (`xl/pivotTables/pivotTableN.xml`) and their workbook-level\ncache definitions (`xl/pivotCache/pivotCacheDefinitionN.xml` plus the\ncompanion `pivotCacheRecordsN.xml`) are read into typed\n`workbook.pivotCaches` and per-sheet `sheet.pivotTables` arrays. On\n`saveXlsx` the pivot parts are re-declared in `[Content_Types].xml`,\nthe workbook rels, the workbook `<pivotCaches>` block, and each host\nsheet's rels — Excel previously saw the pivot parts as orphans and\ndropped the tables on next open.\n\n```ts\nimport { readXlsx } from \"hucre\"\n\nconst wb = await readXlsx(buf)\n\n// Workbook-level cache definitions.\nfor (const cache of wb.pivotCaches ?? []) {\n  console.log(cache.cacheId, cache.sourceSheet, cache.sourceRef, cache.fieldNames)\n}\n\n// Per-sheet pivot table instances.\nfor (const sheet of wb.sheets) {\n  for (const pt of sheet.pivotTables ?? []) {\n    console.log(pt.name, pt.location, pt.cacheId)\n    for (const f of pt.fields) {\n      console.log(\"  \", f.name, f.axis, f.function)\n    }\n  }\n}\n\n// Standalone parsers when you already have the XML strings.\nimport { parsePivotTable, parsePivotCacheDefinition, attachPivotCacheFields } from \"hucre\"\n```\n\n`PivotTable.cacheId` matches the workbook-level `cacheId` rather than a\nper-table relationship, so reordering `Workbook.pivotCaches` keeps the\nlinks sound.\n\n`writeXlsx` can also author pivot tables from scratch via the per-sheet\n`pivotTables` field. Hucre emits the pivot cache (definition + cached\nrecords), the pivot layout, and every required relationship and content\ntype. The numeric layout (row totals, grand totals, value cells) is left\nfor Excel to compute on first open via the existing `fullCalcOnLoad`\nrecompute — Phase 1 ships the structural skeleton, not pre-computed\nvalue cells.\n\n```ts\nimport { writeXlsx } from \"hucre\"\n\nconst xlsx = await writeXlsx({\n  sheets: [\n    {\n      name: \"Data\",\n      rows: [\n        [\"Region\", \"Product\", \"Revenue\"],\n        [\"EU\", \"A\", 100],\n        [\"EU\", \"B\", 50],\n        [\"US\", \"A\", 200],\n        [\"US\", \"B\", 75],\n      ],\n    },\n    {\n      name: \"Pivot\",\n      pivotTables: [\n        {\n          name: \"SalesPivot\",\n          sourceSheet: \"Data\",\n          rows: [\"Region\"],\n          columns: [\"Product\"],\n          values: [{ field: \"Revenue\", function: \"sum\" }],\n        },\n      ],\n    },\n  ],\n})\n```\n\nSupported aggregation functions: `sum` (default), `count`, `average`,\n`max`, `min`, `product`, `countNums`, `stdDev`, `stdDevp`, `var`,\n`varp`. Pivots can source from their own sheet (omit `sourceSheet`)\nor any sibling sheet, and accept either `rows` (raw 2-D arrays) or\n`columns` + `data` (object-style) source shapes.\n\n### Charts\n\nCharts (`xl/charts/chartN.xml` plus the optional `styleN.xml` /\n`colorsN.xml` companions) round-trip through three layers:\n\n- `parseChart(xml)` / `readXlsx().sheets[].charts[]` — surfaces a\n  read-side `Chart` record (kinds, title, series, axes, legend, data\n  labels, fills / borders, manual layout, view3D, etc.).\n- `writeXlsx({ sheets: [{ charts: [SheetChart] }] })` — emits the\n  chart parts and re-anchors the drawing in the regenerated worksheet\n  body so Excel never sees the chart as an orphan.\n- `cloneChart(source, options)` — bridges the two: it converts a\n  parsed `Chart` into a writable `SheetChart`, applies a typed override\n  bag (`undefined` = inherit, `null` = drop, value = replace), and\n  returns the result ready for `writeXlsx`.\n\n`getCharts(workbook)` flattens every chart anchored on the workbook's\nsheets into a single array; `addChart(sheet, chart)` is the symmetric\nwriter-side helper that appends a `SheetChart` to a `WriteSheet`.\n\n#### Capability matrix\n\nThe table summarises which knobs flow through each layer. \"Read\"\nmeans `parseChart` surfaces the field on `Chart` (or\n`ChartAxisInfo` / `ChartDataLabelsInfo` / `ChartDataTable` for nested\nslots); \"Write\" means `writeXlsx` emits it from the matching\n`SheetChart` field; \"Clone\" means `cloneChart` carries it through\nwith the standard `undefined` / `null` / value override grammar. `—`\nmeans the layer does not cover it.\n\nThe write side is narrower than the read side on chart kinds:\n`WriteChartKind` is `bar | column | line | pie | doughnut | scatter |\narea`, and `SheetChart.type` holds a single kind — so combo charts\n(several chart-type elements in one plot area) parse fine but cannot be\nauthored. `writeXlsx` throws on any other kind; `cloneChart` throws on a\nbubble / radar / surface / stock source unless `options.type` coerces it\nto a writable kind.\n\n| Family                                                                                                                       | Read | Write | Clone |\n| ---------------------------------------------------------------------------------------------------------------------------- | :--: | :---: | :---: |\n| Chart kinds — bar / column / line / pie / doughnut / area / scatter                                                          |  x   |   x   |   x   |\n| Chart kinds — bubble / radar / surface / stock / 3-D variants, and combo (multi-kind plot areas)                             |  x   |   —   |   —   |\n| Title text + visibility (`title`, `showTitle`, `autoTitleDeleted`)                                                           |  x   |   x   |   x   |\n| Title typography (size / bold / italic / strike / underline / color / family)                                                |  x   |   x   |   x   |\n| Title rotation, manual layout                                                                                                |  x   |   x   |   x   |\n| Title fill / border color / border width / border dash                                                                       |  x   |   x   |   x   |\n| Legend position, overlay, font knobs, manual layout                                                                          |  x   |   x   |   x   |\n| Legend fill / border color / border width / border dash, per-entry hide                                                      |  x   |   x   |   x   |\n| Plot-area manual layout, fill / border color / border width / border dash                                                    |  x   |   x   |   x   |\n| Chart-space fill / border color / border width / border dash, rounded corners, style preset                                  |  x   |   x   |   x   |\n| Axis title text + typography (per-axis), rotation, manual layout, fill / border knobs                                        |  x   |   x   |   x   |\n| Axis label rotation, font size, bold / italic / underline / strike, color, font family                                       |  x   |   x   |   x   |\n| Axis scale (min / max / logBase / majorUnit / minorUnit), reverse, hidden, crosses, dispUnits                                |  x   |   x   |   x   |\n| Axis number format, tick marks, tick-label position / skip, label offset / alignment                                         |  x   |   x   |   x   |\n| Axis gridlines (major / minor)                                                                                               |  x   |   x   |   x   |\n| Series name, value/category refs, fill color, line stroke (width / dash)                                                     |  x   |   x   |   x   |\n| Series markers (symbol / size / fill / outline)                                                                              |  x   |   x   |   x   |\n| Data labels — chart-level + per-series (show\\*, position, separator, number format, leader lines, typography, fill / border) |  x   |   x   |   x   |\n| Data table (showHorzBorder / showVertBorder / showOutline / showKeys, typography, fill / border)                             |  x   |   x   |   x   |\n| Bar / column gap-width and overlap                                                                                           |  x   |   x   |   x   |\n| Pie / doughnut hole size, vary-colors, first-slice angle                                                                     |  x   |   x   |   x   |\n| Display blanks-as, plot-vis-only, show-data-labels-over-max, lang, date1904                                                  |  x   |   x   |   x   |\n| 3D walls / floor / view3D                                                                                                    |  x   |   x   |   x   |\n| Chart-space protection block                                                                                                 |  x   |   x   |   x   |\n| Anchor (twoCellAnchor / oneCellAnchor) + relative offsets                                                                    |  x   |   x   |   x   |\n\n#### Read side — `parseChart` / `getCharts`\n\n```ts\nimport { getCharts, openXlsx, parseChart } from \"hucre\"\n\nconst wb = await openXlsx(buf)\n\nfor (const { sheetName, chart } of getCharts(wb)) {\n  console.log(sheetName, chart.kinds, chart.title)\n  // e.g. \"Sales\" [\"bar\"] \"Quarterly Sales\"\n  console.log(chart.anchor) // { from: { row: 1, col: 3 }, to: { row: 16, col: 10 } }\n  console.log(chart.axes?.x?.title, chart.axes?.y?.scale)\n\n  for (const s of chart.series ?? []) {\n    console.log(s.kind, s.name, s.valuesRef, s.color, s.dataLabels)\n  }\n}\n\n// Standalone parser when you already have the chart XML.\nconst chart = parseChart(xml)\n```\n\n`Chart.kinds` lists every chart-type element under `<c:plotArea>` in\ndeclaration order, so combo charts surface as e.g. `[\"bar\", \"line\"]`.\n`Chart.series` mirrors the field shape that `ChartSeries` accepts on\nthe write side — a parsed series can be fed straight back into\n`SheetChart.series`. Bubble/scatter `<c:numLit>` series (literal\nembedded data, no formula) intentionally surface no\n`valuesRef` / `categoriesRef`. `Chart.anchor` mirrors\n`SheetChart.anchor`: `twoCellAnchor` charts surface both `from` and\n`to`, `oneCellAnchor` charts surface `from` only,\n`absoluteAnchor` charts (EMU-positioned, no cell anchor) report\n`anchor` as `undefined`.\n\n#### Write side — `writeXlsx` + `addChart`\n\n```ts\nimport { addChart, writeXlsx } from \"hucre\"\n\nconst dashboard = {\n  name: \"Dashboard\",\n  rows: [\n    [\"Quarter\", \"Revenue\", \"Forecast\"],\n    [\"Q1\", 12000, 11500],\n    [\"Q2\", 15500, 15000],\n    [\"Q3\", 14000, 14500],\n    [\"Q4\", 17800, 17200],\n  ],\n}\n\naddChart(dashboard, {\n  type: \"column\",\n  title: \"Quarterly Revenue\",\n  titleFontSize: 14,\n  titleBold: true,\n  titleColor: \"1F77B4\",\n  titleBorderColor: \"1F77B4\",\n  titleBorderWidth: 1.5,\n  titleBorderDash: \"dash\",\n  series: [\n    { name: \"Revenue\", values: \"B2:B5\", categories: \"A2:A5\", color: \"1F77B4\" },\n    { name: \"Forecast\", values: \"C2:C5\", categories: \"A2:A5\", color: \"FF7F0E\" },\n  ],\n  axes: {\n    x: { title: \"Quarter\" },\n    y: { title: \"Revenue (USD)\", numberFormat: { formatCode: \"$#,##0\" } },\n  },\n  legend: \"bottom\",\n  legendFillColor: \"F2F2F2\",\n  legendBorderDash: \"dot\",\n  plotAreaBorderColor: \"DDDDDD\",\n  plotAreaBorderWidth: 0.75,\n  dataLabels: { showValue: true, position: \"outEnd\", fontSize: 9 },\n  anchor: { from: { row: 6, col: 0 }, to: { row: 22, col: 7 } },\n})\n\nconst xlsx = await writeXlsx({ sheets: [dashboard] })\n```\n\nEvery knob the writer accepts lives on the `SheetChart` type — see\n[`SheetChart` in `src/xlsx/chart/types.ts`](https://github.com/productdevbook/hucre/blob/main/src/xlsx/chart/types.ts) for the full\nfield list with per-field semantics, OOXML mapping, and clamp /\ndefault behavior. The capability table above lists the families\ncovered.\n\n#### Clone — `cloneChart(source, options)`\n\n`cloneChart` takes a parsed `Chart` and a `CloneChartOptions` override\nbag and returns a `SheetChart` ready for `writeXlsx`. Every override\nfield uses the same grammar:\n\n- `undefined` (or omitted) — inherit the source value.\n- `null` — drop the source value (writer falls back to the OOXML\n  default for that slot).\n- a typed value — replace the source value (after running through the\n  same clamp / normalize as the writer).\n\nPer-series overrides are supplied as a positional `series` array;\neach entry merges with the source series at the matching index.\n\n```ts\nimport { cloneChart, openXlsx, parseChart, writeXlsx } from \"hucre\"\n\nconst wb = await openXlsx(templateBytes)\nconst sourceChart = wb.sheets[0].charts?.[0]\nif (!sourceChart) throw new Error(\"template missing chart\")\n\n// Re-bind the template chart to a new data range and recolour series.\nconst cloned = cloneChart(sourceChart, {\n  anchor: { from: { row: 0, col: 0 }, to: { row: 18, col: 8 } },\n  title: \"FY 2026 Revenue by Region\",\n  titleColor: \"1F77B4\",\n  legend: \"right\",\n  series: [\n    { values: \"B2:B13\", categories: \"A2:A13\", color: \"1F77B4\" },\n    { values: \"C2:C13\", categories: \"A2:A13\", color: null /* drop template tint */ },\n  ],\n})\n\nconst out = await writeXlsx({\n  sheets: [{ name: \"Sheet1\", rows: dashboardRows, charts: [cloned] }],\n})\n```\n\nA common pattern is \"template -> override -> write\" — keep one\nchart-of-each-flavour template and use `cloneChart` to spawn many\nvariants without re-encoding the whole `<c:chartSpace>` tree by\nhand. See [`CloneChartOptions` in\n`src/xlsx/chart-clone.ts`](https://github.com/productdevbook/hucre/blob/main/src/xlsx/chart-clone.ts) for the full\noverride surface (every knob on `SheetChart` has a matching\n`undefined | null | value` override field).\n\n#### Walking and adding charts\n\n```ts\nimport { addChart, getCharts, openXlsx, writeXlsx } from \"hucre\"\n\nconst wb = await openXlsx(templateBytes)\n\n// Read side — find every chart in a template workbook.\nfor (const { sheetName, chart } of getCharts(wb)) {\n  console.log(sheetName, chart.kinds, chart.title)\n}\n\n// Write side — declarative chart attachment.\nconst dashboard = { name: \"Dashboard\", rows: dashboardRows }\naddChart(dashboard, {\n  type: \"column\",\n  title: \"Q1 Revenue\",\n  series: [{ name: \"Revenue\", values: \"B2:B13\", categories: \"A2:A13\" }],\n  anchor: { from: { row: 14, col: 0 } },\n})\nawait writeXlsx({ sheets: [dashboard] })\n```\n\nCharts also survive the **roundtrip** (`openXlsx` → modify → `saveXlsx`),\nnot just the fresh `writeXlsx` path. A chart attached to a newly added\nsheet — or carried across workbooks by `copySheetToWorkbook` — is\nserialized into proper `xl/charts/chartN.xml` parts with their drawing\nrelationships on save:\n\n```ts\nimport { copySheetToWorkbook, getCharts, openXlsx, saveXlsx } from \"hucre\"\n\nconst template = await openXlsx(templateBytes)\nconst report = await openXlsx(reportBytes)\n\n// Copy a chart-bearing sheet from the template into the report workbook…\ncopySheetToWorkbook(template.sheets[0], report, \"Dashboard\")\n\n// …and saveXlsx re-emits the chart parts (not just the cell data).\nconst out = await saveXlsx(report)\nconsole.log(getCharts(await openXlsx(out)).length) // includes the copied chart\n```\n\nThe roundtrip path serializes model charts for sheets that don't already\nown a drawing (new or copied sheets); to add a chart to a sheet that\nalready carries one, compose it through the fresh `writeXlsx` path\ninstead.\n\n### Unified API\n\nAuto-detect format and work with simple helpers:\n\n```ts\nimport { read, write, readObjects, writeObjects } from \"hucre\"\n\n// Auto-detect. Containers by their magic bytes — XLSX, XLSB, XLS, ODS —\n// and the text formats by their opening: CSV, JSON, NDJSON, XML, HTML.\nconst wb = await read(buffer)\n\n// Write any of nine. Default xlsx; always returns bytes, so nothing\n// downstream has to branch on the format.\nconst bytes = await write({ sheets, format: \"ndjson\" })\n// \"xlsx\" | \"ods\" | \"csv\" | \"tsv\" | \"json\" | \"ndjson\" | \"xml\" | \"html\" | \"markdown\"\n\n// Quick: file → objects, plus the headers they were keyed by. Same\n// { data, headers } shape — and the same options — as readXlsxObjects /\n// readOdsObjects / parseCsvObjects.\nconst { data: products, headers } = await readObjects<{ name: string; price: number }>(buffer, {\n  sheet: 0, // index or name (default: 0)\n  headerRow: 0, // 0-based (default: 0)\n  skipEmptyRows: true, // default\n  maxRows: 1000, // optional cap on the returned rows\n})\n\n// Quick: objects → XLSX\nconst xlsx = await writeObjects(products, { sheetName: \"Products\" })\n```\n\nDetection **decides rather than guesses**: every text format announces\nitself in its first non-whitespace character or two, which is a far\nweaker claim than the delimiter auto-detection `parseCsv` already makes.\nCSV is the fallback, because \"text that is not any of the others\" is what\nCSV is. Bytes that are not text at all still get the same\n`UnsupportedFormatError` as before — a NUL in the first few KB is where\nthat line is drawn.\n\nThe text formats are single-sheet by nature: `write()` takes the first\nsheet for those, and carries values rather than formatting. The\nrecord-shaped ones (`json`, `ndjson`, `xml`) read row 0 as field names,\nwhich is the convention `read()` inverts on the way back in.\n\n### CLI\n\n```bash\nnpx hucre convert input.xlsx output.csv\nnpx hucre convert input.csv output.xlsx\nnpx hucre convert legacy.xls output.xlsx # .xls / .xlsb read as input\nnpx hucre convert data.csv report.md     # .json .ndjson .xml .html .md too\n\n# stdin and stdout, the usual `-`\ncat data.csv | npx hucre convert - out.xlsx\nnpx hucre convert in.xlsx - --to csv\ncat data.csv | npx hucre convert - - --to md\n\nnpx hucre inspect file.xlsx\nnpx hucre inspect file.xlsx --sheet 0\nnpx hucre validate data.xlsx --schema schema.json\n```\n\nInput: `.xlsx`, `.ods`, `.csv`, `.tsv`, `.json`, `.ndjson`/`.jsonl`,\n`.xml`, `.html`, `.xls`, `.xlsb`. Output: the same minus `.xls` and\n`.xlsb`, which are read-only formats — naming one as the output says so —\nplus `.md`, which is write-only, because there is no `fromMarkdown` and\nthere will not be.\n\n`-` reads stdin or writes stdout. From a pipe there is no extension to go\non, so the input format comes from the content and the output format from\n`--to`; guessing a binary default would spray a ZIP into a terminal.\nProgress messages go to stderr whenever stdout is the file.\n\n`convert` carries cell values and sheet names only; from a legacy binary\ninput that is all there is (see the notes on each format above).\n\n### Sheet Operations\n\nManipulate sheet data in memory:\n\n```ts\nimport { insertRows, deleteRows, cloneSheet, moveSheet } from \"hucre\"\n\ninsertRows(sheet, 5, 3) // Insert 3 rows at position 5\ndeleteRows(sheet, 0, 1) // Delete first row\nconst copy = cloneSheet(sheet, \"Copy\") // Deep clone\nmoveSheet(workbook, 0, 2) // Reorder sheets\n```\n\n`insertRows`, `deleteRows`, `insertColumns` and `deleteColumns` move the\nreferences too — formula text, the formulas inside data validations and\nconditional rules, sparkline ranges, page breaks and text-box anchors —\nso a formula below an insertion still points at its own arguments. A\nreference into a deleted row becomes `#REF!`; a range the deletion clips\nshrinks; a reference qualified with another sheet is left alone.\n\nTwo limits worth knowing:\n\n- **Workbook-level references are out of reach.** These functions take a\n  `Sheet`, so `Workbook.namedRanges`, defined names, external-link caches\n  and pivot caches cannot be updated from here.\n- **`sortRows` does not rewrite formulas.** Sorting has no correct answer\n  for a relative reference, which is why Excel warns about it too.\n\n### HTML & Markdown Export\n\n```ts\nimport { toHtml, toMarkdown } from \"hucre\"\n\nconst html = toHtml(workbook.sheets[0], {\n  hasHeaderRow: true,\n  styles: true,\n  classes: true,\n})\n\nconst md = toMarkdown(workbook.sheets[0])\n// | Name   | Price  | Stock |\n// |--------|-------:|------:|\n// | Widget |   9.99 |   142 |\n```\n\n**Markdown is terminal output, not interchange.** `toMarkdown` truncates any\ncell over `maxWidth` characters (default 50) with a `...` suffix and turns\nnewlines into `<br>`. Both are one-way, and there is no `fromMarkdown` —\nnor will there be. Use CSV or JSON to move data.\n\nCell text is escaped so it renders as itself: a cell reading\n`*not emphasis*` comes out as those words rather than as emphasis. Pass\n`escapeInline: false` when the cells hold Markdown you want rendered — a\ncolumn of `**bold**` labels, say. Pipes and newlines are escaped either\nway, because losing those loses the table.\n\n### Reading HTML tables\n\n`fromHtml` parses a table — anyone's table, not just hucre's — into a sheet:\n\n```ts\nimport { fromHtml } from \"hucre\"\n\nconst sheet = fromHtml(scrapedHtml, {\n  sheetName: \"Scraped\",\n  tableIndex: 0, // default: the first table in the document\n  typeInference: true, // default: numbers, booleans and ISO dates from text\n  preserveLeadingZeros: true, // default: \"007\" stays a string\n})\n```\n\nNamed HTML entities (`&nbsp;`, `&mdash;`, `&euro;`, …) are decoded, `<br>`\nbecomes a newline inside the cell, `<script>` and `<style>` contents are\nskipped rather than read as text, and `<tfoot>` lands at the bottom of the\nsheet wherever it was declared — which is where a browser paints it. A\ndocument with several tables reads the first; pass `tableIndex` for another.\n\nIt reads `<table>`, `<thead>`, `<tbody>`, `<tfoot>`, `<tr>`, `<td>`, `<th>`,\n`<caption>`, `colspan` and `rowspan`. Nested tables read as the text of the\ncell holding them, and markup the scanner cannot finish (an unterminated\ncomment, a truncated tag) ends the parse there and returns the rows read so\nfar rather than throwing.\n\n**`fromHtml` is not the inverse of `toHtml`.** HTML export is presentation\noutput; the table it writes is meant for a browser, and most of what makes it\nreadable there has no way back into a sheet:\n\n| `toHtml` writes                                    | `fromHtml` reads          |\n| -------------------------------------------------- | ------------------------- |\n| `colspan` / `rowspan`                              | ✅ `sheet.merges`         |\n| `<thead>`, or a row of all `<th>`                  | ✅ `sheet.a11y.headerRow` |\n| `<caption>`                                        | ✅ `sheet.a11y.summary`   |\n| `hucre-num` / `-bool` / `-date` / `-null` classes  | ✅ the cell's type        |\n| inline `style=` (fonts, fills, borders, alignment) | ❌ not read               |\n| the `<style>` block                                | ❌ not read               |\n| `role` / `aria-label`                              | ❌ not read               |\n\nSo values, structure and types survive a round trip through hucre's own HTML;\nformatting does not. Two other asymmetries worth knowing: cell text is trimmed\non read (whitespace around a `<td>` is indentation, not data) while `toHtml`\nwrites values as they are, and a `Date` is written as `YYYY-MM-DD`, so the\ntime of day is gone before the reader ever sees it.\n\nRe-exporting a sheet that came from HTML keeps the structure if you hand the\ntwo a11y hints back:\n\n```ts\nconst again = toHtml(sheet, {\n  hasHeaderRow: sheet.a11y?.headerRow === 0,\n  caption: sheet.a11y?.summary,\n})\n```\n\n### Number Format Renderer\n\n```ts\nimport { formatValue } from \"hucre\"\n\nformatValue(1234.5, \"#,##0.00\") // \"1,234.50\"\nformatValue(0.15, \"0%\") // \"15%\"\nformatValue(44197, \"yyyy-mm-dd\") // \"2021-01-01\"\nformatValue(1234, \"$#,##0\") // \"$1,234\"\nformatValue(0.333, \"# ?/?\") // \"1/3\"\n```\n\n### Cell Utilities\n\n```ts\nimport { parseCellRef, cellRef, colToLetter, rangeRef } from \"hucre\"\n\nparseCellRef(\"AA15\") // { row: 14, col: 26 }\ncellRef(14, 26) // \"AA15\"\ncolToLetter(26) // \"AA\"\nrangeRef(0, 0, 9, 3) // \"A1:D10\"\n```\n\n### Builder API\n\nFluent method-chaining interface:\n\n```ts\nimport { WorkbookBuilder } from \"hucre\"\n\nconst xlsx = await WorkbookBuilder.create()\n  .addSheet(\"Products\")\n  .columns([\n    { header: \"Name\", key: \"name\", autoWidth: true },\n    { header: \"Price\", key: \"price\", numFmt: \"$#,##0.00\" },\n  ])\n  .row([\"Widget\", 9.99])\n  .row([\"Gadget\", 24.5])\n  .freeze(1)\n  .done()\n  .build()\n```\n\nIt reaches the whole model, not a subset of it. Beyond `columns` / `row` /\n`merge` / `freeze` / `validation` / `cell` there are named methods for\nconditional rules, auto-filter, split panes, row definitions, page setup,\nheaders and footers, sheet views, protection, tables, images and charts —\nand `set()` for anything else on `WriteSheet`:\n\n```ts\nconst xlsx = await WorkbookBuilder.create()\n  .namedRanges([{ name: \"Prices\", range: \"Products!$B$2:$B$3\" }])\n  .addSheet(\"Products\")\n  .rows(data)\n  .pageSetup({ orientation: \"landscape\", paperSize: \"a4\" })\n  .conditionalRule({\n    type: \"cellIs\",\n    priority: 1,\n    range: \"B2:B99\",\n    operator: \"greaterThan\",\n    formula: [\"100\"],\n  })\n  .set({ rowBreaks: [40], outlineProperties: { summaryBelow: false } })\n  .build()\n```\n\n`set()` exists so the builder cannot fall behind the type: whatever\n`WriteSheet` or `WriteOptions` grows, it can already be expressed.\n\n### Template Engine\n\nFill `{{placeholders}}` in existing XLSX templates:\n\n```ts\nimport { openXlsx, saveXlsx, fillTemplate } from \"hucre\"\n\nconst workbook = await openXlsx(templateBuffer)\nfillTemplate(workbook, {\n  company: \"Acme Inc\",\n  date: new Date(),\n  total: 12500,\n})\nconst output = await saveXlsx(workbook)\n```\n\n### Excel 2024 Checkboxes\n\nBoolean cells can be flagged as native Excel 2024 checkboxes via Microsoft's\nFeaturePropertyBag extension. The cell value drives the checked state; older\nExcel and LibreOffice fall back to the raw `TRUE`/`FALSE` display since the\non-disk value is just a normal boolean.\n\n```ts\nimport { writeXlsx, readXlsx } from \"hucre/xlsx\"\n\nconst buf = await writeXlsx({\n  sheets: [\n    {\n      name: \"Tasks\",\n      rows: [[\"Done?\"], [true], [false], [true]],\n      cells: new Map([\n        [\"1,0\", { value: true, type: \"boolean\", checkbox: true }],\n        [\"2,0\", { value: false, type: \"boolean\", checkbox: true }],\n        [\"3,0\", { value: true, type: \"boolean\", checkbox: true }],\n      ]),\n    },\n  ],\n})\n\nconst wb = await readXlsx(buf)\nwb.sheets[0].cells?.get(\"1,0\")?.checkbox // true\n```\n\nThis is the first JS/TS implementation of native checkboxes — only `XlsxWriter`\n(Python) and `rust_xlsxwriter` had it before.\n\n### Accessibility (WCAG 2.1 AA)\n\nGenerate screen-reader-friendly spreadsheets and audit them for common\nWCAG 2.1 AA issues. Alt text on images and text boxes round-trips\nthrough `xdr:cNvPr/@descr` and `@title` (the OOXML attributes Excel and\nassistive tech read), and per-sheet summaries can promote the first\nnon-empty value into `docProps/core.xml` so screen readers announce it\non file open.\n\n```ts\nimport { writeXlsx, a11y, readXlsx } from \"hucre\"\n\nconst xlsx = await writeXlsx({\n  sheets: [\n    {\n      name: \"Q1 Sales\",\n      rows: [\n        [\"Region\", \"Revenue\"],\n        [\"EU\", 12_400],\n      ],\n      a11y: { summary: \"Quarterly sales by region\", headerRow: 0 },\n      images: [\n        {\n          data: pngBytes,\n          type: \"png\",\n          anchor: { from: { row: 0, col: 3 } },\n          altText: \"Bar chart showing 47% YoY growth\",\n        },\n      ],\n    },\n  ],\n})\n\n// Audit a workbook for missing alt text, missing header rows,\n// merged headers, low contrast, and more.\nconst wb = await readXlsx(xlsx)\nfor (const issue of a11y.audit(wb)) {\n  console.log(issue.type, issue.code, issue.message, issue.location)\n}\n\n// Color contrast helpers (WCAG 2.1 sRGB)\na11y.contrastRatio(\"0969DA\", \"FFFFFF\") // ≈ 5.19 (passes AA)\na11y.relativeLuminance(\"808080\")\n```\n\nIssue codes: `no-doc-title`, `no-doc-description`, `empty-sheet`,\n`no-header-row`, `merged-header-row`, `missing-alt-text` (error for\nimages, warning for text boxes), `low-contrast`, `blank-row-in-data`.\nTune the contrast pass with\n`audit(wb, { skipContrast, minContrast, contrastSampleLimit })`.\n\n### Object Shorthand (XLSX / ODS)\n\nSkip t","readmeFilename":""}