@pioneer-platform/pioneer-cache 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +2 -0
- package/README.md +451 -0
- package/dist/core/base-cache.d.ts +75 -0
- package/dist/core/base-cache.js +493 -0
- package/dist/core/cache-manager.d.ts +62 -0
- package/dist/core/cache-manager.js +238 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +25 -0
- package/dist/stores/balance-cache.d.ts +47 -0
- package/dist/stores/balance-cache.js +158 -0
- package/dist/stores/price-cache.d.ts +39 -0
- package/dist/stores/price-cache.js +179 -0
- package/dist/stores/transaction-cache.d.ts +42 -0
- package/dist/stores/transaction-cache.js +148 -0
- package/dist/types/index.d.ts +98 -0
- package/dist/types/index.js +5 -0
- package/dist/workers/refresh-worker.d.ts +57 -0
- package/dist/workers/refresh-worker.js +212 -0
- package/package.json +31 -0
- package/src/core/base-cache.ts +595 -0
- package/src/core/cache-manager.ts +293 -0
- package/src/index.ts +36 -0
- package/src/stores/balance-cache.ts +196 -0
- package/src/stores/price-cache.ts +215 -0
- package/src/stores/transaction-cache.ts +172 -0
- package/src/types/index.ts +121 -0
- package/src/workers/refresh-worker.ts +267 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/*
|
|
2
|
+
PriceCache - Price-specific cache implementation
|
|
3
|
+
|
|
4
|
+
Extends BaseCache with price-specific logic.
|
|
5
|
+
All common logic is inherited from BaseCache (including all 5 fixes!)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { BaseCache } from '../core/base-cache';
|
|
9
|
+
import type { CacheConfig } from '../types';
|
|
10
|
+
|
|
11
|
+
const log = require('@pioneer-platform/loggerdog')();
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Price data structure
|
|
15
|
+
*/
|
|
16
|
+
export interface PriceData {
|
|
17
|
+
caip: string;
|
|
18
|
+
price: number;
|
|
19
|
+
source?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* PriceCache - Caches USD prices for assets
|
|
24
|
+
*/
|
|
25
|
+
export class PriceCache extends BaseCache<PriceData> {
|
|
26
|
+
private markets: any;
|
|
27
|
+
|
|
28
|
+
constructor(redis: any, markets: any, config?: Partial<CacheConfig>) {
|
|
29
|
+
const defaultConfig: CacheConfig = {
|
|
30
|
+
name: 'price',
|
|
31
|
+
keyPrefix: 'price_v2:',
|
|
32
|
+
ttl: 60 * 60 * 1000, // 1 hour
|
|
33
|
+
staleThreshold: 30 * 60 * 1000, // 30 minutes (refresh after 30min)
|
|
34
|
+
enableTTL: true,
|
|
35
|
+
queueName: 'price-refresh-v2',
|
|
36
|
+
enableQueue: true,
|
|
37
|
+
maxRetries: 3,
|
|
38
|
+
retryDelay: 5000,
|
|
39
|
+
blockOnMiss: false, // Return immediately with $0 (prices less critical)
|
|
40
|
+
enableLegacyFallback: true,
|
|
41
|
+
defaultValue: {
|
|
42
|
+
caip: '',
|
|
43
|
+
price: 0
|
|
44
|
+
},
|
|
45
|
+
maxConcurrentJobs: 5,
|
|
46
|
+
apiTimeout: 5000,
|
|
47
|
+
logCacheHits: false,
|
|
48
|
+
logCacheMisses: true,
|
|
49
|
+
logRefreshJobs: true
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
super(redis, { ...defaultConfig, ...config });
|
|
53
|
+
this.markets = markets;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Build Redis key for price data
|
|
58
|
+
* Format: price_v2:caip
|
|
59
|
+
*/
|
|
60
|
+
protected buildKey(params: Record<string, any>): string {
|
|
61
|
+
const { caip } = params;
|
|
62
|
+
if (!caip) {
|
|
63
|
+
throw new Error('PriceCache.buildKey: caip required');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const normalizedCaip = caip.toLowerCase();
|
|
67
|
+
return `${this.config.keyPrefix}${normalizedCaip}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Fetch price from markets API
|
|
72
|
+
*/
|
|
73
|
+
protected async fetchFromSource(params: Record<string, any>): Promise<PriceData> {
|
|
74
|
+
const tag = this.TAG + 'fetchFromSource | ';
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const { caip } = params;
|
|
78
|
+
|
|
79
|
+
// Map CAIP to symbol for markets API
|
|
80
|
+
const assetData = require('@pioneer-platform/pioneer-discovery').assetData;
|
|
81
|
+
const asset = assetData[caip.toUpperCase()] || assetData[caip.toLowerCase()];
|
|
82
|
+
|
|
83
|
+
if (!asset || !asset.symbol) {
|
|
84
|
+
log.warn(tag, `No asset mapping found for ${caip}`);
|
|
85
|
+
return {
|
|
86
|
+
caip,
|
|
87
|
+
price: 0
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Get price from markets module
|
|
92
|
+
const symbol = asset.symbol.toLowerCase();
|
|
93
|
+
const priceResult = await this.markets.getPrice(symbol);
|
|
94
|
+
|
|
95
|
+
// Handle different response formats
|
|
96
|
+
let price = 0;
|
|
97
|
+
if (typeof priceResult === 'object' && priceResult.price) {
|
|
98
|
+
price = parseFloat(priceResult.price);
|
|
99
|
+
} else if (typeof priceResult === 'number') {
|
|
100
|
+
price = priceResult;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (isNaN(price) || price <= 0) {
|
|
104
|
+
log.warn(tag, `Invalid price for ${caip} (${symbol}): ${priceResult}`);
|
|
105
|
+
return {
|
|
106
|
+
caip,
|
|
107
|
+
price: 0
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
log.debug(tag, `Fetched price for ${caip} (${symbol}): $${price}`);
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
caip,
|
|
115
|
+
price,
|
|
116
|
+
source: 'markets'
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
} catch (error) {
|
|
120
|
+
log.error(tag, 'Error fetching price:', error);
|
|
121
|
+
throw error;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Try to get price from legacy cache formats
|
|
127
|
+
*/
|
|
128
|
+
protected async getLegacyCached(params: Record<string, any>): Promise<PriceData | null> {
|
|
129
|
+
const tag = this.TAG + 'getLegacyCached | ';
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
const { caip } = params;
|
|
133
|
+
|
|
134
|
+
// Try CoinGecko format first (more accurate)
|
|
135
|
+
const coingeckoKey = `coingecko:${caip}`;
|
|
136
|
+
const coingeckoData = await this.redis.get(coingeckoKey);
|
|
137
|
+
|
|
138
|
+
if (coingeckoData) {
|
|
139
|
+
const parsed = JSON.parse(coingeckoData);
|
|
140
|
+
if (parsed.current_price && typeof parsed.current_price === 'number') {
|
|
141
|
+
return {
|
|
142
|
+
caip,
|
|
143
|
+
price: parsed.current_price,
|
|
144
|
+
source: 'coingecko-legacy'
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Try CoinCap format
|
|
150
|
+
const coincapKey = `coincap:${caip}`;
|
|
151
|
+
const coincapData = await this.redis.get(coincapKey);
|
|
152
|
+
|
|
153
|
+
if (coincapData) {
|
|
154
|
+
const parsed = JSON.parse(coincapData);
|
|
155
|
+
if (parsed.priceUsd && typeof parsed.priceUsd === 'string') {
|
|
156
|
+
const price = parseFloat(parsed.priceUsd);
|
|
157
|
+
if (!isNaN(price)) {
|
|
158
|
+
return {
|
|
159
|
+
caip,
|
|
160
|
+
price,
|
|
161
|
+
source: 'coincap-legacy'
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return null;
|
|
168
|
+
|
|
169
|
+
} catch (error) {
|
|
170
|
+
log.error(tag, 'Error getting legacy cached price:', error);
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Get price for a specific asset
|
|
177
|
+
* Convenience method that wraps base get()
|
|
178
|
+
*/
|
|
179
|
+
async getPrice(caip: string, waitForFresh?: boolean): Promise<number> {
|
|
180
|
+
const result = await this.get({ caip }, waitForFresh);
|
|
181
|
+
return result.value?.price || 0;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Get prices for multiple assets (batch operation)
|
|
186
|
+
*/
|
|
187
|
+
async getBatchPrices(caips: string[], waitForFresh?: boolean): Promise<Map<string, number>> {
|
|
188
|
+
const tag = this.TAG + 'getBatchPrices | ';
|
|
189
|
+
const startTime = Date.now();
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
log.info(tag, `Batch request for ${caips.length} prices`);
|
|
193
|
+
|
|
194
|
+
// Get all prices in parallel
|
|
195
|
+
const promises = caips.map(caip => this.getPrice(caip, waitForFresh));
|
|
196
|
+
const results = await Promise.all(promises);
|
|
197
|
+
|
|
198
|
+
// Build map of caip -> price
|
|
199
|
+
const priceMap = new Map<string, number>();
|
|
200
|
+
caips.forEach((caip, index) => {
|
|
201
|
+
priceMap.set(caip, results[index]);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
const responseTime = Date.now() - startTime;
|
|
205
|
+
log.info(tag, `Batch completed: ${results.length} prices in ${responseTime}ms (${(responseTime / results.length).toFixed(1)}ms avg)`);
|
|
206
|
+
|
|
207
|
+
return priceMap;
|
|
208
|
+
|
|
209
|
+
} catch (error) {
|
|
210
|
+
log.error(tag, 'Error in batch price request:', error);
|
|
211
|
+
// Return empty map
|
|
212
|
+
return new Map();
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/*
|
|
2
|
+
TransactionCache - Transaction-specific cache implementation
|
|
3
|
+
|
|
4
|
+
Different pattern from Balance/Price caches:
|
|
5
|
+
- Caches transaction data PERMANENTLY (no TTL)
|
|
6
|
+
- No background refresh (blockchain data is immutable)
|
|
7
|
+
- Simple cache-aside pattern with getOrFetch()
|
|
8
|
+
- No workers needed
|
|
9
|
+
|
|
10
|
+
Does NOT extend BaseCache - intentionally different architecture.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const log = require('@pioneer-platform/loggerdog')();
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* TransactionCache - Caches immutable blockchain transaction data
|
|
17
|
+
*/
|
|
18
|
+
export class TransactionCache {
|
|
19
|
+
private redis: any;
|
|
20
|
+
private readonly keyPrefix = 'tx:';
|
|
21
|
+
private readonly TAG = ' | TransactionCache | ';
|
|
22
|
+
|
|
23
|
+
constructor(redis: any) {
|
|
24
|
+
this.redis = redis;
|
|
25
|
+
log.info(this.TAG, 'TransactionCache initialized (permanent caching, no TTL)');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Get cached transaction data by txid
|
|
30
|
+
*/
|
|
31
|
+
async get(txid: string): Promise<any | null> {
|
|
32
|
+
const tag = this.TAG + 'get | ';
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const key = this.buildKey(txid);
|
|
36
|
+
const cached = await this.redis.get(key);
|
|
37
|
+
|
|
38
|
+
if (cached) {
|
|
39
|
+
log.info(tag, `Cache HIT for txid: ${txid.substring(0, 16)}...`);
|
|
40
|
+
return JSON.parse(cached);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
log.info(tag, `Cache MISS for txid: ${txid.substring(0, 16)}...`);
|
|
44
|
+
return null;
|
|
45
|
+
|
|
46
|
+
} catch (error) {
|
|
47
|
+
log.error(tag, `Error getting cached tx ${txid}:`, error);
|
|
48
|
+
return null; // Fail gracefully
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Cache transaction data PERMANENTLY (no expiry)
|
|
54
|
+
* Transactions are immutable - once on the blockchain, they never change
|
|
55
|
+
*/
|
|
56
|
+
async set(txid: string, txData: any): Promise<void> {
|
|
57
|
+
const tag = this.TAG + 'set | ';
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
const key = this.buildKey(txid);
|
|
61
|
+
|
|
62
|
+
// NO TTL - cache permanently
|
|
63
|
+
await this.redis.set(key, JSON.stringify(txData));
|
|
64
|
+
|
|
65
|
+
log.info(tag, `Cached txid: ${txid.substring(0, 16)}... (PERMANENT - no expiry)`);
|
|
66
|
+
|
|
67
|
+
} catch (error) {
|
|
68
|
+
log.error(tag, `Error caching tx ${txid}:`, error);
|
|
69
|
+
// Don't throw - caching is optional
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get transaction from cache or fetch from source and cache it
|
|
75
|
+
* Classic cache-aside pattern
|
|
76
|
+
*/
|
|
77
|
+
async getOrFetch(txid: string, fetchFn: () => Promise<any>): Promise<any> {
|
|
78
|
+
const tag = this.TAG + 'getOrFetch | ';
|
|
79
|
+
|
|
80
|
+
// Try cache first
|
|
81
|
+
const cached = await this.get(txid);
|
|
82
|
+
if (cached) {
|
|
83
|
+
return cached;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Not in cache - fetch from source
|
|
87
|
+
log.info(tag, `Fetching from source: ${txid.substring(0, 16)}...`);
|
|
88
|
+
const txData = await fetchFn();
|
|
89
|
+
|
|
90
|
+
// Cache the result
|
|
91
|
+
await this.set(txid, txData);
|
|
92
|
+
|
|
93
|
+
return txData;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Invalidate cached transaction (useful for unconfirmed transactions)
|
|
98
|
+
*/
|
|
99
|
+
async invalidate(txid: string): Promise<void> {
|
|
100
|
+
const tag = this.TAG + 'invalidate | ';
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
const key = this.buildKey(txid);
|
|
104
|
+
await this.redis.del(key);
|
|
105
|
+
log.info(tag, `Invalidated cache for txid: ${txid.substring(0, 16)}...`);
|
|
106
|
+
|
|
107
|
+
} catch (error) {
|
|
108
|
+
log.error(tag, `Error invalidating cached tx ${txid}:`, error);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Get cache statistics
|
|
114
|
+
*/
|
|
115
|
+
async getStats(): Promise<{ totalKeys: number; memoryUsage: string }> {
|
|
116
|
+
const tag = this.TAG + 'getStats | ';
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
const keys = await this.redis.keys(`${this.keyPrefix}*`);
|
|
120
|
+
const totalKeys = keys.length;
|
|
121
|
+
|
|
122
|
+
// Get Redis memory info
|
|
123
|
+
const info = await this.redis.info('memory');
|
|
124
|
+
const memoryMatch = info.match(/used_memory_human:(.+)/);
|
|
125
|
+
const memoryUsage = memoryMatch ? memoryMatch[1].trim() : 'unknown';
|
|
126
|
+
|
|
127
|
+
log.info(tag, `Cache stats: ${totalKeys} keys, ${memoryUsage} memory`);
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
totalKeys,
|
|
131
|
+
memoryUsage
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
} catch (error) {
|
|
135
|
+
log.error(tag, 'Error getting cache stats:', error);
|
|
136
|
+
return {
|
|
137
|
+
totalKeys: 0,
|
|
138
|
+
memoryUsage: 'unknown'
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Clear all cached transactions (use with caution)
|
|
145
|
+
*/
|
|
146
|
+
async clearAll(): Promise<number> {
|
|
147
|
+
const tag = this.TAG + 'clearAll | ';
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
const keys = await this.redis.keys(`${this.keyPrefix}*`);
|
|
151
|
+
if (keys.length === 0) {
|
|
152
|
+
log.info(tag, 'No cached transactions to clear');
|
|
153
|
+
return 0;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
await this.redis.del(...keys);
|
|
157
|
+
log.info(tag, `Cleared ${keys.length} cached transactions`);
|
|
158
|
+
return keys.length;
|
|
159
|
+
|
|
160
|
+
} catch (error) {
|
|
161
|
+
log.error(tag, 'Error clearing cache:', error);
|
|
162
|
+
return 0;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Build cache key for a transaction
|
|
168
|
+
*/
|
|
169
|
+
private buildKey(txid: string): string {
|
|
170
|
+
return `${this.keyPrefix}${txid}`;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Pioneer Cache - Core Types and Interfaces
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Cache configuration for a specific cache store
|
|
7
|
+
*/
|
|
8
|
+
export interface CacheConfig {
|
|
9
|
+
// Cache identification
|
|
10
|
+
name: string; // e.g., 'balance', 'price', 'transaction'
|
|
11
|
+
keyPrefix: string; // Redis key prefix, e.g., 'balance_v2:'
|
|
12
|
+
|
|
13
|
+
// TTL configuration
|
|
14
|
+
ttl: number; // Time-to-live in milliseconds
|
|
15
|
+
staleThreshold?: number; // When to trigger background refresh (optional)
|
|
16
|
+
enableTTL: boolean; // Set to false for permanent caching (transactions)
|
|
17
|
+
|
|
18
|
+
// Queue configuration
|
|
19
|
+
queueName: string; // Redis queue name
|
|
20
|
+
enableQueue: boolean; // Set to false if no background refresh needed
|
|
21
|
+
maxRetries: number; // Max retry attempts for failed jobs
|
|
22
|
+
retryDelay: number; // Delay between retries in ms
|
|
23
|
+
|
|
24
|
+
// Cache behavior
|
|
25
|
+
blockOnMiss: boolean; // Wait for fresh data on cache miss
|
|
26
|
+
enableLegacyFallback: boolean; // Try legacy cache keys on miss
|
|
27
|
+
defaultValue: any; // Default value to return on error
|
|
28
|
+
|
|
29
|
+
// Performance
|
|
30
|
+
maxConcurrentJobs: number; // Max jobs processed concurrently
|
|
31
|
+
apiTimeout: number; // Timeout for source fetch operations
|
|
32
|
+
|
|
33
|
+
// Logging
|
|
34
|
+
logCacheHits: boolean;
|
|
35
|
+
logCacheMisses: boolean;
|
|
36
|
+
logRefreshJobs: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Generic cached value wrapper
|
|
41
|
+
*/
|
|
42
|
+
export interface CachedValue<T> {
|
|
43
|
+
value: T;
|
|
44
|
+
timestamp: number;
|
|
45
|
+
source: string; // 'network', 'legacy', 'startup'
|
|
46
|
+
lastUpdated: string; // ISO timestamp
|
|
47
|
+
metadata?: Record<string, any>; // Additional metadata
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Cache operation result
|
|
52
|
+
*/
|
|
53
|
+
export interface CacheResult<T> {
|
|
54
|
+
success: boolean;
|
|
55
|
+
value?: T;
|
|
56
|
+
cached: boolean; // Was value served from cache?
|
|
57
|
+
fresh: boolean; // Is value fresh (not stale)?
|
|
58
|
+
age?: number; // Age of cached value in ms
|
|
59
|
+
error?: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Health check result
|
|
64
|
+
*/
|
|
65
|
+
export interface HealthCheckResult {
|
|
66
|
+
status: 'healthy' | 'degraded' | 'unhealthy';
|
|
67
|
+
queueInitialized: boolean;
|
|
68
|
+
redisConnected: boolean;
|
|
69
|
+
stats: CacheStats;
|
|
70
|
+
issues: string[];
|
|
71
|
+
warnings: string[];
|
|
72
|
+
timestamp: number;
|
|
73
|
+
timestampISO: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Cache statistics
|
|
78
|
+
*/
|
|
79
|
+
export interface CacheStats {
|
|
80
|
+
totalEntries: number;
|
|
81
|
+
staleEntries: number;
|
|
82
|
+
freshEntries: number;
|
|
83
|
+
stalenessRate: string; // Percentage string
|
|
84
|
+
sources?: Record<string, number>;
|
|
85
|
+
byNetwork?: Record<string, number>;
|
|
86
|
+
entriesWithoutTTL?: number;
|
|
87
|
+
ttl?: number; // Configured TTL
|
|
88
|
+
staleThreshold?: number; // Configured stale threshold
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Refresh job for background workers
|
|
93
|
+
*/
|
|
94
|
+
export interface RefreshJob {
|
|
95
|
+
type: string; // 'REFRESH_BALANCE', 'REFRESH_PRICE', etc.
|
|
96
|
+
key: string; // Cache key or identifier
|
|
97
|
+
params: Record<string, any>; // Job-specific parameters
|
|
98
|
+
priority?: 'high' | 'normal' | 'low';
|
|
99
|
+
retryCount?: number;
|
|
100
|
+
timestamp?: number;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Source fetcher function signature
|
|
105
|
+
* Returns the data to be cached
|
|
106
|
+
*/
|
|
107
|
+
export type SourceFetcher<T> = (params: Record<string, any>) => Promise<T>;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Key builder function signature
|
|
111
|
+
* Builds Redis key from parameters
|
|
112
|
+
*/
|
|
113
|
+
export type KeyBuilder = (...args: any[]) => string;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Legacy key pattern for migration
|
|
117
|
+
*/
|
|
118
|
+
export interface LegacyKeyPattern {
|
|
119
|
+
prefix: string;
|
|
120
|
+
keyBuilder: KeyBuilder;
|
|
121
|
+
}
|