refactor progress

This commit is contained in:
Hampus Kraft
2026-02-17 12:22:36 +00:00
parent cb31608523
commit d5abd1a7e4
8257 changed files with 1190207 additions and 761040 deletions

View File

@@ -0,0 +1,12 @@
{
"name": "@fluxer/number_utils",
"version": "0.0.0",
"type": "module",
"dependencies": {
"@fluxer/constants": "workspace:*"
},
"exports": {
"./src/*": "./src/*",
"./*": "./*"
}
}

View File

@@ -0,0 +1,24 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {Locales} from '@fluxer/constants/src/Locales';
export const DEFAULT_COMPACT_MAX_FRACTION_DIGITS = 1;
export const DEFAULT_NUMBER_FALLBACK = 0;
export const DEFAULT_NUMBER_LOCALE = Locales.EN_US;

View File

@@ -0,0 +1,43 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
const numberFormatterCache = new Map<string, Intl.NumberFormat>();
export function getNumberFormatter(
locale: string,
numberFormatOptions: Intl.NumberFormatOptions = {},
): Intl.NumberFormat {
const cacheKey = buildFormatterCacheKey(locale, numberFormatOptions);
const cachedFormatter = numberFormatterCache.get(cacheKey);
if (cachedFormatter !== undefined) {
return cachedFormatter;
}
const formatter = new Intl.NumberFormat(locale, numberFormatOptions);
numberFormatterCache.set(cacheKey, formatter);
return formatter;
}
function buildFormatterCacheKey(locale: string, numberFormatOptions: Intl.NumberFormatOptions): string {
const optionEntries = Object.entries(numberFormatOptions)
.filter(([, value]) => value !== undefined)
.sort(([left], [right]) => left.localeCompare(right));
const optionKey = optionEntries.map(([key, value]) => `${key}:${String(value)}`).join('|');
return `${locale}|${optionKey}`;
}

View File

