@web-ts-toolkit/utils 0.28.0 → 0.30.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
@@ -10,8 +10,8 @@ pnpm add @web-ts-toolkit/utils
10
10
 
11
11
  ## Highlights
12
12
 
13
- - object-path helpers such as `get(...)` and `set(...)`
14
- - collection helpers such as `map(...)`, `filter(...)`, `uniq(...)`, and `orderBy(...)`
13
+ - object-path helpers such as `get(...)`, `set(...)`, and `hasOwn(...)`
14
+ - collection helpers such as `map(...)`, `filter(...)`, `uniq(...)`, `uniqBy(...)`, and `orderBy(...)`
15
15
  - small type guards
16
16
  - URL helpers such as `normalizeUrlPath(...)`
17
17
  - async helpers such as `mapValuesAsync(...)`
@@ -19,7 +19,16 @@ pnpm add @web-ts-toolkit/utils
19
19
  ## Quick Start
20
20
 
21
21
  ```ts
22
- import { get, set, normalizeUrlPath, parseBooleanString } from '@web-ts-toolkit/utils';
22
+ import {
23
+ get,
24
+ groupBy,
25
+ hasOwn,
26
+ normalizeUrlPath,
27
+ parseBooleanString,
28
+ startCase,
29
+ sumBy,
30
+ uniqBy,
31
+ } from '@web-ts-toolkit/utils';
23
32
 
24
33
  const payload = {
25
34
  user: {
@@ -30,15 +39,34 @@ const payload = {
30
39
  };
31
40
 
32
41
  get(payload, 'user.profile.name');
33
- set(payload, 'user.profile.role', 'admin');
42
+ hasOwn(payload.user.profile, 'name');
43
+ groupBy(
44
+ [
45
+ { type: 'fruit', name: 'apple' },
46
+ { type: 'fruit', name: 'banana' },
47
+ { type: 'vegetable', name: 'carrot' },
48
+ ],
49
+ 'type',
50
+ );
51
+ uniqBy(
52
+ [
53
+ { id: 'a', name: 'Ada' },
54
+ { id: 'a', name: 'Ada Lovelace' },
55
+ { id: 'b', name: 'Grace' },
56
+ ],
57
+ 'id',
58
+ );
59
+ startCase('api_response_time');
60
+ sumBy([{ hours: 2 }, { hours: 3 }], 'hours');
34
61
  normalizeUrlPath('api//users');
35
62
  parseBooleanString('true', false);
36
63
  ```
37
64
 
38
65
  ## Main Exports
39
66
 
40
- - object helpers: `get`, `set`, `pick`, `omit`, `assign`, `cloneDeep`
41
- - collection helpers: `map`, `filter`, `reduce`, `find`, `flatten`, `uniq`, `orderBy`
67
+ - object helpers: `get`, `set`, `hasOwn`, `pick`, `pickBy`, `omit`, `omitBy`, `assign`, `cloneDeep`, `mapKeys`
68
+ - collection helpers: `map`, `filter`, `reduce`, `find`, `flatten`, `uniq`, `uniqBy`, `orderBy`, `groupBy`, `sum`, `sumBy`
69
+ - string helpers: `startCase`, `upperCase`
42
70
  - guards: `isArray`, `isPlainObject`, `isString`, `isPromise`
43
71
  - URL helpers: `addLeadingSlash`, `removeConsecutiveSlashesFromUrl`, `normalizeUrlPath`
44
72
 
package/index.d.mts CHANGED
@@ -27,6 +27,10 @@ type PropertyPath = string | number | Array<string | number>;
27
27
 
28
28
  declare function get<T = unknown>(object: unknown, path: PropertyPath, defaultValue?: T): T;
29
29
 
30
+ 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[]>;
31
+
32
+ declare function hasOwn<TKey extends PropertyKey>(value: unknown, key: TKey): value is Record<TKey, unknown>;
33
+
30
34
  declare function intersection<T>(...arrays: Array<T[] | null | undefined>): T[];
31
35
 
32
36
  declare function intersectionBy<T>(...args: unknown[]): T[];
@@ -63,6 +67,8 @@ declare function keys(value: unknown): string[];
63
67
 
64
68
  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[];
65
69
 
