From fd44866867ec7b89eebc7627c76d3eb9794b4346 Mon Sep 17 00:00:00 2001 From: Swayymalcolm99 Date: Tue, 24 Mar 2026 20:30:15 +0000 Subject: [PATCH 1/2] updated project updated project files and work done --- packages/common/package.json | 20 +++++ packages/common/src/cache.ts | 144 ++++++++++++++++++++++++++++++++++ packages/common/src/index.ts | 1 + packages/common/test-cache.ts | 52 ++++++++++++ 4 files changed, 217 insertions(+) create mode 100644 packages/common/package.json create mode 100644 packages/common/src/cache.ts create mode 100644 packages/common/src/index.ts create mode 100644 packages/common/test-cache.ts diff --git a/packages/common/package.json b/packages/common/package.json new file mode 100644 index 0000000..d03a8ec --- /dev/null +++ b/packages/common/package.json @@ -0,0 +1,20 @@ +{ + "name": "@stellar-pay/common", + "version": "0.1.0", + "main": "./dist/index.js", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "tsup src/index.ts --format cjs,esm --dts --clean" + }, + "dependencies": { + "ioredis": "^5.3.2" + }, + "devDependencies": { + "tsup": "^8.0.0", + "typescript": "^5.0.0" + } +} \ No newline at end of file diff --git a/packages/common/src/cache.ts b/packages/common/src/cache.ts new file mode 100644 index 0000000..152dc22 --- /dev/null +++ b/packages/common/src/cache.ts @@ -0,0 +1,144 @@ +import Redis, { RedisOptions } from 'ioredis'; + +// --- Redis Client Configuration --- +const redisOptions: RedisOptions = { + host: process.env.REDIS_HOST || 'localhost', + port: parseInt(process.env.REDIS_PORT || '6379', 10), + password: process.env.REDIS_PASSWORD, + db: parseInt(process.env.REDIS_DB || '0', 10), + keyPrefix: process.env.REDIS_PREFIX || 'stellar-pay:', + retryStrategy: (times) => { + return Math.min(times * 50, 2000); + }, +}; + +// Use REDIS_URL if provided, else fallback to individual options +export const redisClient = new Redis(process.env.REDIS_URL || redisOptions); + +redisClient.on('connect', () => { + console.log('[Redis] Connected gracefully'); +}); + +redisClient.on('error', (err) => { + console.error('[Redis] Error connecting: ', err); +}); + +// --- Cache Decorators --- + +/** + * Cacheable Decorator for expensive operations + * @param keyPrefix Prefix for the cache key + * @param ttl Time to live in seconds (default 300) + */ +export function Cacheable(keyPrefix: string, ttl: number = 300) { + return function ( + target: any, + propertyKey: string, + descriptor: PropertyDescriptor + ) { + const originalMethod = descriptor.value; + + descriptor.value = async function (...args: any[]) { + const key = `${keyPrefix}:${args.join(':')}`; + + try { + const cachedValue = await redisClient.get(key); + + if (cachedValue) { + console.log(`[Cache Hit] key: ${key}`); + return JSON.parse(cachedValue); + } + + console.log(`[Cache Miss] key: ${key}`); + const result = await originalMethod.apply(this, args); + + if (result !== undefined && result !== null) { + await redisClient.set(key, JSON.stringify(result), 'EX', ttl); + } + + return result; + } catch (error) { + console.error(`[Cache Error] failed to process key: ${key}`, error); + return await originalMethod.apply(this, args); + } + }; + + return descriptor; + }; +} + +/** + * CacheInvalidate Decorator for clearing cache after updates + * @param keyPrefix Prefix for the cache key to invalidate + */ +export function CacheInvalidate(keyPrefix: string) { + return function ( + target: any, + propertyKey: string, + descriptor: PropertyDescriptor + ) { + const originalMethod = descriptor.value; + + descriptor.value = async function (...args: any[]) { + // Assuming the first argument is the identifier (like transactionId) + const id = args[0]; + const key = id ? `${keyPrefix}:${id}` : keyPrefix; + + const result = await originalMethod.apply(this, args); + + try { + await redisClient.del(key); + console.log(`[Cache Invalidation] cleared key: ${key}`); + } catch (error) { + console.error(`[Cache Error] failed to invalidate key: ${key}`, error); + } + + return result; + }; + + return descriptor; + }; +} + +// --- Specific Transaction Cache Class implementation --- + +export class TransactionStatusCache { + private static PREFIX = 'tx_status'; + private static TTL = 60; + + static async getStatus(transactionId: string): Promise { + const key = `${this.PREFIX}:${transactionId}`; + try { + const value = await redisClient.get(key); + if (value) { + console.log(`[Cache Hit] Transaction Status Lookup - key: ${key}`); + return value; + } + console.log(`[Cache Miss] Transaction Status Lookup - key: ${key}`); + return null; + } catch (err) { + console.error(`[Cache Error] getStatus: ${key}`, err); + return null; + } + } + + static async setStatus(transactionId: string, status: string): Promise { + const key = `${this.PREFIX}:${transactionId}`; + try { + await redisClient.set(key, status, 'EX', this.TTL); + console.log(`[Cache Set] Transaction Status stored - key: ${key}`); + } catch (err) { + console.error(`[Cache Error] setStatus: ${key}`, err); + } + } + + static async invalidateStatus(transactionId: string): Promise { + const key = `${this.PREFIX}:${transactionId}`; + try { + await redisClient.del(key); + console.log(`[Cache Invalidation] Transaction Status cleared - key: ${key}`); + } catch (err) { + console.error(`[Cache Error] invalidateStatus: ${key}`, err); + } + } +} diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts new file mode 100644 index 0000000..77e55d4 --- /dev/null +++ b/packages/common/src/index.ts @@ -0,0 +1 @@ +export * from './cache'; diff --git a/packages/common/test-cache.ts b/packages/common/test-cache.ts new file mode 100644 index 0000000..fb7e79a --- /dev/null +++ b/packages/common/test-cache.ts @@ -0,0 +1,52 @@ +import { redisClient, Cacheable, CacheInvalidate, TransactionStatusCache } from './src/cache'; + +class TestService { + @Cacheable('expensive_op') + async getExpensiveData(id: string) { + console.log(`[TestService] Fetching Expensive Data for ${id}...`); + return { data: `Expensive result ${id}` }; + } + + @CacheInvalidate('expensive_op') + async updateData(id: string) { + console.log(`[TestService] Updating Data for ${id}...`); + return { success: true }; + } +} + +async function runTest() { + const service = new TestService(); + + console.log('--- Testing Decorators ---'); + // 1st call: Cache Miss + await service.getExpensiveData('123'); + // 2nd call: Cache Hit + await service.getExpensiveData('123'); + + // Update data: Invalidate Cache + await service.updateData('123'); + + // 3rd call: Cache Miss again + await service.getExpensiveData('123'); + + console.log('\n--- Testing Transaction Status Cache ---'); + // 1st call: Cache Miss + await TransactionStatusCache.getStatus('TX_999'); + + // Set cache + await TransactionStatusCache.setStatus('TX_999', 'COMPLETED'); + + // 2nd call: Cache Hit + await TransactionStatusCache.getStatus('TX_999'); + + // Invalidate cache + await TransactionStatusCache.invalidateStatus('TX_999'); + + // 3rd call: Cache Miss + await TransactionStatusCache.getStatus('TX_999'); + + // Close redis connection to exit the script + await redisClient.quit(); +} + +runTest().catch(console.error); From 9bc8b07b35bea80401bd9ac36618f2fb63e0f4af Mon Sep 17 00:00:00 2001 From: ALEX AKPOJOSEVBE Date: Fri, 27 Mar 2026 18:27:38 -0700 Subject: [PATCH 2/2] updated work work done --- pnpm-lock.yaml | 106 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 105 insertions(+), 1 deletion(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index de8c444..d7223a9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -407,6 +407,19 @@ importers: specifier: ^5.0.0 version: 5.9.3 + packages/common: + dependencies: + ioredis: + specifier: ^5.3.2 + version: 5.10.1 + devDependencies: + tsup: + specifier: ^8.0.0 + version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(typescript@5.9.3)(yaml@2.8.2) + typescript: + specifier: ^5.0.0 + version: 5.9.3 + packages/compliance-engine: devDependencies: tsup: @@ -1820,6 +1833,12 @@ packages: '@types/node': optional: true + '@ioredis/commands@1.5.1': + resolution: + { + integrity: sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw==, + } + '@isaacs/cliui@8.0.2': resolution: { @@ -6070,6 +6089,13 @@ packages: } engines: { node: '>=6' } + cluster-key-slot@1.1.2: + resolution: + { + integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==, + } + engines: { node: '>=0.10.0' } + cmdk@1.1.1: resolution: { @@ -6532,6 +6558,13 @@ packages: } engines: { node: '>=0.4.0' } + denque@2.1.0: + resolution: + { + integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==, + } + engines: { node: '>=0.10' } + depd@2.0.0: resolution: { @@ -7849,6 +7882,13 @@ packages: } engines: { node: '>=12' } + ioredis@5.10.1: + resolution: + { + integrity: sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA==, + } + engines: { node: '>=12.22.0' } + ip-address@10.0.1: resolution: { @@ -8843,12 +8883,24 @@ packages: integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==, } + lodash.defaults@4.2.0: + resolution: + { + integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==, + } + lodash.includes@4.3.0: resolution: { integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==, } + lodash.isarguments@3.1.0: + resolution: + { + integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==, + } + lodash.isboolean@3.0.3: resolution: { @@ -10147,6 +10199,20 @@ packages: react: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + redis-errors@1.2.0: + resolution: + { + integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==, + } + engines: { node: '>=4' } + + redis-parser@3.0.0: + resolution: + { + integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==, + } + engines: { node: '>=4' } + redux@4.2.1: resolution: { @@ -10602,6 +10668,12 @@ packages: } engines: { node: '>=10' } + standard-as-callback@2.1.0: + resolution: + { + integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==, + } + statuses@2.0.2: resolution: { @@ -12665,6 +12737,8 @@ snapshots: optionalDependencies: '@types/node': 22.19.13 + '@ioredis/commands@1.5.1': {} + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -15706,6 +15780,8 @@ snapshots: clsx@2.1.1: {} + cluster-key-slot@1.1.2: {} + cmdk@1.1.1(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.3) @@ -15933,6 +16009,8 @@ snapshots: delayed-stream@1.0.0: {} + denque@2.1.0: {} + depd@2.0.0: {} detect-libc@2.1.2: {} @@ -16582,7 +16660,7 @@ snapshots: fix-dts-default-cjs-exports@1.0.1: dependencies: - magic-string: 0.30.17 + magic-string: 0.30.21 mlly: 1.8.0 rollup: 4.59.0 @@ -16899,6 +16977,20 @@ snapshots: internmap@2.0.3: {} + ioredis@5.10.1: + dependencies: + '@ioredis/commands': 1.5.1 + cluster-key-slot: 1.1.2 + debug: 4.4.3 + denque: 2.1.0 + lodash.defaults: 4.2.0 + lodash.isarguments: 3.1.0 + redis-errors: 1.2.0 + redis-parser: 3.0.0 + standard-as-callback: 2.1.0 + transitivePeerDependencies: + - supports-color + ip-address@10.0.1: {} ipaddr.js@1.9.1: {} @@ -17624,8 +17716,12 @@ snapshots: lodash.debounce@4.0.8: {} + lodash.defaults@4.2.0: {} + lodash.includes@4.3.0: {} + lodash.isarguments@3.1.0: {} + lodash.isboolean@3.0.3: {} lodash.isinteger@4.0.4: {} @@ -18423,6 +18519,12 @@ snapshots: tiny-invariant: 1.3.3 victory-vendor: 36.9.2 + redis-errors@1.2.0: {} + + redis-parser@3.0.0: + dependencies: + redis-errors: 1.2.0 + redux@4.2.1: dependencies: '@babel/runtime': 7.28.6 @@ -18810,6 +18912,8 @@ snapshots: dependencies: escape-string-regexp: 2.0.0 + standard-as-callback@2.1.0: {} + statuses@2.0.2: {} stdin-discarder@0.2.2: {}