@web-ts-toolkit/utils 0.43.0 → 0.44.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
 
@@ -69,9 +68,141 @@ parseBooleanString('true', false);
69
68
  - string helpers: `startCase`, `upperCase`
70
69
  - guards: `isArray`, `isPlainObject`, `isString`, `isPromise`
71
70
  - URL helpers: `addLeadingSlash`, `removeConsecutiveSlashesFromUrl`, `normalizeUrlPath`
71
+ - async helpers: `mapValuesAsync`, `toAsyncFn`
72
+ - misc: `castArray`, `arrayToRecord`, `mapValues`, `noop`, `padEnd`, `parseBooleanString`
73
+
74
+ ## Path Grammar And Mutation Rules
75
+
76
+ `get`, `set`, `pick`, and `omit` accept a `PropertyPath`: dot segments
77
+ (`'a.b'`), bare brackets (`'a[0]'`), quoted brackets (`'a["b.c"]'`), or
78
+ segment arrays (`['a', 'b']`).
79
+
80
+ - Key identity is literal: string segments are never coerced, so `'01'`
81
+ and `'1'` address different properties, and digit keys beyond
82
+ `MAX_SAFE_INTEGER` never round. Only canonical indices (`'0'`, `'1'`,
83
+ … with no leading zeros) address array slots; `'a[01]'` addresses an
84
+ own `'01'` property. Empty quoted keys, escaped quotes, empty dot
85
+ segments, and malformed brackets are unspecified (no Lodash parity).
86
+ - Mutation segments `__proto__`, `constructor`, and `prototype` (quoted
87
+ or not) are rejected before any write: `set` returns its target
88
+ unchanged and `omit`/`deletePath` are no-ops.
89
+ - In `pick`/`omit`, a flat string array is a **list** of paths
90
+ (`pick(o, ['a', 'b'])` picks keys `a` and `b`); pass a nested array for
91
+ a single segmented path (`pick(o, [['a', 'b']])` picks `a.b`).
92
+ - Reads follow the prototype chain; `get` returns `defaultValue` for a
93
+ `null`/`undefined` intermediate or an `undefined` leaf. Writes never
94
+ traverse inherited containers: an inherited member is shadowed with a
95
+ new own container (array for a following canonical index, plain object
96
+ otherwise) and the final write creates an own data property, bypassing
97
+ inherited setters. Use `hasOwn` when own-key presence matters.
98
+ - Dictionary builders (`groupBy`, `arrayToRecord`, `mapKeys`,
99
+ `mapValues`, `pickBy`, `omitBy`, `toStringRecord`) preserve arbitrary
100
+ string keys — including `__proto__` — as own data properties without
101
+ replacing the result prototype. Results keep `Object.prototype`
102
+ (never null-prototype).
103
+
104
+ ## Mutation Versus Copying
105
+
106
+ - `set` mutates its target in place and returns it. `omit` never mutates
107
+ its input: it deep-clones first, then deletes from the clone.
108
+ - `assign` is a thin wrapper over native `Object.assign` (source getters
109
+ and target setters run); it is not a hardened untrusted-input copier.
110
+ - `orderBy`, `uniq`/`uniqBy`, `difference`, the `intersection` family,
111
+ and `flatten`/`flattenDeep` never mutate their inputs. Sorting and
112
+ deduplication are stable/first-occurrence: ties keep input order and
113
+ the first occurrence wins.
114
+
115
+ ## Clone And Comparison Domains
116
+
117
+ `cloneDeep`, `isEqual`, and `isMatch` share one bounded domain:
118
+
119
+ - Supported: primitives (`NaN` equals itself), plain objects
120
+ (`null`/`Object.prototype`, plus `Object.create` graphs over plain
121
+ ancestors whose prototype is shared by reference), arrays (length,
122
+ holes, and extra own keys participate), `Date` (by time), and `RegExp`
123
+ (by source plus flags). Cycles and repeated references terminate and
124
+ stay shared within the clone.
125
+ - Functions and exotic values (`Map`/`Set`, class instances such as BSON
126
+ `ObjectId`) are opaque: nested occurrences are shared by reference,
127
+ never traversed, and distinct references are never equal. A top-level
128
+ exotic root passed to `cloneDeep` throws `TypeError` instead of
129
+ returning an alias, so `omit` on an uncloneable root throws before
130
+ deleting anything rather than deleting from your input.
131
+ - Comparison uses own enumerable string/symbol keys only: inherited
132
+ state is ignored and prototypes are not compared. `isMatch` requires
133
+ each own source key (including `undefined`-valued and symbol keys) to
134
+ exist as an own key on the target, so `isMatch({}, { a: undefined })`
135
+ is `false`; arrays use prefix semantics.
136
+
137
+ ## Async Contracts
138
+
139
+ - `toAsyncFn` lifts sync results into a promise, but it is not a full
140
+ async-function boundary: a synchronous `throw` escapes synchronously
141
+ instead of becoming a rejection, and thenables (including foreign
142
+ thenables) are returned unchanged with identity preserved rather than
143
+ converted to native promises. When `fn` is absent, the wrapper resolves
144
+ `defaultValue`. `this` is forwarded.
145
+ - `mapValuesAsync` starts every callback eagerly with unbounded
146
+ parallelism (`Promise.all`): one rejection rejects the whole call, and
147
+ there is no concurrency limit, cancellation, or scheduler. Chunk the
148
+ input if downstream throttling is needed.
149
+
150
+ ## Boolean Strings
151
+
152
+ ```ts
153
+ import { parseBooleanString } from '@web-ts-toolkit/utils';
154
+
155
+ parseBooleanString('true'); // true
156
+ parseBooleanString('false'); // false
157
+ parseBooleanString('TRUE'); // false — exact match only, no case folding
158
+ parseBooleanString(''); // undefined — empty string falls back to the default
159
+ parseBooleanString('', false); // false — via the default
160
+ parseBooleanString(undefined, true); // true — missing input uses the default
161
+ ```
162
+
163
+ `parseBooleanString(str, defaultValue)` returns `true` only for the exact
164
+ string `'true'`, returns `false` for any other non-empty string, and falls
165
+ back to `defaultValue` (which is `undefined` when omitted) when the input
166
+ is `undefined` **or the empty string `''`**. Note that an Express-style
167
+ `?flag=` query value parses to `''` and therefore yields the default, not
168
+ `false`.
169
+
170
+ ## URL Paths Are Pathname-Only
171
+
172
+ ```ts
173
+ import { normalizeUrlPath } from '@web-ts-toolkit/utils';
174
+
175
+ normalizeUrlPath('api//users'); // '/api/users'
176
+ normalizeUrlPath('api//users/42'); // '/api/users/42'
177
+ ```
178
+
179
+ `normalizeUrlPath` composes route-path fragments: it collapses every run
180
+ of slashes and prepends a leading slash. The input must be a path
181
+ fragment — no scheme/host, query string, or fragment. Full URLs are out
182
+ of domain and are mangled rather than normalized
183
+ (`normalizeUrlPath('https://example.com//a')` yields
184
+ `'/https:/example.com/a'`; slash runs inside query/fragment values are
185
+ collapsed too). These helpers are route-path composition for workspace
186
+ routers, not WHATWG URL normalization and not a security sanitizer.
187
+
188
+ ## Guards And Types
189
+
190
+ - `isBoolean`/`isNumber`/`isString` accept primitives only: boxed
191
+ instances such as `new Boolean(false)` return `false` and are never
192
+ narrowed to primitives.
193
+ - `flattenDeep<T>(input)` takes `unknown` (a non-array yields `[]`) and
194
+ `T` is an unchecked caller assertion — specify it explicitly
195
+ (`flattenDeep<number>(input)`) or narrow `unknown[]` yourself. Cyclic
196
+ arrays throw `TypeError`; shared (non-ancestor) subarrays flatten once
197
+ per occurrence. Wide and deeply nested inputs flatten iteratively
198
+ without `RangeError`.
199
+ - `intersectionBy`/`difference` ignore non-array value arguments, while
200
+ `intersection` treats a non-array secondary as empty (result `[]`).
201
+ `intersectionBy` evaluates each element's iteratee once per input array;
202
+ redundant-callback side effects are not preserved.
72
203
 
