npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@sdflc/utils

v1.0.28

Published

A set of utilites used in the @sdflc libraries and related projects. Purpose is to minimize usage of 3rd party libraries.

Readme

@sdflc/utils

A collection of TypeScript utility functions for common tasks including address formatting, array manipulation, color helpers, unit conversions, geolocation math, logging, performance measurement, string processing, and more.


Installation

npm install @sdflc/utils

Table of Contents


Addresses

import { formatAddressShort, formatAddressFull } from '@sdflc/utils';

formatAddressShort(address)

Formats an AddressData object into a short display string suitable for place auto-fill. Includes address1, city, and region.

formatAddressShort({ address1: '123 Main St', city: 'Calgary', region: 'AB' });
// => '123 Main St, Calgary, AB'

formatAddressShort(null); // => ''

formatAddressFull(address)

Formats an AddressData object into a full display string. Includes address1, city, region, postalCode, and country.

formatAddressFull({
  address1: '123 Main St',
  city: 'Calgary',
  region: 'AB',
  postalCode: 'T2P 1J9',
  country: 'Canada',
});
// => '123 Main St, Calgary, AB T2P 1J9, Canada'

Arrays

import { arrayToObject, arrToUpperCase, arrToLowerCase, arrToChunks } from '@sdflc/utils';

arrayToObject(arr, nameKey, valueKey)

Converts an array of objects into a plain object using nameKey for property names and valueKey for values.

const arr = [
  { name: 'status', value: 'active' },
  { name: 'role', value: 'admin' },
];
arrayToObject(arr, 'name', 'value');
// => { status: 'active', role: 'admin' }

arrToUpperCase(arr)

Uppercases every string in the array. Null/undefined items become ''.

arrToUpperCase(['hello', 'world']); // => ['HELLO', 'WORLD']

arrToLowerCase(arr)

Lowercases every string in the array. Null/undefined items become ''.

arrToLowerCase(['HELLO', 'WORLD']); // => ['hello', 'world']

arrToChunks(arr, chunkSize)

Splits an array into sequential chunks of chunkSize. Returns the original array if chunkSize is less than 1 or NaN.

arrToChunks([1, 2, 3, 4, 5], 2); // => [[1, 2], [3, 4], [5]]
arrToChunks([1, 2, 3], 0); // => [1, 2, 3]

Colors

import { getRandomHexColor, getMostContrastingColor } from '@sdflc/utils';

getRandomHexColor()

Returns a random hex color string in #RRGGBB format, including #000000 and #FFFFFF.

getRandomHexColor(); // => '#a3f2c1'

getMostContrastingColor(hex)

Returns either #000000 or #FFFFFF — whichever has the higher WCAG contrast ratio against the given background color. Supports #RGB, #RGBA, #RRGGBB, and #RRGGBBAA formats. Alpha is preserved in the output if present in the input.

getMostContrastingColor('#000000'); // => '#FFFFFF'
getMostContrastingColor('#FFFFFF'); // => '#000000'
getMostContrastingColor('#1a1a2e'); // => '#FFFFFF'
getMostContrastingColor('#000000aa'); // => '#FFFFFFaa'

Constants

import {
  VALUE_TYPES,
  STATUSES,
  ACCESS_RIGHTS,
  SORT_ORDER,
  DISTANCE_UNITS,
  VOLUME_UNITS,
  CONSUMPTION_UNITS,
} from '@sdflc/utils';

VALUE_TYPES

Numeric codes for data types used with convertStringToValue.

| Key | Value | | ---------- | ----- | | NUMBER | 100 | | INTEGER | 101 | | FLOAT | 102 | | DECIMAL | 103 | | CURRENCY | 200 | | BOOLEAN | 300 | | STRING | 400 | | JSON | 500 | | EMAIL | 1001 | | PHONE | 1002 | | URL | 1003 |

STATUSES

Standard record status codes.

| Key | Value | | ---------- | ----- | | TEST | 50 | | ACTIVE | 100 | | DRAFT | 200 | | PENDING | 300 | | BLOCKED | 1000 | | DISABLED | 5000 | | REMOVED | 10000 |

ACCESS_RIGHTS

Bitmask values for permission checks.

