Strongly-typed physical quantities and unit conversions for Node.js, powered by Rust.
@siderust/qtty gives you compile-time safe, zero-overhead unit conversions in
JavaScript and TypeScript by delegating all math to the Rust qtty library via
a native Node addon built with napi-rs.
npm install @siderust/qttyThe package ships a prebuilt native addon. If a prebuilt binary is not available
for your platform you will need a Rust toolchain (rustup) and the napi-rs CLI:
npm install -g @napi-rs/cli
cd node_modules/@siderust/qtty && napi build --release --platformconst { Quantity, DerivedQuantity, convert } = require('@siderust/qtty');
// Create a quantity and convert
const distance = new Quantity(1000, 'Meter');
const km = distance.to('Kilometer');
console.log(km.value); // 1
console.log(km.unit); // "Kilometer"
// Arithmetic with automatic unit conversion
const a = new Quantity(1, 'Kilometer');
const b = new Quantity(500, 'Meter');
const total = a.add(b); // 1.5 km
const doubled = total.mul(2); // 3.0 km
// Derived (compound) quantities
const velocity = new DerivedQuantity(100, 'Meter', 'Second');
const kmh = velocity.to('Kilometer', 'Hour');
console.log(kmh.value); // 360
// One-shot value conversion
const hours = convert(7200, 'Second', 'Hour'); // 2Import named unit factory functions to create quantities without typing
new Quantity(…). Each factory is callable and reads like an expression:
// Both lines produce the same Quantity:
const a = Degrees(180); // factory style
const b = new Quantity(180, 'Degree'); // class style
// Works for any dimension:
const dist = Kilometers(1000);
const mass = Kilograms(70);
const time = Hours(24);
const power = Kilowatts(3.5);Because JavaScript has no operator overloading, 180 * Degrees cannot
return a Quantity — write Degrees(180) instead.
import {
Meters, Kilometers, Miles, AstronomicalUnits, LightYears, Parsecs,
Seconds, Minutes, Hours, Days, Years, JulianYears,
Degrees, Radians, Arcseconds,
Grams, Kilograms, SolarMasses,
Watts, Kilowatts, SolarLuminosities,
} from '@siderust/qtty/units';
// Factory metadata
console.log(Meters.unit); // 'Meter'
console.log(Meters.symbol); // 'm'
console.log(Meters.dimension); // 'Length'
// Convert straight from the factory call
const rad = Degrees(180).to('Radian'); // π rad
const pc = LightYears(1).to('Parsec'); // 0.3066 pc
// Chain arithmetic
const total = Kilometers(1).add(Meters(500)); // 1.5 km
const speed = Meters(100).div(1).to('Kilometer').mul(3600); // naive example
// Dynamic lookup by name
import { unit, units } from '@siderust/qtty/units';
const factory = unit('SolarMass'); // UnitFactory | undefined
const sun = factory!(1); // Quantity { value: 1, unit: 'SolarMass' }
// All factories as a record
const lengthNames = Object.values(units)
.filter(f => f.dimension === 'Length')
.map(f => f.unit);Full type information is provided out of the box:
import { Quantity, convert, isCompatible } from '@siderust/qtty';
import { Degrees, Kilometers, Hours, type UnitFactory } from '@siderust/qtty/units';
// Using the class
const q: Quantity = new Quantity(180, 'Degree');
const rad: Quantity = q.to('Radian');
console.log(rad.value); // 3.141592653589793
// Using factories
const angle: Quantity = Degrees(180); // identical result
const speed: Quantity = Kilometers(100).div(Hours(1).value);
// Type of a factory
const f: UnitFactory = Degrees;
console.log(f.unit); // 'Degree'
console.log(f.symbol); // '°'
console.log(f.dimension); // 'Angle'
const ok: boolean = isCompatible('Meter', 'Kilometer'); // true| Member | Description |
|---|---|
new Quantity(value, unit) |
Create a quantity. Unit is a string like "Meter". |
.value |
The numeric value (getter). |
.unit |
The unit name (getter). |
.symbol |
The unit symbol, e.g. "m" (getter). |
.dimension |
The dimension name, e.g. "Length" (getter). |
.to(unit) |
Convert to another unit. Throws on dimension mismatch. |
.add(other) |
Add another quantity (same dimension). |
.sub(other) |
Subtract another quantity (same dimension). |
.mul(scalar) |
Multiply by a number. |
.div(scalar) |
Divide by a number. |
.neg() |
Negate. |
.compatible(other) |
Check dimension compatibility. |
.format(precision?) |
Format as string, e.g. "1000 m". |
.toJson() |
Return { value, unit } plain object. |
.toString() |
Same as .format(). |
| Member | Description |
|---|---|
new DerivedQuantity(value, num, den) |
Create a compound quantity (e.g. m/s). |
.value |
Numeric value. |
.numerator / .denominator |
Unit names. |
.symbol |
Compound symbol, e.g. "m/s". |
.to(num, den) |
Convert to different units. |
.mul(scalar) / .div(scalar) / .neg() |
Arithmetic. |
.format(precision?) / .toString() |
Formatting. |
.toJson() |
Return { value, numerator, denominator }. |
| Function | Description |
|---|---|
convert(value, from, to) |
Convert a bare number between units. |
isCompatible(unitA, unitB) |
Check if two units share a dimension. |
unitDimension(unit) |
Get the dimension name for a unit. |
unitSymbol(unit) |
Get the symbol for a unit. |
isValidUnit(unit) |
Check if a unit name is recognized. |
listUnits() |
Return all registered units as { name, symbol, dimension }[]. |
ffiVersion() |
FFI ABI version number. |
Every named export is a UnitFactory: call it with a number to get a Quantity.
import { Degrees, Kilometers, Kilograms, Watts } from '@siderust/qtty/units';
Degrees(180); // Quantity(180, 'Degree')
Kilometers(2.5); // Quantity(2.5, 'Kilometer')
Kilograms(70); // Quantity(70, 'Kilogram')
Watts(1500); // Quantity(1500, 'Watt')Use unit(name) for dynamic lookup and units for the full registry.
| Dimension | Example units |
|---|---|
| Length | Meter, Kilometer, Mile, AstronomicalUnit, Parsec, LightYear, … |
| Time | Second, Minute, Hour, Day, Year, JulianCentury, … |
| Angle | Radian, Degree, Arcminute, Arcsecond, HourAngle, … |
| Mass | Gram, Kilogram, Pound, SolarMass, AtomicMassUnit, … |
| Power | Watt, Kilowatt, Megawatt, SolarLuminosity, … |
Use isValidUnit(name) to check at runtime, or see the
units.csv for the full list.
Runnable examples are in the examples/ folder:
| File | What it shows |
|---|---|
| quickstart.mjs | Construction, conversion, convert(), isCompatible() |
| unit_factories.mjs | Factory-style construction, metadata, dynamic unit()/units |
| arithmetic.mjs | .add(), .sub(), .mul(), .div(), velocity with DerivedQuantity |
| astronomy.mjs | Parsec/ly/AU scales, angular measures, solar units |
node examples/quickstart.mjs
node examples/unit_factories.mjs
node examples/arithmetic.mjs
node examples/astronomy.mjs# Prerequisites: Rust toolchain
npm install
npx napi build --release --platform
npm test┌──────────────┐ napi-rs ┌──────────────┐ path dep ┌───────────┐
│ JavaScript │ ◄─────────────► │ qtty-node │ ◄──────────────► │ qtty-ffi │
│ / TypeScript│ native addon │ (Rust crate) │ registry & │ registry │
└──────────────┘ └──────────────┘ conversion └───────────┘
│
┌────┴────┐
│ qtty │
│ (core) │
└─────────┘
All conversion math lives in Rust. The Node layer is a thin wrapper that translates between JavaScript strings/numbers and Rust types.
AGPL-3.0 — see LICENSE.