73
204
  ## Documentation
74
205
 
75
- Full package documentation lives in `website/docs/packages/utils.md`.
206
+ Full package documentation lives on the published docs site:
76
207
 
77
208
  - live docs: https://web-ts-toolkit.pages.dev/docs/packages/utils
package/index.d.mts CHANGED
@@ -1,15 +1,57 @@
1
+ /**
2
+ * Ensure `value` starts with a `/`. Values that already start with `/` are
3
+ * returned unchanged; the empty string becomes `'/'`.
4
+ */
1
5
  declare function addLeadingSlash(value: string): string;
2
6
 
7
+ /**
8
+ * Converts an array of strings to a lookup record.
9
+ *
10
+ * UTILS-01 contract: every entry becomes an own data property, including
11
+ * `__proto__` (which plain assignment would drop by attempting a prototype
12
+ * set). The result keeps the default `Object.prototype` prototype.
13
+ */
3
14
  declare function arrayToRecord(arr: string[]): Record<string, true>;
4
15
 
16
+ /**
17
+ * Thin wrapper over native `Object.assign`: copies own enumerable
18
+ * string/symbol properties from each source onto `target` and returns
19
+ * `target`. Source getters and target setters run as normal JavaScript
20
+ * semantics dictate. This is a plain copy helper, not a hardened
21
+ * untrusted-input sanitizer.
22
+ */
5
23
  declare function assign<T extends object>(target: T, ...sources: unknown[]): T;
6
24
 
7
25
  declare function castArray<T>(value: T | T[]): T[];
8
26
 
