bilig

xlsx-calc alternative for Node workbook recalculation

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.

Quick choice

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.

Node service recalculation path

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
}

Measured Lane

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:

Run 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.

What I Would Test

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.

If this saved you a spreadsheet-recalculation detour, star the repo so the next Node developer can find it: https://github.com/proompteng/bilig/stargazers.