clonus
Version:
Ultimate object cloning library.
174 lines • 6.89 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.resolveCloner = exports.cloneObjectKeys = exports.makeBasicObjectClone = exports.makeContextualClone = exports.makeClone = exports.GetSelfCloner = exports.SelfClone = void 0;
const cloneConfig_1 = require("./cloneConfig");
const cloneContext_1 = require("./cloneContext");
const primitive_1 = require("./primitive");
exports.SelfClone = Symbol("selfClone");
exports.GetSelfCloner = Symbol("getSelfCloner");
/**
* Will clone {@link input}.
*
* @param input Input to be cloned.
* @param options Optional options to influence the cloning operations. See {@link CloneConfig}.
* @returns Cloned value/object of {@link input}.
*/
function makeClone(input, options) {
var _a, _b;
if ((0, primitive_1.isPrimitive)(input)) {
return input;
}
const config = (_a = options === null || options === void 0 ? void 0 : options.config) !== null && _a !== void 0 ? _a : cloneConfig_1.CloneConfig.makeDefault();
(_b = options === null || options === void 0 ? void 0 : options.setupConfig) === null || _b === void 0 ? void 0 : _b.call(options, config);
const context = cloneContext_1.CloneContext.make({ config });
return makeContextualClone(input, context);
}
exports.makeClone = makeClone;
/**
* Will clone {@link input}.
*
* @param input Input to be cloned.
* @param context Contextual information about cloning operation. See {@link CloneContext}.
* @returns Cloned value/object of {@link input}.
*/
function makeContextualClone(input, context) {
var _a, _b, _c;
if ((0, primitive_1.isPrimitive)(input)) {
return input;
}
if (typeof input == "function") {
return input;
}
/// input is object or bigint (still object)
const objInput = input;
const existing = (_a = context.cache.get(objInput)) === null || _a === void 0 ? void 0 : _a.clone;
if (existing != null) {
return existing;
}
const cloner = resolveCloner(input, context);
if (cloner) {
const clone = cloner.clone(input, context);
return clone;
}
const latestCloner = (_c = (_b = context === null || context === void 0 ? void 0 : context.config) === null || _b === void 0 ? void 0 : _b.cloners) === null || _c === void 0 ? void 0 : _c.fallback;
if (latestCloner === null || latestCloner === void 0 ? void 0 : latestCloner.canClone(input)) {
const clone = latestCloner.clone(input, context);
return clone;
}
const clone = makeBasicObjectClone(objInput, context);
return clone;
}
exports.makeContextualClone = makeContextualClone;
/**
* The basic implementation of object cloning operation.
*
* @param input Input (object) to be cloned.
* @param context Contextual information about cloning operation. See {@link CloneContext}.
* @returns Cloned object of {@link input}.
*/
function makeBasicObjectClone(input, context) {
const proto = Object.getPrototypeOf(input);
const ctor = proto.constructor;
const result = new ctor();
const item = {
clone: result
};
context.cache.set(input, item);
context.cache.set(result, item);
cloneObjectKeys(input, result, context);
return result;
}
exports.makeBasicObjectClone = makeBasicObjectClone;
function cloneObjectKeys(original, result, context) {
const origProps = getPropertyDescriptors(original);
const rsltProps = getPropertyDescriptors(result);
for (const origProp of Object.entries(origProps)) {
const key = origProp[0];
const desc = rsltProps[key];
const origValue = original[key];
if (desc && (!desc.writable && !desc.set)) {
const copyValue = result[key];
if (copyValue !== origValue) {
// the property is readonly, but cloned instance has different instance.
// its probably because of lazy-initialization
if (!context.cache.has(copyValue)) {
context.cache.set(origValue, copyValue);
context.cache.set(copyValue, copyValue);
cloneObjectKeys(origValue, copyValue, context);
}
}
continue;
}
const clone = makeContextualClone(origValue, context);
try {
result[key] = clone;
}
catch (error) {
throw error;
}
}
}
exports.cloneObjectKeys = cloneObjectKeys;
/**
* Will resolve proper cloner for specified {@link input}.
* If no cloner is eligible, the NULL is returned.
*
* @param input Input supposed to be cloned.
* @param context Contextual information about cloning operation. See {@link CloneContext}.
* @returns NULL or cloner supposed to be used to clone the specified {@link input}.
*/
function resolveCloner(input, context) {
var _a, _b;
const cloners = context === null || context === void 0 ? void 0 : context.config.cloners;
return (_b = (_a = findFirstEligibleCloner(input, cloners === null || cloners === void 0 ? void 0 : cloners.preClass)) !== null && _a !== void 0 ? _a : findFirstEligibleCloner(input, cloners === null || cloners === void 0 ? void 0 : cloners.class)) !== null && _b !== void 0 ? _b : findFirstEligibleCloner(input, cloners === null || cloners === void 0 ? void 0 : cloners.postClass);
}
exports.resolveCloner = resolveCloner;
function findFirstEligibleCloner(input, cloners) {
return cloners === null || cloners === void 0 ? void 0 : cloners.find(c => c.canClone(input));
}
function getKeys(input) {
return reflectMembers(input, (obj, x) => x);
}
function getPropertyDescriptors(input) {
const entries = reflectMembers(input, (obj, x) => [x, Reflect.getOwnPropertyDescriptor(obj, x)]);
return Object.fromEntries(entries);
}
function reflectMembers(input, func) {
const cache = [];
function recurse(x) {
if (x == null)
return;
switch (typeof x) {
case "object":
case "function":
// only objects and functions can be reflected
break;
default:
return;
}
const proto = Object.getPrototypeOf(x);
if (proto == null) {
// the "x" === Object
// we dont want get keys of Object
return;
}
const keys = Reflect.ownKeys(x)
.filter(x => x != "constructor");
if (keys.length > 0) {
const items = keys.map(key => func(x, key));
cache.push(items);
}
if (proto === Object
|| proto === Boolean
|| proto === Number
|| proto === BigInt
|| proto === String) {
return;
}
recurse(proto);
}
recurse(input);
const arr = cache.flat();
return new Array(...new Set(arr).values());
}
//# sourceMappingURL=makeClone.js.map