measurable 1.0.0

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.
Files changed (45) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +241 -0
  3. package/dist/dimensions/angle.d.ts +7 -0
  4. package/dist/dimensions/angle.js +10 -0
  5. package/dist/dimensions/force.d.ts +8 -0
  6. package/dist/dimensions/force.js +11 -0
  7. package/dist/dimensions/index.d.ts +7 -0
  8. package/dist/dimensions/index.js +23 -0
  9. package/dist/dimensions/length.d.ts +11 -0
  10. package/dist/dimensions/length.js +14 -0
  11. package/dist/dimensions/mass.d.ts +12 -0
  12. package/dist/dimensions/mass.js +17 -0
  13. package/dist/dimensions/temperature.d.ts +9 -0
  14. package/dist/dimensions/temperature.js +12 -0
  15. package/dist/dimensions/time.d.ts +9 -0
  16. package/dist/dimensions/time.js +12 -0
  17. package/dist/dimensions/volume.d.ts +18 -0
  18. package/dist/dimensions/volume.js +34 -0
  19. package/dist/errors/AmbiguousUnitError.d.ts +4 -0
  20. package/dist/errors/AmbiguousUnitError.js +11 -0
  21. package/dist/errors/InvalidConversionError.d.ts +4 -0
  22. package/dist/errors/InvalidConversionError.js +9 -0
  23. package/dist/errors/UnknownUnitError.d.ts +4 -0
  24. package/dist/errors/UnknownUnitError.js +9 -0
  25. package/dist/index.d.ts +7 -0
  26. package/dist/index.js +23 -0
  27. package/dist/lib/Dimension.d.ts +60 -0
  28. package/dist/lib/Dimension.js +95 -0
  29. package/dist/lib/MeasurementSystem.d.ts +29 -0
  30. package/dist/lib/MeasurementSystem.js +58 -0
  31. package/dist/lib/Quantity.d.ts +32 -0
  32. package/dist/lib/Quantity.js +71 -0
  33. package/dist/lib/Unit.d.ts +35 -0
  34. package/dist/lib/Unit.js +32 -0
  35. package/dist/lib/scale.d.ts +3 -0
  36. package/dist/lib/scale.js +6 -0
  37. package/dist/systems/imperial.d.ts +3 -0
  38. package/dist/systems/imperial.js +18 -0
  39. package/dist/systems/index.d.ts +3 -0
  40. package/dist/systems/index.js +19 -0
  41. package/dist/systems/metric.d.ts +3 -0
  42. package/dist/systems/metric.js +18 -0
  43. package/dist/systems/usCustomary.d.ts +3 -0
  44. package/dist/systems/usCustomary.js +18 -0
  45. package/package.json +53 -0
