initial commit

This commit is contained in:
ALIF\Alif Firmansyah
2026-08-27 17:57:30 +07:00
commit c1de380d88
5072 changed files with 1373737 additions and 0 deletions
+23
View File
@@ -0,0 +1,23 @@
export type Options<T> = {
props?: (keyof T)[];
nonenumerable?: boolean;
};
/**
* Copy (clone) an object and all its props recursively to get rid of any prop referenced of the
* original object. Arrays and the objects inside them are cloned as well.
*
* Nesting depth is limited only by available memory, and circular references are reproduced in the
* copy rather than followed forever. Two props pointing at the same object still point at one
* shared copy afterwards.
*
* @param target Target can be anything
* @param [options={}] See type {@link Options} for more details.
*
* - `{ props: ['key1'] }` will only copy the `key1` property. When using this you will need to cast
* the return type manually (in order to keep the TS implementation in here simple I didn't
* built a complex auto resolved type for those few cases people want to use this option)
* - `{ nonenumerable: true }` will copy all non-enumerable properties. Default is `{}`
*
* @returns The target with replaced values
*/
export declare function copy<T>(target: T, options?: Options<T>): T;
+125
View File
@@ -0,0 +1,125 @@
/**
* Assigns a prop the copy is keeping, preserving whether it was enumerable. Callers decide *whether*
* to keep a non-enumerable prop before getting here, so by this point the answer is always yes.
*/
function assignProp(carry, key, newVal, originalObject) {
if (Object.prototype.propertyIsEnumerable.call(originalObject, key)) {
carry[key] = newVal;
return;
}
Object.defineProperty(carry, key, {
value: newVal,
enumerable: false,
writable: true,
configurable: true,
});
}
/**
* Whether `value` is an object literal, as opposed to a class instance, a built-in like `Date`, an
* `arguments` object, or an object with a null prototype.
*
* The prototype check comes first because it is cheap and rejects almost everything; the tag check
* is only reached by values that are about to be cloned anyway.
*/
function isPlainObject(value) {
return (Object.getPrototypeOf(value) === Object.prototype &&
Object.prototype.toString.call(value) === '[object Object]');
}
/**
* Returns the clone that `value` should be replaced with, and queues it to be filled in later.
* Anything that isn't an array or a plain object is returned untouched.
*
* Reusing the clone already registered in `clones` is what makes circular and shared references
* work: the placeholder is registered before its contents exist, so a reference back to an ancestor
* resolves to it rather than walking forever.
*/
function cloneRef(value, clones, sources, dests) {
if (typeof value !== 'object' || value === null)
return value;
const array = Array.isArray(value);
if (!array && !isPlainObject(value))
return value;
const existing = clones.get(value);
if (existing !== undefined)
return existing;
const clone = array ? new Array(value.length) : {};
clones.set(value, clone);
sources.push(value);
dests.push(clone);
return clone;
}
/**
* Copy (clone) an object and all its props recursively to get rid of any prop referenced of the
* original object. Arrays and the objects inside them are cloned as well.
*
* Nesting depth is limited only by available memory, and circular references are reproduced in the
* copy rather than followed forever. Two props pointing at the same object still point at one
* shared copy afterwards.
*
* @param target Target can be anything
* @param [options={}] See type {@link Options} for more details.
*
* - `{ props: ['key1'] }` will only copy the `key1` property. When using this you will need to cast
* the return type manually (in order to keep the TS implementation in here simple I didn't
* built a complex auto resolved type for those few cases people want to use this option)
* - `{ nonenumerable: true }` will copy all non-enumerable properties. Default is `{}`
*
* @returns The target with replaced values
*/
export function copy(target, options = {}) {
if (typeof target !== 'object' || target === null)
return target;
const clones = new Map();
// An explicit work list rather than recursion, so nesting depth is bounded by the heap instead
// of the call stack. Deeply nested input used to throw a RangeError here.
const sources = [];
const dests = [];
const result = cloneRef(target, clones, sources, dests);
const onlyProps = Array.isArray(options.props) ? options.props : undefined;
const nonenumerable = options.nonenumerable === true;
while (sources.length) {
const source = sources.pop();
const dest = dests.pop();
if (Array.isArray(source)) {
for (let i = 0; i < source.length; i++) {
if (i in source)
dest[i] = cloneRef(source[i], clones, sources, dests);
}
continue;
}
if (nonenumerable) {
for (const key of Object.getOwnPropertyNames(source)) {
// Skip __proto__ properties to prevent prototype pollution
if (key === '__proto__')
continue;
if (onlyProps && !onlyProps.includes(key))
continue;
const newVal = cloneRef(source[key], clones, sources, dests);
assignProp(dest, key, newVal, source);
}
}
else {
// `for in` skips non-enumerable keys and allocates no key array, which is why it beats
// getOwnPropertyNames here. The own check is needed because it also walks the prototype.
for (const key in source) {
if (!Object.prototype.hasOwnProperty.call(source, key))
continue;
// Skip __proto__ properties to prevent prototype pollution
if (key === '__proto__')
continue;
if (onlyProps && !onlyProps.includes(key))
continue;
dest[key] = cloneRef(source[key], clones, sources, dests);
}
}
for (const key of Object.getOwnPropertySymbols(source)) {
if (onlyProps && !onlyProps.includes(key))
continue;
if (!nonenumerable && !Object.prototype.propertyIsEnumerable.call(source, key))
continue;
const newVal = cloneRef(source[key], clones, sources, dests);
assignProp(dest, key, newVal, source);
}
}
return result;
}