@web-ts-toolkit/utils
v0.51.0
Published
Shared collection, object, async, and URL helpers used across workspace packages
Maintainers
Readme
@web-ts-toolkit/utils
Shared collection, object, async, and URL helpers used across the workspace.
Installation
pnpm add @web-ts-toolkit/utilsRequires Node >=22. Import from the package root with canonical named
imports; there is no default export and no public subpath:
import { get, normalizeUrlPath, parseBooleanString } from '@web-ts-toolkit/utils';Quick Start
import {
get,
groupBy,
hasOwn,
normalizeUrlPath,
parseBooleanString,
startCase,
sumBy,
uniqBy,
} from '@web-ts-toolkit/utils';
const payload = {
user: {
profile: {
name: 'Ada',
},
},
};
get(payload, 'user.profile.name');
hasOwn(payload.user.profile, 'name');
groupBy(
[
{ type: 'fruit', name: 'apple' },
{ type: 'fruit', name: 'banana' },
{ type: 'vegetable', name: 'carrot' },
],
'type',
);
uniqBy(
[
{ id: 'a', name: 'Ada' },
{ id: 'a', name: 'Ada Lovelace' },
{ id: 'b', name: 'Grace' },
],
'id',
);
startCase('api_response_time');
sumBy([{ hours: 2 }, { hours: 3 }], 'hours');
normalizeUrlPath('api//users');
parseBooleanString('true', false);Main Exports
58 functions plus the PropertyPath type, all from the package root
(see src/index.ts):
- object/path helpers:
get,set,hasOwn,pick,pickBy,omit,omitBy,assign,keys,mapKeys,mapValues,toStringRecord - collection helpers:
map,filter,forEach,eachRight,join,reduce,find,flatten,flattenDeep,compact,uniq,uniqBy,difference,intersection,intersectionBy,groupBy,orderBy,sum,sumBy - guards:
isArray,isBoolean,isEmpty,isEqual,isFunction,isMatch,isNaN,isNil,isNumber,isObject,isPlainObject,isPromise,isString,isUndefined - string helpers:
startCase,upperCase,padEnd - URL helpers:
addLeadingSlash,removeConsecutiveSlashesFromUrl,normalizeUrlPath - async helpers:
mapValuesAsync,toAsyncFn - misc:
castArray,arrayToRecord,cloneDeep,noop,parseBooleanString
Path Grammar And Mutation Rules
get, set, pick, and omit accept a PropertyPath: dot segments
('a.b'), bare brackets ('a[0]'), quoted brackets ('a["b.c"]'), or
segment arrays (['a', 'b']).
- Key identity is literal: string segments are never coerced, so
'01'and'1'address different properties, and digit keys beyondMAX_SAFE_INTEGERnever round. Only canonical indices ('0','1', … with no leading zeros) address array slots;'a[01]'addresses an own'01'property. Empty quoted keys, escaped quotes, empty dot segments, and malformed brackets are unspecified (no Lodash parity). - Mutation segments
__proto__,constructor, andprototype(quoted or not) are rejected before any write:setreturns its target unchanged andomitis a no-op. - In
pick/omit, a flat string array is a list of paths (pick(o, ['a', 'b'])picks keysaandb); pass a nested array for a single segmented path (pick(o, [['a', 'b']])picksa.b). - Reads follow the prototype chain;
getreturnsdefaultValuefor anull/undefinedintermediate or anundefinedleaf. Writes never traverse inherited containers: an inherited member is shadowed with a new own container (array for a following canonical index, plain object otherwise) and the final write creates an own data property, bypassing inherited setters. UsehasOwnwhen own-key presence matters. - Dictionary builders (
groupBy,arrayToRecord,mapKeys,mapValues,pickBy,omitBy,toStringRecord) preserve arbitrary string keys — including__proto__— as own data properties without replacing the result prototype. Results keepObject.prototype(never null-prototype).
Mutation Versus Copying
setmutates its target in place and returns it.omitnever mutates its input: it deep-clones first, then deletes from the clone.assignis a thin wrapper over nativeObject.assign(source getters and target setters run); it is not a hardened untrusted-input copier.orderBy,uniq/uniqBy,difference, theintersectionfamily, andflatten/flattenDeepnever mutate their inputs. Sorting and deduplication are stable/first-occurrence: ties keep input order and the first occurrence wins.
Clone And Comparison Domains
cloneDeep, isEqual, and isMatch share one bounded domain:
- Supported: primitives (
NaNequals itself), plain objects (null/Object.prototype, plusObject.creategraphs over plain ancestors whose prototype is shared by reference), arrays (length, holes, and extra own keys participate),Date(by time), andRegExp(by source plus flags). Cycles and repeated references terminate and stay shared within the clone. - Functions and exotic values (
Map/Set, class instances such as BSONObjectId) are opaque: nested occurrences are shared by reference, never traversed, and distinct references are never equal. A top-level exotic root passed tocloneDeepthrowsTypeErrorinstead of returning an alias, soomiton an uncloneable root throws before deleting anything rather than deleting from your input. - Comparison uses own enumerable string/symbol keys only: inherited
state is ignored and prototypes are not compared.
isMatchrequires each own source key (includingundefined-valued and symbol keys) to exist as an own key on the target, soisMatch({}, { a: undefined })isfalse; arrays use prefix semantics.
Async Contracts
toAsyncFnlifts sync results into a promise, but it is not a full async-function boundary: a synchronousthrowescapes synchronously instead of becoming a rejection, and thenables (including foreign thenables) are returned unchanged with identity preserved rather than converted to native promises. Whenfnis absent, the wrapper resolvesdefaultValue.thisis forwarded.mapValuesAsyncstarts every callback eagerly with unbounded parallelism (Promise.all): one rejection rejects the whole call, and there is no concurrency limit, cancellation, or scheduler. Chunk the input if downstream throttling is needed.
Boolean Strings
import { parseBooleanString } from '@web-ts-toolkit/utils';
parseBooleanString('true'); // true
parseBooleanString('false'); // false
parseBooleanString('TRUE'); // false — exact match only, no case folding
parseBooleanString(''); // undefined — empty string falls back to the default
parseBooleanString('', false); // false — via the default
parseBooleanString(undefined, true); // true — missing input uses the defaultparseBooleanString(str, defaultValue) returns true only for the exact
string 'true', returns false for any other non-empty string, and falls
back to defaultValue (which is undefined when omitted) when the input
is undefined or the empty string ''. Note that an Express-style
?flag= query value parses to '' and therefore yields the default, not
false.
URL Paths Are Pathname-Only
import { normalizeUrlPath } from '@web-ts-toolkit/utils';
normalizeUrlPath('api//users'); // '/api/users'
normalizeUrlPath('api//users/42'); // '/api/users/42'normalizeUrlPath composes route-path fragments: it collapses every run
of slashes and prepends a leading slash. The input must be a path
fragment — no scheme/host, query string, or fragment. Full URLs are out
of domain and are mangled rather than normalized
(normalizeUrlPath('https://example.com//a') yields
'/https:/example.com/a'; slash runs inside query/fragment values are
collapsed too). These helpers are route-path composition for workspace
routers, not WHATWG URL normalization and not a security sanitizer.
Guards And Types
isBoolean/isNumber/isStringaccept primitives only: boxed instances such asnew Boolean(false)returnfalseand are never narrowed to primitives.flattenDeep<T>(input)takesunknown(a non-array yields[]) andTis an unchecked caller assertion — specify it explicitly (flattenDeep<number>(input)) or narrowunknown[]yourself. Cyclic arrays throwTypeError; shared (non-ancestor) subarrays flatten once per occurrence. Wide and deeply nested inputs flatten iteratively withoutRangeError.intersectionBy/differenceignore non-array value arguments, whileintersectiontreats a non-array secondary as empty (result[]).intersectionByevaluates each element's iteratee once per input array; redundant-callback side effects are not preserved.
Documentation
Full package documentation lives on the published docs site:
- live docs: https://web-ts-toolkit.pages.dev/docs/packages/utils
