@pfeiferio/object-utils 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Pascal Pfeifer <pascal@pfeifer.zone>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,130 @@
1
+ # @pfeiferio/object-utils
2
+
3
+ Small, predictable utility functions for working with plain JavaScript objects.
4
+
5
+ This package provides a minimal set of helpers for **object inspection and deep merging** with strict, explicit behavior and zero dependencies.
6
+
7
+ ---
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ npm install @pfeiferio/object-utils
13
+ ````
14
+
15
+ ---
16
+
17
+ ## Usage
18
+
19
+ ```ts
20
+ import {
21
+ isObject,
22
+ isPlainObject,
23
+ mergeObjects
24
+ } from '@pfeiferio/object-utils'
25
+ ```
26
+
27
+ ---
28
+
29
+ ## `isObject(value)`
30
+
31
+ Checks whether a value is a **non-null object**, excluding arrays.
32
+
33
+ ```ts
34
+ isObject({}) // true
35
+ isObject([]) // false
36
+ isObject(null) // false
37
+ isObject('string') // false
38
+ ```
39
+
40
+ This is a safe alternative to `typeof value === 'object'`.
41
+
42
+ ---
43
+
44
+ ## `isPlainObject(value)`
45
+
46
+ Checks whether a value is a **plain object**.
47
+
48
+ A plain object is defined as:
49
+
50
+ * created via object literal (`{}`)
51
+ * or `Object.create(null)`
52
+
53
+ ```ts
54
+ isPlainObject({}) // true
55
+ isPlainObject(Object.create(null)) // true
56
+
57
+ isPlainObject([]) // false
58
+ isPlainObject(new Date()) // false
59
+ isPlainObject(null) // false
60
+ ```
61
+
62
+ Useful for merge logic and configuration handling.
63
+
64
+ ---
65
+
66
+ ## `mergeObjects(a, b)`
67
+
68
+ Deeply merges two values.
69
+
70
+ * **Only plain objects** are merged recursively
71
+ * All other values **overwrite**
72
+ * Inputs are **never mutated**
73
+
74
+ ```ts
75
+ mergeObjects(
76
+ { server: { host: 'example.com' } },
77
+ { server: { port: 80 } }
78
+ )
79
+ // → { server: { host: 'example.com', port: 80 } }
80
+ ```
81
+
82
+ ### Overwrite behavior
83
+
84
+ ```ts
85
+ mergeObjects(
86
+ { a: { b: 1 } },
87
+ { a: 2 }
88
+ )
89
+ // → { a: 2 }
90
+ ```
91
+
92
+ ```ts
93
+ mergeObjects(
94
+ { list: [1, 2] },
95
+ { list: [3] }
96
+ )
97
+ // → { list: [3] }
98
+ ```
99
+
100
+ ### Non-plain values always win
101
+
102
+ ```ts
103
+ mergeObjects(
104
+ { a: { b: 1 } },
105
+ null
106
+ )
107
+ // → null
108
+ ```
109
+
110
+ ---
111
+
112
+ ## Design Principles
113
+
114
+ * Explicit semantics
115
+ * No array merging
116
+ * No mutation
117
+ * Predictable results
118
+ * Suitable for configuration and business logic
119
+
120
+ This package intentionally avoids:
121
+
122
+ * implicit deep cloning
123
+ * class instance merging
124
+ * array merging heuristics
125
+
126
+ ---
127
+
128
+ ## License
129
+
130
+ MIT
@@ -0,0 +1,4 @@
1
+ export { isPlainObject } from "./isPlainObject.js";
2
+ export { isObject } from "./isObject.js";
3
+ export { mergeObjects } from "./mergeObjects.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,aAAa,EAAC,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAC,QAAQ,EAAC,MAAM,eAAe,CAAC;AACvC,OAAO,EAAC,YAAY,EAAC,MAAM,mBAAmB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { isPlainObject } from "./isPlainObject.js";
2
+ export { isObject } from "./isObject.js";
3
+ export { mergeObjects } from "./mergeObjects.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,aAAa,EAAC,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAC,QAAQ,EAAC,MAAM,eAAe,CAAC;AACvC,OAAO,EAAC,YAAY,EAAC,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Checks whether a value is a non-null object excluding arrays.
3
+ * @param {unknown} value
4
+ * @returns {boolean}
5
+ */
6
+ export declare const isObject: (value: unknown) => value is object;
7
+ //# sourceMappingURL=isObject.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"isObject.d.ts","sourceRoot":"","sources":["../src/isObject.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,eAAO,MAAM,QAAQ,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,MAMlD,CAAA"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Checks whether a value is a non-null object excluding arrays.
3
+ * @param {unknown} value
4
+ * @returns {boolean}
5
+ */
6
+ export const isObject = (value) => {
7
+ return (typeof value === 'object' &&
8
+ value !== null &&
9
+ !Array.isArray(value));
10
+ };
11
+ //# sourceMappingURL=isObject.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"isObject.js","sourceRoot":"","sources":["../src/isObject.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,KAAc,EAAmB,EAAE;IAC1D,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACd,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CACtB,CAAA;AACH,CAAC,CAAA"}
@@ -0,0 +1,17 @@
1
+ type PlainObject = Record<string, unknown>;
2
+ /**
3
+ * Checks whether a value is a plain object.
4
+ *
5
+ * A plain object is defined as an object created via object literal
6
+ * notation or `Object` constructor. Arrays, null, and all other
7
+ * non-object types are explicitly excluded.
8
+ *
9
+ * @param {*} value
10
+ * The value to check.
11
+ *
12
+ * @returns {boolean}
13
+ * `true` if the value is a plain object, otherwise `false`.
14
+ */
15
+ export declare const isPlainObject: (value: unknown) => value is PlainObject;
16
+ export {};
17
+ //# sourceMappingURL=isPlainObject.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"isPlainObject.d.ts","sourceRoot":"","sources":["../src/isPlainObject.ts"],"names":[],"mappings":"AAEA,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAE1C;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,aAAa,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,WAQvD,CAAA"}
@@ -0,0 +1,22 @@
1
+ import { isObject } from "./isObject.js";
2
+ /**
3
+ * Checks whether a value is a plain object.
4
+ *
5
+ * A plain object is defined as an object created via object literal
6
+ * notation or `Object` constructor. Arrays, null, and all other
7
+ * non-object types are explicitly excluded.
8
+ *
9
+ * @param {*} value
10
+ * The value to check.
11
+ *
12
+ * @returns {boolean}
13
+ * `true` if the value is a plain object, otherwise `false`.
14
+ */
15
+ export const isPlainObject = (value) => {
16
+ if (!isObject(value)) {
17
+ return false;
18
+ }
19
+ const proto = Object.getPrototypeOf(value);
20
+ return proto === Object.prototype || proto === null;
21
+ };
22
+ //# sourceMappingURL=isPlainObject.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"isPlainObject.js","sourceRoot":"","sources":["../src/isPlainObject.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,QAAQ,EAAC,MAAM,eAAe,CAAC;AAIvC;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,KAAc,EAAwB,EAAE;IAEpE,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO,KAAK,CAAA;IACd,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,CAAA;IAC1C,OAAO,KAAK,KAAK,MAAM,CAAC,SAAS,IAAI,KAAK,KAAK,IAAI,CAAA;AACrD,CAAC,CAAA"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Deeply merges two objects.
3
+ *
4
+ * Plain objects are merged recursively. For all other value types
5
+ * (arrays, primitives, null, or differing types), the value from
6
+ * the second object overrides the first one.
7
+ *
8
+ * This function does not mutate either input object.
9
+ *
10
+ * @param {Object} a
11
+ * Base object.
12
+ *
13
+ * @param {Object} b
14
+ * object whose values take precedence.
15
+ *
16
+ * @returns {Object}
17
+ * A new object containing the merged result.
18
+ *
19
+ * @example
20
+ * mergeObjects(
21
+ * { server: { host: 'example.com' } },
22
+ * { server: { port: 80 } }
23
+ * )
24
+ * // → { server: { host: 'example.com', port: 80 } }
25
+ */
26
+ /**
27
+ * @param a
28
+ * @param b
29
+ */
30
+ export declare const mergeObjects: (a: unknown, b: unknown) => unknown;
31
+ //# sourceMappingURL=mergeObjects.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mergeObjects.d.ts","sourceRoot":"","sources":["../src/mergeObjects.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH;;;GAGG;AACH,eAAO,MAAM,YAAY,GAAI,GAAG,OAAO,EAAE,GAAG,OAAO,KAAG,OAmBrD,CAAA"}
@@ -0,0 +1,48 @@
1
+ import { isPlainObject } from "./isPlainObject.js";
2
+ /**
3
+ * Deeply merges two objects.
4
+ *
5
+ * Plain objects are merged recursively. For all other value types
6
+ * (arrays, primitives, null, or differing types), the value from
7
+ * the second object overrides the first one.
8
+ *
9
+ * This function does not mutate either input object.
10
+ *
11
+ * @param {Object} a
12
+ * Base object.
13
+ *
14
+ * @param {Object} b
15
+ * object whose values take precedence.
16
+ *
17
+ * @returns {Object}
18
+ * A new object containing the merged result.
19
+ *
20
+ * @example
21
+ * mergeObjects(
22
+ * { server: { host: 'example.com' } },
23
+ * { server: { port: 80 } }
24
+ * )
25
+ * // → { server: { host: 'example.com', port: 80 } }
26
+ */
27
+ /**
28
+ * @param a
29
+ * @param b
30
+ */
31
+ export const mergeObjects = (a, b) => {
32
+ // if b is not a plain object, it always wins
33
+ if (!isPlainObject(a) || !isPlainObject(b)) {
34
+ return b;
35
+ }
36
+ const result = { ...a };
37
+ for (const [key, valueB] of Object.entries(b)) {
38
+ const valueA = a[key];
39
+ if (isPlainObject(valueA) && isPlainObject(valueB)) {
40
+ result[key] = mergeObjects(valueA, valueB);
41
+ }
42
+ else {
43
+ result[key] = valueB;
44
+ }
45
+ }
46
+ return result;
47
+ };
48
+ //# sourceMappingURL=mergeObjects.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mergeObjects.js","sourceRoot":"","sources":["../src/mergeObjects.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,aAAa,EAAC,MAAM,oBAAoB,CAAC;AAEjD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAU,EAAE,CAAU,EAAW,EAAE;IAC9D,6CAA6C;IAC7C,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3C,OAAO,CAAC,CAAA;IACV,CAAC;IAED,MAAM,MAAM,GAA4B,EAAC,GAAG,CAAC,EAAC,CAAA;IAE9C,KAAK,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9C,MAAM,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,CAAA;QAErB,IAAI,aAAa,CAAC,MAAM,CAAC,IAAI,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC;YACnD,MAAM,CAAC,GAAG,CAAC,GAAG,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;QAC5C,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,GAAG,CAAC,GAAG,MAAM,CAAA;QACtB,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAA;AACf,CAAC,CAAA"}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@pfeiferio/object-utils",
3
+ "version": "1.0.0",
4
+ "description": "Small, predictable utilities for working with plain JavaScript objects",
5
+ "license": "MIT",
6
+ "author": "Pascal Pfeifer <pascal@pfeifer.zone>",
7
+ "sideEffects": false,
8
+ "type": "module",
9
+ "main": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist/",
19
+ "README.md",
20
+ "LICENSE"
21
+ ],
22
+ "keywords": [
23
+ "object",
24
+ "utils",
25
+ "merge",
26
+ "plain-object",
27
+ "deep-merge",
28
+ "utility",
29
+ "configuration"
30
+ ],
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/pfeiferio/utils-object.git"
34
+ },
35
+ "bugs": {
36
+ "url": "https://github.com/pfeiferio/utils-object/issues"
37
+ },
38
+ "homepage": "https://github.com/pfeiferio/utils-object#readme",
39
+ "engines": {
40
+ "node": ">=18.0.0"
41
+ },
42
+ "scripts": {
43
+ "build": "tsc",
44
+ "test": "npm run build && node --test",
45
+ "test:coverage": "npm run build && node --test --experimental-test-coverage",
46
+ "test:watch": "node --test --watch",
47
+ "prepublishOnly": "npm run clean && npm test",
48
+ "clean": "rm -rf dist"
49
+ },
50
+ "devDependencies": {
51
+ "@types/node": "^20.17.9",
52
+ "typescript": "^5.9.3"
53
+ }
54
+ }