nhb-toolbox
Version:
A versatile collection of smart, efficient, and reusable utility functions, classes and types for everyday development needs.
218 lines (217 loc) • 10.3 kB
TypeScript
import type { Maybe } from '../types/index';
import type { LooseLiteral } from '../utils/types';
import type { BN_DIGITS, CURRENCY_CODES, CURRENCY_LOCALES, FRANKFURTER_CURRENCIES, LOCALE_CODES, PREFIX_MULTIPLIERS, UNITS } from './constants';
import type { Unit } from './Unit';
/** Enumerate & Enumerate Internal: builds a union of all numbers from 0 to N - 1 */
type $EnumerateInternal<N extends number, Acc extends number[] = []> = Acc['length'] extends N ? Acc[number] : $EnumerateInternal<N, [...Acc, Acc['length']]>;
/**
* * Builds a union of numeric literals from `0` to `N - 1`.
*
* @remarks
* - This utility supports ranges up to 998 due to TypeScript recursion limits.
*
* @example
* type U = Enumerate<3>; // 0 | 1 | 2
*/
export type Enumerate<N extends number> = $EnumerateInternal<N>;
/** Helper: Add 1 to a number */
type $AddOne<N extends number, Acc extends unknown[] = []> = Acc['length'] extends N ? [...Acc, unknown]['length'] : $AddOne<N, [...Acc, unknown]>;
/**
*
* * Creates a union type of all numeric literals starting from `From` up to `To`.
*
* @example
* type R = NumberRange<2, 5>; // 2 | 3 | 4 5
* type N = NumberRange<0, 998>; // 0 | 1 | 2 | ... | 998
*
* @remarks
* - This utility supports ranges up to 998 due to TypeScript recursion limits.
* - `From` and `To` both are inclusive, — so `NumberRange<0, 998>` generates `0` to `998`.
* - Result is a union type, not a tuple or array.
*
* @generic From - A number from 0-998, start of the range.
* @generic To - A number from 1-998, end of the range.
* @returns A union of numeric literal types from `From` to `To - 1`.
*/
export type NumberRange<From extends number, To extends number> = Exclude<Enumerate<$AddOne<To>>, Enumerate<From>>;
/** - Number value in percentage `(0% - 100%)` without `%` symbol. */
export type Percent = Enumerate<101>;
/** - Options for random number generator */
export interface RandomNumberOptions {
/** Minimum number to start with. */
min?: number;
/** Maximum number to end with. */
max?: number;
/** Whether to include the minimum number. */
includeMin?: boolean;
/** Whether to include the maximum number. */
includeMax?: boolean;
}
/** - Decimal options for converting to decimal */
export interface DecimalOptions<T extends Maybe<boolean> = false> {
/** Number of decimal places to round to. Defaults to `2`. */
decimalPlaces?: number;
/** If the return value is in `string` or `number`. Defaults to `false`. */
isString?: T;
}
/** - Converted decimal type either `number` or `string`. */
export type ConvertedDecimal<T> = T extends true ? `${number}` : number;
/** - Type of numbers to generate */
export type NumberType = 'any' | 'natural' | 'odd' | 'even' | 'prime' | 'random';
/** - Options for generating numbers in a range */
export interface RangeOptions<T extends boolean = false> extends RandomNumberOptions {
/** Separator for the string format if `getAsString` is `true`. Defaults to `", "`. */
separator?: T extends true ? string : never;
/** The multiples of which number to consider in the result. */
multiplesOf?: number;
/** The format for the result - `{ getAsString: true }` returns strings with custom separator and `false` returns array of numbers. Default is `false`. */
getAsString?: T;
}
/** - The return type of the `getNumbersInRange` function */
export type RangedNumbers<T extends boolean = false> = T extends true ? string : number[];
/** List of ISO 4217 currency codes. */
export type CurrencyCode = keyof typeof CURRENCY_LOCALES | (typeof CURRENCY_CODES)[number];
/** - List of all supported BCP 47 locales */
export type LocaleCode = (typeof CURRENCY_LOCALES)[keyof typeof CURRENCY_LOCALES] | (typeof LOCALE_CODES)[number];
/** * Fiat currencies supported by Frankfurter API */
export type FrankFurterCurrency = (typeof FRANKFURTER_CURRENCIES)[number];
export type { FrankFurterCurrency as SupportedCurrency };
/** - Options for `convert` method in `Currency` class */
export interface ConvertOptions {
/** A manual exchange rate to use if the API call fails. */
fallbackRate?: number;
/** If true, bypasses the cache and fetches fresh rate. */
forceRefresh?: boolean;
}
/** Type Interface for API Response from `api.frankfurter.app` */
export interface FrankFurter {
amount: number;
base: FrankFurterCurrency;
date: string;
rates: Record<FrankFurterCurrency, number>;
}
/** * Options to calculate what percentage a `part` is of a `total`. */
export interface GetPercentOptions {
/** Mode to calculate percentage from `part` and `total` */
mode: 'get-percent';
/** The part value (e.g., 25 out of 100) */
part: number;
/** The total value representing 100% */
total: number;
}
/** * Options to calculate a value from a `percentage` of a `total`. */
export interface GetValueOptions {
/** Mode to calculate value from `percentage` and `total` */
mode: 'get-value';
/** The percentage (e.g., 25%) */
percentage: number;
/** The total value representing 100% */
total: number;
}
/** * Options to calculate the original total from a known `value` and `percentage`. */
export interface GetOriginalOptions {
/** Mode to calculate original total from `value` and `percentage` */
mode: 'get-original';
/** The percentage the `value` represents */
percentage: number;
/** The known value that is a percentage of the original total */
value: number;
}
/** * Calculates the percentage change from `oldValue` to `newValue`. */
export interface GetChangeOptions {
/** Mode to calculate percentage change from `oldValue` to `newValue` */
mode: 'get-change-percent';
/** The original value before the change */
oldValue: number;
/** The new value after the change */
newValue: number;
}
/** * Applies a percentage increase or decrease to a `baseValue`. */
export interface ApplyChangeOptions {
/** Mode to apply percentage change to `baseValue` */
mode: 'apply-percent-change';
/** The base value to apply the percentage change to */
baseValue: number;
/** The percentage change to apply (positive or negative) */
percentage: number;
}
/** * Calculates the absolute percentage difference between two values. */
export interface GetDifferenceOptions {
/** Mode to calculate percentage difference between `value1` and `value2` */
mode: 'get-percent-difference';
/** The first value to compare */
value1: number;
/** The second value to compare */
value2: number;
}
/** * Calculates the inverse percentage: what percent `total` is of `part`. */
export interface InversePercentageOptions {
/** Mode to calculate inverse percentage from `part` and `total` */
mode: 'inverse-percent';
/** The part value to calculate inverse percentage from */
part: number;
/** The total value to calculate inverse percentage of */
total: number;
}
/** * Options for calculating percentages and related values. */
export type PercentageOptions = (GetPercentOptions | GetValueOptions | GetOriginalOptions | GetChangeOptions | ApplyChangeOptions | GetDifferenceOptions | InversePercentageOptions) & {
/** The number of decimal places to round the result to. Defaults to `3`. */
roundTo?: number;
};
/** * Static methods from `Unit` class that accept a single number argument and return a number. */
export type UnitNumberMethods = {
[K in keyof typeof Unit]: (typeof Unit)[K] extends (value: number) => number ? K : never;
}[keyof typeof Unit];
/** - Short forms of units */
export type UnitKey = keyof typeof UNITS;
/** - Labels for the units */
export type UnitLabel = (typeof UNITS)[UnitKey];
/** - Prefixes for SI units */
export type SIPrefix = keyof typeof PREFIX_MULTIPLIERS;
/** Roman numerals representing only the thousand (1000, 2000 and 3000) */
type $RomanThousands = '' | 'M' | 'MM' | 'MMM';
/** Roman numerals representing only the hundreds (100, 200, ... 900) */
type $RomanHundreds = '' | 'C' | 'CC' | 'CCC' | 'CD' | 'D' | 'DC' | 'DCC' | 'DCCC' | 'CM';
/** Roman numerals representing only the tens (10, 20, ... 90) */
type $RomanTens = '' | 'X' | 'XX' | 'XXX' | 'XL' | 'L' | 'LX' | 'LXX' | 'LXXX' | 'XC';
/** Roman numerals representing only the ones (1-9) */
type $RomanOnes = '' | 'I' | 'II' | 'III' | 'IV' | 'V' | 'VI' | 'VII' | 'VIII' | 'IX';
/** Roman numerals representing the combination of thousands, hundreds, tens and ones */
type $RawRoman = `${$RomanThousands}${$RomanHundreds}${$RomanTens}${$RomanOnes}`;
/**
* * Literal type representing every valid Roman numeral (uppercase) from 1 to 3999 (I .. MMMCMXCIX).
*
* @example
* const a: RomanCapital = "MMXXV"; // ✅ OK
* const b: RomanCapital = "MMMM"; // 🛑 Error (4000 not allowed)
* const c: RomanCapital = ""; // 🛑 Error (0 not allowed)
*/
export type RomanCapital = Exclude<$RawRoman, ''>;
/**
* * Strict Roman numeral in both literal lower and uppercase (1-3999).
*
* @example
* const a: RomanNumeral = 'xiv'; // ✅ Lowercase: OK
* const b: RomanNumeral = 'MMX'; // ✅ Uppercase: OK
* const c: RomanNumeral = 'xyz'; // 🛑 Invalid: Error (xyz not allowed)
*/
export type RomanNumeral = RomanCapital | Lowercase<RomanCapital>;
/**
* * Comprehensive valid Roman numeral in both literal lower and uppercase (1-3999) & any string type.
*
* @example
* const a: LooseRomanNumeral = 'xiv'; // ✅ IntelliSense suggests Roman letters
* const b: LooseRomanNumeral = 'MMX'; // ✅ Supported
* const c: LooseRomanNumeral = 'xyz'; // ⚠️ Allowed only via LooseLiteral fallback
*
* @remarks
* - Combines {@link RomanCapital} and its lowercase variants, see {@link RomanNumeral}.
* - The {@link LooseLiteral} wrapper allows non-literal strings (e.g., variables) without losing IntelliSense for literals.
*/
export type LooseRomanNumeral = LooseLiteral<RomanNumeral>;
/** Bangla digits from `০-৯` */
export type BanglaDigit = keyof typeof BN_DIGITS;
/** Bangla digits from `১-৯` */
export type $BnOnes = Exclude<BanglaDigit, '০'>;
/** Result type for Bangla digit conversion based on `force` flag */
export type BnDigitResult<Force extends boolean> = Force extends true ? number : string;