27
+ /**
28
+ * Deep-clones a value within the UTILS-04 bounded domain.
29
+ *
30
+ * Supported: primitives, plain objects (`null`/`Object.prototype`),
31
+ * `Object.create` graphs over plain-object ancestors (prototype preserved
32
+ * by reference, own keys deep-cloned), arrays, `Date`, `RegExp`. Functions
33
+ * and exotic values nested inside a supported container (class instances
34
+ * such as BSON `ObjectId`, `Map`/`Set`) are preserved by reference as
35
+ * opaque leaves. A top-level exotic root throws `TypeError` instead of
36
+ * returning an alias.
37
+ * Cycles and repeated references are preserved (shared stays shared,
38
+ * detached from the original). Class instances, `Map`/`Set`, and other
39
+ * non-plain objects throw `TypeError` instead of returning an alias.
40
+ */
9
41
  declare function cloneDeep<T>(value: T): T;
10
42
 
11
43
  declare function compact<T>(array: T[] | null | undefined): T[];
12
44
 
45
+ /**
46
+ * Set difference.
47
+ *
48
+ * UTILS-08: exclusion membership is precomputed once into a `Set`
49
+ * (SameValueZero: NaN equals NaN, +0/-0 equal; objects by reference),
50
+ * matching the previous linear `arrayIncludes` scan. First-occurrence
51
+ * order, item references, and input non-mutation are preserved. Non-array
52
+ * `values` entries are ignored (preserved ignore-policy, consistent with
53
+ * `intersectionBy`; differs from `intersection`'s empty-on-non-array rule).
54
+ */
13
55
  declare function difference<T>(array: T[] | null | undefined, ...values: Array<T[] | null | undefined>): T[];
14
56
 
15
57
  declare function eachRight<T>(collection: T[] | null | undefined, iteratee: (value: T, key: number, collection: T[]) => unknown): T[] | null | undefined;