// Combine with bitwise OR
const rights = ACCESS_RIGHTS.LIST | ACCESS_RIGHTS.GET; // => 3

| Key | Value | | -------- | ----- | | LIST | 1 | | GET | 2 | | CREATE | 4 | | UPDATE | 8 | | REMOVE | 16 | | RUN | 32 |

DISTANCE_UNITS / VOLUME_UNITS / CONSUMPTION_UNITS

Type-safe string constants for unit conversion functions. Always prefer these over raw strings.

DISTANCE_UNITS.KM; // => 'km'
DISTANCE_UNITS.MILES; // => 'mi'

VOLUME_UNITS.LITERS; // => 'l'
VOLUME_UNITS.GALLONS_US; // => 'gal-us'
VOLUME_UNITS.GALLONS_UK; // => 'gal-uk'
VOLUME_UNITS.KWH; // => 'kwh'
VOLUME_UNITS.KG; // => 'kg'

CONSUMPTION_UNITS.L_PER_100KM; // => 'l100km'
CONSUMPTION_UNITS.MPG_US; // => 'mpg-us'
CONSUMPTION_UNITS.KWH_PER_100KM; // => 'kwh100km'
// ... and more

Other constants

import { UUID_EMPTY, UUID_ZERO, ALPHABET, ALPHABET_CODE, EARTH_RADIUS_M } from '@sdflc/utils';

Converters

import {
  convertStringToValue,
  stringifyObject,
  parseObject,
  toRadians,
  metersToKm,
  mpsToKmh,
  isElectricUnit,
  isHydrogenUnit,
  isLiquidUnit,
  toMetricDistance,
  fromMetricDistance,
  fromMetricDistanceRounded,
  toMetricVolume,
  fromMetricVolume,
  getConsumptionUnitForFuelType,
  deriveConsumptionUnit,
  calculateConsumption,
} from '@sdflc/utils';

convertStringToValue(value, valueType)

Converts a string to a typed value using a VALUE_TYPES constant.

convertStringToValue('42', VALUE_TYPES.INTEGER); // => 42
convertStringToValue('3.14', VALUE_TYPES.FLOAT); // => 3.14
convertStringToValue('true', VALUE_TYPES.BOOLEAN); // => true

stringifyObject(config, defaultObj?)

Safely JSON-stringifies an object. Returns the stringified defaultObj on failure.

stringifyObject({ a: 1 }); // => '{"a":1}'
stringifyObject(circular, { ok: 1 }); // => '{"ok":1}'

parseObject(jsonString, defaultObj)

Safely parses a JSON string into an object shaped like defaultObj. Fills in missing keys from defaults and ignores type mismatches.

parseObject('{"name":"Alice"}', { name: '', age: 0 });
// => { name: 'Alice', age: 0 }

Unit conversion functions

All return null for null/undefined inputs.

toRadians(180); // => 3.14159...
metersToKm(1500); // => 1.5
mpsToKmh(10); // => 36

toMetricDistance(10, DISTANCE_UNITS.MILES); // => 16.093
fromMetricDistance(16.0934, DISTANCE_UNITS.MILES); // => ~10

toMetricVolume(1, VOLUME_UNITS.GALLONS_US); // => 3.785
fromMetricVolume(3.785, VOLUME_UNITS.GALLONS_US); // => ~1

calculateConsumption(distanceKm, volumeOrEnergy, consumptionUnit)

Calculates fuel/energy consumption in the specified unit. Supports liquid, electric, and hydrogen units.

// 10L over 100km = 10 l/100km
calculateConsumption(100, 10, CONSUMPTION_UNITS.L_PER_100KM); // => 10

// 20kWh over 100km = 20 kWh/100km
calculateConsumption(100, 20, CONSUMPTION_UNITS.KWH_PER_100KM); // => 20

Geo

import {
  buildCoordinatesStr,
  extractLatitudeLongitude,
  isValidCoordinate,
  calcDistance,
  areCoordinatesNear,
  findNearestLocation,
  haversineDistanceM,
  formatCoordinates,
  createBoundingBox,
  isWithinBoundingBox,
  calculateBearing,
  bearingDelta,
  calculateTotalDistance,
} from '@sdflc/utils';

