@helpers4/object 2.0.0-alpha.8 → 2.0.0-beta.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
@@ -12,19 +12,24 @@ A set of helpers for working with URLs.
12
12
  [https://helpers4.dev/typescript/categories/object/](https://helpers4.dev/typescript/categories/object/)
13
13
 
14
14
  <!-- AUTOMATIC-METHODS -->
15
+ - compact
15
16
  - deepClone
16
17
  - deepCompare
17
18
  - deepMerge
18
19
  - get
19
- - quickCompare
20
+ - omit
21
+ - pick
20
22
  - removeUndefinedNull
23
+ - safeJsonParse
21
24
  - set
25
+ - shallowEquals
22
26
  <!-- /AUTOMATIC-METHODS -->
23
27
 
24
28
  ## See
25
29
 
26
30
  <!-- AUTOMATIC-SIBLINGS -->
27
31
  - [array](../array)
32
+ - [commit](../commit)
28
33
  - [date](../date)
29
34
  - [function](../function)
30
35
  - [math](../math)
@@ -43,15 +48,16 @@ A set of helpers for working with URLs.
43
48
  | Name | Package | Source Code | Description |
44
49
  |------|---------|-------------|-------------|
45
50
  | array | [@helpers4/array](https://www.npmjs.com/package/@helpers4/array) | [Source](https://github.com/helpers4/helpers4/tree/main/helpers/array) | Array manipulation utilities |
46
- | date | [@helpers4/date](https://www.npmjs.com/package/@helpers4/date) | [Source](https://github.com/helpers4/helpers4/tree/main/helpers/date) | date utilities |
51
+ | commit | [@helpers4/commit](https://www.npmjs.com/package/@helpers4/commit) | [Source](https://github.com/helpers4/helpers4/tree/main/helpers/commit) | Conventional Commits parsing and analysis |
52
+ | date | [@helpers4/date](https://www.npmjs.com/package/@helpers4/date) | [Source](https://github.com/helpers4/helpers4/tree/main/helpers/date) | Date and time utility functions |
47
53
  | function | [@helpers4/function](https://www.npmjs.com/package/@helpers4/function) | [Source](https://github.com/helpers4/helpers4/tree/main/helpers/function) | Function utilities and type guards |
48
- | math | [@helpers4/math](https://www.npmjs.com/package/@helpers4/math) | [Source](https://github.com/helpers4/helpers4/tree/main/helpers/math) | math utilities |
49
- | number | [@helpers4/number](https://www.npmjs.com/package/@helpers4/number) | [Source](https://github.com/helpers4/helpers4/tree/main/helpers/number) | number utilities |
54
+ | math | [@helpers4/math](https://www.npmjs.com/package/@helpers4/math) | [Source](https://github.com/helpers4/helpers4/tree/main/helpers/math) | Mathematical utility functions and calculations |
55
+ | number | [@helpers4/number](https://www.npmjs.com/package/@helpers4/number) | [Source](https://github.com/helpers4/helpers4/tree/main/helpers/number) | Number utility functions and validation |
50
56
  | object | [@helpers4/object](https://www.npmjs.com/package/@helpers4/object) | [Source](https://github.com/helpers4/helpers4/tree/main/helpers/object) | Object manipulation utilities |
51
57
  | observable | [@helpers4/observable](https://www.npmjs.com/package/@helpers4/observable) | [Source](https://github.com/helpers4/helpers4/tree/main/helpers/observable) | Observable utilities and combinators |
52
58
  | promise | [@helpers4/promise](https://www.npmjs.com/package/@helpers4/promise) | [Source](https://github.com/helpers4/helpers4/tree/main/helpers/promise) | Promise utilities and error handling |
53
59
  | string | [@helpers4/string](https://www.npmjs.com/package/@helpers4/string) | [Source](https://github.com/helpers4/helpers4/tree/main/helpers/string) | String manipulation and formatting utilities |
54
- | type | [@helpers4/type](https://www.npmjs.com/package/@helpers4/type) | [Source](https://github.com/helpers4/helpers4/tree/main/helpers/type) | type utilities |
60
+ | type | [@helpers4/type](https://www.npmjs.com/package/@helpers4/type) | [Source](https://github.com/helpers4/helpers4/tree/main/helpers/type) | Type checking and validation utilities |
55
61
  | url | [@helpers4/url](https://www.npmjs.com/package/@helpers4/url) | [Source](https://github.com/helpers4/helpers4/tree/main/helpers/url) | URL parsing and manipulation utilities |
56
62
  | version | [@helpers4/version](https://www.npmjs.com/package/@helpers4/version) | [Source](https://github.com/helpers4/helpers4/tree/main/helpers/version) | Version string manipulation utilities |
57
63
  <!-- /AUTOMATIC-CATEGORIES-TABLE -->
package/lib/index.d.ts CHANGED
@@ -1,3 +1,21 @@
1
+ /**
2
+ * This file is part of helpers4.
3
+ * Copyright (C) 2025 baxyz
4
+ * SPDX-License-Identifier: LGPL-3.0-or-later
5
+ */
6
+ /**
7
+ * Removes all entries with falsy values (`false`, `null`, `undefined`, `0`, `""`, `NaN`) from an object.
8
+ * @param obj - The source object
9
+ * @returns A new object containing only entries with truthy values
10
+ * @example
11
+ * compact({ a: 1, b: null, c: '', d: 0, e: 'hello' })
12
+ * // => { a: 1, e: 'hello' }
13
+ * @since 2.0.0
14
+ */
15
+ declare function compact<T extends Record<string, unknown>>(obj: T): Partial<T>;
16
+ declare function compact(obj: undefined): undefined;
17
+ declare function compact(obj: null): null;
18
+
1
19
  /**
2
20
  * This file is part of helpers4.
3
21
  * Copyright (C) 2025 baxyz
@@ -45,7 +63,9 @@ declare function deepCompare(objA: object | undefined | null, objB: object | und
45
63
  * @returns The merged object
46
64
  * @since 1.9.0
47
65
  */
48
- declare function deepMerge<T extends Record<string, any>>(target: T, ...sources: Record<string, any>[]): T;
66
+ declare function deepMerge<T extends Record<string, unknown>>(target: T, ...sources: Record<string, unknown>[]): T;
67
+ declare function deepMerge(target: undefined, ...sources: Record<string, unknown>[]): undefined;
68
+ declare function deepMerge(target: null, ...sources: Record<string, unknown>[]): null;
49
69
 
50
70
  /**
51
71
  * This file is part of helpers4.
@@ -60,7 +80,7 @@ declare function deepMerge<T extends Record<string, any>>(target: T, ...sources:
60
80
  * @returns The value at the path or default value
61
81
  * @since 1.9.0
62
82
  */
63
- declare function get<T = any>(obj: any, path: string, defaultValue?: T): T | undefined;
83
+ declare function get<T = unknown>(obj: unknown, path: string, defaultValue?: T): T | undefined;
64
84
 
65
85
  /**
66
86
  * This file is part of helpers4.
@@ -68,15 +88,37 @@ declare function get<T = any>(obj: any, path: string, defaultValue?: T): T | und
68
88
  * SPDX-License-Identifier: LGPL-3.0-or-later
69
89
  */
70
90
  /**
71
- * Quick comparison of two objects using JSON.stringify.
72
- * This is a fast but simple comparison that may not work for all edge cases.
73
- *
74
- * @param objA - First object to compare
75
- * @param objB - Second object to compare
76
- * @returns `true` if objects are identical according to JSON.stringify, `false` otherwise
91
+ * Creates a new object without the specified keys.
92
+ * @param obj - The source object
93
+ * @param keys - The keys to omit
94
+ * @returns A new object without the omitted keys
95
+ * @example
96
+ * omit({ a: 1, b: 2, c: 3 }, ['b'])
97
+ * // => { a: 1, c: 3 }
98
+ * @since 2.0.0
99
+ */
100
+ declare function omit<T extends Record<string, unknown>, K extends keyof T>(obj: T, keys: readonly K[]): Omit<T, K>;
101
+ declare function omit(obj: undefined, keys: readonly string[]): undefined;
102
+ declare function omit(obj: null, keys: readonly string[]): null;
103
+
104
+ /**
105
+ * This file is part of helpers4.
106
+ * Copyright (C) 2025 baxyz
107
+ * SPDX-License-Identifier: LGPL-3.0-or-later
108
+ */
109
+ /**
110
+ * Creates a new object with only the specified keys.
111
+ * @param obj - The source object
112
+ * @param keys - The keys to pick
113
+ * @returns A new object with only the picked keys
114
+ * @example
115
+ * pick({ a: 1, b: 2, c: 3 }, ['a', 'c'])
116
+ * // => { a: 1, c: 3 }
77
117
  * @since 2.0.0
78
118
  */
79
- declare function quickCompare(objA: unknown, objB: unknown): boolean;
119
+ declare function pick<T extends Record<string, unknown>, K extends keyof T>(obj: T, keys: readonly K[]): Pick<T, K>;
120
+ declare function pick(obj: undefined, keys: readonly string[]): undefined;
121
+ declare function pick(obj: null, keys: readonly string[]): null;
80
122
 
81
123
  /**
82
124
  * This file is part of helpers4.
@@ -108,6 +150,29 @@ declare function removeUndefinedNull(obj: null): null;
108
150
  */
109
151
  declare function removeUndefinedNull(obj: undefined): undefined;
110
152
 
153
+ /**
154
+ * This file is part of helpers4.
155
+ * Copyright (C) 2025 baxyz
156
+ * SPDX-License-Identifier: LGPL-3.0-or-later
157
+ */
158
+ /**
159
+ * Parses a JSON string, returning `null` (or a fallback) on any parse failure.
160
+ *
161
+ * Unlike `JSON.parse`, this never throws. Invalid JSON strings and other
162
+ * parsing edge-cases resolve to `null` or the provided `fallback`.
163
+ *
164
+ * @param input - The JSON string to parse.
165
+ * @param fallback - Value returned on failure. Defaults to `null` when omitted.
166
+ * @returns The parsed value typed as `T`, or `fallback` on failure.
167
+ * @example
168
+ * safeJsonParse<{ a: number }>('{"a":1}') // => { a: 1 }
169
+ * safeJsonParse('invalid') // => null
170
+ * safeJsonParse('invalid', []) // => []
171
+ * @since next
172
+ */
173
+ declare function safeJsonParse<T>(input: string): T | null;
174
+ declare function safeJsonParse<T>(input: string, fallback: T): T;
175
+
111
176
  /**
112
177
  * This file is part of helpers4.
113
178
  * Copyright (C) 2025 baxyz
@@ -121,7 +186,23 @@ declare function removeUndefinedNull(obj: undefined): undefined;
121
186
  * @returns The modified object
122
187
  * @since 1.9.0
123
188
  */
124
- declare function set(obj: Record<string, any>, path: string, value: any): Record<string, any>;
189
+ declare function set(obj: Record<string, unknown>, path: string, value: unknown): Record<string, unknown>;
190
+
191
+ /**
192
+ * This file is part of helpers4.
193
+ * Copyright (C) 2025 baxyz
194
+ * SPDX-License-Identifier: LGPL-3.0-or-later
195
+ */
196
+ /**
197
+ * Quick comparison of two objects using JSON.stringify.
198
+ * This is a fast but simple comparison that may not work for all edge cases.
199
+ *
200
+ * @param objA - First object to compare
201
+ * @param objB - Second object to compare
202
+ * @returns `true` if objects are identical according to JSON.stringify, `false` otherwise
203
+ * @since 2.0.0
204
+ */
205
+ declare function shallowEquals(objA: unknown, objB: unknown): boolean;
125
206
 
126
- export { deepClone, deepCompare, deepMerge, get, quickCompare, removeUndefinedNull, set };
207
+ export { compact, deepClone, deepCompare, deepMerge, get, omit, pick, removeUndefinedNull, safeJsonParse, set, shallowEquals };
127
208
  export type { DeepCompareResult };
package/lib/index.js CHANGED
@@ -1,10 +1,22 @@
1
- import { isNullish } from "radashi";
1
+ //#region helpers/object/compact.ts
2
+ function compact(obj) {
3
+ if (obj === void 0 || obj === null) return obj;
4
+ const result = {};
5
+ for (const key of Object.keys(obj)) if (obj[key]) result[key] = obj[key];
6
+ return result;
7
+ }
8
+ //#endregion
2
9
  //#region helpers/object/deepClone.ts
3
10
  /**
4
11
  * This file is part of helpers4.
5
12
  * Copyright (C) 2025 baxyz
6
13
  * SPDX-License-Identifier: LGPL-3.0-or-later
7
14
  */
15
+ var UNSAFE_KEYS$2 = new Set([
16
+ "__proto__",
17
+ "constructor",
18
+ "prototype"
19
+ ]);
8
20
  /**
9
21
  * Creates a deep copy of an object or array
10
22
  * @param obj - The object to clone
@@ -16,11 +28,11 @@ function deepClone(obj) {
16
28
  if (obj instanceof Date) return new Date(obj.getTime());
17
29
  if (Array.isArray(obj)) return obj.map((item) => deepClone(item));
18
30
  const cloned = {};
19
- for (const key in obj) if (obj.hasOwnProperty(key)) cloned[key] = deepClone(obj[key]);
31
+ for (const key in obj) if (Object.prototype.hasOwnProperty.call(obj, key) && !UNSAFE_KEYS$2.has(key)) cloned[key] = deepClone(obj[key]);
20
32
  return cloned;
21
33
  }
22
34
  //#endregion
23
- //#region helpers/array/quickCompare.ts
35
+ //#region helpers/array/shallowEquals.ts
24
36
  /**
25
37
  * This file is part of helpers4.
26
38
  * Copyright (C) 2025 baxyz
@@ -29,13 +41,13 @@ function deepClone(obj) {
29
41
  /**
30
42
  * Quick comparison of two arrays using JSON.stringify.
31
43
  * This is a fast but simple comparison that may not work for all edge cases.
32
- *
44
+ *
33
45
  * @param arrA - First array to compare
34
46
  * @param arrB - Second array to compare
35
47
  * @returns `true` if arrays are identical according to JSON.stringify, `false` otherwise
36
48
  * @since 2.0.0
37
49
  */
38
- function quickCompare$1(arrA, arrB) {
50
+ function shallowEquals$1(arrA, arrB) {
39
51
  try {
40
52
  return JSON.stringify(arrA) === JSON.stringify(arrB);
41
53
  } catch {
@@ -43,26 +55,116 @@ function quickCompare$1(arrA, arrB) {
43
55
  }
44
56
  }
45
57
  //#endregion
58
+ //#region helpers/date/timestamp.ts
59
+ /**
60
+ * Checks if a timestamp is likely in seconds (Java/Unix style) vs milliseconds (JavaScript style)
61
+ * @param timestamp - The timestamp to check
62
+ * @returns True if timestamp appears to be in seconds
63
+ * @since 1.9.0
64
+ */
65
+ function isTimestampInSeconds(timestamp) {
66
+ return Math.abs(timestamp) < 1e10;
67
+ }
68
+ /**
69
+ * Converts a timestamp to JavaScript milliseconds format
70
+ * @param timestamp - The timestamp (in seconds or milliseconds)
71
+ * @returns Timestamp in milliseconds
72
+ * @since 1.9.0
73
+ */
74
+ function normalizeTimestamp(timestamp) {
75
+ return isTimestampInSeconds(timestamp) ? timestamp * 1e3 : timestamp;
76
+ }
77
+ //#endregion
78
+ //#region helpers/date/ensureDate.ts
79
+ /**
80
+ * This file is part of helpers4.
81
+ * Copyright (C) 2025 baxyz
82
+ * SPDX-License-Identifier: LGPL-3.0-or-later
83
+ */
84
+ /**
85
+ * Type guard that checks whether a value satisfies the {@link EpochMilliseconds}
86
+ * structural interface (e.g. `Temporal.Instant`, `Temporal.ZonedDateTime`).
87
+ */
88
+ function hasEpochMilliseconds(value) {
89
+ return typeof value === "object" && value !== null && "epochMilliseconds" in value && typeof value.epochMilliseconds === "number";
90
+ }
91
+ /**
92
+ * Safely converts a date-like value to a valid `Date` object, or returns `null`.
93
+ *
94
+ * Accepts `Date`, timestamps (seconds or milliseconds, auto-detected), date strings,
95
+ * and objects with an `epochMilliseconds` property (e.g. `Temporal.Instant`,
96
+ * `Temporal.ZonedDateTime`).
97
+ * Returns `null` for `null`, `undefined`, empty strings, `0`, and any value that
98
+ * produces an invalid `Date`.
99
+ *
100
+ * This is the date equivalent of {@link ensureArray} — it normalizes flexible
101
+ * input into a guaranteed type (or a safe fallback).
102
+ *
103
+ * @param input - A date-like value to convert
104
+ * @returns A valid `Date` object, or `null` if the input is invalid
105
+ * @example
106
+ * ensureDate('2025-01-19T12:00:00Z') // => Date
107
+ * ensureDate(1737290400) // => Date (from Unix seconds)
108
+ * ensureDate(1737290400000) // => Date (from milliseconds)
109
+ * ensureDate(new Date()) // => Date (same reference)
110
+ * ensureDate(null) // => null
111
+ * ensureDate('invalid') // => null
112
+ *
113
+ * @see {@link DateLike} for the accepted input types
114
+ * @since 2.0.0
115
+ */
116
+ function ensureDate(input) {
117
+ if (input === null || input === void 0 || input === "" || input === 0) return null;
118
+ if (input instanceof Date) return isNaN(input.getTime()) ? null : input;
119
+ if (hasEpochMilliseconds(input)) {
120
+ const date = new Date(input.epochMilliseconds);
121
+ return isNaN(date.getTime()) ? null : date;
122
+ }
123
+ if (typeof input === "number") {
124
+ const normalized = normalizeTimestamp(input);
125
+ const date = new Date(normalized);
126
+ return isNaN(date.getTime()) ? null : date;
127
+ }
128
+ if (typeof input === "string") {
129
+ const date = new Date(input);
130
+ return isNaN(date.getTime()) ? null : date;
131
+ }
132
+ return null;
133
+ }
134
+ //#endregion
46
135
  //#region helpers/date/compare.ts
47
136
  /**
137
+ * This file is part of helpers4.
138
+ * Copyright (C) 2025 baxyz
139
+ * SPDX-License-Identifier: LGPL-3.0-or-later
140
+ */
141
+ /**
48
142
  * Comparison of two dates.
49
- *
143
+ *
144
+ * Accepts any {@link DateLike} input (Date, timestamp, or date string).
145
+ *
50
146
  * @param dateA - First date to compare
51
147
  * @param dateB - Second date to compare
52
148
  * @param options - Comparison options
53
149
  * @returns `true` if dates are identical according to the specified precision, `false` otherwise
150
+ * @example
151
+ * compare('2025-01-19', new Date('2025-01-19T08:00:00Z'), { precision: 'days' })
152
+ * // => true
54
153
  * @since 2.0.0
55
154
  */
56
155
  function compare(dateA, dateB, options = {}) {
57
156
  const { precision = "milliseconds" } = options;
58
- if (!(dateA instanceof Date) || !(dateB instanceof Date)) return dateA === dateB;
59
- if (isNaN(dateA.getTime()) || isNaN(dateB.getTime())) return isNaN(dateA.getTime()) && isNaN(dateB.getTime());
157
+ const a = ensureDate(dateA);
158
+ const b = ensureDate(dateB);
159
+ if (a === null || b === null) return false;
60
160
  switch (precision) {
61
- case "days": return dateA.toDateString() === dateB.toDateString();
62
- case "hours": return Math.floor(dateA.getTime() / (1e3 * 60 * 60)) === Math.floor(dateB.getTime() / (1e3 * 60 * 60));
63
- case "minutes": return Math.floor(dateA.getTime() / (1e3 * 60)) === Math.floor(dateB.getTime() / (1e3 * 60));
64
- case "seconds": return Math.floor(dateA.getTime() / 1e3) === Math.floor(dateB.getTime() / 1e3);
65
- default: return dateA.getTime() === dateB.getTime();
161
+ case "years": return a.getFullYear() === b.getFullYear();
162
+ case "months": return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth();
163
+ case "days": return a.toDateString() === b.toDateString();
164
+ case "hours": return Math.floor(a.getTime() / (1e3 * 60 * 60)) === Math.floor(b.getTime() / (1e3 * 60 * 60));
165
+ case "minutes": return Math.floor(a.getTime() / (1e3 * 60)) === Math.floor(b.getTime() / (1e3 * 60));
166
+ case "seconds": return Math.floor(a.getTime() / 1e3) === Math.floor(b.getTime() / 1e3);
167
+ default: return a.getTime() === b.getTime();
66
168
  }
67
169
  }
68
170
  //#endregion
@@ -113,7 +215,7 @@ function isSpecialObject(value) {
113
215
  */
114
216
  /**
115
217
  * Deep comparison of two objects that returns detailed information about differences.
116
- *
218
+ *
117
219
  * @param objA - First object to compare (can be object, undefined, or null)
118
220
  * @param objB - Second object to compare (can be object, undefined, or null)
119
221
  * @returns `true` if objects are identical, `false` if incompatible types, or a `DeepCompareResult` object detailing differences
@@ -123,7 +225,7 @@ function deepCompare(objA, objB) {
123
225
  if (objA === objB) return true;
124
226
  if (objA == null || objB == null) return false;
125
227
  if (objA instanceof Date && objB instanceof Date) return compare(objA, objB);
126
- if (Array.isArray(objA) && Array.isArray(objB)) return quickCompare$1(objA, objB);
228
+ if (Array.isArray(objA) && Array.isArray(objB)) return shallowEquals$1(objA, objB);
127
229
  if (Array.isArray(objA) || Array.isArray(objB)) return false;
128
230
  if (isSpecialObject(objA) || isSpecialObject(objB)) return objA === objB;
129
231
  const keys = Array.from(new Set([...Object.keys(objA), ...Object.keys(objB)]));
@@ -137,7 +239,7 @@ function deepCompare(objA, objB) {
137
239
  const valueA = objA[key];
138
240
  const valueB = objB[key];
139
241
  if (Array.isArray(valueA) && Array.isArray(valueB)) {
140
- if (!quickCompare$1(valueA, valueB)) differences[key] = false;
242
+ if (!shallowEquals$1(valueA, valueB)) differences[key] = false;
141
243
  } else if (valueA instanceof Date && valueB instanceof Date) {
142
244
  if (!compare(valueA, valueB)) differences[key] = false;
143
245
  } else if (isSpecialObject(valueA) || isSpecialObject(valueB)) {
@@ -151,34 +253,59 @@ function deepCompare(objA, objB) {
151
253
  return Object.keys(differences).length > 0 ? differences : true;
152
254
  }
153
255
  //#endregion
154
- //#region helpers/object/deepMerge.ts
256
+ //#region helpers/type/isPlainObject.ts
155
257
  /**
156
258
  * This file is part of helpers4.
157
259
  * Copyright (C) 2025 baxyz
158
260
  * SPDX-License-Identifier: LGPL-3.0-or-later
159
261
  */
160
262
  /**
161
- * Merges two or more objects deeply
162
- * @param target - The target object
163
- * @param sources - The source objects to merge
164
- * @returns The merged object
165
- * @since 1.9.0
263
+ * Checks if a value is a plain object.
264
+ *
265
+ * A plain object is created by `{}`, `new Object()`, or `Object.create(null)`.
266
+ * Returns `false` for arrays, Date, Map, Set, RegExp, class instances, etc.
267
+ *
268
+ * @param value - The value to check
269
+ * @returns True if value is a plain object
270
+ * @example
271
+ * isPlainObject({}) // => true
272
+ * isPlainObject({ a: 1 }) // => true
273
+ * isPlainObject(new Date()) // => false
274
+ * isPlainObject([]) // => false
275
+ * isPlainObject(null) // => false
276
+ * @since 2.0.0
166
277
  */
278
+ function isPlainObject(value) {
279
+ if (value === null || typeof value !== "object") return false;
280
+ const proto = Object.getPrototypeOf(value);
281
+ return proto === Object.prototype || proto === null;
282
+ }
283
+ //#endregion
284
+ //#region helpers/object/deepMerge.ts
285
+ /**
286
+ * This file is part of helpers4.
287
+ * Copyright (C) 2025 baxyz
288
+ * SPDX-License-Identifier: LGPL-3.0-or-later
289
+ */
290
+ var UNSAFE_KEYS$1 = new Set([
291
+ "__proto__",
292
+ "constructor",
293
+ "prototype"
294
+ ]);
167
295
  function deepMerge(target, ...sources) {
296
+ if (target === void 0 || target === null) return target;
168
297
  if (!sources.length) return target;
169
298
  const source = sources.shift();
170
299
  if (!source) return deepMerge(target, ...sources);
171
300
  for (const key in source) {
301
+ if (!Object.hasOwn(source, key) || UNSAFE_KEYS$1.has(key)) continue;
172
302
  const targetValue = target[key];
173
303
  const sourceValue = source[key];
174
- if (isObject(targetValue) && isObject(sourceValue)) target[key] = deepMerge(targetValue, sourceValue);
304
+ if (isPlainObject(targetValue) && isPlainObject(sourceValue)) target[key] = deepMerge(targetValue, sourceValue);
175
305
  else if (sourceValue !== void 0) target[key] = sourceValue;
176
306
  }
177
307
  return deepMerge(target, ...sources);
178
308
  }
179
- function isObject(item) {
180
- return item && typeof item === "object" && !Array.isArray(item);
181
- }
182
309
  //#endregion
183
310
  //#region helpers/object/get.ts
184
311
  /**
@@ -204,29 +331,44 @@ function get(obj, path, defaultValue) {
204
331
  return result !== void 0 ? result : defaultValue;
205
332
  }
206
333
  //#endregion
207
- //#region helpers/object/quickCompare.ts
334
+ //#region helpers/object/omit.ts
335
+ function omit(obj, keys) {
336
+ if (obj === void 0 || obj === null) return obj;
337
+ const result = { ...obj };
338
+ for (const key of keys) delete result[key];
339
+ return result;
340
+ }
341
+ //#endregion
342
+ //#region helpers/object/pick.ts
343
+ function pick(obj, keys) {
344
+ if (obj === void 0 || obj === null) return obj;
345
+ const result = {};
346
+ for (const key of keys) if (Object.prototype.hasOwnProperty.call(obj, key)) result[key] = obj[key];
347
+ return result;
348
+ }
349
+ //#endregion
350
+ //#region helpers/type/isNullish.ts
208
351
  /**
209
352
  * This file is part of helpers4.
210
353
  * Copyright (C) 2025 baxyz
211
354
  * SPDX-License-Identifier: LGPL-3.0-or-later
212
355
  */
213
356
  /**
214
- * Quick comparison of two objects using JSON.stringify.
215
- * This is a fast but simple comparison that may not work for all edge cases.
357
+ * Checks if a value is null or undefined (nullish).
358
+ * @param value - The value to check
359
+ * @returns True if value is null or undefined
360
+ * @example
361
+ * isNullish(null) // => true
362
+ * isNullish(undefined) // => true
363
+ * isNullish(0) // => false
364
+ * isNullish('') // => false
216
365
  *
217
- * @param objA - First object to compare
218
- * @param objB - Second object to compare
219
- * @returns `true` if objects are identical according to JSON.stringify, `false` otherwise
366
+ * @see {@link isDefined} for the inverse check (`!isNullish`)
367
+ * @see {@link isFalsy} to also catch `0`, `""`, `false`, and `NaN`
220
368
  * @since 2.0.0
221
369
  */
222
- function quickCompare(objA, objB) {
223
- if (objA === objB) return true;
224
- if (typeof objA === "function" || typeof objB === "function") return objA === objB;
225
- try {
226
- return JSON.stringify(objA) === JSON.stringify(objB);
227
- } catch {
228
- return objA === objB;
229
- }
370
+ function isNullish(value) {
371
+ return value === null || value === void 0;
230
372
  }
231
373
  //#endregion
232
374
  //#region helpers/object/removeUndefinedNull.ts
@@ -243,10 +385,19 @@ function quickCompare(objA, objB) {
243
385
  * @since 1.0.0
244
386
  */
245
387
  function removeUndefinedNull(obj) {
246
- return obj ? Object.entries(obj).filter(([_, v]) => !isNullish(v)).reduce((acc, [k, v]) => ({
247
- ...acc,
248
- [k]: v
249
- }), {}) : obj;
388
+ if (!obj) return obj;
389
+ const result = {};
390
+ for (const [k, v] of Object.entries(obj)) if (!isNullish(v)) result[k] = v;
391
+ return result;
392
+ }
393
+ //#endregion
394
+ //#region helpers/object/safeJsonParse.ts
395
+ function safeJsonParse(input, fallback) {
396
+ try {
397
+ return JSON.parse(input);
398
+ } catch {
399
+ return arguments.length < 2 ? null : fallback;
400
+ }
250
401
  }
251
402
  //#endregion
252
403
  //#region helpers/object/set.ts
@@ -255,6 +406,11 @@ function removeUndefinedNull(obj) {
255
406
  * Copyright (C) 2025 baxyz
256
407
  * SPDX-License-Identifier: LGPL-3.0-or-later
257
408
  */
409
+ var UNSAFE_KEYS = new Set([
410
+ "__proto__",
411
+ "constructor",
412
+ "prototype"
413
+ ]);
258
414
  /**
259
415
  * Sets a value in an object using a dot-notated path
260
416
  * @param obj - The object to set value in
@@ -265,6 +421,7 @@ function removeUndefinedNull(obj) {
265
421
  */
266
422
  function set(obj, path, value) {
267
423
  const keys = path.split(".");
424
+ if (keys.some((k) => UNSAFE_KEYS.has(k))) return obj;
268
425
  let current = obj;
269
426
  for (let i = 0; i < keys.length - 1; i++) {
270
427
  const key = keys[i];
@@ -275,6 +432,31 @@ function set(obj, path, value) {
275
432
  return obj;
276
433
  }
277
434
  //#endregion
278
- export { deepClone, deepCompare, deepMerge, get, quickCompare, removeUndefinedNull, set };
435
+ //#region helpers/object/shallowEquals.ts
436
+ /**
437
+ * This file is part of helpers4.
438
+ * Copyright (C) 2025 baxyz
439
+ * SPDX-License-Identifier: LGPL-3.0-or-later
440
+ */
441
+ /**
442
+ * Quick comparison of two objects using JSON.stringify.
443
+ * This is a fast but simple comparison that may not work for all edge cases.
444
+ *
445
+ * @param objA - First object to compare
446
+ * @param objB - Second object to compare
447
+ * @returns `true` if objects are identical according to JSON.stringify, `false` otherwise
448
+ * @since 2.0.0
449
+ */
450
+ function shallowEquals(objA, objB) {
451
+ if (objA === objB) return true;
452
+ if (typeof objA === "function" || typeof objB === "function") return objA === objB;
453
+ try {
454
+ return JSON.stringify(objA) === JSON.stringify(objB);
455
+ } catch {
456
+ return objA === objB;
457
+ }
458
+ }
459
+ //#endregion
460
+ export { compact, deepClone, deepCompare, deepMerge, get, omit, pick, removeUndefinedNull, safeJsonParse, set, shallowEquals };
279
461
 
280
462
  //# sourceMappingURL=index.js.map