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

@wanshi-kg/metricat

v0.2.0

Published

Parse messy human measurement strings and canonicalize them — deterministic, dimension-safe, affine-correct unit conversion.

Readme

metricat

CI License: MIT

Parse messy human measurement strings and canonicalize them — deterministic, dimension-safe, affine-correct unit conversion.

metricat is a TypeScript library (and CLI) that turns ¾ cup, 2 1/2 tbsp, 0,5 ml, 3/10 kg, −40 °C into structured, canonicalized measurements. It owns the unsolved half — parsing the messy human number + unit (fractions, Unicode vulgar fractions, decimal commas, signs) — and libraries the solved half — dimension-safe conversion with correct affine handling (temperature) via unitmath. No LLM; every result is verifiable.

It's built to be embedded: a small, semver-tagged package (the document-outline-gen pattern) that knowledge-graph pipelines consume to stamp measurement facts and merge equivalent quantities.

Install · Quick start · CLI · API · Extensibility · Design

Install

npm install @wanshi-kg/metricat

Or straight from GitHub over git+https (no registry, no SSH key — handy in CI/sandboxes), where dist/ is built on install via the prepare hook:

npm install github:wanshi-kg/metricat#semver:^0.2.0

Pin by semver tag, never by SHA — that is the whole point of tagging from day one.

Quick start

import metricat, { parse, extractAll, convert, normalize, areEquivalent } from '@wanshi-kg/metricat';

parse('¾ cup');
// { raw: '¾ cup', value: 0.75, unit: 'cup', dimension: 'volume',
//   canonical: { value: 0.000177…, unit: 'm^3' } }

parse('2 fluid ounces');        // multi-word units resolve too → { unit: 'floz', dimension: 'volume', … }
parse('350 degrees Fahrenheit');// → { unit: 'degF', dimension: 'temperature', … }

parse('hello world');           // null  — no false positives in prose

extractAll('Add 2½ cups flour, ¾ tsp salt, bake at 350°F for 25 min.');
// → measurements for "2½ cups", "¾ tsp", "350°F", "25 min", each with a { start, end } span

convert(parse('0°C')!, '°F');   // value ≈ 32     (affine-correct: NOT a linear multiple)
convert(parse('3 g')!, 's');    // null           (dimension-safe: incompatible)
normalize(parse('2 cups')!);    // → value in m^3 (the dimension's base unit)

areEquivalent(parse('¾g')!, parse('0.00075 kg')!);  // true
areEquivalent(parse('0°C')!, parse('32°F')!);       // true
areEquivalent(parse('3 g')!, parse('2 s')!);        // false

The Measurement shape

interface Measurement {
  raw: string;                                   // the exact substring parsed
  value: number;                                 // numeric value in `unit`
  unit: string;                                  // resolved unit, e.g. 'g', 'cup', 'degC', 'ohm'
  dimension: string;                             // 'mass' | 'volume' | 'temperature' | … (or base signature)
  canonical: { value: number; unit: string };    // normalized to the dimension's base unit
  span?: { start: number; end: number };          // source span (set by extractAll)
}

API

| Function | Description | | --- | --- | | parse(input): Measurement \| null | One string → one measurement, or null for non-measurements. | | extractAll(text): Measurement[] | Scan a block of text for every measurement, each with a span. | | convert(m, toUnit): Measurement \| null | Dimension-safe conversion; null on incompatible dimensions. | | normalize(m): Measurement | Normalize to the dimension's canonical base unit. | | areEquivalent(a, b): boolean | Same physical quantity? The merge/dedup hook. | | createMetricat(options): Metricat | A bound instance with custom units/aliases. |

CLI

Installing the package provides a metricat binary. Point it at a file, or pipe text through it:

metricat recipe.txt                       # extract all measurements → JSON array
cat notes.md | metricat --ndjson          # one JSON object per line (pipe-friendly)
echo "350°F" | metricat --parse           # parse the input as a single measurement
cat data.txt | metricat --ndjson | jq -r '.unit'

| Flag | Effect | | --- | --- | | -n, --ndjson | Emit one JSON object per line. | | -c, --compact | Compact JSON (no indentation). | | --parse | Treat the whole input as one measurement; exit 1 if it isn't one. | | -h, --help | Show help. | | -V, --version | Print the version. |

With no file argument it reads stdin (pass - to force it). Default output is the extractAll result as a pretty JSON array; each item carries a span locator into the source text.

Extensibility

One package, many consumers. Register domain units against the same package without touching the shared default registry — this is how a downstream electronics knowledge base adds the units it needs that aren't built in:

import { createMetricat } from '@wanshi-kg/metricat';

const m = createMetricat({
  unitDefinitions: { smoot: { value: '1.7018 m' } }, // unitmath definition syntax
  aliases: { sqm: 'm^2' },                            // extra token normalizations
});

m.parse('1 smoot');   // dimension: 'length'
m.parse('5 sqm');     // dimension: 'area'

(unitmath already knows the common electronics set — V, A, ohm/Ω, F, H, W, Hz — out of the box; createMetricat is for what it doesn't.)

Design

  • Two layers. Parse (a salvaged + rebuilt Peggy grammar) produces (value, unitToken); the units layer (unitmath) resolves, converts, and compares. The grammar never does unit math; the library never parses messy strings.
  • Affine-correct. 0 °C canonicalizes to 273.15 K, not 0 K. This package exists partly to not repeat the legacy extension's linear temperature bug — there is a standing regression test.
  • Deterministic & safe-by-default. No network, no LLM. parse returns null and extractAll returns [] rather than guessing.

Versioning

Published to npm and semver-tagged from day one. npm consumers use @wanshi-kg/metricat@^0.2; git consumers pin #semver:^0.2.0 and get patch/minor updates without re-pinning a commit. The API graduates to v1.0.0 once stable. Releases ship via CI on a published GitHub Release (see .github/workflows/publish.yml).

License

MIT © Alex Sabaka