diff --git a/packages/actor-memory-expression/src/memory_calculator.ts b/packages/actor-memory-expression/src/memory_calculator.ts index 842545671..4e4beb728 100644 --- a/packages/actor-memory-expression/src/memory_calculator.ts +++ b/packages/actor-memory-expression/src/memory_calculator.ts @@ -104,6 +104,7 @@ math.import( * * This is custom function designed to be injected into the math expression evaluator, * allowing expressions like `get(input, 'user.settings.memory', 512)` or `get(input, 'startUrls.length', 10)` to get array length. + * Only own properties are followed, so inherited members like `toString` resolve to `defaultVal`. * * @param obj The source object to search within. * @param path A dot-separated string representing the nested path (e.g., "input.payload.size"). @@ -111,7 +112,14 @@ math.import( * @returns The retrieved value, or `defaultVal` if the path is unreachable. */ const customGetFunc = (obj: any, path: string, defaultVal?: number) => { - return path.split('.').reduce((current, key) => current?.[key], obj) ?? defaultVal; + return ( + path + .split('.') + .reduce( + (current, key) => (current != null && Object.hasOwn(current, key) ? current[key] : undefined), + obj, + ) ?? defaultVal + ); }; /** diff --git a/test/memory_calculator.test.ts b/test/memory_calculator.test.ts index 2dc77b887..f7476e18f 100644 --- a/test/memory_calculator.test.ts +++ b/test/memory_calculator.test.ts @@ -55,6 +55,25 @@ describe('calculateDefaultMemoryFromExpression', () => { expect(result).toBe(512); }); + it('should resolve array and string length with get()', async () => { + const context = { input: { startUrls: ['a', 'b', 'c', 'd'], name: 'abcdefgh' }, runOptions: {} }; + expect(await calculateRunDynamicMemory("get(input, 'startUrls.length', 1) * 256", context)).toBe(1024); + expect(await calculateRunDynamicMemory("get(input, 'name.length', 1) * 256", context)).toBe(2048); + }); + + it.each([ + 'constructor', + 'constructor.constructor', + '__proto__', + 'toString', + 'nested.hasOwnProperty', + 'count.toFixed', + ])('should use get() default value for inherited property %s', async (path) => { + const context = { input: { nested: {}, count: 5 }, runOptions: {} }; + const result = await calculateRunDynamicMemory(`get(input, '${path}', 512)`, context); + expect(result).toBe(512); + }); + describe('matching string enum values with compareText()', () => { const expr = "compareText(input.dataOutput, 'full detail') == 0 ? 4096 : 1024";