measurable 1.1.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "measurable",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Convert between units of measurement with custom and built-in systems",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"conversion",
|
|
@@ -49,5 +49,6 @@
|
|
|
49
49
|
"format": "biome format .",
|
|
50
50
|
"typecheck": "tsc --noEmit",
|
|
51
51
|
"test": "vitest run"
|
|
52
|
-
}
|
|
52
|
+
},
|
|
53
|
+
"readme": "# measurable\n\nConvert between units of measurement, with batteries-included common units and\nfirst-class support for defining your own.\n\n- **No drift** — each unit defines a single transform to its dimension's base\n unit; reverse conversions are derived, never stored, so they can't fall out of\n sync.\n- **Free chaining** — any unit converts to any other in the same dimension\n (e.g. `mile → inch`) without you defining every pair.\n- **Affine units** — temperature scales (°C/°F/K) and anything else needing an\n offset, not just a scale factor.\n- **Two orthogonal ideas** — a **dimension** decides what _can_ convert; a\n **measurement system** (metric/imperial/US) is a tag that never gates\n conversion but powers filtering, formatting, and parse disambiguation.\n\n## Installation\n\n```sh\nnpm install measurable\n```\n\n## Entry points\n\nThe package is split into three import paths so the core stays lean:\n\n| Import | What you get |\n| -------------------------- | ------------------------------------------------------------------------- |\n| `measurable` | The building blocks: `Quantity`, `Dimension`, `MeasurementSystem`, `Unit`, errors |\n| `measurable/dimensions` | Predefined dimensions and their units (`length`, `meter`, `volume`, …) |\n| `measurable/systems` | Predefined measurement systems (`metric`, `imperial`, `usCustomary`) |\n\n## Quick start\n\n```ts\nimport { Quantity } from \"measurable\";\nimport { meter, mile, celsius, fahrenheit } from \"measurable/dimensions\";\n\n// Convert: `.to()` returns a Quantity, `.in()` returns a raw number.\nnew Quantity(5, mile).to(meter).magnitude; // 8046.72\nnew Quantity(5, mile).in(meter); // 8046.72\n\n// Affine scales work the same way.\nnew Quantity(100, celsius).in(fahrenheit); // 212\n```\n\n## Concepts\n\n- **`Dimension`** — a kind of measurable quantity (length, volume, mass, …). It\n owns a canonical **base unit** and is where all conversion happens. A unit\n belongs to exactly one dimension.\n- **`Unit`** — a name plus a transform (`toBase` / `fromBase`) into its\n dimension's base unit. Created through a dimension's builder methods.\n- **`Quantity`** — a magnitude paired with a unit (e.g. `5 kilometer`).\n- **`MeasurementSystem`** — a cross-dimension tag (metric/imperial/…). A unit can\n belong to many; membership is optional and never affects whether a conversion\n is allowed.\n\n## Built-in dimensions\n\nImport any dimension or unit from `measurable/dimensions`:\n\n| Dimension | Base | Units (a selection) |\n| ------------- | ---------- | ----------------------------------------------------------------------------------- |\n| `length` | `meter` | `kilometer`, `centimeter`, `millimeter`, `inch`, `foot`, `yard`, `mile` |\n| `volume` | `liter` | `milliliter`, `us*`/`imperial*` `Gallon`/`Quart`/`Pint`/`Gill`/`FluidOunce`, `cup`, `tablespoon`, `teaspoon` |\n| `mass` | `gram` | `kilogram`, `milligram`, `tonne`, `pound`, `ounce`, `stone`, `shortTon`, `longTon` |\n| `time` | `second` | `millisecond`, `minute`, `hour`, `day`, `week` |\n| `temperature` | `kelvin` | `celsius`, `fahrenheit` |\n| `angle` | `radian` | `degree`, `gradian`, `turn` |\n| `force` | `newton` | `kilonewton`, `dyne`, `poundForce`, `kilogramForce` |\n\nThe metric units carry the **full SI prefix ladder** (yotta → yocto), generated for\nyou: `length`, `mass`, `volume`, and `force` get every prefix (so `kilogram`\nitself is just the kilo-prefixed gram), while `time` and `angle` get the\nfractional prefixes only (e.g.\n`millisecond`, `microradian`). So `decimeter`, `hectometer`, `megagram`,\n`kiloliter`, `nanosecond`, etc. are all available and parse from their symbols\n(`dm`, `hm`, `Mg`, `kL`, `ns`, and `µm`/`um` for micro). You can apply the same\nladder to your own dimensions with `definePrefixed` (see below).\n\n## Built-in measurement systems\n\nImport `metric`, `imperial`, or `usCustomary` from `measurable/systems`.\n\n```ts\nimport { foot } from \"measurable/dimensions\";\nimport { imperial, usCustomary, metric } from \"measurable/systems\";\n\nimperial.has(foot); // true — a unit can belong to several systems\nusCustomary.has(foot); // true\nmetric.has(foot); // false\n```\n\n### Listing units in a system\n\n```ts\nimport { length } from \"measurable/dimensions\";\nimport { metric } from \"measurable/systems\";\n\nmetric.in(length).map((u) => u.name); // [\"meter\", \"kilometer\", \"centimeter\", \"millimeter\"]\n```\n\n### Best-fit formatting\n\n`express` re-expresses a quantity in a system's most readable unit (the largest\nunit whose magnitude is still ≥ 1):\n\n```ts\nimport { Quantity } from \"measurable\";\nimport { meter } from \"measurable/dimensions\";\nimport { metric, imperial } from \"measurable/systems\";\n\nmetric.express(new Quantity(5000, meter)); // Quantity(5, kilometer)\nimperial.express(new Quantity(5000, meter)); // Quantity(3.107…, mile)\n```\n\nA `Quantity` also has a `toString()` that renders `\"<magnitude> <unit name>\"`\n(e.g. `new Quantity(5, kilometer).toString()` → `\"5 kilometer\"`), and `round(decimals)`\nto trim the magnitude for display (`new Quantity(1.6213, mile).round(2)` → `1.62 mile`).\n\n## Parsing strings\n\n`Quantity.parse(input, dimension, options?)` reads a string into a `Quantity`.\nCompound inputs are summed and returned in the finest unit present:\n\n```ts\nimport { Quantity } from \"measurable\";\nimport { length, time } from \"measurable/dimensions\";\n\nQuantity.parse(\"1km\", length); // Quantity(1, kilometer)\nQuantity.parse(\"5 hr\", time); // Quantity(5, hour)\nQuantity.parse(\"5hr 20min\", time); // Quantity(320, minute)\n```\n\n### Ambiguous aliases\n\nSome names mean different things in different systems — a US gallon (3.785 L) is\nnot an imperial gallon (4.546 L), and `ton` could be short or long. These are\ndistinct units that share an alias, so an unqualified parse throws; pass a\n`prefer`red system to disambiguate:\n\n```ts\nimport { Quantity, AmbiguousUnitError } from \"measurable\";\nimport { volume, mass } from \"measurable/dimensions\";\nimport { usCustomary, imperial } from \"measurable/systems\";\n\nQuantity.parse(\"1 gallon\", volume); // throws AmbiguousUnitError\nQuantity.parse(\"1 gallon\", volume, { prefer: usCustomary }).unit.name; // \"usGallon\"\nQuantity.parse(\"1 ton\", mass, { prefer: imperial }).unit.name; // \"longTon\"\n```\n\nConversion itself is governed only by the dimension, so cross-system conversions\nalways work regardless of tags:\n\n```ts\nimport { shortTon, tonne } from \"measurable/dimensions\";\n\nnew Quantity(1, shortTon).in(tonne); // 0.90718474\n```\n\n## Arithmetic\n\nQuantities can be combined. `plus`/`minus` take another `Quantity` (converted into\nthe receiver's unit first, so the operands may use different units of the same\ndimension); `times`/`dividedBy` apply a dimensionless scalar, and `negate`/`abs`\ntransform the magnitude. All return a **new** `Quantity` in the receiver's unit and\nleave the operands untouched.\n\n```ts\nimport { Quantity } from \"measurable\";\nimport { kilometer, mile } from \"measurable/dimensions\";\n\nnew Quantity(1, mile).plus(new Quantity(1, kilometer)); // Quantity(1.6213…, mile)\nnew Quantity(1, mile).minus(new Quantity(1, kilometer)); // Quantity(0.3786…, mile)\nnew Quantity(2, mile).times(3); // Quantity(6, mile)\nnew Quantity(6, mile).dividedBy(2); // Quantity(3, mile)\n```\n\nShort aliases are available: **`add`** (`plus`), **`sub`** (`minus`), **`mul`**\n(`times`), **`div`** (`dividedBy`).\n\nCombining different dimensions throws `InvalidConversionError`. Note that adding\n**affine** units (e.g. temperatures) is mathematically defined but physically\nquestionable, since it adds absolute points rather than a difference.\n\n## Ratios\n\n`ratioTo` divides two quantities of the **same dimension** and returns a plain\n(dimensionless) number — _how many of one fit in the other_:\n\n```ts\nimport { Quantity } from \"measurable\";\nimport { liter, milliliter } from \"measurable/dimensions\";\n\n// How many 250 mL servings are in a 2 L bottle?\nnew Quantity(2, liter).ratioTo(new Quantity(250, milliliter)); // 8\n```\n\nThis is different from `.in(unit)`: `.in(milliliter)` only uses the *unit* on the\nright (giving `2000`), whereas `ratioTo` also uses the other quantity's\n**magnitude** (the `250`), so it answers \"how many of *that quantity* fit in this\none.\" It's the inverse of scalar `times` — `b.times(a.ratioTo(b))` reconstructs\n`a`. Comparing different dimensions throws `InvalidConversionError`.\n\n## Comparison\n\n`equals`/`notEquals`/`lessThan`/`greaterThan`/`lessThanOrEqual`/`greaterThanOrEqual`\ncompare two quantities (the other is converted into the receiver's unit first),\nreturning a boolean. Comparing different dimensions throws `InvalidConversionError`.\n\n```ts\nimport { Quantity } from \"measurable\";\nimport { kilometer, meter } from \"measurable/dimensions\";\n\nnew Quantity(1, kilometer).equals(new Quantity(1000, meter)); // true\nnew Quantity(1, meter).lessThan(new Quantity(1, kilometer)); // true\nnew Quantity(1, kilometer).greaterThan(new Quantity(1, meter)); // true\n```\n\nShort aliases: **`eq`** (`equals`), **`ne`** (`notEquals`), **`lt`** (`lessThan`),\n**`gt`** (`greaterThan`), **`lte`** (`lessThanOrEqual`), **`gte`**\n(`greaterThanOrEqual`). Equality is exact, so values differing only by\nfloating-point rounding from a conversion may compare unequal.\n\n`compareTo(other)` returns `-1`, `0`, or `1`, suitable as an `Array#sort`\ncomparator: `quantities.sort((a, b) => a.compareTo(b))`.\n\n## Combining quantities\n\n`Quantity.min`/`max`/`sum` aggregate several quantities at once; `clamp` is an\ninstance method that bounds one quantity to a range. Each converts operands as\nneeded, so mixing dimensions throws `InvalidConversionError`.\n\n```ts\nimport { Quantity } from \"measurable\";\nimport { kilometer, meter } from \"measurable/dimensions\";\n\nconst a = new Quantity(1, kilometer);\nconst b = new Quantity(500, meter);\n\nQuantity.min(a, b); // Quantity(500, meter) — the smaller\nQuantity.max(a, b); // Quantity(1, kilometer) — the larger\nQuantity.sum(a, b); // Quantity(1.5, kilometer) — total, in a's unit\nb.clamp(a, new Quantity(2, kilometer)); // b bounded to [a, 2 km], in b's unit\n```\n\n## Defining your own units\n\nCreate a `Dimension` and add units through its builder methods. `scale` is how\nmany base units make up one of the unit being defined.\n\n```ts\nimport { Dimension, Quantity } from \"measurable\";\n\nconst data = new Dimension(\"data\");\nconst byte = data.base(\"byte\", [\"B\", \"bytes\"]); // the base unit (identity)\nconst kilobyte = data.unit(\"kilobyte\", 1024, [\"KB\"]);\nconst megabyte = data.unit(\"megabyte\", 1024 ** 2, [\"MB\"]);\n\nnew Quantity(2, megabyte).in(kilobyte); // 2048\n```\n\n### Affine units (offset, not just scale)\n\n```ts\nconst temperature = new Dimension(\"temperature\");\nconst kelvin = temperature.base(\"kelvin\", [\"K\"]);\n// value_in_base = value * scale + offset\nconst celsius = temperature.affine(\"celsius\", { scale: 1, offset: 273.15 }, [\"C\"]);\n```\n\n### Fully custom transforms\n\nFor anything non-linear, provide an explicit inverse pair:\n\n```ts\nconst dim = new Dimension(\"custom\");\ndim.base(\"base\");\ndim.custom(\"squared\", {\n toBase: (x) => x * x,\n fromBase: (x) => Math.sqrt(x),\n});\n```\n\n### Generating SI prefixes\n\n`definePrefixed` adds the metric prefix ladder to a reference unit and returns the\ncreated units keyed by name (skipping any name that already exists). Pass\n`SI_SUBMULTIPLE_PREFIXES` to generate fractions only.\n\n```ts\nimport { Dimension, Quantity, definePrefixed } from \"measurable\";\n\nconst data = new Dimension(\"data\");\nconst bit = data.base(\"bit\", [\"b\"]);\nconst prefixed = definePrefixed(data, { name: \"bit\", symbol: \"b\", scale: 1 });\n\nnew Quantity(1, prefixed.kilobit).in(bit); // 1000 (SI kilo = 1e3)\n```\n\n### Tagging units into a measurement system\n\n```ts\nimport { MeasurementSystem } from \"measurable\";\n\nconst si = new MeasurementSystem(\"si\").add(byte, kilobyte, megabyte);\nsi.has(kilobyte); // true\n```\n\n## API reference\n\n### `Dimension`\n\n- `new Dimension(name)`\n- `.base(name, aliases?)` — define the canonical base unit\n- `.unit(name, scale, aliases?)` — linear unit (`scale` base units per unit)\n- `.affine(name, { scale, offset }, aliases?)` — linear with additive offset\n- `.custom(name, { toBase, fromBase }, aliases?)` — arbitrary inverse pair\n- `.convert(value, from, to)` — convert a raw number between two of its units\n- `.get(token)` — units matching a name/alias (`Unit[] | undefined`)\n- `.has(unit)`, `.units`, `.baseUnit`\n\n### `Unit`\n\nA passive handle, normally created via a dimension's builder methods rather than\n`new Unit` directly. Read-only properties:\n\n- `.name` — the unit's canonical name\n- `.dimension` — the `Dimension` it belongs to\n- `.toBase(value)` → `number` — convert a value in this unit to base units\n- `.fromBase(value)` → `number` — convert a value in base units to this unit\n\n### `Quantity`\n\n- `new Quantity(magnitude, unit)`\n- `.to(target)` → `Quantity`\n- `.in(target)` → `number`\n- `.toString()` → `string` — e.g. `\"5 kilometer\"`\n- `.plus(other)` / `.minus(other)` → `Quantity` — add/subtract another quantity (aliases: `add` / `sub`)\n- `.times(factor)` / `.dividedBy(divisor)` → `Quantity` — scale by a number (aliases: `mul` / `div`)\n- `.ratioTo(other)` → `number` — dimensionless ratio (how many of `other` fit in this)\n- `.negate()` / `.abs()` → `Quantity`\n- `.clamp(lower, upper)` → `Quantity` — bound to a range, in this unit\n- `.round(decimals?)` → `Quantity` — round the magnitude (default 0 decimals)\n- `.equals(other)` / `.notEquals(other)` → `boolean` (aliases: `eq` / `ne`)\n- `.lessThan(other)` / `.greaterThan(other)` → `boolean` (aliases: `lt` / `gt`)\n- `.lessThanOrEqual(other)` / `.greaterThanOrEqual(other)` → `boolean` (aliases: `lte` / `gte`)\n- `.compareTo(other)` → `-1 | 0 | 1` — sort comparator\n- `.isZero()` / `.isPositive()` / `.isNegative()` → `boolean`\n- `Quantity.min(...quantities)` / `Quantity.max(...quantities)` / `Quantity.sum(...quantities)` → `Quantity`\n- `Quantity.parse(input, dimension, { prefer? })` → `Quantity`\n\n### `MeasurementSystem`\n\n- `new MeasurementSystem(name)`\n- `.add(...units)`, `.has(unit)`\n- `.in(dimension)` → `Unit[]`\n- `.express(quantity)` → `Quantity`\n\n### Errors\n\n- `InvalidConversionError` — units are from different dimensions\n- `UnknownUnitError` — a parsed token matches no unit\n- `AmbiguousUnitError` — a parsed token matches several units and no `prefer` was given\n\n## License\n\nISC\n"
|
|
53
54
|
}
|