mutative
Version:
A JavaScript library for efficient immutable updates
92 lines (85 loc) • 3 kB
text/typescript
import type { Options, ProxyDraft } from '../interface';
import { dataTypes } from '../constant';
import { getValue, isDraft, isDraftable } from './draft';
function strictCopy(target: any) {
const descriptors = Object.getOwnPropertyDescriptors(target);
Reflect.ownKeys(descriptors).forEach((key: any) => {
const desc = descriptors[key];
// for freeze
if (!desc.writable) {
desc.writable = true;
desc.configurable = true;
}
if (desc.get || desc.set)
descriptors[key] = {
configurable: true,
writable: true,
enumerable: desc.enumerable,
value: target[key],
};
});
return Object.create(Object.getPrototypeOf(target), descriptors);
}
export function shallowCopy(original: any, options?: Options<any, any>) {
let markResult: any;
if (Array.isArray(original)) {
return Array.prototype.concat.call(original);
} else if (original instanceof Set) {
return new Set(original.values());
} else if (original instanceof Map) {
return new Map(original);
} else if (
options?.mark &&
((markResult = options.mark(original, dataTypes)),
markResult !== undefined) &&
markResult !== dataTypes.mutable
) {
if (markResult === dataTypes.immutable) {
return strictCopy(original);
} else if (typeof markResult === 'function') {
if (__DEV__ && (options.enablePatches || options.enableAutoFreeze)) {
throw new Error(
`You can't use mark and patches or auto freeze together.`
);
}
return markResult();
}
throw new Error(`Unsupported mark result: ${markResult}`);
} else if (
typeof original === 'object' &&
Object.getPrototypeOf(original) === Object.prototype
) {
// For best performance with shallow copies,
// don't use `Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));` by default.
const copy: Record<string | symbol, any> = {};
Object.keys(original).forEach((key) => {
copy![key] = original[key];
});
return copy;
} else {
throw new Error(
`Please check mark() to ensure that it is a stable marker draftable function.`
);
}
}
export function ensureShallowCopy(target: ProxyDraft) {
if (target.copy) return;
target.copy = shallowCopy(target.original, target.options)!;
}
function deepClone<T>(target: T): T;
function deepClone(target: any) {
if (!isDraftable(target)) return getValue(target);
if (Array.isArray(target)) return target.map(deepClone);
if (target instanceof Map)
return new Map(
Array.from(target.entries()).map(([k, v]) => [k, deepClone(v)])
);
if (target instanceof Set) return new Set(Array.from(target).map(deepClone));
const copy = Object.create(Object.getPrototypeOf(target));
for (const key in target) copy[key] = deepClone(target[key]);
return copy;
}
export function cloneIfNeeded<T>(target: T): T {
return isDraft(target) ? deepClone(target) : target;
}
export { deepClone };