diff --git a/src/defu.ts b/src/defu.ts index df34a1d..b5f91bf 100644 --- a/src/defu.ts +++ b/src/defu.ts @@ -5,9 +5,9 @@ function isObject (val: any) { } // Base function to apply defaults -function _defu (baseObj: T, defaults: any, merger?: Merger): T { +function _defu (baseObj: T, defaults: any, namespace: string = '.', merger?: Merger): T { if (!isObject(defaults)) { - return _defu(baseObj, {}, merger) + return _defu(baseObj, {}, namespace, merger) } const obj = Object.assign({}, defaults) @@ -23,14 +23,14 @@ function _defu (baseObj: T, defaults: any, merger?: Merger): T { continue } - if (merger && merger(obj, key, val)) { + if (merger && merger(obj, key, val, namespace)) { continue } if (Array.isArray(val) && Array.isArray(obj[key])) { obj[key] = obj[key].concat(val) } else if (isObject(val) && isObject(obj[key])) { - obj[key] = _defu(val, obj[key], merger) + obj[key] = _defu(val, obj[key], (namespace ? `${namespace}.` : '') + key.toString(), merger) } else { obj[key] = val } @@ -41,14 +41,14 @@ function _defu (baseObj: T, defaults: any, merger?: Merger): T { // Create defu wrapper with optional merger and multi arg support function extend (merger?: Merger): DefuFn { - return (...args) => args.reduce((p, c) => _defu(p, c, merger), {} as any) + return (...args) => args.reduce((p, c) => _defu(p, c, '', merger), {} as any) } // Basic version const defu = extend() as Defu // Custom version with function merge support -defu.fn = extend((obj, key, currentValue) => { +defu.fn = extend((obj, key, currentValue, _namespace) => { if (typeof obj[key] !== 'undefined' && typeof currentValue === 'function') { obj[key] = currentValue(obj[key]) return true @@ -56,7 +56,7 @@ defu.fn = extend((obj, key, currentValue) => { }) // Custom version with function merge support only for defined arrays -defu.arrayFn = extend((obj, key, currentValue) => { +defu.arrayFn = extend((obj, key, currentValue, _namespace) => { if (Array.isArray(obj[key]) && typeof currentValue === 'function') { obj[key] = currentValue(obj[key]) return true diff --git a/src/types.ts b/src/types.ts index e5cd347..b75ef1d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,7 +1,8 @@ export type Merger = ( obj: T, key: keyof T, - value: T[K] + value: T[K], + namespace: string ) => any; export type DefuFn = ( diff --git a/test/defu.test.ts b/test/defu.test.ts index 46e0fc9..258d864 100644 --- a/test/defu.test.ts +++ b/test/defu.test.ts @@ -25,7 +25,7 @@ describe('defu', () => { it('should copy nested values', () => { const result = defu({ a: { b: 'c' } }, { a: { d: 'e' } }) expect(result).toEqual({ - a: { b: 'c', d: 'e' }, + a: { b: 'c', d: 'e' } }) expectTypeOf(result).toEqualTypeOf<{ a: { b: string, d: string } }>() }) @@ -33,7 +33,7 @@ describe('defu', () => { it('should concat array values by default', () => { const result = defu({ array: ['b', 'c'] }, { array: ['a'] }) expect(result).toEqual({ - array: ['a', 'b', 'c'], + array: ['a', 'b', 'c'] }) expectTypeOf(result).toEqualTypeOf<{ array: string[] }>() }) @@ -52,7 +52,7 @@ describe('defu', () => { const result = defu({ a: fn }, { a: re }) expect(result).toEqual({ a: fn }) - expectTypeOf(result).toEqualTypeOf<{ a: (() => number) | RegExp }>() + expectTypeOf(result).toEqualTypeOf<{ a:(() => number) | RegExp }>() }) it('should handle non object first param', () => { @@ -74,7 +74,7 @@ describe('defu', () => { expect(result).toEqual({ a: 1, b: 2, - c: 3, + c: 3 }) expectTypeOf(result).toEqualTypeOf<{ a: string | number, b: string | number, c?: number }>() }) @@ -94,7 +94,7 @@ describe('defu', () => { // @ts-expect-error expect(defu(null, { foo: 1 }, false, 123, { bar: 2 })).toEqual({ foo: 1, - bar: 2, + bar: 2 }) }) @@ -113,7 +113,7 @@ describe('defu', () => { expect( defu.fn( { - ignore: (val) => val.filter((i) => i !== 'dist'), + ignore: val => val.filter(i => i !== 'dist'), num, ignored: num }, @@ -142,4 +142,20 @@ describe('defu', () => { num }) }) + + it('custom merger with namespace', () => { + const ext = defu.extend((obj, key, val, namespace) => { + // console.log({ obj, key, val, namespace }) + if (key === 'modules') { + // TODO: It is not possible to override types with extend() + // @ts-ignore + obj[key] = namespace + ':' + [...val, ...obj[key]].sort().join(',') + return true + } + }) + + const obj1 = { modules: ['A'], foo: { bar: { modules: ['X'] } } } + const obj2 = { modules: ['B'], foo: { bar: { modules: ['Y'] } } } + expect(ext(obj1, obj2)).toEqual({ modules: ':A,B', foo: { bar: { modules: 'foo.bar:X,Y' } } }) + }) })