react-native-onyx
Version:
State management for React Native
178 lines (177 loc) • 8.55 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
/** Checks whether the given object is an object and not null/undefined. */
function isEmptyObject(obj) {
return typeof obj === 'object' && Object.keys(obj || {}).length === 0;
}
// Mostly copied from https://medium.com/@lubaka.a/how-to-remove-lodash-performance-improvement-b306669ad0e1
/**
* Checks whether the given value can be merged. It has to be an object, but not an array, RegExp or Date.
*/
function isMergeableObject(value) {
const isNonNullObject = value != null ? typeof value === 'object' : false;
return isNonNullObject && !(value instanceof RegExp) && !(value instanceof Date) && !Array.isArray(value);
}
/**
* Merges the source object into the target object.
* @param target - The target object.
* @param source - The source object.
* @param shouldRemoveNestedNulls - If true, null object values will be removed.
* @returns - The merged object.
*/
function mergeObject(target, source, shouldRemoveNestedNulls = true) {
const destination = {};
const targetObject = isMergeableObject(target) ? target : undefined;
// First we want to copy over all keys from the target into the destination object,
// in case "target" is a mergable object.
// If "shouldRemoveNestedNulls" is true, we want to remove null values from the merged object
// and therefore we need to omit keys where either the source or target value is null.
if (targetObject) {
// eslint-disable-next-line no-restricted-syntax, guard-for-in
for (const key in targetObject) {
const sourceValue = source === null || source === void 0 ? void 0 : source[key];
const targetValue = targetObject === null || targetObject === void 0 ? void 0 : targetObject[key];
// If "shouldRemoveNestedNulls" is true, we want to remove null values from the merged object.
// Therefore, if either target or source value is null, we want to prevent the key from being set.
// targetValue should techincally never be "undefined", because it will always be a value from cache or storage
// and we never set "undefined" there. Still, if there targetValue is undefined we don't want to set
// the key explicitly to prevent loose undefined values in objects in cache and storage.
const isSourceOrTargetNull = targetValue === undefined || targetValue === null || sourceValue === null;
const shouldOmitTargetKey = shouldRemoveNestedNulls && isSourceOrTargetNull;
if (!shouldOmitTargetKey) {
destination[key] = targetValue;
}
}
}
// After copying over all keys from the target object, we want to merge the source object into the destination object.
// eslint-disable-next-line no-restricted-syntax, guard-for-in
for (const key in source) {
const sourceValue = source === null || source === void 0 ? void 0 : source[key];
const targetValue = targetObject === null || targetObject === void 0 ? void 0 : targetObject[key];
// If undefined is passed as the source value for a key, we want to generally ignore it.
// If "shouldRemoveNestedNulls" is set to true and the source value is null,
// we don't want to set/merge the source value into the merged object.
const shouldIgnoreNullSourceValue = shouldRemoveNestedNulls && sourceValue === null;
const shouldOmitSourceKey = sourceValue === undefined || shouldIgnoreNullSourceValue;
if (!shouldOmitSourceKey) {
// If the source value is a mergable object, we want to merge it into the target value.
// If "shouldRemoveNestedNulls" is true, "fastMerge" will recursively
// remove nested null values from the merged object.
// If source value is any other value we need to set the source value it directly.
if (isMergeableObject(sourceValue)) {
// If the target value is null or undefined, we need to fallback to an empty object,
// so that we can still use "fastMerge" to merge the source value,
// to ensure that nested null values are removed from the merged object.
const targetValueWithFallback = (targetValue !== null && targetValue !== void 0 ? targetValue : {});
destination[key] = fastMerge(targetValueWithFallback, sourceValue, shouldRemoveNestedNulls);
}
else {
destination[key] = sourceValue;
}
}
}
return destination;
}
/**
* Merges two objects and removes null values if "shouldRemoveNestedNulls" is set to true
*
* We generally want to remove null values from objects written to disk and cache, because it decreases the amount of data stored in memory and on disk.
* On native, when merging an existing value with new changes, SQLite will use JSON_PATCH, which removes top-level nullish values.
* To be consistent with the behaviour for merge, we'll also want to remove null values for "set" operations.
*/
function fastMerge(target, source, shouldRemoveNestedNulls = true) {
// We have to ignore arrays and nullish values here,
// otherwise "mergeObject" will throw an error,
// because it expects an object as "source"
if (Array.isArray(source) || source === null || source === undefined) {
return source;
}
return mergeObject(target, source, shouldRemoveNestedNulls);
}
/** Deep removes the nested null values from the given value. */
function removeNestedNullValues(value) {
if (typeof value === 'object' && !Array.isArray(value)) {
const objectValue = value;
return fastMerge(objectValue, objectValue);
}
return value;
}
/** Formats the action name by uppercasing and adding the key if provided. */
function formatActionName(method, key) {
return key ? `${method.toUpperCase()}/${key}` : method.toUpperCase();
}
/** validate that the update and the existing value are compatible */
function checkCompatibilityWithExistingValue(value, existingValue) {
if (!existingValue || !value) {
return {
isCompatible: true,
};
}
const existingValueType = Array.isArray(existingValue) ? 'array' : 'non-array';
const newValueType = Array.isArray(value) ? 'array' : 'non-array';
if (existingValueType !== newValueType) {
return {
isCompatible: false,
existingValueType,
newValueType,
};
}
return {
isCompatible: true,
};
}
/**
* Filters an object based on a condition and an inclusion flag.
*
* @param obj - The object to filter.
* @param condition - The condition to apply.
* @param include - If true, include entries that match the condition; otherwise, exclude them.
* @returns The filtered object.
*/
function filterObject(obj, condition, include) {
const result = {};
const entries = Object.entries(obj);
for (const [key, value] of entries) {
let shouldInclude;
if (Array.isArray(condition)) {
shouldInclude = condition.includes(key);
}
else if (typeof condition === 'string') {
shouldInclude = key === condition;
}
else {
shouldInclude = condition([key, value]);
}
if (include ? shouldInclude : !shouldInclude) {
result[key] = value;
}
}
return result;
}
/**
* Picks entries from an object based on a condition.
*
* @param obj - The object to pick entries from.
* @param condition - The condition to determine which entries to pick.
* @returns The object containing only the picked entries.
*/
function pick(obj, condition) {
return filterObject(obj, condition, true);
}
/**
* Omits entries from an object based on a condition.
*
* @param obj - The object to omit entries from.
* @param condition - The condition to determine which entries to omit.
* @returns The object containing only the remaining entries after omission.
*/
function omit(obj, condition) {
return filterObject(obj, condition, false);
}
/**
* Whether the connect options has the `withOnyxInstance` property defined, that is, it's used by the `withOnyx()` HOC.
*/
function hasWithOnyxInstance(mapping) {
return 'withOnyxInstance' in mapping && mapping.withOnyxInstance;
}
exports.default = { isEmptyObject, fastMerge, formatActionName, removeNestedNullValues, checkCompatibilityWithExistingValue, pick, omit, hasWithOnyxInstance };
;