measure-convert
Version:
JS/TS package for managing units of measurement. Convert, add, subtract, multiply, divide, and compare units of measurement.
869 lines (640 loc) • 30 kB
Markdown
# Unit Conversion Library
A robust and comprehensive TypeScript library for handling a wide array of measurement units and performing precise unit conversions. Whether you're working on scientific computations, engineering applications, or everyday unit conversions, this library provides the tools you need with accuracy and ease.
## Table of Contents
- [Features](#features)
- [Supported Measurement Types](#supported-measurement-types)
- [Installation](#installation)
- [Usage](#usage)
- [Basic Conversion](#basic-conversion)
- [Formatting with Decimal Places](#formatting-with-decimal-places)
- [International Number Formatting (Locale Support)](#international-number-formatting-locale-support)
- [Duration Formatting and Components](#duration-formatting-and-components)
- [Adding and Subtracting Measurements](#adding-and-subtracting-measurements)
- [Density-Based Conversions Between Mass and Volume](#density-based-conversions-between-mass-and-volume)
- [Checking Proximity with `closeTo`](#checking-proximity-with-closeto)
- [Database Serialization and Hydration](#database-serialization-and-hydration)
- [Compact Database Format](#compact-database-format)
- [Quick Reference: Unit Types and Common Units](#quick-reference-unit-types-and-common-units)
- [API Reference](#api-reference)
- [Measurement Class](#measurement-class)
- [Unit Classes](#unit-classes)
- [Examples](#examples)
- [Testing](#testing)
- [Contributing](#contributing)
- [License](#license)
## Features
- **Wide Range of Units:** Supports over 20 different measurement types, including Temperature, Speed, Length, Mass, Volume, and more.
- **Accurate Conversions:** Utilizes precise conversion factors to ensure minimal rounding errors.
- **Intelligent Formatting:** Smart decimal place handling with customizable precision control.
- **International Locale Support:** Format numbers with locale-specific thousand separators and decimal marks (US, EU, Swiss, Indian, Arabic, and more).
- **Duration Formatting:** Special formatting for time durations with multiple display options (colon notation, labeled format) and type-safe component extraction.
- **Scaling & Ratios:** Calculate ratios between measurements and scale values for nutrition calculations and recipe scaling.
- **Database Integration:** Seamless serialization and hydration for database storage (MongoDB, Parse Server, etc.) with compact format option.
- **Extensible Design:** Easily add new units and measurement types as needed.
- **Comprehensive Testing:** Ensures reliability with thorough unit tests covering all functionalities.
- **TypeScript Support:** Leverages TypeScript for type safety and enhanced developer experience.
## Supported Measurement Types
Your library currently supports the following measurement types:
- **Acceleration** (`UnitAcceleration`)
- **Angle** (`UnitAngle`)
- **Area** (`UnitArea`)
- **Concentration (Mass)** (`UnitConcentrationMass`)
- **Dispersion** (`UnitDispersion`)
- **Duration** (`UnitDuration`)
- **Electric Charge** (`UnitElectricCharge`)
- **Electric Current** (`UnitElectricCurrent`)
- **Electric Potential Difference** (`UnitElectricPotentialDifference`)
- **Electric Resistance** (`UnitElectricResistance`)
- **Energy** (`UnitEnergy`)
- **Frequency** (`UnitFrequency`)
- **Fuel Efficiency** (`UnitFuelEfficiency`)
- **Illuminance** (`UnitIlluminance`)
- **Information Storage** (`UnitInformationStorage`)
- **Length** (`UnitLength`)
- **Mass** (`UnitMass`)
- **Power** (`UnitPower`)
- **Pressure** (`UnitPressure`)
- **Speed** (`UnitSpeed`)
- **Temperature** (`UnitTemperature`)
- **Volume** (`UnitVolume`)
Each measurement type comes with its own set of units, enabling precise and context-specific conversions.
## Installation
Install the library via npm:
```bash
npm install measure-convert
```
Or using Yarn:
```bash
yarn add measure-convert
```
## Usage
### Basic Conversion
To perform a basic unit conversion, create a `Measurement` instance with a value and its corresponding unit, then convert it to the desired unit.
```typescript
import { Measurement, UnitTemperature } from 'measure-convert';
// Convert 100°C to Fahrenheit
const tempCelsius = new Measurement(100, UnitTemperature.celsius);
const tempFahrenheit = tempCelsius.converted(UnitTemperature.fahrenheit);
console.log(`${tempCelsius.value}°C is equal to ${tempFahrenheit.value}°F`);
```
**Output:**
```100°C is equal to 212°F```
### Formatting with Decimal Places
Control how values are displayed with intelligent decimal formatting:
```typescript
import { Measurement, UnitMass } from 'measure-convert';
const weight = new Measurement(4.0000000000123, UnitMass.grams);
// Intelligent defaults (0 decimals for values ≥100, 2 for 1-10, etc.)
console.log(weight.shortLabel); // "4 g"
// Force specific decimal places
console.log(weight.getShortLabel(2)); // "4.00 g"
// Smart rounding avoids showing 0 for significant values
const smallWeight = new Measurement(0.5, UnitMass.grams);
console.log(smallWeight.getShortLabel(0)); // "0.5 g" (not "0 g")
// Show approximation for very small values
const tinyWeight = new Measurement(0.09, UnitMass.grams);
console.log(tinyWeight.getShortLabel(0, true)); // "~0 g"
```
### International Number Formatting (Locale Support)
Format measurements with locale-specific thousand separators and decimal marks:
```typescript
import { Measurement, UnitMass, UnitEnergy } from 'measure-convert';
const weight = new Measurement(1234567.89, UnitMass.grams);
const energy = new Measurement(1234.56, UnitEnergy.kilocalories);
// US/UK Format: comma for thousands, period for decimals
console.log(weight.getShortLabel(2, false, false, 'US')); // "1,234,567.89 g"
console.log(energy.getShortLabel(2, false, false, 'UK')); // "1,234.56 kCal"
// European Format (modern): space for thousands, comma for decimals
console.log(weight.getShortLabel(2, false, false, 'EU_SPACE')); // "1 234 567,89 g"
// European Format (older): period for thousands, comma for decimals
console.log(weight.getShortLabel(2, false, false, 'EU')); // "1.234.567,89 g"
// Swiss Format: apostrophe for thousands, period for decimals
console.log(weight.getShortLabel(2, false, false, 'CH')); // "1'234'567.89 g"
// Scientific (SI): thin space for thousands, period for decimals
console.log(weight.getShortLabel(2, false, false, 'SI')); // "1 234 567.89 g"
// Indian Numbering: groups of 2 after first 3 digits
const indianWeight = new Measurement(1234567.89, UnitMass.grams);
console.log(indianWeight.getShortLabel(2, false, false, 'IN')); // "12,34,567.89 g"
// Arabic: Arabic-Indic numerals with Arabic separators
console.log(weight.getShortLabel(2, false, false, 'AR')); // "١٬٢٣٤٬٥٦٧٫٨٩ g"
// Default (no formatting): no thousand separators
console.log(weight.getShortLabel(2)); // "1234567.89 g"
```
**Available Locale Formats:**
- `'NONE'` - No thousand separators (default for backward compatibility)
- `'US'` / `'UK'` - 1,234,567.89 (comma thousands, period decimal)
- `'EU'` - 1.234.567,89 (period thousands, comma decimal - older European style)
- `'EU_SPACE'` - 1 234 567,89 (space thousands, comma decimal - modern EU standard)
- `'SI'` - 1 234 567.89 (thin space thousands, period decimal - scientific)
- `'CH'` - 1'234'567.89 (apostrophe thousands, period decimal - Swiss)
- `'IN'` - 12,34,567.89 (Indian numbering system)
- `'AR'` - ١٬٢٣٤٬٥٦٧٫٨٩ (Arabic-Indic numerals with Arabic separators)
### Duration Formatting and Components
The library provides special formatting and component extraction for duration measurements:
#### Getting Duration Components
Extract hours, minutes, and seconds from any duration measurement (type-safe, only available on `Measurement<UnitDuration>`):
```typescript
import { Measurement, UnitDuration } from 'measure-convert';
// Get duration parts from any duration unit
const duration = new Measurement(480, UnitDuration.minutes);
const parts = duration.getDurationParts();
console.log(parts); // { hours: 8, minutes: 0, seconds: 0 }
const complex = new Measurement(3661, UnitDuration.seconds);
const complexParts = complex.getDurationParts();
console.log(complexParts); // { hours: 1, minutes: 1, seconds: 1 }
// Type safety: getDurationParts() only exists on duration measurements
const mass = new Measurement(100, UnitMass.grams);
// mass.getDurationParts(); // ← TypeScript Error: Property doesn't exist
```
#### Formatting Durations
Display durations in various formats (numeric, colon notation, or labeled):
```typescript
import { Measurement, UnitDuration } from 'measure-convert';
const duration = new Measurement(7265, UnitDuration.seconds);
// Numeric format (default)
console.log(duration.getDurationLabel('numeric')); // "7265 sec"
// Colon notation with different unit displays
console.log(duration.getDurationLabel('colon', 'auto')); // "2:01:05"
console.log(duration.getDurationLabel('colon', 'hms')); // "2:01:05"
console.log(duration.getDurationLabel('colon', 'hm')); // "2:01"
console.log(duration.getDurationLabel('colon', 'ms')); // "121:05"
console.log(duration.getDurationLabel('colon', 'm')); // "121.08"
console.log(duration.getDurationLabel('colon', 'h')); // "2.02"
// Labeled format
console.log(duration.getDurationLabel('labeled', 'auto')); // "2 hrs 1 min 5 secs"
console.log(duration.getDurationLabel('labeled', 'hms')); // "2 hrs 1 min 5 secs"
console.log(duration.getDurationLabel('labeled', 'hm')); // "2 hrs 1 min"
console.log(duration.getDurationLabel('labeled', 'm')); // "121.08 mins"
// Different input units work seamlessly
const minutes = new Measurement(90, UnitDuration.minutes);
console.log(minutes.getDurationLabel('colon', 'hm')); // "1:30"
console.log(minutes.getDurationLabel('labeled', 'auto')); // "1 hr 30 mins"
```
**Duration Format Options:**
- `'numeric'` - Standard numeric display with unit (default)
- `'colon'` - Time notation (1:30:45)
- `'labeled'` - Descriptive format (1 hr 30 mins 45 secs)
**Duration Unit Display Options:**
- `'auto'` - Automatically choose best units based on magnitude
- `'s'` - Seconds only
- `'m'` - Minutes only (can be decimal)
- `'h'` - Hours only (can be decimal)
- `'ms'` - Minutes and seconds
- `'hm'` - Hours and minutes
- `'hms'` - Hours, minutes and seconds
### Adding and Subtracting Measurements
You can add or subtract two measurements, provided they are of the same measurement type.
```typescript
import { Measurement, UnitMass, UnitVolume } from 'measure-convert';
// Adding masses (cooking example)
const flour = new Measurement(120, UnitMass.grams);
const sugar = new Measurement(0.25, UnitMass.pounds);
const totalMass = flour.add(sugar.converted(UnitMass.grams));
console.log(totalMass.shortLabel); // "233.4 g"
// Adding volumes
const water = new Measurement(1, UnitVolume.cups);
const milk = new Measurement(250, UnitVolume.milliliters);
const totalVolume = water.add(milk.converted(UnitVolume.cups));
console.log(totalVolume.getShortLabel(2)); // "2.04 cup"
```
### Density-Based Conversions Between Mass and Volume
Convert between mass and volume units using density (always in g/mL). This is particularly useful for cooking and recipe conversions.
```typescript
import { Measurement, UnitMass, UnitVolume } from 'measure-convert';
// Convert mass to volume
const flourMass = new Measurement(120, UnitMass.grams);
const flourDensity = 0.5; // g/mL (all-purpose flour)
const flourVolume = Measurement.convertMassToVolume(flourMass, flourDensity, UnitVolume.cups);
console.log(flourVolume.getShortLabel(2)); // "1.00 cup"
// Convert volume to mass
const oilVolume = new Measurement(100, UnitVolume.milliliters);
const oilDensity = 0.92; // g/mL (olive oil)
const oilMass = Measurement.convertVolumeToMass(oilVolume, oilDensity, UnitMass.grams);
console.log(oilMass.shortLabel); // "92 g"
// Real-world example: Converting butter tablespoons to grams
const butterVolume = new Measurement(2, UnitVolume.tablespoons);
const butterDensity = 0.96; // g/mL
const butterMass = Measurement.convertVolumeToMass(butterVolume, butterDensity);
console.log(butterMass.getShortLabel(1)); // "28.4 g"
// Convert with specific output units
const sugarMass = new Measurement(1, UnitMass.pounds);
const sugarDensity = 0.85; // g/mL (granulated sugar)
const sugarVolume = Measurement.convertMassToVolume(sugarMass, sugarDensity, UnitVolume.cups);
console.log(sugarVolume.getShortLabel(2)); // "2.22 cups"
```
**Common Densities (g/mL):**
- Water: 1.0
- All-purpose flour: 0.5
- Granulated sugar: 0.85
- Brown sugar: 0.72
- Olive oil: 0.92
- Butter: 0.96
- Honey: 1.4
- Milk: 1.03
### Checking Proximity with `closeTo`
Determine if two measurements are within a specified tolerance of each other.
```typescript
import { Measurement, UnitLength } from 'measure-convert';
// Check if 100 meters is close to 328.084 feet within 0.1 meters tolerance
const length1 = new Measurement(100, UnitLength.meters);
const length2 = new Measurement(328.084, UnitLength.feet);
const isClose = length1.closeTo(length2, 0.1);
console.log(`Are the lengths close? ${isClose}`);
```
**Output:**
```Are the lengths close? true```
## Quick Reference: Unit Types and Common Units
### Mass (UnitMass)
```typescript
import { UnitMass } from 'measure-convert';
UnitMass.grams // g
UnitMass.kilograms // kg
UnitMass.milligrams // mg
UnitMass.ounces // oz
UnitMass.pounds // lb
```
### Volume (UnitVolume)
```typescript
import { UnitVolume } from 'measure-convert';
// Metric
UnitVolume.milliliters // mL
UnitVolume.liters // L
// US Customary (default when using fromCompact)
UnitVolume.teaspoons // tsp (US: 4.93 mL)
UnitVolume.tablespoons // tbsp (US: 14.79 mL)
UnitVolume.fluidOunces // fl oz (US: 29.57 mL)
UnitVolume.cups // cup (US: 240 mL)
UnitVolume.pints // pt (US: 473.18 mL)
UnitVolume.quarts // qt (US: 946.35 mL)
UnitVolume.gallons // gal (US: 3785.41 mL)
// Imperial (UK)
UnitVolume.imperialTeaspoons // tsp (Imperial: 5.92 mL)
UnitVolume.imperialTablespoons // tbsp (Imperial: 17.76 mL)
UnitVolume.imperialFluidOunces // fl oz (Imperial: 28.41 mL)
UnitVolume.imperialPints // pt (Imperial: 568.26 mL)
UnitVolume.imperialQuarts // qt (Imperial: 1136.52 mL)
UnitVolume.imperialGallons // gal (Imperial: 4546.09 mL)
```
**Note on US vs Imperial Units:**
When using `fromCompact()` with volume units that have both US and Imperial variants (tsp, tbsp, fl oz, pt, qt, gal), the library defaults to US Customary units. To use Imperial units, pass `true` as the second parameter:
```typescript
const volumeData = { type: 'volume', unit: 'tsp', value: 1 };
const usTeaspoon = Measurement.fromCompact(volumeData); // US: 4.93 mL
const imperialTeaspoon = Measurement.fromCompact(volumeData, true); // Imperial: 5.92 mL
```
### Temperature (UnitTemperature)
```typescript
import { UnitTemperature } from 'measure-convert';
UnitTemperature.celsius // °C
UnitTemperature.fahrenheit // °F
UnitTemperature.kelvin // K
```
### Length (UnitLength)
```typescript
import { UnitLength } from 'measure-convert';
UnitLength.millimeters // mm
UnitLength.centimeters // cm
UnitLength.meters // m
UnitLength.kilometers // km
UnitLength.inches // in
UnitLength.feet // ft
UnitLength.yards // yd
UnitLength.miles // mi
```
## API Reference
### Measurement Class
The `Measurement` class represents a value with an associated unit and provides methods for conversion and comparison.
#### Constructor
```typescript
constructor(value: number, unit: Unit)
```
- `value`: The numerical value of the measurement.
- `unit`: The unit of the measurement.
#### Methods
- **`converted(targetUnit: V): Measurement<V>`**
Converts the current measurement to the `targetUnit`.
```typescript
converted<V extends Unit>(targetUnit: V): Measurement<V>
```
- **`getShortLabel(decimalPlaces?: number, showApprox?: boolean, simple?: boolean, locale?: LocaleFormat): string`**
Returns a formatted string with the unit symbol, with optional decimal place control and locale-specific formatting.
```typescript
getShortLabel(decimalPlaces?: number, showApprox?: boolean, simple?: boolean, locale?: LocaleFormat): string
```
- **`getLongLabel(decimalPlaces?: number, showApprox?: boolean, locale?: LocaleFormat): string`**
Returns a formatted string with the full unit name, with optional decimal place control and locale-specific formatting.
```typescript
getLongLabel(decimalPlaces?: number, showApprox?: boolean, locale?: LocaleFormat): string
```
- **`add(other: Measurement<U>): Measurement<U>`**
Adds another measurement of the same type.
```typescript
add(other: Measurement<U>): Measurement<U>
```
- **`subtract(other: Measurement<U>): Measurement<U>`**
Subtracts another measurement of the same type.
```typescript
subtract(other: Measurement<U>): Measurement<U>
```
- **`ratioTo(other: Measurement<U>): number`**
Calculates the ratio between two measurements of the same type.
```typescript
ratioTo(other: Measurement<U>): number
```
- **`scaledBy(factor: number): Measurement<U>`**
Scales a measurement by a factor while preserving the unit.
```typescript
scaledBy(factor: number): Measurement<U>
```
- **`closeTo(other: Measurement<U>, tolerance: number): boolean`**
Checks if another measurement is within a specified `tolerance`.
```typescript
closeTo(other: Measurement<U>, tolerance: number): boolean
```
- **`Static closeTo(measurement1: Measurement<U>, measurement2: Measurement<U>, tolerance: number): boolean`**
Static method to check if two measurements are within a specified `tolerance`.
```typescript
static closeTo<U extends Unit>(measurement1: Measurement<U>, measurement2: Measurement<U>, tolerance: number): boolean
```
- **`Static hydrateAuto(data): Measurement<any>`**
Auto-detects unit type and restores a `Measurement` object from serialized data (recommended).
```typescript
static hydrateAuto(data: SerializedMeasurement): Measurement<any>
```
- **`Static hydrate(UnitClass, data): Measurement<U>`**
Restores a `Measurement` object from serialized data when you know the unit type (for type safety).
```typescript
static hydrate<T extends typeof Unit, U extends InstanceType<T>>(UnitClass: T, data: SerializedMeasurement): Measurement<U>
```
- **`Static detectUnitType(data): string | null`**
Detects what unit type a serialized measurement belongs to (for advanced use cases).
```typescript
static detectUnitType(data: SerializedMeasurement): string | null
```
- **`toCompact(): CompactMeasurementBase`**
Converts the measurement to a compact format for efficient database storage.
```typescript
toCompact(): CompactMeasurementBase
```
- **`Static fromCompact(data, useImperial?): Measurement<Unit>`**
Creates a measurement from compact database format. For volume units, you can specify whether to use Imperial units instead of US Customary units (default is US).
```typescript
static fromCompact(data: CompactMeasurementBase | CompactMeasurement<any>, useImperial?: boolean): Measurement<Unit>
```
### Unit Classes
Each measurement type has its own `Unit` class, encapsulating the specific units and their conversion factors. Below are brief descriptions of each supported `Unit` class.
- **UnitAcceleration**
- **UnitAngle**
- **UnitArea**
- **UnitConcentrationMass**
- **UnitDispersion**
- **UnitDuration**
- **UnitElectricCharge**
- **UnitElectricCurrent**
- **UnitElectricPotentialDifference**
- **UnitElectricResistance**
- **UnitEnergy**
- **UnitFrequency**
- **UnitFuelEfficiency**
- **UnitIlluminance**
- **UnitInformationStorage**
- **UnitLength**
- **UnitMass**
- **UnitPower**
- **UnitPressure**
- **UnitSpeed**
- **UnitTemperature**
- **UnitVolume**
Each `Unit` class provides predefined units relevant to its measurement type. For example, `UnitTemperature` includes Celsius, Fahrenheit, and Kelvin.
#### Example: UnitTemperature
```typescript
import { Unit } from 'measure-convert';
// Access predefined temperature units
const celsius = UnitTemperature.celsius;
const fahrenheit = UnitTemperature.fahrenheit;
const kelvin = UnitTemperature.kelvin;
```
## Examples
### Cooking/Nutrition Examples
```typescript
import { Measurement, UnitMass, UnitVolume, UnitTemperature } from 'measure-convert';
// Recipe scaling
const flourPerServing = new Measurement(120, UnitMass.grams);
const additionalFlour = new Measurement(360, UnitMass.grams); // 3 more servings
const flourFor4Servings = flourPerServing.add(additionalFlour);
console.log(`Flour needed: ${flourFor4Servings.getShortLabel(0)}`);
// Output: Flour needed: 480 g
// Unit conversion for international recipes
const butter = new Measurement(0.5, UnitMass.pounds);
const butterInGrams = butter.converted(UnitMass.grams);
console.log(`${butter.shortLabel} = ${butterInGrams.shortLabel}`);
// Output: 0.5 lb = 227 g
// Volume conversions
const milk = new Measurement(1, UnitVolume.cups);
const milkInMl = milk.converted(UnitVolume.milliliters);
console.log(`${milk.shortLabel} = ${milkInMl.getShortLabel(0)}`);
// Output: 1 cup = 240 mL
// Temperature for cooking
const ovenTemp = new Measurement(350, UnitTemperature.fahrenheit);
const ovenTempC = ovenTemp.converted(UnitTemperature.celsius);
console.log(`Oven: ${ovenTemp.shortLabel} = ${ovenTempC.getShortLabel(0)}`);
// Output: Oven: 350°F = 177°C
```
### Precision Control for Nutritional Data
```typescript
import { Measurement, UnitMass } from 'measure-convert';
// Nutritional values often need precise formatting
const protein = new Measurement(23.456789, UnitMass.grams);
const fat = new Measurement(0.123456, UnitMass.grams);
// Standard nutrition label formatting (1 decimal place)
console.log(`Protein: ${protein.getShortLabel(1)}`); // "23.5 g"
console.log(`Fat: ${fat.getShortLabel(1)}`); // "0.1 g"
// Scientific precision (3 decimal places)
console.log(`Precise protein: ${protein.getShortLabel(3)}`); // "23.457 g"
// Handle trace amounts
const trace = new Measurement(0.001, UnitMass.grams);
console.log(`Trace amount: ${trace.getShortLabel(0, true)}`); // "~0 g"
```
### Scaling for Nutrition Calculations
Perfect for nutrition apps, recipe scaling, and food portion calculations:
```typescript
import { Measurement, UnitMass, UnitEnergy, UnitVolume } from 'measure-convert';
// Calculate nutrition per serving from per-100g values
const energyPer100g = new Measurement(400, UnitEnergy.calories);
const foodPortion = new Measurement(20, UnitMass.grams);
const baseReference = new Measurement(100, UnitMass.grams);
// Calculate the ratio and scale the energy
const ratio = foodPortion.ratioTo(baseReference); // 0.2
const portionEnergy = energyPer100g.scaledBy(ratio); // 80 calories
console.log(`Energy per portion: ${portionEnergy.shortLabel}`);
// Output: Energy per portion: 80 Cal
// Works with different units - volume-based nutrition
const energyPer100ml = new Measurement(250, UnitEnergy.kilocalories);
const servingSize = new Measurement(0.25, UnitVolume.liters);
const mlReference = new Measurement(100, UnitVolume.milliliters);
const volumeRatio = servingSize.ratioTo(mlReference); // 2.5
const servingEnergy = energyPer100ml.scaledBy(volumeRatio); // 625 kcal
console.log(`Energy per serving: ${servingEnergy.shortLabel}`);
// Output: Energy per serving: 625 kCal
// Recipe scaling example
const flourPerServing = new Measurement(120, UnitMass.grams);
const scaledFlour = flourPerServing.scaledBy(4); // Scale for 4 servings
console.log(`Flour needed: ${scaledFlour.shortLabel}`);
// Output: Flour needed: 480 g
```
### Database Serialization and Hydration
When storing `Measurement` objects in databases (MongoDB, Parse Server, etc.), they get serialized as plain objects and lose their methods. Use the hydration system to restore full functionality:
```typescript
import { Measurement, UnitEnergy } from 'measure-convert';
// Original measurement
const energy = new Measurement(100, UnitEnergy.kilocalories);
// When saved to database, it becomes a plain object like:
const dbData = {
"value": 100,
"unit": {
"name": "Kilocalories",
"symbol": "kCal",
"description": "Unit of measure for energy",
"baseUnitConversionFactor": 4184
}
};
// SIMPLEST: Auto-detect and hydrate in one step
const measurement = Measurement.hydrateAuto(dbData);
console.log(measurement.shortLabel); // "100 kCal"
console.log(measurement.converted(UnitEnergy.joules).shortLabel); // "418400 J"
// ALTERNATIVE: When you need type safety, specify the unit class
const typedMeasurement = Measurement.hydrate(UnitEnergy, dbData);
// Returns Measurement<UnitEnergy> with full TypeScript typing
// ADVANCED: Just detect the type (for complex logic)
const unitType = Measurement.detectUnitType(dbData);
console.log(unitType); // "UnitEnergy"
```
**When to use hydration:**
- Loading measurements from databases (MongoDB, Parse Server, Firebase, etc.)
- Receiving measurement data from APIs as JSON
- Restoring measurements from localStorage or sessionStorage
- Any time you have a plain object that was originally a `Measurement`
**The hydrated measurement has all the original functionality:**
- `.converted()` - Unit conversion
- `.add()`, `.subtract()` - Arithmetic operations
- `.shortLabel`, `.longLabel` - Formatted display
- `.closeTo()`, `.equals()` - Comparisons
### Compact Database Format
For more efficient database storage, use the compact format which stores only the essential data:
```typescript
import { Measurement, UnitMass, UnitEnergy, CompactMeasurement } from 'measure-convert';
// Create measurements
const weight = new Measurement(234.4, UnitMass.grams);
const energy = new Measurement(100, UnitEnergy.kilocalories);
// Convert to compact format for database storage
const compactWeight = weight.toCompact();
// { type: "mass", unit: "g", value: 234.4 }
const compactEnergy = energy.toCompact();
// { type: "energy", unit: "kCal", value: 100 }
// Store in database (MongoDB, Parse, Firebase, etc.)
await db.collection('nutrition').insertOne({
weight: compactWeight,
energy: compactEnergy
});
// Later, retrieve and restore from database
const data = await db.collection('nutrition').findOne({});
const restoredWeight = Measurement.fromCompact(data.weight);
const restoredEnergy = Measurement.fromCompact(data.energy);
console.log(restoredWeight.shortLabel); // "234 g"
console.log(restoredEnergy.shortLabel); // "100 kCal"
// For volume units, you can specify Imperial units
const volumeData = { type: 'volume', unit: 'tsp', value: 1 };
const usTeaspoon = Measurement.fromCompact(volumeData); // US teaspoon (4.93 mL)
const imperialTeaspoon = Measurement.fromCompact(volumeData, true); // Imperial teaspoon (5.92 mL)
```
**Benefits of Compact Format:**
- **Minimal Storage:** Only stores `type`, `unit` symbol, and `value`
- **Type Safety:** Full TypeScript support with typed interfaces
- **Easy Migration:** Works alongside existing serialization
- **Parse SDK Compatible:** Can be used directly in Parse object definitions
**TypeScript Type Definitions:**
```typescript
import { CompactMassMeasurement, CompactEnergyMeasurement } from 'measure-convert';
// Define your database schema with typed measurements
interface NutritionData {
consumed: CompactMassMeasurement; // { type: "mass", unit: MassUnitSymbol, value: number }
energy: CompactEnergyMeasurement; // { type: "energy", unit: EnergyUnitSymbol, value: number }
}
// Type guards for runtime validation
import { isCompactMassMeasurement } from 'measure-convert';
if (isCompactMassMeasurement(data)) {
// TypeScript knows this is a CompactMassMeasurement
const measurement = Measurement.fromCompact(data);
}
```
**Comparison: Standard vs Compact Format**
Standard format (what gets saved by default):
```json
{
"value": 100,
"unit": {
"name": "Kilocalories",
"symbol": "kCal",
"description": "Unit of measure for energy",
"baseUnitConversionFactor": 4184
}
}
```
Compact format (using `toCompact()`):
```json
{
"type": "energy",
"unit": "kCal",
"value": 100
}
```
The compact format reduces storage size by ~70% while maintaining all functionality after restoration.
## Testing
The library includes a comprehensive test suite to ensure all functionalities work as expected. Tests cover unit conversions, arithmetic operations, comparison methods, and error handling.
### Running Tests
Ensure you have all dependencies installed:
```bash
npm install
```
Run the test suite using Jest:
```bash
npm test
```
### Test Coverage
To generate a test coverage report, run:
```bash
npm test -- --coverage
```
This command will display coverage statistics and generate a detailed report, ensuring that all parts of your codebase are adequately tested.
## Contributing
Contributions are welcome! Whether it's fixing bugs, improving documentation, or adding new features, your help is appreciated.
### How to Contribute
1. **Fork the Repository**
2. **Create a New Branch**
```bash
git checkout -b feature/YourFeatureName
```
3. **Make Your Changes**
4. **Run Tests**
Ensure all tests pass:
```bash
npm test
```
5. **Commit Your Changes**
```bash
git commit -m "Add feature: YourFeatureName"
```
6. **Push to Your Fork**
```bash
git push origin feature/YourFeatureName
```
7. **Create a Pull Request**
Submit your changes for review.
### Guidelines
- Follow the existing coding style and conventions.
- Ensure all tests pass before submitting.
- Provide clear and concise commit messages.
- Update documentation as necessary.
## License
This project is licensed under the [Apache License 2.0](LICENSE).
---