@@ -21,47 +63,205 @@ declare function find<T>(collection: T[] | Record<string, T> | null | undefined,
21
63
 
22
64
  declare function flatten<T>(array: Array<T | T[]> | null | undefined): T[];
23
65
 
24
- declare function flattenDeep<T>(array: unknown[]): T[];
66
+ /**
67
+ * Recursively flatten nested arrays into a single flat array.
68
+ *
69
+ * Iterative implementation: uses an explicit frame stack instead of call-stack
70
+ * recursion and pushes leaves one at a time instead of spreading whole child
71
+ * results into `push`, so wide (many-leaf) and deeply nested inputs do not
72
+ * throw `RangeError`. Left-to-right ordering is preserved.
73
+ *
74
+ * Leaf behavior: any non-array value (including `undefined`, holes read as
75
+ * `undefined`, objects, and functions) is appended as-is. A non-array input
76
+ * returns `[]`. There are no size limits.
77
+ *
78
+ * Cycle policy: an array that contains itself, directly or transitively
79
+ * through its current ancestor chain, throws a `TypeError`. Legitimate
80
+ * repeated (shared but non-ancestor) subarrays are flattened once per
81
+ * occurrence, so `const sub = [1]; flattenDeep([sub, sub])` is `[1, 1]`.
82
+ *
83
+ * @param array The value to flatten; non-array inputs yield `[]`.
84
+ * @returns A new flat array of the leaf values, in left-to-right order.
85
+ * @throws {TypeError} When a cyclic array reference is detected.
86
+ *
87
+ * UTILS-09 typing note: `T` is an unchecked caller assertion, not an
88
+ * inferred or validated leaf type — the function cannot verify element
89
+ * types at runtime. Specify it explicitly for typed leaves (e.g.
90
+ * `flattenDeep<number>(input)`) or narrow the default `unknown[]`
91
+ * yourself. A fully inferred recursive `FlatArray`-style result type is
92
+ * deliberately deferred: it would need an arbitrary depth cap and could
93
+ * not stay sound for non-array inputs (which yield `[]`).
94
+ */
95
+ declare function flattenDeep<T = unknown>(array: unknown): T[];
25
96
 
26
97
  declare function forEach<T>(collection: T[] | null | undefined, iteratee: (value: T, key: number, collection: T[]) => unknown): T[] | null | undefined;
27
98
  declare function forEach<T extends object>(collection: T | null | undefined, iteratee: (value: T[keyof T], key: string, collection: T) => unknown): T | null | undefined;
28
99
 
29
100
  type PropertyPath = string | number | Array<string | number>;
30
101
 
102
+ /**
103
+ * Read the value at `path`, returning `defaultValue` when unreachable.
104
+ *
105
+ * Supported path grammar (see `toPath` in `_internal.ts`): dot segments
106
+ * (`'a.b'`), bare brackets (`'a[0]'`), quoted brackets (`'a["b.c"]'`), and
107
+ * segment arrays (`['a', 'b']`). String segments keep literal identity
108
+ * (`'01'` ≠ `'1'`); only canonical indices address array slots.
109
+ *
110
+ * Reads follow the prototype chain; use `hasOwn` when own-key presence
111
+ * matters. A `null`/`undefined` intermediate or an `undefined` leaf yields
112
+ * `defaultValue`, so an explicit `undefined` value is indistinguishable
113
+ * from a missing path.
114
+ */
115
+
31
116
  declare function get<T = unknown>(object: unknown, path: PropertyPath, defaultValue?: T): T;
32
117
 
118
+ /**
119
+ * Groups collection values by iteratee result.
120
+ *
121
+ * UTILS-01 contract: group names are ordinary string keys preserved as own
122
+ * data properties, including `__proto__`, `constructor`, `prototype`, and
123
+ * `toString`. The result keeps the default `Object.prototype` prototype;
124
+ * inherited members are never read as existing groups.
125
+ */
33
126
  declare function groupBy<T>(collection: T[] | Record<string, T> | null | undefined, iteratee: string | number | ((value: T, key: number | string, collection: T[] | Record<string, T>) => unknown)): Record<string, T[]>;
34
127
 
128
+ /**
129
+ * Own-property check (`Object.hasOwn`): inherited properties do not count,
130
+ * and `null`/`undefined` values safely return `false`.
131
+ */
35
132
  declare function hasOwn<TKey extends PropertyKey>(value: unknown, key: TKey): value is Record<TKey, unknown>;
36
133
 
134
+ /**
135
+ * Set intersection.
136
+ *
137
+ * UTILS-08: each secondary array's membership is precomputed once into a
138
+ * `Set` (SameValueZero: NaN equals NaN, +0/-0 equal; objects by reference),
139
+ * matching the previous linear `arrayIncludes` scans. First-occurrence
140
+ * order (via `baseUniq`), item references, and input non-mutation are
141
+ * preserved.
142
+ *
143
+ * Nullish contract (parity decision, preserved as-is): a non-array first
144
+ * argument yields `[]`, and any non-array secondary empties the result
145
+ * (`every` requires `Array.isArray`). This strict rule differs from
146
+ * `intersectionBy`/`difference`, which ignore non-array values arguments.
147
+ * The divergence is documented here rather than silently unified;
148
+ * unification is deferred to UTILS-11.
149
+ */
37
150
  declare function intersection<T>(...arrays: Array<T[] | null | undefined>): T[];
38
151
 
39
- declare function intersectionBy<T>(...args: unknown[]): T[];
152
+ /**
153
+ * Intersection by iteratee with precomputed secondary membership.
154
+ *
155
+ * UTILS-08 performance contract:
156
+ * - Each element's iteratee is evaluated exactly once per input array
157
+ * (work proportional to total input length), not once per candidate per
158
+ * secondary. A 100-by-100 workload therefore invokes the callback ~200
159
+ * times, not 10,100 times as before. Callback invocation count is
160
+ * intentionally changed; side effects from redundant evaluation are not
161
+ * preserved.
162
+ * - Secondary projections are stored in `Set`s, which use SameValueZero
163
+ * membership (NaN equals NaN, +0/-0 equal; objects by reference),
164
+ * matching the previous `sameValueZero` scans (UTILS-05).
165
+ * - First-occurrence ordering and original item references are preserved;
166
+ * inputs are never mutated.
167
+ *
168
+ * Nullish contract (parity decision, preserved as-is): non-array arguments
169
+ * are dropped via `Array.isArray` filtering (same ignore-policy as
170
+ * `difference`), so a nullish secondary is ignored rather than emptying
171
+ * the result. This differs from `intersection`, which treats a non-array
172
+ * secondary as empty (result `[]`). The divergence is documented here
173
+ * rather than silently unified; unification is deferred to UTILS-11.
174
+ */
175
+ type IntersectionByIteratee<T> = ((value: T) => unknown) | string | number;
176
+ /**
177
+ * Intersection by iteratee with precomputed secondary membership.
178
+ *
179
+ * UTILS-09: the result element type is inferred from the first array; the
180
+ * iteratee parameter is excluded from inference (`NoInfer`) so a callback
181
+ * never distorts the element type. Non-array secondary arguments are ignored
182
+ * at runtime (see below) and are accepted in the type for that reason.
183
+ */
184
+ declare function intersectionBy<T>(array: readonly T[], ...args: Array<readonly unknown[] | IntersectionByIteratee<NoInfer<T>> | null | undefined>): T[];
185
+ declare function intersectionBy<T = unknown>(...args: Array<readonly T[] | IntersectionByIteratee<T> | null | undefined>): T[];
40
186
 
41
187
  declare function isArray(value: unknown): value is unknown[];
42
188
 
189
+ /**
190
+ * Whether a value is a primitive boolean.
191
+ *
192
+ * UTILS-09 contract change: boxed `Boolean` objects are no longer accepted.
193
+ * A boxed instance (e.g. `new Boolean(false)`) is a truthy object, not the
194
+ * primitive `false`, so narrowing it to `boolean` was unsound. Use
195
+ * `value instanceof Boolean` explicitly when boxed instances are intended.
196
+ */
43
197
  declare function isBoolean(value: unknown): value is boolean;
44
198
 
45
199
  declare function isEmpty(value: unknown): boolean;
46
200
 
201
+ /**
202
+ * Deep equality within the UTILS-05 bounded domain (aligned with UTILS-04).
203
+ *
204
+ * Supported: primitives (`NaN` equals itself), plain objects (own
205
+ * enumerable string/symbol keys only; inherited state ignored, prototypes
206
+ * not compared), arrays (length + holes + extra own keys), `Date` (by
207
+ * time), `RegExp` (by source + flags). Functions and exotic objects
208
+ * (`Map`/`Set`, class instances, etc.) compare by identity only, so
209
+ * distinct instances are never equal. Cyclic graphs terminate via
210
+ * pair-aware bookkeeping. See `deepEqual` in `_internal.ts` for the full
211
+ * contract.
212
+ */
47
213
  declare function isEqual(left: unknown, right: unknown): boolean;
48
214
 
49
215
  declare function isFunction(value: unknown): value is (...args: unknown[]) => unknown;
50
216
 
217
+ /**
218
+ * Partial matching within the UTILS-05 bounded domain.
219
+ *
220
+ * Every own enumerable string/symbol key of a plain `source` must exist
221
+ * as an own key on `object` and match recursively; extra keys on
222
+ * `object` are ignored. Absence is distinguished from `undefined`
223
+ * (`isMatch({}, { a: undefined })` is false). Arrays use prefix
224
+ * semantics. `Date`/`RegExp`/primitive/function/exotic sources fall back
225
+ * to `deepEqual` (identity for opaque types). See `partialMatch` in
226
+ * `_internal.ts` for the full contract.
227
+ */
51
228
  declare function isMatch(object: unknown, source: unknown): boolean;
52
229
 
53
230
  declare function isNaNValue(value: unknown): boolean;
54
231
 
55
232
  declare function isNil(value: unknown): value is null | undefined;
56
233
 
234
+ /**
235
+ * Whether a value is a primitive number.
236
+ *
237
+ * UTILS-09 contract change: boxed `Number` objects are no longer accepted.
238
+ * A boxed instance (e.g. `new Number(0)`) is an object, not the primitive
239
+ * `0`, so narrowing it to `number` was unsound. Use
240
+ * `value instanceof Number` explicitly when boxed instances are intended.
241
+ */
57
242
  declare function isNumber(value: unknown): value is number;
58
243
 
59
244
  declare function isObject(value: unknown): value is object;
60
245
 
61
246
  declare function isPlainObject(value: unknown): value is Record<string, unknown>;
62
247
 
248
+ /**
249
+ * Thenable check: true for any non-null value whose `then` is a function.
250
+ *
251
+ * UTILS-07 contract: native promises and foreign/cross-realm thenables
252
+ * satisfy this; a throwing `then` accessor propagates its throw. It does
253
+ * not verify genuine promise semantics beyond a callable `then`.
254
+ */
63
255
  declare function isPromise<T = unknown>(value: unknown): value is PromiseLike<T>;
64
256
 
257
+ /**
258
+ * Whether a value is a primitive string.
259
+ *
260
+ * UTILS-09 contract change: boxed `String` objects are no longer accepted.
261
+ * A boxed instance (e.g. `new String('')`) is an object, not the primitive
262
+ * `''`, so narrowing it to `string` was unsound. Use
263
+ * `value instanceof String` explicitly when boxed instances are intended.
264
+ */
65
265
  declare function isString(value: unknown): value is string;
66
266
 
67
267
  declare function isUndefined(value: unknown): value is undefined;
@@ -70,37 +270,180 @@ declare function join(array: unknown[] | null | undefined, separator?: string):
70
270
 
71
271
  declare function keys(value: unknown): string[];
72
272
 
273
+ /**
274
+ * Map an array by a known element key: the result element type follows the
275
+ * property type (e.g. `map(users, 'name')` yields `string[]`).
276
+ *
277
+ * UTILS-09: deeper property paths (`'a.b'`) are not key types and fall
278
+ * through to the generic overload below, yielding an honest `unknown[]`
279
+ * instead of a fabricated inference. No speculative path-type machinery.
280
+ */
281
+ declare function map<T, K extends keyof T>(collection: readonly T[] | null | undefined, iteratee: K): Array<T[K]>;
73
282
  declare function map<T, TResult>(collection: T[] | Record<string, T> | null | undefined, iteratee: string | number | ((value: T, key: number | string, collection: T[] | Record<string, T>) => TResult)): TResult[];
74
283
 
284
+ /**
285
+ * Maps object keys through an iteratee.
286
+ *
287
+ * UTILS-01 contract: mapped keys are stored as own data properties, so a
288
+ * mapped `__proto__` with an object value does not replace the result
289
+ * prototype. The result keeps the default `Object.prototype` prototype.
290
+ * Last-write-wins on duplicate mapped keys; callback order follows
291
+ * `Object.keys` order.
292
+ */
75
293
  declare function mapKeys<TValue>(object: Record<string, TValue> | null | undefined, iteratee: (value: TValue, key: string, object: Record<string, TValue>) => string): Record<string, TValue>;
76
294
 
295
+ /**
296
+ * Maps object values through an iteratee.
297
+ *
298
+ * UTILS-01 contract: keys are preserved as own data properties (including
299
+ * `__proto__` with object values). The result keeps the default
300
+ * `Object.prototype` prototype; callback order follows `Object.keys` order.
301
+ */
77
302
  declare function mapValues<TValue, TResult>(object: Record<string, TValue> | null | undefined, iteratee: (value: TValue, key: string, object: Record<string, TValue>) => TResult): Record<string, TResult>;
78
303
 
304
+ /**
305
+ * Map an object's values through an async callback.
306
+ *
307
+ * UTILS-07 contract (current behavior, locked by tests): all callbacks
308
+ * start eagerly with unbounded parallelism (`Promise.all`), preserving
309
+ * key order in the result. There is no concurrency limit, cancellation,
310
+ * or scheduler; a single rejection (or sync callback throw) rejects the
311
+ * whole call. An opt-in concurrency option is deferred to a follow-up
312
+ * (see UTILS-07 decision) pending a concrete caller need.
313
+ */
79
314
  declare function mapValuesAsync<TObject extends Record<string, unknown>, TResult>(object: TObject, asyncFn: (value: TObject[keyof TObject], key: string, object: TObject) => Promise<TResult> | TResult): Promise<Record<string, TResult>>;
80
315
 
81
316
  declare function noop(): void;
82
317
 
318
+ /**
319
+ * UTILS-03: `paths` is one path or a list of paths. A flat string array is a
320
+ * LIST of paths (`omit(o, ['a', 'b'])` omits keys `a` and `b`), not one
321
+ * segmented path. Pass a nested array for a single segmented path:
322
+ * `omit(o, [['a', 'b']])` omits `a.b`. Key identity follows `toPath`.
323
+ *
324
+ * UTILS-04: `omit` never mutates its input. The input is deep-cloned first
325
+ * within the bounded clone domain (plain objects, `Object.create` graphs
326
+ * over plain data, arrays, `Date`, `RegExp`; functions and nested exotics
327
+ * such as `ObjectId`/`Map` preserved by reference as opaque leaves).
328
+ * A top-level unsupported root (class instance, `Map`/`Set`, etc.) throws
329
+ * `TypeError` before any deletion instead of returning an aliased clone
330
+ * that would delete from input-owned state.
331
+ */
83
332
  declare function omit<T extends object>(object: T, paths: PropertyPath | PropertyPath[]): Partial<T>;
84
333
 
334
+ /**
335
+ * Omits entries whose predicate returns true.
336
+ *
337
+ * UTILS-01 contract: kept keys are stored as own data properties (including
338
+ * `__proto__` with object values). The result keeps the default
339
+ * `Object.prototype` prototype; the input is never mutated.
340
+ */
85
341
  declare function omitBy<TValue>(object: Record<string, TValue> | null | undefined, predicate: (value: TValue, key: string, object: Record<string, TValue>) => boolean): Record<string, TValue>;
86
342
 
343
+ /**
344
+ * Parse a string-encoded boolean with an explicit fallback.
345
+ *
346
+ * Returns `true` only for the exact string `'true'`. Any other non-empty
347
+ * string (`'false'`, `'TRUE'`, `'1'`, …) returns `false` — matching is
348
+ * case-sensitive and no trimming is applied. The empty string `''` is
349
+ * treated like missing input and yields `defaultValue` (which is
350
+ * `undefined` when omitted); it does NOT return `false`.
351
+ *
352
+ * Query-string note: an Express-style `?flag=` parses to `''` and therefore
353
+ * falls back to `defaultValue`, not `false`. Pass an explicit default when
354
+ * that distinction matters.
355
+ */
87
356
  declare function parseBooleanString(str: string | undefined, defaultValue?: boolean): boolean | undefined;
88
357
 
358
+ /**
359
+ * Sort a collection copy by iteratees with a stable tie-break.
360
+ *
361
+ * Ties keep their original relative order, and the input is never mutated
362
+ * (a copy is sorted). An unrecognized `orders` entry sorts ascending; a
363
+ * non-array collection yields `[]`.
364
+ */
89
365
  declare function orderBy<T>(collection: T[] | null | undefined, iteratees?: Array<string | number | ((value: T) => unknown)>, orders?: string[]): T[];
90
366
 
91
367
  declare function padEnd(value: unknown, length?: number, chars?: string): string;
92
368
 
369
+ /**
370
+ * UTILS-03: `paths` is one path or a list of paths. A flat string array is a
371
+ * LIST of paths (`pick(o, ['a', 'b'])` picks keys `a` and `b`), not one
372
+ * segmented path. Pass a nested array for a single segmented path:
373
+ * `pick(o, [['a', 'b']])` picks `a.b`. Key identity follows `toPath`
374
+ * (literal `'01'`, quoted digit keys, canonical numeric indices).
375
+ */
93
376
  declare function pick<T extends object>(object: T, paths: PropertyPath | PropertyPath[]): Partial<T>;
94
377
 
378
+ /**
379
+ * Picks entries whose predicate returns true.
380
+ *
381
+ * UTILS-01 contract: kept keys are stored as own data properties (including
382
+ * `__proto__`). The result keeps the default `Object.prototype` prototype;
383
+ * the input is never mutated.
384
+ */
95
385
  declare function pickBy<TValue>(object: Record<string, TValue> | null | undefined, predicate: (value: TValue, key: string, object: Record<string, TValue>) => boolean): Record<string, TValue>;
96
386
 
387
+ /**
388
+ * Normalize a route-path fragment: collapse consecutive slashes and ensure
389
+ * a leading slash (`'api//users'` → `'/api/users'`).
390
+ *
391
+ * Pathname-only contract: the input must be a path fragment without a
392
+ * scheme/host, query string, or fragment. Full URLs are out of domain —
393
+ * slash runs are collapsed everywhere, including `https://` (which becomes
394
+ * `https:/`) and inside query/fragment values, and a leading slash is
395
+ * always prepended. This composes workspace router paths; it is not WHATWG
396
+ * URL normalization and not a security sanitizer.
397
+ */
97
398
  declare function normalizeUrlPath(url: string): string;
98
399
 
99
- declare function reduce<T, TResult>(collection: T[] | null | undefined, iteratee: (accumulator: TResult, value: T, key: number, collection: T[]) => TResult, accumulator?: TResult): TResult;
100
- declare function reduce<T, TResult>(collection: Record<string, T> | null | undefined, iteratee: (accumulator: TResult, value: T, key: string, collection: Record<string, T>) => TResult, accumulator?: TResult): TResult;
101
-
400
+ /**
401
+ * Reduce an array to a single value with an explicit initial accumulator.
402
+ *
403
+ * UTILS-09: the with-initial and without-initial contracts are separate
404
+ * overloads (previously one ambiguous optional-accumulator signature). An
405
+ * empty collection with an initial value returns that value.
406
+ */
407
+ declare function reduce<T, TResult>(collection: T[] | null | undefined, iteratee: (accumulator: TResult, value: T, key: number, collection: T[]) => TResult, accumulator: TResult): TResult;
408
+ /**
409
+ * Reduce a record to a single value with an explicit initial accumulator.
410
+ */
411
+ declare function reduce<T, TResult>(collection: Record<string, T> | null | undefined, iteratee: (accumulator: TResult, value: T, key: string, collection: Record<string, T>) => TResult, accumulator: TResult): TResult;
412
+ /**
413
+ * Reduce an array without an initial value: the first element seeds the
414
+ * accumulator, so the accumulator and result share the element type. Throws
415
+ * `TypeError` on an empty collection (never silently yields `undefined`).
416
+ */
417
+ declare function reduce<T>(collection: T[] | null | undefined, iteratee: (accumulator: T, value: T, key: number, collection: T[]) => T): T;
418
+ /**
419
+ * Reduce a record without an initial value: the first value seeds the
420
+ * accumulator. Throws `TypeError` on an empty collection.
421
+ */
422
+ declare function reduce<T>(collection: Record<string, T> | null | undefined, iteratee: (accumulator: T, value: T, key: string, collection: Record<string, T>) => T): T;
423
+
424
+ /**
425
+ * Collapse every run of two or more `/` characters to a single `/`.
426
+ *
427
+ * Pathname-only helper (see `normalizeUrlPath`): runs are collapsed
428
+ * everywhere in the string, including a URL scheme (`'https://…'` becomes
429
+ * `'https:/…'`) and query/fragment values. Do not apply to full URLs.
430
+ */
102
431
  declare function removeConsecutiveSlashesFromUrl(url: string): string;
103
432
 
433
+ /**
434
+ * Sets `value` at `path` of `object` and returns `object`.
435
+ *
436
+ * UTILS-02 contract: forbidden segments (`__proto__`, `constructor`,
437
+ * `prototype`) are rejected before any mutation. Intermediate segments reuse
438
+ * only own object containers; inherited containers are shadowed with a new
439
+ * own container (array when the next segment is numeric, plain object
440
+ * otherwise) so ancestors are never mutated. The final write creates an own
441
+ * data property, bypassing inherited setters when shadowing.
442
+ *
443
+ * UTILS-03 key identity: string segments keep their literal identity (`'01'`
444
+ * stays `'01'`, never index 1; digit keys beyond `MAX_SAFE_INTEGER` never
445
+ * round). Arrays are created only for canonical indices (see `toPath`).
446
+ */
104
447
  declare function set<T>(object: T, path: PropertyPath, value: unknown): T;
105
448
 
106
449
  declare function startCase(value: unknown): string;
@@ -109,14 +452,61 @@ declare function sum(values: number[] | null | undefined): number;
109
452
 
110
453
  declare function sumBy<T>(collection: T[] | null | undefined, iteratee: string | number | ((value: T, key: number, collection: T[]) => number | undefined)): number;
111
454
 
112
- declare function toAsyncFn<TArgs extends unknown[], TResult>(fn?: ((this: unknown, ...args: TArgs) => TResult | PromiseLike<TResult>) | null, defaultValue?: TResult): (this: unknown, ...args: TArgs) => PromiseLike<unknown> | Promise<any>;
113
-
455
+ /**
456
+ * Wrap a function so sync results are lifted into a promise.
457
+ *
458
+ * UTILS-07 contract (current behavior, locked by tests):
459
+ * - This is NOT a full async-function boundary. A synchronous `throw`
460
+ * from `fn` escapes synchronously instead of becoming a rejection.
461
+ * - Thenable results (including foreign/cross-realm thenables) are
462
+ * returned unchanged with identity preserved; they are not converted
463
+ * into native promises. A throwing `then` accessor therefore throws
464
+ * synchronously during the `isPromise` check.
465
+ * - When `fn` is absent/null, the wrapper resolves `defaultValue` in a
466
+ * native promise. `this` is forwarded via `fn.apply`.
467
+ * - UTILS-09 gave this contract an explicit truthful result type (see the
468
+ * overloads); do not widen behavior here without maintainer approval.
469
+ *
470
+ * UTILS-09 typing note: the overloads below type that contract truthfully.
471
+ * A present `fn` yields `Promise<Awaited<TResult>> | PromiseLike<TResult>` —
472
+ * deliberately NOT a bare `Promise`, because thenables pass through with
473
+ * identity preserved (so `instanceof Promise` may be false). A synchronous
474
+ * `throw` is not expressible in the return type and stays documented above.
475
+ * An absent `fn` yields `Promise<TResult | undefined>` since an omitted
476
+ * `defaultValue` resolves `undefined`.
477
+ */
478
+ declare function toAsyncFn<TArgs extends unknown[], TResult>(fn: (this: unknown, ...args: TArgs) => TResult | PromiseLike<TResult>, defaultValue?: TResult): (this: unknown, ...args: TArgs) => Promise<Awaited<TResult>> | PromiseLike<TResult>;
479
+ declare function toAsyncFn<TArgs extends unknown[], TResult>(fn: null | undefined, defaultValue?: TResult): (this: unknown, ...args: TArgs) => Promise<TResult | undefined>;
480
+
481
+ /**
482
+ * Stringifies own values of a plain object.
483
+ *
484
+ * UTILS-01 contract: keys are preserved as own data properties (including
485
+ * `__proto__`, which plain assignment would drop for string values). The
486
+ * result keeps the default `Object.prototype` prototype; the input is never
487
+ * mutated.
488
+ */
114
489
  declare function toStringRecord(value: unknown): Record<string, string> | undefined;
115
490
 
491
+ /**
492
+ * Deduplicate an array keeping the first occurrence of each value.
493
+ *
494
+ * Membership is SameValueZero (`NaN` equals `NaN`, `+0`/`-0` equal; objects
495
+ * by reference). Never mutates the input; a non-array input yields `[]`.
496
+ */
116
497
  declare function uniq<T>(array: T[] | null | undefined): T[];
117
498
 
499
+ /**
500
+ * Dedup by iteratee.
501
+ *
502
+ * UTILS-08: `seen` membership uses a `Set` (SameValueZero: NaN equals NaN,
503
+ * +0/-0 equal; objects by reference), matching the previous linear
504
+ * `sameValueZero` scan. First-occurrence order, item references, and input
505
+ * non-mutation are preserved. The iteratee is still invoked exactly once
506
+ * per element.
507
+ */
118
508
  declare function uniqBy<T>(array: T[] | null | undefined, iteratee?: string | number | ((value: T, key: number, collection: T[]) => unknown)): T[];
119
509
 
120
510
  declare function upperCase(value: unknown): string;
121
511
 
122
- export { addLeadingSlash, arrayToRecord, assign, castArray, cloneDeep, compact, difference, eachRight, filter, find, flatten, flattenDeep, forEach, get, groupBy, hasOwn, intersection, intersectionBy, isArray, isBoolean, isEmpty, isEqual, isFunction, isMatch, isNaNValue as isNaN, isNil, isNumber, isObject, isPlainObject, isPromise, isString, isUndefined, join, keys, map, mapKeys, mapValues, mapValuesAsync, noop, normalizeUrlPath, omit, omitBy, orderBy, padEnd, parseBooleanString, pick, pickBy, reduce, removeConsecutiveSlashesFromUrl, set, startCase, sum, sumBy, toAsyncFn, toStringRecord, uniq, uniqBy, upperCase };
512
+ export { type PropertyPath, addLeadingSlash, arrayToRecord, assign, castArray, cloneDeep, compact, difference, eachRight, filter, find, flatten, flattenDeep, forEach, get, groupBy, hasOwn, intersection, intersectionBy, isArray, isBoolean, isEmpty, isEqual, isFunction, isMatch, isNaNValue as isNaN, isNil, isNumber, isObject, isPlainObject, isPromise, isString, isUndefined, join, keys, map, mapKeys, mapValues, mapValuesAsync, noop, normalizeUrlPath, omit, omitBy, orderBy, padEnd, parseBooleanString, pick, pickBy, reduce, removeConsecutiveSlashesFromUrl, set, startCase, sum, sumBy, toAsyncFn, toStringRecord, uniq, uniqBy, upperCase };