70
+ declare function mapKeys<TValue>(object: Record<string, TValue> | null | undefined, iteratee: (value: TValue, key: string, object: Record<string, TValue>) => string): Record<string, TValue>;
71
+
66
72
  declare function mapValues<TValue, TResult>(object: Record<string, TValue> | null | undefined, iteratee: (value: TValue, key: string, object: Record<string, TValue>) => TResult): Record<string, TResult>;
67
73
 
68
74
  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>>;
@@ -71,6 +77,8 @@ declare function noop(): void;
71
77
 
72
78
  declare function omit<T extends object>(object: T, paths: PropertyPath | PropertyPath[]): Partial<T>;
73
79
 
80
+ declare function omitBy<TValue>(object: Record<string, TValue> | null | undefined, predicate: (value: TValue, key: string, object: Record<string, TValue>) => boolean): Record<string, TValue>;
81
+
74
82
  declare function parseBooleanString(str: string | undefined, defaultValue?: boolean): boolean | undefined;
75
83
 
76
84
  declare function orderBy<T>(collection: T[] | null | undefined, iteratees?: Array<string | number | ((value: T) => unknown)>, orders?: string[]): T[];
@@ -79,6 +87,8 @@ declare function padEnd(value: unknown, length?: number, chars?: string): string
79
87
 
80
88
  declare function pick<T extends object>(object: T, paths: PropertyPath | PropertyPath[]): Partial<T>;
81
89
 
90
+ declare function pickBy<TValue>(object: Record<string, TValue> | null | undefined, predicate: (value: TValue, key: string, object: Record<string, TValue>) => boolean): Record<string, TValue>;
91
+
82
92
  declare function normalizeUrlPath(url: string): string;
83
93
 
84
94
  declare function reduce<T, TResult>(collection: T[] | null | undefined, iteratee: (accumulator: TResult, value: T, key: number, collection: T[]) => TResult, accumulator?: TResult): TResult;
@@ -88,10 +98,20 @@ declare function removeConsecutiveSlashesFromUrl(url: string): string;
88
98
 
89
99
  declare function set<T>(object: T, path: PropertyPath, value: unknown): T;
90
100
 
101
+ declare function startCase(value: unknown): string;
102
+
103
+ declare function sum(values: number[] | null | undefined): number;
104
+
105
+ declare function sumBy<T>(collection: T[] | null | undefined, iteratee: string | number | ((value: T, key: number, collection: T[]) => number | undefined)): number;
106
+
91
107
  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>;
92
108
 
93
109
  declare function toStringRecord(value: unknown): Record<string, string> | undefined;
94
110
 
95
111
  declare function uniq<T>(array: T[] | null | undefined): T[];
96
112
 
97
- export { addLeadingSlash, arrayToRecord, assign, castArray, cloneDeep, compact, difference, filter, find, flatten, flattenDeep, forEach, get, intersection, intersectionBy, isArray, isBoolean, isEmpty, isEqual, isFunction, isMatch, isNaNValue as isNaN, isNil, isNumber, isObject, isPlainObject, isPromise, isString, isUndefined, keys, map, mapValues, mapValuesAsync, noop, normalizeUrlPath, omit, orderBy, padEnd, parseBooleanString, pick, reduce, removeConsecutiveSlashesFromUrl, set, toAsyncFn, toStringRecord, uniq };
113
+ declare function uniqBy<T>(array: T[] | null | undefined, iteratee?: string | number | ((value: T, key: number, collection: T[]) => unknown)): T[];
114
+
115
+ declare function upperCase(value: unknown): string;
116
+
117
+ export { addLeadingSlash, arrayToRecord, assign, castArray, cloneDeep, compact, difference, 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, 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 };
package/index.d.ts CHANGED
@@ -27,6 +27,10 @@ type PropertyPath = string | number | Array<string | number>;
27
27
 
28
28
  declare function get<T = unknown>(object: unknown, path: PropertyPath, defaultValue?: T): T;
29
29
 
30
+ 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[]>;
31
+
32
+ declare function hasOwn<TKey extends PropertyKey>(value: unknown, key: TKey): value is Record<TKey, unknown>;
33
+
30
34
  declare function intersection<T>(...arrays: Array<T[] | null | undefined>): T[];
31
35
 
