@web-ts-toolkit/utils 0.43.0 → 0.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,13 +8,12 @@ Shared collection, object, async, and URL helpers used across the workspace.
8
8
  pnpm add @web-ts-toolkit/utils
9
9
  ```
10
10
 
11
- ## Highlights
11
+ Requires Node `>=22`. Import from the package root with canonical named
12
+ imports; there is no default export and no public subpath:
12
13
 
13
- - object-path helpers such as `get(...)`, `set(...)`, and `hasOwn(...)`
14
- - collection helpers such as `map(...)`, `filter(...)`, `eachRight(...)`, `join(...)`, `uniq(...)`, `uniqBy(...)`, and `orderBy(...)`
15
- - small type guards
16
- - URL helpers such as `normalizeUrlPath(...)`
17
- - async helpers such as `mapValuesAsync(...)`
14
+ ```ts
15
+ import { get, normalizeUrlPath, parseBooleanString } from '@web-ts-toolkit/utils';
16
+ ```
18
17
 
19
18
  ## Quick Start
20
19
 
@@ -64,14 +63,149 @@ parseBooleanString('true', false);
64
63
 
65
64
  ## Main Exports
66
65
 
67
- - object helpers: `get`, `set`, `hasOwn`, `pick`, `pickBy`, `omit`, `omitBy`, `assign`, `cloneDeep`, `mapKeys`
68
- - collection helpers: `map`, `filter`, `eachRight`, `join`, `reduce`, `find`, `flatten`, `uniq`, `uniqBy`, `orderBy`, `groupBy`, `sum`, `sumBy`
69
- - string helpers: `startCase`, `upperCase`
70
- - guards: `isArray`, `isPlainObject`, `isString`, `isPromise`
66
+ 58 functions plus the `PropertyPath` type, all from the package root
67
+ (see `src/index.ts`):
68
+
69
+ - object/path helpers: `get`, `set`, `hasOwn`, `pick`, `pickBy`, `omit`, `omitBy`, `assign`, `keys`, `mapKeys`, `mapValues`, `toStringRecord`
70
+ - collection helpers: `map`, `filter`, `forEach`, `eachRight`, `join`, `reduce`, `find`, `flatten`, `flattenDeep`, `compact`, `uniq`, `uniqBy`, `difference`, `intersection`, `intersectionBy`, `groupBy`, `orderBy`, `sum`, `sumBy`
71
+ - guards: `isArray`, `isBoolean`, `isEmpty`, `isEqual`, `isFunction`, `isMatch`, `isNaN`, `isNil`, `isNumber`, `isObject`, `isPlainObject`, `isPromise`, `isString`, `isUndefined`
72
+ - string helpers: `startCase`, `upperCase`, `padEnd`
71
73
  - URL helpers: `addLeadingSlash`, `removeConsecutiveSlashesFromUrl`, `normalizeUrlPath`
74
+ - async helpers: `mapValuesAsync`, `toAsyncFn`
75
+ - misc: `castArray`, `arrayToRecord`, `cloneDeep`, `noop`, `parseBooleanString`
76
+
77
+ ## Path Grammar And Mutation Rules
78
+
79
+ `get`, `set`, `pick`, and `omit` accept a `PropertyPath`: dot segments
80
+ (`'a.b'`), bare brackets (`'a[0]'`), quoted brackets (`'a["b.c"]'`), or
81
+ segment arrays (`['a', 'b']`).
82
+
83
+ - Key identity is literal: string segments are never coerced, so `'01'`
84
+ and `'1'` address different properties, and digit keys beyond
85
+ `MAX_SAFE_INTEGER` never round. Only canonical indices (`'0'`, `'1'`,
86
+ … with no leading zeros) address array slots; `'a[01]'` addresses an
87
+ own `'01'` property. Empty quoted keys, escaped quotes, empty dot
88
+ segments, and malformed brackets are unspecified (no Lodash parity).
89
+ - Mutation segments `__proto__`, `constructor`, and `prototype` (quoted
90
+ or not) are rejected before any write: `set` returns its target
91
+ unchanged and `omit` is a no-op.
92
+ - In `pick`/`omit`, a flat string array is a **list** of paths
93
+ (`pick(o, ['a', 'b'])` picks keys `a` and `b`); pass a nested array for
94
+ a single segmented path (`pick(o, [['a', 'b']])` picks `a.b`).
95
+ - Reads follow the prototype chain; `get` returns `defaultValue` for a
96
+ `null`/`undefined` intermediate or an `undefined` leaf. Writes never
97
+ traverse inherited containers: an inherited member is shadowed with a
98
+ new own container (array for a following canonical index, plain object
99
+ otherwise) and the final write creates an own data property, bypassing
100
+ inherited setters. Use `hasOwn` when own-key presence matters.
101
+ - Dictionary builders (`groupBy`, `arrayToRecord`, `mapKeys`,
102
+ `mapValues`, `pickBy`, `omitBy`, `toStringRecord`) preserve arbitrary
103
+ string keys — including `__proto__` — as own data properties without
104
+ replacing the result prototype. Results keep `Object.prototype`
105
+ (never null-prototype).
106
+
107
+ ## Mutation Versus Copying
108
+
109
+ - `set` mutates its target in place and returns it. `omit` never mutates
110
+ its input: it deep-clones first, then deletes from the clone.
111
+ - `assign` is a thin wrapper over native `Object.assign` (source getters
112
+ and target setters run); it is not a hardened untrusted-input copier.
113
+ - `orderBy`, `uniq`/`uniqBy`, `difference`, the `intersection` family,
114
+ and `flatten`/`flattenDeep` never mutate their inputs. Sorting and
115
+ deduplication are stable/first-occurrence: ties keep input order and
116
+ the first occurrence wins.
117
+
118
+ ## Clone And Comparison Domains
119
+
120
+ `cloneDeep`, `isEqual`, and `isMatch` share one bounded domain:
121
+
122
+ - Supported: primitives (`NaN` equals itself), plain objects
123
+ (`null`/`Object.prototype`, plus `Object.create` graphs over plain
124
+ ancestors whose prototype is shared by reference), arrays (length,
125
+ holes, and extra own keys participate), `Date` (by time), and `RegExp`
126
+ (by source plus flags). Cycles and repeated references terminate and
127
+ stay shared within the clone.
128
+ - Functions and exotic values (`Map`/`Set`, class instances such as BSON
129
+ `ObjectId`) are opaque: nested occurrences are shared by reference,
130
+ never traversed, and distinct references are never equal. A top-level
131
+ exotic root passed to `cloneDeep` throws `TypeError` instead of
132
+ returning an alias, so `omit` on an uncloneable root throws before
133
+ deleting anything rather than deleting from your input.
134
+ - Comparison uses own enumerable string/symbol keys only: inherited
135
+ state is ignored and prototypes are not compared. `isMatch` requires
136
+ each own source key (including `undefined`-valued and symbol keys) to
137
+ exist as an own key on the target, so `isMatch({}, { a: undefined })`
138
+ is `false`; arrays use prefix semantics.
139
+
140
+ ## Async Contracts
141
+
142
+ - `toAsyncFn` lifts sync results into a promise, but it is not a full
143
+ async-function boundary: a synchronous `throw` escapes synchronously
144
+ instead of becoming a rejection, and thenables (including foreign
145
+ thenables) are returned unchanged with identity preserved rather than
146
+ converted to native promises. When `fn` is absent, the wrapper resolves
147
+ `defaultValue`. `this` is forwarded.
148
+ - `mapValuesAsync` starts every callback eagerly with unbounded
149
+ parallelism (`Promise.all`): one rejection rejects the whole call, and
150
+ there is no concurrency limit, cancellation, or scheduler. Chunk the
151
+ input if downstream throttling is needed.
152
+
153
+ ## Boolean Strings
154
+
155
+ ```ts
156
+ import { parseBooleanString } from '@web-ts-toolkit/utils';
157
+
158
+ parseBooleanString('true'); // true
159
+ parseBooleanString('false'); // false
160
+ parseBooleanString('TRUE'); // false — exact match only, no case folding
161
+ parseBooleanString(''); // undefined — empty string falls back to the default
162
+ parseBooleanString('', false); // false — via the default
163
+ parseBooleanString(undefined, true); // true — missing input uses the default
164
+ ```
165
+
166
+ `parseBooleanString(str, defaultValue)` returns `true` only for the exact
167
+ string `'true'`, returns `false` for any other non-empty string, and falls
168
+ back to `defaultValue` (which is `undefined` when omitted) when the input
169
+ is `undefined` **or the empty string `''`**. Note that an Express-style
170
+ `?flag=` query value parses to `''` and therefore yields the default, not
171
+ `false`.
172
+
173
+ ## URL Paths Are Pathname-Only
174
+
175
+ ```ts
176
+ import { normalizeUrlPath } from '@web-ts-toolkit/utils';
177
+
178
+ normalizeUrlPath('api//users'); // '/api/users'
179
+ normalizeUrlPath('api//users/42'); // '/api/users/42'
180
+ ```
181
+
182
+ `normalizeUrlPath` composes route-path fragments: it collapses every run
183
+ of slashes and prepends a leading slash. The input must be a path
184
+ fragment — no scheme/host, query string, or fragment. Full URLs are out
185
+ of domain and are mangled rather than normalized
186
+ (`normalizeUrlPath('https://example.com//a')` yields
187
+ `'/https:/example.com/a'`; slash runs inside query/fragment values are
188
+ collapsed too). These helpers are route-path composition for workspace
189
+ routers, not WHATWG URL normalization and not a security sanitizer.
190
+
191
+ ## Guards And Types
192
+
193
+ - `isBoolean`/`isNumber`/`isString` accept primitives only: boxed
194
+ instances such as `new Boolean(false)` return `false` and are never
195
+ narrowed to primitives.
196
+ - `flattenDeep<T>(input)` takes `unknown` (a non-array yields `[]`) and
197
+ `T` is an unchecked caller assertion — specify it explicitly
198
+ (`flattenDeep<number>(input)`) or narrow `unknown[]` yourself. Cyclic
199
+ arrays throw `TypeError`; shared (non-ancestor) subarrays flatten once
200
+ per occurrence. Wide and deeply nested inputs flatten iteratively
201
+ without `RangeError`.
202
+ - `intersectionBy`/`difference` ignore non-array value arguments, while
203
+ `intersection` treats a non-array secondary as empty (result `[]`).
204
+ `intersectionBy` evaluates each element's iteratee once per input array;
205
+ redundant-callback side effects are not preserved.
72
206
 
73
207
  ## Documentation
74
208
 
75
- Full package documentation lives in `website/docs/packages/utils.md`.
209
+ Full package documentation lives on the published docs site:
76
210
 
77
211
  - live docs: https://web-ts-toolkit.pages.dev/docs/packages/utils