buildCoordinatesStr(coords, precision?)

Formats a GeoCoords object as "lat, lng" string. Defaults to 5 decimal places.

buildCoordinatesStr({ latitude: 51.5074, longitude: -0.1278 });
// => '51.50740, -0.12780'

extractLatitudeLongitude(coordinatesStr)

Parses a "lat,lng" string into a GeoCoords object with numeric values.

extractLatitudeLongitude('51.5074,-0.1278');
// => { latitude: 51.5074, longitude: -0.1278 }

isValidCoordinate(coords)

Returns true if the coordinates are non-null, numeric, and within valid lat/lng ranges.

isValidCoordinate({ latitude: 51.5074, longitude: -0.1278 }); // => true
isValidCoordinate({ latitude: 999, longitude: 0 }); // => false

calcDistance(args)

Calculates the great-circle distance between two coordinates using the Haversine formula (atan2 variant). Returns distance in kilometers.

calcDistance({
  position1: { latitude: 51.5074, longitude: -0.1278 },
  position2: { latitude: 48.8566, longitude: 2.3522 },
}); // => ~343 km

haversineDistanceM(coord1, coord2)

Same as calcDistance but returns distance in meters.

haversineDistanceM({ latitude: 51.5074, longitude: -0.1278 }, { latitude: 48.8566, longitude: 2.3522 }); // => ~343000 meters

areCoordinatesNear(coord1, coord2, thresholdM?)

Returns true if two coordinates are within thresholdM meters of each other. Default threshold is DEFAULT_DISTANCE_THRESHOLD_M.

areCoordinatesNear(coordA, coordB, 100); // within 100m?

findNearestLocation(target, locations, getCoords)

Finds the nearest item in locations to target. Returns { item, distance } or null.

const nearest = findNearestLocation(userCoords, stores, (store) => store.coords);
// => { item: nearestStore, distance: 250.5 }

createBoundingBox(center, radiusM)

Creates a BoundingBox around a coordinate with the given radius in meters.

createBoundingBox({ latitude: 51.5074, longitude: -0.1278 }, 1000);
// => { north: ..., south: ..., east: ..., west: ... }

isWithinBoundingBox(coords, box)

Returns true if coords falls within the bounding box.

calculateBearing(p1, p2)

Returns the initial bearing from p1 to p2 in degrees (0–360). Returns null for invalid input.

calculateBearing({ latitude: 0, longitude: 0 }, { latitude: 0, longitude: 10 });
// => ~90 (due East)

bearingDelta(bearing1, bearing2)

Returns the absolute angular difference between two bearings (0–180), handling the 0°/360° wraparound.

bearingDelta(10, 350); // => 20

calculateTotalDistance(points)

Sums great-circle distances between consecutive points. Returns total in meters.

calculateTotalDistance([london, paris, berlin]); // => total meters

Languages

import { extractLanguages } from '@sdflc/utils';

extractLanguages(str)

Parses an Accept-Language HTTP header into an ordered array of language tags.

extractLanguages('en-US,en;q=0.9,ru;q=0.8,fr;q=0.7');
// => ['en-US', 'en', 'ru', 'fr']

Logger

import { Logger } from '@sdflc/utils';
import { LoggerLevels } from '@sdflc/utils/interfaces';

A configurable logger with level filtering, optional timestamp, request ID, and module name prefixes.

Levels (lowest to highest)

NONEERRORWARNINGLOGDEBUG

Only messages at or below the current level are emitted.

Basic usage

const logger = new Logger({ level: LoggerLevels.DEBUG });

logger.debug('Fetching user...');
logger.log('User fetched');
logger.warn('Cache miss');
logger.error('Connection failed');

Custom handlers

const logger = new Logger({
  level: LoggerLevels.LOG,
  log: (msg) => myLoggingService.info(msg),
  error: (msg) => myLoggingService.error(msg),
});

Prefix options

const logger = new Logger({
  level: LoggerLevels.DEBUG,
  timestamp: true, // prepend timestamp to every line
  timestampUtc: true, // use UTC (default: local time)
  requestId: 'req-abc123',
  module: 'UserService',
});

