hypertune
Version:
[Hypertune](https://www.hypertune.com/) is the most flexible platform for feature flags, A/B testing, analytics and app configuration. Built with full end-to-end type-safety, Git-style version control and local, synchronous, in-memory flag evaluation. Opt
73 lines (68 loc) • 2.15 kB
text/typescript
import { DeepPartial, ObjectValue, Value } from "../shared/types";
/**
* merge recursively merges sources into value, returning a value.
*/
export default function merge<T extends Value>(
value: T,
...sources: DeepPartial<T>[]
): T {
return sources.reduce((currentValue, source) => {
return mergeSource(currentValue, source);
}, value);
}
function mergeSource<T extends Value>(value: T, source: DeepPartial<T>): T {
if (source === null || source === undefined) {
return value; // No override specified so we just return the value.
}
if (Array.isArray(value) && Array.isArray(source)) {
return mergeArraySource(value, source) as T;
}
if (
!Array.isArray(value) &&
!Array.isArray(source) &&
typeof value === "object" &&
typeof source === "object"
) {
return mergeObjectSource(value, source) as T;
}
return source as T;
}
function mergeArraySource<T extends Value>(
value: T[],
source: DeepPartial<T>[]
): T[] {
// No null or undefined values indicate that we should override the list length.
const overrideLength = !source.some(
(item) => item === undefined || item === null
);
if (overrideLength) {
return source.map((itemOverride, index) =>
mergeSource(value[index], itemOverride)
);
}
// Add extra items to the end in case override array is longer than value array.
return (
[...value, ...source.slice(value.length)]
.map((itemValue, index) => mergeSource(itemValue as T, source[index]))
// Remove any null or undefined values that are beyond the original list length.
.filter((item) => item !== null && item !== undefined)
);
}
function mergeObjectSource<T extends ObjectValue>(
value: T,
override: DeepPartial<T>
): T {
return Object.fromEntries(
Object.entries(value)
.map(([fieldName, field]) => [
fieldName,
mergeSource(field, (override as T)[fieldName]),
])
.concat(
// Add any additional fields from the override to the object.
Object.entries(override as ObjectValue).filter(
([fieldName]) => !(fieldName in value)
)
)
) as T;
}