@nr1e/commons 0.3.1 → 0.3.3
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 +12 -0
- package/dist/errors/index.d.mts +1 -0
- package/dist/errors/index.d.mts.map +1 -1
- package/dist/errors/index.mjs +1 -0
- package/dist/errors/index.mjs.map +1 -1
- package/dist/errors/safe.d.mts +15 -0
- package/dist/errors/safe.d.mts.map +1 -0
- package/dist/errors/safe.mjs +18 -0
- package/dist/errors/safe.mjs.map +1 -0
- package/dist/lang/amount.d.mts +38 -0
- package/dist/lang/amount.d.mts.map +1 -0
- package/dist/lang/amount.mjs +70 -0
- package/dist/lang/amount.mjs.map +1 -0
- package/dist/lang/amount.test.d.mts +2 -0
- package/dist/lang/amount.test.d.mts.map +1 -0
- package/dist/lang/amount.test.mjs +104 -0
- package/dist/lang/amount.test.mjs.map +1 -0
- package/dist/lang/currency.d.mts +18 -5
- package/dist/lang/currency.d.mts.map +1 -1
- package/dist/lang/currency.mjs +33 -7
- package/dist/lang/currency.mjs.map +1 -1
- package/dist/lang/currency.test.d.mts +2 -0
- package/dist/lang/currency.test.d.mts.map +1 -0
- package/dist/lang/currency.test.mjs +97 -0
- package/dist/lang/currency.test.mjs.map +1 -0
- package/dist/lang/index.d.mts +1 -0
- package/dist/lang/index.d.mts.map +1 -1
- package/dist/lang/index.mjs +1 -0
- package/dist/lang/index.mjs.map +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -5,6 +5,18 @@
|
|
|
5
5
|
|
|
6
6
|
This project provides reusable components commonly needed in TypeScript projects.
|
|
7
7
|
|
|
8
|
+
| Module | Description |
|
|
9
|
+
| -------------------------- | --------------------------------------------------------------------------------------------- |
|
|
10
|
+
| `@nr1e/commons/bitsnbytes` | Base64 encoding/decoding utilities |
|
|
11
|
+
| `@nr1e/commons/encryption` | Cryptographic utilities including AES encryption and RSA encryption |
|
|
12
|
+
| `@nr1e/commons/errors` | Error handling utilities and custom error types |
|
|
13
|
+
| `@nr1e/commons/http` | HTTP status codes and HTTP method constants |
|
|
14
|
+
| `@nr1e/commons/ids` | ID generation utilities (UUID v4/v7, KSUID) |
|
|
15
|
+
| `@nr1e/commons/lang` | Language utilities for currency, datetime, equality, merge, sleep, string, and type functions |
|
|
16
|
+
| `@nr1e/commons/oauth` | OAuth 2.0 cryptographic functions (PKCE) |
|
|
17
|
+
| `@nr1e/commons/os` | Operating system utilities including environment variable helpers |
|
|
18
|
+
| `@nr1e/commons/validator` | Input validation utilities |
|
|
19
|
+
|
|
8
20
|
[github-url]: https://github.com/nr1etech/lib-js/actions
|
|
9
21
|
[github-image]: https://github.com/nr1etech/lib-js/workflows/ci/badge.svg
|
|
10
22
|
[npm-url]: https://npmjs.org/package/@nr1e/commons-js
|
package/dist/errors/index.d.mts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../../src/errors/index.mts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../../src/errors/index.mts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC"}
|
package/dist/errors/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sourceRoot":"","sources":["../../src/errors/index.mts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.mjs","sourceRoot":"","sources":["../../src/errors/index.mts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type SafeResult<T> = {
|
|
2
|
+
success: true;
|
|
3
|
+
output: T;
|
|
4
|
+
} | {
|
|
5
|
+
success: false;
|
|
6
|
+
error: string;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Wraps a throwing function and returns a non-throwing result.
|
|
10
|
+
*
|
|
11
|
+
* @param fn - Function that may throw
|
|
12
|
+
* @param defaultMessage - Message to use if a non-Error is thrown
|
|
13
|
+
*/
|
|
14
|
+
export declare function safeCall<T>(fn: () => T, defaultMessage?: string): SafeResult<T>;
|
|
15
|
+
//# sourceMappingURL=safe.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"safe.d.mts","sourceRoot":"","sources":["../../src/errors/safe.mts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,CAAC,CAAC,IACpB;IAAC,OAAO,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,CAAC,CAAA;CAAC,GAC1B;IAAC,OAAO,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAC,CAAC;AAEpC;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EACxB,EAAE,EAAE,MAAM,CAAC,EACX,cAAc,SAAkB,GAC/B,UAAU,CAAC,CAAC,CAAC,CASf"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wraps a throwing function and returns a non-throwing result.
|
|
3
|
+
*
|
|
4
|
+
* @param fn - Function that may throw
|
|
5
|
+
* @param defaultMessage - Message to use if a non-Error is thrown
|
|
6
|
+
*/
|
|
7
|
+
export function safeCall(fn, defaultMessage = 'Unknown error') {
|
|
8
|
+
try {
|
|
9
|
+
return { success: true, output: fn() };
|
|
10
|
+
}
|
|
11
|
+
catch (err) {
|
|
12
|
+
return {
|
|
13
|
+
success: false,
|
|
14
|
+
error: err instanceof Error ? err.message : defaultMessage,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=safe.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"safe.mjs","sourceRoot":"","sources":["../../src/errors/safe.mts"],"names":[],"mappings":"AAIA;;;;;GAKG;AACH,MAAM,UAAU,QAAQ,CACtB,EAAW,EACX,cAAc,GAAG,eAAe;IAEhC,IAAI,CAAC;QACH,OAAO,EAAC,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,EAAC,CAAC;IACvC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO;YACL,OAAO,EAAE,KAAK;YACd,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,cAAc;SAC3D,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { SafeResult } from '../errors/index.mjs';
|
|
2
|
+
/**
|
|
3
|
+
* Parses an amount in the format "0.00", "0.0" or "0" and converts it to a
|
|
4
|
+
* number. Both positive and negative values are supported. This will throw
|
|
5
|
+
* an error if the amount is not in the expected format.
|
|
6
|
+
*
|
|
7
|
+
* @param amount - The amount string to parse
|
|
8
|
+
* @returns The parsed amount as a number
|
|
9
|
+
* @throws Error if the amount is not in the expected format or is not a finite number
|
|
10
|
+
*/
|
|
11
|
+
export declare function parseAmount(amount: string): number;
|
|
12
|
+
/**
|
|
13
|
+
* Parses an amount in the format "0.00", "0.0" or "0" and converts it to a
|
|
14
|
+
* number. Both positive and negative values are supported. This will return
|
|
15
|
+
* an error message if the amount is not in the expected format.
|
|
16
|
+
*
|
|
17
|
+
* @param input - The amount string to parse
|
|
18
|
+
* @returns A safe result containing the parsed amount as a number, or an error message if the amount is invalid.
|
|
19
|
+
*/
|
|
20
|
+
export declare function safeParseAmount(input: string): SafeResult<number>;
|
|
21
|
+
/**
|
|
22
|
+
* Formats a number into a canonical "0.00" amount string. Numbers must be two
|
|
23
|
+
* decimal places or less. Both positive and negative values are supported.
|
|
24
|
+
*
|
|
25
|
+
* @param value - The number to format
|
|
26
|
+
* @returns The formatted amount string, e.g., "10.00" or "-10.00"
|
|
27
|
+
* @throws Error if the value is not finite or exceeds two decimal places
|
|
28
|
+
*/
|
|
29
|
+
export declare function formatAmount(value: number): string;
|
|
30
|
+
/**
|
|
31
|
+
* Formats a number into a canonical "0.00" amount string. Numbers must be two
|
|
32
|
+
* decimal places or less. Both positive and negative values are supported.
|
|
33
|
+
*
|
|
34
|
+
* @param value - The number to format
|
|
35
|
+
* @returns A safe result containing the formatted amount string, or an error message if the value is invalid.
|
|
36
|
+
*/
|
|
37
|
+
export declare function safeFormatAmount(value: number): SafeResult<string>;
|
|
38
|
+
//# sourceMappingURL=amount.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"amount.d.mts","sourceRoot":"","sources":["../../src/lang/amount.mts"],"names":[],"mappings":"AAAA,OAAO,EAAW,UAAU,EAAC,MAAM,qBAAqB,CAAC;AAEzD;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAelD;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC,CAEjE;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAsBlD;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC,CAElE"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { safeCall } from '../errors/index.mjs';
|
|
2
|
+
/**
|
|
3
|
+
* Parses an amount in the format "0.00", "0.0" or "0" and converts it to a
|
|
4
|
+
* number. Both positive and negative values are supported. This will throw
|
|
5
|
+
* an error if the amount is not in the expected format.
|
|
6
|
+
*
|
|
7
|
+
* @param amount - The amount string to parse
|
|
8
|
+
* @returns The parsed amount as a number
|
|
9
|
+
* @throws Error if the amount is not in the expected format or is not a finite number
|
|
10
|
+
*/
|
|
11
|
+
export function parseAmount(amount) {
|
|
12
|
+
const trimmed = amount.trim();
|
|
13
|
+
const AMOUNT_REGEX = /^-?\d+(\.\d{1,2})?$/;
|
|
14
|
+
if (!AMOUNT_REGEX.test(trimmed)) {
|
|
15
|
+
throw new Error(`Invalid amount format: "${amount}"`);
|
|
16
|
+
}
|
|
17
|
+
const value = Number(trimmed);
|
|
18
|
+
if (!Number.isFinite(value)) {
|
|
19
|
+
throw new Error(`Invalid numeric value: "${amount}"`);
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Parses an amount in the format "0.00", "0.0" or "0" and converts it to a
|
|
25
|
+
* number. Both positive and negative values are supported. This will return
|
|
26
|
+
* an error message if the amount is not in the expected format.
|
|
27
|
+
*
|
|
28
|
+
* @param input - The amount string to parse
|
|
29
|
+
* @returns A safe result containing the parsed amount as a number, or an error message if the amount is invalid.
|
|
30
|
+
*/
|
|
31
|
+
export function safeParseAmount(input) {
|
|
32
|
+
return safeCall(() => parseAmount(input), 'Invalid amount format');
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Formats a number into a canonical "0.00" amount string. Numbers must be two
|
|
36
|
+
* decimal places or less. Both positive and negative values are supported.
|
|
37
|
+
*
|
|
38
|
+
* @param value - The number to format
|
|
39
|
+
* @returns The formatted amount string, e.g., "10.00" or "-10.00"
|
|
40
|
+
* @throws Error if the value is not finite or exceeds two decimal places
|
|
41
|
+
*/
|
|
42
|
+
export function formatAmount(value) {
|
|
43
|
+
if (!Number.isFinite(value)) {
|
|
44
|
+
throw new Error('Amount must be a finite number');
|
|
45
|
+
}
|
|
46
|
+
if (Number.isNaN(value)) {
|
|
47
|
+
throw new Error('Amount must be a valid number');
|
|
48
|
+
}
|
|
49
|
+
// Normalize -0 → 0
|
|
50
|
+
const normalized = Object.is(value, -0) ? 0 : value;
|
|
51
|
+
// Convert to string without scientific notation
|
|
52
|
+
const asString = normalized.toString();
|
|
53
|
+
// Validate decimal precision (max 2 decimal places)
|
|
54
|
+
const DECIMAL_REGEX = /^-?\d+(\.\d{1,2})?$/;
|
|
55
|
+
if (!DECIMAL_REGEX.test(asString)) {
|
|
56
|
+
throw new Error(`Invalid amount format: "${asString}"`);
|
|
57
|
+
}
|
|
58
|
+
return normalized.toFixed(2);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Formats a number into a canonical "0.00" amount string. Numbers must be two
|
|
62
|
+
* decimal places or less. Both positive and negative values are supported.
|
|
63
|
+
*
|
|
64
|
+
* @param value - The number to format
|
|
65
|
+
* @returns A safe result containing the formatted amount string, or an error message if the value is invalid.
|
|
66
|
+
*/
|
|
67
|
+
export function safeFormatAmount(value) {
|
|
68
|
+
return safeCall(() => formatAmount(value), 'Invalid amount');
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=amount.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"amount.mjs","sourceRoot":"","sources":["../../src/lang/amount.mts"],"names":[],"mappings":"AAAA,OAAO,EAAC,QAAQ,EAAa,MAAM,qBAAqB,CAAC;AAEzD;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW,CAAC,MAAc;IACxC,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;IAC9B,MAAM,YAAY,GAAG,qBAAqB,CAAC;IAE3C,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QAChC,MAAM,IAAI,KAAK,CAAC,2BAA2B,MAAM,GAAG,CAAC,CAAC;IACxD,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IAE9B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CAAC,2BAA2B,MAAM,GAAG,CAAC,CAAC;IACxD,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,KAAa;IAC3C,OAAO,QAAQ,CAAC,GAAG,EAAE,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,uBAAuB,CAAC,CAAC;AACrE,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IACpD,CAAC;IACD,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;IACnD,CAAC;IAED,mBAAmB;IACnB,MAAM,UAAU,GAAG,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IAEpD,gDAAgD;IAChD,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,EAAE,CAAC;IAEvC,oDAAoD;IACpD,MAAM,aAAa,GAAG,qBAAqB,CAAC;IAE5C,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QAClC,MAAM,IAAI,KAAK,CAAC,2BAA2B,QAAQ,GAAG,CAAC,CAAC;IAC1D,CAAC;IAED,OAAO,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AAC/B,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC5C,OAAO,QAAQ,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,gBAAgB,CAAC,CAAC;AAC/D,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"amount.test.d.mts","sourceRoot":"","sources":["../../src/lang/amount.test.mts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { test, expect, describe } from 'vitest';
|
|
2
|
+
import { parseAmount, safeParseAmount, formatAmount, safeFormatAmount, } from './amount.mjs';
|
|
3
|
+
describe('parseAmount', () => {
|
|
4
|
+
test('parses valid integer formats', () => {
|
|
5
|
+
expect(parseAmount('0')).toBe(0);
|
|
6
|
+
expect(parseAmount('10')).toBe(10);
|
|
7
|
+
expect(parseAmount('100')).toBe(100);
|
|
8
|
+
});
|
|
9
|
+
test('parses valid decimal formats', () => {
|
|
10
|
+
expect(parseAmount('10.0')).toBe(10);
|
|
11
|
+
expect(parseAmount('10.00')).toBe(10);
|
|
12
|
+
expect(parseAmount('10.5')).toBe(10.5);
|
|
13
|
+
expect(parseAmount('10.99')).toBe(10.99);
|
|
14
|
+
expect(parseAmount('0.01')).toBe(0.01);
|
|
15
|
+
});
|
|
16
|
+
test('parses negative values', () => {
|
|
17
|
+
expect(parseAmount('-10')).toBe(-10);
|
|
18
|
+
expect(parseAmount('-10.50')).toBe(-10.5);
|
|
19
|
+
expect(parseAmount('-0.01')).toBe(-0.01);
|
|
20
|
+
});
|
|
21
|
+
test('handles whitespace', () => {
|
|
22
|
+
expect(parseAmount(' 10.00 ')).toBe(10);
|
|
23
|
+
expect(parseAmount('\t10.50\n')).toBe(10.5);
|
|
24
|
+
});
|
|
25
|
+
test('throws on invalid formats', () => {
|
|
26
|
+
expect(() => parseAmount('')).toThrow('Invalid amount format');
|
|
27
|
+
expect(() => parseAmount(' ')).toThrow('Invalid amount format');
|
|
28
|
+
expect(() => parseAmount('abc')).toThrow('Invalid amount format');
|
|
29
|
+
expect(() => parseAmount('10.000')).toThrow('Invalid amount format');
|
|
30
|
+
expect(() => parseAmount('10.')).toThrow('Invalid amount format');
|
|
31
|
+
expect(() => parseAmount('.10')).toThrow('Invalid amount format');
|
|
32
|
+
expect(() => parseAmount('10.5.5')).toThrow('Invalid amount format');
|
|
33
|
+
expect(() => parseAmount('$10.00')).toThrow('Invalid amount format');
|
|
34
|
+
expect(() => parseAmount('10,000.00')).toThrow('Invalid amount format');
|
|
35
|
+
});
|
|
36
|
+
test('throws on special numeric values', () => {
|
|
37
|
+
expect(() => parseAmount('NaN')).toThrow('Invalid amount format');
|
|
38
|
+
expect(() => parseAmount('Infinity')).toThrow('Invalid amount format');
|
|
39
|
+
expect(() => parseAmount('-Infinity')).toThrow('Invalid amount format');
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
describe('safeParseAmount', () => {
|
|
43
|
+
test('returns success for valid amounts', () => {
|
|
44
|
+
const result = safeParseAmount('10.50');
|
|
45
|
+
expect(result.success).toBe(true);
|
|
46
|
+
if (result.success) {
|
|
47
|
+
expect(result.output).toBe(10.5);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
test('returns error for invalid amounts', () => {
|
|
51
|
+
const result = safeParseAmount('invalid');
|
|
52
|
+
expect(result.success).toBe(false);
|
|
53
|
+
if (!result.success) {
|
|
54
|
+
expect(result.error).toContain('Invalid amount format');
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
describe('formatAmount', () => {
|
|
59
|
+
test('formats integers with two decimal places', () => {
|
|
60
|
+
expect(formatAmount(0)).toBe('0.00');
|
|
61
|
+
expect(formatAmount(10)).toBe('10.00');
|
|
62
|
+
expect(formatAmount(100)).toBe('100.00');
|
|
63
|
+
});
|
|
64
|
+
test('formats decimals with two decimal places', () => {
|
|
65
|
+
expect(formatAmount(10.5)).toBe('10.50');
|
|
66
|
+
expect(formatAmount(10.99)).toBe('10.99');
|
|
67
|
+
expect(formatAmount(0.01)).toBe('0.01');
|
|
68
|
+
});
|
|
69
|
+
test('formats negative values', () => {
|
|
70
|
+
expect(formatAmount(-10)).toBe('-10.00');
|
|
71
|
+
expect(formatAmount(-10.5)).toBe('-10.50');
|
|
72
|
+
expect(formatAmount(-0.01)).toBe('-0.01');
|
|
73
|
+
});
|
|
74
|
+
test('normalizes negative zero', () => {
|
|
75
|
+
expect(formatAmount(-0)).toBe('0.00');
|
|
76
|
+
});
|
|
77
|
+
test('throws on non-finite numbers', () => {
|
|
78
|
+
expect(() => formatAmount(NaN)).toThrow('Amount must be a finite number');
|
|
79
|
+
expect(() => formatAmount(Infinity)).toThrow('Amount must be a finite number');
|
|
80
|
+
expect(() => formatAmount(-Infinity)).toThrow('Amount must be a finite number');
|
|
81
|
+
});
|
|
82
|
+
test('throws on excessive decimal precision', () => {
|
|
83
|
+
expect(() => formatAmount(10.999)).toThrow('Invalid amount format');
|
|
84
|
+
expect(() => formatAmount(0.001)).toThrow('Invalid amount format');
|
|
85
|
+
expect(() => formatAmount(10.123456)).toThrow('Invalid amount format');
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
describe('safeFormatAmount', () => {
|
|
89
|
+
test('returns success for valid amounts', () => {
|
|
90
|
+
const result = safeFormatAmount(10.5);
|
|
91
|
+
expect(result.success).toBe(true);
|
|
92
|
+
if (result.success) {
|
|
93
|
+
expect(result.output).toBe('10.50');
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
test('returns error for invalid amounts', () => {
|
|
97
|
+
const result = safeFormatAmount(NaN);
|
|
98
|
+
expect(result.success).toBe(false);
|
|
99
|
+
if (!result.success) {
|
|
100
|
+
expect(result.error).toBe('Amount must be a finite number');
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
//# sourceMappingURL=amount.test.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"amount.test.mjs","sourceRoot":"","sources":["../../src/lang/amount.test.mts"],"names":[],"mappings":"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"}
|
package/dist/lang/currency.d.mts
CHANGED
|
@@ -1,10 +1,23 @@
|
|
|
1
|
+
import { SafeResult } from '../errors/index.mjs';
|
|
1
2
|
/**
|
|
2
|
-
* Formats a currency amount using the Intl.NumberFormat API
|
|
3
|
+
* Formats a currency amount using the Intl.NumberFormat API.
|
|
4
|
+
* All inputs are validated and normalized to two decimal places.
|
|
3
5
|
*
|
|
4
|
-
* @param amount
|
|
5
|
-
* @param currency -
|
|
6
|
-
* @param locale
|
|
6
|
+
* @param amount - Amount as number or string ("0", "0.0", "0.00")
|
|
7
|
+
* @param currency - Currency code (e.g., 'USD', 'EUR')
|
|
8
|
+
* @param locale - Locale (defaults to 'en-US')
|
|
7
9
|
* @returns Formatted currency string
|
|
10
|
+
* @throws Error if the amount is invalid
|
|
8
11
|
*/
|
|
9
|
-
export declare
|
|
12
|
+
export declare function formatCurrency(amount: number | string, currency: string, locale?: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Safely formats a currency amount.
|
|
15
|
+
*
|
|
16
|
+
* @param amount - Amount as number or string
|
|
17
|
+
* @param currency - Currency code (e.g., 'USD')
|
|
18
|
+
* @param locale - Locale (defaults to 'en-US')
|
|
19
|
+
* @returns A safe result containing the formatted currency string,
|
|
20
|
+
* or an error message if the amount is invalid.
|
|
21
|
+
*/
|
|
22
|
+
export declare function safeFormatCurrency(amount: number | string, currency: string, locale?: string): SafeResult<string>;
|
|
10
23
|
//# sourceMappingURL=currency.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"currency.d.mts","sourceRoot":"","sources":["../../src/lang/currency.mts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"currency.d.mts","sourceRoot":"","sources":["../../src/lang/currency.mts"],"names":[],"mappings":"AACA,OAAO,EAAW,UAAU,EAAC,MAAM,qBAAqB,CAAC;AAEzD;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,MAAM,GAAG,MAAM,EACvB,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,MAAM,GACd,MAAM,CAgBR;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,GAAG,MAAM,EACvB,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,MAAM,GACd,UAAU,CAAC,MAAM,CAAC,CAKpB"}
|
package/dist/lang/currency.mjs
CHANGED
|
@@ -1,16 +1,42 @@
|
|
|
1
|
+
import { formatAmount, parseAmount } from './amount.mjs';
|
|
2
|
+
import { safeCall } from '../errors/index.mjs';
|
|
1
3
|
/**
|
|
2
|
-
* Formats a currency amount using the Intl.NumberFormat API
|
|
4
|
+
* Formats a currency amount using the Intl.NumberFormat API.
|
|
5
|
+
* All inputs are validated and normalized to two decimal places.
|
|
3
6
|
*
|
|
4
|
-
* @param amount
|
|
5
|
-
* @param currency -
|
|
6
|
-
* @param locale
|
|
7
|
+
* @param amount - Amount as number or string ("0", "0.0", "0.00")
|
|
8
|
+
* @param currency - Currency code (e.g., 'USD', 'EUR')
|
|
9
|
+
* @param locale - Locale (defaults to 'en-US')
|
|
7
10
|
* @returns Formatted currency string
|
|
11
|
+
* @throws Error if the amount is invalid
|
|
8
12
|
*/
|
|
9
|
-
export
|
|
10
|
-
|
|
13
|
+
export function formatCurrency(amount, currency, locale) {
|
|
14
|
+
let numericAmount;
|
|
15
|
+
if (typeof amount === 'string') {
|
|
16
|
+
// strict string validation: disallows "0.000", etc.
|
|
17
|
+
numericAmount = parseAmount(amount);
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
// strict numeric validation + normalization
|
|
21
|
+
numericAmount = parseFloat(formatAmount(amount));
|
|
22
|
+
}
|
|
11
23
|
return new Intl.NumberFormat(locale ?? 'en-US', {
|
|
12
24
|
style: 'currency',
|
|
13
25
|
currency: currency.toUpperCase(),
|
|
26
|
+
minimumFractionDigits: 2,
|
|
27
|
+
maximumFractionDigits: 2,
|
|
14
28
|
}).format(numericAmount);
|
|
15
|
-
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Safely formats a currency amount.
|
|
32
|
+
*
|
|
33
|
+
* @param amount - Amount as number or string
|
|
34
|
+
* @param currency - Currency code (e.g., 'USD')
|
|
35
|
+
* @param locale - Locale (defaults to 'en-US')
|
|
36
|
+
* @returns A safe result containing the formatted currency string,
|
|
37
|
+
* or an error message if the amount is invalid.
|
|
38
|
+
*/
|
|
39
|
+
export function safeFormatCurrency(amount, currency, locale) {
|
|
40
|
+
return safeCall(() => formatCurrency(amount, currency, locale), 'Invalid currency amount');
|
|
41
|
+
}
|
|
16
42
|
//# sourceMappingURL=currency.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"currency.mjs","sourceRoot":"","sources":["../../src/lang/currency.mts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"currency.mjs","sourceRoot":"","sources":["../../src/lang/currency.mts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAE,WAAW,EAAC,MAAM,cAAc,CAAC;AACvD,OAAO,EAAC,QAAQ,EAAa,MAAM,qBAAqB,CAAC;AAEzD;;;;;;;;;GASG;AACH,MAAM,UAAU,cAAc,CAC5B,MAAuB,EACvB,QAAgB,EAChB,MAAe;IAEf,IAAI,aAAqB,CAAC;IAC1B,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC/B,oDAAoD;QACpD,aAAa,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;IACtC,CAAC;SAAM,CAAC;QACN,4CAA4C;QAC5C,aAAa,GAAG,UAAU,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC;IACnD,CAAC;IAED,OAAO,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,IAAI,OAAO,EAAE;QAC9C,KAAK,EAAE,UAAU;QACjB,QAAQ,EAAE,QAAQ,CAAC,WAAW,EAAE;QAChC,qBAAqB,EAAE,CAAC;QACxB,qBAAqB,EAAE,CAAC;KACzB,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;AAC3B,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,kBAAkB,CAChC,MAAuB,EACvB,QAAgB,EAChB,MAAe;IAEf,OAAO,QAAQ,CACb,GAAG,EAAE,CAAC,cAAc,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,EAC9C,yBAAyB,CAC1B,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"currency.test.d.mts","sourceRoot":"","sources":["../../src/lang/currency.test.mts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { test, expect, describe } from 'vitest';
|
|
2
|
+
import { formatCurrency, safeFormatCurrency } from './currency.mjs';
|
|
3
|
+
describe('formatCurrency', () => {
|
|
4
|
+
test('formats string amounts with USD', () => {
|
|
5
|
+
expect(formatCurrency('10', 'USD')).toBe('$10.00');
|
|
6
|
+
expect(formatCurrency('10.5', 'USD')).toBe('$10.50');
|
|
7
|
+
expect(formatCurrency('10.99', 'USD')).toBe('$10.99');
|
|
8
|
+
expect(formatCurrency('0', 'USD')).toBe('$0.00');
|
|
9
|
+
});
|
|
10
|
+
test('formats number amounts with USD', () => {
|
|
11
|
+
expect(formatCurrency(10, 'USD')).toBe('$10.00');
|
|
12
|
+
expect(formatCurrency(10.5, 'USD')).toBe('$10.50');
|
|
13
|
+
expect(formatCurrency(10.99, 'USD')).toBe('$10.99');
|
|
14
|
+
expect(formatCurrency(0, 'USD')).toBe('$0.00');
|
|
15
|
+
});
|
|
16
|
+
test('formats negative amounts', () => {
|
|
17
|
+
expect(formatCurrency('-10.50', 'USD')).toBe('-$10.50');
|
|
18
|
+
expect(formatCurrency(-10.5, 'USD')).toBe('-$10.50');
|
|
19
|
+
});
|
|
20
|
+
test('handles different currencies', () => {
|
|
21
|
+
expect(formatCurrency('10', 'EUR')).toBe('€10.00');
|
|
22
|
+
expect(formatCurrency('10', 'GBP')).toBe('£10.00');
|
|
23
|
+
expect(formatCurrency('10', 'JPY')).toBe('¥10.00');
|
|
24
|
+
});
|
|
25
|
+
test('handles lowercase currency codes', () => {
|
|
26
|
+
expect(formatCurrency('10', 'usd')).toBe('$10.00');
|
|
27
|
+
expect(formatCurrency('10', 'eur')).toBe('€10.00');
|
|
28
|
+
});
|
|
29
|
+
test('formats with different locales', () => {
|
|
30
|
+
// German locale uses different formatting
|
|
31
|
+
expect(formatCurrency('1234.56', 'EUR', 'de-DE')).toBe('1.234,56\u00A0€');
|
|
32
|
+
// Japanese locale (uses fullwidth yen symbol ¥)
|
|
33
|
+
expect(formatCurrency('1234.56', 'JPY', 'ja-JP')).toBe('¥1,234.56');
|
|
34
|
+
// US locale (default)
|
|
35
|
+
expect(formatCurrency('1234.56', 'USD', 'en-US')).toBe('$1,234.56');
|
|
36
|
+
});
|
|
37
|
+
test('validates and normalizes numeric precision', () => {
|
|
38
|
+
// Numbers with valid precision are formatted correctly
|
|
39
|
+
expect(formatCurrency(10.5, 'USD')).toBe('$10.50');
|
|
40
|
+
expect(formatCurrency(10.99, 'USD')).toBe('$10.99');
|
|
41
|
+
});
|
|
42
|
+
test('throws on invalid string amounts', () => {
|
|
43
|
+
expect(() => formatCurrency('invalid', 'USD')).toThrow('Invalid amount format');
|
|
44
|
+
expect(() => formatCurrency('10.000', 'USD')).toThrow('Invalid amount format');
|
|
45
|
+
expect(() => formatCurrency('$10.00', 'USD')).toThrow('Invalid amount format');
|
|
46
|
+
});
|
|
47
|
+
test('throws on invalid numeric amounts', () => {
|
|
48
|
+
expect(() => formatCurrency(NaN, 'USD')).toThrow('Amount must be a finite number');
|
|
49
|
+
expect(() => formatCurrency(Infinity, 'USD')).toThrow('Amount must be a finite number');
|
|
50
|
+
expect(() => formatCurrency(10.999, 'USD')).toThrow('Invalid amount format');
|
|
51
|
+
});
|
|
52
|
+
test('uses en-US locale by default', () => {
|
|
53
|
+
expect(formatCurrency('1234.56', 'USD')).toBe('$1,234.56');
|
|
54
|
+
});
|
|
55
|
+
test('handles large amounts', () => {
|
|
56
|
+
expect(formatCurrency('1000000', 'USD')).toBe('$1,000,000.00');
|
|
57
|
+
expect(formatCurrency('1000000.99', 'USD')).toBe('$1,000,000.99');
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
describe('safeFormatCurrency', () => {
|
|
61
|
+
test('returns success for valid string amounts', () => {
|
|
62
|
+
const result = safeFormatCurrency('10.50', 'USD');
|
|
63
|
+
expect(result.success).toBe(true);
|
|
64
|
+
if (result.success) {
|
|
65
|
+
expect(result.output).toBe('$10.50');
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
test('returns success for valid numeric amounts', () => {
|
|
69
|
+
const result = safeFormatCurrency(10.5, 'USD');
|
|
70
|
+
expect(result.success).toBe(true);
|
|
71
|
+
if (result.success) {
|
|
72
|
+
expect(result.output).toBe('$10.50');
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
test('returns error for invalid string amounts', () => {
|
|
76
|
+
const result = safeFormatCurrency('invalid', 'USD');
|
|
77
|
+
expect(result.success).toBe(false);
|
|
78
|
+
if (!result.success) {
|
|
79
|
+
expect(result.error).toBe('Invalid amount format: "invalid"');
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
test('returns error for invalid numeric amounts', () => {
|
|
83
|
+
const result = safeFormatCurrency(NaN, 'USD');
|
|
84
|
+
expect(result.success).toBe(false);
|
|
85
|
+
if (!result.success) {
|
|
86
|
+
expect(result.error).toBe('Amount must be a finite number');
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
test('returns error for excessive precision', () => {
|
|
90
|
+
const result = safeFormatCurrency(10.999, 'USD');
|
|
91
|
+
expect(result.success).toBe(false);
|
|
92
|
+
if (!result.success) {
|
|
93
|
+
expect(result.error).toBe('Invalid amount format: "10.999"');
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
//# sourceMappingURL=currency.test.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"currency.test.mjs","sourceRoot":"","sources":["../../src/lang/currency.test.mts"],"names":[],"mappings":"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"}
|
package/dist/lang/index.d.mts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../../src/lang/index.mts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,sBAAsB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../../src/lang/index.mts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,sBAAsB,CAAC"}
|
package/dist/lang/index.mjs
CHANGED
package/dist/lang/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sourceRoot":"","sources":["../../src/lang/index.mts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,sBAAsB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.mjs","sourceRoot":"","sources":["../../src/lang/index.mts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,sBAAsB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nr1e/commons",
|
|
3
3
|
"description": "Common utilities for TypeScript projects",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.3",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "NR1E, Inc.",
|
|
7
7
|
"publishConfig": {
|
|
@@ -17,10 +17,10 @@
|
|
|
17
17
|
],
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@eslint/js": "latest",
|
|
20
|
-
"@types/node": "^
|
|
20
|
+
"@types/node": "^25.0.10",
|
|
21
21
|
"@types/uuid": "^10.0.0",
|
|
22
|
-
"eslint": "9.
|
|
23
|
-
"prettier": "3.
|
|
22
|
+
"eslint": "9.39.2",
|
|
23
|
+
"prettier": "3.8.1",
|
|
24
24
|
"typescript": "5.4.5",
|
|
25
25
|
"typescript-eslint": "8.38.0",
|
|
26
26
|
"vitest": "4.0.6"
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"prebuild": "prettier --check . && eslint .",
|
|
73
73
|
"build": "tsc",
|
|
74
74
|
"watch": "tsc -w",
|
|
75
|
-
"test": "vitest run",
|
|
75
|
+
"test": "vitest run src",
|
|
76
76
|
"clean": "rm -rf dist",
|
|
77
77
|
"fmt": "prettier --write ."
|
|
78
78
|
}
|