npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@xlscalc/formula-engine

v0.4.0

Published

Zero-dependency Excel formula parser and evaluator. Fail-loud: never renders a guessed number.

Readme

@xlscalc/formula-engine

An Excel formula parser and evaluator with no dependencies and one rule: it never renders a number it is not sure of.

npm install @xlscalc/formula-engine
import { Workbook } from '@xlscalc/formula-engine';

const wb = new Workbook();
wb.addSheet('Revenue');

wb.setValue(0, 1, 1, 100);           // A1 = 100
wb.setValue(0, 2, 1, 250);           // A2 = 250
wb.setFormula(0, 3, 1, 'SUM(A1:A2)*1.2');

const report = wb.evaluateAll();

wb.record(0, 3, 1).value;            // 420
wb.record(0, 3, 1).provenance;       // 'computed'
report.stats;                        // { formulas: 1, computed: 1, unsupported: 0, … }

Rows and columns are 1-based, as they are in Excel. Sheets are 0-based indices in the order they were added.

Why it refuses

Every cell that comes back carries a provenance, and that is the whole design rather than a diagnostic:

| provenance | meaning | |---|---| | literal | straight from the file; we did not touch it | | cached | the file's own computed value, reused | | computed | we evaluated it | | volatile | we evaluated it, and it depends on the clock | | circular | part of a reference cycle | | unsupported | outside capability — no value at all |

unsupported never carries a number. There is no code path from "we could not compute this" to a figure, and refusal propagates: a SUM over a cell we declined to compute is itself refused rather than quietly dropping the input. That is deliberate, and it is expensive — in the largest workbook of the real-data corpus, 33 cells using OFFSET and CELL leave 64,809 cells warning rather than numbered. A total that silently omits a term it could not evaluate is precisely the failure this library exists to prevent.

Excel's own error values are a separate thing entirely. #DIV/0! is a computed result and renders as itself; the two are never conflated.

What it knows

204 functions. The list is not prose that can go stale — ask the library:

import { implementedFunctions, refusedFunctions } from '@xlscalc/formula-engine';

implementedFunctions();   // ['ABS', 'ACOS', 'AND', … ] — 204 of them
refusedFunctions();       // ['AGGREGATE', 'CELL', 'FILTER', … ] — 26

CAPABILITY.md ships inside this package and is generated from the same registry, so diffing it between two installed versions shows exactly what changed about what renders.

26 further functions are known and deliberately refusedOFFSET, INDIRECT, CELL, INFO, the dynamic-array family — each with a stated reason that reaches the tooltip. Refusing something by name is better than not recognising it, because the reason can be specific.

Note that adding a function changes what existing users see — a workbook that showed a screen of warnings starts showing numbers, with no change on the caller's side. So capability growth is never a patch release; the repository's VERSIONING.md states the policy.

How it is checked

Not by hand-picked probes. Three harnesses, each answering a question the last one could not:

  1. An oracle — thousands of generated formulas, every answer compared against LibreOffice.
  2. Ten whole synthetic workbooks — agent-written models in the shape of real ones, since a formula in isolation is not a formula in a model.
  3. Ten real workbooks — 202,795 formula cells nobody wrote for this project, graded against the values their own applications computed. On the 128,976 cells it answers there are zero unexplained disagreements. 3,050 of them deliberately differ from the value the file stored — a Google Sheets export treats a blank reference differently from Excel, and we follow Excel — and each falls under a named rule with an exact expected count, gated in both directions so a rule that starts explaining more cells, or fewer, fails the build.

The real corpus found five bugs on its first run and three more on a second pass — most of them things the synthetic corpus could not have found in principle. Those workbooks are other people's confidential business data, so they are not published and neither is the harness that reads them; the two harnesses you can rerun are the oracle and the synthetic corpus, both in the repository.

Beyond evaluating

import { parseFormula, unparse, walk } from '@xlscalc/formula-engine';

const ast = parseFormula('=SUM(A1:A3)*(1-$B$1)');
walk(ast, (n) => { if (n.k === 'fn') console.log(n.name); });   // SUM
unparse(ast);                                                   // SUM(A1:A3)*(1-$B$1)

parseFormula(unparse(ast)) is asserted to give back the same tree. That is not decoration: unparse is how shared formulas are reconstructed, and when it and the parser held separate precedence tables they drifted — O19*(1-$E$17) came back as O19*1-$E$17, no error, a number two and a half times too large.

Requirements

Node 18+ or any modern browser. ESM only. TypeScript declarations included. MIT.