Skip to content

Commit

Permalink
feat(core): add daffMerge (#3441)
Browse files Browse the repository at this point in the history
  • Loading branch information
griest024 authored Jan 15, 2025
1 parent c461515 commit b20b7ce
Show file tree
Hide file tree
Showing 9 changed files with 154 additions and 0 deletions.
52 changes: 52 additions & 0 deletions libs/core/src/merge/merge.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { daffMerge } from './merge';
import {
daffArrayConcatMerger,
daffDictAssignMerger,
} from './mergers/public_api';

describe('@daffodil/core | daffMerge', () => {
const a = {
ary: [1, 2],
obj: {
foo: 'bar',
bar: 'baz',
},
fish: 'dumplings',
};
const b = {
ary: [3, 4],
obj: {
foo: 5,
},
fish: 'tacos',
};

describe('when there is no defined strategy', () => {
it('should merge the dicts with a shallow merge', () => {
expect(daffMerge([a, b])).toEqual({
ary: [3, 4],
obj: {
foo: 5,
},
fish: 'tacos',
});
});
});

describe('when there is a defined strategy', () => {
it('should merge the dicts according to the passed strategy', () => {
expect(daffMerge([a, b], {
ary: daffArrayConcatMerger,
obj: daffDictAssignMerger,
fish: (aa, bb) => aa.toUpperCase().concat(bb),
})).toEqual({
ary: [1, 2, 3, 4],
obj: {
foo: 5,
bar: 'baz',
},
fish: 'DUMPLINGStacos',
});
});
});
});
76 changes: 76 additions & 0 deletions libs/core/src/merge/merge.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { DaffMergeStrategy } from './strategy.type';

/**
* Merges dictionaries with a specific strategy for handling collisions.
* @see {@link DaffMergeStrategy}.
*
* @example Merging two dictionaries with predefined mergers
*
* ```ts
* const a = {
* ary: [1, 2],
* obj: {foo: 5, bar: 10}
* }
* const a = {
* ary: [3, 4],
* obj: {foo: 6},
* fish: 'tacos'
* }
* const result = daffMerge(
* [a, b],
* {
* ary: daffArrayConcatMerger,
* obj: daffDictAssignMerger
* }
* )
* ```
* the value of result would be:
* ```ts
* {
* ary: [1, 2, 3, 4],
* obj: {foo: 6, bar: 10},
* fish: 'tacos'
* }
* ```
*
* @example Merging two dictionaries with predefined mergers
*
* ```ts
* const a = {
* ary: [1, 2],
* obj: {foo: 5, bar: 10}
* }
* const a = {
* ary: [3, 4],
* obj: {foo: 6},
* fish: 'tacos'
* }
* const result = daffMerge(
* [a, b],
* {
* ary: daffArrayConcatMerger,
* obj: daffDictAssignMerger
* }
* )
* ```
* the value of result would be:
* ```ts
* {
* ary: [1, 2, 3, 4],
* obj: {foo: 6, bar: 10},
* fish: 'tacos'
* }
* ```
*/
export const daffMerge = <T extends Record<string, unknown> = Record<string, unknown>>(dicts: Array<T>, strategy: DaffMergeStrategy<T> = {}): T =>
dicts.reduce((acc, dict) => {
for (const k in dict) {
if (Object.hasOwn(acc, k) && strategy[k]) {
acc[k] = strategy[k](acc[k], dict[k]);
} else {
acc[k] = dict[k];
}
}

return acc;
}, <T>{});
3 changes: 3 additions & 0 deletions libs/core/src/merge/mergers/array-concat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function daffArrayConcatMerger<T extends Array<unknown> = Array<unknown>>(a: T, b: T): T {
return <T>a.concat(b);
}
3 changes: 3 additions & 0 deletions libs/core/src/merge/mergers/dict-assign.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function daffDictAssignMerger<T extends Record<string, unknown> = Record<string, unknown>>(a: T, b: T): T {
return Object.assign(a, b);
}
3 changes: 3 additions & 0 deletions libs/core/src/merge/mergers/public_api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './array-concat';
export * from './dict-assign';
export * from './type';
4 changes: 4 additions & 0 deletions libs/core/src/merge/mergers/type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/**
* A way to merge two fields into one.
*/
export type DaffMerger<T> = (a: T, b: T) => T;
3 changes: 3 additions & 0 deletions libs/core/src/merge/public_api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './mergers/public_api';
export * from './merge';
export * from './strategy.type';
9 changes: 9 additions & 0 deletions libs/core/src/merge/strategy.type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { DaffMerger } from './mergers/public_api';

/**
* A strategy for merging objects of the same structure.
* Each key can contain a merger function for handling collisions.
*/
export type DaffMergeStrategy<T extends Record<string, unknown> = Record<string, unknown>> = {
[k in keyof T]?: DaffMerger<T[k]>
};
1 change: 1 addition & 0 deletions libs/core/src/public_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export * from './identifiable/public_api';
export * from './filterable/public_api';
export * from './filters/public_api';
export * from './injection-tokens/public_api';
export * from './merge/public_api';

export { DaffOrderable } from './orderable/orderable';
export { MaybeAsync } from './async/maybe.type';

0 comments on commit b20b7ce

Please sign in to comment.