mikro-orm/mikro-orm

View on GitHub
packages/core/src/utils/clone.ts

Summary

Maintainability
C
1 day
Test Coverage
A
100%
/**
 * Inspired by https://github.com/pvorb/clone but simplified and never tries to
 * clone `EventEmitter`s to get around https://github.com/mikro-orm/mikro-orm/issues/2748
 * @internal
 */

import { EventEmitter } from 'events';
import { RawQueryFragment } from './RawQueryFragment';

export function clone<T>(parent: T, respectCustomCloneMethod = true): T {
  const allParents: unknown[] = [];
  const allChildren: unknown[] = [];

  function _clone(parent: any) {
    // cloning null always returns null
    if (parent === null) {
      return null;
    }

    const raw = RawQueryFragment.getKnownFragment(parent, false);

    if (raw && respectCustomCloneMethod) {
      return raw.clone();
    }

    if (typeof parent !== 'object' || parent instanceof EventEmitter) {
      return parent;
    }

    if (respectCustomCloneMethod && 'clone' in parent && typeof parent.clone === 'function') {
      return parent.clone();
    }

    let child: unknown;
    let proto;

    if (parent instanceof Map) {
      child = new Map();
    } else if (parent instanceof Set) {
      child = new Set();
    } else if (parent instanceof Promise) {
      child = new Promise((resolve, reject) => {
        parent.then(resolve.bind(null, _clone), reject.bind(null, _clone));
      });
    } else if (Array.isArray(parent)) {
      child = [];
    } else if (parent instanceof RegExp) {
      let flags = '';

      if (parent.global) {
        flags += 'g';
      }

      if (parent.ignoreCase) {
        flags += 'i';
      }

      if (parent.multiline) {
        flags += 'm';
      }

      child = new RegExp(parent.source, flags);

      if (parent.lastIndex) {
        (child as RegExp).lastIndex = parent.lastIndex;
      }
    } else if (parent instanceof Date) {
      child = new Date(parent.getTime());
    } else if (Buffer.isBuffer(parent)) {
      child = Buffer.allocUnsafe(parent.length);
      parent.copy(child as Buffer);
      return child;
    } else if (parent instanceof Error) {
      child = Object.create(parent);
    } else {
      proto = Object.getPrototypeOf(parent);
      child = Object.create(proto);
    }

    const index = allParents.indexOf(parent);

    if (index !== -1) {
      return allChildren[index];
    }

    allParents.push(parent);
    allChildren.push(child);

    if (parent instanceof Map) {
      parent.forEach((value: unknown, key: string) => {
        const keyChild = _clone(key);
        const valueChild = _clone(value);
        (child as any).set(keyChild, valueChild);
      });
    }

    if (parent instanceof Set) {
      parent.forEach((value: unknown) => {
        const entryChild = _clone(value);
        (child as any).add(entryChild);
      });
    }

    for (const i in parent) {
      let attrs;

      if (proto) {
        attrs = Object.getOwnPropertyDescriptor(proto, i);
      }

      if (attrs && attrs.set == null) {
        continue;
      }

      const raw = RawQueryFragment.getKnownFragment(i, false);

      if (raw && respectCustomCloneMethod) {
        const i2 = raw.clone().toString();
        (child as any)[i2] = _clone(parent[i]);
        continue;
      }

      (child as any)[i] = _clone(parent[i]);
    }

    if (Object.getOwnPropertySymbols) {
      const symbols = Object.getOwnPropertySymbols(parent);

      for (let i = 0; i < symbols.length; i++) {
        const symbol = symbols[i];
        const descriptor = Object.getOwnPropertyDescriptor(parent, symbol);

        /* istanbul ignore next */
        if (descriptor && !descriptor.enumerable) {
          continue;
        }

        (child as any)[symbol] = _clone(parent[symbol]);
      }
    }

    return child;
  }

  return _clone(parent);
}