From e515e555f4705409a196f0ce793809014f89d5fa Mon Sep 17 00:00:00 2001 From: Konstantina Blazhukova Date: Mon, 23 Jun 2025 21:19:46 +0300 Subject: [PATCH 1/7] fixes caching for nested objects and adds tests Signed-off-by: Konstantina Blazhukova --- .../src/lib/decorators/cache.decorator.ts | 4 +- .../relay/tests/lib/decorators/cache.spec.ts | 125 +++++++++++++++--- 2 files changed, 106 insertions(+), 23 deletions(-) diff --git a/packages/relay/src/lib/decorators/cache.decorator.ts b/packages/relay/src/lib/decorators/cache.decorator.ts index 9ccec1b15f..b38dec23fd 100644 --- a/packages/relay/src/lib/decorators/cache.decorator.ts +++ b/packages/relay/src/lib/decorators/cache.decorator.ts @@ -123,7 +123,9 @@ const generateCacheKey = (methodName: string, args: IArgument[]) => { if (value?.constructor?.name != 'RequestDetails') { if (value && typeof value === 'object') { for (const [key, innerValue] of Object.entries(value)) { - cacheKey += `_${key}_${innerValue}`; + const serializedValue = + typeof innerValue === 'object' && innerValue !== null ? JSON.stringify(innerValue) : innerValue; + cacheKey += `_${key}_${serializedValue}`; } continue; } diff --git a/packages/relay/tests/lib/decorators/cache.spec.ts b/packages/relay/tests/lib/decorators/cache.spec.ts index 42e0b82eec..5d9155577f 100644 --- a/packages/relay/tests/lib/decorators/cache.spec.ts +++ b/packages/relay/tests/lib/decorators/cache.spec.ts @@ -107,13 +107,13 @@ describe('cache decorator', () => { describe('shouldSkipCachingForSingleParams', () => { it('should return false if no skip rules are provided', () => { - const args = ['safe', 'latest'] as unknown as IArguments; + const args = ['safe', 'latest'] as unknown as IArguments[]; const result = __test__.__private.shouldSkipCachingForSingleParams(args, []); expect(result).to.be.false; }); it('should return false if argument exists but is not in skip values', () => { - const args = ['latest', 'earliest'] as unknown as IArguments; + const args = ['latest', 'earliest'] as unknown as IArguments[]; const params = [ { index: '0', value: 'pending' }, { index: '1', value: 'safe|finalized' }, @@ -123,21 +123,21 @@ describe('cache decorator', () => { }); it('should return true if a param at index matches any value in the pipe-separated list', () => { - const args = ['earliest', 'safe'] as unknown as IArguments; + const args = ['earliest', 'safe'] as unknown as IArguments[]; const params = [{ index: '1', value: 'pending|safe' }]; const result = __test__.__private.shouldSkipCachingForSingleParams(args, params); expect(result).to.be.true; }); it('should return true if the argument at index is missing (do not cache optional parameters)', () => { - const args = ['latest'] as unknown as IArguments; + const args = ['latest'] as unknown as IArguments[]; const params = [{ index: '1', value: 'pending|safe' }]; const result = __test__.__private.shouldSkipCachingForSingleParams(args, params); expect(result).to.be.true; }); it('should return true if the argument at index is explicitly undefined', () => { - const args = ['finalized', undefined] as unknown as IArguments; + const args = ['finalized', undefined] as unknown as IArguments[]; const params = [{ index: '1', value: 'pending|safe' }]; const result = __test__.__private.shouldSkipCachingForSingleParams(args, params); expect(result).to.be.true; @@ -146,13 +146,13 @@ describe('cache decorator', () => { describe('shouldSkipCachingForNamedParams', () => { it('should return false when no rules are provided', () => { - const args = [{ fromBlock: 'safe' }] as unknown as IArguments; + const args = [{ fromBlock: 'safe' }] as unknown as IArguments[]; const result = __test__.__private.shouldSkipCachingForNamedParams(args, []); expect(result).to.be.false; }); it('should return false if the field value does not match skip values', () => { - const args = [{ fromBlock: 'confirmed' }] as unknown as IArguments; + const args = [{ fromBlock: 'confirmed' }] as unknown as IArguments[]; const params = [ { index: '0', @@ -164,7 +164,7 @@ describe('cache decorator', () => { }); it('should return false if none of the multiple fields match', () => { - const args = [{ fromBlock: 'finalized', toBlock: 'earliest' }] as unknown as IArguments; + const args = [{ fromBlock: 'finalized', toBlock: 'earliest' }] as unknown as IArguments[]; const params = [ { index: '0', @@ -179,7 +179,7 @@ describe('cache decorator', () => { }); it('should return true if a field matches one of the skip values', () => { - const args = [{ fromBlock: 'pending' }] as unknown as IArguments; + const args = [{ fromBlock: 'pending' }] as unknown as IArguments[]; const params = [ { index: '0', @@ -191,7 +191,7 @@ describe('cache decorator', () => { }); it('should return true if multiple fields are specified and one matches', () => { - const args = [{ fromBlock: 'earliest', toBlock: 'latest' }] as unknown as IArguments; + const args = [{ fromBlock: 'earliest', toBlock: 'latest' }] as unknown as IArguments[]; const params = [ { index: '0', @@ -208,21 +208,21 @@ describe('cache decorator', () => { describe('generateCacheKey', () => { it('should return only the method name when args are empty', () => { - const args = [] as unknown as IArguments; + const args = [] as unknown as IArguments[]; const result = __test__.__private.generateCacheKey('eth_getBalance', args); expect(result).to.equal('eth_getBalance'); }); it('should append primitive arguments to the cache key', () => { - const args = ['0xabc', 'latest'] as unknown as IArguments; + const args = ['0xabc', 'latest'] as unknown as IArguments[]; const result = __test__.__private.generateCacheKey('eth_getBalance', args); expect(result).to.equal('eth_getBalance_0xabc_latest'); }); it('should append object key-value pairs to the cache key', () => { - const args = [{ fromBlock: 'earliest', toBlock: 5644 }] as unknown as IArguments; + const args = [{ fromBlock: 'earliest', toBlock: 5644 }] as unknown as IArguments[]; const result = __test__.__private.generateCacheKey('eth_getLogs', args); expect(result).to.equal('eth_getLogs_fromBlock_earliest_toBlock_5644'); @@ -233,38 +233,119 @@ describe('cache decorator', () => { constructor: { name: 'RequestDetails' }, someField: 'shouldBeIgnored', }; - const args = [mockRequestDetails, 'earliest'] as unknown as IArguments; + const args = [mockRequestDetails, 'earliest'] as unknown as IArguments[]; const result = __test__.__private.generateCacheKey('eth_call', args); expect(result).to.equal('eth_call_earliest'); }); it('should not skip null or undefined args', () => { - const args = [undefined, null, 'pending'] as unknown as IArguments; + const args = [undefined, null, 'pending'] as unknown as IArguments[]; const result = __test__.__private.generateCacheKey('eth_call', args); expect(result).to.equal('eth_call_undefined_null_pending'); }); it('should process multiple arguments correctly', () => { - const args = [{ fromBlock: '0xabc' }, 5644, 'safe'] as unknown as IArguments; + const args = [{ fromBlock: '0xabc' }, 5644, 'safe'] as unknown as IArguments[]; const result = __test__.__private.generateCacheKey('eth_getLogs', args); expect(result).to.equal('eth_getLogs_fromBlock_0xabc_5644_safe'); }); it('should work with mixed types including booleans and numbers', () => { - const args = [true, 42, { fromBlock: 'safe' }] as unknown as IArguments; + const args = [true, 42, { fromBlock: 'safe' }] as unknown as IArguments[]; const result = __test__.__private.generateCacheKey('custom_method', args); expect(result).to.equal('custom_method_true_42_fromBlock_safe'); }); + + it('should serialize nested objects using JSON.stringify', () => { + const args = [ + { + tracer: 'callTracer', + tracerConfig: { onlyTopCall: true }, + }, + ] as unknown as IArguments[]; + + const result = __test__.__private.generateCacheKey('debug_traceTransaction', args); + expect(result).to.equal('debug_traceTransaction_tracer_callTracer_tracerConfig_{"onlyTopCall":true}'); + }); + + it('should differentiate between different nested object values', () => { + const args1 = [ + { + tracer: 'callTracer', + tracerConfig: { onlyTopCall: true }, + }, + ] as unknown as IArguments[]; + + const args2 = [ + { + tracer: 'callTracer', + tracerConfig: { onlyTopCall: false }, + }, + ] as unknown as IArguments[]; + + const result1 = __test__.__private.generateCacheKey('debug_traceTransaction', args1); + const result2 = __test__.__private.generateCacheKey('debug_traceTransaction', args2); + + expect(result1).to.equal('debug_traceTransaction_tracer_callTracer_tracerConfig_{"onlyTopCall":true}'); + expect(result2).to.equal('debug_traceTransaction_tracer_callTracer_tracerConfig_{"onlyTopCall":false}'); + expect(result1).to.not.equal(result2); + }); + + it('should handle nested objects with multiple properties', () => { + const args = [ + { + config: { + timeout: 5000, + retries: 3, + options: { debug: true }, + }, + }, + ] as unknown as IArguments[]; + + const result = __test__.__private.generateCacheKey('test_method', args); + expect(result).to.equal('test_method_config_{"timeout":5000,"retries":3,"options":{"debug":true}}'); + }); + + it('should handle nested arrays in objects', () => { + const args = [ + { + filter: { + addresses: ['0x123', '0x456'], + topics: [null, '0xabc'], + }, + }, + ] as unknown as IArguments[]; + + const result = __test__.__private.generateCacheKey('eth_getLogs', args); + expect(result).to.equal('eth_getLogs_filter_{"addresses":["0x123","0x456"],"topics":[null,"0xabc"]}'); + }); + + it('should handle deeply nested objects', () => { + const args = [ + { + level1: { + level2: { + level3: { + value: 'deep', + }, + }, + }, + }, + ] as unknown as IArguments[]; + + const result = __test__.__private.generateCacheKey('deep_method', args); + expect(result).to.equal('deep_method_level1_{"level2":{"level3":{"value":"deep"}}}'); + }); }); describe('extractRequestDetails', () => { it('should return the RequestDetails instance if found in args', () => { const requestDetails = new RequestDetails({ requestId: 'abc123', ipAddress: '127.0.0.1' }); - const args = [5644, requestDetails, 'other'] as unknown as IArguments; + const args = [5644, requestDetails, 'other'] as unknown as IArguments[]; const result = __test__.__private.extractRequestDetails(args); expect(result.requestId).to.equal('abc123'); @@ -272,7 +353,7 @@ describe('cache decorator', () => { }); it('should return a new default RequestDetails if not found', () => { - const args = [5644, { fromBlock: 'pending' }, 'value'] as unknown as IArguments; + const args = [5644, { fromBlock: 'pending' }, 'value'] as unknown as IArguments[]; const result = __test__.__private.extractRequestDetails(args); expect(result.requestId).to.equal(''); @@ -280,7 +361,7 @@ describe('cache decorator', () => { }); it('should return new RequestDetails when args is empty', () => { - const args = [] as unknown as IArguments; + const args = [] as unknown as IArguments[]; const result = __test__.__private.extractRequestDetails(args); expect(result.requestId).to.equal(''); @@ -290,14 +371,14 @@ describe('cache decorator', () => { it('should return the first RequestDetails instance if multiple are present', () => { const rd1 = new RequestDetails({ requestId: 'first', ipAddress: '1.1.1.1' }); const rd2 = new RequestDetails({ requestId: 'second', ipAddress: '2.2.2.2' }); - const args = [rd1, rd2] as unknown as IArguments; + const args = [rd1, rd2] as unknown as IArguments[]; const result = __test__.__private.extractRequestDetails(args); expect(result).to.equal(rd1); }); it('should handle null or undefined values in args', () => { - const args = [undefined, null, 5644] as unknown as IArguments; + const args = [undefined, null, 5644] as unknown as IArguments[]; const result = __test__.__private.extractRequestDetails(args); expect(result.requestId).to.equal(''); From 310b936532d40feedb2821ccd7b1a38d4c2491b1 Mon Sep 17 00:00:00 2001 From: Konstantina Blazhukova Date: Thu, 26 Jun 2025 17:07:15 +0300 Subject: [PATCH 2/7] removes IArgument type and fixes tests Signed-off-by: Konstantina Blazhukova --- .../src/lib/decorators/cache.decorator.ts | 32 ++++---- .../relay/tests/lib/decorators/cache.spec.ts | 74 +++++++++---------- 2 files changed, 50 insertions(+), 56 deletions(-) diff --git a/packages/relay/src/lib/decorators/cache.decorator.ts b/packages/relay/src/lib/decorators/cache.decorator.ts index b38dec23fd..8b4b9870e5 100644 --- a/packages/relay/src/lib/decorators/cache.decorator.ts +++ b/packages/relay/src/lib/decorators/cache.decorator.ts @@ -26,13 +26,11 @@ interface CacheOptions { ttl?: number; } -type IArgument = Record; - /** * Iterates through the provided 'params' array and checks if any argument in 'args' at the specified 'index' * matches one of the pipe-separated values in 'value'. If a match is found, caching should be skipped. * - * @param args - The IArguments arguments object + * @param args - The arguments passed to the method in an array * @param params - An array of CacheSingleParam caching rules * @returns 'true' if any argument matches a rule and caching should be skipped; otherwise, 'false'. * @@ -42,7 +40,7 @@ type IArgument = Record; * value: 'pending|safe' * }] */ -const shouldSkipCachingForSingleParams = (args: IArgument[], params: CacheSingleParam[] = []): boolean => { +const shouldSkipCachingForSingleParams = (args: unknown[], params: CacheSingleParam[] = []): boolean => { for (const item of params) { const values = item.value.split('|'); if (values.indexOf(args[item.index]) > -1) { @@ -64,7 +62,7 @@ const shouldSkipCachingForSingleParams = (args: IArgument[], params: CacheSingle * a list of field-based skip conditions and checks if any of the fields in the input argument match any of the provided * values (supports multiple values via pipe '|' separators). * - * @param args - The function's arguments object (e.g., `IArguments`), where values are accessed by index. + * @param args - The function's arguments object, where values are accessed by index. * @param params - An array of `CacheNamedParams` defining which arguments and which fields to inspect. * @returns `true` if any field value matches a skip condition; otherwise, `false`. * @@ -78,7 +76,7 @@ const shouldSkipCachingForSingleParams = (args: IArgument[], params: CacheSingle * }], * }] */ -const shouldSkipCachingForNamedParams = (args: IArgument[], params: CacheNamedParams[] = []): boolean => { +const shouldSkipCachingForNamedParams = (args: unknown[], params: CacheNamedParams[] = []): boolean => { for (const { index, fields } of params) { const input = args[index]; @@ -90,7 +88,7 @@ const shouldSkipCachingForNamedParams = (args: IArgument[], params: CacheNamedPa // convert "latest|safe" to ["latest", "safe"] const allowedValues = value.split('|'); // get the actual value from the input object - const actualValue = (input as IArgument)[key]; + const actualValue = input[key]; // if the actual value is one of the values that should skip caching, return true if (allowedValues.includes(actualValue)) { @@ -111,22 +109,18 @@ const shouldSkipCachingForNamedParams = (args: IArgument[], params: CacheNamedPa * - Arguments of type `RequestDetails` are ignored in the key generation. * * @param methodName - The name of the method being cached. - * @param args - The arguments passed to the method (typically from `IArguments`). + * @param args - The arguments passed to the method. * @returns A string that uniquely identifies the method call for caching purposes. * * @example * generateCacheKey('getBlockByNumber', arguments); // should return getBlockByNumber_0x160c_false */ -const generateCacheKey = (methodName: string, args: IArgument[]) => { +const generateCacheKey = (methodName: string, args: unknown[]) => { let cacheKey: string = methodName; for (const [, value] of Object.entries(args)) { if (value?.constructor?.name != 'RequestDetails') { if (value && typeof value === 'object') { - for (const [key, innerValue] of Object.entries(value)) { - const serializedValue = - typeof innerValue === 'object' && innerValue !== null ? JSON.stringify(innerValue) : innerValue; - cacheKey += `_${key}_${serializedValue}`; - } + cacheKey += `_${JSON.stringify(value)}`; continue; } @@ -138,18 +132,18 @@ const generateCacheKey = (methodName: string, args: IArgument[]) => { }; /** - * This utility is used to scan through the provided arguments (typically from `IArguments`) + * This utility is used to scan through the provided arguments. * and return the first value that is identified as an instance of `RequestDetails`. * * If no such instance is found, it returns a new `RequestDetails` object with empty defaults. * - * @param args - The arguments object from a function (typically `IArguments`). + * @param args - The arguments from a function. * @returns The first found `RequestDetails` instance, or a new one with default values if none is found. */ -const extractRequestDetails = (args: IArgument): RequestDetails => { +const extractRequestDetails = (args: unknown[]): RequestDetails => { for (const [, value] of Object.entries(args)) { if (value?.constructor?.name === 'RequestDetails') { - return value; + return value as RequestDetails; } } @@ -177,7 +171,7 @@ export function cache(cacheService: CacheService, options: CacheOptions = {}) { return function (_target: any, _propertyKey: string, descriptor: PropertyDescriptor) { const method = descriptor.value; - descriptor.value = async function (...args: IArgument[]) { + descriptor.value = async function (...args: unknown[]) { const requestDetails = extractRequestDetails(args); const cacheKey = generateCacheKey(method.name, args); diff --git a/packages/relay/tests/lib/decorators/cache.spec.ts b/packages/relay/tests/lib/decorators/cache.spec.ts index 5d9155577f..6646931c58 100644 --- a/packages/relay/tests/lib/decorators/cache.spec.ts +++ b/packages/relay/tests/lib/decorators/cache.spec.ts @@ -107,13 +107,13 @@ describe('cache decorator', () => { describe('shouldSkipCachingForSingleParams', () => { it('should return false if no skip rules are provided', () => { - const args = ['safe', 'latest'] as unknown as IArguments[]; + const args = ['safe', 'latest'] as unknown[]; const result = __test__.__private.shouldSkipCachingForSingleParams(args, []); expect(result).to.be.false; }); it('should return false if argument exists but is not in skip values', () => { - const args = ['latest', 'earliest'] as unknown as IArguments[]; + const args = ['latest', 'earliest'] as unknown[]; const params = [ { index: '0', value: 'pending' }, { index: '1', value: 'safe|finalized' }, @@ -123,21 +123,21 @@ describe('cache decorator', () => { }); it('should return true if a param at index matches any value in the pipe-separated list', () => { - const args = ['earliest', 'safe'] as unknown as IArguments[]; + const args = ['earliest', 'safe'] as unknown[]; const params = [{ index: '1', value: 'pending|safe' }]; const result = __test__.__private.shouldSkipCachingForSingleParams(args, params); expect(result).to.be.true; }); it('should return true if the argument at index is missing (do not cache optional parameters)', () => { - const args = ['latest'] as unknown as IArguments[]; + const args = ['latest'] as unknown[]; const params = [{ index: '1', value: 'pending|safe' }]; const result = __test__.__private.shouldSkipCachingForSingleParams(args, params); expect(result).to.be.true; }); it('should return true if the argument at index is explicitly undefined', () => { - const args = ['finalized', undefined] as unknown as IArguments[]; + const args = ['finalized', undefined] as unknown[]; const params = [{ index: '1', value: 'pending|safe' }]; const result = __test__.__private.shouldSkipCachingForSingleParams(args, params); expect(result).to.be.true; @@ -146,13 +146,13 @@ describe('cache decorator', () => { describe('shouldSkipCachingForNamedParams', () => { it('should return false when no rules are provided', () => { - const args = [{ fromBlock: 'safe' }] as unknown as IArguments[]; + const args = [{ fromBlock: 'safe' }] as unknown[]; const result = __test__.__private.shouldSkipCachingForNamedParams(args, []); expect(result).to.be.false; }); it('should return false if the field value does not match skip values', () => { - const args = [{ fromBlock: 'confirmed' }] as unknown as IArguments[]; + const args = [{ fromBlock: 'confirmed' }] as unknown[]; const params = [ { index: '0', @@ -164,7 +164,7 @@ describe('cache decorator', () => { }); it('should return false if none of the multiple fields match', () => { - const args = [{ fromBlock: 'finalized', toBlock: 'earliest' }] as unknown as IArguments[]; + const args = [{ fromBlock: 'finalized', toBlock: 'earliest' }] as unknown[]; const params = [ { index: '0', @@ -179,7 +179,7 @@ describe('cache decorator', () => { }); it('should return true if a field matches one of the skip values', () => { - const args = [{ fromBlock: 'pending' }] as unknown as IArguments[]; + const args = [{ fromBlock: 'pending' }] as unknown[]; const params = [ { index: '0', @@ -191,7 +191,7 @@ describe('cache decorator', () => { }); it('should return true if multiple fields are specified and one matches', () => { - const args = [{ fromBlock: 'earliest', toBlock: 'latest' }] as unknown as IArguments[]; + const args = [{ fromBlock: 'earliest', toBlock: 'latest' }] as unknown[]; const params = [ { index: '0', @@ -208,24 +208,24 @@ describe('cache decorator', () => { describe('generateCacheKey', () => { it('should return only the method name when args are empty', () => { - const args = [] as unknown as IArguments[]; + const args = [] as unknown[]; const result = __test__.__private.generateCacheKey('eth_getBalance', args); expect(result).to.equal('eth_getBalance'); }); it('should append primitive arguments to the cache key', () => { - const args = ['0xabc', 'latest'] as unknown as IArguments[]; + const args = ['0xabc', 'latest'] as unknown[]; const result = __test__.__private.generateCacheKey('eth_getBalance', args); expect(result).to.equal('eth_getBalance_0xabc_latest'); }); it('should append object key-value pairs to the cache key', () => { - const args = [{ fromBlock: 'earliest', toBlock: 5644 }] as unknown as IArguments[]; + const args = [{ fromBlock: 'earliest', toBlock: 5644 }] as unknown[]; const result = __test__.__private.generateCacheKey('eth_getLogs', args); - expect(result).to.equal('eth_getLogs_fromBlock_earliest_toBlock_5644'); + expect(result).to.equal('eth_getLogs_{"fromBlock":"earliest","toBlock":5644}'); }); it('should ignore arguments with constructor name "RequestDetails"', () => { @@ -233,31 +233,31 @@ describe('cache decorator', () => { constructor: { name: 'RequestDetails' }, someField: 'shouldBeIgnored', }; - const args = [mockRequestDetails, 'earliest'] as unknown as IArguments[]; + const args = [mockRequestDetails, 'earliest'] as unknown[]; const result = __test__.__private.generateCacheKey('eth_call', args); expect(result).to.equal('eth_call_earliest'); }); it('should not skip null or undefined args', () => { - const args = [undefined, null, 'pending'] as unknown as IArguments[]; + const args = [undefined, null, 'pending'] as unknown[]; const result = __test__.__private.generateCacheKey('eth_call', args); expect(result).to.equal('eth_call_undefined_null_pending'); }); it('should process multiple arguments correctly', () => { - const args = [{ fromBlock: '0xabc' }, 5644, 'safe'] as unknown as IArguments[]; + const args = [{ fromBlock: '0xabc' }, 5644, 'safe'] as unknown[]; const result = __test__.__private.generateCacheKey('eth_getLogs', args); - expect(result).to.equal('eth_getLogs_fromBlock_0xabc_5644_safe'); + expect(result).to.equal('eth_getLogs_{"fromBlock":"0xabc"}_5644_safe'); }); it('should work with mixed types including booleans and numbers', () => { - const args = [true, 42, { fromBlock: 'safe' }] as unknown as IArguments[]; + const args = [true, 42, { fromBlock: 'safe' }] as unknown[]; const result = __test__.__private.generateCacheKey('custom_method', args); - expect(result).to.equal('custom_method_true_42_fromBlock_safe'); + expect(result).to.equal('custom_method_true_42_{"fromBlock":"safe"}'); }); it('should serialize nested objects using JSON.stringify', () => { @@ -266,10 +266,10 @@ describe('cache decorator', () => { tracer: 'callTracer', tracerConfig: { onlyTopCall: true }, }, - ] as unknown as IArguments[]; + ] as unknown[]; const result = __test__.__private.generateCacheKey('debug_traceTransaction', args); - expect(result).to.equal('debug_traceTransaction_tracer_callTracer_tracerConfig_{"onlyTopCall":true}'); + expect(result).to.equal('debug_traceTransaction_{"tracer":"callTracer","tracerConfig":{"onlyTopCall":true}}'); }); it('should differentiate between different nested object values', () => { @@ -278,20 +278,20 @@ describe('cache decorator', () => { tracer: 'callTracer', tracerConfig: { onlyTopCall: true }, }, - ] as unknown as IArguments[]; + ] as unknown[]; const args2 = [ { tracer: 'callTracer', tracerConfig: { onlyTopCall: false }, }, - ] as unknown as IArguments[]; + ] as unknown[]; const result1 = __test__.__private.generateCacheKey('debug_traceTransaction', args1); const result2 = __test__.__private.generateCacheKey('debug_traceTransaction', args2); - expect(result1).to.equal('debug_traceTransaction_tracer_callTracer_tracerConfig_{"onlyTopCall":true}'); - expect(result2).to.equal('debug_traceTransaction_tracer_callTracer_tracerConfig_{"onlyTopCall":false}'); + expect(result1).to.equal('debug_traceTransaction_{"tracer":"callTracer","tracerConfig":{"onlyTopCall":true}}'); + expect(result2).to.equal('debug_traceTransaction_{"tracer":"callTracer","tracerConfig":{"onlyTopCall":false}}'); expect(result1).to.not.equal(result2); }); @@ -304,10 +304,10 @@ describe('cache decorator', () => { options: { debug: true }, }, }, - ] as unknown as IArguments[]; + ] as unknown[]; const result = __test__.__private.generateCacheKey('test_method', args); - expect(result).to.equal('test_method_config_{"timeout":5000,"retries":3,"options":{"debug":true}}'); + expect(result).to.equal('test_method_{"config":{"timeout":5000,"retries":3,"options":{"debug":true}}}'); }); it('should handle nested arrays in objects', () => { @@ -318,10 +318,10 @@ describe('cache decorator', () => { topics: [null, '0xabc'], }, }, - ] as unknown as IArguments[]; + ] as unknown[]; const result = __test__.__private.generateCacheKey('eth_getLogs', args); - expect(result).to.equal('eth_getLogs_filter_{"addresses":["0x123","0x456"],"topics":[null,"0xabc"]}'); + expect(result).to.equal('eth_getLogs_{"filter":{"addresses":["0x123","0x456"],"topics":[null,"0xabc"]}}'); }); it('should handle deeply nested objects', () => { @@ -335,17 +335,17 @@ describe('cache decorator', () => { }, }, }, - ] as unknown as IArguments[]; + ] as unknown[]; const result = __test__.__private.generateCacheKey('deep_method', args); - expect(result).to.equal('deep_method_level1_{"level2":{"level3":{"value":"deep"}}}'); + expect(result).to.equal('deep_method_{"level1":{"level2":{"level3":{"value":"deep"}}}}'); }); }); describe('extractRequestDetails', () => { it('should return the RequestDetails instance if found in args', () => { const requestDetails = new RequestDetails({ requestId: 'abc123', ipAddress: '127.0.0.1' }); - const args = [5644, requestDetails, 'other'] as unknown as IArguments[]; + const args = [5644, requestDetails, 'other'] as unknown[]; const result = __test__.__private.extractRequestDetails(args); expect(result.requestId).to.equal('abc123'); @@ -353,7 +353,7 @@ describe('cache decorator', () => { }); it('should return a new default RequestDetails if not found', () => { - const args = [5644, { fromBlock: 'pending' }, 'value'] as unknown as IArguments[]; + const args = [5644, { fromBlock: 'pending' }, 'value'] as unknown[]; const result = __test__.__private.extractRequestDetails(args); expect(result.requestId).to.equal(''); @@ -361,7 +361,7 @@ describe('cache decorator', () => { }); it('should return new RequestDetails when args is empty', () => { - const args = [] as unknown as IArguments[]; + const args = [] as unknown[]; const result = __test__.__private.extractRequestDetails(args); expect(result.requestId).to.equal(''); @@ -371,14 +371,14 @@ describe('cache decorator', () => { it('should return the first RequestDetails instance if multiple are present', () => { const rd1 = new RequestDetails({ requestId: 'first', ipAddress: '1.1.1.1' }); const rd2 = new RequestDetails({ requestId: 'second', ipAddress: '2.2.2.2' }); - const args = [rd1, rd2] as unknown as IArguments[]; + const args = [rd1, rd2] as unknown[]; const result = __test__.__private.extractRequestDetails(args); expect(result).to.equal(rd1); }); it('should handle null or undefined values in args', () => { - const args = [undefined, null, 5644] as unknown as IArguments[]; + const args = [undefined, null, 5644] as unknown[]; const result = __test__.__private.extractRequestDetails(args); expect(result.requestId).to.equal(''); From 2881811fb199b06a212b11781457efa2fc6f54fb Mon Sep 17 00:00:00 2001 From: Konstantina Blazhukova Date: Fri, 27 Jun 2025 12:14:09 +0300 Subject: [PATCH 3/7] removes casting Signed-off-by: Konstantina Blazhukova --- .../relay/tests/lib/decorators/cache.spec.ts | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/packages/relay/tests/lib/decorators/cache.spec.ts b/packages/relay/tests/lib/decorators/cache.spec.ts index 6646931c58..ef35e31498 100644 --- a/packages/relay/tests/lib/decorators/cache.spec.ts +++ b/packages/relay/tests/lib/decorators/cache.spec.ts @@ -107,13 +107,13 @@ describe('cache decorator', () => { describe('shouldSkipCachingForSingleParams', () => { it('should return false if no skip rules are provided', () => { - const args = ['safe', 'latest'] as unknown[]; + const args = ['safe', 'latest']; const result = __test__.__private.shouldSkipCachingForSingleParams(args, []); expect(result).to.be.false; }); it('should return false if argument exists but is not in skip values', () => { - const args = ['latest', 'earliest'] as unknown[]; + const args = ['latest', 'earliest']; const params = [ { index: '0', value: 'pending' }, { index: '1', value: 'safe|finalized' }, @@ -123,21 +123,21 @@ describe('cache decorator', () => { }); it('should return true if a param at index matches any value in the pipe-separated list', () => { - const args = ['earliest', 'safe'] as unknown[]; + const args = ['earliest', 'safe']; const params = [{ index: '1', value: 'pending|safe' }]; const result = __test__.__private.shouldSkipCachingForSingleParams(args, params); expect(result).to.be.true; }); it('should return true if the argument at index is missing (do not cache optional parameters)', () => { - const args = ['latest'] as unknown[]; + const args = ['latest']; const params = [{ index: '1', value: 'pending|safe' }]; const result = __test__.__private.shouldSkipCachingForSingleParams(args, params); expect(result).to.be.true; }); it('should return true if the argument at index is explicitly undefined', () => { - const args = ['finalized', undefined] as unknown[]; + const args = ['finalized', undefined]; const params = [{ index: '1', value: 'pending|safe' }]; const result = __test__.__private.shouldSkipCachingForSingleParams(args, params); expect(result).to.be.true; @@ -146,13 +146,13 @@ describe('cache decorator', () => { describe('shouldSkipCachingForNamedParams', () => { it('should return false when no rules are provided', () => { - const args = [{ fromBlock: 'safe' }] as unknown[]; + const args = [{ fromBlock: 'safe' }]; const result = __test__.__private.shouldSkipCachingForNamedParams(args, []); expect(result).to.be.false; }); it('should return false if the field value does not match skip values', () => { - const args = [{ fromBlock: 'confirmed' }] as unknown[]; + const args = [{ fromBlock: 'confirmed' }]; const params = [ { index: '0', @@ -164,7 +164,7 @@ describe('cache decorator', () => { }); it('should return false if none of the multiple fields match', () => { - const args = [{ fromBlock: 'finalized', toBlock: 'earliest' }] as unknown[]; + const args = [{ fromBlock: 'finalized', toBlock: 'earliest' }]; const params = [ { index: '0', @@ -179,7 +179,7 @@ describe('cache decorator', () => { }); it('should return true if a field matches one of the skip values', () => { - const args = [{ fromBlock: 'pending' }] as unknown[]; + const args = [{ fromBlock: 'pending' }]; const params = [ { index: '0', @@ -191,7 +191,7 @@ describe('cache decorator', () => { }); it('should return true if multiple fields are specified and one matches', () => { - const args = [{ fromBlock: 'earliest', toBlock: 'latest' }] as unknown[]; + const args = [{ fromBlock: 'earliest', toBlock: 'latest' }]; const params = [ { index: '0', @@ -208,21 +208,21 @@ describe('cache decorator', () => { describe('generateCacheKey', () => { it('should return only the method name when args are empty', () => { - const args = [] as unknown[]; + const args = []; const result = __test__.__private.generateCacheKey('eth_getBalance', args); expect(result).to.equal('eth_getBalance'); }); it('should append primitive arguments to the cache key', () => { - const args = ['0xabc', 'latest'] as unknown[]; + const args = ['0xabc', 'latest']; const result = __test__.__private.generateCacheKey('eth_getBalance', args); expect(result).to.equal('eth_getBalance_0xabc_latest'); }); it('should append object key-value pairs to the cache key', () => { - const args = [{ fromBlock: 'earliest', toBlock: 5644 }] as unknown[]; + const args = [{ fromBlock: 'earliest', toBlock: 5644 }]; const result = __test__.__private.generateCacheKey('eth_getLogs', args); expect(result).to.equal('eth_getLogs_{"fromBlock":"earliest","toBlock":5644}'); @@ -233,28 +233,28 @@ describe('cache decorator', () => { constructor: { name: 'RequestDetails' }, someField: 'shouldBeIgnored', }; - const args = [mockRequestDetails, 'earliest'] as unknown[]; + const args = [mockRequestDetails, 'earliest']; const result = __test__.__private.generateCacheKey('eth_call', args); expect(result).to.equal('eth_call_earliest'); }); it('should not skip null or undefined args', () => { - const args = [undefined, null, 'pending'] as unknown[]; + const args = [undefined, null, 'pending']; const result = __test__.__private.generateCacheKey('eth_call', args); expect(result).to.equal('eth_call_undefined_null_pending'); }); it('should process multiple arguments correctly', () => { - const args = [{ fromBlock: '0xabc' }, 5644, 'safe'] as unknown[]; + const args = [{ fromBlock: '0xabc' }, 5644, 'safe']; const result = __test__.__private.generateCacheKey('eth_getLogs', args); expect(result).to.equal('eth_getLogs_{"fromBlock":"0xabc"}_5644_safe'); }); it('should work with mixed types including booleans and numbers', () => { - const args = [true, 42, { fromBlock: 'safe' }] as unknown[]; + const args = [true, 42, { fromBlock: 'safe' }]; const result = __test__.__private.generateCacheKey('custom_method', args); expect(result).to.equal('custom_method_true_42_{"fromBlock":"safe"}'); @@ -266,7 +266,7 @@ describe('cache decorator', () => { tracer: 'callTracer', tracerConfig: { onlyTopCall: true }, }, - ] as unknown[]; + ]; const result = __test__.__private.generateCacheKey('debug_traceTransaction', args); expect(result).to.equal('debug_traceTransaction_{"tracer":"callTracer","tracerConfig":{"onlyTopCall":true}}'); @@ -278,14 +278,14 @@ describe('cache decorator', () => { tracer: 'callTracer', tracerConfig: { onlyTopCall: true }, }, - ] as unknown[]; + ]; const args2 = [ { tracer: 'callTracer', tracerConfig: { onlyTopCall: false }, }, - ] as unknown[]; + ]; const result1 = __test__.__private.generateCacheKey('debug_traceTransaction', args1); const result2 = __test__.__private.generateCacheKey('debug_traceTransaction', args2); @@ -304,7 +304,7 @@ describe('cache decorator', () => { options: { debug: true }, }, }, - ] as unknown[]; + ]; const result = __test__.__private.generateCacheKey('test_method', args); expect(result).to.equal('test_method_{"config":{"timeout":5000,"retries":3,"options":{"debug":true}}}'); @@ -318,7 +318,7 @@ describe('cache decorator', () => { topics: [null, '0xabc'], }, }, - ] as unknown[]; + ]; const result = __test__.__private.generateCacheKey('eth_getLogs', args); expect(result).to.equal('eth_getLogs_{"filter":{"addresses":["0x123","0x456"],"topics":[null,"0xabc"]}}'); @@ -335,7 +335,7 @@ describe('cache decorator', () => { }, }, }, - ] as unknown[]; + ]; const result = __test__.__private.generateCacheKey('deep_method', args); expect(result).to.equal('deep_method_{"level1":{"level2":{"level3":{"value":"deep"}}}}'); @@ -345,7 +345,7 @@ describe('cache decorator', () => { describe('extractRequestDetails', () => { it('should return the RequestDetails instance if found in args', () => { const requestDetails = new RequestDetails({ requestId: 'abc123', ipAddress: '127.0.0.1' }); - const args = [5644, requestDetails, 'other'] as unknown[]; + const args = [5644, requestDetails, 'other']; const result = __test__.__private.extractRequestDetails(args); expect(result.requestId).to.equal('abc123'); @@ -353,7 +353,7 @@ describe('cache decorator', () => { }); it('should return a new default RequestDetails if not found', () => { - const args = [5644, { fromBlock: 'pending' }, 'value'] as unknown[]; + const args = [5644, { fromBlock: 'pending' }, 'value']; const result = __test__.__private.extractRequestDetails(args); expect(result.requestId).to.equal(''); @@ -361,7 +361,7 @@ describe('cache decorator', () => { }); it('should return new RequestDetails when args is empty', () => { - const args = [] as unknown[]; + const args = []; const result = __test__.__private.extractRequestDetails(args); expect(result.requestId).to.equal(''); @@ -371,14 +371,14 @@ describe('cache decorator', () => { it('should return the first RequestDetails instance if multiple are present', () => { const rd1 = new RequestDetails({ requestId: 'first', ipAddress: '1.1.1.1' }); const rd2 = new RequestDetails({ requestId: 'second', ipAddress: '2.2.2.2' }); - const args = [rd1, rd2] as unknown[]; + const args = [rd1, rd2]; const result = __test__.__private.extractRequestDetails(args); expect(result).to.equal(rd1); }); it('should handle null or undefined values in args', () => { - const args = [undefined, null, 5644] as unknown[]; + const args = [undefined, null, 5644]; const result = __test__.__private.extractRequestDetails(args); expect(result.requestId).to.equal(''); From c126a798007b80d5167fe7260834345a43e2aab7 Mon Sep 17 00:00:00 2001 From: konstantinabl Date: Fri, 27 Jun 2025 12:18:27 +0300 Subject: [PATCH 4/7] Update for loop in cache decorator Co-authored-by: Luis Mastrangelo Signed-off-by: konstantinabl --- packages/relay/src/lib/decorators/cache.decorator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/relay/src/lib/decorators/cache.decorator.ts b/packages/relay/src/lib/decorators/cache.decorator.ts index 8b4b9870e5..66d5e38826 100644 --- a/packages/relay/src/lib/decorators/cache.decorator.ts +++ b/packages/relay/src/lib/decorators/cache.decorator.ts @@ -117,7 +117,7 @@ const shouldSkipCachingForNamedParams = (args: unknown[], params: CacheNamedPara */ const generateCacheKey = (methodName: string, args: unknown[]) => { let cacheKey: string = methodName; - for (const [, value] of Object.entries(args)) { + for (const value of args) { if (value?.constructor?.name != 'RequestDetails') { if (value && typeof value === 'object') { cacheKey += `_${JSON.stringify(value)}`; From c9211a8b6085c612597edae30d7d982de63b455d Mon Sep 17 00:00:00 2001 From: Konstantina Blazhukova Date: Fri, 27 Jun 2025 12:20:46 +0300 Subject: [PATCH 5/7] improves value variable check Signed-off-by: Konstantina Blazhukova --- packages/relay/src/lib/decorators/cache.decorator.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/relay/src/lib/decorators/cache.decorator.ts b/packages/relay/src/lib/decorators/cache.decorator.ts index 66d5e38826..b611129588 100644 --- a/packages/relay/src/lib/decorators/cache.decorator.ts +++ b/packages/relay/src/lib/decorators/cache.decorator.ts @@ -117,8 +117,8 @@ const shouldSkipCachingForNamedParams = (args: unknown[], params: CacheNamedPara */ const generateCacheKey = (methodName: string, args: unknown[]) => { let cacheKey: string = methodName; - for (const value of args) { - if (value?.constructor?.name != 'RequestDetails') { + for (const [, value] of Object.entries(args)) { + if (!(value instanceof RequestDetails)) { if (value && typeof value === 'object') { cacheKey += `_${JSON.stringify(value)}`; continue; @@ -142,8 +142,8 @@ const generateCacheKey = (methodName: string, args: unknown[]) => { */ const extractRequestDetails = (args: unknown[]): RequestDetails => { for (const [, value] of Object.entries(args)) { - if (value?.constructor?.name === 'RequestDetails') { - return value as RequestDetails; + if (value instanceof RequestDetails) { + return value; } } From 56b34534b939767d4282bb796887327be0b2386f Mon Sep 17 00:00:00 2001 From: Konstantina Blazhukova Date: Fri, 27 Jun 2025 12:40:33 +0300 Subject: [PATCH 6/7] improves for loop Signed-off-by: Konstantina Blazhukova --- packages/relay/src/lib/decorators/cache.decorator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/relay/src/lib/decorators/cache.decorator.ts b/packages/relay/src/lib/decorators/cache.decorator.ts index b611129588..9ccb83c242 100644 --- a/packages/relay/src/lib/decorators/cache.decorator.ts +++ b/packages/relay/src/lib/decorators/cache.decorator.ts @@ -117,7 +117,7 @@ const shouldSkipCachingForNamedParams = (args: unknown[], params: CacheNamedPara */ const generateCacheKey = (methodName: string, args: unknown[]) => { let cacheKey: string = methodName; - for (const [, value] of Object.entries(args)) { + for (const value of args) { if (!(value instanceof RequestDetails)) { if (value && typeof value === 'object') { cacheKey += `_${JSON.stringify(value)}`; From 154ba139301d5d1437a595968123f8fbba7ba008 Mon Sep 17 00:00:00 2001 From: Konstantina Blazhukova Date: Fri, 27 Jun 2025 14:44:40 +0300 Subject: [PATCH 7/7] fixes tests Signed-off-by: Konstantina Blazhukova --- packages/relay/tests/lib/decorators/cache.spec.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/relay/tests/lib/decorators/cache.spec.ts b/packages/relay/tests/lib/decorators/cache.spec.ts index ef35e31498..6da3f77073 100644 --- a/packages/relay/tests/lib/decorators/cache.spec.ts +++ b/packages/relay/tests/lib/decorators/cache.spec.ts @@ -4,8 +4,8 @@ import { ConfigService } from '@hashgraph/json-rpc-config-service/dist/services' import { expect } from 'chai'; import sinon from 'sinon'; -import { __test__, cache } from '../../../dist/lib/decorators'; -import { CacheService } from '../../../dist/lib/services/cacheService/cacheService'; +import { __test__, cache } from '../../../src/lib/decorators'; +import { CacheService } from '../../../src/lib/services/cacheService/cacheService'; import { RequestDetails } from '../../../src/lib/types'; describe('cache decorator', () => { @@ -229,10 +229,7 @@ describe('cache decorator', () => { }); it('should ignore arguments with constructor name "RequestDetails"', () => { - const mockRequestDetails = { - constructor: { name: 'RequestDetails' }, - someField: 'shouldBeIgnored', - }; + const mockRequestDetails = new RequestDetails({ requestId: '1', ipAddress: '127.0.0.1' }); const args = [mockRequestDetails, 'earliest']; const result = __test__.__private.generateCacheKey('eth_call', args);