@mahounou/uconv 0.1.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.
- package/README.md +67 -0
- package/package.json +28 -0
- package/src/converters/currency.js +36 -0
- package/src/converters/distance.js +20 -0
- package/src/converters/time.js +19 -0
- package/src/converters/utils/convert.js +3 -0
- package/src/converters/weight.js +20 -0
- package/src/index.js +77 -0
- package/src/parser.js +25 -0
- package/src/units.js +129 -0
package/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# UConv Node.js
|
|
2
|
+
|
|
3
|
+
Lightweight unit converter for Node.js with zero runtime dependencies.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```javascript
|
|
14
|
+
import { convert } from './src/index.js';
|
|
15
|
+
|
|
16
|
+
// Distance conversions
|
|
17
|
+
convert("10km", "m"); // 10000
|
|
18
|
+
convert("5ft", "cm"); // 152.4
|
|
19
|
+
|
|
20
|
+
// Weight conversions
|
|
21
|
+
convert("5lbs", "kg"); // 2.26796
|
|
22
|
+
convert("1kg", "g"); // 1000
|
|
23
|
+
|
|
24
|
+
// Time conversions
|
|
25
|
+
convert("1hr", "min"); // 60
|
|
26
|
+
convert("30min", "s"); // 1800
|
|
27
|
+
|
|
28
|
+
// Currency conversions (example rates)
|
|
29
|
+
convert("100USD", "EUR"); // ~85
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Supported Units
|
|
33
|
+
|
|
34
|
+
### Distance
|
|
35
|
+
- Metric: m, km, cm, mm
|
|
36
|
+
- Imperial: ft, in, yd, mi
|
|
37
|
+
|
|
38
|
+
### Weight
|
|
39
|
+
- Metric: g, kg, mg, t
|
|
40
|
+
- Imperial: lb, oz, st
|
|
41
|
+
|
|
42
|
+
### Time
|
|
43
|
+
- s, min, hr, day, week, month, year
|
|
44
|
+
|
|
45
|
+
### Currency
|
|
46
|
+
- USD, EUR, GBP, JPY, CAD, AUD, CHF, CNY
|
|
47
|
+
|
|
48
|
+
## Error Handling
|
|
49
|
+
|
|
50
|
+
The library throws specific errors for different failure cases:
|
|
51
|
+
|
|
52
|
+
- `UnknownUnitError`: When a unit is not recognized
|
|
53
|
+
- `InvalidInputError`: When input format is invalid
|
|
54
|
+
- `IncompatibleUnitsError`: When trying to convert between different unit types
|
|
55
|
+
|
|
56
|
+
## Testing
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npm test
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Base Units
|
|
63
|
+
|
|
64
|
+
- Distance: meter (m)
|
|
65
|
+
- Weight: gram (g)
|
|
66
|
+
- Time: second (s)
|
|
67
|
+
- Currency: USD
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mahounou/uconv",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Lightweight unit converter library",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"src/",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"test": "vitest",
|
|
16
|
+
"test:watch": "vitest --watch"
|
|
17
|
+
},
|
|
18
|
+
"keywords": ["units", "converter", "measurement", "distance", "weight", "time", "currency"],
|
|
19
|
+
"author": "",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "https://github.com/ksnjkdppdojdim-star/Uconv"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"vitest": "latest"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { getConversionFactor } from '../units.js';
|
|
2
|
+
import { getConversionFactor } from '../units.js';
|
|
3
|
+
import { factorConvert } from '../utils/convert.js';
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Convert currency units
|
|
8
|
+
* Note: In production, exchange rates should be fetched from a live API
|
|
9
|
+
* @param {number} value - Value to convert
|
|
10
|
+
* @param {string} fromUnit - Source currency
|
|
11
|
+
* @param {string} toUnit - Target currency
|
|
12
|
+
* @returns {number} Converted value
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
export function convertCurrency(value, fromUnit, toUnit) {
|
|
17
|
+
const from = getConversionFactor(fromUnit);
|
|
18
|
+
const to = getConversionFactor(toUnit);
|
|
19
|
+
if (from === null || to === null) throw new Error('Invalid currency unit');
|
|
20
|
+
return factorConvert(value, from, to);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Async live rates — optional
|
|
24
|
+
export async function convertCurrencyLive(value, fromUnit, toUnit) {
|
|
25
|
+
const res = await fetch(`https://api.exchangerate-api.com/v4/latest/USD`);
|
|
26
|
+
if (!res.ok) throw new Error('Failed to fetch live exchange rates');
|
|
27
|
+
const { rates } = await res.json();
|
|
28
|
+
|
|
29
|
+
const from = fromUnit.toUpperCase();
|
|
30
|
+
const to = toUnit.toUpperCase();
|
|
31
|
+
|
|
32
|
+
if (!rates[from]) throw new Error(`Unknown currency: ${from}`);
|
|
33
|
+
if (!rates[to]) throw new Error(`Unknown currency: ${to}`);
|
|
34
|
+
|
|
35
|
+
return (value / rates[from]) * rates[to];
|
|
36
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { getConversionFactor } from '../units.js';
|
|
2
|
+
import { getConversionFactor } from '../units.js';
|
|
3
|
+
import { factorConvert } from '../utils/convert.js';
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Convert distance units
|
|
8
|
+
* @param {number} value - Value to convert
|
|
9
|
+
* @param {string} fromUnit - Source unit
|
|
10
|
+
* @param {string} toUnit - Target unit
|
|
11
|
+
* @returns {number} Converted value
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
export function convertDistance(value, fromUnit, toUnit) {
|
|
16
|
+
const from = getConversionFactor(fromUnit);
|
|
17
|
+
const to = getConversionFactor(toUnit);
|
|
18
|
+
if (from === null || to === null) throw new Error('Invalid distance unit');
|
|
19
|
+
return factorConvert(value, from, to);
|
|
20
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { getConversionFactor } from '../units.js';
|
|
2
|
+
import { getConversionFactor } from '../units.js';
|
|
3
|
+
import { factorConvert } from '../utils/convert.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Convert time units
|
|
7
|
+
* @param {number} value - Value to convert
|
|
8
|
+
* @param {string} fromUnit - Source unit
|
|
9
|
+
* @param {string} toUnit - Target unit
|
|
10
|
+
* @returns {number} Converted value
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
export function convertTime(value, fromUnit, toUnit) {
|
|
15
|
+
const from = getConversionFactor(fromUnit);
|
|
16
|
+
const to = getConversionFactor(toUnit);
|
|
17
|
+
if (from === null || to === null) throw new Error('Invalid time unit');
|
|
18
|
+
return factorConvert(value, from, to);
|
|
19
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { getConversionFactor } from '../units.js';
|
|
2
|
+
import { getConversionFactor } from '../units.js';
|
|
3
|
+
import { factorConvert } from '../utils/convert.js';
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Convert weight units
|
|
8
|
+
* @param {number} value - Value to convert
|
|
9
|
+
* @param {string} fromUnit - Source unit
|
|
10
|
+
* @param {string} toUnit - Target unit
|
|
11
|
+
* @returns {number} Converted value
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
export function convertWeight(value, fromUnit, toUnit) {
|
|
16
|
+
const from = getConversionFactor(fromUnit);
|
|
17
|
+
const to = getConversionFactor(toUnit);
|
|
18
|
+
if (from === null || to === null) throw new Error('Invalid weight unit');
|
|
19
|
+
return factorConvert(value, from, to);
|
|
20
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { parseInput } from './parser.js';
|
|
2
|
+
import { getUnitCategory, isValidUnit } from './units.js';
|
|
3
|
+
import { convertDistance } from './converters/distance.js';
|
|
4
|
+
import { convertWeight } from './converters/weight.js';
|
|
5
|
+
import { convertTime } from './converters/time.js';
|
|
6
|
+
import { convertCurrency } from './converters/currency.js';
|
|
7
|
+
|
|
8
|
+
// Custom error classes
|
|
9
|
+
export class UnknownUnitError extends Error {
|
|
10
|
+
constructor(unit) {
|
|
11
|
+
super(`Unknown unit: ${unit}`);
|
|
12
|
+
this.name = 'UnknownUnitError';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class InvalidInputError extends Error {
|
|
17
|
+
constructor(input) {
|
|
18
|
+
super(`Invalid input format: ${input}`);
|
|
19
|
+
this.name = 'InvalidInputError';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class IncompatibleUnitsError extends Error {
|
|
24
|
+
constructor(fromUnit, toUnit) {
|
|
25
|
+
super(`Cannot convert from ${fromUnit} to ${toUnit}: incompatible unit types`);
|
|
26
|
+
this.name = 'IncompatibleUnitsError';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const converters = {
|
|
31
|
+
distance: convertDistance,
|
|
32
|
+
weight: convertWeight,
|
|
33
|
+
time: convertTime,
|
|
34
|
+
currency: convertCurrency
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Convert between units
|
|
39
|
+
* @param {string} from - Source value and unit (e.g., "10km")
|
|
40
|
+
* @param {string} to - Target unit (e.g., "m")
|
|
41
|
+
* @returns {number} Converted value
|
|
42
|
+
*/
|
|
43
|
+
export function convert(from, to) {
|
|
44
|
+
if (typeof to !== 'string' || !to.trim()) {
|
|
45
|
+
throw new InvalidInputError(to);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const toUnit = to.trim().toLowerCase();
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
const parsed = parseInput(from);
|
|
52
|
+
if (!parsed) throw new InvalidInputError(from);
|
|
53
|
+
|
|
54
|
+
const { value, unit: fromUnit } = parsed;
|
|
55
|
+
|
|
56
|
+
if (!isValidUnit(fromUnit)) throw new UnknownUnitError(fromUnit);
|
|
57
|
+
if (!isValidUnit(toUnit)) throw new UnknownUnitError(toUnit);
|
|
58
|
+
|
|
59
|
+
const fromCategory = getUnitCategory(fromUnit);
|
|
60
|
+
const toCategory = getUnitCategory(toUnit);
|
|
61
|
+
|
|
62
|
+
if (fromCategory !== toCategory) throw new IncompatibleUnitsError(fromUnit, toUnit);
|
|
63
|
+
|
|
64
|
+
const converter = converters[fromCategory];
|
|
65
|
+
if (!converter) throw new Error(`No converter for: ${fromCategory}`);
|
|
66
|
+
|
|
67
|
+
return converter(value, fromUnit, toUnit);
|
|
68
|
+
|
|
69
|
+
} catch (error) {
|
|
70
|
+
if (error instanceof UnknownUnitError ||
|
|
71
|
+
error instanceof InvalidInputError ||
|
|
72
|
+
error instanceof IncompatibleUnitsError) {
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
throw new Error(`Conversion failed: ${error.message}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
package/src/parser.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse input string containing value and unit
|
|
3
|
+
* @param {string} input - Input string like "10km", "5.5 lbs", "100 USD"
|
|
4
|
+
* @returns {Object|null} Parsed object {value, unit} or null if invalid
|
|
5
|
+
*/
|
|
6
|
+
const MAX_INPUT_LENGTH = 50;
|
|
7
|
+
const MAX_VALUE = 1e15;
|
|
8
|
+
const PARSE_REGEX = /^(-?\d+(?:\.\d+)?(?:e[+-]?\d+)?)\s*([a-zA-Z°]+)$/i;
|
|
9
|
+
|
|
10
|
+
export function parseInput(input) {
|
|
11
|
+
if (typeof input !== 'string') return null;
|
|
12
|
+
|
|
13
|
+
const clean = input.trim();
|
|
14
|
+
if (!clean || clean.length > MAX_INPUT_LENGTH) return null;
|
|
15
|
+
|
|
16
|
+
const match = clean.match(PARSE_REGEX);
|
|
17
|
+
if (!match) return null;
|
|
18
|
+
|
|
19
|
+
const value = parseFloat(match[1]);
|
|
20
|
+
|
|
21
|
+
if (!isFinite(value)) return null;
|
|
22
|
+
if (Math.abs(value) > MAX_VALUE) return null;
|
|
23
|
+
|
|
24
|
+
return { value, unit: match[2].toLowerCase() };
|
|
25
|
+
}
|
package/src/units.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// Base units for each category
|
|
2
|
+
export const BASE_UNITS = {
|
|
3
|
+
distance: 'm', // meter
|
|
4
|
+
weight: 'g', // gram
|
|
5
|
+
time: 's', // second
|
|
6
|
+
currency: 'usd' // US Dollar
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
// Unit definitions with conversion factors to base units
|
|
10
|
+
export const UNITS = {
|
|
11
|
+
// Distance (base: meter)
|
|
12
|
+
distance: {
|
|
13
|
+
// Metric
|
|
14
|
+
'm': 1,
|
|
15
|
+
'meter': 1,
|
|
16
|
+
'metre': 1,
|
|
17
|
+
'km': 1000,
|
|
18
|
+
'kilometer': 1000,
|
|
19
|
+
'kilometre': 1000,
|
|
20
|
+
'cm': 0.01,
|
|
21
|
+
'centimeter': 0.01,
|
|
22
|
+
'centimetre': 0.01,
|
|
23
|
+
'mm': 0.001,
|
|
24
|
+
'millimeter': 0.001,
|
|
25
|
+
'millimetre': 0.001,
|
|
26
|
+
|
|
27
|
+
// Imperial
|
|
28
|
+
'ft': 0.3048,
|
|
29
|
+
'foot': 0.3048,
|
|
30
|
+
'feet': 0.3048,
|
|
31
|
+
'in': 0.0254,
|
|
32
|
+
'inch': 0.0254,
|
|
33
|
+
'yd': 0.9144,
|
|
34
|
+
'yard': 0.9144,
|
|
35
|
+
'mi': 1609.344,
|
|
36
|
+
'mile': 1609.344
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
// Weight (base: gram)
|
|
40
|
+
weight: {
|
|
41
|
+
// Metric
|
|
42
|
+
'g': 1,
|
|
43
|
+
'gram': 1,
|
|
44
|
+
'kg': 1000,
|
|
45
|
+
'kilogram': 1000,
|
|
46
|
+
'mg': 0.001,
|
|
47
|
+
'milligram': 0.001,
|
|
48
|
+
't': 1000000,
|
|
49
|
+
'ton': 1000000,
|
|
50
|
+
'tonne': 1000000,
|
|
51
|
+
|
|
52
|
+
// Imperial
|
|
53
|
+
'lb': 453.592,
|
|
54
|
+
'lbs': 453.592,
|
|
55
|
+
'pound': 453.592,
|
|
56
|
+
'oz': 28.3495,
|
|
57
|
+
'ounce': 28.3495,
|
|
58
|
+
'st': 6350.29,
|
|
59
|
+
'stone': 6350.29
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
// Time (base: second)
|
|
63
|
+
time: {
|
|
64
|
+
's': 1,
|
|
65
|
+
'sec': 1,
|
|
66
|
+
'second': 1,
|
|
67
|
+
'min': 60,
|
|
68
|
+
'minute': 60,
|
|
69
|
+
'hr': 3600,
|
|
70
|
+
'hour': 3600,
|
|
71
|
+
'day': 86400,
|
|
72
|
+
'week': 604800,
|
|
73
|
+
'month': 2629746, // Average month
|
|
74
|
+
'year': 31556952 // Average year
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
// Currency (base: USD)
|
|
78
|
+
// Note: In a real implementation, these would be fetched from an API
|
|
79
|
+
currency: {
|
|
80
|
+
// ⚠️ Static rates as of 2024-01-01 — use live option for accuracy
|
|
81
|
+
// CURRENCY_RATES_DATE: '2024-01-01'
|
|
82
|
+
'usd': 1,
|
|
83
|
+
'eur': 0.85, // Example rates
|
|
84
|
+
'gbp': 0.75,
|
|
85
|
+
'jpy': 110,
|
|
86
|
+
'cad': 1.25,
|
|
87
|
+
'aud': 1.35,
|
|
88
|
+
'chf': 0.92,
|
|
89
|
+
'cny': 6.45
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Get the category of a unit
|
|
95
|
+
* @param {string} unit - Unit name
|
|
96
|
+
* @returns {string|null} Category name or null if not found
|
|
97
|
+
*/
|
|
98
|
+
export function getUnitCategory(unit) {
|
|
99
|
+
const normalizedUnit = unit.toLowerCase();
|
|
100
|
+
for (const [category, units] of Object.entries(UNITS)) {
|
|
101
|
+
if (Object.hasOwn(units, normalizedUnit)) {
|
|
102
|
+
return category;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Check if a unit is valid
|
|
110
|
+
* @param {string} unit - Unit name
|
|
111
|
+
* @returns {boolean} True if unit exists
|
|
112
|
+
*/
|
|
113
|
+
export function isValidUnit(unit) {
|
|
114
|
+
return getUnitCategory(unit) !== null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Get conversion factor to base unit
|
|
119
|
+
* @param {string} unit - Unit name
|
|
120
|
+
* @returns {number|null} Conversion factor or null if not found
|
|
121
|
+
*/
|
|
122
|
+
export function getConversionFactor(unit) {
|
|
123
|
+
const category = getUnitCategory(unit);
|
|
124
|
+
if (!category) {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return UNITS[category][unit.toLowerCase()];
|
|
129
|
+
}
|