iamfns
v1.18.0
Published
A collection of lightweight, type-safe utility functions for TypeScript.
Readme
iamfns
A collection of lightweight, type-safe utility functions for TypeScript.
Installation
bun add iamfnsTable of Contents
- Numbers
- abbreviateNumber
- afterDecimals
- adjust
- add
- calcPercent
- calcVolatility
- parseEnvNumber
- subtract
- multiply
- divide
- genId
- genIntId
- pFloat
- roundDecimals
- clamp
- clamp01
- clampFinite
- clampFiniteInteger
- clampFiniteTruncatedInteger
- clampOptionalBounds
- clampUnknownInteger
- isFiniteNumber
- isPositiveFiniteNumber
- finiteOrZero
- positiveFiniteOrZero
- toFiniteNumber
- parseFiniteNumber
- Array buffers
- Arrays
- Objects
- Records
- isRecord
- isNonArrayRecord
- getRecordString
- getRecordStringOr
- getRecordNonEmptyString
- getRecordTrimmedNonEmptyString
- getRecordIdString
- getRecordNumber
- getRecordNumberOr
- getRecordBoolean
- getRecordLooseBoolean
- getRecordArray
- getRecordFirstString
- getRecordFirstNumber
- getRecordFirstIdString
- requireRecord
- requireRecordString
- requireRecordNumber
- JSON
- Strings
- Async
- Events
- Inspectable values
- Formatting
- Draft numbers
- Sort
Numbers
abbreviateNumber
Formats large numbers with K, M, B, T suffixes for better readability.
function abbreviateNumber(n: number): number | stringParameters:
n- The number to abbreviate
Returns: The original number if < 1000, otherwise a string with suffix (K, M, B, T)
Example:
import { abbreviateNumber } from 'iamfns';
// Numbers < 1000 return as-is
abbreviateNumber(500); // => 500
abbreviateNumber(999); // => 999
// Thousands (K)
abbreviateNumber(1000); // => '1k'
abbreviateNumber(1500); // => '1.5k'
abbreviateNumber(50000); // => '50k'
// Millions (M)
abbreviateNumber(1000000); // => '1m'
abbreviateNumber(1500000); // => '1.5m'
// Billions (B)
abbreviateNumber(1000000000); // => '1b'
abbreviateNumber(1500000000); // => '1.5b'
// Trillions (T)
abbreviateNumber(1000000000000); // => '1t'
// Negative numbers
abbreviateNumber(-1500); // => '-1.5k'
abbreviateNumber(-1000000); // => '-1m'afterDecimals
Returns the number of digits after the decimal point. Handles integers, floats, string inputs, and scientific notation.
function afterDecimals(num: number | string): numberParameters:
num- A number or string representation of a number
Returns: The count of digits after the decimal point
Example:
import { afterDecimals } from 'iamfns';
// Integers return 0
afterDecimals(5); // => 0
afterDecimals(100); // => 0
// Floats return decimal count
afterDecimals(5.25); // => 2
afterDecimals(3.14159); // => 5
// String inputs
afterDecimals('5.25'); // => 2
afterDecimals('0.00001'); // => 5
// Scientific notation
afterDecimals(1e-5); // => 5
afterDecimals(1.23e-7); // => 7adjust
Rounds a value to the nearest step increment. Useful for price/quantity adjustments.
function adjust(value: number, step: number | string): numberParameters:
value- The number to adjuststep- The step increment (e.g.,0.01for cents)
Returns: The value rounded to the nearest step
Example:
import { adjust } from 'iamfns';
adjust(10.123, 0.1); // => 10.1
adjust(10.123, 0.01); // => 10.12
adjust(10.126, 0.01); // => 10.13
adjust(10.6, 1); // => 11
adjust(10.4, 1); // => 10
// String step values
adjust(10.123, '0.1'); // => 10.1add
Adds two numbers with correct decimal precision, avoiding floating point errors.
function add(a: number, b: number): numberParameters:
a- First numberb- Second number
Returns: The sum with correct precision
Example:
import { add } from 'iamfns';
// Native JS: 0.1 + 0.2 = 0.30000000000000004
add(0.1, 0.2); // => 0.3
add(0.01, 0.02); // => 0.03
add(0.3, 0.6); // => 0.9
add(1, 0.001); // => 1.001calcPercent
Calculates the percentage change between two values.
function calcPercent({ now, start }: { now: number; start: number }): numberParameters:
now- The current valuestart- The starting/reference value
Returns: The percentage change (positive for increase, negative for decrease)
Example:
import { calcPercent } from 'iamfns';
// Positive change (50% increase)
calcPercent({ now: 150, start: 100 }); // => 50
// Negative change (50% decrease)
calcPercent({ now: 50, start: 100 }); // => -50
// No change
calcPercent({ now: 100, start: 100 }); // => 0
// Both zero
calcPercent({ now: 0, start: 0 }); // => 0
// Division by zero (start is 0)
calcPercent({ now: 100, start: 0 }); // => Infinity
// Decimal values
calcPercent({ now: 1.5, start: 1 }); // => 50calcVolatility
Calculates the volatility (standard deviation of percentage changes) for a series of values. Useful for measuring price fluctuations in financial data.
function calcVolatility(values: number[]): numberParameters:
values- An array of numeric values (e.g., prices over time)
Returns: The volatility as standard deviation of percentage changes
Example:
import { calcVolatility } from 'iamfns';
// Varying changes = higher volatility
calcVolatility([100, 110, 100]); // => ~13.49
// Price series with fluctuations
calcVolatility([100, 105, 102, 108, 106, 110]); // => > 0
// Consistent percentage changes = no volatility
calcVolatility([100, 200, 400]); // => 0 (doubling each time)
// Constant values = no volatility
calcVolatility([100, 100, 100]); // => 0
// Need at least 3 values for meaningful result
calcVolatility([100, 110]); // => 0
calcVolatility([100]); // => 0subtract
Subtracts two numbers with correct decimal precision, avoiding floating point errors.
function subtract(a: number, b: number): numberParameters:
a- Number to subtract fromb- Number to subtract
Returns: The difference with correct precision
Example:
import { subtract } from 'iamfns';
// Native JS: 0.3 - 0.1 = 0.19999999999999998
subtract(0.3, 0.1); // => 0.2
subtract(0.03, 0.01); // => 0.02
subtract(2, 0.001); // => 1.999
subtract(0.1, 0.3); // => -0.2multiply
Multiplies two numbers with correct decimal precision, avoiding floating point errors.
function multiply(a: number, b: number): numberParameters:
a- First numberb- Second number
Returns: The product with correct precision
Example:
import { multiply } from 'iamfns';
// Native JS: 0.1 * 0.2 = 0.020000000000000004
multiply(0.1, 0.2); // => 0.02
multiply(0.3, 0.3); // => 0.09
multiply(10, 0.123); // => 1.23
multiply(2, 0.5); // => 1divide
Divides two numbers with correct decimal precision, avoiding floating point errors.
function divide(a: number, b: number): numberParameters:
a- Dividendb- Divisor
Returns: The quotient with correct precision
Example:
import { divide } from 'iamfns';
divide(0.3, 0.1); // => 3
divide(0.12, 0.1); // => 1.2
divide(1, 0.5); // => 2
divide(10.123, 10); // => 1.012genId
Generates a random 16-character alphanumeric ID using base-36 encoding.
function genId(): stringReturns: A 16-character string containing lowercase letters (a-z) and digits (0-9)
Example:
import { genId } from 'iamfns';
genId(); // => 'k7x2m9p1q4w8e3r6'
genId(); // => 'a1b2c3d4e5f6g7h8'
genId(); // => 'z9y8x7w6v5u4t3s2'
// Use for unique identifiers
const userId = genId();
const sessionId = genId();genIntId
Generates a random integer ID between 0 and 999,999.
function genIntId(): numberReturns: A random integer from 0 to 999,999
Example:
import { genIntId } from 'iamfns';
genIntId(); // => 482957
genIntId(); // => 139482
genIntId(); // => 7234parseEnvNumber
Parses an environment variable string into a positive integer, with a safe fallback for invalid input.
function parseEnvNumber(value: string | undefined, fallback: number): numberParameters:
value- The environment value to parsefallback- Value returned when input is missing, invalid, or not a positive integer
Returns: A positive integer parsed from value, or fallback
Example:
import { parseEnvNumber } from 'iamfns';
// Valid positive integers
parseEnvNumber('3000', 8080); // => 3000
parseEnvNumber('42', 10); // => 42
// Invalid or non-positive inputs use fallback
parseEnvNumber(undefined, 8080); // => 8080
parseEnvNumber('', 8080); // => 8080
parseEnvNumber('0', 8080); // => 8080
parseEnvNumber('-5', 8080); // => 8080
parseEnvNumber('abc', 8080); // => 8080pFloat
Parses a number from a string with support for comma decimal separators (European format). Returns NaN for undefined or invalid input.
function pFloat(value?: number | string): numberParameters:
value- A number or string to parse (optional)
Returns: The parsed number, or NaN if invalid/undefined
Example:
import { pFloat } from 'iamfns';
// Numbers pass through
pFloat(42); // => 42
pFloat(3.14); // => 3.14
// Standard decimal strings
pFloat('42'); // => 42
pFloat('3.14'); // => 3.14
// European format (comma as decimal separator)
pFloat('3,14'); // => 3.14
pFloat('10,50'); // => 10.5
// Handles undefined safely
pFloat(undefined); // => NaN
// Invalid strings
pFloat('invalid'); // => NaN
pFloat(''); // => NaN
// Scientific notation
pFloat('1e6'); // => 1000000
pFloat('2.5e3'); // => 2500roundDecimals
Rounds a number to a specified number of decimal places.
function roundDecimals(num: number, decimals: number = 8): numberParameters:
num- The number to rounddecimals- The number of decimal places (default: 8)
Returns: The rounded number
Example:
import { roundDecimals } from 'iamfns';
// Round to integer
roundDecimals(3.14, 0); // => 3
roundDecimals(3.5, 0); // => 4
// Round to 1 decimal
roundDecimals(3.14, 1); // => 3.1
roundDecimals(3.15, 1); // => 3.2
// Round to 2 decimals
roundDecimals(3.14159, 2); // => 3.14
roundDecimals(3.145, 2); // => 3.15
// Round to many decimals
roundDecimals(3.14159265359, 4); // => 3.1416
// Handles floating point issues
roundDecimals(0.1 + 0.2, 1); // => 0.3
// Negative numbers
roundDecimals(-3.14, 1); // => -3.1clamp
Clamps a number to an inclusive range. If max is less than min, it returns min.
function clamp(value: number, min: number, max: number): numberExample:
import { clamp } from 'iamfns';
clamp(-1, 0, 10); // => 0
clamp(5, 0, 10); // => 5
clamp(11, 0, 10); // => 10clamp01
Clamps a number to the range 0 to 1.
function clamp01(value: number): numberExample:
import { clamp01 } from 'iamfns';
clamp01(-0.5); // => 0
clamp01(0.75); // => 0.75
clamp01(2); // => 1clampFinite
Clamps a finite number to a range. It returns fallback for NaN and infinities. The default fallback is min.
function clampFinite(
value: number,
min: number,
max: number,
fallback?: number,
): numberExample:
import { clampFinite } from 'iamfns';
clampFinite(5, 0, 10); // => 5
clampFinite(Number.NaN, 0, 10); // => 0
clampFinite(Number.POSITIVE_INFINITY, 0, 10, 4); // => 4clampFiniteInteger
Rounds a finite number with Math.round, then clamps it to a range. Non-finite values use fallback, which defaults to min.
function clampFiniteInteger(
value: number,
min: number,
max: number,
fallback?: number,
): numberExample:
import { clampFiniteInteger } from 'iamfns';
clampFiniteInteger(6.4, 1, 10); // => 6
clampFiniteInteger(10.6, 1, 10); // => 10
clampFiniteInteger(Number.NaN, 1, 10); // => 1clampFiniteTruncatedInteger
Truncates a finite number with Math.trunc, then clamps it to a range. Non-finite values use fallback, which defaults to min.
function clampFiniteTruncatedInteger(
value: number,
min: number,
max: number,
fallback?: number,
): numberExample:
import { clampFiniteTruncatedInteger } from 'iamfns';
clampFiniteTruncatedInteger(6.9, 1, 10); // => 6
clampFiniteTruncatedInteger(-1.9, 1, 10); // => 1
clampFiniteTruncatedInteger(Number.NaN, 1, 10, 4); // => 4clampOptionalBounds
Clamps a number to the numeric bounds that you provide. An omitted bound is ignored.
function clampOptionalBounds(
value: number,
min?: number,
max?: number,
): numberExample:
import { clampOptionalBounds } from 'iamfns';
clampOptionalBounds(5, 0, 10); // => 5
clampOptionalBounds(-1, 0); // => 0
clampOptionalBounds(11, undefined, 10); // => 10clampUnknownInteger
Converts an unknown value to a number, truncates it, and clamps it to a range. It returns fallback when conversion produces a non-finite number.
function clampUnknownInteger(
value: unknown,
fallback: number,
min: number,
max: number,
): numberExample:
import { clampUnknownInteger } from 'iamfns';
clampUnknownInteger('6.9', 1, 1, 10); // => 6
clampUnknownInteger('12', 1, 1, 10); // => 10
clampUnknownInteger('abc', 4, 1, 10); // => 4isFiniteNumber
Checks whether a value is a finite number. It does not coerce strings.
function isFiniteNumber(value: unknown): value is numberExample:
import { isFiniteNumber } from 'iamfns';
isFiniteNumber(42); // => true
isFiniteNumber(Number.NaN); // => false
isFiniteNumber('42'); // => falseisPositiveFiniteNumber
Checks whether a value is a finite number greater than zero.
function isPositiveFiniteNumber(value: unknown): value is numberExample:
import { isPositiveFiniteNumber } from 'iamfns';
isPositiveFiniteNumber(1); // => true
isPositiveFiniteNumber(0); // => false
isPositiveFiniteNumber(-1); // => falsefiniteOrZero
Returns a finite number as-is, or 0 for any other value.
function finiteOrZero(value: unknown): numberExample:
import { finiteOrZero } from 'iamfns';
finiteOrZero(-3); // => -3
finiteOrZero(Number.NaN); // => 0
finiteOrZero('3'); // => 0positiveFiniteOrZero
Returns a positive finite number as-is, or 0 for any other value.
function positiveFiniteOrZero(value: unknown): numberExample:
import { positiveFiniteOrZero } from 'iamfns';
positiveFiniteOrZero(3); // => 3
positiveFiniteOrZero(0); // => 0
positiveFiniteOrZero(-3); // => 0toFiniteNumber
Converts a finite number or a numeric string to a number. It returns null for other values and non-finite results.
function toFiniteNumber(value: unknown): number | nullExample:
import { toFiniteNumber } from 'iamfns';
toFiniteNumber(12.5); // => 12.5
toFiniteNumber('12.5'); // => 12.5
toFiniteNumber('12px'); // => null
toFiniteNumber('abc'); // => nullparseFiniteNumber
Parses a finite number or numeric string with Number.parseFloat. It returns null when parsing fails.
function parseFiniteNumber(value: unknown): number | nullExample:
import { parseFiniteNumber } from 'iamfns';
parseFiniteNumber(12.5); // => 12.5
parseFiniteNumber('12.5px'); // => 12.5
parseFiniteNumber('abc'); // => nullArray buffers
arrayBufferToBase64
Encodes the bytes in an ArrayBuffer as a standard Base64 string.
function arrayBufferToBase64(bytes: ArrayBuffer): stringExample:
import { arrayBufferToBase64 } from 'iamfns';
const bytes = new Uint8Array([0x00, 0x0f, 0xff]).buffer;
arrayBufferToBase64(bytes); // => 'AA//'arrayBufferToHex
Encodes the bytes in an ArrayBuffer as a lowercase hexadecimal string. Each byte uses two characters.
function arrayBufferToHex(bytes: ArrayBuffer): stringExample:
import { arrayBufferToHex } from 'iamfns';
const bytes = new Uint8Array([0x00, 0x0f, 0xff]).buffer;
arrayBufferToHex(bytes); // => '000fff'Arrays
prependCapped
Prepends an item to an array and keeps at most maxItems items. It returns a new array and leaves the source unchanged.
function prependCapped<T>(
items: readonly T[],
item: T,
maxItems: number,
): T[]Example:
import { prependCapped } from 'iamfns';
prependCapped([2, 3, 4], 1, 3); // => [1, 2, 3]chunk
Splits an array into chunks of a specified size.
function chunk<T>(arr: T[], size: number): T[][]Parameters:
arr- The array to splitsize- The size of each chunk
Returns: A new array of chunks
Example:
import { chunk } from 'iamfns';
chunk([1, 2, 3, 4, 5], 2);
// => [[1, 2], [3, 4], [5]]
chunk(['a', 'b', 'c', 'd'], 3);
// => [['a', 'b', 'c'], ['d']]
chunk([1, 2, 3, 4, 5, 6], 2);
// => [[1, 2], [3, 4], [5, 6]]last
Returns the last element of an array.
function last<T>(array: T[]): T | undefinedParameters:
array- The source array
Returns: The last element, or undefined if the array is empty
Example:
import { last } from 'iamfns';
last([1, 2, 3]); // => 3
last(['a', 'b', 'c']); // => 'c'
last([42]); // => 42
last([]); // => undefinedorderBy
Sorts an array by one or more criteria with configurable sort orders. Supports property keys and custom iteratee functions.
function orderBy<T>(
arr: T[],
iteratees?: Array<keyof T | ((item: T) => any)>,
orders?: Array<'asc' | 'desc'>
): T[]Parameters:
arr- The array to sortiteratees- Array of property keys or functions to sort by (defaults to identity)orders- Array of sort directions:'asc'or'desc'(defaults to'asc')
Returns: A new sorted array
Example:
import { orderBy } from 'iamfns';
const users = [
{ name: 'Charlie', age: 30 },
{ name: 'Alice', age: 25 },
{ name: 'Bob', age: 35 }
];
// Sort by name ascending
orderBy(users, ['name'], ['asc']);
// => [{ name: 'Alice', age: 25 }, { name: 'Bob', age: 35 }, { name: 'Charlie', age: 30 }]
// Sort by age descending
orderBy(users, ['age'], ['desc']);
// => [{ name: 'Bob', age: 35 }, { name: 'Charlie', age: 30 }, { name: 'Alice', age: 25 }]
// Sort by multiple criteria
orderBy(users, ['age', 'name'], ['desc', 'asc']);
// Sort primitives (uses identity by default)
orderBy([3, 1, 2]);
// => [1, 2, 3]
// Sort with custom iteratee function
const words = ['apple', 'pear', 'banana', 'kiwi'];
orderBy(words, [(w) => w.length], ['desc']);
// => ['banana', 'apple', 'pear', 'kiwi']
// Mix property keys and functions
const items = [
{ name: 'aa', value: 2 },
{ name: 'a', value: 1 },
{ name: 'aaa', value: 1 }
];
orderBy(items, [(u) => u.name.length, 'value'], ['asc', 'desc']);take
Returns the first n elements from an array.
function take<T>(array: T[], n: number): T[]Parameters:
array- The source arrayn- The number of elements to take
Returns: A new array with the first n elements
Example:
import { take } from 'iamfns';
take([1, 2, 3, 4, 5], 3);
// => [1, 2, 3]
take(['a', 'b', 'c'], 2);
// => ['a', 'b']takeRight
Returns the last n elements from an array.
function takeRight<T>(array: T[], n: number): T[]Parameters:
array- The source arrayn- The number of elements to take from the end
Returns: A new array with the last n elements
Example:
import { takeRight } from 'iamfns';
takeRight([1, 2, 3, 4, 5], 3);
// => [3, 4, 5]
takeRight(['a', 'b', 'c'], 2);
// => ['b', 'c']uniq
Removes duplicate values from an array, preserving the first occurrence of each value. Supports arrays of strings, numbers, and booleans.
function uniq<T extends string | number | boolean>(arr: T[]): T[]Parameters:
arr- The array to deduplicate
Returns: A new array with duplicate values removed
Example:
import { uniq } from 'iamfns';
uniq([1, 2, 3, 4, 5]);
// => [1, 2, 3, 4, 5]
uniq(['a', 'b', 'c', 'a', 'd']);
// => ['a', 'b', 'c', 'd']
uniq([true, false, true, false]);
// => [true, false]uniqBy
Removes duplicate values from an array of objects based on a key or iteratee function, preserving the first occurrence of each unique value.
function uniqBy<T, K extends keyof T>(
arr: T[],
key: K | ((item: T) => unknown),
): T[]Parameters:
arr- The array to deduplicatekey- A property key or a function that returns the value to compare uniqueness by
Returns: A new array with duplicate entries removed, keeping the first occurrence
Example:
import { uniqBy } from 'iamfns';
// Deduplicate by object key
uniqBy([{ id: 1 }, { id: 2 }, { id: 1 }], 'id');
// => [{ id: 1 }, { id: 2 }]
// Deduplicate using an iteratee function
uniqBy([{ id: 1 }, { id: 2 }, { id: 1 }], (item) => item.id);
// => [{ id: 1 }, { id: 2 }]
// Deduplicate by a computed value
const users = [
{ name: 'Alice', role: 'admin' },
{ name: 'Bob', role: 'user' },
{ name: 'Charlie', role: 'admin' },
];
uniqBy(users, 'role');
// => [{ name: 'Alice', role: 'admin' }, { name: 'Bob', role: 'user' }]upsertBy
Replaces the first item with a matching key, or appends the new item when no match exists. It returns a new array.
function upsertBy<T, TKey>(
items: readonly T[],
next: T,
getKey: (item: T) => TKey,
): T[]Example:
import { upsertBy } from 'iamfns';
const rows = [
{ id: 'one', value: 1 },
{ id: 'two', value: 2 },
];
upsertBy(rows, { id: 'two', value: 20 }, (row) => row.id);
// => [{ id: 'one', value: 1 }, { id: 'two', value: 20 }]
upsertBy(rows, { id: 'three', value: 3 }, (row) => row.id);
// => [...rows, { id: 'three', value: 3 }]Objects
deepMerge
Deeply merges two objects. Arrays are replaced (not merged). Does not mutate the original target.
function deepMerge<T, S extends object>(target: T, source?: S): T & SParameters:
target- The target objectsource- The source object to merge into target (optional)
Returns: A new deeply merged object
Example:
import { deepMerge } from 'iamfns';
// Flat objects
deepMerge({ a: 1, b: 2 }, { b: 3, c: 4 });
// => { a: 1, b: 3, c: 4 }
// Nested objects
deepMerge(
{ user: { name: 'John', age: 30 } },
{ user: { age: 31, city: 'NYC' } }
);
// => { user: { name: 'John', age: 31, city: 'NYC' } }
// Arrays are replaced, not merged
deepMerge({ tags: [1, 2, 3] }, { tags: [4, 5] });
// => { tags: [4, 5] }
// Complex nested structures
deepMerge(
{ user: { settings: { theme: 'dark' }, tags: ['admin'] } },
{ user: { settings: { lang: 'en' }, tags: ['user'] } }
);
// => { user: { settings: { theme: 'dark', lang: 'en' }, tags: ['user'] } }
// Original target is not mutated
const target = { a: 1 };
const result = deepMerge(target, { b: 2 });
// target is still { a: 1 }
// result is { a: 1, b: 2 }getKV
Gets a value from an object by key with automatic case conversion. Tries the exact key first, then snake_case, then camelCase versions.
function getKV(obj: Record<string, any> | undefined, key: string): anyParameters:
obj- The source object (orundefined)key- The key to look up
Returns: The value if found, otherwise undefined
Example:
import { getKV } from 'iamfns';
// Direct key match
getKV({ testKey: 'value' }, 'testKey');
// => 'value'
// camelCase key finds snake_case property
getKV({ test_key: 'value' }, 'testKey');
// => 'value'
// snake_case key finds camelCase property
getKV({ testKey: 'value' }, 'test_key');
// => 'value'
// Complex key conversion
getKV({ my_complex_key_name: 'value' }, 'myComplexKeyName');
// => 'value'
// Handles undefined objects
getKV(undefined, 'testKey');
// => undefined
// Returns undefined when key not found
getKV({ otherKey: 'value' }, 'testKey');
// => undefinedinverseObj
Inverts the keys and values of an object.
function inverseObj<K extends PropertyKey, V extends PropertyKey>(
obj: Record<K, V>
): Record<V, K>Parameters:
obj- An object with keys and values that are valid property keys (string, number, or symbol)
Returns: A new object with keys and values swapped
Example:
import { inverseObj } from 'iamfns';
inverseObj({ a: 'x', b: 'y', c: 'z' });
// => { x: 'a', y: 'b', z: 'c' }
inverseObj({ one: 1, two: 2, three: 3 });
// => { 1: 'one', 2: 'two', 3: 'three' }
// Note: duplicate values will be overwritten
inverseObj({ a: 'x', b: 'x', c: 'y' });
// => { x: 'b', y: 'c' }objDelta
Returns the changed properties between two objects. Only includes keys that exist in the first object and have different values in the second object.
function objDelta<T extends Record<string, any>>(obj1: T, obj2: T): Partial<T>Parameters:
obj1- The original object (defines which keys to compare)obj2- The updated object (source of new values)
Returns: An object containing only the properties that changed
Example:
import { objDelta } from 'iamfns';
// Single property changed
objDelta({ a: 1, b: 2, c: 3 }, { a: 1, b: 2, c: 4 });
// => { c: 4 }
// Multiple properties changed
objDelta({ a: 1, b: 2, c: 3 }, { a: 10, b: 2, c: 30 });
// => { a: 10, c: 30 }
// No changes returns empty object
objDelta({ a: 1, b: 2 }, { a: 1, b: 2 });
// => {}
// Only compares keys from first object
objDelta({ a: 1, b: 2 }, { a: 10, b: 2, c: 30 });
// => { a: 10 } (c is ignored since it's not in obj1)
// Excludes undefined values from delta
objDelta({ a: 1, b: 2, c: 3 }, { a: 1, b: undefined, c: 4 });
// => { c: 4 }omit
Creates a new object with the specified keys removed.
function omit<T extends Record<string, any>>(obj: T, keys: (keyof T)[]): Omit<T, keyof T>Parameters:
obj- The source objectkeys- An array of keys to omit from the object
Returns: A new object without the specified keys
Example:
import { omit } from 'iamfns';
// Omit a single key
omit({ a: 1, b: 2, c: 3 }, ['b']);
// => { a: 1, c: 3 }
// Omit multiple keys
omit({ a: 1, b: 2, c: 3 }, ['a', 'c']);
// => { b: 2 }
// Empty keys array returns a copy of the object
omit({ a: 1, b: 2, c: 3 }, []);
// => { a: 1, b: 2, c: 3 }
// Omit all keys returns an empty object
omit({ a: 1, b: 2, c: 3 }, ['a', 'b', 'c']);
// => {}omitBy
Filters an object's entries based on a predicate function. Omits the entries where the filter returns true.
function omitBy<T extends Record<string, any>>(
obj: T,
filter: (key: keyof T, value: T[keyof T]) => boolean
): TParameters:
obj- The source objectfilter- A predicate function that receives the key and value, returnstrueto omit the entry
Returns: A new object without the entries that match the filter
Example:
import { omitBy } from 'iamfns';
// Omit entries with value greater than 1
omitBy({ a: 1, b: 2, c: 3 }, (_key, value) => value > 1);
// => { a: 1 }
// Omit a specific key
omitBy({ a: 1, b: 2, c: 3 }, (key) => key === 'b');
// => { a: 1, c: 3 }
// Omit falsy values
omitBy({ a: 0, b: '', c: 'hello', d: 42 }, (_key, value) => !value);
// => { c: 'hello', d: 42 }
// Omit by both key and value
omitBy({ name: 'John', age: 30, active: true }, (key, value) =>
typeof value === 'string' || key === 'active'
);
// => { age: 30 }omitUndefined
Removes all properties with undefined values from an object. Preserves null, false, 0, and empty string values.
function omitUndefined<T extends Record<string, any>>(obj: T): OmitUndefined<T>Parameters:
obj- The source object
Returns: A new object with all undefined values removed
Example:
import { omitUndefined } from 'iamfns';
omitUndefined({ a: 1, b: undefined, c: 'test' });
// => { a: 1, c: 'test' }
omitUndefined({ a: null, b: undefined, c: 0 });
// => { a: null, c: 0 }
omitUndefined({ a: false, b: '', c: 0, d: undefined });
// => { a: false, b: '', c: 0 }Records
These helpers inspect unknown values and read typed fields without throwing. Field readers require non-array records. The require helpers throw when the required record or field is invalid.
isRecord
Checks whether a value is a non-null object. Arrays also satisfy this check.
function isRecord(value: unknown): value is Record<string, unknown>Example:
import { isRecord } from 'iamfns';
isRecord({ value: 1 }); // => true
isRecord([1, 2]); // => true
isRecord(null); // => falseisNonArrayRecord
Checks whether a value is a non-null object that is not an array.
function isNonArrayRecord(value: unknown): value is Record<string, unknown>Example:
import { isNonArrayRecord } from 'iamfns';
isNonArrayRecord({ value: 1 }); // => true
isNonArrayRecord([1, 2]); // => falsegetRecordString
Returns a string field from a non-array record, or null when the field is not a string.
function getRecordString(value: unknown, key: string): string | nullExample:
import { getRecordString } from 'iamfns';
getRecordString({ symbol: 'BTCUSDT' }, 'symbol'); // => 'BTCUSDT'
getRecordString({ symbol: 42 }, 'symbol'); // => nullgetRecordStringOr
Returns a string field or fallback when the field is missing or has another type.
function getRecordStringOr(
value: unknown,
key: string,
fallback: string,
): stringExample:
import { getRecordStringOr } from 'iamfns';
getRecordStringOr({ symbol: 'BTCUSDT' }, 'symbol', 'UNKNOWN'); // => 'BTCUSDT'
getRecordStringOr({}, 'symbol', 'UNKNOWN'); // => 'UNKNOWN'getRecordNonEmptyString
Returns a string with at least one character, or null for a missing or empty string field.
function getRecordNonEmptyString(
value: unknown,
key: string,
): string | nullExample:
import { getRecordNonEmptyString } from 'iamfns';
getRecordNonEmptyString({ name: 'Ada' }, 'name'); // => 'Ada'
getRecordNonEmptyString({ name: '' }, 'name'); // => nullgetRecordTrimmedNonEmptyString
Trims a string field and returns it when the result is not empty. Otherwise, it returns null.
function getRecordTrimmedNonEmptyString(
value: unknown,
key: string,
): string | nullExample:
import { getRecordTrimmedNonEmptyString } from 'iamfns';
getRecordTrimmedNonEmptyString({ name: ' Ada ' }, 'name'); // => 'Ada'
getRecordTrimmedNonEmptyString({ name: ' ' }, 'name'); // => nullgetRecordIdString
Returns a non-empty string ID after trimming, or converts a finite number ID to a string. It returns null for other values.
function getRecordIdString(value: unknown, key: string): string | nullExample:
import { getRecordIdString } from 'iamfns';
getRecordIdString({ id: ' 42 ' }, 'id'); // => '42'
getRecordIdString({ id: 42 }, 'id'); // => '42'
getRecordIdString({ id: '' }, 'id'); // => nullgetRecordNumber
Returns a finite number field or parses a non-empty numeric string. It returns null for invalid values.
function getRecordNumber(value: unknown, key: string): number | nullExample:
import { getRecordNumber } from 'iamfns';
getRecordNumber({ size: 12.5 }, 'size'); // => 12.5
getRecordNumber({ size: '12.5' }, 'size'); // => 12.5
getRecordNumber({ size: 'unknown' }, 'size'); // => nullgetRecordNumberOr
Returns a number field or fallback when the field is missing or invalid.
function getRecordNumberOr(
value: unknown,
key: string,
fallback: number,
): numberExample:
import { getRecordNumberOr } from 'iamfns';
getRecordNumberOr({ size: '12.5' }, 'size', 0); // => 12.5
getRecordNumberOr({}, 'size', 0); // => 0getRecordBoolean
Returns a boolean field, or null when the field is missing or has another type.
function getRecordBoolean(value: unknown, key: string): boolean | nullExample:
import { getRecordBoolean } from 'iamfns';
getRecordBoolean({ active: true }, 'active'); // => true
getRecordBoolean({ active: 'true' }, 'active'); // => nullgetRecordLooseBoolean
Returns a boolean field or parses the strings true, false, 1, and 0. String parsing ignores case and surrounding whitespace.
function getRecordLooseBoolean(value: unknown, key: string): boolean | nullExample:
import { getRecordLooseBoolean } from 'iamfns';
getRecordLooseBoolean({ active: ' TRUE ' }, 'active'); // => true
getRecordLooseBoolean({ active: '0' }, 'active'); // => false
getRecordLooseBoolean({ active: 'yes' }, 'active'); // => nullgetRecordArray
Returns an array field, or an empty array when the record or field is invalid.
function getRecordArray(value: unknown, key: string): unknown[]Example:
import { getRecordArray } from 'iamfns';
getRecordArray({ items: [1, 2] }, 'items'); // => [1, 2]
getRecordArray({ items: 'none' }, 'items'); // => []getRecordFirstString
Reads string fields in key order and returns the first non-null value.
function getRecordFirstString(
value: unknown,
keys: readonly string[],
): string | nullExample:
import { getRecordFirstString } from 'iamfns';
getRecordFirstString({ symbol: 'BTCUSDT' }, ['name', 'symbol']); // => 'BTCUSDT'getRecordFirstNumber
Reads numeric fields in key order and returns the first non-null value.
function getRecordFirstNumber(
value: unknown,
keys: readonly string[],
): number | nullExample:
import { getRecordFirstNumber } from 'iamfns';
getRecordFirstNumber({ q: '1.5' }, ['quantity', 'q']); // => 1.5getRecordFirstIdString
Reads ID fields in key order and returns the first non-null string ID.
function getRecordFirstIdString(
value: unknown,
keys: readonly string[],
): string | nullExample:
import { getRecordFirstIdString } from 'iamfns';
getRecordFirstIdString({ id: 42 }, ['orderId', 'id']); // => '42'requireRecord
Returns a non-array record or throws an error with the supplied label.
function requireRecord(
value: unknown,
label: string,
): Record<string, unknown>Example:
import { requireRecord } from 'iamfns';
requireRecord({ value: 1 }, 'payload'); // => { value: 1 }
requireRecord([], 'payload');
// throws Error: 'Invalid payload.'requireRecordString
Returns a string field or throws an error when the field is missing or empty.
function requireRecordString(
value: unknown,
key: string,
label: string,
): stringExample:
import { requireRecordString } from 'iamfns';
requireRecordString({ symbol: 'BTCUSDT' }, 'symbol', 'payload'); // => 'BTCUSDT'
requireRecordString({}, 'symbol', 'payload');
// throws Error: 'Invalid payload: missing symbol.'requireRecordNumber
Returns a finite number field or throws an error when the field is missing or invalid.
function requireRecordNumber(
value: unknown,
key: string,
label: string,
): numberExample:
import { requireRecordNumber } from 'iamfns';
requireRecordNumber({ size: '12.5' }, 'size', 'payload'); // => 12.5
requireRecordNumber({}, 'size', 'payload');
// throws Error: 'Invalid payload: missing size.'JSON
tryParse
Safely parses a JSON string, returning undefined instead of throwing on invalid input.
function tryParse<T>(json: any): T | undefinedParameters:
json- The JSON string to parse
Returns: The parsed value typed as T, or undefined if parsing fails
Example:
import { tryParse } from 'iamfns';
tryParse<{ a: number }>('{"a": 1}');
// => { a: 1 }
tryParse<{ a: number }>('invalid json');
// => undefined
tryParse<number[]>('[1, 2, 3]');
// => [1, 2, 3]Strings
isNonEmptyString
Checks whether a value is a string with at least one character. Whitespace counts as a character.
function isNonEmptyString(value: unknown): value is stringExample:
import { isNonEmptyString } from 'iamfns';
isNonEmptyString('hello'); // => true
isNonEmptyString(' '); // => true
isNonEmptyString(''); // => falseisNonEmptyTrimmedString
Checks whether a value is a string with at least one non-whitespace character. It returns the original string when the check succeeds.
function isNonEmptyTrimmedString(value: unknown): value is stringExample:
import { isNonEmptyTrimmedString } from 'iamfns';
isNonEmptyTrimmedString(' hello '); // => true
isNonEmptyTrimmedString(' '); // => falsetrimTrailingSlashes
Removes all / characters at the end of a string.
function trimTrailingSlashes(value: string): stringExample:
import { trimTrailingSlashes } from 'iamfns';
trimTrailingSlashes('https://api.example.com///'); // => 'https://api.example.com'
trimTrailingSlashes('/terminal'); // => '/terminal'stringify
Converts an object to a URL query string. Supports arrays, nested objects, and properly encodes special characters.
function stringify(obj: Record<string, any>): stringParameters:
obj- The object to convert
Returns: A URL-encoded query string
Example:
import { stringify } from 'iamfns';
// Simple object
stringify({ a: 1, b: '2', c: true });
// => 'a=1&b=2&c=true'
// Arrays (repeated keys)
stringify({ tags: ['js', 'ts', 'node'] });
// => 'tags=js&tags=ts&tags=node'
// Nested objects (bracket notation)
stringify({ user: { name: 'John', age: 30 } });
// => 'user[name]=John&user[age]=30'
// Null/undefined values (flags)
stringify({ a: null, b: undefined, c: 'value' });
// => 'a&b&c=value'
// Special characters are encoded
stringify({ q: 'hello world' });
// => 'q=hello%20world'parse
Parses a URL query string into an object. Handles arrays, nested objects with bracket notation, and decodes special characters.
function parse(str: string): Record<string, any>Parameters:
str- The query string to parse (with or without leading?)
Returns: The parsed object
Example:
import { parse } from 'iamfns';
// Simple query string
parse('a=1&b=2&c=true');
// => { a: '1', b: '2', c: 'true' }
// With leading question mark
parse('?name=John&age=30');
// => { name: 'John', age: '30' }
// Repeated keys become arrays
parse('tag=js&tag=ts&tag=node');
// => { tag: ['js', 'ts', 'node'] }
// Bracket notation becomes nested object
parse('user[name]=John&user[age]=30');
// => { user: { name: 'John', age: '30' } }
// Flags (keys without values)
parse('active&verified&role=admin');
// => { active: true, verified: true, role: 'admin' }
// Decodes special characters
parse('q=hello%20world');
// => { q: 'hello world' }truncate
Truncates a string to a maximum length, appending an omission string when the text is cut. Accounts for the omission length so the total result never exceeds length.
function truncate(
text: string,
options: { length: number; omission?: string }
): stringParameters:
text- The string to truncateoptions.length- The maximum length of the resulting string (including the omission)options.omission- The string appended when truncating (default:"...")
Returns: The original string if it fits within length, otherwise the truncated string with the omission appended
Example:
import { truncate } from 'iamfns';
// Text within limit is returned as-is
truncate('hello', { length: 10 }); // => 'hello'
truncate('hello', { length: 5 }); // => 'hello'
// Text exceeding limit is truncated with default omission
truncate('hello world', { length: 8 }); // => 'hello...'
// Custom omission string
truncate('hello world', { length: 8, omission: '…' }); // => 'hello w…'
// Empty omission truncates without any suffix
truncate('hello world', { length: 5, omission: '' }); // => 'hello'
// When length is shorter than the omission itself
truncate('hello world', { length: 2 }); // => '..'
// Empty string is returned as-is
truncate('', { length: 5 }); // => ''Async
sleep
Returns a promise that resolves after the specified number of milliseconds. Useful for adding delays in async functions.
function sleep(ms: number): Promise<void>Parameters:
ms- The number of milliseconds to wait
Returns: A promise that resolves after the delay
Example:
import { sleep } from 'iamfns';
// Wait 1 second
await sleep(1000);
// Use in async function
async function fetchWithRetry(url: string, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
return await fetch(url);
} catch (error) {
if (i < retries - 1) {
await sleep(1000 * (i + 1)); // Exponential backoff
}
}
}
}
// Delay between operations
for (const item of items) {
await processItem(item);
await sleep(100); // Rate limiting
}retry
Runs an async function and retries it on failure. After each failure, waits before the next attempt using linear backoff: 100ms × attemptIndex (100ms before the second try, 200ms before the third, and so on). When the remaining retry budget reaches zero, the last error is rethrown.
function retry<T>(
fn: () => Promise<T>,
retries?: number,
tryCount?: number,
): Promise<T>Parameters:
fn- A function that returns a promise (invoked on each attempt)retries- How many times to retry after a failure (default:3). With the default, you get up to 4 attempts in total (initial try plus 3 retries)tryCount- Internal counter used for backoff timing; you normally omit this and use the default1. It increments on each recursive retry and multiplies the delay (100 * tryCountms)
Returns: The resolved value of fn on success
Throws: The error from the final failed attempt when retries are exhausted
Example:
import { retry } from 'iamfns';
// Default: up to 4 attempts, 100ms / 200ms / 300ms delays between failures
await retry(() => fetch('/api/data').then((r) => {
if (!r.ok) throw new Error('HTTP error');
return r.json();
}));
// Fewer retries (1 initial + 2 retries = 3 attempts)
await retry(() => unstableCall(), 2);
// Usually you only pass fn and optionally retries; do not pass tryCount unless
// you are extending the helper.Events
Emitter
A lightweight event emitter class for pub/sub patterns.
class Emitter {
on(event: string, listener: (...args: any[]) => void): void
off(event: string, listener?: (...args: any[]) => void): void
emit(event: string, ...args: any[]): void
}Methods:
on(event, listener)- Registers a listener for an eventoff(event, listener?)- Removes a specific listener (or all listeners for the event)emit(event, ...args)- Emits an event with optional arguments
Example:
import { Emitter } from 'iamfns';
const emitter = new Emitter();
// Register listeners
const onLogin = (user) => {
console.log(`${user.name} logged in`);
};
emitter.on('user:login', onLogin);
emitter.on('user:login', (user) => {
trackAnalytics('login', user.id);
});
// Emit events
emitter.emit('user:login', { id: 1, name: 'John' });
// => "John logged in"
// => tracks analytics
// Remove a specific listener
emitter.off('user:login', onLogin);
// Remove all listeners for an event
emitter.off('user:login');
// Multiple arguments
emitter.on('order:created', (orderId, items, total) => {
console.log(`Order ${orderId}: ${items.length} items, $${total}`);
});
emitter.emit('order:created', 'ORD-123', ['item1', 'item2'], 99.99);
// => "Order ORD-123: 2 items, $99.99"
// Safe to emit events with no listeners
emitter.emit('unknown:event'); // No errorInspectable values
Primitive identity checks for I/O boundaries. These avoid typeof, so boxed primitives and functions are rejected where a real primitive or record is required.
import {
isPrimitiveString,
isInspectableRecord,
requireInspectableRecord,
} from 'iamfns'
isPrimitiveString('btc') // true
isPrimitiveString(new String('btc')) // false
isInspectableRecord({ id: '1' }) // true
isInspectableRecord([]) // false
requireInspectableRecord({ id: '1' }, 'payload')Formatting
import {
capitalize,
toTitleCase,
getNumberFormatter,
formatFixedTwoDecimalNumber,
formatTimestamp,
} from 'iamfns'
capitalize('mark price') // 'Mark price'
toTitleCase('take profit market') // 'Take Profit Market'
formatFixedTwoDecimalNumber(1234.5) // '1,234.50'Draft numbers
import {
parseDraftNumber,
parsePositiveDraftNumber,
isInteger,
isSafeInteger,
isSafeNonNegativeInteger,
} from 'iamfns'
parseDraftNumber(' 1.25 ') // 1.25
parsePositiveDraftNumber('0') // null
isInteger(-3) // true
isSafeInteger(12) // true
isSafeNonNegativeInteger(12) // trueSort
import { compareSortValues } from 'iamfns'
compareSortValues(1, 2, 'asc') // negative
compareSortValues(undefined, 1, 'asc') // missing values sort lastDevelopment
# Install dependencies
bun install
# Run tests
bun test
# Build
bun run build
# Lint
bun run lintLicense
MIT