32
36
  declare function intersectionBy<T>(...args: unknown[]): T[];
@@ -63,6 +67,8 @@ declare function keys(value: unknown): string[];
63
67
 
64
68
  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[];
65
69
 
70
+ declare function mapKeys<TValue>(object: Record<string, TValue> | null | undefined, iteratee: (value: TValue, key: string, object: Record<string, TValue>) => string): Record<string, TValue>;
71
+
66
72
  declare function mapValues<TValue, TResult>(object: Record<string, TValue> | null | undefined, iteratee: (value: TValue, key: string, object: Record<string, TValue>) => TResult): Record<string, TResult>;
67
73
 
68
74
  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>>;
@@ -71,6 +77,8 @@ declare function noop(): void;
71
77
 
72
78
  declare function omit<T extends object>(object: T, paths: PropertyPath | PropertyPath[]): Partial<T>;
73
79
 
80
+ declare function omitBy<TValue>(object: Record<string, TValue> | null | undefined, predicate: (value: TValue, key: string, object: Record<string, TValue>) => boolean): Record<string, TValue>;
81
+
74
82
  declare function parseBooleanString(str: string | undefined, defaultValue?: boolean): boolean | undefined;
75
83
 
76
84
  declare function orderBy<T>(collection: T[] | null | undefined, iteratees?: Array<string | number | ((value: T) => unknown)>, orders?: string[]): T[];
@@ -79,6 +87,8 @@ declare function padEnd(value: unknown, length?: number, chars?: string): string
79
87
 
80
88
  declare function pick<T extends object>(object: T, paths: PropertyPath | PropertyPath[]): Partial<T>;
81
89
 
90
+ declare function pickBy<TValue>(object: Record<string, TValue> | null | undefined, predicate: (value: TValue, key: string, object: Record<string, TValue>) => boolean): Record<string, TValue>;
91
+
82
92
  declare function normalizeUrlPath(url: string): string;
83
93
 
84
94
  declare function reduce<T, TResult>(collection: T[] | null | undefined, iteratee: (accumulator: TResult, value: T, key: number, collection: T[]) => TResult, accumulator?: TResult): TResult;
@@ -88,10 +98,20 @@ declare function removeConsecutiveSlashesFromUrl(url: string): string;
88
98
 
89
99
  declare function set<T>(object: T, path: PropertyPath, value: unknown): T;
90
100
 
101
+ declare function startCase(value: unknown): string;
102
+
103
+ declare function sum(values: number[] | null | undefined): number;
104
+
105
+ declare function sumBy<T>(collection: T[] | null | undefined, iteratee: string | number | ((value: T, key: number, collection: T[]) => number | undefined)): number;
106
+
91
107
  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>;
92
108
 
93
109
  declare function toStringRecord(value: unknown): Record<string, string> | undefined;
94
110
 
95
111
  declare function uniq<T>(array: T[] | null | undefined): T[];
96
112
 
