UNPKG

clonus

Version:

Ultimate object cloning library.

174 lines 6.89 kB
"use strict"; 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