@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
package/README.md
ADDED
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
# @pioneer-platform/pioneer-cache
|
|
2
|
+
|
|
3
|
+
Unified caching system for Pioneer Platform with stale-while-revalidate pattern, TTL management, background refresh workers, and production health monitoring.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
✅ **Stale-While-Revalidate** - Return cached data instantly, refresh in background
|
|
8
|
+
✅ **Automatic TTL Management** - Redis keys auto-expire, preventing stale data
|
|
9
|
+
✅ **Background Refresh Workers** - Queue-based async refresh with retry logic
|
|
10
|
+
✅ **Health Monitoring** - Real-time cache health checks with staleness metrics
|
|
11
|
+
✅ **Legacy Migration** - Automatic fallback to old cache formats
|
|
12
|
+
✅ **Blocking Mode** - Wait for fresh data on first request (configurable)
|
|
13
|
+
✅ **Batch Operations** - Efficient parallel requests for multiple items
|
|
14
|
+
✅ **TypeScript** - Full type safety with generics
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
bun add @pioneer-platform/pioneer-cache
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Architecture
|
|
23
|
+
|
|
24
|
+
### BaseCache Pattern
|
|
25
|
+
|
|
26
|
+
All cache implementations extend `BaseCache<T>` which provides:
|
|
27
|
+
- Stale-while-revalidate logic
|
|
28
|
+
- TTL management
|
|
29
|
+
- Background refresh coordination
|
|
30
|
+
- Health monitoring
|
|
31
|
+
- Legacy fallback
|
|
32
|
+
|
|
33
|
+
Subclasses implement only 3 methods:
|
|
34
|
+
1. `buildKey(params)` - Build Redis key from parameters
|
|
35
|
+
2. `fetchFromSource(params)` - Fetch fresh data from source
|
|
36
|
+
3. `getLegacyCached(params)` - Optional legacy cache migration
|
|
37
|
+
|
|
38
|
+
### Cache Types
|
|
39
|
+
|
|
40
|
+
**BalanceCache** - Blockchain balance data (5 minute TTL, 2 minute refresh)
|
|
41
|
+
- Fetches from blockchain via balance module
|
|
42
|
+
- Blocks on first miss for accurate balances
|
|
43
|
+
- Supports batch balance requests
|
|
44
|
+
|
|
45
|
+
**PriceCache** - USD price data (1 hour TTL, 30 minute refresh)
|
|
46
|
+
- Fetches from markets API
|
|
47
|
+
- Returns immediately with $0 on miss (non-blocking)
|
|
48
|
+
- Supports batch price requests
|
|
49
|
+
|
|
50
|
+
**TransactionCache** - Immutable transaction data (no TTL)
|
|
51
|
+
- Classic cache-aside pattern
|
|
52
|
+
- Permanent caching (blockchain data never changes)
|
|
53
|
+
- No background workers needed
|
|
54
|
+
|
|
55
|
+
## Quick Start
|
|
56
|
+
|
|
57
|
+
### Using CacheManager (Recommended)
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
import { CacheManager } from '@pioneer-platform/pioneer-cache';
|
|
61
|
+
|
|
62
|
+
const cacheManager = new CacheManager({
|
|
63
|
+
redis, // Redis client
|
|
64
|
+
balanceModule, // Balance fetcher
|
|
65
|
+
markets, // Price fetcher
|
|
66
|
+
enableBalanceCache: true,
|
|
67
|
+
enablePriceCache: true,
|
|
68
|
+
enableTransactionCache: true,
|
|
69
|
+
startWorkers: true // Auto-start background workers
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Get caches
|
|
73
|
+
const { balance, price, transaction } = cacheManager.getCaches();
|
|
74
|
+
|
|
75
|
+
// Get balance
|
|
76
|
+
const balanceData = await balance.getBalance('eip155:1/slip44:60', 'xpub123...');
|
|
77
|
+
|
|
78
|
+
// Get price
|
|
79
|
+
const btcPrice = await price.getPrice('eip155:1/slip44:0');
|
|
80
|
+
|
|
81
|
+
// Get transaction
|
|
82
|
+
const tx = await transaction.getOrFetch('txid123...', async () => {
|
|
83
|
+
return await fetchTransactionFromBlockchain(txid);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Health check
|
|
87
|
+
const health = await cacheManager.getHealth();
|
|
88
|
+
console.log(health.status); // 'healthy' | 'degraded' | 'unhealthy'
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Using Individual Caches
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
import { BalanceCache, PriceCache, TransactionCache } from '@pioneer-platform/pioneer-cache';
|
|
95
|
+
|
|
96
|
+
// Balance Cache
|
|
97
|
+
const balanceCache = new BalanceCache(redis, balanceModule, {
|
|
98
|
+
ttl: 5 * 60 * 1000, // 5 minutes
|
|
99
|
+
staleThreshold: 2 * 60 * 1000, // Refresh after 2 minutes
|
|
100
|
+
blockOnMiss: true // Wait for first request
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const balance = await balanceCache.getBalance('eip155:1/slip44:60', 'xpub123...');
|
|
104
|
+
console.log(balance.balance); // "1234567890"
|
|
105
|
+
|
|
106
|
+
// Price Cache
|
|
107
|
+
const priceCache = new PriceCache(redis, markets, {
|
|
108
|
+
ttl: 60 * 60 * 1000, // 1 hour
|
|
109
|
+
staleThreshold: 30 * 60 * 1000, // Refresh after 30 minutes
|
|
110
|
+
blockOnMiss: false // Return $0 immediately
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const price = await priceCache.getPrice('eip155:1/slip44:60');
|
|
114
|
+
console.log(price); // 2500.00
|
|
115
|
+
|
|
116
|
+
// Transaction Cache
|
|
117
|
+
const txCache = new TransactionCache(redis);
|
|
118
|
+
|
|
119
|
+
const tx = await txCache.getOrFetch('txid123...', async () => {
|
|
120
|
+
return await blockchain.getTransaction('txid123...');
|
|
121
|
+
});
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Batch Operations
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
// Batch balances
|
|
128
|
+
const items = [
|
|
129
|
+
{ caip: 'eip155:1/slip44:60', pubkey: 'xpub123...' },
|
|
130
|
+
{ caip: 'eip155:1/slip44:0', pubkey: 'xpub456...' },
|
|
131
|
+
];
|
|
132
|
+
const balances = await balanceCache.getBatchBalances(items);
|
|
133
|
+
|
|
134
|
+
// Batch prices
|
|
135
|
+
const caips = ['eip155:1/slip44:60', 'eip155:1/slip44:0'];
|
|
136
|
+
const prices = await priceCache.getBatchPrices(caips);
|
|
137
|
+
console.log(prices.get('eip155:1/slip44:60')); // 2500.00
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Configuration
|
|
141
|
+
|
|
142
|
+
### CacheConfig Interface
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
interface CacheConfig {
|
|
146
|
+
name: string; // Cache name for logging
|
|
147
|
+
keyPrefix: string; // Redis key prefix (e.g., "balance_v2:")
|
|
148
|
+
ttl: number; // Time-to-live in milliseconds
|
|
149
|
+
staleThreshold: number; // Refresh threshold in milliseconds
|
|
150
|
+
enableTTL: boolean; // Enable automatic expiration
|
|
151
|
+
queueName: string; // Queue name for background jobs
|
|
152
|
+
enableQueue: boolean; // Enable background refresh
|
|
153
|
+
maxRetries: number; // Max retry attempts
|
|
154
|
+
retryDelay: number; // Delay between retries (ms)
|
|
155
|
+
blockOnMiss: boolean; // Block on first request vs return default
|
|
156
|
+
enableLegacyFallback: boolean; // Try legacy cache formats
|
|
157
|
+
defaultValue: T; // Default value on miss
|
|
158
|
+
maxConcurrentJobs: number; // Worker concurrency limit
|
|
159
|
+
apiTimeout: number; // Source API timeout (ms)
|
|
160
|
+
logCacheHits: boolean; // Log cache hits
|
|
161
|
+
logCacheMisses: boolean; // Log cache misses
|
|
162
|
+
logRefreshJobs: boolean; // Log refresh jobs
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Default Configurations
|
|
167
|
+
|
|
168
|
+
**Balance Cache** (Critical - block on miss)
|
|
169
|
+
```typescript
|
|
170
|
+
{
|
|
171
|
+
ttl: 5 * 60 * 1000, // 5 minutes
|
|
172
|
+
staleThreshold: 2 * 60 * 1000, // Refresh after 2 minutes
|
|
173
|
+
blockOnMiss: true, // Wait for fresh data
|
|
174
|
+
maxRetries: 3,
|
|
175
|
+
retryDelay: 10000
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**Price Cache** (Non-critical - return immediately)
|
|
180
|
+
```typescript
|
|
181
|
+
{
|
|
182
|
+
ttl: 60 * 60 * 1000, // 1 hour
|
|
183
|
+
staleThreshold: 30 * 60 * 1000, // Refresh after 30 minutes
|
|
184
|
+
blockOnMiss: false, // Return $0 immediately
|
|
185
|
+
maxRetries: 3,
|
|
186
|
+
retryDelay: 5000
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Background Workers
|
|
191
|
+
|
|
192
|
+
### Unified Worker
|
|
193
|
+
|
|
194
|
+
A single worker processes refresh jobs for all cache types:
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
import { startUnifiedWorker } from '@pioneer-platform/pioneer-cache';
|
|
198
|
+
|
|
199
|
+
const cacheRegistry = new Map();
|
|
200
|
+
cacheRegistry.set('balance', balanceCache);
|
|
201
|
+
cacheRegistry.set('price', priceCache);
|
|
202
|
+
|
|
203
|
+
const worker = await startUnifiedWorker(
|
|
204
|
+
redis,
|
|
205
|
+
cacheRegistry,
|
|
206
|
+
'cache-refresh',
|
|
207
|
+
{
|
|
208
|
+
maxRetries: 3,
|
|
209
|
+
retryDelay: 5000,
|
|
210
|
+
pollInterval: 100
|
|
211
|
+
}
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
// Worker stats
|
|
215
|
+
const stats = await worker.getStats();
|
|
216
|
+
console.log(stats.queueLength); // Pending jobs
|
|
217
|
+
console.log(stats.isRunning); // Worker status
|
|
218
|
+
|
|
219
|
+
// Stop worker
|
|
220
|
+
await worker.stop();
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Health Monitoring
|
|
224
|
+
|
|
225
|
+
### Cache Health
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
const health = await balanceCache.getHealth();
|
|
229
|
+
|
|
230
|
+
console.log(health.status); // 'healthy' | 'degraded' | 'unhealthy'
|
|
231
|
+
console.log(health.queueInitialized); // true/false
|
|
232
|
+
console.log(health.redisConnected); // true/false
|
|
233
|
+
console.log(health.stats); // Cache statistics
|
|
234
|
+
console.log(health.issues); // Critical issues (unhealthy)
|
|
235
|
+
console.log(health.warnings); // Non-critical warnings (degraded)
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
**Status Levels:**
|
|
239
|
+
- `healthy` - All systems operational
|
|
240
|
+
- `degraded` - Non-critical issues (warnings)
|
|
241
|
+
- `unhealthy` - Critical issues requiring immediate attention
|
|
242
|
+
|
|
243
|
+
### Aggregate Health (CacheManager)
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
const health = await cacheManager.getHealth();
|
|
247
|
+
|
|
248
|
+
console.log(health.status); // Overall status
|
|
249
|
+
console.log(health.checks.balance); // Balance cache health
|
|
250
|
+
console.log(health.checks.price); // Price cache health
|
|
251
|
+
console.log(health.checks.transaction);// Transaction cache health
|
|
252
|
+
console.log(health.checks.worker); // Worker health
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## Critical Fixes Included
|
|
256
|
+
|
|
257
|
+
This module fixes 5 critical bugs from the legacy cache implementations:
|
|
258
|
+
|
|
259
|
+
### ✅ Fix #1: Cache Miss Blocking
|
|
260
|
+
**Problem:** Cache miss returned "0" immediately instead of waiting for fresh data
|
|
261
|
+
**Solution:** Added `blockOnMiss` config and `fetchFresh()` method that waits for source
|
|
262
|
+
|
|
263
|
+
### ✅ Fix #2: Redis TTL Management
|
|
264
|
+
**Problem:** Cache values never expired, causing stale data to stick forever
|
|
265
|
+
**Solution:** All cache writes include TTL: `redis.set(key, value, 'EX', ttlSeconds)`
|
|
266
|
+
|
|
267
|
+
### ✅ Fix #3: Loud Queue Failures
|
|
268
|
+
**Problem:** Queue initialization failures were silent (debug logs only)
|
|
269
|
+
**Solution:** Changed to ERROR logs and added validation checks
|
|
270
|
+
|
|
271
|
+
### ✅ Fix #4: Synchronous Fallback
|
|
272
|
+
**Problem:** When queue failed, no fallback - refresh never happened
|
|
273
|
+
**Solution:** Added immediate synchronous refresh when queue unavailable
|
|
274
|
+
|
|
275
|
+
### ✅ Fix #5: Health Monitoring
|
|
276
|
+
**Problem:** No way to detect cache issues in production
|
|
277
|
+
**Solution:** Added `getHealth()` with staleness detection and issue reporting
|
|
278
|
+
|
|
279
|
+
## Legacy Migration
|
|
280
|
+
|
|
281
|
+
The cache automatically migrates from old formats:
|
|
282
|
+
|
|
283
|
+
**Balance Cache Legacy:**
|
|
284
|
+
- `cache:balance:pubkey:networkId` → `balance_v2:caip:pubkey`
|
|
285
|
+
|
|
286
|
+
**Price Cache Legacy:**
|
|
287
|
+
- `coingecko:caip` → `price_v2:caip`
|
|
288
|
+
- `coincap:caip` → `price_v2:caip`
|
|
289
|
+
|
|
290
|
+
Legacy keys are automatically migrated to new format on first access.
|
|
291
|
+
|
|
292
|
+
## Advanced Usage
|
|
293
|
+
|
|
294
|
+
### Custom Cache Implementation
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
import { BaseCache, CacheConfig, CacheResult } from '@pioneer-platform/pioneer-cache';
|
|
298
|
+
|
|
299
|
+
interface MyData {
|
|
300
|
+
id: string;
|
|
301
|
+
value: number;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
class MyCache extends BaseCache<MyData> {
|
|
305
|
+
protected buildKey(params: Record<string, any>): string {
|
|
306
|
+
return `${this.config.keyPrefix}${params.id}`;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
protected async fetchFromSource(params: Record<string, any>): Promise<MyData> {
|
|
310
|
+
// Fetch from your source
|
|
311
|
+
const response = await fetch(`https://api.example.com/data/${params.id}`);
|
|
312
|
+
return response.json();
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
protected async getLegacyCached(params: Record<string, any>): Promise<MyData | null> {
|
|
316
|
+
// Optional: migrate from old cache format
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Use it
|
|
322
|
+
const myCache = new MyCache(redis, {
|
|
323
|
+
name: 'my-cache',
|
|
324
|
+
keyPrefix: 'my:',
|
|
325
|
+
ttl: 60 * 1000,
|
|
326
|
+
// ... other config
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
const data = await myCache.get({ id: '123' });
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### Manual Refresh
|
|
333
|
+
|
|
334
|
+
```typescript
|
|
335
|
+
// Force refresh specific item
|
|
336
|
+
await balanceCache.fetchFresh({ caip: 'eip155:1/slip44:60', pubkey: 'xpub123...' });
|
|
337
|
+
|
|
338
|
+
// Clear all caches
|
|
339
|
+
const cleared = await cacheManager.clearAll();
|
|
340
|
+
console.log(cleared.balance); // Number of keys deleted
|
|
341
|
+
console.log(cleared.price);
|
|
342
|
+
console.log(cleared.transaction);
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
## Testing
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
import { BalanceCache } from '@pioneer-platform/pioneer-cache';
|
|
349
|
+
|
|
350
|
+
describe('BalanceCache', () => {
|
|
351
|
+
let redis;
|
|
352
|
+
let balanceModule;
|
|
353
|
+
let cache;
|
|
354
|
+
|
|
355
|
+
beforeEach(() => {
|
|
356
|
+
redis = createMockRedis();
|
|
357
|
+
balanceModule = createMockBalanceModule();
|
|
358
|
+
cache = new BalanceCache(redis, balanceModule);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it('should block on first miss', async () => {
|
|
362
|
+
const result = await cache.getBalance('eip155:1/slip44:60', 'xpub123...');
|
|
363
|
+
expect(result.balance).not.toBe('0');
|
|
364
|
+
expect(result.balance).toBe('1234567890');
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it('should return stale and refresh async', async () => {
|
|
368
|
+
// First request - cache miss, blocks
|
|
369
|
+
await cache.getBalance('eip155:1/slip44:60', 'xpub123...');
|
|
370
|
+
|
|
371
|
+
// Second request - cache hit, returns stale, refreshes async
|
|
372
|
+
const result = await cache.getBalance('eip155:1/slip44:60', 'xpub123...');
|
|
373
|
+
expect(result.source).toBe('cache_stale');
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
it('should include TTL on all writes', async () => {
|
|
377
|
+
await cache.getBalance('eip155:1/slip44:60', 'xpub123...');
|
|
378
|
+
|
|
379
|
+
// Verify TTL was set
|
|
380
|
+
expect(redis.set).toHaveBeenCalledWith(
|
|
381
|
+
expect.any(String),
|
|
382
|
+
expect.any(String),
|
|
383
|
+
'EX',
|
|
384
|
+
expect.any(Number)
|
|
385
|
+
);
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
## Migration from Legacy Caches
|
|
391
|
+
|
|
392
|
+
### Before (pioneer-server)
|
|
393
|
+
|
|
394
|
+
```typescript
|
|
395
|
+
// services/balance-cache.service.ts
|
|
396
|
+
import { BalanceCacheService } from './services/balance-cache.service';
|
|
397
|
+
|
|
398
|
+
const balanceCache = new BalanceCacheService(redis, balanceModule);
|
|
399
|
+
const balance = await balanceCache.getBalance(caip, pubkey);
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
### After
|
|
403
|
+
|
|
404
|
+
```typescript
|
|
405
|
+
// Using CacheManager
|
|
406
|
+
import { CacheManager } from '@pioneer-platform/pioneer-cache';
|
|
407
|
+
|
|
408
|
+
const cacheManager = new CacheManager({
|
|
409
|
+
redis,
|
|
410
|
+
balanceModule,
|
|
411
|
+
markets,
|
|
412
|
+
enableBalanceCache: true,
|
|
413
|
+
enablePriceCache: true,
|
|
414
|
+
startWorkers: true
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
const balance = await cacheManager.getCache('balance')
|
|
418
|
+
.getBalance(caip, pubkey);
|
|
419
|
+
|
|
420
|
+
// Or direct import
|
|
421
|
+
import { BalanceCache } from '@pioneer-platform/pioneer-cache';
|
|
422
|
+
|
|
423
|
+
const balanceCache = new BalanceCache(redis, balanceModule);
|
|
424
|
+
const balance = await balanceCache.getBalance(caip, pubkey);
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
### Migration Checklist
|
|
428
|
+
|
|
429
|
+
- [ ] Install `@pioneer-platform/pioneer-cache`
|
|
430
|
+
- [ ] Replace `BalanceCacheService` imports with `BalanceCache`
|
|
431
|
+
- [ ] Replace `PriceCacheService` imports with `PriceCache`
|
|
432
|
+
- [ ] Update worker initialization to use `startUnifiedWorker()`
|
|
433
|
+
- [ ] Add health check endpoints using `getHealth()`
|
|
434
|
+
- [ ] Test thoroughly in staging environment
|
|
435
|
+
- [ ] Monitor for issues in production
|
|
436
|
+
- [ ] Remove old cache service files after validation
|
|
437
|
+
|
|
438
|
+
## Performance
|
|
439
|
+
|
|
440
|
+
**Token Reduction:** ~2,000 lines → ~1,800 lines (10% smaller)
|
|
441
|
+
**Code Duplication:** 70-80% duplicate code eliminated
|
|
442
|
+
**Maintenance:** Single BaseCache implementation for all fixes
|
|
443
|
+
**Type Safety:** Full TypeScript generics support
|
|
444
|
+
|
|
445
|
+
## License
|
|
446
|
+
|
|
447
|
+
MIT
|
|
448
|
+
|
|
449
|
+
## Contributing
|
|
450
|
+
|
|
451
|
+
See main Pioneer Platform contributing guidelines.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { CacheConfig, CachedValue, CacheResult, HealthCheckResult, CacheStats } from '../types';
|
|
2
|
+
export declare abstract class BaseCache<T> {
|
|
3
|
+
protected redis: any;
|
|
4
|
+
protected redisQueue: any;
|
|
5
|
+
protected config: CacheConfig;
|
|
6
|
+
protected queueInitialized: boolean;
|
|
7
|
+
protected TAG: string;
|
|
8
|
+
private cachedStats;
|
|
9
|
+
private cachedStatsTimestamp;
|
|
10
|
+
private readonly STATS_CACHE_TTL;
|
|
11
|
+
constructor(redis: any, config: CacheConfig);
|
|
12
|
+
/**
|
|
13
|
+
* Initialize Redis queue for background refresh
|
|
14
|
+
*/
|
|
15
|
+
private initializeQueue;
|
|
16
|
+
/**
|
|
17
|
+
* Main cache get method
|
|
18
|
+
* Implements stale-while-revalidate pattern with optional blocking
|
|
19
|
+
*/
|
|
20
|
+
get(params: Record<string, any>, waitForFresh?: boolean): Promise<CacheResult<T>>;
|
|
21
|
+
/**
|
|
22
|
+
* Get value from cache
|
|
23
|
+
*/
|
|
24
|
+
protected getCached(key: string): Promise<CachedValue<T> | null>;
|
|
25
|
+
/**
|
|
26
|
+
* Update cache with new value
|
|
27
|
+
* FIX #2: Always includes TTL
|
|
28
|
+
*/
|
|
29
|
+
updateCache(key: string, value: T, metadata?: Record<string, any>): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Trigger background refresh job
|
|
32
|
+
* FIX #3: Loud error logging
|
|
33
|
+
* FIX #4: Synchronous fallback for high-priority
|
|
34
|
+
*/
|
|
35
|
+
protected triggerAsyncRefresh(params: Record<string, any>, priority?: 'high' | 'normal' | 'low'): void;
|
|
36
|
+
/**
|
|
37
|
+
* Fetch fresh data and update cache
|
|
38
|
+
* FIX #1 & #4: Used for blocking requests and fallback
|
|
39
|
+
*/
|
|
40
|
+
fetchFresh(params: Record<string, any>): Promise<T>;
|
|
41
|
+
/**
|
|
42
|
+
* Migrate legacy cache value to new format
|
|
43
|
+
* FIX #2: Includes TTL
|
|
44
|
+
*/
|
|
45
|
+
protected migrateLegacyValue(key: string, value: T): void;
|
|
46
|
+
/**
|
|
47
|
+
* Get cache statistics
|
|
48
|
+
* @param forceRefresh - Skip cache and fetch fresh stats (for testing/debugging)
|
|
49
|
+
*/
|
|
50
|
+
getCacheStats(forceRefresh?: boolean): Promise<CacheStats>;
|
|
51
|
+
/**
|
|
52
|
+
* FIX #5: Health check
|
|
53
|
+
* @param forceRefresh - Force refresh stats (bypasses 30s cache)
|
|
54
|
+
*/
|
|
55
|
+
getHealth(forceRefresh?: boolean): Promise<HealthCheckResult>;
|
|
56
|
+
/**
|
|
57
|
+
* Clear all cache entries (use with caution)
|
|
58
|
+
*/
|
|
59
|
+
clearAll(): Promise<number>;
|
|
60
|
+
/**
|
|
61
|
+
* Build Redis key from parameters
|
|
62
|
+
* Each cache type implements its own key structure
|
|
63
|
+
*/
|
|
64
|
+
protected abstract buildKey(params: Record<string, any>): string;
|
|
65
|
+
/**
|
|
66
|
+
* Fetch data from source (blockchain, API, etc.)
|
|
67
|
+
* Each cache type implements its own data fetching
|
|
68
|
+
*/
|
|
69
|
+
protected abstract fetchFromSource(params: Record<string, any>): Promise<T>;
|
|
70
|
+
/**
|
|
71
|
+
* Get legacy cached value (optional)
|
|
72
|
+
* Each cache type can implement legacy key migration
|
|
73
|
+
*/
|
|
74
|
+
protected abstract getLegacyCached(params: Record<string, any>): Promise<T | null>;
|
|
75
|
+
}
|