package/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ ISC License
2
+
3
+ Copyright 2026 Matt Huggins
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,241 @@
1
+ # measurable
2
+
3
+ Convert between units of measurement, with batteries-included common units and
4
+ first-class support for defining your own.
5
+
6
+ - **No drift** — each unit defines a single transform to its dimension's base
7
+ unit; reverse conversions are derived, never stored, so they can't fall out of
8
+ sync.
9
+ - **Free chaining** — any unit converts to any other in the same dimension
10
+ (e.g. `mile → inch`) without you defining every pair.
11
+ - **Affine units** — temperature scales (°C/°F/K) and anything else needing an
12
+ offset, not just a scale factor.
13
+ - **Two orthogonal ideas** — a **dimension** decides what _can_ convert; a
14
+ **measurement system** (metric/imperial/US) is a tag that never gates
15
+ conversion but powers filtering, formatting, and parse disambiguation.
16
+
17
+ ## Installation
18
+
19
+ ```sh
20
+ npm install measurable
21
+ ```
22
+
23
+ ## Entry points
24
+
25
+ The package is split into three import paths so the core stays lean:
26
+
27
+ | Import | What you get |
28
+ | -------------------------- | ------------------------------------------------------------------------- |
29
+ | `measurable` | The building blocks: `Quantity`, `Dimension`, `MeasurementSystem`, `Unit`, errors |
30
+ | `measurable/dimensions` | Predefined dimensions and their units (`length`, `meter`, `volume`, …) |
31
+ | `measurable/systems` | Predefined measurement systems (`metric`, `imperial`, `usCustomary`) |
32
+
33
+ ## Quick start
34
+
35
+ ```ts
36
+ import { Quantity } from "measurable";
37
+ import { meter, mile, celsius, fahrenheit } from "measurable/dimensions";
38
+
39
+ // Convert: `.to()` returns a Quantity, `.in()` returns a raw number.
40
+ new Quantity(5, mile).to(meter).magnitude; // 8046.72
41
+ new Quantity(5, mile).in(meter); // 8046.72
42
+
43
+ // Affine scales work the same way.
44
+ new Quantity(100, celsius).in(fahrenheit); // 212
45
+ ```
46
+
47
+ ## Concepts
48
+
49
+ - **`Dimension`** — a kind of measurable quantity (length, volume, mass, …). It
50
+ owns a canonical **base unit** and is where all conversion happens. A unit
51
+ belongs to exactly one dimension.
52
+ - **`Unit`** — a name plus a transform (`toBase` / `fromBase`) into its
53
+ dimension's base unit. Created through a dimension's builder methods.
54
+ - **`Quantity`** — a magnitude paired with a unit (e.g. `5 kilometer`).
55
+ - **`MeasurementSystem`** — a cross-dimension tag (metric/imperial/…). A unit can
56
+ belong to many; membership is optional and never affects whether a conversion
57
+ is allowed.
58
+
59
+ ## Built-in dimensions
60
+
61
+ Import any dimension or unit from `measurable/dimensions`:
62
+
63
+ | Dimension | Base | Units (a selection) |
64
+ | ------------- | ---------- | ----------------------------------------------------------------------------------- |
65
+ | `length` | `meter` | `kilometer`, `centimeter`, `millimeter`, `inch`, `foot`, `yard`, `mile` |
66
+ | `volume` | `liter` | `milliliter`, `us*`/`imperial*` `Gallon`/`Quart`/`Pint`/`Gill`/`FluidOunce`, `cup`, `tablespoon`, `teaspoon` |
67
+ | `mass` | `kilogram` | `gram`, `milligram`, `tonne`, `pound`, `ounce`, `stone`, `shortTon`, `longTon` |
68
+ | `time` | `second` | `millisecond`, `minute`, `hour`, `day`, `week` |
69
+ | `temperature` | `kelvin` | `celsius`, `fahrenheit` |
70
+ | `angle` | `radian` | `degree`, `gradian`, `turn` |
71
+ | `force` | `newton` | `kilonewton`, `dyne`, `poundForce`, `kilogramForce` |
72
+
73
+ ## Built-in measurement systems
74
+
75
+ Import `metric`, `imperial`, or `usCustomary` from `measurable/systems`.
76
+
77
+ ```ts
78
+ import { foot } from "measurable/dimensions";
79
+ import { imperial, usCustomary, metric } from "measurable/systems";
80
+
81
+ imperial.has(foot); // true — a unit can belong to several systems
82
+ usCustomary.has(foot); // true
83
+ metric.has(foot); // false
84
+ ```
85
+
86
+ ### Listing units in a system
87
+
88
+ ```ts
89
+ import { length } from "measurable/dimensions";
90
+ import { metric } from "measurable/systems";
91
+
92
+ metric.in(length).map((u) => u.name); // ["meter", "kilometer", "centimeter", "millimeter"]
93
+ ```
94
+
95
+ ### Best-fit formatting
96
+
97
+ `express` re-expresses a quantity in a system's most readable unit (the largest
98
+ unit whose magnitude is still ≥ 1):
99
+
100
+ ```ts
101
+ import { Quantity } from "measurable";
102
+ import { meter } from "measurable/dimensions";
103
+ import { metric, imperial } from "measurable/systems";
104
+
105
+ metric.express(new Quantity(5000, meter)); // Quantity(5, kilometer)
106
+ imperial.express(new Quantity(5000, meter)); // Quantity(3.107…, mile)
107
+ ```
108
+
109
+ ## Parsing strings
110
+
111
+ `Quantity.parse(input, dimension, options?)` reads a string into a `Quantity`.
112
+ Compound inputs are summed and returned in the finest unit present:
113
+
114
+ ```ts
115
+ import { Quantity } from "measurable";
116
+ import { length, time } from "measurable/dimensions";
117
+
118
+ Quantity.parse("1km", length); // Quantity(1, kilometer)
119
+ Quantity.parse("5 hr", time); // Quantity(5, hour)
120
+ Quantity.parse("5hr 20min", time); // Quantity(320, minute)
121
+ ```
122
+
123
+ ### Ambiguous aliases
124
+
125
+ Some names mean different things in different systems — a US gallon (3.785 L) is
126
+ not an imperial gallon (4.546 L), and `ton` could be short or long. These are
127
+ distinct units that share an alias, so an unqualified parse throws; pass a
128
+ `prefer`red system to disambiguate:
129
+
130
+ ```ts
131
+ import { Quantity, AmbiguousUnitError } from "measurable";
132
+ import { volume, mass } from "measurable/dimensions";
133
+ import { usCustomary, imperial } from "measurable/systems";
134
+
135
+ Quantity.parse("1 gallon", volume); // throws AmbiguousUnitError
136
+ Quantity.parse("1 gallon", volume, { prefer: usCustomary }).unit.name; // "usGallon"
137
+ Quantity.parse("1 ton", mass, { prefer: imperial }).unit.name; // "longTon"
138
+ ```
139
+
140
+ Conversion itself is governed only by the dimension, so cross-system conversions
141
+ always work regardless of tags:
142
+
143
+ ```ts
144
+ import { shortTon, tonne } from "measurable/dimensions";
145
+
146
+ new Quantity(1, shortTon).in(tonne); // 0.90718474
147
+ ```
148
+
149
+ ## Defining your own units
150
+
151
+ Create a `Dimension` and add units through its builder methods. `scale` is how
152
+ many base units make up one of the unit being defined.
153
+
154
+ ```ts
155
+ import { Dimension, Quantity } from "measurable";
156
+
157
+ const data = new Dimension("data");
158
+ const byte = data.base("byte", ["B", "bytes"]); // the base unit (identity)
159
+ const kilobyte = data.unit("kilobyte", 1024, ["KB"]);
160
+ const megabyte = data.unit("megabyte", 1024 ** 2, ["MB"]);
161
+
162
+ new Quantity(2, megabyte).in(kilobyte); // 2048
163
+ ```
164
+
165
+ ### Affine units (offset, not just scale)
166
+
167
+ ```ts
168
+ const temperature = new Dimension("temperature");
169
+ const kelvin = temperature.base("kelvin", ["K"]);
170
+ // value_in_base = value * scale + offset
171
+ const celsius = temperature.affine("celsius", { scale: 1, offset: 273.15 }, ["C"]);
172
+ ```
173
+
174
+ ### Fully custom transforms
175
+
176
+ For anything non-linear, provide an explicit inverse pair:
177
+
178
+ ```ts
179
+ const dim = new Dimension("custom");
180
+ dim.base("base");
181
+ dim.custom("squared", {
182
+ toBase: (x) => x * x,
183
+ fromBase: (x) => Math.sqrt(x),
184
+ });
185
+ ```
186
+
187
+ ### Tagging units into a measurement system
188
+
189
+ ```ts
190
+ import { MeasurementSystem } from "measurable";
191
+
192
+ const si = new MeasurementSystem("si").add(byte, kilobyte, megabyte);
193
+ si.has(kilobyte); // true
194
+ ```
195
+
196
+ ## API reference
197
+
198
+ ### `Dimension`
199
+
200
+ - `new Dimension(name)`
201
+ - `.base(name, aliases?)` — define the canonical base unit
202
+ - `.unit(name, scale, aliases?)` — linear unit (`scale` base units per unit)
203
+ - `.affine(name, { scale, offset }, aliases?)` — linear with additive offset
204
+ - `.custom(name, { toBase, fromBase }, aliases?)` — arbitrary inverse pair
205
+ - `.convert(value, from, to)` — convert a raw number between two of its units
206
+ - `.get(token)` — units matching a name/alias (`Unit[] | undefined`)
207
+ - `.has(unit)`, `.units`, `.baseUnit`
208
+
209
+ ### `Unit`
210
+
211
+ A passive handle, normally created via a dimension's builder methods rather than
212
+ `new Unit` directly. Read-only properties:
213
+
214
+ - `.name` — the unit's canonical name
215
+ - `.dimension` — the `Dimension` it belongs to
216
+ - `.toBase(value)` → `number` — convert a value in this unit to base units
217
+ - `.fromBase(value)` → `number` — convert a value in base units to this unit
218
+
219
+ ### `Quantity`
220
+
221
+ - `new Quantity(magnitude, unit)`
222
+ - `.to(target)` → `Quantity`
223
+ - `.in(target)` → `number`
224
+ - `Quantity.parse(input, dimension, { prefer? })` → `Quantity`
225
+
226
+ ### `MeasurementSystem`
227
+
228
+ - `new MeasurementSystem(name)`
229
+ - `.add(...units)`, `.has(unit)`
230
+ - `.in(dimension)` → `Unit[]`
231
+ - `.express(quantity)` → `Quantity`
232
+
233
+ ### Errors
234
+
235
+ - `InvalidConversionError` — units are from different dimensions
236
+ - `UnknownUnitError` — a parsed token matches no unit
237
+ - `AmbiguousUnitError` — a parsed token matches several units and no `prefer` was given
238
+
239
+ ## License
240
+
241
+ ISC
@@ -0,0 +1,7 @@
1
+ import { Dimension } from "../lib/Dimension";
2
+ /** Plane angle. Base unit: radian. */
3
+ export declare const angle: Dimension;
4
+ export declare const radian: import("..").Unit;
5
+ export declare const degree: import("..").Unit;
6
+ export declare const gradian: import("..").Unit;
7
+ export declare const turn: import("..").Unit;
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.turn = exports.gradian = exports.degree = exports.radian = exports.angle = void 0;
4
+ const Dimension_1 = require("../lib/Dimension");
5
+ /** Plane angle. Base unit: radian. */
6
+ exports.angle = new Dimension_1.Dimension("angle");
7
+ exports.radian = exports.angle.base("radian", ["rad", "radians"]);
8
+ exports.degree = exports.angle.unit("degree", Math.PI / 180, ["deg", "°", "degrees"]);
9
+ exports.gradian = exports.angle.unit("gradian", Math.PI / 200, ["grad", "gradians"]);
10
+ exports.turn = exports.angle.unit("turn", 2 * Math.PI, ["turns", "revolution", "revolutions"]);
@@ -0,0 +1,8 @@
1
+ import { Dimension } from "../lib/Dimension";
2
+ /** Force. Base unit: newton. */
3
+ export declare const force: Dimension;
4
+ export declare const newton: import("..").Unit;
5
+ export declare const kilonewton: import("..").Unit;
6
+ export declare const dyne: import("..").Unit;
7
+ export declare const poundForce: import("..").Unit;
8
+ export declare const kilogramForce: import("..").Unit;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.kilogramForce = exports.poundForce = exports.dyne = exports.kilonewton = exports.newton = exports.force = void 0;
4
+ const Dimension_1 = require("../lib/Dimension");
5
+ /** Force. Base unit: newton. */
6
+ exports.force = new Dimension_1.Dimension("force");
7
+ exports.newton = exports.force.base("newton", ["N", "newtons"]);
8
+ exports.kilonewton = exports.force.unit("kilonewton", 1000, ["kN", "kilonewtons"]);
9
+ exports.dyne = exports.force.unit("dyne", 0.00001, ["dyn", "dynes"]);
10
+ exports.poundForce = exports.force.unit("poundForce", 4.4482216152605, ["lbf"]);
11
+ exports.kilogramForce = exports.force.unit("kilogramForce", 9.80665, ["kgf"]);
@@ -0,0 +1,7 @@
1
+ export * from "./angle";
2
+ export * from "./force";
3
+ export * from "./length";
4
+ export * from "./mass";
5
+ export * from "./temperature";
6
+ export * from "./time";
7
+ export * from "./volume";
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./angle"), exports);
18
+ __exportStar(require("./force"), exports);
19
+ __exportStar(require("./length"), exports);
20
+ __exportStar(require("./mass"), exports);
21
+ __exportStar(require("./temperature"), exports);
22
+ __exportStar(require("./time"), exports);
23
+ __exportStar(require("./volume"), exports);
@@ -0,0 +1,11 @@
1
+ import { Dimension } from "../lib/Dimension";
2
+ /** Length / distance. Base unit: meter. */
3
+ export declare const length: Dimension;
4
+ export declare const meter: import("..").Unit;
5
+ export declare const kilometer: import("..").Unit;
6
+ export declare const centimeter: import("..").Unit;
7
+ export declare const millimeter: import("..").Unit;
8
+ export declare const inch: import("..").Unit;
9
+ export declare const foot: import("..").Unit;
10
+ export declare const yard: import("..").Unit;
11
+ export declare const mile: import("..").Unit;
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mile = exports.yard = exports.foot = exports.inch = exports.millimeter = exports.centimeter = exports.kilometer = exports.meter = exports.length = void 0;
4
+ const Dimension_1 = require("../lib/Dimension");
5
+ /** Length / distance. Base unit: meter. */
6
+ exports.length = new Dimension_1.Dimension("length");
7
+ exports.meter = exports.length.base("meter", ["m", "meters"]);
8
+ exports.kilometer = exports.length.unit("kilometer", 1000, ["km", "kilometers"]);
9
+ exports.centimeter = exports.length.unit("centimeter", 0.01, ["cm", "centimeters"]);
10
+ exports.millimeter = exports.length.unit("millimeter", 0.001, ["mm", "millimeters"]);
11
+ exports.inch = exports.length.unit("inch", 0.0254, ["in", "inches"]);
12
+ exports.foot = exports.length.unit("foot", 0.3048, ["ft", "feet"]);
13
+ exports.yard = exports.length.unit("yard", 0.9144, ["yd", "yards"]);
14
+ exports.mile = exports.length.unit("mile", 1609.344, ["mi", "miles"]);
@@ -0,0 +1,12 @@
1
+ import { Dimension } from "../lib/Dimension";
2
+ /** Mass / weight. Base unit: kilogram. */
3
+ export declare const mass: Dimension;
4
+ export declare const kilogram: import("..").Unit;
5
+ export declare const gram: import("..").Unit;
6
+ export declare const milligram: import("..").Unit;
7
+ export declare const tonne: import("..").Unit;
8
+ export declare const pound: import("..").Unit;
9
+ export declare const ounce: import("..").Unit;
10
+ export declare const stone: import("..").Unit;
11
+ export declare const shortTon: import("..").Unit;
12
+ export declare const longTon: import("..").Unit;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.longTon = exports.shortTon = exports.stone = exports.ounce = exports.pound = exports.tonne = exports.milligram = exports.gram = exports.kilogram = exports.mass = void 0;
4
+ const Dimension_1 = require("../lib/Dimension");
5
+ /** Mass / weight. Base unit: kilogram. */
6
+ exports.mass = new Dimension_1.Dimension("mass");
7
+ exports.kilogram = exports.mass.base("kilogram", ["kg", "kilograms"]);
8
+ exports.gram = exports.mass.unit("gram", 0.001, ["g", "grams"]);
9
+ exports.milligram = exports.mass.unit("milligram", 0.000001, ["mg", "milligrams"]);
10
+ exports.tonne = exports.mass.unit("tonne", 1000, ["t", "tonnes"]);
11
+ exports.pound = exports.mass.unit("pound", 0.45359237, ["lb", "lbs", "pounds"]);
12
+ exports.ounce = exports.mass.unit("ounce", 0.028349523125, ["oz", "ounces"]);
13
+ exports.stone = exports.mass.unit("stone", 6.35029318, ["st", "stones"]);
14
+ // Short (US) and long (Imperial) tons share the "ton" alias but differ in size;
15
+ // the metric tonne above is a separate unit again. Parsing disambiguates these.
16
+ exports.shortTon = exports.mass.unit("shortTon", 907.18474, ["ton", "tons"]);
17
+ exports.longTon = exports.mass.unit("longTon", 1016.0469088, ["ton", "tons"]);
@@ -0,0 +1,9 @@
1
+ import { Dimension } from "../lib/Dimension";
2
+ /**
3
+ * Temperature. Base unit: kelvin. Uses affine units because Celsius and
4
+ * Fahrenheit are offset from the base, not just scaled.
5
+ */
6
+ export declare const temperature: Dimension;
7
+ export declare const kelvin: import("..").Unit;
8
+ export declare const celsius: import("..").Unit;
9
+ export declare const fahrenheit: import("..").Unit;
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fahrenheit = exports.celsius = exports.kelvin = exports.temperature = void 0;
4
+ const Dimension_1 = require("../lib/Dimension");
5
+ /**
6
+ * Temperature. Base unit: kelvin. Uses affine units because Celsius and
7
+ * Fahrenheit are offset from the base, not just scaled.
8
+ */
9
+ exports.temperature = new Dimension_1.Dimension("temperature");
10
+ exports.kelvin = exports.temperature.base("kelvin", ["K"]);
11
+ exports.celsius = exports.temperature.affine("celsius", { scale: 1, offset: 273.15 }, ["C", "°C"]);
12
+ exports.fahrenheit = exports.temperature.affine("fahrenheit", { scale: 5 / 9, offset: 273.15 - 32 * (5 / 9) }, ["F", "°F"]);
@@ -0,0 +1,9 @@
1
+ import { Dimension } from "../lib/Dimension";
2
+ /** Time / duration. Base unit: second. */
3
+ export declare const time: Dimension;
4
+ export declare const second: import("..").Unit;
5
+ export declare const millisecond: import("..").Unit;
6
+ export declare const minute: import("..").Unit;
7
+ export declare const hour: import("..").Unit;
8
+ export declare const day: import("..").Unit;
9
+ export declare const week: import("..").Unit;
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.week = exports.day = exports.hour = exports.minute = exports.millisecond = exports.second = exports.time = void 0;
4
+ const Dimension_1 = require("../lib/Dimension");
5
+ /** Time / duration. Base unit: second. */
6
+ exports.time = new Dimension_1.Dimension("time");
7
+ exports.second = exports.time.base("second", ["s", "sec", "secs", "seconds"]);
8
+ exports.millisecond = exports.time.unit("millisecond", 0.001, ["ms", "milliseconds"]);
9
+ exports.minute = exports.time.unit("minute", 60, ["min", "mins", "minutes"]);
10
+ exports.hour = exports.time.unit("hour", 3600, ["h", "hr", "hrs", "hours"]);
11
+ exports.day = exports.time.unit("day", 86400, ["d", "days"]);
12
+ exports.week = exports.time.unit("week", 604800, ["wk", "weeks"]);
@@ -0,0 +1,18 @@
1
+ import { Dimension } from "../lib/Dimension";
2
+ /** Volume / capacity. Base unit: liter. */
3
+ export declare const volume: Dimension;
4
+ export declare const liter: import("..").Unit;
5
+ export declare const milliliter: import("..").Unit;
6
+ export declare const usGallon: import("..").Unit;
7
+ export declare const usQuart: import("..").Unit;
8
+ export declare const usPint: import("..").Unit;
9
+ export declare const usGill: import("..").Unit;
10
+ export declare const usFluidOunce: import("..").Unit;
11
+ export declare const imperialGallon: import("..").Unit;
12
+ export declare const imperialQuart: import("..").Unit;
13
+ export declare const imperialPint: import("..").Unit;
14
+ export declare const imperialGill: import("..").Unit;
15
+ export declare const imperialFluidOunce: import("..").Unit;
16
+ export declare const cup: import("..").Unit;
17
+ export declare const tablespoon: import("..").Unit;
18
+ export declare const teaspoon: import("..").Unit;
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.teaspoon = exports.tablespoon = exports.cup = exports.imperialFluidOunce = exports.imperialGill = exports.imperialPint = exports.imperialQuart = exports.imperialGallon = exports.usFluidOunce = exports.usGill = exports.usPint = exports.usQuart = exports.usGallon = exports.milliliter = exports.liter = exports.volume = void 0;
4
+ const Dimension_1 = require("../lib/Dimension");
5
+ /** Volume / capacity. Base unit: liter. */
6
+ exports.volume = new Dimension_1.Dimension("volume");
7
+ exports.liter = exports.volume.base("liter", ["L", "liters"]);
8
+ exports.milliliter = exports.volume.unit("milliliter", 0.001, ["mL", "milliliters"]);
9
+ // US and Imperial liquid measures share names ("gallon", "pint", …) but differ
10
+ // in size, so each is a distinct unit carrying the shared aliases; parsing
11
+ // disambiguates via a preferred measurement system. Imperial has 160 fluid
12
+ // ounces per gallon, US customary has 128.
13
+ exports.usGallon = exports.volume.unit("usGallon", 3.785411784, ["gal", "gallon", "gallons"]);
14
+ exports.usQuart = exports.volume.unit("usQuart", 0.946352946, ["qt", "quart", "quarts"]);
15
+ exports.usPint = exports.volume.unit("usPint", 0.473176473, ["pt", "pint", "pints"]);
16
+ exports.usGill = exports.volume.unit("usGill", 0.11829411825, ["gill", "gills"]);
17
+ exports.usFluidOunce = exports.volume.unit("usFluidOunce", 0.0295735295625, [
18
+ "floz",
19
+ "fluidOunce",
20
+ "fluidOunces",
21
+ ]);
22
+ exports.imperialGallon = exports.volume.unit("imperialGallon", 4.54609, ["gal", "gallon", "gallons"]);
23
+ exports.imperialQuart = exports.volume.unit("imperialQuart", 1.1365225, ["qt", "quart", "quarts"]);
24
+ exports.imperialPint = exports.volume.unit("imperialPint", 0.56826125, ["pt", "pint", "pints"]);
25
+ exports.imperialGill = exports.volume.unit("imperialGill", 0.1420653125, ["gill", "gills"]);
26
+ exports.imperialFluidOunce = exports.volume.unit("imperialFluidOunce", 0.0284130625, [
27
+ "floz",
28
+ "fluidOunce",
29
+ "fluidOunces",
30
+ ]);
31
+ // US-only cooking measures (no competing imperial unit, so left unprefixed).
32
+ exports.cup = exports.volume.unit("cup", 0.2365882365, ["cups"]);
33
+ exports.tablespoon = exports.volume.unit("tablespoon", 0.01478676478125, ["tbsp", "tablespoons"]);
34
+ exports.teaspoon = exports.volume.unit("teaspoon", 0.00492892159375, ["tsp", "teaspoons"]);
@@ -0,0 +1,4 @@
1
+ import type { Unit } from "../lib/Unit";
2
+ export declare class AmbiguousUnitError extends Error {
3
+ constructor(token: string, candidates: Unit[]);
4
+ }
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AmbiguousUnitError = void 0;
4
+ class AmbiguousUnitError extends Error {
5
+ constructor(token, candidates) {
6
+ const names = candidates.map((unit) => unit.name).join(", ");
7
+ super(`Ambiguous unit "${token}": matches ${names}. ` +
8
+ `Pass a preferred measurement system to disambiguate.`);
9
+ }
10
+ }
11
+ exports.AmbiguousUnitError = AmbiguousUnitError;
@@ -0,0 +1,4 @@
1
+ import type { Unit } from "../lib/Unit";
2
+ export declare class InvalidConversionError extends Error {
3
+ constructor(from: Unit, to: Unit);
4
+ }
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.InvalidConversionError = void 0;
4
+ class InvalidConversionError extends Error {
5
+ constructor(from, to) {
6
+ super(`Invalid conversion: ${from.name} to ${to.name}`);
7
+ }
8
+ }
9
+ exports.InvalidConversionError = InvalidConversionError;
@@ -0,0 +1,4 @@
1
+ import type { Dimension } from "../lib/Dimension";
2
+ export declare class UnknownUnitError extends Error {
3
+ constructor(token: string, dimension: Dimension);
4
+ }
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UnknownUnitError = void 0;
4
+ class UnknownUnitError extends Error {
5
+ constructor(token, dimension) {
6
+ super(`Unknown unit "${token}" in dimension "${dimension.name}"`);
7
+ }
8
+ }
9
+ exports.UnknownUnitError = UnknownUnitError;
@@ -0,0 +1,7 @@
1
+ export * from "./errors/AmbiguousUnitError";
2
+ export * from "./errors/InvalidConversionError";
3
+ export * from "./errors/UnknownUnitError";
4
+ export * from "./lib/Dimension";
5
+ export * from "./lib/MeasurementSystem";
6
+ export * from "./lib/Quantity";
7
+ export * from "./lib/Unit";
package/dist/index.js ADDED
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./errors/AmbiguousUnitError"), exports);
18
+ __exportStar(require("./errors/InvalidConversionError"), exports);
19
+ __exportStar(require("./errors/UnknownUnitError"), exports);
20
+ __exportStar(require("./lib/Dimension"), exports);
21
+ __exportStar(require("./lib/MeasurementSystem"), exports);
22
+ __exportStar(require("./lib/Quantity"), exports);
23
+ __exportStar(require("./lib/Unit"), exports);
@@ -0,0 +1,60 @@
1
+ import { Unit } from "./Unit";
2
+ /** A linear unit with an additive offset (e.g. temperature scales). */
3
+ export interface AffineSpec {
4
+ /** Multiplier applied when converting a value to the base unit. */
5
+ scale: number;
6
+ /** Constant added (in base units) after scaling. */
7
+ offset: number;
8
+ }
9
+ /** A fully custom transform pair for non-linear units. */
10
+ export interface CustomSpec {
11
+ toBase: (value: number) => number;
12
+ fromBase: (value: number) => number;
13
+ }
14
+ /**
15
+ * A dimension is a single *kind* of measurable quantity (length, volume, mass,
16
+ * temperature, …). It owns one canonical **base unit** that every other unit in
17
+ * the dimension is defined relative to, and it is the single place where all
18
+ * conversion math happens.
19
+ *
20
+ * Converting `A → B` is always `B.fromBase(A.toBase(value))`: route through the
21
+ * base unit. This gives transitive conversions for free (any unit ↔ any unit)
22
+ * and means each unit only ever stores its relationship to the base — never a
23
+ * redundant, drift-prone pair of factors.
24
+ *
25
+ * A dimension is distinct from a {@link MeasurementSystem} (metric/imperial/…):
26
+ * the dimension decides what *can convert*, while a measurement system is a tag
27
+ * that never participates in conversion.
28
+ */
29
+ export declare class Dimension {
30
+ readonly name: string;
31
+ readonly units: Set<Unit>;
32
+ /**
33
+ * Lookup from every name/alias to its candidate units, used by parsing. A
34
+ * token can map to more than one unit (e.g. "ton" → short ton & long ton);
35
+ * the caller disambiguates with a preferred measurement system.
36
+ */
37
+ private readonly index;
38
+ /** The canonical unit all others convert through. Set by {@link base}. */
39
+ baseUnit?: Unit;
40
+ constructor(name: string);
41
+ /** Define the canonical base unit (identity transform). */
42
+ base(name: string, aliases?: string[]): Unit;
43
+ /**
44
+ * Define a linear unit. `scale` is how many base units make up one of this
45
+ * unit (e.g. a kilometer is `1000` meters).
46
+ */
47
+ unit(name: string, scale: number, aliases?: string[]): Unit;
48
+ /** Define an affine unit (scale plus additive offset, e.g. °C against K). */
49
+ affine(name: string, { scale, offset }: AffineSpec, aliases?: string[]): Unit;
50
+ /** Define a unit with an arbitrary, hand-written inverse transform pair. */
51
+ custom(name: string, { toBase, fromBase }: CustomSpec, aliases?: string[]): Unit;
52
+ /** Convert a value between two units of this dimension, routed through the base. */
53
+ convert(value: number, from: Unit, to: Unit): number;
54
+ /** Resolve a name or alias to its candidate unit(s) (used by parsing). */
55
+ get(token: string): Unit[] | undefined;
56
+ has(unit: Unit): boolean;
57
+ private define;
58
+ /** Append a name/alias → unit mapping; shared aliases accumulate candidates. */
59
+ private register;
60
+ }
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Dimension = void 0;
4
+ const InvalidConversionError_1 = require("../errors/InvalidConversionError");
5
+ const Unit_1 = require("./Unit");
6
+ /**
7
+ * A dimension is a single *kind* of measurable quantity (length, volume, mass,
8
+ * temperature, …). It owns one canonical **base unit** that every other unit in
9
+ * the dimension is defined relative to, and it is the single place where all
10
+ * conversion math happens.
11
+ *
12
+ * Converting `A → B` is always `B.fromBase(A.toBase(value))`: route through the
13
+ * base unit. This gives transitive conversions for free (any unit ↔ any unit)
14
+ * and means each unit only ever stores its relationship to the base — never a
15
+ * redundant, drift-prone pair of factors.
16
+ *
17
+ * A dimension is distinct from a {@link MeasurementSystem} (metric/imperial/…):
18
+ * the dimension decides what *can convert*, while a measurement system is a tag
19
+ * that never participates in conversion.
20
+ */
21
+ class Dimension {
22
+ constructor(name) {
23
+ this.name = name;
24
+ this.units = new Set();
25
+ /**
26
+ * Lookup from every name/alias to its candidate units, used by parsing. A
27
+ * token can map to more than one unit (e.g. "ton" → short ton & long ton);
28
+ * the caller disambiguates with a preferred measurement system.
29
+ */
30
+ this.index = new Map();
31
+ }
32
+ /** Define the canonical base unit (identity transform). */
33
+ base(name, aliases = []) {
34
+ const unit = this.define(name, (x) => x, (x) => x, aliases);
35
+ this.baseUnit = unit;
36
+ return unit;
37
+ }
38
+ /**
39
+ * Define a linear unit. `scale` is how many base units make up one of this
40
+ * unit (e.g. a kilometer is `1000` meters).
41
+ */
42
+ unit(name, scale, aliases = []) {
43
+ return this.define(name, (x) => x * scale, (x) => x / scale, aliases);
44
+ }
45
+ /** Define an affine unit (scale plus additive offset, e.g. °C against K). */
46
+ affine(name, { scale, offset }, aliases = []) {
47
+ return this.define(name, (x) => x * scale + offset, (x) => (x - offset) / scale, aliases);
48
+ }
49
+ /** Define a unit with an arbitrary, hand-written inverse transform pair. */
50
+ custom(name, { toBase, fromBase }, aliases = []) {
51
+ return this.define(name, toBase, fromBase, aliases);
52
+ }
53
+ /** Convert a value between two units of this dimension, routed through the base. */
54
+ convert(value, from, to) {
55
+ if (!this.units.has(from) || !this.units.has(to)) {
56
+ throw new InvalidConversionError_1.InvalidConversionError(from, to);
57
+ }
58
+ if (from === to) {
59
+ return value;
60
+ }
61
+ return to.fromBase(from.toBase(value));
62
+ }
63
+ /** Resolve a name or alias to its candidate unit(s) (used by parsing). */
64
+ get(token) {
65
+ return this.index.get(token);
66
+ }
67
+ has(unit) {
68
+ return this.units.has(unit);
69
+ }
70
+ define(name, toBase, fromBase, aliases) {
71
+ for (const existing of this.units) {
72
+ if (existing.name === name) {
73
+ throw new Error(`Duplicate unit name "${name}" in dimension "${this.name}"`);
74
+ }
75
+ }
76
+ const unit = new Unit_1.Unit({ name, dimension: this, toBase, fromBase });
77
+ this.units.add(unit);
78
+ this.register(name, unit);
79
+ for (const alias of aliases) {
80
+ this.register(alias, unit);
81
+ }
82
+ return unit;
83
+ }
84
+ /** Append a name/alias → unit mapping; shared aliases accumulate candidates. */
85
+ register(token, unit) {
86
+ const candidates = this.index.get(token);
87
+ if (candidates) {
88
+ candidates.push(unit);
89
+ }
90
+ else {
91
+ this.index.set(token, [unit]);
92
+ }
93
+ }
94
+ }
95
+ exports.Dimension = Dimension;
@@ -0,0 +1,29 @@
1
+ import type { Dimension } from "./Dimension";
2
+ import { Quantity } from "./Quantity";
3
+ import type { Unit } from "./Unit";
4
+ /**
5
+ * A measurement system (metric, imperial, US customary, …) is a cross-dimension
6
+ * collection of units that share a real-world standard.
7
+ *
8
+ * It is purely additive metadata: it never participates in conversion (that is
9
+ * the job of {@link Dimension}). A unit may belong to several measurement
10
+ * systems — e.g. `foot` is both imperial and US customary — and membership is
11
+ * optional, so untagged units still convert normally; they simply won't appear
12
+ * under any standard. Membership lives here, the single source of truth, rather
13
+ * than on the {@link Unit}.
14
+ */
15
+ export declare class MeasurementSystem {
16
+ readonly name: string;
17
+ readonly units: Set<Unit>;
18
+ constructor(name: string);
19
+ add(...units: Unit[]): this;
20
+ has(unit: Unit): boolean;
21
+ /** Units of a given dimension that belong to this measurement system. */
22
+ in(dimension: Dimension): Unit[];
23
+ /**
24
+ * Re-express a quantity in this system's best-fit unit for its dimension:
25
+ * the largest unit whose absolute magnitude is still at least 1 (falling back
26
+ * to the smallest unit when even that rounds below 1).
27
+ */
28
+ express(quantity: Quantity): Quantity;
29
+ }
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MeasurementSystem = void 0;
4
+ const scale_1 = require("./scale");
5
+ /**
6
+ * A measurement system (metric, imperial, US customary, …) is a cross-dimension
7
+ * collection of units that share a real-world standard.
8
+ *
9
+ * It is purely additive metadata: it never participates in conversion (that is
10
+ * the job of {@link Dimension}). A unit may belong to several measurement
11
+ * systems — e.g. `foot` is both imperial and US customary — and membership is
12
+ * optional, so untagged units still convert normally; they simply won't appear
13
+ * under any standard. Membership lives here, the single source of truth, rather
14
+ * than on the {@link Unit}.
15
+ */
16
+ class MeasurementSystem {
17
+ constructor(name) {
18
+ this.name = name;
19
+ this.units = new Set();
20
+ }
21
+ add(...units) {
22
+ for (const unit of units) {
23
+ this.units.add(unit);
24
+ }
25
+ return this;
26
+ }
27
+ has(unit) {
28
+ return this.units.has(unit);
29
+ }
30
+ /** Units of a given dimension that belong to this measurement system. */
31
+ in(dimension) {
32
+ return [...this.units].filter((unit) => unit.dimension === dimension);
33
+ }
34
+ /**
35
+ * Re-express a quantity in this system's best-fit unit for its dimension:
36
+ * the largest unit whose absolute magnitude is still at least 1 (falling back
37
+ * to the smallest unit when even that rounds below 1).
38
+ */
39
+ express(quantity) {
40
+ const candidates = this.in(quantity.unit.dimension).sort((a, b) => (0, scale_1.scaleOf)(a) - (0, scale_1.scaleOf)(b));
41
+ if (candidates.length === 0) {
42
+ throw new Error(`Measurement system "${this.name}" has no ` +
43
+ `"${quantity.unit.dimension.name}" units to express in`);
44
+ }
45
+ const base = quantity.unit.toBase(quantity.magnitude);
46
+ let chosen = candidates[0];
47
+ for (const unit of candidates) {
48
+ if (Math.abs(unit.fromBase(base)) >= 1) {
49
+ chosen = unit;
50
+ }
51
+ else {
52
+ break;
53
+ }
54
+ }
55
+ return quantity.to(chosen);
56
+ }
57
+ }
58
+ exports.MeasurementSystem = MeasurementSystem;
@@ -0,0 +1,32 @@
1
+ import type { Dimension } from "./Dimension";
2
+ import type { MeasurementSystem } from "./MeasurementSystem";
3
+ import type { Unit } from "./Unit";
4
+ /** Options for {@link Quantity.parse}. */
5
+ export interface ParseOptions {
6
+ /** Preferred measurement system, used only to break ties on shared aliases. */
7
+ prefer?: MeasurementSystem;
8
+ }
9
+ /** A magnitude paired with a unit (e.g. `5` `kilometer`). */
10
+ export declare class Quantity {
11
+ magnitude: number;
12
+ unit: Unit;
13
+ constructor(magnitude: number, unit: Unit);
14
+ /** Return an equivalent quantity expressed in `target`. */
15
+ to(target: Unit): Quantity;
16
+ /** Return this quantity's raw magnitude expressed in `target`. */
17
+ in(target: Unit): number;
18
+ /**
19
+ * Parse a string into a `Quantity` using a dimension's known units and aliases.
20
+ *
21
+ * - `"1km"` -> `Quantity(1, kilometer)`
22
+ * - `"5 hr"` -> `Quantity(5, hour)`
23
+ * - `"5hr 20min"` -> `Quantity(320, minute)`
24
+ *
25
+ * Compound inputs are summed in base units and returned in the *finest*
26
+ * (smallest-scale) unit present, so `"5hr 20min"` collapses to `320 minute`.
27
+ *
28
+ * When a token is a shared alias (e.g. `"ton"` → short ton & long ton), pass
29
+ * `options.prefer` to pick the candidate belonging to that measurement system.
30
+ */
31
+ static parse(str: string, dimension: Dimension, options?: ParseOptions): Quantity;
32
+ }
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Quantity = void 0;
4
+ const AmbiguousUnitError_1 = require("../errors/AmbiguousUnitError");
5
+ const UnknownUnitError_1 = require("../errors/UnknownUnitError");
6
+ const scale_1 = require("./scale");
7
+ /** A magnitude paired with a unit (e.g. `5` `kilometer`). */
8
+ class Quantity {
9
+ constructor(magnitude, unit) {
10
+ this.magnitude = magnitude;
11
+ this.unit = unit;
12
+ }
13
+ /** Return an equivalent quantity expressed in `target`. */
14
+ to(target) {
15
+ return new Quantity(this.in(target), target);
16
+ }
17
+ /** Return this quantity's raw magnitude expressed in `target`. */
18
+ in(target) {
19
+ return this.unit.dimension.convert(this.magnitude, this.unit, target);
20
+ }
21
+ /**
22
+ * Parse a string into a `Quantity` using a dimension's known units and aliases.
23
+ *
24
+ * - `"1km"` -> `Quantity(1, kilometer)`
25
+ * - `"5 hr"` -> `Quantity(5, hour)`
26
+ * - `"5hr 20min"` -> `Quantity(320, minute)`
27
+ *
28
+ * Compound inputs are summed in base units and returned in the *finest*
29
+ * (smallest-scale) unit present, so `"5hr 20min"` collapses to `320 minute`.
30
+ *
31
+ * When a token is a shared alias (e.g. `"ton"` → short ton & long ton), pass
32
+ * `options.prefer` to pick the candidate belonging to that measurement system.
33
+ */
34
+ static parse(str, dimension, options = {}) {
35
+ const pattern = /(-?\d+(?:\.\d+)?)\s*([^\d\s]+)/g;
36
+ let total = 0; // accumulated in base units
37
+ let finest;
38
+ let count = 0;
39
+ for (let match = pattern.exec(str); match !== null; match = pattern.exec(str)) {
40
+ const value = Number.parseFloat(match[1]);
41
+ const unit = resolve(match[2], dimension, options.prefer);
42
+ total += unit.toBase(value);
43
+ if (!finest || (0, scale_1.scaleOf)(unit) < (0, scale_1.scaleOf)(finest)) {
44
+ finest = unit;
45
+ }
46
+ count += 1;
47
+ }
48
+ if (count === 0 || !finest) {
49
+ throw new Error(`Could not parse a quantity from "${str}"`);
50
+ }
51
+ return new Quantity(finest.fromBase(total), finest);
52
+ }
53
+ }
54
+ exports.Quantity = Quantity;
55
+ /** Resolve a token to a single unit, disambiguating shared aliases by system. */
56
+ function resolve(token, dimension, prefer) {
57
+ const candidates = dimension.get(token);
58
+ if (!candidates || candidates.length === 0) {
59
+ throw new UnknownUnitError_1.UnknownUnitError(token, dimension);
60
+ }
61
+ if (candidates.length === 1) {
62
+ return candidates[0];
63
+ }
64
+ if (prefer) {
65
+ const matches = candidates.filter((unit) => prefer.has(unit));
66
+ if (matches.length === 1) {
67
+ return matches[0];
68
+ }
69
+ }
70
+ throw new AmbiguousUnitError_1.AmbiguousUnitError(token, candidates);
71
+ }
@@ -0,0 +1,35 @@
1
+ import type { Dimension } from "./Dimension";
2
+ interface UnitOptions {
3
+ name: string;
4
+ dimension: Dimension;
5
+ toBase: (value: number) => number;
6
+ fromBase: (value: number) => number;
7
+ }
8
+ /**
9
+ * A single unit of measurement (e.g. "meter", "celsius").
10
+ *
11
+ * A `Unit` is a passive handle: it knows its name, its home {@link Dimension},
12
+ * and how to transform a value to and from that dimension's canonical base
13
+ * unit. It does NOT know about other units or store pairwise conversions — all
14
+ * conversion math lives in {@link Dimension}, derived from these two transforms.
15
+ * Because the reverse direction (`fromBase`) is the mathematical inverse of the
16
+ * forward direction (`toBase`), the two can never fall out of sync.
17
+ *
18
+ * A unit belongs to exactly one dimension, but may belong to many
19
+ * {@link MeasurementSystem}s (metric/imperial/…); that membership lives on the
20
+ * measurement systems, not here, so a `Unit` stays a lean descriptor.
21
+ *
22
+ * Names and aliases live solely in the dimension's lookup index; they are
23
+ * declared once when the unit is defined.
24
+ *
25
+ * Units are normally created through a {@link Dimension}'s builder methods
26
+ * (`base`, `unit`, `affine`, `custom`) rather than constructed directly.
27
+ */
28
+ export declare class Unit {
29
+ readonly name: string;
30
+ readonly dimension: Dimension;
31
+ readonly toBase: (value: number) => number;
32
+ readonly fromBase: (value: number) => number;
33
+ constructor({ name, dimension, toBase, fromBase }: UnitOptions);
34
+ }
35
+ export {};
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Unit = void 0;
4
+ /**
5
+ * A single unit of measurement (e.g. "meter", "celsius").
6
+ *
7
+ * A `Unit` is a passive handle: it knows its name, its home {@link Dimension},
8
+ * and how to transform a value to and from that dimension's canonical base
9
+ * unit. It does NOT know about other units or store pairwise conversions — all
10
+ * conversion math lives in {@link Dimension}, derived from these two transforms.
11
+ * Because the reverse direction (`fromBase`) is the mathematical inverse of the
12
+ * forward direction (`toBase`), the two can never fall out of sync.
13
+ *
14
+ * A unit belongs to exactly one dimension, but may belong to many
15
+ * {@link MeasurementSystem}s (metric/imperial/…); that membership lives on the
16
+ * measurement systems, not here, so a `Unit` stays a lean descriptor.
17
+ *
18
+ * Names and aliases live solely in the dimension's lookup index; they are
19
+ * declared once when the unit is defined.
20
+ *
21
+ * Units are normally created through a {@link Dimension}'s builder methods
22
+ * (`base`, `unit`, `affine`, `custom`) rather than constructed directly.
23
+ */
24
+ class Unit {
25
+ constructor({ name, dimension, toBase, fromBase }) {
26
+ this.name = name;
27
+ this.dimension = dimension;
28
+ this.toBase = toBase;
29
+ this.fromBase = fromBase;
30
+ }
31
+ }
32
+ exports.Unit = Unit;
@@ -0,0 +1,3 @@
1
+ import type { Unit } from "./Unit";
2
+ /** Pure linear scale of a unit relative to base, ignoring any affine offset. */
3
+ export declare const scaleOf: (unit: Unit) => number;
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.scaleOf = void 0;
4
+ /** Pure linear scale of a unit relative to base, ignoring any affine offset. */
5
+ const scaleOf = (unit) => unit.toBase(1) - unit.toBase(0);
6
+ exports.scaleOf = scaleOf;
@@ -0,0 +1,3 @@
1
+ import { MeasurementSystem } from "../lib/MeasurementSystem";
2
+ /** The British imperial standard. */
3
+ export declare const imperial: MeasurementSystem;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.imperial = void 0;
4
+ const length_1 = require("../dimensions/length");
5
+ const mass_1 = require("../dimensions/mass");
6
+ const temperature_1 = require("../dimensions/temperature");
7
+ const volume_1 = require("../dimensions/volume");
8
+ const MeasurementSystem_1 = require("../lib/MeasurementSystem");
9
+ /** The British imperial standard. */
10
+ exports.imperial = new MeasurementSystem_1.MeasurementSystem("imperial").add(
11
+ // length
12
+ length_1.inch, length_1.foot, length_1.yard, length_1.mile,
13
+ // mass
14
+ mass_1.pound, mass_1.ounce, mass_1.stone, mass_1.longTon,
15
+ // volume
16
+ volume_1.imperialGallon, volume_1.imperialQuart, volume_1.imperialPint, volume_1.imperialGill, volume_1.imperialFluidOunce,
17
+ // temperature
18
+ temperature_1.fahrenheit);
@@ -0,0 +1,3 @@
1
+ export * from "./imperial";
2
+ export * from "./metric";
3
+ export * from "./usCustomary";
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./imperial"), exports);
18
+ __exportStar(require("./metric"), exports);
19
+ __exportStar(require("./usCustomary"), exports);
@@ -0,0 +1,3 @@
1
+ import { MeasurementSystem } from "../lib/MeasurementSystem";
2
+ /** The metric / SI standard. */
3
+ export declare const metric: MeasurementSystem;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.metric = void 0;
4
+ const length_1 = require("../dimensions/length");
5
+ const mass_1 = require("../dimensions/mass");
6
+ const temperature_1 = require("../dimensions/temperature");
7
+ const volume_1 = require("../dimensions/volume");
8
+ const MeasurementSystem_1 = require("../lib/MeasurementSystem");
9
+ /** The metric / SI standard. */
10
+ exports.metric = new MeasurementSystem_1.MeasurementSystem("metric").add(
11
+ // length
12
+ length_1.meter, length_1.kilometer, length_1.centimeter, length_1.millimeter,
13
+ // mass
14
+ mass_1.kilogram, mass_1.gram, mass_1.milligram, mass_1.tonne,
15
+ // volume
16
+ volume_1.liter, volume_1.milliliter,
17
+ // temperature
18
+ temperature_1.kelvin, temperature_1.celsius);
@@ -0,0 +1,3 @@
1
+ import { MeasurementSystem } from "../lib/MeasurementSystem";
2
+ /** The US customary standard. Shares foot/pound/ounce with imperial. */
3
+ export declare const usCustomary: MeasurementSystem;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.usCustomary = void 0;
4
+ const length_1 = require("../dimensions/length");
5
+ const mass_1 = require("../dimensions/mass");
6
+ const temperature_1 = require("../dimensions/temperature");
7
+ const volume_1 = require("../dimensions/volume");
8
+ const MeasurementSystem_1 = require("../lib/MeasurementSystem");
9
+ /** The US customary standard. Shares foot/pound/ounce with imperial. */
10
+ exports.usCustomary = new MeasurementSystem_1.MeasurementSystem("usCustomary").add(
11
+ // length
12
+ length_1.inch, length_1.foot, length_1.yard, length_1.mile,
13
+ // mass
14
+ mass_1.pound, mass_1.ounce, mass_1.shortTon,
15
+ // volume
16
+ volume_1.usGallon, volume_1.usQuart, volume_1.usPint, volume_1.usGill, volume_1.usFluidOunce, volume_1.cup, volume_1.tablespoon, volume_1.teaspoon,
17
+ // temperature
18
+ temperature_1.fahrenheit);
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "measurable",
3
+ "version": "1.0.0",
4
+ "description": "Convert between units of measurement with custom and built-in systems",
5
+ "keywords": [
6
+ "conversion",
7
+ "convert",
8
+ "magnitude",
9
+ "measure",
10
+ "measurement",
11
+ "measurements",
12
+ "quantities",
13
+ "quantity",
14
+ "unit",
15
+ "units"
16
+ ],
17
+ "author": "Matt Huggins <matt.huggins@gmail.com>",
18
+ "license": "ISC",
19
+ "repository": "https://github.com/mhuggins/measurable",
20
+ "main": "dist/index.js",
21
+ "types": "dist/index.d.ts",
22
+ "exports": {
23
+ ".": {
24
+ "types": "./dist/index.d.ts",
25
+ "default": "./dist/index.js"
26
+ },
27
+ "./dimensions": {
28
+ "types": "./dist/dimensions/index.d.ts",
29
+ "default": "./dist/dimensions/index.js"
30
+ },
31
+ "./systems": {
32
+ "types": "./dist/systems/index.d.ts",
33
+ "default": "./dist/systems/index.js"
34
+ }
35
+ },
36
+ "files": [
37
+ "dist"
38
+ ],
39
+ "devDependencies": {
40
+ "@biomejs/biome": "^2.5.0",
41
+ "typescript": "^6.0.3",
42
+ "vite": "^8.0.16",
43
+ "vitest": "^4.1.9"
44
+ },
45
+ "scripts": {
46
+ "prebuild": "pnpm run lint && pnpm run format && pnpm run typecheck && pnpm run test",
47
+ "build": "tsc",
48
+ "lint": "biome check .",
49
+ "format": "biome format .",
50
+ "typecheck": "tsc --noEmit",
51
+ "test": "vitest run"
52
+ }
53
+ }