From c459d58579e6cf8e314e070ac124431059f9709f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Wed, 2 Nov 2022 12:52:22 +0100 Subject: [PATCH 01/12] fix(core): make `deepCopy` backward compatible `JSON.parse(JSON.stringify())` uses `.toJSON` when available. so should `deepCopy` --- packages/workflow/src/utils.ts | 29 ++++++++++++++++++---------- packages/workflow/test/utils.test.ts | 23 +++++++++++++++------- 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/packages/workflow/src/utils.ts b/packages/workflow/src/utils.ts index 87fa050a5ce39..67efef9215e85 100644 --- a/packages/workflow/src/utils.ts +++ b/packages/workflow/src/utils.ts @@ -1,7 +1,13 @@ -/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-argument */ -export const deepCopy = (source: T, hash = new WeakMap(), path = ''): T => { +/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return */ + +type Serializable = any & { toJSON?: (key?: any) => string }; + +export const deepCopy = ( + source: T, + hash = new WeakMap(), + path = '', +): T => { let clone: any; - let i: any; const hasOwnProp = Object.prototype.hasOwnProperty.bind(source); // Primitives & Null & Function if (typeof source !== 'object' || source === null || source instanceof Function) { @@ -10,23 +16,26 @@ export const deepCopy = (source: T, hash = new WeakMap(), path = ''): T => { if (hash.has(source)) { return hash.get(source); } - // Date - if (source instanceof Date) { - return new Date(source.getTime()) as T; - } // Array if (Array.isArray(source)) { clone = []; const len = source.length; - for (i = 0; i < len; i++) { - clone[i] = deepCopy(source[i], hash, path + `[${i as string}]`); + for (let i = 0; i < len; i++) { + clone[i] = deepCopy(source[i], hash, path + `[${i}]`); } return clone; } + // Date and other Serializable objects + const toJSON = (source as Serializable).toJSON; + if (typeof toJSON === 'function') { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + return toJSON.call(source) as T; + } + // Object clone = {}; hash.set(source, clone); - for (i in source) { + for (const i in source) { if (hasOwnProp(i)) { clone[i] = deepCopy((source as any)[i], hash, path + `.${i as string}`); } diff --git a/packages/workflow/test/utils.test.ts b/packages/workflow/test/utils.test.ts index bd83309bba9f8..9fe8a6d49000c 100644 --- a/packages/workflow/test/utils.test.ts +++ b/packages/workflow/test/utils.test.ts @@ -19,6 +19,11 @@ describe('jsonParse', () => { describe('deepCopy', () => { it('should deep copy an object', () => { + const serializable = { + x: 1, + y: 2, + toJSON: () => 'x:1,y:2', + }; const object = { deep: { props: { @@ -26,6 +31,7 @@ describe('deepCopy', () => { }, arr: [1, 2, 3], }, + serializable, arr: [ { prop: { @@ -34,17 +40,18 @@ describe('deepCopy', () => { }, ], func: () => {}, - date: new Date(), + date: new Date(1667389172201), undef: undefined, nil: null, bool: true, num: 1, }; const copy = deepCopy(object); - expect(copy).toEqual(object); expect(copy).not.toBe(object); expect(copy.arr).toEqual(object.arr); expect(copy.arr).not.toBe(object.arr); + expect(copy.date).toBe('2022-11-02T11:39:32.201Z'); + expect(copy.serializable).toBe(serializable.toJSON()); expect(copy.deep.props).toEqual(object.deep.props); expect(copy.deep.props).not.toBe(object.deep.props); }); @@ -65,7 +72,7 @@ describe('deepCopy', () => { }, ], func: () => {}, - date: new Date(), + date: new Date(1667389172201), undef: undefined, nil: null, bool: true, @@ -74,14 +81,16 @@ describe('deepCopy', () => { object.circular = object; object.deep.props.circular = object; - object.deep.arr.push(object) + object.deep.arr.push(object); const copy = deepCopy(object); - expect(copy).toEqual(object); expect(copy).not.toBe(object); expect(copy.arr).toEqual(object.arr); expect(copy.arr).not.toBe(object.arr); - expect(copy.deep.props).toEqual(object.deep.props); - expect(copy.deep.props).not.toBe(object.deep.props); + expect(copy.date).toBe('2022-11-02T11:39:32.201Z'); + expect(copy.deep.props.circular).toBe(copy); + expect(copy.deep.props.circular).not.toBe(object); + expect(copy.deep.arr.slice(-1)[0]).toBe(copy); + expect(copy.deep.arr.slice(-1)[0]).not.toBe(object); }); }); From 76dcc056866a703e697bfe4f4cc626f80746358f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Wed, 2 Nov 2022 13:45:04 +0100 Subject: [PATCH 02/12] fix(core): prevent double quotes on luxon datetimes (#4508) * :bug: Prevent double quotes on luxon datetimes * :zap: Generalize solution --- packages/nodes-base/nodes/Code/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nodes-base/nodes/Code/utils.ts b/packages/nodes-base/nodes/Code/utils.ts index 7d92c9eddbfc3..0b389af9c145d 100644 --- a/packages/nodes-base/nodes/Code/utils.ts +++ b/packages/nodes-base/nodes/Code/utils.ts @@ -21,7 +21,7 @@ export function isObject(maybe: unknown): maybe is { [key: string]: unknown } { } function isTraversable(maybe: unknown): maybe is IDataObject { - return isObject(maybe) && Object.keys(maybe).length > 0; + return isObject(maybe) && typeof maybe.toJSON !== 'function' && Object.keys(maybe).length > 0; } export type CodeNodeMode = 'runOnceForAllItems' | 'runOnceForEachItem'; From 6c60c72934b75a57c91c54dc8aa340dd2ff7f97f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Wed, 2 Nov 2022 13:47:14 +0100 Subject: [PATCH 03/12] update the types in packages/workflow/src/utils.ts --- packages/workflow/src/utils.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/workflow/src/utils.ts b/packages/workflow/src/utils.ts index 67efef9215e85..7cf9d02dc192c 100644 --- a/packages/workflow/src/utils.ts +++ b/packages/workflow/src/utils.ts @@ -1,12 +1,8 @@ /* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return */ -type Serializable = any & { toJSON?: (key?: any) => string }; +type Serializable = { toJSON?: () => string }; -export const deepCopy = ( - source: T, - hash = new WeakMap(), - path = '', -): T => { +export const deepCopy = (source: T, hash = new WeakMap(), path = ''): T => { let clone: any; const hasOwnProp = Object.prototype.hasOwnProperty.bind(source); // Primitives & Null & Function From b0e2b188d5c1486c1ef98853c0b28f791d18f863 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Wed, 2 Nov 2022 13:47:43 +0100 Subject: [PATCH 04/12] add `toJSON` check to NodeErrors.isTraversableObject as well --- packages/workflow/src/NodeErrors.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/workflow/src/NodeErrors.ts b/packages/workflow/src/NodeErrors.ts index ad921e5a33f07..834111088c0b1 100644 --- a/packages/workflow/src/NodeErrors.ts +++ b/packages/workflow/src/NodeErrors.ts @@ -176,8 +176,12 @@ abstract class NodeError extends ExecutionBaseError { // eslint-disable-next-line @typescript-eslint/no-explicit-any protected isTraversableObject(value: any): value is JsonObject { return ( + value && + typeof value === 'object' && + !Array.isArray(value) && + typeof value.toJSON !== 'function' && // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - value && typeof value === 'object' && !Array.isArray(value) && !!Object.keys(value).length + !!Object.keys(value).length ); } From 19b81392645d6705ee5840a359ee2d11932fbb59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Wed, 2 Nov 2022 14:23:25 +0100 Subject: [PATCH 05/12] move the toJSON check before the cyclic dependency check --- packages/workflow/src/utils.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/workflow/src/utils.ts b/packages/workflow/src/utils.ts index 7cf9d02dc192c..1d71b9353957a 100644 --- a/packages/workflow/src/utils.ts +++ b/packages/workflow/src/utils.ts @@ -9,6 +9,13 @@ export const deepCopy = (source: T, hash = new WeakMap(), path = ''): T => { if (typeof source !== 'object' || source === null || source instanceof Function) { return source; } + // Date and other Serializable objects + const toJSON = (source as Serializable).toJSON; + if (typeof toJSON === 'function') { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + return toJSON.call(source) as T; + } + // Break any cyclic dependencies if (hash.has(source)) { return hash.get(source); } @@ -21,13 +28,6 @@ export const deepCopy = (source: T, hash = new WeakMap(), path = ''): T => { } return clone; } - // Date and other Serializable objects - const toJSON = (source as Serializable).toJSON; - if (typeof toJSON === 'function') { - // eslint-disable-next-line @typescript-eslint/no-unsafe-call - return toJSON.call(source) as T; - } - // Object clone = {}; hash.set(source, clone); From e237dab3ed0e3e0bf5f6412a04214003a61086d4 Mon Sep 17 00:00:00 2001 From: Csaba Tuncsik Date: Wed, 2 Nov 2022 15:45:26 +0100 Subject: [PATCH 06/12] fix(core): keep backward compatibility in deepCopy by calling `toJSON` on objects that have it --- packages/workflow/src/utils.ts | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/packages/workflow/src/utils.ts b/packages/workflow/src/utils.ts index 1d71b9353957a..7e51695bd2e4b 100644 --- a/packages/workflow/src/utils.ts +++ b/packages/workflow/src/utils.ts @@ -1,37 +1,41 @@ -/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return */ - -type Serializable = { toJSON?: () => string }; - -export const deepCopy = (source: T, hash = new WeakMap(), path = ''): T => { +/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-argument */ +type Primitives = string | number | boolean | bigint | symbol | null | undefined; +export const deepCopy = string }) | Primitives>( + source: T, + hash = new WeakMap(), + path = '', +): T => { let clone: any; + let i: any; const hasOwnProp = Object.prototype.hasOwnProperty.bind(source); // Primitives & Null & Function - if (typeof source !== 'object' || source === null || source instanceof Function) { + if (typeof source !== 'object' || source === null || typeof source === 'function') { return source; } - // Date and other Serializable objects - const toJSON = (source as Serializable).toJSON; - if (typeof toJSON === 'function') { - // eslint-disable-next-line @typescript-eslint/no-unsafe-call - return toJSON.call(source) as T; + // TODO: remove this when other code parts not expecting objects with `.toJSON` method called + if (typeof source.toJSON === 'function') { + return source.toJSON() as T; } - // Break any cyclic dependencies if (hash.has(source)) { return hash.get(source); } + // Date + if (source instanceof Date) { + return new Date(source.getTime()) as T; + } // Array if (Array.isArray(source)) { clone = []; const len = source.length; - for (let i = 0; i < len; i++) { - clone[i] = deepCopy(source[i], hash, path + `[${i}]`); + for (i = 0; i < len; i++) { + clone[i] = deepCopy(source[i], hash, path + `[${i as string}]`); } return clone; } // Object clone = {}; hash.set(source, clone); - for (const i in source) { + for (i in source) { if (hasOwnProp(i)) { clone[i] = deepCopy((source as any)[i], hash, path + `.${i as string}`); } From 100a0f1f3d7ddac5425ccc8498381324f418d7a2 Mon Sep 17 00:00:00 2001 From: Csaba Tuncsik Date: Wed, 2 Nov 2022 16:32:32 +0100 Subject: [PATCH 07/12] fix(core): updating deepCopy typings --- packages/workflow/src/utils.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/workflow/src/utils.ts b/packages/workflow/src/utils.ts index 7e51695bd2e4b..00da4721eff37 100644 --- a/packages/workflow/src/utils.ts +++ b/packages/workflow/src/utils.ts @@ -1,12 +1,13 @@ /* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-argument */ -type Primitives = string | number | boolean | bigint | symbol | null | undefined; -export const deepCopy = string }) | Primitives>( +type Primitives = string | number | boolean | bigint | symbol; +type WithToJSON = (object | Date) & { toJSON?: () => string }; +export const deepCopy = ( source: T, hash = new WeakMap(), path = '', ): T => { let clone: any; - let i: any; + let i: string | number; const hasOwnProp = Object.prototype.hasOwnProperty.bind(source); // Primitives & Null & Function if (typeof source !== 'object' || source === null || typeof source === 'function') { @@ -28,7 +29,7 @@ export const deepCopy = string }) clone = []; const len = source.length; for (i = 0; i < len; i++) { - clone[i] = deepCopy(source[i], hash, path + `[${i as string}]`); + clone[i] = deepCopy(source[i], hash, path + `[${i}]`); } return clone; } @@ -37,7 +38,7 @@ export const deepCopy = string }) hash.set(source, clone); for (i in source) { if (hasOwnProp(i)) { - clone[i] = deepCopy((source as any)[i], hash, path + `.${i as string}`); + clone[i] = deepCopy((source as any)[i], hash, path + `.${i}`); } } return clone; From b8052c392193893e8ddd6c8d5c30068e5e9df9be Mon Sep 17 00:00:00 2001 From: Csaba Tuncsik Date: Wed, 2 Nov 2022 16:35:21 +0100 Subject: [PATCH 08/12] Revert "fix(core): updating deepCopy typings" This reverts commit 100a0f1f3d7ddac5425ccc8498381324f418d7a2. --- packages/workflow/src/utils.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/workflow/src/utils.ts b/packages/workflow/src/utils.ts index 00da4721eff37..7e51695bd2e4b 100644 --- a/packages/workflow/src/utils.ts +++ b/packages/workflow/src/utils.ts @@ -1,13 +1,12 @@ /* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-argument */ -type Primitives = string | number | boolean | bigint | symbol; -type WithToJSON = (object | Date) & { toJSON?: () => string }; -export const deepCopy = ( +type Primitives = string | number | boolean | bigint | symbol | null | undefined; +export const deepCopy = string }) | Primitives>( source: T, hash = new WeakMap(), path = '', ): T => { let clone: any; - let i: string | number; + let i: any; const hasOwnProp = Object.prototype.hasOwnProperty.bind(source); // Primitives & Null & Function if (typeof source !== 'object' || source === null || typeof source === 'function') { @@ -29,7 +28,7 @@ export const deepCopy = ( clone = []; const len = source.length; for (i = 0; i < len; i++) { - clone[i] = deepCopy(source[i], hash, path + `[${i}]`); + clone[i] = deepCopy(source[i], hash, path + `[${i as string}]`); } return clone; } @@ -38,7 +37,7 @@ export const deepCopy = ( hash.set(source, clone); for (i in source) { if (hasOwnProp(i)) { - clone[i] = deepCopy((source as any)[i], hash, path + `.${i}`); + clone[i] = deepCopy((source as any)[i], hash, path + `.${i as string}`); } } return clone; From b37159d0318e44c089c2f4e29bb96de0057fd89e Mon Sep 17 00:00:00 2001 From: Csaba Tuncsik Date: Wed, 2 Nov 2022 16:41:55 +0100 Subject: [PATCH 09/12] fix(core): temporarily removing Date cloning from deepCopy --- packages/workflow/src/utils.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/workflow/src/utils.ts b/packages/workflow/src/utils.ts index 7e51695bd2e4b..22b78319ac509 100644 --- a/packages/workflow/src/utils.ts +++ b/packages/workflow/src/utils.ts @@ -12,17 +12,14 @@ export const deepCopy = string }) if (typeof source !== 'object' || source === null || typeof source === 'function') { return source; } - // TODO: remove this when other code parts not expecting objects with `.toJSON` method called + // Date and other objects with toJSON method + // TODO: remove this when other code parts not expecting objects with `.toJSON` method called and add back checking for Date and cloning it properly if (typeof source.toJSON === 'function') { return source.toJSON() as T; } if (hash.has(source)) { return hash.get(source); } - // Date - if (source instanceof Date) { - return new Date(source.getTime()) as T; - } // Array if (Array.isArray(source)) { clone = []; From f3f84c7e5da6a77561ade6e267d0f6c8cb62bd0d Mon Sep 17 00:00:00 2001 From: Csaba Tuncsik Date: Wed, 2 Nov 2022 17:01:40 +0100 Subject: [PATCH 10/12] fix(core): updating deepCopy types --- packages/workflow/src/utils.ts | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/packages/workflow/src/utils.ts b/packages/workflow/src/utils.ts index 22b78319ac509..d8760238815ac 100644 --- a/packages/workflow/src/utils.ts +++ b/packages/workflow/src/utils.ts @@ -1,12 +1,10 @@ /* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-argument */ -type Primitives = string | number | boolean | bigint | symbol | null | undefined; +type Primitives = string | number | boolean | bigint | symbol; export const deepCopy = string }) | Primitives>( source: T, hash = new WeakMap(), path = '', ): T => { - let clone: any; - let i: any; const hasOwnProp = Object.prototype.hasOwnProperty.bind(source); // Primitives & Null & Function if (typeof source !== 'object' || source === null || typeof source === 'function') { @@ -22,19 +20,19 @@ export const deepCopy = string }) } // Array if (Array.isArray(source)) { - clone = []; + const clone = []; const len = source.length; - for (i = 0; i < len; i++) { - clone[i] = deepCopy(source[i], hash, path + `[${i as string}]`); + for (let i = 0; i < len; i++) { + clone[i] = deepCopy(source[i], hash, path + `[${i}]`); } - return clone; + return clone as T; } // Object - clone = {}; + const clone = {} as T; hash.set(source, clone); - for (i in source) { + for (const i in source) { if (hasOwnProp(i)) { - clone[i] = deepCopy((source as any)[i], hash, path + `.${i as string}`); + clone[i] = deepCopy((source as any)[i], hash, path + `.${i}`); } } return clone; From 3ec7f7b90e6b98ad2a9782eccfb1bdd01f8aa569 Mon Sep 17 00:00:00 2001 From: Csaba Tuncsik Date: Wed, 2 Nov 2022 17:08:59 +0100 Subject: [PATCH 11/12] fix(core): updating deepCopy --- packages/workflow/src/utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/workflow/src/utils.ts b/packages/workflow/src/utils.ts index d8760238815ac..c2cc97e92c0fb 100644 --- a/packages/workflow/src/utils.ts +++ b/packages/workflow/src/utils.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-argument */ -type Primitives = string | number | boolean | bigint | symbol; +type Primitives = string | number | boolean | bigint | symbol | null | undefined; export const deepCopy = string }) | Primitives>( source: T, hash = new WeakMap(), @@ -28,7 +28,7 @@ export const deepCopy = string }) return clone as T; } // Object - const clone = {} as T; + const clone = Object.create(Object.getPrototypeOf(source)); hash.set(source, clone); for (const i in source) { if (hasOwnProp(i)) { From 9ec7dc9743babfc758bf264acfcee6eba008f24d Mon Sep 17 00:00:00 2001 From: Csaba Tuncsik Date: Wed, 2 Nov 2022 17:27:53 +0100 Subject: [PATCH 12/12] fix(core): updating deepCopy get prototype of object --- packages/workflow/src/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/workflow/src/utils.ts b/packages/workflow/src/utils.ts index c2cc97e92c0fb..8201567c90c89 100644 --- a/packages/workflow/src/utils.ts +++ b/packages/workflow/src/utils.ts @@ -28,7 +28,7 @@ export const deepCopy = string }) return clone as T; } // Object - const clone = Object.create(Object.getPrototypeOf(source)); + const clone = Object.create(Object.getPrototypeOf({})); hash.set(source, clone); for (const i in source) { if (hasOwnProp(i)) {