@@ -0,0 +1,204 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {
DEFAULT_COMPACT_MAX_FRACTION_DIGITS,
DEFAULT_NUMBER_FALLBACK,
DEFAULT_NUMBER_LOCALE,
} from '@fluxer/number_utils/src/NumberConstants';
import {getNumberFormatter} from '@fluxer/number_utils/src/NumberFormatterCache';
import {parseNumberInput} from '@fluxer/number_utils/src/NumberParsing';
import type {
BoundCompactNumberFormatOptions,
BoundCurrencyNumberFormatOptions,
CompactNumberFormatOptions,
CurrencyNumberFormatOptions,
INumberFormatter,
NumberFormatBaseOptions,
NumberFormatOptions,
NumberFormatterFactoryOptions,
NumberInput,
} from '@fluxer/number_utils/src/NumberTypes';
interface ResolvedNumberFormatBaseOptions {
locale: string;
fallbackValue: number;
}
interface CompactFormatOptionsInput {
maximumFractionDigits?: number;
minimumFractionDigits?: number;
}
interface CurrencyFormatOptionsInput {
currency: string;
numberFormatOptions?: Omit<Intl.NumberFormatOptions, 'style' | 'currency'>;
}
function resolveBaseOptions(options?: NumberFormatBaseOptions): ResolvedNumberFormatBaseOptions {
return {
locale: options?.locale ?? DEFAULT_NUMBER_LOCALE,
fallbackValue: options?.fallbackValue ?? DEFAULT_NUMBER_FALLBACK,
};
}
function resolveNumberFormatOptions(optionsOrLocale: NumberFormatOptions | string | undefined): NumberFormatOptions {
if (typeof optionsOrLocale === 'string') {
return {locale: optionsOrLocale};
}
return optionsOrLocale ?? {};
}
function resolveCompactFormatOptions(
optionsOrLocale: CompactNumberFormatOptions | string | undefined,
maximumFractionDigits: number | undefined,
): CompactNumberFormatOptions {
if (typeof optionsOrLocale === 'string') {
return {
locale: optionsOrLocale,
maximumFractionDigits,
};
}
if (optionsOrLocale === undefined) {
if (maximumFractionDigits === undefined) {
return {};
}
return {maximumFractionDigits};
}
if (maximumFractionDigits === undefined) {
return optionsOrLocale;
}
return {
...optionsOrLocale,
maximumFractionDigits: optionsOrLocale.maximumFractionDigits ?? maximumFractionDigits,
};
}
function resolveCurrencyFormatOptions(
optionsOrCurrency: CurrencyNumberFormatOptions | string,
locale: string | undefined,
): CurrencyNumberFormatOptions {
if (typeof optionsOrCurrency === 'string') {
return {
currency: optionsOrCurrency,
locale,
};
}
return optionsOrCurrency;
}
function formatNumberValue(
value: NumberInput,
resolvedOptions: ResolvedNumberFormatBaseOptions,
numberFormatOptions: Intl.NumberFormatOptions = {},
): string {
const parsedValue = parseNumberInput(value, resolvedOptions.fallbackValue);
return getNumberFormatter(resolvedOptions.locale, numberFormatOptions).format(parsedValue);
}
function buildCompactFormatOptions(options: CompactFormatOptionsInput): Intl.NumberFormatOptions {
const numberFormatOptions: Intl.NumberFormatOptions = {
notation: 'compact',
maximumFractionDigits: options.maximumFractionDigits ?? DEFAULT_COMPACT_MAX_FRACTION_DIGITS,
};
if (options.minimumFractionDigits !== undefined) {
numberFormatOptions.minimumFractionDigits = options.minimumFractionDigits;
}
return numberFormatOptions;
}
function buildCurrencyFormatOptions(options: CurrencyFormatOptionsInput): Intl.NumberFormatOptions {
return {
...options.numberFormatOptions,
style: 'currency',
currency: options.currency,
};
}
export function parseNumber(value: NumberInput, options: NumberFormatBaseOptions = {}): number {
const resolvedOptions = resolveBaseOptions(options);
return parseNumberInput(value, resolvedOptions.fallbackValue);
}
export function formatNumber(value: NumberInput, locale?: string): string;
export function formatNumber(value: NumberInput, options?: NumberFormatOptions): string;
export function formatNumber(value: NumberInput, optionsOrLocale: NumberFormatOptions | string = {}): string {
const options = resolveNumberFormatOptions(optionsOrLocale);
const resolvedOptions = resolveBaseOptions(options);
return formatNumberValue(value, resolvedOptions, options.numberFormatOptions);
}
export function formatCompactNumber(value: NumberInput, locale?: string, maximumFractionDigits?: number): string;
export function formatCompactNumber(value: NumberInput, options?: CompactNumberFormatOptions): string;
export function formatCompactNumber(
value: NumberInput,
optionsOrLocale: CompactNumberFormatOptions | string = {},
maximumFractionDigits?: number,
): string {
const options = resolveCompactFormatOptions(optionsOrLocale, maximumFractionDigits);
const resolvedOptions = resolveBaseOptions(options);
return formatNumberValue(value, resolvedOptions, buildCompactFormatOptions(options));
}
export function formatCurrency(value: NumberInput, currency: string, locale?: string): string;
export function formatCurrency(value: NumberInput, options: CurrencyNumberFormatOptions): string;
export function formatCurrency(
value: NumberInput,
optionsOrCurrency: CurrencyNumberFormatOptions | string,
locale?: string,
): string {
const options = resolveCurrencyFormatOptions(optionsOrCurrency, locale);
const resolvedOptions = resolveBaseOptions(options);
return formatNumberValue(value, resolvedOptions, buildCurrencyFormatOptions(options));
}
export function createNumberFormatter(options: NumberFormatterFactoryOptions = {}): INumberFormatter {
const resolvedOptions = resolveBaseOptions(options);
function parse(value: NumberInput): number {
return parseNumberInput(value, resolvedOptions.fallbackValue);
}
function format(value: NumberInput, numberFormatOptions: Intl.NumberFormatOptions = {}): string {
return formatNumberValue(value, resolvedOptions, numberFormatOptions);
}
function formatCompact(value: NumberInput, compactOptions: BoundCompactNumberFormatOptions = {}): string {
return formatNumberValue(value, resolvedOptions, buildCompactFormatOptions(compactOptions));
}
function formatCurrency(value: NumberInput, currencyOptions: BoundCurrencyNumberFormatOptions): string {
return formatNumberValue(value, resolvedOptions, buildCurrencyFormatOptions(currencyOptions));
}
return {
parse,
format,
formatCompact,
formatCurrency,
};
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {DEFAULT_NUMBER_FALLBACK} from '@fluxer/number_utils/src/NumberConstants';
import type {NumberInput} from '@fluxer/number_utils/src/NumberTypes';
export function parseNumberInput(value: NumberInput, fallbackValue: number = DEFAULT_NUMBER_FALLBACK): number {
if (typeof value === 'number') {
if (Number.isFinite(value)) {
return value;
}
return fallbackValue;
}
if (typeof value === 'string') {
const trimmedValue = value.trim();
if (trimmedValue === '') {
return fallbackValue;
}
const parsedValue = Number(trimmedValue);
if (Number.isFinite(parsedValue)) {
return parsedValue;
}
}
return fallbackValue;
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
export type NumberInput = number | string | null | undefined;
export interface NumberFormatBaseOptions {
locale?: string;
fallbackValue?: number;
}
export interface NumberFormatOptions extends NumberFormatBaseOptions {
numberFormatOptions?: Intl.NumberFormatOptions;
}
export interface CompactNumberFormatOptions extends NumberFormatBaseOptions {
maximumFractionDigits?: number;
minimumFractionDigits?: number;
}
export interface CurrencyNumberFormatOptions extends NumberFormatBaseOptions {
currency: string;
numberFormatOptions?: Omit<Intl.NumberFormatOptions, 'style' | 'currency'>;
}
export interface NumberFormatterFactoryOptions extends NumberFormatBaseOptions {}
export interface BoundCompactNumberFormatOptions {
maximumFractionDigits?: number;
minimumFractionDigits?: number;
}
export interface BoundCurrencyNumberFormatOptions {
currency: string;
numberFormatOptions?: Omit<Intl.NumberFormatOptions, 'style' | 'currency'>;
}
export interface INumberFormatter {
parse(value: NumberInput): number;
format(value: NumberInput, numberFormatOptions?: Intl.NumberFormatOptions): string;
formatCompact(value: NumberInput, options?: BoundCompactNumberFormatOptions): string;
formatCurrency(value: NumberInput, options: BoundCurrencyNumberFormatOptions): string;
}

View File

@@ -0,0 +1,3 @@
{
"extends": "../../tsconfigs/package.json"
}