logger.log('User created');
// => [20250325-143022.123Z] [req-abc123] [UserService] User created

setLevel(newLevel) / getLevel()

logger.setLevel(LoggerLevels.ERROR); // suppress everything below ERROR
logger.getLevel(); // => LoggerLevels.ERROR

MeasureTool

import { MeasureTool } from '@sdflc/utils';

A cross-environment performance measurement utility using performance.now() (available in Node.js 16+ and all modern browsers).

Basic measurement

const tool = new MeasureTool();
tool.start();

// ... do work ...
const result = tool.stop('total');
console.log(result.durationMs); // milliseconds

Checkpoints

tool.start();
await fetchData();
tool.addCheckpoint('fetch');

await processData();
tool.addCheckpoint('process');

const final = tool.stop('done');
console.log(tool.getCheckpoints());
// => [{ name: 'fetch', durationMs: 120, hrtime: [...] }, ...]

measureExecTime(name, fn)

Wraps an async function and returns its result along with timing data.

const { result, durationMs } = await tool.measureExecTime('myQuery', async () => {
  return await db.query('SELECT ...');
});

threadBlockTime(name?, fn?)

Measures event-loop blocking time using setImmediate. Node.js only.

tool.threadBlockTime('check', ({ name, durationMs }) => {
  console.log(`${name} blocked for ${durationMs}ms`);
});

Numbers

import { roundNumberValue, roundNumberValues, parseNumericInput } from '@sdflc/utils';

roundNumberValue(value, decimals?)

Rounds a number to decimals decimal places (default: 2).

roundNumberValue(2.34567); // => 2.35
roundNumberValue(2.34567, 3); // => 2.346
roundNumberValue(2.5, 0); // => 3

roundNumberValues(obj, decimals?)

Recursively rounds all numeric values in an object or array in place. Also returns the mutated value.

const data = { price: 9.9999, tax: 1.2345, label: 'item' };
roundNumberValues(data);
// data => { price: 10, tax: 1.23, label: 'item' }

roundNumberValues([1.111, 2.999]); // => [1.11, 3]

parseNumericInput(value)

Normalises a value from a numeric input field into number | null.

parseNumericInput('42'); // => 42
parseNumericInput(''); // => null
parseNumericInput(null); // => null
parseNumericInput('abc'); // => null
parseNumericInput(NaN); // => null

Objects

import { setNullOnEmptyString, onlyPropsOf, compactObject, cloneDeep } from '@sdflc/utils';

setNullOnEmptyString(obj)

Recursively replaces all empty string values ('') with null in an object or array.

setNullOnEmptyString({ firstName: 'John', lastName: '' });
// => { firstName: 'John', lastName: null }

setNullOnEmptyString({ user: { name: '', age: 30 } });
// => { user: { name: null, age: 30 } }

setNullOnEmptyString([{ a: '' }, { a: 'keep' }]);
// => [{ a: null }, { a: 'keep' }]

onlyPropsOf(source, destinationType)

Creates an instance of destinationType and copies only the properties that exist on that class from source.

class UserDto {
  id = 0;
  name = '';
}

onlyPropsOf({ id: 1, name: 'Alice', extra: 'ignored' }, UserDto);
// => UserDto { id: 1, name: 'Alice' }

compactObject(obj)

Recursively removes all undefined properties from an object or array. null values are preserved.

compactObject({ a: 1, b: undefined, c: { d: undefined, e: 2 } });
// => { a: 1, c: { e: 2 } }

cloneDeep(obj)

Creates a deep clone handling Date, undefined, NaN, Infinity, and circular references correctly.

const original = { a: { b: 1 }, d: new Date() };
const clone = cloneDeep(original);
clone.a.b = 99;
original.a.b; // => 1 (unchanged)
clone.d instanceof Date; // => true

Strings

import { camelKeys, camelResponse, pascalCase, pascalCases, buildKey, buildKeys, isIdEmpty, slug } from '@sdflc/utils';

camelKeys(result)

Recursively converts all object keys to camelCase. Handles arrays and nested objects. Leaves Date instances and primitives untouched.

camelKeys({ first_name: 'John', last_name: 'Doe' });
// => { firstName: 'John', lastName: 'Doe' }

