@komed3/deepmerge 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) 2026 Paul Köhler
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,200 @@
1
+ # @komed3/deepmerge
2
+
3
+ A fast and efficient deep object merging and manipulation library for Node.js and the browser, optimized for performance and flexibility.
4
+
5
+ `deepmerge` provides a high-performance `Merger` with a non-recursive stack-based implementation and a versatile `Accessor` for safe, deep object manipulation using dot and bracket notation. It includes a built-in path compiler with caching to ensure maximum efficiency during repeated operations.
6
+
7
+ ## Installation
8
+
9
+ Install via npm:
10
+
11
+ ```bash
12
+ npm install @komed3/deepmerge
13
+ ```
14
+
15
+ ## Import Methods
16
+
17
+ `deepmerge` supports multiple module formats for seamless integration across different environments.
18
+
19
+ ### ESM (ECMAScript Modules)
20
+ For modern projects using `import`:
21
+
22
+ ```ts
23
+ import { factory, Merger, Accessor } from '@komed3/deepmerge';
24
+ ```
25
+
26
+ ### CommonJS
27
+ For Node.js projects using `require`:
28
+
29
+ ```js
30
+ const { factory, Merger, Accessor } = require( '@komed3/deepmerge' );
31
+ ```
32
+
33
+ ### UMD (Browser)
34
+ Include the script in your HTML:
35
+
36
+ ```html
37
+ <script src="node_modules/@komed3/deepmerge/dist/index.umd.min.js"></script>
38
+ <script>
39
+ const { factory } = deepmerge;
40
+ </script>
41
+ ```
42
+
43
+ ## Quick Usage
44
+
45
+ The easiest way to get started is using the `factory()` function to create a new instance with a shared configuration.
46
+
47
+ ```ts
48
+ import { factory } from '@komed3/deepmerge';
49
+
50
+ // initialize with default options
51
+ const qm = factory();
52
+
53
+ const target = { user: { name: 'Max' } };
54
+ const source = { user: { role: 'admin' }, tags: [ 'lead' ] };
55
+
56
+ // deep merge objects
57
+ qm.merger.merge( target, source );
58
+ console.log( target ); // { user: { name: 'Max', role: 'admin' }, tags: [ 'lead' ] }
59
+
60
+ // safe property access using dot/bracket notation
61
+ const name = qm.accessor.get( target, 'user.name' ); // 'Max'
62
+ qm.accessor.set( target, 'user.id', 123 );
63
+ qm.accessor.update( target, 'tags[0]', v => v.toUpperCase() );
64
+
65
+ console.log( target.user.id ); // 123
66
+ console.log( target.tags ); // [ 'LEAD' ]
67
+ ```
68
+
69
+ Instead of using the factory function, you can also create instances of `Merger`, `Accessor`, and `Path` directly:
70
+
71
+ ```ts
72
+ import { Merger, Accessor, Path } from '@komed3/deepmerge';
73
+
74
+ const merger = new Merger( { ... } );
75
+ const accessor = new Accessor( { ... } );
76
+ const path = new Path( { ... } );
77
+ ```
78
+
79
+ ## API Reference
80
+
81
+ ### factory( options? )
82
+ Creates an object containing pre-configured instances of `Merger`, `Accessor`, and `Path`.
83
+
84
+ ```ts
85
+ const { merger, accessor, path } = factory( { deep: true, protect: false } );
86
+ ```
87
+
88
+ ### Merger
89
+
90
+ - `merge< T >( target: T, ...sources: any[] ) : T`
91
+ Performs a deep merge of all source objects into the target.
92
+ - `mergeAt< T >( target: T, path: PathLike, ...sources: any[] ) : T`
93
+ Merges sources into the target at a specific nested path.
94
+
95
+ ### Accessor
96
+
97
+ - `get< O, V >( obj: O, path: PathLike ) : V | undefined`
98
+ Retrieves a value at the given path.
99
+ - `set< O, V >( obj: O, path: PathLike, value: V ) : void`
100
+ Sets a value at the path, creating missing structures if needed.
101
+ - `has< O >( obj: O, path: PathLike ) : boolean`
102
+ Checks if a nested property exists.
103
+ - `delete< O >( obj: O, path: PathLike ) : void`
104
+ Removes a property or array index at the path.
105
+ - `update< O >( obj: O, path: PathLike, fn: ( v: any ) => any ) : void`
106
+ Transforms a value at the path using a function.
107
+
108
+ ### Path
109
+
110
+ - `compile( path: string ) : CompiledPath`
111
+ Parses a path string into tokens and caches the result.
112
+ - `normalize( path: PathLike ) : CompiledPath`
113
+ Ensures a path is in its compiled format.
114
+
115
+ ## Options
116
+
117
+ ### MergeOptions
118
+ - `deep` (boolean, default: `true`)
119
+ Whether to perform deep merging of nested objects.
120
+ - `protect` (boolean, default: `false`)
121
+ If true, existing properties in the target are not overwritten.
122
+ - `mergeUndefined` (boolean, default: `false`)
123
+ Whether to allow `undefined` values from sources to overwrite target values.
124
+ - `strict` (boolean, default: `false`)
125
+ If true, new objects/arrays will not be created when paths are missing.
126
+ - `createObject` (function, default: `() => Object.create( null )`)
127
+ A factory function for creating new objects when missing structures are encountered during a deep merge.
128
+ - `arrayMode` (`'replace' | 'keep' | 'concat' | 'unique'` | function, default: `'replace'`)
129
+ Strategy for merging arrays or a custom merge function `( target: any[], source: any[] ) => any[]`.
130
+ - `valueFn` (function)
131
+ A custom function to handle specific value merging logic: `( key, targetVal, sourceVal ) => any`.
132
+ - `pathOptions` (`PathOptions`)
133
+ Configuration for the internal path compiler.
134
+
135
+ ### PathOptions
136
+ - `cache` (boolean, default: `true`)
137
+ Enable or disable caching of compiled path strings.
138
+ - `maxCacheSize` (number, default: `1000`)
139
+ The maximum number of paths to keep in the cache.
140
+
141
+ ## Customization
142
+
143
+ ### Custom Object Creation
144
+ Use `createObject` to control how new objects are instantiated, for example to use plain objects instead of null-prototype objects:
145
+
146
+ ```ts
147
+ const qm = factory( {
148
+ createObject: () => ( {} )
149
+ } );
150
+ ```
151
+
152
+ ### Custom Array Merging
153
+ Pass a function to `arrayMode` to implement your own logic, such as merging by a specific property:
154
+
155
+ ```ts
156
+ const qm = factory( {
157
+ arrayMode: ( target, source ) => {
158
+ // custom merge logic
159
+ return [ ...target, ...source ].filter( v => v.active );
160
+ }
161
+ } );
162
+ ```
163
+
164
+ ### Custom Value Merging
165
+ Use `valueFn` to define custom logic for merging individual values:
166
+
167
+ ```ts
168
+ const qm = factory( {
169
+ valueFn: ( key, targetVal, sourceVal ) => {
170
+ if ( key === 'count' ) {
171
+ return targetVal + sourceVal;
172
+ }
173
+ return sourceVal;
174
+ }
175
+ } );
176
+ ```
177
+
178
+ ## Array Modes
179
+
180
+ Control how arrays are handled during a merge using the built-in `ArrayMode` enums:
181
+
182
+ - `Replace`: Overwrites the target array with the source array (default).
183
+ - `Keep`: Retains the target array and ignores the source.
184
+ - `Concat`: Appends source elements to the target array.
185
+ - `Unique`: Combines both arrays and removes duplicates.
186
+
187
+ ```ts
188
+ import { factory, ArrayMode } from '@komed3/deepmerge';
189
+
190
+ const qm = factory( { arrayMode: ArrayMode.Unique } );
191
+ const target = { ids: [ 1, 2 ] };
192
+ qm.merger.merge( target, { ids: [ 2, 3 ] } );
193
+
194
+ console.log( target.ids ); // [ 1, 2, 3 ]
195
+ ```
196
+
197
+ ----
198
+
199
+ Copyright (c) 2026 Paul Köhler (komed3). All rights reserved.
200
+ Released under the MIT license. See LICENSE file in the project root for details.
@@ -0,0 +1,62 @@
1
+ 'use strict';
2
+
3
+ var path = require('./path.cjs');
4
+
5
+ class Accessor {
6
+ path;
7
+ constructor(options = {}) {
8
+ this.path = new path.Path(options);
9
+ }
10
+ isUnsafeKey(key) {
11
+ return key === '__proto__' || key === 'constructor' || key === 'prototype';
12
+ }
13
+ walk(obj, path, create) {
14
+ const tokens = this.path.normalize(path).tokens;
15
+ let cur = obj;
16
+ for (let i = 0; i < tokens.length - 1; i++) {
17
+ const key = tokens[i];
18
+ if (this.isUnsafeKey(key)) return;
19
+ let next = cur?.[key];
20
+ if (next == null) {
21
+ if (!create) return;
22
+ next = typeof tokens[i + 1] === 'number' ? [] : {};
23
+ cur[key] = next;
24
+ }
25
+ cur = next;
26
+ }
27
+ const lastKey = tokens[tokens.length - 1];
28
+ return { parent: cur, key: lastKey, value: cur?.[lastKey] };
29
+ }
30
+ has(obj, path) {
31
+ const tokens = this.path.normalize(path).tokens;
32
+ let cur = obj;
33
+ for (let i = 0; i < tokens.length; i++) {
34
+ if (cur == null) return false;
35
+ cur = cur[tokens[i]];
36
+ }
37
+ return true;
38
+ }
39
+ get(obj, path) {
40
+ const tokens = this.path.normalize(path).tokens;
41
+ let cur = obj;
42
+ for (let i = 0; i < tokens.length; i++) {
43
+ if (cur == null) return undefined;
44
+ cur = cur[tokens[i]];
45
+ }
46
+ return cur;
47
+ }
48
+ set(obj, path, value) {
49
+ const res = this.walk(obj, path, true);
50
+ if (res) res.parent[res.key] = value;
51
+ }
52
+ delete(obj, path) {
53
+ const res = this.walk(obj, path, false);
54
+ if (res && res.parent != null) delete res.parent[res.key];
55
+ }
56
+ update(obj, path, fn) {
57
+ const res = this.walk(obj, path, true);
58
+ if (res) res.parent[res.key] = fn(res.value);
59
+ }
60
+ }
61
+
62
+ exports.Accessor = Accessor;
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Accessor provides high-level methods for interacting with nested object structures.
3
+ *
4
+ * It allows for safe getting, setting, deleting, and updating of values at specific paths.
5
+ * The class handles path normalization and provides protection against prototype pollution.
6
+ *
7
+ * @author Paul Köhler
8
+ * @license MIT
9
+ */
10
+ import { PathLike, PathOptions } from './path';
11
+ /**
12
+ * The Accessor class provides a set of methods to access and manipulate data within objects
13
+ * using path strings or compiled path objects.
14
+ */
15
+ export declare class Accessor {
16
+ /** The internal Path instance used for path normalization and compilation. */
17
+ private readonly path;
18
+ /**
19
+ * Creates a new Accessor instance with the given path options.
20
+ *
21
+ * @param {PathOptions} [options={}] - Configuration options for the internal Path handler.
22
+ */
23
+ constructor(options?: PathOptions);
24
+ /**
25
+ * Checks if a key is considered "unsafe" for object manipulation (prototype pollution protection).
26
+ *
27
+ * @param {any} key - The key to check.
28
+ * @returns {boolean} True if the key is unsafe, otherwise false.
29
+ */
30
+ private isUnsafeKey;
31
+ /**
32
+ * Internal method to traverse an object structure following a given path.
33
+ *
34
+ * @param {any} obj - The target object to walk through.
35
+ * @param {PathLike} path - The path to follow.
36
+ * @param {boolean} create - Whether to create missing objects/arrays along the path.
37
+ * @returns {{ parent: any, key: PathToken, value: any } | undefined} An object containing the parent, key, and value at the end of the path, or undefined if not found.
38
+ */
39
+ private walk;
40
+ /**
41
+ * Checks if a value exists at the specified path within an object.
42
+ *
43
+ * @template O
44
+ * @param {O} obj - The object to check.
45
+ * @param {PathLike} path - The path to verify.
46
+ * @returns {boolean} True if the path exists, otherwise false.
47
+ */
48
+ has<O = any>(obj: O, path: PathLike): boolean;
49
+ /**
50
+ * Retrieves the value at the specified path within an object.
51
+ *
52
+ * @template O, V
53
+ * @param {O} obj - The object to retrieve the value from.
54
+ * @param {PathLike} path - The path to the value.
55
+ * @returns {V | undefined} The value at the path, or undefined if not found.
56
+ */
57
+ get<O = any, V = any>(obj: O, path: PathLike): V | undefined;
58
+ /**
59
+ * Sets a value at the specified path within an object, creating missing structures if necessary.
60
+ *
61
+ * @template O, V
62
+ * @param {O} obj - The object to modify.
63
+ * @param {PathLike} path - The path where the value should be set.
64
+ * @param {V} value - The value to set.
65
+ */
66
+ set<O = any, V = any>(obj: O, path: PathLike, value: V): void;
67
+ /**
68
+ * Deletes the property/index at the specified path within an object.
69
+ *
70
+ * @template O
71
+ * @param {O} obj - The object to modify.
72
+ * @param {PathLike} path - The path to the property to delete.
73
+ */
74
+ delete<O = any>(obj: O, path: PathLike): void;
75
+ /**
76
+ * Updates the value at the specified path using a transformation function.
77
+ *
78
+ * @template O
79
+ * @param {O} obj - The object to modify.
80
+ * @param {PathLike} path - The path to the value to update.
81
+ * @param {( v: any ) => any} fn - The function to transform the existing value.
82
+ */
83
+ update<O = any>(obj: O, path: PathLike, fn: (v: any) => any): void;
84
+ }
@@ -0,0 +1,60 @@
1
+ import { Path } from './path.mjs';
2
+
3
+ class Accessor {
4
+ path;
5
+ constructor(options = {}) {
6
+ this.path = new Path(options);
7
+ }
8
+ isUnsafeKey(key) {
9
+ return key === '__proto__' || key === 'constructor' || key === 'prototype';
10
+ }
11
+ walk(obj, path, create) {
12
+ const tokens = this.path.normalize(path).tokens;
13
+ let cur = obj;
14
+ for (let i = 0; i < tokens.length - 1; i++) {
15
+ const key = tokens[i];
16
+ if (this.isUnsafeKey(key)) return;
17
+ let next = cur?.[key];
18
+ if (next == null) {
19
+ if (!create) return;
20
+ next = typeof tokens[i + 1] === 'number' ? [] : {};
21
+ cur[key] = next;
22
+ }
23
+ cur = next;
24
+ }
25
+ const lastKey = tokens[tokens.length - 1];
26
+ return { parent: cur, key: lastKey, value: cur?.[lastKey] };
27
+ }
28
+ has(obj, path) {
29
+ const tokens = this.path.normalize(path).tokens;
30
+ let cur = obj;
31
+ for (let i = 0; i < tokens.length; i++) {
32
+ if (cur == null) return false;
33
+ cur = cur[tokens[i]];
34
+ }
35
+ return true;
36
+ }
37
+ get(obj, path) {
38
+ const tokens = this.path.normalize(path).tokens;
39
+ let cur = obj;
40
+ for (let i = 0; i < tokens.length; i++) {
41
+ if (cur == null) return undefined;
42
+ cur = cur[tokens[i]];
43
+ }
44
+ return cur;
45
+ }
46
+ set(obj, path, value) {
47
+ const res = this.walk(obj, path, true);
48
+ if (res) res.parent[res.key] = value;
49
+ }
50
+ delete(obj, path) {
51
+ const res = this.walk(obj, path, false);
52
+ if (res && res.parent != null) delete res.parent[res.key];
53
+ }
54
+ update(obj, path, fn) {
55
+ const res = this.walk(obj, path, true);
56
+ if (res) res.parent[res.key] = fn(res.value);
57
+ }
58
+ }
59
+
60
+ export { Accessor };
@@ -0,0 +1,248 @@
1
+ (function (global, factory) {
2
+ typeof exports === 'object' && typeof module !== 'undefined'
3
+ ? factory(exports)
4
+ : typeof define === 'function' && define.amd
5
+ ? define(['exports'], factory)
6
+ : ((global =
7
+ typeof globalThis !== 'undefined' ? globalThis : global || self),
8
+ factory((global.deepmerge = {})));
9
+ })(this, function (exports) {
10
+ 'use strict';
11
+
12
+ class Path {
13
+ cache;
14
+ maxCacheSize;
15
+ constructor(options = {}) {
16
+ if (options.cache !== false) this.cache = new Map();
17
+ this.maxCacheSize = options.maxCacheSize ?? 1000;
18
+ }
19
+ compile(path) {
20
+ if (!path) return Object.freeze({ tokens: [] });
21
+ if (this.cache) {
22
+ const cached = this.cache.get(path);
23
+ if (cached) return cached;
24
+ }
25
+ const tokens = [];
26
+ let i = 0,
27
+ key = '';
28
+ while (i < path.length) {
29
+ const c = path.charCodeAt(i);
30
+ if (c === 46) {
31
+ if (key) {
32
+ (tokens.push(key), (key = ''));
33
+ }
34
+ i++;
35
+ continue;
36
+ }
37
+ if (c === 91) {
38
+ if (key) {
39
+ (tokens.push(key), (key = ''));
40
+ }
41
+ i++;
42
+ const start = i;
43
+ while (i < path.length && path.charCodeAt(i) !== 93) i++;
44
+ const inner = path.slice(start, i);
45
+ const num = inner >>> 0;
46
+ if (String(num) === inner) tokens.push(num);
47
+ else tokens.push(inner);
48
+ i++;
49
+ continue;
50
+ }
51
+ ((key += path[i]), i++);
52
+ }
53
+ if (key) tokens.push(key);
54
+ const compiled = Object.freeze({ tokens });
55
+ if (this.cache) {
56
+ if (this.cache.size >= this.maxCacheSize) this.cache.clear();
57
+ this.cache.set(path, compiled);
58
+ }
59
+ return compiled;
60
+ }
61
+ isCompiled(path) {
62
+ return path && Array.isArray(path.tokens);
63
+ }
64
+ normalize(path) {
65
+ return typeof path === 'string' ? this.compile(path) : path;
66
+ }
67
+ clearCache() {
68
+ this.cache?.clear();
69
+ }
70
+ get size() {
71
+ return this.cache?.size ?? 0;
72
+ }
73
+ }
74
+
75
+ class Accessor {
76
+ path;
77
+ constructor(options = {}) {
78
+ this.path = new Path(options);
79
+ }
80
+ isUnsafeKey(key) {
81
+ return (
82
+ key === '__proto__' || key === 'constructor' || key === 'prototype'
83
+ );
84
+ }
85
+ walk(obj, path, create) {
86
+ const tokens = this.path.normalize(path).tokens;
87
+ let cur = obj;
88
+ for (let i = 0; i < tokens.length - 1; i++) {
89
+ const key = tokens[i];
90
+ if (this.isUnsafeKey(key)) return;
91
+ let next = cur?.[key];
92
+ if (next == null) {
93
+ if (!create) return;
94
+ next = typeof tokens[i + 1] === 'number' ? [] : {};
95
+ cur[key] = next;
96
+ }
97
+ cur = next;
98
+ }
99
+ const lastKey = tokens[tokens.length - 1];
100
+ return { parent: cur, key: lastKey, value: cur?.[lastKey] };
101
+ }
102
+ has(obj, path) {
103
+ const tokens = this.path.normalize(path).tokens;
104
+ let cur = obj;
105
+ for (let i = 0; i < tokens.length; i++) {
106
+ if (cur == null) return false;
107
+ cur = cur[tokens[i]];
108
+ }
109
+ return true;
110
+ }
111
+ get(obj, path) {
112
+ const tokens = this.path.normalize(path).tokens;
113
+ let cur = obj;
114
+ for (let i = 0; i < tokens.length; i++) {
115
+ if (cur == null) return undefined;
116
+ cur = cur[tokens[i]];
117
+ }
118
+ return cur;
119
+ }
120
+ set(obj, path, value) {
121
+ const res = this.walk(obj, path, true);
122
+ if (res) res.parent[res.key] = value;
123
+ }
124
+ delete(obj, path) {
125
+ const res = this.walk(obj, path, false);
126
+ if (res && res.parent != null) delete res.parent[res.key];
127
+ }
128
+ update(obj, path, fn) {
129
+ const res = this.walk(obj, path, true);
130
+ if (res) res.parent[res.key] = fn(res.value);
131
+ }
132
+ }
133
+
134
+ class Merger {
135
+ protect;
136
+ deep;
137
+ mergeUndefined;
138
+ strict;
139
+ createObject;
140
+ valueFn;
141
+ arrayFn;
142
+ path;
143
+ constructor(options = {}) {
144
+ this.protect = !!options.protect;
145
+ this.deep = options.deep !== false;
146
+ this.mergeUndefined = !!options.mergeUndefined;
147
+ this.strict = !!options.strict;
148
+ this.createObject = options.createObject ?? (() => Object.create(null));
149
+ this.valueFn = options.valueFn;
150
+ this.arrayFn = this.compileArrayFn(options.arrayMode);
151
+ this.path = new Path(options.pathOptions);
152
+ }
153
+ compileArrayFn(mode) {
154
+ switch (mode ?? 'replace') {
155
+ case 'replace':
156
+ return (_, s) => s;
157
+ case 'keep':
158
+ return (t, _) => t;
159
+ case 'concat':
160
+ return (t, s) => t.concat(s);
161
+ case 'unique':
162
+ return (t, s) => {
163
+ const set = new Set(t);
164
+ for (let i = 0; i < s.length; i++) set.add(s[i]);
165
+ return Array.from(set);
166
+ };
167
+ }
168
+ if (typeof mode === 'function') return mode;
169
+ else throw new Error(`Invalid array merge mode: ${mode}`);
170
+ }
171
+ isUnsafeKey(key) {
172
+ return (
173
+ key === '__proto__' || key === 'constructor' || key === 'prototype'
174
+ );
175
+ }
176
+ mergeInto(target, source) {
177
+ if (source == null) return;
178
+ const stack = [[target, source]];
179
+ while (stack.length) {
180
+ const [t, s] = stack.pop();
181
+ for (const key in s) {
182
+ if (this.isUnsafeKey(key)) continue;
183
+ const sv = s[key],
184
+ tv = t[key];
185
+ if (sv === undefined && !this.mergeUndefined) continue;
186
+ if (this.protect && key in t) continue;
187
+ if (this.valueFn) {
188
+ const res = this.valueFn(key, tv, sv);
189
+ if (res !== undefined) {
190
+ t[key] = res;
191
+ continue;
192
+ }
193
+ }
194
+ if (Array.isArray(sv)) {
195
+ if (Array.isArray(tv)) t[key] = this.arrayFn(tv, sv);
196
+ else t[key] = this.arrayFn([], sv);
197
+ continue;
198
+ }
199
+ if (this.deep && sv && typeof sv === 'object') {
200
+ if (tv && typeof tv === 'object') stack.push([tv, sv]);
201
+ else {
202
+ if (this.strict) continue;
203
+ const next = this.createObject();
204
+ t[key] = next;
205
+ stack.push([next, sv]);
206
+ }
207
+ continue;
208
+ }
209
+ t[key] = sv;
210
+ }
211
+ }
212
+ }
213
+ merge(target, ...sources) {
214
+ for (let i = 0; i < sources.length; i++)
215
+ this.mergeInto(target, sources[i]);
216
+ return target;
217
+ }
218
+ mergeAt(target, path, ...sources) {
219
+ const tokens = this.path.normalize(path).tokens;
220
+ let cur = target;
221
+ for (let i = 0; i < tokens.length; i++) {
222
+ const key = tokens[i];
223
+ if (this.isUnsafeKey(key)) return target;
224
+ let next = cur[key];
225
+ if (next == null) {
226
+ if (this.strict) return target;
227
+ next = typeof tokens[i + 1] === 'number' ? [] : this.createObject();
228
+ cur[key] = next;
229
+ }
230
+ cur = next;
231
+ }
232
+ for (let i = 0; i < sources.length; i++) this.mergeInto(cur, sources[i]);
233
+ return target;
234
+ }
235
+ }
236
+
237
+ const factory = (options) =>
238
+ Object.freeze({
239
+ accessor: new Accessor(options?.pathOptions),
240
+ merger: new Merger(options),
241
+ path: new Path(options?.pathOptions)
242
+ });
243
+
244
+ exports.Accessor = Accessor;
245
+ exports.Merger = Merger;
246
+ exports.Path = Path;
247
+ exports.factory = factory;
248
+ });
@@ -0,0 +1 @@
1
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).deepmerge={})}(this,function(e){"use strict";class t{cache;maxCacheSize;constructor(e={}){!1!==e.cache&&(this.cache=new Map),this.maxCacheSize=e.maxCacheSize??1e3}compile(e){if(!e)return Object.freeze({tokens:[]});if(this.cache){const t=this.cache.get(e);if(t)return t}const t=[];let r=0,n="";for(;r<e.length;){const s=e.charCodeAt(r);if(46!==s){if(91===s){n&&(t.push(n),n=""),r++;const s=r;for(;r<e.length&&93!==e.charCodeAt(r);)r++;const i=e.slice(s,r),c=i>>>0;String(c)===i?t.push(c):t.push(i),r++;continue}n+=e[r],r++}else n&&(t.push(n),n=""),r++}n&&t.push(n);const s=Object.freeze({tokens:t});return this.cache&&(this.cache.size>=this.maxCacheSize&&this.cache.clear(),this.cache.set(e,s)),s}isCompiled(e){return e&&Array.isArray(e.tokens)}normalize(e){return"string"==typeof e?this.compile(e):e}clearCache(){this.cache?.clear()}get size(){return this.cache?.size??0}}class r{path;constructor(e={}){this.path=new t(e)}isUnsafeKey(e){return"__proto__"===e||"constructor"===e||"prototype"===e}walk(e,t,r){const n=this.path.normalize(t).tokens;let s=e;for(let e=0;e<n.length-1;e++){const t=n[e];if(this.isUnsafeKey(t))return;let i=s?.[t];if(null==i){if(!r)return;i="number"==typeof n[e+1]?[]:{},s[t]=i}s=i}const i=n[n.length-1];return{parent:s,key:i,value:s?.[i]}}has(e,t){const r=this.path.normalize(t).tokens;let n=e;for(let e=0;e<r.length;e++){if(null==n)return!1;n=n[r[e]]}return!0}get(e,t){const r=this.path.normalize(t).tokens;let n=e;for(let e=0;e<r.length;e++){if(null==n)return;n=n[r[e]]}return n}set(e,t,r){const n=this.walk(e,t,!0);n&&(n.parent[n.key]=r)}delete(e,t){const r=this.walk(e,t,!1);r&&null!=r.parent&&delete r.parent[r.key]}update(e,t,r){const n=this.walk(e,t,!0);n&&(n.parent[n.key]=r(n.value))}}class n{protect;deep;mergeUndefined;strict;createObject;valueFn;arrayFn;path;constructor(e={}){this.protect=!!e.protect,this.deep=!1!==e.deep,this.mergeUndefined=!!e.mergeUndefined,this.strict=!!e.strict,this.createObject=e.createObject??(()=>Object.create(null)),this.valueFn=e.valueFn,this.arrayFn=this.compileArrayFn(e.arrayMode),this.path=new t(e.pathOptions)}compileArrayFn(e){switch(e??"replace"){case"replace":return(e,t)=>t;case"keep":return(e,t)=>e;case"concat":return(e,t)=>e.concat(t);case"unique":return(e,t)=>{const r=new Set(e);for(let e=0;e<t.length;e++)r.add(t[e]);return Array.from(r)}}if("function"==typeof e)return e;throw new Error(`Invalid array merge mode: ${e}`)}isUnsafeKey(e){return"__proto__"===e||"constructor"===e||"prototype"===e}mergeInto(e,t){if(null==t)return;const r=[[e,t]];for(;r.length;){const[e,t]=r.pop();for(const n in t){if(this.isUnsafeKey(n))continue;const s=t[n],i=e[n];if((void 0!==s||this.mergeUndefined)&&(!this.protect||!(n in e))){if(this.valueFn){const t=this.valueFn(n,i,s);if(void 0!==t){e[n]=t;continue}}if(Array.isArray(s))Array.isArray(i)?e[n]=this.arrayFn(i,s):e[n]=this.arrayFn([],s);else if(this.deep&&s&&"object"==typeof s)if(i&&"object"==typeof i)r.push([i,s]);else{if(this.strict)continue;const t=this.createObject();e[n]=t,r.push([t,s])}else e[n]=s}}}}merge(e,...t){for(let r=0;r<t.length;r++)this.mergeInto(e,t[r]);return e}mergeAt(e,t,...r){const n=this.path.normalize(t).tokens;let s=e;for(let t=0;t<n.length;t++){const r=n[t];if(this.isUnsafeKey(r))return e;let i=s[r];if(null==i){if(this.strict)return e;i="number"==typeof n[t+1]?[]:this.createObject(),s[r]=i}s=i}for(let e=0;e<r.length;e++)this.mergeInto(s,r[e]);return e}}e.Accessor=r,e.Merger=n,e.Path=t,e.factory=e=>Object.freeze({accessor:new r(e?.pathOptions),merger:new n(e),path:new t(e?.pathOptions)})});
package/dist/index.cjs ADDED
@@ -0,0 +1,17 @@
1
+ 'use strict';
2
+
3
+ var accessor = require('./accessor.cjs');
4
+ var merger = require('./merger.cjs');
5
+ var path = require('./path.cjs');
6
+
7
+ const factory = (options) =>
8
+ Object.freeze({
9
+ accessor: new accessor.Accessor(options?.pathOptions),
10
+ merger: new merger.Merger(options),
11
+ path: new path.Path(options?.pathOptions)
12
+ });
13
+
14
+ exports.Accessor = accessor.Accessor;
15
+ exports.Merger = merger.Merger;
16
+ exports.Path = path.Path;
17
+ exports.factory = factory;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * DeepMerge is a fast and flexible object merging library.
3
+ *
4
+ * Use the factory function to create a new instance of DeepMerge or
5
+ * import the individual classes to create your own instances.
6
+ *
7
+ * @author Paul Köhler
8
+ * @license MIT
9
+ */
10
+ import { Accessor } from './accessor';
11
+ import { Merger, type MergeOptions } from './merger';
12
+ import { Path } from './path';
13
+ export * from './accessor';
14
+ export * from './merger';
15
+ export * from './path';
16
+ /**
17
+ * Creates a new instance of DeepMerge.
18
+ *
19
+ * @param {MergeOptions} options - Configuration options for the DeepMerge instance.
20
+ * @returns {DeepMerge} A new instance of DeepMerge.
21
+ */
22
+ export declare const factory: (options?: MergeOptions) => Readonly<{
23
+ accessor: Accessor;
24
+ merger: Merger;
25
+ path: Path;
26
+ }>;
package/dist/index.mjs ADDED
@@ -0,0 +1,12 @@
1
+ import { Accessor } from './accessor.mjs';
2
+ import { Merger } from './merger.mjs';
3
+ import { Path } from './path.mjs';
4
+
5
+ const factory = (options) =>
6
+ Object.freeze({
7
+ accessor: new Accessor(options?.pathOptions),
8
+ merger: new Merger(options),
9
+ path: new Path(options?.pathOptions)
10
+ });
11
+
12
+ export { Accessor, Merger, Path, factory };
@@ -0,0 +1,105 @@
1
+ 'use strict';
2
+
3
+ var path = require('./path.cjs');
4
+
5
+ class Merger {
6
+ protect;
7
+ deep;
8
+ mergeUndefined;
9
+ strict;
10
+ createObject;
11
+ valueFn;
12
+ arrayFn;
13
+ path;
14
+ constructor(options = {}) {
15
+ this.protect = !!options.protect;
16
+ this.deep = options.deep !== false;
17
+ this.mergeUndefined = !!options.mergeUndefined;
18
+ this.strict = !!options.strict;
19
+ this.createObject = options.createObject ?? (() => Object.create(null));
20
+ this.valueFn = options.valueFn;
21
+ this.arrayFn = this.compileArrayFn(options.arrayMode);
22
+ this.path = new path.Path(options.pathOptions);
23
+ }
24
+ compileArrayFn(mode) {
25
+ switch (mode ?? 'replace') {
26
+ case 'replace':
27
+ return (_, s) => s;
28
+ case 'keep':
29
+ return (t, _) => t;
30
+ case 'concat':
31
+ return (t, s) => t.concat(s);
32
+ case 'unique':
33
+ return (t, s) => {
34
+ const set = new Set(t);
35
+ for (let i = 0; i < s.length; i++) set.add(s[i]);
36
+ return Array.from(set);
37
+ };
38
+ }
39
+ if (typeof mode === 'function') return mode;
40
+ else throw new Error(`Invalid array merge mode: ${mode}`);
41
+ }
42
+ isUnsafeKey(key) {
43
+ return key === '__proto__' || key === 'constructor' || key === 'prototype';
44
+ }
45
+ mergeInto(target, source) {
46
+ if (source == null) return;
47
+ const stack = [[target, source]];
48
+ while (stack.length) {
49
+ const [t, s] = stack.pop();
50
+ for (const key in s) {
51
+ if (this.isUnsafeKey(key)) continue;
52
+ const sv = s[key],
53
+ tv = t[key];
54
+ if (sv === undefined && !this.mergeUndefined) continue;
55
+ if (this.protect && key in t) continue;
56
+ if (this.valueFn) {
57
+ const res = this.valueFn(key, tv, sv);
58
+ if (res !== undefined) {
59
+ t[key] = res;
60
+ continue;
61
+ }
62
+ }
63
+ if (Array.isArray(sv)) {
64
+ if (Array.isArray(tv)) t[key] = this.arrayFn(tv, sv);
65
+ else t[key] = this.arrayFn([], sv);
66
+ continue;
67
+ }
68
+ if (this.deep && sv && typeof sv === 'object') {
69
+ if (tv && typeof tv === 'object') stack.push([tv, sv]);
70
+ else {
71
+ if (this.strict) continue;
72
+ const next = this.createObject();
73
+ t[key] = next;
74
+ stack.push([next, sv]);
75
+ }
76
+ continue;
77
+ }
78
+ t[key] = sv;
79
+ }
80
+ }
81
+ }
82
+ merge(target, ...sources) {
83
+ for (let i = 0; i < sources.length; i++) this.mergeInto(target, sources[i]);
84
+ return target;
85
+ }
86
+ mergeAt(target, path, ...sources) {
87
+ const tokens = this.path.normalize(path).tokens;
88
+ let cur = target;
89
+ for (let i = 0; i < tokens.length; i++) {
90
+ const key = tokens[i];
91
+ if (this.isUnsafeKey(key)) return target;
92
+ let next = cur[key];
93
+ if (next == null) {
94
+ if (this.strict) return target;
95
+ next = typeof tokens[i + 1] === 'number' ? [] : this.createObject();
96
+ cur[key] = next;
97
+ }
98
+ cur = next;
99
+ }
100
+ for (let i = 0; i < sources.length; i++) this.mergeInto(cur, sources[i]);
101
+ return target;
102
+ }
103
+ }
104
+
105
+ exports.Merger = Merger;
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Merger provides powerful and flexible object merging capabilities.
3
+ *
4
+ * It supports deep merging, multiple array merge modes, custom value handlers,
5
+ * and path-based merging. The implementation is optimized for performance
6
+ * using a non-recursive stack-based approach.
7
+ *
8
+ * @author Paul Köhler
9
+ * @license MIT
10
+ */
11
+ import { PathLike, PathOptions } from './path';
12
+ /**
13
+ * Defines how arrays should be merged when they exist in both target and source.
14
+ */
15
+ export declare const enum ArrayMode {
16
+ /** Replace the target array with the source array (default). */
17
+ Replace = "replace",
18
+ /** Keep the target array and ignore the source array. */
19
+ Keep = "keep",
20
+ /** Concatenate the source array to the target array. */
21
+ Concat = "concat",
22
+ /** Merge unique elements from both arrays. */
23
+ Unique = "unique"
24
+ }
25
+ /** Function signature for custom array merging logic. */
26
+ export type ArrayFn = (target: any[], source: any[]) => any[];
27
+ /** Function signature for custom value merging logic. */
28
+ export type ValueFn = (key: PropertyKey, targetVal: any, sourceVal: any) => any;
29
+ /** Configuration options for the Merger. */
30
+ export interface MergeOptions {
31
+ /** If true, existing properties in the target are not overwritten. */
32
+ protect?: boolean;
33
+ /** Whether to perform a deep merge (defaults to true). */
34
+ deep?: boolean;
35
+ /** If true, undefined values in the source will overwrite target values (defaults to false). */
36
+ mergeUndefined?: boolean;
37
+ /** If true, only existing objects in the target will be merged into. */
38
+ strict?: boolean;
39
+ /** Factory function for creating new objects during deep merge (defaults to Object.create( null )). */
40
+ createObject?: () => any;
41
+ /** The mode for merging arrays or a custom merge function. */
42
+ arrayMode?: ArrayMode | ArrayFn;
43
+ /** A custom function to handle specific value merging. */
44
+ valueFn?: ValueFn;
45
+ /** Options for the internal Path handler used in mergeAt. */
46
+ pathOptions?: PathOptions;
47
+ }
48
+ /**
49
+ * The Merger class implements the core logic for deep-merging objects.
50
+ * It is designed to be highly configurable and efficient.
51
+ */
52
+ export declare class Merger {
53
+ /** Whether to protect existing properties in the target. */
54
+ private readonly protect;
55
+ /** Whether to perform deep merging. */
56
+ private readonly deep;
57
+ /** Whether to allow merging of undefined values. */
58
+ private readonly mergeUndefined;
59
+ /** Whether to operate in strict mode (no creation of missing structures). */
60
+ private readonly strict;
61
+ /** Function to create new objects when needed. */
62
+ private readonly createObject;
63
+ /** Custom value merging function. */
64
+ private readonly valueFn?;
65
+ /** Internal function used for array merging. */
66
+ private readonly arrayFn;
67
+ /** Internal Path instance for path-based operations. */
68
+ private readonly path;
69
+ /**
70
+ * Creates a new Merger instance with the specified options.
71
+ *
72
+ * @param {MergeOptions} options - Configuration options for the Merger.
73
+ */
74
+ constructor(options?: MergeOptions);
75
+ /**
76
+ * Compiles an array merge mode or function into an ArrayFn.
77
+ *
78
+ * @param {ArrayMode | ArrayFn} [mode] - The mode or function to compile.
79
+ * @returns {ArrayFn} The compiled array merge function.
80
+ * @throws {Error} If an invalid mode is provided.
81
+ */
82
+ private compileArrayFn;
83
+ /**
84
+ * Checks if a key is considered "unsafe" for object manipulation.
85
+ *
86
+ * @param {any} key - The key to check.
87
+ * @returns {boolean} True if the key is unsafe, otherwise false.
88
+ */
89
+ private isUnsafeKey;
90
+ /**
91
+ * Performs the actual merging of a source object into a target object.
92
+ * This method uses a stack-based approach to avoid recursion for better performance.
93
+ *
94
+ * @param {any} target - The destination object.
95
+ * @param {any} source - The source object to merge from.
96
+ */
97
+ private mergeInto;
98
+ /**
99
+ * Merges one or more source objects into the specified target object.
100
+ *
101
+ * @template T
102
+ * @param {T} target - The target object to merge into.
103
+ * @param {...any[]} sources - One or more source objects.
104
+ * @returns {T} The updated target object.
105
+ */
106
+ merge<T>(target: T, ...sources: any[]): T;
107
+ /**
108
+ * Merges source objects into the target object at a specific path.
109
+ *
110
+ * @template T
111
+ * @param {T} target - The target object.
112
+ * @param {PathLike} path - The path inside the target where sources should be merged.
113
+ * @param {...any[]} sources - One or more source objects to merge.
114
+ * @returns {T} The updated target object.
115
+ */
116
+ mergeAt<T>(target: T, path: PathLike, ...sources: any[]): T;
117
+ }
@@ -0,0 +1,103 @@
1
+ import { Path } from './path.mjs';
2
+
3
+ class Merger {
4
+ protect;
5
+ deep;
6
+ mergeUndefined;
7
+ strict;
8
+ createObject;
9
+ valueFn;
10
+ arrayFn;
11
+ path;
12
+ constructor(options = {}) {
13
+ this.protect = !!options.protect;
14
+ this.deep = options.deep !== false;
15
+ this.mergeUndefined = !!options.mergeUndefined;
16
+ this.strict = !!options.strict;
17
+ this.createObject = options.createObject ?? (() => Object.create(null));
18
+ this.valueFn = options.valueFn;
19
+ this.arrayFn = this.compileArrayFn(options.arrayMode);
20
+ this.path = new Path(options.pathOptions);
21
+ }
22
+ compileArrayFn(mode) {
23
+ switch (mode ?? 'replace') {
24
+ case 'replace':
25
+ return (_, s) => s;
26
+ case 'keep':
27
+ return (t, _) => t;
28
+ case 'concat':
29
+ return (t, s) => t.concat(s);
30
+ case 'unique':
31
+ return (t, s) => {
32
+ const set = new Set(t);
33
+ for (let i = 0; i < s.length; i++) set.add(s[i]);
34
+ return Array.from(set);
35
+ };
36
+ }
37
+ if (typeof mode === 'function') return mode;
38
+ else throw new Error(`Invalid array merge mode: ${mode}`);
39
+ }
40
+ isUnsafeKey(key) {
41
+ return key === '__proto__' || key === 'constructor' || key === 'prototype';
42
+ }
43
+ mergeInto(target, source) {
44
+ if (source == null) return;
45
+ const stack = [[target, source]];
46
+ while (stack.length) {
47
+ const [t, s] = stack.pop();
48
+ for (const key in s) {
49
+ if (this.isUnsafeKey(key)) continue;
50
+ const sv = s[key],
51
+ tv = t[key];
52
+ if (sv === undefined && !this.mergeUndefined) continue;
53
+ if (this.protect && key in t) continue;
54
+ if (this.valueFn) {
55
+ const res = this.valueFn(key, tv, sv);
56
+ if (res !== undefined) {
57
+ t[key] = res;
58
+ continue;
59
+ }
60
+ }
61
+ if (Array.isArray(sv)) {
62
+ if (Array.isArray(tv)) t[key] = this.arrayFn(tv, sv);
63
+ else t[key] = this.arrayFn([], sv);
64
+ continue;
65
+ }
66
+ if (this.deep && sv && typeof sv === 'object') {
67
+ if (tv && typeof tv === 'object') stack.push([tv, sv]);
68
+ else {
69
+ if (this.strict) continue;
70
+ const next = this.createObject();
71
+ t[key] = next;
72
+ stack.push([next, sv]);
73
+ }
74
+ continue;
75
+ }
76
+ t[key] = sv;
77
+ }
78
+ }
79
+ }
80
+ merge(target, ...sources) {
81
+ for (let i = 0; i < sources.length; i++) this.mergeInto(target, sources[i]);
82
+ return target;
83
+ }
84
+ mergeAt(target, path, ...sources) {
85
+ const tokens = this.path.normalize(path).tokens;
86
+ let cur = target;
87
+ for (let i = 0; i < tokens.length; i++) {
88
+ const key = tokens[i];
89
+ if (this.isUnsafeKey(key)) return target;
90
+ let next = cur[key];
91
+ if (next == null) {
92
+ if (this.strict) return target;
93
+ next = typeof tokens[i + 1] === 'number' ? [] : this.createObject();
94
+ cur[key] = next;
95
+ }
96
+ cur = next;
97
+ }
98
+ for (let i = 0; i < sources.length; i++) this.mergeInto(cur, sources[i]);
99
+ return target;
100
+ }
101
+ }
102
+
103
+ export { Merger };
package/dist/path.cjs ADDED
@@ -0,0 +1,66 @@
1
+ 'use strict';
2
+
3
+ class Path {
4
+ cache;
5
+ maxCacheSize;
6
+ constructor(options = {}) {
7
+ if (options.cache !== false) this.cache = new Map();
8
+ this.maxCacheSize = options.maxCacheSize ?? 1000;
9
+ }
10
+ compile(path) {
11
+ if (!path) return Object.freeze({ tokens: [] });
12
+ if (this.cache) {
13
+ const cached = this.cache.get(path);
14
+ if (cached) return cached;
15
+ }
16
+ const tokens = [];
17
+ let i = 0,
18
+ key = '';
19
+ while (i < path.length) {
20
+ const c = path.charCodeAt(i);
21
+ if (c === 46) {
22
+ if (key) {
23
+ (tokens.push(key), (key = ''));
24
+ }
25
+ i++;
26
+ continue;
27
+ }
28
+ if (c === 91) {
29
+ if (key) {
30
+ (tokens.push(key), (key = ''));
31
+ }
32
+ i++;
33
+ const start = i;
34
+ while (i < path.length && path.charCodeAt(i) !== 93) i++;
35
+ const inner = path.slice(start, i);
36
+ const num = inner >>> 0;
37
+ if (String(num) === inner) tokens.push(num);
38
+ else tokens.push(inner);
39
+ i++;
40
+ continue;
41
+ }
42
+ ((key += path[i]), i++);
43
+ }
44
+ if (key) tokens.push(key);
45
+ const compiled = Object.freeze({ tokens });
46
+ if (this.cache) {
47
+ if (this.cache.size >= this.maxCacheSize) this.cache.clear();
48
+ this.cache.set(path, compiled);
49
+ }
50
+ return compiled;
51
+ }
52
+ isCompiled(path) {
53
+ return path && Array.isArray(path.tokens);
54
+ }
55
+ normalize(path) {
56
+ return typeof path === 'string' ? this.compile(path) : path;
57
+ }
58
+ clearCache() {
59
+ this.cache?.clear();
60
+ }
61
+ get size() {
62
+ return this.cache?.size ?? 0;
63
+ }
64
+ }
65
+
66
+ exports.Path = Path;
package/dist/path.d.ts ADDED
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Path provides utilities for compiling and normalizing object paths.
3
+ *
4
+ * It supports dot notation (e.g., 'a.b.c') and bracket notation (e.g., 'a[0].b').
5
+ * Efficient path compilation is achieved through optional caching.
6
+ *
7
+ * @author Paul Köhler
8
+ * @license MIT
9
+ */
10
+ /** Represents a single segment of a path, either a string key or a numeric index. */
11
+ export type PathToken = string | number;
12
+ /** Represents a compiled path, consisting of an array of tokens. */
13
+ export interface CompiledPath {
14
+ /** The tokens that make up the path. */
15
+ readonly tokens: PathToken[];
16
+ }
17
+ /** Types that can be treated as a path: a string or a pre-compiled path object. */
18
+ export type PathLike = string | CompiledPath;
19
+ /** Configuration options for path handling. */
20
+ export interface PathOptions {
21
+ /** Whether to enable caching of compiled paths (defaults to true). */
22
+ cache?: boolean;
23
+ /** The maximum number of paths to keep in the cache (defaults to 1000). */
24
+ maxCacheSize?: number;
25
+ }
26
+ /**
27
+ * The Path class handles the parsing and compilation of path strings into tokens.
28
+ * It provides a centralized way to work with object paths across the library.
29
+ */
30
+ export declare class Path {
31
+ /** Internal cache for compiled paths. */
32
+ private cache?;
33
+ /** The maximum allowed size of the path cache. */
34
+ private maxCacheSize;
35
+ /**
36
+ * Creates a new Path instance with the specified options.
37
+ *
38
+ * @param {PathOptions} [options={}] - Configuration options for the Path instance.
39
+ */
40
+ constructor(options?: PathOptions);
41
+ /**
42
+ * Compiles a path string into a CompiledPath object.
43
+ *
44
+ * @param {string} path - The path string to compile.
45
+ * @returns {CompiledPath} The compiled path object.
46
+ */
47
+ compile(path: string): CompiledPath;
48
+ /**
49
+ * Checks if a value is a CompiledPath object.
50
+ *
51
+ * @param {any} path - The value to check.
52
+ * @returns {path is CompiledPath} True if the value is a CompiledPath.
53
+ */
54
+ isCompiled(path: any): path is CompiledPath;
55
+ /**
56
+ * Normalizes a path-like value into a CompiledPath.
57
+ *
58
+ * @param {PathLike} path - The path-like value to normalize.
59
+ * @returns {CompiledPath} The normalized CompiledPath.
60
+ */
61
+ normalize(path: PathLike): CompiledPath;
62
+ /**
63
+ * Clears the internal path cache.
64
+ */
65
+ clearCache(): void;
66
+ /**
67
+ * Returns the current number of cached paths.
68
+ *
69
+ * @returns {number} The size of the cache.
70
+ */
71
+ get size(): number;
72
+ }
package/dist/path.mjs ADDED
@@ -0,0 +1,64 @@
1
+ class Path {
2
+ cache;
3
+ maxCacheSize;
4
+ constructor(options = {}) {
5
+ if (options.cache !== false) this.cache = new Map();
6
+ this.maxCacheSize = options.maxCacheSize ?? 1000;
7
+ }
8
+ compile(path) {
9
+ if (!path) return Object.freeze({ tokens: [] });
10
+ if (this.cache) {
11
+ const cached = this.cache.get(path);
12
+ if (cached) return cached;
13
+ }
14
+ const tokens = [];
15
+ let i = 0,
16
+ key = '';
17
+ while (i < path.length) {
18
+ const c = path.charCodeAt(i);
19
+ if (c === 46) {
20
+ if (key) {
21
+ (tokens.push(key), (key = ''));
22
+ }
23
+ i++;
24
+ continue;
25
+ }
26
+ if (c === 91) {
27
+ if (key) {
28
+ (tokens.push(key), (key = ''));
29
+ }
30
+ i++;
31
+ const start = i;
32
+ while (i < path.length && path.charCodeAt(i) !== 93) i++;
33
+ const inner = path.slice(start, i);
34
+ const num = inner >>> 0;
35
+ if (String(num) === inner) tokens.push(num);
36
+ else tokens.push(inner);
37
+ i++;
38
+ continue;
39
+ }
40
+ ((key += path[i]), i++);
41
+ }
42
+ if (key) tokens.push(key);
43
+ const compiled = Object.freeze({ tokens });
44
+ if (this.cache) {
45
+ if (this.cache.size >= this.maxCacheSize) this.cache.clear();
46
+ this.cache.set(path, compiled);
47
+ }
48
+ return compiled;
49
+ }
50
+ isCompiled(path) {
51
+ return path && Array.isArray(path.tokens);
52
+ }
53
+ normalize(path) {
54
+ return typeof path === 'string' ? this.compile(path) : path;
55
+ }
56
+ clearCache() {
57
+ this.cache?.clear();
58
+ }
59
+ get size() {
60
+ return this.cache?.size ?? 0;
61
+ }
62
+ }
63
+
64
+ export { Path };
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@komed3/deepmerge",
3
+ "description": "Fast and efficient deep object merging and manipulation library",
4
+ "license": "MIT",
5
+ "version": "1.0.0",
6
+ "author": {
7
+ "name": "komed3 (Paul Köhler)",
8
+ "email": "webmaster@komed3.de",
9
+ "url": "https://komed3.de"
10
+ },
11
+ "homepage": "https://github.com/komed3/deepmerge",
12
+ "keywords": [
13
+ "merge",
14
+ "object",
15
+ "manipulation",
16
+ "path",
17
+ "accessor",
18
+ "merger",
19
+ "deep-merge",
20
+ "deep-manipulation"
21
+ ],
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "git+https://github.com/komed3/deepmerge.git"
25
+ },
26
+ "bugs": {
27
+ "url": "https://github.com/komed3/deepmerge/issues"
28
+ },
29
+ "funding": {
30
+ "type": "ko-fi",
31
+ "url": "https://ko-fi.com/komed3"
32
+ },
33
+ "files": [
34
+ "dist",
35
+ "README",
36
+ "LICENSE"
37
+ ],
38
+ "engines": {
39
+ "node": ">=18.0.0"
40
+ },
41
+ "types": "dist/index.d.ts",
42
+ "main": "dist/index.cjs",
43
+ "module": "dist/index.mjs",
44
+ "browser": "dist/index.umd.min.js",
45
+ "jsdelivr": "dist/index.umd.min.js",
46
+ "unpkg": "dist/index.umd.min.js",
47
+ "scripts": {
48
+ "build": "rollup -c && tsc --emitDeclarationOnly",
49
+ "test": "node tests/access.js && node tests/merge.js && node tests/path.js"
50
+ },
51
+ "devDependencies": {
52
+ "@rollup/plugin-commonjs": "^29.0.2",
53
+ "@rollup/plugin-node-resolve": "^16.0.3",
54
+ "@rollup/plugin-terser": "^1.0.0",
55
+ "@rollup/plugin-typescript": "^12.3.0",
56
+ "rollup-plugin-cleanup": "^3.2.1",
57
+ "rollup-plugin-prettier": "^4.1.2",
58
+ "tslib": "^2.8.1",
59
+ "typescript": "^6.0.2"
60
+ }
61
+ }