Platform Support for hucre
mainfs, crypto, or Buffer. It is compatible with modern runtimes and edge environments.repository·main·Indexed 23 days ago
https://github.com/productdevbook/hucreA zero-dependency spreadsheet engine written in pure TypeScript for reading and writing XLSX, CSV, ODS, JSON, NDJSON, and XML. It features schema validation, streaming for large files via streamXlsxRows and XlsxStreamWriter, and support for advanced Excel features including password protection (ECMA-376 Agile), Pivot Tables, charts, data validation, and hyperlinks. The library is tree-shakeable and optimized for environments like the Edge runtime.
fs, crypto, or Buffer. It is compatible with modern runtimes and edge environments.Hucre supports reading Excel Slicers (2010+) and Timeline Slicers (2013+). These are stored in workbook.slicerCaches / workbook.timelineCaches at the workbook level, and in sheet.slicers / sheet.timelines at the sheet level. When using saveXlsx, hucre ensures these parts are re-declared in the necessary XML files (Content_Types, workbook rels, extLst, and sheet rels) to prevent Excel from treating them as orphans and dropping them.
import { readXlsx, parseSlicers, parseSlicerCache, parseTimelines, parseTimelineCache } from "hucre"
const wb = await readXlsx(buf)
// Workbook-level cache definitions.
console.log(wb.slicerCaches) // SlicerCache[] (pivot-table or table source)
console.log(wb.timelineCaches) // TimelineCache[]
// Per-sheet slicer / timeline instances.
for (const sheet of wb.sheets) {
for (const s of sheet.slicers ?? []) console.log(s.name, s.cache, s.caption)
for (const t of sheet.timelines ?? []) console.log(t.name, t.cache, t.level)
}hucre is a modular library composed of several specialized engines. The core hucre module provides a unified read/write API with automatic format detection. Other key components include:
AsyncGenerator), and auto-column width calculation.WorkbookBuilder and SheetBuilder APIs for constructing files.{{placeholder}} template engine.The library is zero-dependency and written in pure TypeScript. The ZIP engine utilizes the CompressionStream/DecompressionStream Web APIs, with a pure TypeScript fallback for environments where they are unavailable.
Hucre provides a three-layer system for handling charts:
parseChart(xml) or getCharts(workbook) surfaces a Chart record containing titles, series, axes, legends, etc.writeXlsx with charts: [SheetChart] emits chart parts and re-anchors them in the worksheet.cloneChart(source, options) converts a parsed Chart into a writable SheetChart.Override Grammar for cloneChart:
undefined: Inherit from source.null: Drop the property.value: Replace with new value.Helper functions:
getCharts(workbook): Flattens all charts in the workbook into a single array.addChart(sheet, chart): Appends a SheetChart to a WriteSheet.import { getCharts, openXlsx, parseChart, addChart, writeXlsx } from "hucre"
// Reading charts
const wb = await openXlsx(buf)
for (const { sheetName, chart } of getCharts(wb)) {
console.log(sheetName, chart.kinds, chart.title)
console.log(chart.anchor) // { from: { row: 1, col: 3 }, to: { row: 16, col: 10 } }
console.log(chart.axes?.x?.title, chart.axes?.y?.scale)
for (const s of chart.series ?? []) {
console.log(s.kind, s.name, s.valuesRef, s.color, s.dataLabels)
}
}
// Standalone parser
const chart = parseChart(xml)To process large files without loading them entirely into memory, use streamXlsxRows from hucre/xlsx. This works with Uint8Array or ReadableStream (e.g., from a fetch response).
Key features:
maxRows: Cap the number of rows yielded to save resources.range: Filter to a specific A1 range (e.g., "B2:D1000"). Cells outside the column span are masked to null.To modify an existing XLSX file without losing features that hucre doesn't natively manage (like charts, macros/VBA, or specific themes), use openXlsx and saveXlsx from hucre/xlsx. This ensures the underlying XML structure for unsupported features is preserved.
import { openXlsx, saveXlsx } from "hucre/xlsx"
const workbook = await openXlsx(buffer)
workbook.sheets[0].rows[0][0] = "Updated!"
const output = await saveXlsx(workbook) // Charts, VBA, themes preservedhucre supports reading and writing password-protected XLSX files using the ECMA-376 Agile encryption scheme (Excel 2010+). This uses the platform's WebCrypto API.
encryption object with a password to writeXlsx.password in the options for readXlsx, readObjects, or streamXlsxRows.EncryptedFileError: Thrown if the file is encrypted but no password was provided.DecryptionError: Thrown if the provided password is incorrect.Note: You can adjust the security/speed trade-off using spinCount in the encryption options.
import { writeXlsx, readXlsx, readObjects, EncryptedFileError, DecryptionError } from "hucre"
// Write encrypted
const encrypted = await writeXlsx({
sheets: [{ name: "Secret", rows: [["pin", 1234]] }],
encryption: { password: "hunter2" },
})
// Read encrypted
try {
const wb = await readXlsx(encrypted, { password: "hunter2" })
const rows = await readObjects(encrypted, { password: "hunter2" })
} catch (e) {
if (e instanceof EncryptedFileError) console.log("needs a password")
if (e instanceof DecryptionError) console.log("wrong password")
}You can use readXlsx to parse an XLSX buffer and writeXlsx to generate an XLSX file from a structured object. The writeXlsx function accepts an object containing an array of sheets. Each sheet defines columns (with header, key, width, and optional numFmt) and data (an array of objects matching the column keys).
import { readXlsx, writeXlsx } from "hucre"
// Read an XLSX file
const workbook = await readXlsx(buffer)
console.log(workbook.sheets[0].rows)
// Write an XLSX file
const xlsx = await writeXlsx({
sheets: [
{
name: "Products",
columns: [
{ header: "Name", key: "name", width: 25 },
{ header: "Price", key: "price", width: 12, numFmt: "$#,##0.00" },
{ header: "Stock", key: "stock", width: 10 },
],
data: [
{ name: "Widget", price: 9.99, stock: 142 },
{ name: "Gadget", price: 24.5, stock: 87 },
],
},
],
})To minimize your bundle size, avoid importing from the main hucre entry point. Instead, import specific format handlers from their respective sub-paths. This allows your bundler to tree-shake unused formats.
import { readXlsx, writeXlsx } from "hucre/xlsx" // XLSX only
import { parseCsv, writeCsv } from "hucre/csv" // CSV only (~2 KB gzipped)
import { readOds, writeOds } from "hucre/ods" // ODS only
import { parseJson, writeNdjson } from "hucre/json" // JSON / NDJSON
import { readXml, writeXml } from "hucre/xml" // Tabular XMLTo use the hucre spreadsheet engine in your project, install it using npm:
npm install hucreYou can add hyperlinks to cells in two ways:
cells map: For non-tabular layouts, map cell coordinates (e.g., "0,0") to objects containing a hyperlink property.data rows: For tabular reports, include a link object directly in the data row, keyed by the column's key. Use the link() helper or a plain object { text, hyperlink, tooltip? }.Internal references (to other sheets/cells) should be prefixed with # (e.g., #Sheet2!A1).
import { writeXlsx, link } from "hucre/xlsx"
// Inline in data rows (Recommended for tables)
await writeXlsx({
sheets: [
{
name: "Summary",
columns: [
{ header: "Link", key: "link" },
{ header: "ID", key: "id" },
],
data: [
{ link: link("Open", "https://example.com/items/abc-123"), id: "abc-123" },
{ link: { text: "Open", hyperlink: "https://example.com/items/def-456" }, id: "def-456" },
],
},
],
})Hucre can read existing Pivot Tables and their workbook-level caches into workbook.pivotCaches and sheet.pivotTables.
Authoring from scratch:
You can create new Pivot Tables using writeXlsx by defining them in the pivotTables array of a sheet. Hucre handles the creation of the pivot cache (definition + records) and all required relationships. Note that the numeric layout (totals, etc.) is computed by Excel on the first open via fullCalcOnLoad.
import { readXlsx, writeXlsx, parsePivotTable, parsePivotCacheDefinition, attachPivotCacheFields } from "hucre"
// Reading existing pivots
const wb = await readXlsx(buf)
for (const cache of wb.pivotCaches ?? []) {
console.log(cache.cacheId, cache.sourceSheet, cache.sourceRef, cache.fieldNames)
}
for (const sheet of wb.sheets) {
for (const pt of sheet.pivotTables ?? []) {
console.log(pt.name, pt.location, pt.cacheId)
for (const f of pt.fields) {
console.log(" ", f.name, f.axis, f.function)
}
}
}
// Authoring new pivots
const xlsx = await writeXlsx({
sheets: [
{
name: "Data",
rows: [
["Region", "Product", "Revenue"],
["EU", "A", 100],
["EU", "B", 50],
["US", "A", 200],
["US", "B", 75],
],
},
{
name: "Pivot",
pivotTables: [
{
name: "SalesPivot",
sourceSheet: "Data",
rows: ["Region"],
columns: ["Product"],
values: [{ field: "Revenue", function: "sum" }],
},
],
},
],
})