Files
twenty/packages/twenty-shared/src/utils/deepMerge.ts
T
Paul RastoinandGitHub 14537d74b4 FlatFieldMetadataType hashmap arch and enum validation (#13502)
# Introduction
- FlatFieldMetadataType validators hashmap
- Do not fail fast on validation but aggregate errors
- Implemented `enum` validation
- Plugged the new v2 dynamic call in the field metadata service v2

## What's next:
- Implem integration tests to make things run 🙃 
- migrate existing settings valdiation
- Finish the create object metadata service
- Handle update input transpilation and validation

## Open question
Should we implement, not covered validation ?, adding strictness now or
never.
This will be required by the import anw

## Discovered issue with cache
Currently the cache is not accurately typed, `fieldsById` map are not
storing any relations.
Which means the current transpilation tools are hitting undefined at
runtime
In the best of the world we will refactor the cache to be storing
`FlatObjectMetadata` and `FlatFieldMetadata` so we don't even have to
transpile them for validation and so on
But it would require to refactor the loaders that returns the cache to
the front on hit as FieldMetadataEntity, so we might land on a lighter
solution to rather add a new `getExistingFlatCache` that handles the
transpilation itself
About to do that in an other PR to be discussed with Coco
2025-07-31 14:54:39 +02:00

80 lines
2.2 KiB
TypeScript

/**
* Deep merges two objects or arrays recursively
* - Objects are merged by combining their properties
* - Arrays are merged by concatenating them
* - Primitive values from target override source
* - Null values from target are preserved
* - Undefined values from target are ignored
* - Date and RegExp objects are treated as primitives (replaced, not merged)
*
* @param source The source object to merge from
* @param target The target object to merge into
* @returns A new merged object
*/
export const deepMerge = <T extends object>(
source: Required<T>,
target: Required<T>,
): T => {
// Handle null/undefined cases
if (!source) return target as T;
if (!target) return source;
// Create a new object to avoid mutations
const output = { ...source };
// Iterate through all keys in target
Object.keys(target).forEach((key) => {
const sourceValue = source[key as keyof T];
const targetValue = target[key as keyof T];
// Skip undefined values in target
if (targetValue === undefined) {
return;
}
// Handle null values - explicitly assign them
if (targetValue === null) {
output[key as keyof T] = null as T[keyof T];
return;
}
// Handle arrays - concatenate them
if (Array.isArray(sourceValue) && Array.isArray(targetValue)) {
output[key as keyof T] = [...sourceValue, ...targetValue] as T[keyof T];
return;
}
// Handle Date and RegExp objects - treat them as primitives
if (
targetValue instanceof Date ||
targetValue instanceof RegExp ||
sourceValue instanceof Date ||
sourceValue instanceof RegExp
) {
output[key as keyof T] = targetValue as T[keyof T];
return;
}
// Handle nested objects - recurse
if (
sourceValue &&
targetValue &&
typeof sourceValue === 'object' &&
typeof targetValue === 'object' &&
!Array.isArray(sourceValue) &&
!Array.isArray(targetValue)
) {
output[key as keyof T] = deepMerge(
sourceValue as object,
targetValue as object,
) as T[keyof T];
return;
}
// For primitives
output[key as keyof T] = targetValue as T[keyof T];
});
return output;
};