You probably got here because you have an .xlsx file, you changed an input
cell in Node, and now the cached formula result is wrong.
xlsx-calc can be the right answer. If your workbook is already a SheetJS
object and the formulas you use are in its supported set, the API is simple:
edit cells, call XLSX_CALC(workbook), read the values.
Use @bilig/headless when the spreadsheet is not just a file. The usual case
is a backend decision path: quote approval, payout checks, import validation,
or an agent tool that needs to write inputs, recalculate, read outputs, and
save a state it can test again later.
| You need | Start with |
|---|---|
| Recalculate a supported formula set on a SheetJS workbook object | xlsx-calc |
| Read and write lots of spreadsheet file formats | SheetJS |
Build styled .xlsx files |
ExcelJS or SheetJS |
| Keep a formula workbook as service state | @bilig/headless |
| Read recalculated outputs before accepting a request | @bilig/headless |
| Persist JSON state and still import or export XLSX at the edge | @bilig/headless |
That is the whole distinction. xlsx-calc is a calculator over a workbook
object. Bilig is a workbook runtime with import/export at the edges.
import { readFile, writeFile } from 'node:fs/promises'
import { WorkPaper } from '@bilig/headless'
import { exportXlsx, importXlsx } from '@bilig/headless/xlsx'
const source = await readFile('pricing-model.xlsx')
const imported = importXlsx(source, 'pricing-model.xlsx')
const workbook = WorkPaper.buildFromSnapshot(imported.snapshot)
const inputs = workbook.getSheetId('Inputs')
const summary = workbook.getSheetId('Summary')
if (inputs === undefined || summary === undefined) {
throw new Error('Expected Inputs and Summary sheets')
}
workbook.setCellContents({ sheet: inputs, row: 1, col: 1 }, 48)
workbook.setCellContents({ sheet: inputs, row: 2, col: 1 }, 1250)
const decision = workbook.getCellValue({ sheet: summary, row: 6, col: 1 })
if (decision !== 'approved') {
throw new Error(`Expected approved decision, got ${String(decision)}`)
}
const edited = exportXlsx(workbook.exportSnapshot())
await writeFile('pricing-model-edited.xlsx', edited)
The maintained example is small enough to inspect:
git clone https://github.com/proompteng/bilig.git
cd bilig/examples/xlsx-recalculation-node
npm install
npm start
It should end with checks like these:
{
"decisionChanged": true,
"exportedReimportMatchesAfter": true,
"formulasSurvivedXlsxRoundTrip": true,
"verified": true
}
There is a checked-in xlsx-calc comparison, but it is deliberately narrow.
It covers four workbook-wide recalculation workloads: aggregate, exact-match
lookup, approximate lookup, and formula-chain recalculation.
Current artifact:
packages/benchmarks/baselines/workpaper-vs-xlsx-calc.json
The artifact records:
xlsx-calc version: 0.9.244/44/44/4Run the local check with:
pnpm workpaper:bench:xlsx-calc:check
That benchmark does not mean “Bilig replaces Excel.” It only says that this particular Node recalculation lane is measured, checked in, and easy to rerun.
For a production service, I would not ship this based on one happy-path example. I would add tests for:
If the service mostly formats files, keep using a file library. If the service acts on the calculated result, put the cells behind a small adapter and test that adapter like business logic.
xlsx-calc repository:
https://github.com/fabiooshiro/xlsx-calcIf this saved you a spreadsheet-recalculation detour, star the repo so the next Node developer can find it: https://github.com/proompteng/bilig/stargazers.