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

@kairos-innovations/report-builder

v0.2.0

Published

Headless report-builder rules for Kairos consoles: one reading of how a cube rolls up, so two dashboards cannot disagree about a number

Readme

@kairos-innovations/report-builder

The rules a self-service report builder needs, as pure functions. No React, no transport, no geography.

It exists because two consoles reading the same cube must not disagree about a number — and because the two ways a report silently lies are easy to write by accident.

Install

npm install @kairos-innovations/report-builder

No dependencies and no peer dependencies.

The idea

The reporting engine cannot GROUP BY. Every dataset is therefore a cube: a view already aggregated to the finest grain of its dimensions, with summable measures as columns. This package rolls that cube up to whatever grain the user asked for — and refuses when the maths would be wrong.

import { pivot } from "@kairos-innovations/report-builder"

const result = pivot(rows, config, dataset)

if (!result.ok) {
  // NON_ADDITIVE_ROLLUP · MIXED_CURRENCY · UNKNOWN_DATASET · UNKNOWN_MEASURE
  showNotice(result.message)
} else {
  render(result.spec) // { type, rows, seriesKeys?, unit, measureLabel, total?, currency? }
}

What it refuses, and why

A non-additive measure. A measure declares how it survives a roll-up:

| aggregation | Roll-up | Example | |---|---|---| | { kind: "sum", column } | adds | collected_amount | | { kind: "ratio", numerator, denominator } | sums both, divides once | days_in_state_sum ÷ occupancy_count | | { kind: "distinct", column } | cannot be combined | vehicle_count |

An average cannot be re-averaged: two cells of "9.5 days" only average to 9.5 when both hold the same number of observations. A distinct count cannot be combined at all — a vehicle charged in March and April is one vehicle. pivot returns { ok: false, reason: "NON_ADDITIVE_ROLLUP" } naming the dimension responsible, instead of a number that looks right.

Two currencies. Money cubes group by currency. If the rows carry more than one and currency is not a break-down, pivot returns MIXED_CURRENCY. Adding MZN to UGX is a money bug.

API

| Export | What it answers | |---|---| | pivot(rows, config, dataset, options?) | cube rows → VisualSpec, or a refusal | | eligibleCharts(config, dataset, options?) | which chart types this config can render, and what each refusal needs | | autoChart(config, dataset) | the chart to draw, and the sentence explaining why | | resolveChart(config, dataset, options?) | the user's override if it still holds, else auto | | canAggregate(measure, config, dataset, rows) | may this measure be rolled up to this grain? | | singleCurrency(rows, dataset) | the one currency, null if several, undefined if not money | | formatValue(value, unit, options?) | one number, one unit, one string | | visualTitle / visualSubtitle | generated headline and context line | | encodeConfig / decodeConfig | a configuration that survives a URL |

Geography is the host's: eligibleCharts draws no map unless you pass mapDimensions, because hardcoding one deployment's administrative vocabulary would make it everyone's.

Release

Bump version in package.json and merge to dev. The workflow publishes only when that version is not already on npm, so an ordinary feature merge is a no-op. Releases are published from CI by trusted publishing, so there is no npm token stored anywhere and every release carries a provenance attestation.

Licence

MIT

Drawing

layoutChart(spec, box, options?) returns plain geometry for all eight chart types: numbers, SVG path strings, ordinal series indices and 0..1 intensities.

It never returns a colour. Not a hex value, not a palette, not a default. Your product's theme is yours; this package tells you which series and how intense, and you map that onto your own tokens. It is why one package can serve products that look nothing like each other, and why a download made from your chart carries your colours rather than ours.

A map resolves by what you supply. Pass options.shapes — region outlines keyed to the dimension value, with [lon, lat] rings — and you get a choropleth; pass none and you get a cartogram over the same values. Which one a deployment shows follows its DATA, not a build flag, so a product renders a cartogram before its boundaries are loaded and a real map afterwards with no code change. Values that have data but no shape come back in unmatched rather than being dropped.

buildReportDocument(input) returns everything a printed report says other than the chart: title, subtitle, dataset, break-down, the figures as formatted strings with their shares, and the values a filter excluded — named, not counted. It takes generatedAt as a parameter and never reads a clock.

WELLS is the field-well vocabulary in plain language, in the order that reads as a sentence: the number, broken down by, then split by, narrow down.