Use @bilig/headless when a Node.js program needs a small workbook model it can
edit, recalculate, read, and persist. The useful case is not “open Excel on the
server.” It is “run spreadsheet-shaped logic in a service and prove the result
that came back.”
That usually means one of these jobs:
If the main job is styling or writing an .xlsx file, start with an XLSX
library. If the main job is a long tail of arbitrary Excel edge cases with no
fixture-reduction step, evaluate a mature formula engine first. If the job is a
TypeScript workbook object with formulas, edits, readback, and persistence, try
the WorkPaper path below.
Start from an empty directory:
mkdir bilig-node-formulas
cd bilig-node-formulas
npm init -y
npm pkg set type=module
npm install @bilig/headless
npm install -D tsx typescript @types/node
Create eval-node-formulas.ts:
import {
WorkPaper,
createWorkPaperFromDocument,
exportWorkPaperDocument,
parseWorkPaperDocument,
serializeWorkPaperDocument,
type WorkPaperCellAddress,
} from '@bilig/headless'
type NumericCell = {
value: number
}
function readNumber(workbook: WorkPaper, address: WorkPaperCellAddress): number {
const value = workbook.getCellValue(address)
if (typeof value !== 'object' || value === null || typeof (value as NumericCell).value !== 'number') {
throw new Error(`Expected numeric cell, got ${JSON.stringify(value)}`)
}
return (value as NumericCell).value
}
const workbook = WorkPaper.buildFromSheets({
Quote: [
['Metric', 'Value'],
['Seats', 12],
['Price', 49],
['Discount', 0.1],
],
Summary: [
['Metric', 'Value'],
['Monthly total', '=Quote!B2*Quote!B3*(1-Quote!B4)'],
],
})
const quote = workbook.getSheetId('Quote')
const summary = workbook.getSheetId('Summary')
if (quote === undefined || summary === undefined) {
throw new Error('Expected Quote and Summary sheets')
}
const total: WorkPaperCellAddress = { sheet: summary, row: 1, col: 1 }
const before = readNumber(workbook, total)
workbook.setCellContents({ sheet: quote, row: 1, col: 1 }, 20)
const afterEdit = readNumber(workbook, total)
const serialized = serializeWorkPaperDocument(exportWorkPaperDocument(workbook, { includeConfig: true }))
const restored = createWorkPaperFromDocument(parseWorkPaperDocument(serialized))
const afterRestore = readNumber(restored, total)
if (before !== 529.2 || afterEdit !== 882 || afterRestore !== afterEdit) {
throw new Error(JSON.stringify({ before, afterEdit, afterRestore }))
}
console.log({ before, afterEdit, afterRestore, verified: true })
Run it:
npx tsx eval-node-formulas.ts
Expected output:
{ "before": 529.2, "afterEdit": 882, "afterRestore": 882, "verified": true }
That proves the Node process created a workbook, evaluated a cross-sheet formula, edited an input cell, read the dependent result, serialized the WorkPaper document, restored it, and read the same calculated value again.
A direct formula-function library is useful when code wants to call something
like SUM() as a JavaScript function. A workbook runtime is useful when the
formula depends on sheet state:
That is the boundary @bilig/headless is built around.
An XLSX library is the right first choice when the file is the product: reports, styles, images, tables, and handoff to Excel or another spreadsheet app.
Use @bilig/headless when the service needs calculated workbook state before a
person opens any file. You can still export or import at the system boundary,
but the WorkPaper model is the part that calculates and verifies the values in
Node.
Before using any headless spreadsheet engine in a service, check these items:
For @bilig/headless, start with one TypeScript fixture and keep it boring:
build the workbook, edit one input, read one dependent output, persist, restore,
and assert the same value after restore.
If this is the shape of formula automation you were looking for, star the repo so the package is easier to find later: https://github.com/proompteng/bilig/stargazers.
If it almost matches but a gap blocks adoption, use the adoption blocker form: https://github.com/proompteng/bilig/discussions/new?category=general.