97
- export { addLeadingSlash, arrayToRecord, assign, castArray, cloneDeep, compact, difference, filter, find, flatten, flattenDeep, forEach, get, intersection, intersectionBy, isArray, isBoolean, isEmpty, isEqual, isFunction, isMatch, isNaNValue as isNaN, isNil, isNumber, isObject, isPlainObject, isPromise, isString, isUndefined, keys, map, mapValues, mapValuesAsync, noop, normalizeUrlPath, omit, orderBy, padEnd, parseBooleanString, pick, reduce, removeConsecutiveSlashesFromUrl, set, toAsyncFn, toStringRecord, uniq };
113
+ declare function uniqBy<T>(array: T[] | null | undefined, iteratee?: string | number | ((value: T, key: number, collection: T[]) => unknown)): T[];
114
+
115
+ declare function upperCase(value: unknown): string;
116
+
117
+ export { addLeadingSlash, arrayToRecord, assign, castArray, cloneDeep, compact, difference, 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, 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 };
package/index.js CHANGED
@@ -32,6 +32,8 @@ __export(index_exports, {
32
32
  flattenDeep: () => flattenDeep,
33
33
  forEach: () => forEach,
34
34
  get: () => get,
35
+ groupBy: () => groupBy,
36
+ hasOwn: () => hasOwn,
35
37
  intersection: () => intersection,
36
38
  intersectionBy: () => intersectionBy,
37
39
  isArray: () => isArray,
@@ -50,21 +52,29 @@ __export(index_exports, {
50
52
  isUndefined: () => isUndefined,
51
53
  keys: () => keys,
52
54
  map: () => map,
55
+ mapKeys: () => mapKeys,
53
56
  mapValues: () => mapValues,
54
57
  mapValuesAsync: () => mapValuesAsync,
55
58
  noop: () => noop,
56
59
  normalizeUrlPath: () => normalizeUrlPath,
57
60
  omit: () => omit,
61
+ omitBy: () => omitBy,
58
62
  orderBy: () => orderBy,
59
63
  padEnd: () => padEnd,
60
64
  parseBooleanString: () => parseBooleanString,
61
65
  pick: () => pick,
66
+ pickBy: () => pickBy,
62
67
  reduce: () => reduce,
63
68
  removeConsecutiveSlashesFromUrl: () => removeConsecutiveSlashesFromUrl,
64
69
  set: () => set,
70
+ startCase: () => startCase,
71
+ sum: () => sum,
72
+ sumBy: () => sumBy,
65
73
  toAsyncFn: () => toAsyncFn,
66
74
  toStringRecord: () => toStringRecord,
67
- uniq: () => uniq
75
+ uniq: () => uniq,
76
+ uniqBy: () => uniqBy,
77
+ upperCase: () => upperCase
68
78
  });
69
79
  module.exports = __toCommonJS(index_exports);
70
80
 
@@ -94,6 +104,7 @@ function castArray(value) {
94
104
 
95
105
  // src/_internal.ts
96
106
  var pathPattern = /[^.[\]]+|\[(?:([^"'[\]]+)|["']([^"']+)["'])\]/g;
107
+ var unsafePathParts = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
97
108
  function isObject(value) {
98
109
  return value !== null && (typeof value === "object" || typeof value === "function");
99
110
  }
@@ -126,6 +137,15 @@ function toPath(path) {
126
137
  });
127
138
  return result.length > 0 ? result : [input];
128
139
  }
140
+ function hasUnsafePathPart(parts) {
141
+ for (let index = 0; index < parts.length; index++) {
142
+ const part = parts[index];
143
+ if (typeof part === "string" && unsafePathParts.has(part)) {
144
+ return true;
145
+ }
146
+ }
147
+ return false;
148
+ }
129
149
  function hasPath(value, path) {
130
150
  const parts = toPath(path);
131
151
  if (parts.length === 0) {
@@ -160,7 +180,7 @@ function setPath(target, path, value) {
160
180
  return target;
161
181
  }
162
182
  const parts = toPath(path);
163
- if (parts.length === 0) {
183
+ if (parts.length === 0 || hasUnsafePathPart(parts)) {
164
184
  return target;
165
185
  }
166
186
  let current = target;
@@ -181,7 +201,7 @@ function deletePath(target, path) {
181
201
  return;
182
202
  }
183
203
  const parts = toPath(path);
184
- if (parts.length === 0) {
204
+ if (parts.length === 0 || hasUnsafePathPart(parts)) {
185
205
  return;
186
206
  }
187
207
  let current = target;
@@ -227,6 +247,14 @@ function getIteratee(iteratee) {
227
247
  }
228
248
  return (value) => getPath(value, iteratee);
229
249
  }
250
+ var wordPattern = /[A-Z]{2,}(?=[A-Z][a-z]|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]+|[0-9]+/g;
251
+ function words(value) {
252
+ const input = String(value ?? "");
253
+ if (input.length === 0) {
254
+ return [];
255
+ }
256
+ return input.replace(/[']/g, "").match(wordPattern) ?? [];
257
+ }
230
258
  function compareAscending(left, right) {
231
259
  if (sameValueZero(left, right)) {
232
260
  return 0;
@@ -487,6 +515,39 @@ function get(object, path, defaultValue) {
487
515
  return getPath(object, path, defaultValue);
488
516
  }
489
517
 
518
+ // src/groupBy.ts
519
+ function groupBy(collection, iteratee) {
520
+ if (!collection) {
521
+ return {};
522
+ }
523
+ const callback = getIteratee(iteratee);
524
+ const result = {};
525
+ if (Array.isArray(collection)) {
526
+ for (let index = 0; index < collection.length; index++) {
527
+ const value = collection[index];
528
+ const key = String(callback(value, index, collection));
529
+ (result[key] ??= []).push(value);
530
+ }
531
+ return result;
532
+ }
533
+ const keys2 = Object.keys(collection);
534
+ for (let index = 0; index < keys2.length; index++) {
535
+ const key = keys2[index];
536
+ const value = collection[key];
537
+ const group = String(callback(value, key, collection));
538
+ (result[group] ??= []).push(value);
539
+ }
540
+ return result;
541
+ }
542
+
543
+ // src/hasOwn.ts
544
+ function hasOwn(value, key) {
545
+ if (value === null || value === void 0) {
546
+ return false;
547
+ }
548
+ return Object.hasOwn(Object(value), key);
549
+ }
550
+
490
551
  // src/intersection.ts
491
552
  function intersection(...arrays) {
492
553
  const [first = [], ...rest] = arrays;
@@ -640,6 +701,20 @@ function map(collection, iteratee) {
640
701
  return result;
641
702
  }
642
703
 
704
+ // src/mapKeys.ts
705
+ function mapKeys(object, iteratee) {
706
+ if (!object) {
707
+ return {};
708
+ }
709
+ const result = {};
710
+ const keys2 = Object.keys(object);
711
+ for (let index = 0; index < keys2.length; index++) {
712
+ const key = keys2[index];
713
+ result[String(iteratee(object[key], key, object))] = object[key];
714
+ }
715
+ return result;
716
+ }
717
+
643
718
  // src/mapValues.ts
644
719
  function mapValues(object, iteratee) {
645
720
  if (!object) {
@@ -680,6 +755,23 @@ function omit(object, paths) {
680
755
  return result;
681
756
  }
682
757
 
758
+ // src/omitBy.ts
759
+ function omitBy(object, predicate) {
760
+ if (!object) {
761
+ return {};
762
+ }
763
+ const result = {};
764
+ const keys2 = Object.keys(object);
765
+ for (let index = 0; index < keys2.length; index++) {
766
+ const key = keys2[index];
767
+ const value = object[key];
768
+ if (!predicate(value, key, object)) {
769
+ result[key] = value;
770
+ }
771
+ }
772
+ return result;
773
+ }
774
+
683
775
  // src/parseBooleanString.ts
684
776
  function parseBooleanString(str, defaultValue) {
685
777
  return str ? str === "true" : defaultValue;
@@ -725,6 +817,23 @@ function pick(object, paths) {
725
817
  return result;
726
818
  }
727
819
 
820
+ // src/pickBy.ts
821
+ function pickBy(object, predicate) {
822
+ if (!object) {
823
+ return {};
824
+ }
825
+ const result = {};
826
+ const keys2 = Object.keys(object);
827
+ for (let index = 0; index < keys2.length; index++) {
828
+ const key = keys2[index];
829
+ const value = object[key];
830
+ if (predicate(value, key, object)) {
831
+ result[key] = value;
832
+ }
833
+ }
834
+ return result;
835
+ }
836
+
728
837
  // src/removeConsecutiveSlashesFromUrl.ts
729
838
  function removeConsecutiveSlashesFromUrl(url) {
730
839
  return url.replace(/\/{2,}/g, "/");
@@ -771,6 +880,42 @@ function set(object, path, value) {
771
880
  return setPath(object, path, value);
772
881
  }
773
882
 
883
+ // src/startCase.ts
884
+ function startCase(value) {
885
+ return words(value).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
886
+ }
887
+
888
+ // src/sum.ts
889
+ function sum(values) {
890
+ if (!Array.isArray(values)) {
891
+ return 0;
892
+ }
893
+ let result = 0;
894
+ for (let index = 0; index < values.length; index++) {
895
+ const value = values[index];
896
+ if (value !== void 0) {
897
+ result += value;
898
+ }
899
+ }
900
+ return result;
901
+ }
902
+
903
+ // src/sumBy.ts
904
+ function sumBy(collection, iteratee) {
905
+ if (!Array.isArray(collection)) {
906
+ return 0;
907
+ }
908
+ const callback = getIteratee(iteratee);
909
+ let result = 0;
910
+ for (let index = 0; index < collection.length; index++) {
911
+ const current = callback(collection[index], index, collection);
912
+ if (current !== void 0) {
913
+ result += current;
914
+ }
915
+ }
916
+ return result;
917
+ }
918
+
774
919
  // src/toAsyncFn.ts
775
920
  function toAsyncFn(fn, defaultValue) {
776
921
  if (!fn) return () => Promise.resolve(defaultValue);
@@ -801,6 +946,31 @@ function uniq(array) {
801
946
  }
802
947
  return baseUniq(array);
803
948
  }
949
+
950
+ // src/uniqBy.ts
951
+ function uniqBy(array, iteratee) {
952
+ if (!Array.isArray(array)) {
953
+ return [];
954
+ }
955
+ const callback = getIteratee(iteratee);
956
+ const result = [];
957
+ const seen = [];
958
+ for (let index = 0; index < array.length; index++) {
959
+ const value = array[index];
960
+ const computed = callback(value, index, array);
961
+ if (seen.some((entry) => sameValueZero(entry, computed))) {
962
+ continue;
963
+ }
964
+ seen.push(computed);
965
+ result.push(value);
966
+ }
967
+ return result;
968
+ }
969
+
970
+ // src/upperCase.ts
971
+ function upperCase(value) {
972
+ return words(value).join(" ").toUpperCase();
973
+ }
804
974
  // Annotate the CommonJS export names for ESM import in node:
805
975
  0 && (module.exports = {
806
976
  addLeadingSlash,
@@ -816,6 +986,8 @@ function uniq(array) {
816
986
  flattenDeep,
817
987
  forEach,
818
988
  get,
989
+ groupBy,
990
+ hasOwn,
819
991
  intersection,
820
992
  intersectionBy,
821
993
  isArray,
@@ -834,19 +1006,27 @@ function uniq(array) {
834
1006
  isUndefined,
835
1007
  keys,
836
1008
  map,
1009
+ mapKeys,
837
1010
  mapValues,
838
1011
  mapValuesAsync,
839
1012
  noop,
840
1013
  normalizeUrlPath,
841
1014
  omit,
1015
+ omitBy,
842
1016
  orderBy,
843
1017
  padEnd,
844
1018
  parseBooleanString,
845
1019
  pick,
1020
+ pickBy,
846
1021
  reduce,
847
1022
  removeConsecutiveSlashesFromUrl,
848
1023
  set,
1024
+ startCase,
1025
+ sum,
1026
+ sumBy,
849
1027
  toAsyncFn,
850
1028
  toStringRecord,
851
- uniq
1029
+ uniq,
1030
+ uniqBy,
1031
+ upperCase
852
1032
  });
package/index.mjs CHANGED
@@ -24,6 +24,7 @@ function castArray(value) {
24
24
 
25
25
  // src/_internal.ts
26
26
  var pathPattern = /[^.[\]]+|\[(?:([^"'[\]]+)|["']([^"']+)["'])\]/g;
27
+ var unsafePathParts = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
27
28
  function isObject(value) {
28
29
  return value !== null && (typeof value === "object" || typeof value === "function");
29
30
  }
@@ -56,6 +57,15 @@ function toPath(path) {
56
57
  });
57
58
  return result.length > 0 ? result : [input];
58
59
  }
60
+ function hasUnsafePathPart(parts) {
61
+ for (let index = 0; index < parts.length; index++) {
62
+ const part = parts[index];
63
+ if (typeof part === "string" && unsafePathParts.has(part)) {
64
+ return true;
65
+ }
66
+ }
67
+ return false;
68
+ }
59
69
  function hasPath(value, path) {
60
70
  const parts = toPath(path);
61
71
  if (parts.length === 0) {
@@ -90,7 +100,7 @@ function setPath(target, path, value) {
90
100
  return target;
91
101
  }
92
102
  const parts = toPath(path);
93
- if (parts.length === 0) {
103
+ if (parts.length === 0 || hasUnsafePathPart(parts)) {
94
104
  return target;
95
105
  }
96
106
  let current = target;
@@ -111,7 +121,7 @@ function deletePath(target, path) {
111
121
  return;
112
122
  }
113
123
  const parts = toPath(path);
114
- if (parts.length === 0) {
124
+ if (parts.length === 0 || hasUnsafePathPart(parts)) {
115
125
  return;
116
126
  }
117
127
  let current = target;
@@ -157,6 +167,14 @@ function getIteratee(iteratee) {
157
167
  }
158
168
  return (value) => getPath(value, iteratee);
159
169
  }
170
+ var wordPattern = /[A-Z]{2,}(?=[A-Z][a-z]|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]+|[0-9]+/g;
171
+ function words(value) {
172
+ const input = String(value ?? "");
173
+ if (input.length === 0) {
174
+ return [];
175
+ }
176
+ return input.replace(/[']/g, "").match(wordPattern) ?? [];
177
+ }
160
178
  function compareAscending(left, right) {
161
179
  if (sameValueZero(left, right)) {
162
180
  return 0;
@@ -417,6 +435,39 @@ function get(object, path, defaultValue) {
417
435
  return getPath(object, path, defaultValue);
418
436
  }
419
437
 
438
+ // src/groupBy.ts
439
+ function groupBy(collection, iteratee) {
440
+ if (!collection) {
441
+ return {};
442
+ }
443
+ const callback = getIteratee(iteratee);
444
+ const result = {};
445
+ if (Array.isArray(collection)) {
446
+ for (let index = 0; index < collection.length; index++) {
447
+ const value = collection[index];
448
+ const key = String(callback(value, index, collection));
449
+ (result[key] ??= []).push(value);
450
+ }
451
+ return result;
452
+ }
453
+ const keys2 = Object.keys(collection);
454
+ for (let index = 0; index < keys2.length; index++) {
455
+ const key = keys2[index];
456
+ const value = collection[key];
457
+ const group = String(callback(value, key, collection));
458
+ (result[group] ??= []).push(value);
459
+ }
460
+ return result;
461
+ }
462
+
463
+ // src/hasOwn.ts
464
+ function hasOwn(value, key) {
465
+ if (value === null || value === void 0) {
466
+ return false;
467
+ }
468
+ return Object.hasOwn(Object(value), key);
469
+ }
470
+
420
471
  // src/intersection.ts
421
472
  function intersection(...arrays) {
422
473
  const [first = [], ...rest] = arrays;
@@ -570,6 +621,20 @@ function map(collection, iteratee) {
570
621
  return result;
571
622
  }
572
623
 
624
+ // src/mapKeys.ts
625
+ function mapKeys(object, iteratee) {
626
+ if (!object) {
627
+ return {};
628
+ }
629
+ const result = {};
630
+ const keys2 = Object.keys(object);
631
+ for (let index = 0; index < keys2.length; index++) {
632
+ const key = keys2[index];
633
+ result[String(iteratee(object[key], key, object))] = object[key];
634
+ }
635
+ return result;
636
+ }
637
+
573
638
  // src/mapValues.ts
574
639
  function mapValues(object, iteratee) {
575
640
  if (!object) {
@@ -610,6 +675,23 @@ function omit(object, paths) {
610
675
  return result;
611
676
  }
612
677
 
678
+ // src/omitBy.ts
679
+ function omitBy(object, predicate) {
680
+ if (!object) {
681
+ return {};
682
+ }
683
+ const result = {};
684
+ const keys2 = Object.keys(object);
685
+ for (let index = 0; index < keys2.length; index++) {
686
+ const key = keys2[index];
687
+ const value = object[key];
688
+ if (!predicate(value, key, object)) {
689
+ result[key] = value;
690
+ }
691
+ }
692
+ return result;
693
+ }
694
+
613
695
  // src/parseBooleanString.ts
614
696
  function parseBooleanString(str, defaultValue) {
615
697
  return str ? str === "true" : defaultValue;
@@ -655,6 +737,23 @@ function pick(object, paths) {
655
737
  return result;
656
738
  }
657
739
 
740
+ // src/pickBy.ts
741
+ function pickBy(object, predicate) {
742
+ if (!object) {
743
+ return {};
744
+ }
745
+ const result = {};
746
+ const keys2 = Object.keys(object);
747
+ for (let index = 0; index < keys2.length; index++) {
748
+ const key = keys2[index];
749
+ const value = object[key];
750
+ if (predicate(value, key, object)) {
751
+ result[key] = value;
752
+ }
753
+ }
754
+ return result;
755
+ }
756
+
658
757
  // src/removeConsecutiveSlashesFromUrl.ts
659
758
  function removeConsecutiveSlashesFromUrl(url) {
660
759
  return url.replace(/\/{2,}/g, "/");
@@ -701,6 +800,42 @@ function set(object, path, value) {
701
800
  return setPath(object, path, value);
702
801
  }
703
802
 
803
+ // src/startCase.ts
804
+ function startCase(value) {
805
+ return words(value).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
806
+ }
807
+
808
+ // src/sum.ts
809
+ function sum(values) {
810
+ if (!Array.isArray(values)) {
811
+ return 0;
812
+ }
813
+ let result = 0;
814
+ for (let index = 0; index < values.length; index++) {
815
+ const value = values[index];
816
+ if (value !== void 0) {
817
+ result += value;
818
+ }
819
+ }
820
+ return result;
821
+ }
822
+
823
+ // src/sumBy.ts
824
+ function sumBy(collection, iteratee) {
825
+ if (!Array.isArray(collection)) {
826
+ return 0;
827
+ }
828
+ const callback = getIteratee(iteratee);
829
+ let result = 0;
830
+ for (let index = 0; index < collection.length; index++) {
831
+ const current = callback(collection[index], index, collection);
832
+ if (current !== void 0) {
833
+ result += current;
834
+ }
835
+ }
836
+ return result;
837
+ }
838
+
704
839
  // src/toAsyncFn.ts
705
840
  function toAsyncFn(fn, defaultValue) {
706
841
  if (!fn) return () => Promise.resolve(defaultValue);
@@ -731,6 +866,31 @@ function uniq(array) {
731
866
  }
732
867
  return baseUniq(array);
733
868
  }
869
+
870
+ // src/uniqBy.ts
871
+ function uniqBy(array, iteratee) {
872
+ if (!Array.isArray(array)) {
873
+ return [];
874
+ }
875
+ const callback = getIteratee(iteratee);
876
+ const result = [];
877
+ const seen = [];
878
+ for (let index = 0; index < array.length; index++) {
879
+ const value = array[index];
880
+ const computed = callback(value, index, array);
881
+ if (seen.some((entry) => sameValueZero(entry, computed))) {
882
+ continue;
883
+ }
884
+ seen.push(computed);
885
+ result.push(value);
886
+ }
887
+ return result;
888
+ }
889
+
890
+ // src/upperCase.ts
891
+ function upperCase(value) {
892
+ return words(value).join(" ").toUpperCase();
893
+ }
734
894
  export {
735
895
  addLeadingSlash,
736
896
  arrayToRecord,
@@ -745,6 +905,8 @@ export {
745
905
  flattenDeep,
746
906
  forEach,
747
907
  get,
908
+ groupBy,
909
+ hasOwn,
748
910
  intersection,
749
911
  intersectionBy,
750
912
  isArray,
@@ -763,19 +925,27 @@ export {
763
925
  isUndefined,
764
926
  keys,
765
927
  map,
928
+ mapKeys,
766
929
  mapValues,
767
930
  mapValuesAsync,
768
931
  noop,
769
932
  normalizeUrlPath,
770
933
  omit,
934
+ omitBy,
771
935
  orderBy,
772
936
  padEnd,
773
937
  parseBooleanString,
774
938
  pick,
939
+ pickBy,
775
940
  reduce,
776
941
  removeConsecutiveSlashesFromUrl,
777
942
  set,
943
+ startCase,
944
+ sum,
945
+ sumBy,
778
946
  toAsyncFn,
779
947
  toStringRecord,
780
- uniq
948
+ uniq,
949
+ uniqBy,
950
+ upperCase
781
951
  };
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@web-ts-toolkit/utils",
3
3
  "description": "Shared collection, object, async, and URL helpers used across workspace packages",
4
4
  "homepage": "https://web-ts-toolkit.pages.dev/docs/packages/utils",
5
- "version": "0.28.0",
5
+ "version": "0.30.0",
6
6
  "sideEffects": false,
7
7
  "keywords": [
8
8
  "utilities",