camelKeys([{ user_id: 1 }, { user_id: 2 }]);
// => [{ userId: 1 }, { userId: 2 }]

camelResponse(result)

Converts object keys to camelCase. Returns the value unchanged if it is falsy or has a rows property (raw database result sets).

camelResponse({ user_id: 1 }); // => { userId: 1 }
camelResponse(null); // => null

pascalCase(name)

Converts a string to PascalCase.

pascalCase('hello_world'); // => 'HelloWorld'
pascalCase('hello-world'); // => 'HelloWorld'

pascalCases(args)

Recursively converts all keys in an object or array to PascalCase. Supports a mapKey override for specific keys.

pascalCases({ src: { first_name: 'Alice' } });
// => { FirstName: 'Alice' }

pascalCases({ src: { id: 1, name: 'Alice' }, mapKey: { id: 'Id' } });
// => { Id: 1, Name: 'Alice' }

buildKey(keys)

Converts a string, number, array, or object to a slug-based key string.

buildKey('hello world'); // => 'hello-world'
buildKey(42); // => '42'
buildKey(['foo', 'bar']); // => 'foo-bar'
buildKey({ a: 1 }); // => '{"a":1}'

buildKeys(keys)

Calls buildKey on each item in an array.

buildKeys(['hello world', 42]); // => ['hello-world', '42']

isIdEmpty(value)

Returns true if the value represents an empty/unset ID.

isIdEmpty(null); // => true
isIdEmpty(''); // => true
isIdEmpty('0'); // => true
isIdEmpty(0); // => true
isIdEmpty(UUID_EMPTY); // => true
isIdEmpty('abc-123'); // => false

slug(str)

Converts a string to a URL-friendly slug. Normalises Unicode/accented characters to ASCII equivalents.

slug('Hello World!'); // => 'hello-world'
slug('café résumé'); // => 'cafe-resume'
slug('Привет мир'); // => 'privet-mir'

String Helpers

import {
  doesValueMatchAlphabet,
  isLengthBetween,
  areStringsEqual,
  replaceAt,
  insertAt,
  randomString,
  escapeRegExp,
  formatString,
  normalizeName,
} from '@sdflc/utils';

doesValueMatchAlphabet(value, alphabet)

Returns true if every character in value is present in alphabet.

