Skip to content

Commit b20b7ce

Browse files
authored
feat(core): add daffMerge (#3441)
1 parent c461515 commit b20b7ce

9 files changed

+154
-0
lines changed

libs/core/src/merge/merge.spec.ts

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { daffMerge } from './merge';
2+
import {
3+
daffArrayConcatMerger,
4+
daffDictAssignMerger,
5+
} from './mergers/public_api';
6+
7+
describe('@daffodil/core | daffMerge', () => {
8+
const a = {
9+
ary: [1, 2],
10+
obj: {
11+
foo: 'bar',
12+
bar: 'baz',
13+
},
14+
fish: 'dumplings',
15+
};
16+
const b = {
17+
ary: [3, 4],
18+
obj: {
19+
foo: 5,
20+
},
21+
fish: 'tacos',
22+
};
23+
24+
describe('when there is no defined strategy', () => {
25+
it('should merge the dicts with a shallow merge', () => {
26+
expect(daffMerge([a, b])).toEqual({
27+
ary: [3, 4],
28+
obj: {
29+
foo: 5,
30+
},
31+
fish: 'tacos',
32+
});
33+
});
34+
});
35+
36+
describe('when there is a defined strategy', () => {
37+
it('should merge the dicts according to the passed strategy', () => {
38+
expect(daffMerge([a, b], {
39+
ary: daffArrayConcatMerger,
40+
obj: daffDictAssignMerger,
41+
fish: (aa, bb) => aa.toUpperCase().concat(bb),
42+
})).toEqual({
43+
ary: [1, 2, 3, 4],
44+
obj: {
45+
foo: 5,
46+
bar: 'baz',
47+
},
48+
fish: 'DUMPLINGStacos',
49+
});
50+
});
51+
});
52+
});

libs/core/src/merge/merge.ts

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { DaffMergeStrategy } from './strategy.type';
2+
3+
/**
4+
* Merges dictionaries with a specific strategy for handling collisions.
5+
* @see {@link DaffMergeStrategy}.
6+
*
7+
* @example Merging two dictionaries with predefined mergers
8+
*
9+
* ```ts
10+
* const a = {
11+
* ary: [1, 2],
12+
* obj: {foo: 5, bar: 10}
13+
* }
14+
* const a = {
15+
* ary: [3, 4],
16+
* obj: {foo: 6},
17+
* fish: 'tacos'
18+
* }
19+
* const result = daffMerge(
20+
* [a, b],
21+
* {
22+
* ary: daffArrayConcatMerger,
23+
* obj: daffDictAssignMerger
24+
* }
25+
* )
26+
* ```
27+
* the value of result would be:
28+
* ```ts
29+
* {
30+
* ary: [1, 2, 3, 4],
31+
* obj: {foo: 6, bar: 10},
32+
* fish: 'tacos'
33+
* }
34+
* ```
35+
*
36+
* @example Merging two dictionaries with predefined mergers
37+
*
38+
* ```ts
39+
* const a = {
40+
* ary: [1, 2],
41+
* obj: {foo: 5, bar: 10}
42+
* }
43+
* const a = {
44+
* ary: [3, 4],
45+
* obj: {foo: 6},
46+
* fish: 'tacos'
47+
* }
48+
* const result = daffMerge(
49+
* [a, b],
50+
* {
51+
* ary: daffArrayConcatMerger,
52+
* obj: daffDictAssignMerger
53+
* }
54+
* )
55+
* ```
56+
* the value of result would be:
57+
* ```ts
58+
* {
59+
* ary: [1, 2, 3, 4],
60+
* obj: {foo: 6, bar: 10},
61+
* fish: 'tacos'
62+
* }
63+
* ```
64+
*/
65+
export const daffMerge = <T extends Record<string, unknown> = Record<string, unknown>>(dicts: Array<T>, strategy: DaffMergeStrategy<T> = {}): T =>
66+
dicts.reduce((acc, dict) => {
67+
for (const k in dict) {
68+
if (Object.hasOwn(acc, k) && strategy[k]) {
69+
acc[k] = strategy[k](acc[k], dict[k]);
70+
} else {
71+
acc[k] = dict[k];
72+
}
73+
}
74+
75+
return acc;
76+
}, <T>{});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function daffArrayConcatMerger<T extends Array<unknown> = Array<unknown>>(a: T, b: T): T {
2+
return <T>a.concat(b);
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function daffDictAssignMerger<T extends Record<string, unknown> = Record<string, unknown>>(a: T, b: T): T {
2+
return Object.assign(a, b);
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './array-concat';
2+
export * from './dict-assign';
3+
export * from './type';

libs/core/src/merge/mergers/type.ts

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/**
2+
* A way to merge two fields into one.
3+
*/
4+
export type DaffMerger<T> = (a: T, b: T) => T;

libs/core/src/merge/public_api.ts

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './mergers/public_api';
2+
export * from './merge';
3+
export * from './strategy.type';

libs/core/src/merge/strategy.type.ts

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { DaffMerger } from './mergers/public_api';
2+
3+
/**
4+
* A strategy for merging objects of the same structure.
5+
* Each key can contain a merger function for handling collisions.
6+
*/
7+
export type DaffMergeStrategy<T extends Record<string, unknown> = Record<string, unknown>> = {
8+
[k in keyof T]?: DaffMerger<T[k]>
9+
};

libs/core/src/public_api.ts

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export * from './identifiable/public_api';
1616
export * from './filterable/public_api';
1717
export * from './filters/public_api';
1818
export * from './injection-tokens/public_api';
19+
export * from './merge/public_api';
1920

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

0 commit comments

Comments
 (0)