doesValueMatchAlphabet('ABC123', 'ABCDEFGHJKLMNPQRSTUVWXYZ0123456789'); // => true
doesValueMatchAlphabet('abc', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'); // => false

isLengthBetween(str, minLen, maxLen)

Returns true if str length is between minLen and maxLen (inclusive).

isLengthBetween('hello', 3, 10); // => true
isLengthBetween('hi', 3, 10); // => false

areStringsEqual(strLeft, strRight)

Returns true if the two strings are equal ignoring case.

areStringsEqual('Hello', 'hello'); // => true

replaceAt(str, index, replacement)

Replaces characters in str starting at index with replacement.

replaceAt('hello world', 6, 'there'); // => 'hello there'

insertAt(str, index, insert)

Inserts insert into str at index without removing any characters.

insertAt('helo', 3, 'l'); // => 'hello'

randomString(length, alphabet?)

Generates a random string. Not suitable for cryptographic purposes.

randomString(8); // => 'A3KF9Z2M'  (from default alphabet)
randomString(6, 'abc'); // => 'bcaabc'

escapeRegExp(str)

Escapes all regex special characters so the string can be safely used in new RegExp(...).

escapeRegExp('{{name}}'); // => '\\{\\{name\\}\\}'

formatString(str, obj, opt?)

Replaces {{key}} placeholders in a template string with values from obj. Delimiters are configurable. Null/undefined values leave the placeholder unchanged.

formatString('Hello {{name}}!', { name: 'Alice' });
// => 'Hello Alice!'

formatString('Hi ${name}!', { name: 'Bob' }, { leftWrapper: '${', rightWrapper: '}' });
// => 'Hi Bob!'

normalizeName(name)

Trims and lowercases a name string. Returns undefined for null/undefined input.

normalizeName('  Alice  '); // => 'alice'
normalizeName(null); // => undefined

StrOrderHelpers

import { StrOrderHelpers } from '@sdflc/utils';

A counter that operates in any numeric base (default: base-36) using string representations. Useful for generating sortable string order keys.

const order = new StrOrderHelpers({ start: '100', step: '5' });

order.current(); // => '100'
order.increase(); // => '105'
order.increase(); // => '10a'
order.decrease(); // => '105'
order.reset(); // => resets to '100'

Arithmetic helpers

order.add('1000', 'bb'); // => '10bb'
order.subtract('10cc', 'cc'); // => '1000'
order.addValue('aa'); // adds to current counter, returns new value
order.subtractValue('a'); // subtracts from current counter

valueBetween(valueA, valueB)

Finds the midpoint between two values in the current base. Returns null if no distinct midpoint exists (adjacent or equal values).

order.valueBetween('aaa', 'ccc'); // => 'bbb'
order.valueBetween('100', '104'); // => '102'
order.valueBetween('100', '101'); // => null

Constructor options

| Option | Type | Default | Description | | ------- | -------- | ------- | ------------------------ | | start | string | '0' | Initial counter value | | step | string | '1' | Increment/decrement step | | base | number | 36 | Numeric base (2–36) |


Transformers

import { buildHierarchy, mapArrayBy, flattenHierarchy, getLowestLevelItems } from '@sdflc/utils';

buildHierarchy(arr, idField, parentIdField, nameForChildren)

Converts a flat array into a tree structure. Items with no valid parent are roots; items whose parent isn't in the array are orphans.

const flat = [
  { id: '1', parentId: null, name: 'Root' },
  { id: '2', parentId: '1', name: 'Child' },
];

buildHierarchy(flat, 'id', 'parentId', 'children');
// => {
//   tree: [{ id: '1', name: 'Root', children: [{ id: '2', name: 'Child', children: [] }] }],
//   orphans: []
// }

mapArrayBy(arr, mapBy, opt?)

Maps an array of objects into a lookup object keyed by one or more fields.

const arr = [
  { id: '1', val: 'a' },
  { id: '2', val: 'b' },
];

mapArrayBy(arr, 'id');
// => { '1': { id: '1', val: 'a' }, '2': { id: '2', val: 'b' } }

mapArrayBy(arr, 'id', { multiple: true });
// => { '1': [{ id: '1', val: 'a' }], '2': [{ id: '2', val: 'b' }] }

mapArrayBy(arr, ['id', 'val']);
// => { id: { '1': {...}, '2': {...} }, val: { a: {...}, b: {...} } }

flattenHierarchy(arr, nameForChildren, opt?)

Flattens a tree into a depth-first ordered array.

flattenHierarchy(tree, 'children');
// => [root, child1, grandchild1, child2, ...]

// With callback:
flattenHierarchy(tree, 'children', {
  onItem: (item) => console.log(item.name),
});

getLowestLevelItems(arr, idField, parentIdField, nameForChildren, setRoot?)

Returns only the leaf nodes from a flat array. Optionally attaches a rootId to each leaf.

getLowestLevelItems(flat, 'id', 'parentId', 'children', true);
// => [
//   { id: '4', ..., rootId: '1' },
//   { id: '5', ..., rootId: '1' },
// ]

URL Params

import { buildURLSearchParams } from '@sdflc/utils';

buildURLSearchParams(data)

Converts a plain object into a URLSearchParams instance. Supports nested objects (dot notation), arrays (bracket notation), Date values, and mixed structures. Null and undefined values are omitted.

buildURLSearchParams({ name: 'Alice', age: 30 }).toString();
// => 'name=Alice&age=30'

buildURLSearchParams({ tags: ['a', 'b'] }).toString();
// => 'tags[0]=a&tags[1]=b'

buildURLSearchParams({ address: { city: 'Calgary' } }).toString();
// => 'address.city=Calgary'

buildURLSearchParams({
  filters: [{ field: 'status', values: ['active', 'pending'] }],
}).toString();
// => 'filters[0].field=status&filters[0].values[0]=active&filters[0].values[1]=pending'

buildURLSearchParams({ createdAt: new Date('2025-01-01T00:00:00Z') }).toString();
// => 'createdAt=2025-01-01T00:00:00.000Z'

License

MIT