@pioneer-platform/pioneer-cache 1.7.0 → 1.10.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.
@@ -0,0 +1,319 @@
1
+ # Actual Cache Configuration Audit
2
+ **Date**: 2025-12-27
3
+ **Location**: `/modules/pioneer/pioneer-cache/src/stores/`
4
+
5
+ ## Cache Configuration Reality Check
6
+
7
+ ### Balance Cache (CRITICAL - Your Issue)
8
+ **File**: `balance-cache.ts:77-79`
9
+
10
+ ```typescript
11
+ ttl: 0, // Ignored when enableTTL: false
12
+ staleThreshold: 5 * 60 * 1000, // 5 minutes - triggers background refresh
13
+ enableTTL: false, // NEVER EXPIRE - data persists forever
14
+ ```
15
+
16
+ **Reality**:
17
+ - ✅ **Stale threshold**: 5 minutes (triggers background refresh)
18
+ - ❌ **TTL**: DISABLED (no hard expiry)
19
+ - ⚠️ **Risk**: If background refresh fails, data becomes **infinitely stale**
20
+
21
+ **Your Case**:
22
+ - Balance is 22.5 hours old (1,350 minutes)
23
+ - Should have refreshed 270 times already (every 5 minutes)
24
+ - Background refresh **completely broken**
25
+
26
+ ---
27
+
28
+ ### Price Cache
29
+ **File**: `price-cache.ts:32-34`
30
+
31
+ ```typescript
32
+ ttl: 0, // Ignored when enableTTL: false
33
+ staleThreshold: 30 * 60 * 1000, // 30 minutes - triggers background refresh
34
+ enableTTL: false, // NEVER EXPIRE - data persists forever
35
+ ```
36
+
37
+ **Reality**:
38
+ - ✅ **Stale threshold**: 30 minutes
39
+ - ❌ **TTL**: DISABLED
40
+ - ⚠️ **Risk**: Stale prices can persist indefinitely
41
+
42
+ ---
43
+
44
+ ### Portfolio Cache
45
+ **File**: `portfolio-cache.ts:57-59`
46
+
47
+ ```typescript
48
+ ttl: 0, // Ignored when enableTTL: false
49
+ staleThreshold: 5 * 60 * 1000, // 5 minutes - triggers background refresh
50
+ enableTTL: false, // NEVER EXPIRE - data persists forever
51
+ ```
52
+
53
+ **Reality**:
54
+ - ✅ **Stale threshold**: 5 minutes
55
+ - ❌ **TTL**: DISABLED
56
+ - ⚠️ **Risk**: Same as balance cache
57
+
58
+ ---
59
+
60
+ ### Staking Cache (ONLY ONE WITH TTL!)
61
+ **File**: `staking-cache.ts:59-61`
62
+
63
+ ```typescript
64
+ ttl: 5 * 60 * 1000, // 5 minutes - staking changes slowly
65
+ staleThreshold: 2 * 60 * 1000, // Refresh after 2 minutes
66
+ enableTTL: true, // Enable expiration ✅
67
+ ```
68
+
69
+ **Reality**:
70
+ - ✅ **Stale threshold**: 2 minutes (soft refresh)
71
+ - ✅ **TTL**: 5 minutes (hard expiry) ✅
72
+ - ✅ **Safety net**: Even if background refresh fails, data expires in 5 minutes
73
+
74
+ ---
75
+
76
+ ### Zapper Cache (Most Expensive)
77
+ **File**: `zapper-cache.ts:123-125`
78
+
79
+ ```typescript
80
+ ttl: 0, // Ignored when enableTTL: false
81
+ staleThreshold: 24 * 60 * 60 * 1000, // 24 hours - ONLY refresh after this
82
+ enableTTL: false, // NEVER EXPIRE - data persists forever
83
+ ```
84
+
85
+ **Reality**:
86
+ - ✅ **Stale threshold**: 24 hours (expensive API)
87
+ - ❌ **TTL**: DISABLED
88
+ - ⚠️ **Risk**: If refresh fails, could be stale for days/weeks
89
+
90
+ ---
91
+
92
+ ## The Pattern
93
+
94
+ **All caches except Staking**:
95
+ ```
96
+ enableTTL: false → No hard expiry, relies 100% on background refresh
97
+ ```
98
+
99
+ **Design Intent**: "Stale-while-revalidate" pattern
100
+ 1. Return stale data instantly (fast UX)
101
+ 2. Trigger background refresh if older than threshold
102
+ 3. Cache persists forever for instant loads
103
+
104
+ **Critical Flaw**: **NO SAFETY NET** if background refresh breaks!
105
+
106
+ ---
107
+
108
+ ## Why Background Refresh is Broken
109
+
110
+ ### The Queue System
111
+ **File**: `base-cache.ts:334-347`
112
+
113
+ ```typescript
114
+ protected triggerAsyncRefresh(params, priority = 'normal'): void {
115
+ if (!this.config.enableQueue) {
116
+ return; // Queue disabled
117
+ }
118
+
119
+ if (!this.queueInitialized || !this.redisQueue) {
120
+ log.error(tag, `❌ QUEUE NOT INITIALIZED! Cannot refresh`);
121
+ log.error(tag, `Background refresh is BROKEN - cache will NOT update!`);
122
+ return; // ← SILENT FAILURE (logs only, no alerts)
123
+ }
124
+
125
+ // Queue the refresh job...
126
+ }
127
+ ```
128
+
129
+ **Possible Failures**:
130
+ 1. ❌ Queue never initialized at startup
131
+ 2. ❌ Workers never started
132
+ 3. ❌ Workers started but crashed
133
+ 4. ❌ Jobs queued but never processed
134
+ 5. ❌ Redis connection issues
135
+
136
+ **Current Visibility**: ZERO (only logs, no monitoring)
137
+
138
+ ---
139
+
140
+ ## Recommended TTL Values (Safety Net)
141
+
142
+ ### Balance Cache
143
+ ```typescript
144
+ ttl: 15 * 60 * 1000, // 15 minutes - reasonable balance freshness
145
+ staleThreshold: 5 * 60 * 1000, // 5 minutes - trigger soft refresh
146
+ enableTTL: true, // Enable safety net ✅
147
+ ```
148
+
149
+ **Reasoning**:
150
+ - Background refresh should hit at 5 minutes
151
+ - TTL expires at 15 minutes if refresh fails
152
+ - Max staleness: 15 minutes (vs current: infinite)
153
+
154
+ ### Price Cache
155
+ ```typescript
156
+ ttl: 60 * 60 * 1000, // 60 minutes - prices don't change that fast
157
+ staleThreshold: 30 * 60 * 1000, // 30 minutes - trigger soft refresh
158
+ enableTTL: true, // Enable safety net ✅
159
+ ```
160
+
161
+ **Reasoning**:
162
+ - Crypto prices don't change drastically in 1 hour
163
+ - Background refresh at 30 minutes
164
+ - Max staleness: 60 minutes
165
+
166
+ ### Portfolio Cache
167
+ ```typescript
168
+ ttl: 15 * 60 * 1000, // 15 minutes - same as balance
169
+ staleThreshold: 5 * 60 * 1000, // 5 minutes - trigger soft refresh
170
+ enableTTL: true, // Enable safety net ✅
171
+ ```
172
+
173
+ ### Zapper Cache
174
+ ```typescript
175
+ ttl: 48 * 60 * 60 * 1000, // 48 hours - expensive API
176
+ staleThreshold: 24 * 60 * 60 * 1000, // 24 hours - trigger soft refresh
177
+ enableTTL: true, // Enable safety net ✅
178
+ ```
179
+
180
+ **Reasoning**:
181
+ - Expensive API, minimize calls
182
+ - Background refresh at 24 hours
183
+ - Max staleness: 48 hours (vs current: infinite)
184
+
185
+ ---
186
+
187
+ ## Critical Discord Alerts Needed
188
+
189
+ ### 1. Queue Initialization Failure (CRITICAL)
190
+ ```typescript
191
+ if (!this.queueInitialized) {
192
+ await discord.alert({
193
+ severity: 'critical',
194
+ title: '🚨 Cache Queue Failed to Initialize',
195
+ message: `Cache: ${this.config.name}\nBackground refresh BROKEN!`
196
+ });
197
+ }
198
+ ```
199
+
200
+ ### 2. High Staleness Rate (WARNING)
201
+ ```typescript
202
+ // Monitor every 5 minutes
203
+ if (stats.stalenessRate > 50) {
204
+ await discord.alert({
205
+ severity: 'warning',
206
+ title: '⚠️ High Cache Staleness',
207
+ message: `${this.config.name}: ${stats.stalenessRate}% stale\nWorkers may be failing`
208
+ });
209
+ }
210
+ ```
211
+
212
+ ### 3. Excessive Cache Age (CRITICAL)
213
+ ```typescript
214
+ // Check max age in cache
215
+ const maxAge = Math.max(...Object.values(stats.ages));
216
+ const maxAgeHours = maxAge / (60 * 60 * 1000);
217
+
218
+ if (maxAgeHours > 1) { // More than 1 hour old
219
+ await discord.alert({
220
+ severity: 'critical',
221
+ title: '🚨 Extremely Stale Cache Data',
222
+ message: `${this.config.name}: Data ${maxAgeHours.toFixed(1)}h old!\nRefresh completely broken`
223
+ });
224
+ }
225
+ ```
226
+
227
+ ### 4. Worker Crash Detection
228
+ ```typescript
229
+ // On worker exit
230
+ process.on('exit', async () => {
231
+ await discord.alert({
232
+ severity: 'critical',
233
+ title: '🚨 Cache Worker Crashed',
234
+ message: 'Background refresh workers stopped unexpectedly'
235
+ });
236
+ });
237
+ ```
238
+
239
+ ---
240
+
241
+ ## Action Items
242
+
243
+ ### Immediate (Today)
244
+ 1. ✅ Add TTL to balance cache (15 min)
245
+ 2. ✅ Add TTL to portfolio cache (15 min)
246
+ 3. ✅ Add Discord alerts to queue failures
247
+ 4. ✅ Clear your stale Bitcoin balance
248
+
249
+ ### Short-term (This Week)
250
+ 1. Diagnose why workers aren't running
251
+ 2. Add health monitoring (check staleness every 5 min)
252
+ 3. Add startup validation (fail fast if queue doesn't init)
253
+ 4. Test with cleared cache to verify refresh works
254
+
255
+ ### Long-term (Next Sprint)
256
+ 1. Implement comprehensive cache monitoring dashboard
257
+ 2. Add Prometheus metrics for cache health
258
+ 3. Document cache incident runbook
259
+ 4. Consider Redis TTL fallback for all caches
260
+
261
+ ---
262
+
263
+ ## Testing the Fix
264
+
265
+ ### Step 1: Clear Stale Cache
266
+ ```bash
267
+ redis-cli DEL "balance_v2:bip122:000000000019d6689c085ae165831e93/slip44:0:d66c81ddb395633630fbd6949e5cb0b4"
268
+ ```
269
+
270
+ ### Step 2: Fetch Fresh Balance
271
+ ```bash
272
+ curl -X POST http://localhost:9001/api/balances \
273
+ -H "Content-Type: application/json" \
274
+ -d '{
275
+ "pubkeys": [{
276
+ "caip": "bip122:000000000019d6689c085ae165831e93/slip44:0",
277
+ "pubkey": "zpub6rPBGeYJtMkALyZgM9AQ8cYQnFprAfetXQeDrSjWpCEPnhfC8tpTDoVzdTrLKg7box2zncYFptaWPCaR8wW4FCiULBU6y77RtqFaGjs9mN6"
278
+ }]
279
+ }'
280
+ ```
281
+
282
+ ### Step 3: Verify Balance Correct
283
+ ```json
284
+ {
285
+ "balance": "0.00002285", // ✅ Should be correct now
286
+ "valueUsd": "2.00",
287
+ "fetchedAt": 1735346400000 // ✅ Should be recent
288
+ }
289
+ ```
290
+
291
+ ### Step 4: Wait 16 Minutes
292
+ - At 5min: Background refresh should trigger
293
+ - At 15min: If refresh failed, TTL expires and forces refetch
294
+
295
+ ### Step 5: Check Cache Age
296
+ ```bash
297
+ redis-cli GET "balance_v2:bip122:000000000019d6689c085ae165831e93/slip44:0:d66c81ddb395633630fbd6949e5cb0b4"
298
+ ```
299
+
300
+ Should be <15 minutes old, or not exist (expired via TTL).
301
+
302
+ ---
303
+
304
+ ## Summary
305
+
306
+ **Current State**:
307
+ - Balance cache: NO TTL, relies 100% on background refresh
308
+ - Background refresh: COMPLETELY BROKEN (22.5 hours stale)
309
+ - Alerts: NONE (silent failures)
310
+
311
+ **Proposed State**:
312
+ - Balance cache: 15-minute TTL as safety net
313
+ - Background refresh: 5-minute soft refresh
314
+ - Alerts: Discord alerts on all failures
315
+ - Monitoring: Health checks every 5 minutes
316
+
317
+ **Max Staleness**:
318
+ - Current: **INFINITE** (if refresh fails)
319
+ - Proposed: **15 minutes** (TTL safety net)
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # @pioneer-platform/pioneer-cache
2
2
 
3
+ ## 1.10.0
4
+
5
+ ### Minor Changes
6
+
7
+ - chore: Merge pull request #10 from coinmastersguild/feature/fix-blockbook-websocket-subscriptions
8
+
9
+ ## 1.9.0
10
+
11
+ ### Minor Changes
12
+
13
+ - Merge pull request #10 from coinmastersguild/feature/fix-blockbook-websocket-subscriptions
14
+
15
+ ## 1.8.1
16
+
17
+ ### Patch Changes
18
+
19
+ - fix: Filter xpubs from Blockbook WebSocket subscriptions
20
+
21
+ ## 1.8.0
22
+
23
+ ### Minor Changes
24
+
25
+ - chore: chore: chore: chore: chore: chore: feat(pioneer): implement end-to-end Solana transaction signing
26
+
3
27
  ## 1.7.0
4
28
 
5
29
  ### Minor Changes
@@ -293,6 +293,17 @@ class BaseCache {
293
293
  const key = this.buildKey(params);
294
294
  log.error(tag, `❌ QUEUE NOT INITIALIZED! Cannot refresh ${key}`);
295
295
  log.error(tag, `Background refresh is BROKEN - cache will NOT update!`);
296
+ // Send Discord alert for queue failure (if handler configured)
297
+ if (this.config.alertHandler?.onQueueFailure) {
298
+ this.config.alertHandler.onQueueFailure(this.config.name, {
299
+ key,
300
+ queueInitialized: this.queueInitialized,
301
+ redisQueue: !!this.redisQueue,
302
+ enableQueue: this.config.enableQueue
303
+ }).catch(err => {
304
+ log.error(tag, 'Failed to send queue failure alert:', err);
305
+ });
306
+ }
296
307
  // FIX #4: Synchronous fallback for high-priority (only if useSyncFallback is enabled)
297
308
  // Default: use sync fallback only for blocking caches (blockOnMiss=true)
298
309
  const shouldUseSyncFallback = this.config.useSyncFallback !== undefined
@@ -3,7 +3,9 @@ import { PriceCache } from '../stores/price-cache';
3
3
  import { PortfolioCache } from '../stores/portfolio-cache';
4
4
  import { TransactionCache } from '../stores/transaction-cache';
5
5
  import { StakingCache } from '../stores/staking-cache';
6
+ import { ZapperCache } from '../stores/zapper-cache';
6
7
  import type { HealthCheckResult } from '../types';
8
+ import type { CacheAlertHandler } from '../types';
7
9
  /**
8
10
  * Configuration for CacheManager
9
11
  */
@@ -13,11 +15,14 @@ export interface CacheManagerConfig {
13
15
  balanceModule?: any;
14
16
  markets?: any;
15
17
  networkModules?: Map<string, any>;
18
+ zapperClient?: any;
19
+ alertHandler?: CacheAlertHandler;
16
20
  enableBalanceCache?: boolean;
17
21
  enablePriceCache?: boolean;
18
22
  enablePortfolioCache?: boolean;
19
23
  enableTransactionCache?: boolean;
20
24
  enableStakingCache?: boolean;
25
+ enableZapperCache?: boolean;
21
26
  startWorkers?: boolean;
22
27
  }
23
28
  /**
@@ -31,8 +36,14 @@ export declare class CacheManager {
31
36
  private portfolioCache?;
32
37
  private transactionCache?;
33
38
  private stakingCache?;
39
+ private zapperCache?;
34
40
  private workers;
35
41
  constructor(config: CacheManagerConfig);
42
+ /**
43
+ * Purge all pending jobs from the cache refresh queue
44
+ * Used in local dev to clear stale jobs on startup
45
+ */
46
+ purgeQueue(queueName?: string): Promise<void>;
36
47
  /**
37
48
  * Start background refresh workers for all caches
38
49
  */
@@ -57,11 +68,12 @@ export declare class CacheManager {
57
68
  portfolio: PortfolioCache | undefined;
58
69
  transaction: TransactionCache | undefined;
59
70
  staking: StakingCache | undefined;
71
+ zapper: ZapperCache | undefined;
60
72
  };
61
73
  /**
62
74
  * Get specific cache by name
63
75
  */
64
- getCache(name: 'balance' | 'price' | 'portfolio' | 'transaction' | 'staking'): BalanceCache | PriceCache | PortfolioCache | TransactionCache | StakingCache | undefined;
76
+ getCache(name: 'balance' | 'price' | 'portfolio' | 'transaction' | 'staking' | 'zapper'): BalanceCache | PriceCache | PortfolioCache | TransactionCache | StakingCache | ZapperCache | undefined;
65
77
  /**
66
78
  * Clear all caches (use with caution!)
67
79
  */
@@ -71,5 +83,6 @@ export declare class CacheManager {
71
83
  portfolio?: number;
72
84
  transaction?: number;
73
85
  staking?: number;
86
+ zapper?: number;
74
87
  }>;
75
88
  }
@@ -12,6 +12,7 @@ const price_cache_1 = require("../stores/price-cache");
12
12
  const portfolio_cache_1 = require("../stores/portfolio-cache");
13
13
  const transaction_cache_1 = require("../stores/transaction-cache");
14
14
  const staking_cache_1 = require("../stores/staking-cache");
15
+ const zapper_cache_1 = require("../stores/zapper-cache");
15
16
  const refresh_worker_1 = require("../workers/refresh-worker");
16
17
  const log = require('@pioneer-platform/loggerdog')();
17
18
  const TAG = ' | CacheManager | ';
@@ -25,17 +26,23 @@ class CacheManager {
25
26
  this.redisQueue = config.redisQueue || config.redis; // Fallback to main redis if not provided
26
27
  // Initialize Balance Cache
27
28
  if (config.enableBalanceCache !== false && config.balanceModule) {
28
- this.balanceCache = new balance_cache_1.BalanceCache(this.redis, config.balanceModule);
29
+ this.balanceCache = new balance_cache_1.BalanceCache(this.redis, config.balanceModule, {
30
+ alertHandler: config.alertHandler
31
+ });
29
32
  log.info(TAG, '✅ Balance cache initialized');
30
33
  }
31
34
  // Initialize Price Cache
32
35
  if (config.enablePriceCache !== false && config.markets) {
33
- this.priceCache = new price_cache_1.PriceCache(this.redis, config.markets);
36
+ this.priceCache = new price_cache_1.PriceCache(this.redis, config.markets, {
37
+ alertHandler: config.alertHandler
38
+ });
34
39
  log.info(TAG, '✅ Price cache initialized');
35
40
  }
36
41
  // Initialize Portfolio Cache
37
42
  if (config.enablePortfolioCache !== false && config.balanceModule && config.markets) {
38
- this.portfolioCache = new portfolio_cache_1.PortfolioCache(this.redis, config.balanceModule, config.markets);
43
+ this.portfolioCache = new portfolio_cache_1.PortfolioCache(this.redis, config.balanceModule, config.markets, {
44
+ alertHandler: config.alertHandler
45
+ });
39
46
  log.info(TAG, '✅ Portfolio cache initialized');
40
47
  }
41
48
  // Initialize Transaction Cache
@@ -45,17 +52,57 @@ class CacheManager {
45
52
  }
46
53
  // Initialize Staking Cache (with markets module for price enrichment)
47
54
  if (config.enableStakingCache !== false && config.networkModules && config.networkModules.size > 0) {
48
- this.stakingCache = new staking_cache_1.StakingCache(this.redis, config.networkModules, config.markets);
55
+ this.stakingCache = new staking_cache_1.StakingCache(this.redis, config.networkModules, config.markets, {
56
+ alertHandler: config.alertHandler
57
+ });
49
58
  log.info(TAG, '✅ Staking cache initialized');
50
59
  }
60
+ // Initialize Zapper Cache
61
+ if (config.enableZapperCache !== false && config.zapperClient) {
62
+ this.zapperCache = new zapper_cache_1.ZapperCache(this.redis, config.zapperClient, {
63
+ alertHandler: config.alertHandler
64
+ });
65
+ log.info(TAG, '✅ Zapper cache initialized');
66
+ }
51
67
  // Auto-start workers if requested
52
68
  if (config.startWorkers) {
53
- setImmediate(() => {
54
- this.startWorkers();
69
+ setImmediate(async () => {
70
+ try {
71
+ await this.startWorkers();
72
+ }
73
+ catch (error) {
74
+ log.error(TAG, '❌ CRITICAL: Failed to start cache workers:', error);
75
+ // Send alert if handler configured
76
+ if (config.alertHandler?.onQueueFailure) {
77
+ await config.alertHandler.onQueueFailure('CacheManager', {
78
+ error: error instanceof Error ? error.message : String(error),
79
+ stage: 'worker_startup'
80
+ }).catch(alertErr => {
81
+ log.error(TAG, 'Failed to send worker startup failure alert:', alertErr);
82
+ });
83
+ }
84
+ }
55
85
  });
56
86
  }
57
87
  log.info(TAG, '✅ CacheManager initialized');
58
88
  }
89
+ /**
90
+ * Purge all pending jobs from the cache refresh queue
91
+ * Used in local dev to clear stale jobs on startup
92
+ */
93
+ async purgeQueue(queueName = 'cache-refresh') {
94
+ const tag = TAG + 'purgeQueue | ';
95
+ try {
96
+ log.info(tag, `Purging queue: ${queueName}...`);
97
+ // Delete the entire queue (Redis list)
98
+ const deleted = await this.redis.del(queueName);
99
+ log.info(tag, `✅ Queue purged: ${queueName} (${deleted} keys deleted)`);
100
+ }
101
+ catch (error) {
102
+ log.error(tag, `❌ Failed to purge queue ${queueName}:`, error);
103
+ throw error;
104
+ }
105
+ }
59
106
  /**
60
107
  * Start background refresh workers for all caches
61
108
  */
@@ -77,6 +124,9 @@ class CacheManager {
77
124
  if (this.stakingCache) {
78
125
  cacheRegistry.set('staking', this.stakingCache);
79
126
  }
127
+ if (this.zapperCache) {
128
+ cacheRegistry.set('zapper', this.zapperCache);
129
+ }
80
130
  // Start unified worker if we have any caches with queues
81
131
  if (cacheRegistry.size > 0) {
82
132
  const worker = await (0, refresh_worker_1.startUnifiedWorker)(this.redisQueue, // Use dedicated queue client for blocking operations
@@ -175,6 +225,17 @@ class CacheManager {
175
225
  warnings.push(...stakingHealth.warnings.map(w => `Staking: ${w}`));
176
226
  }
177
227
  }
228
+ // Check zapper cache
229
+ if (this.zapperCache) {
230
+ const zapperHealth = await this.zapperCache.getHealth(forceRefresh);
231
+ checks.zapper = zapperHealth;
232
+ if (zapperHealth.status === 'unhealthy') {
233
+ issues.push(...zapperHealth.issues.map(i => `Zapper: ${i}`));
234
+ }
235
+ else if (zapperHealth.status === 'degraded') {
236
+ warnings.push(...zapperHealth.warnings.map(w => `Zapper: ${w}`));
237
+ }
238
+ }
178
239
  // Check workers
179
240
  for (const worker of this.workers) {
180
241
  const workerStats = await worker.getStats();
@@ -236,7 +297,8 @@ class CacheManager {
236
297
  price: this.priceCache,
237
298
  portfolio: this.portfolioCache,
238
299
  transaction: this.transactionCache,
239
- staking: this.stakingCache
300
+ staking: this.stakingCache,
301
+ zapper: this.zapperCache
240
302
  };
241
303
  }
242
304
  /**
@@ -254,6 +316,8 @@ class CacheManager {
254
316
  return this.transactionCache;
255
317
  case 'staking':
256
318
  return this.stakingCache;
319
+ case 'zapper':
320
+ return this.zapperCache;
257
321
  default:
258
322
  return undefined;
259
323
  }
@@ -280,6 +344,9 @@ class CacheManager {
280
344
  if (this.stakingCache) {
281
345
  result.staking = await this.stakingCache.clearAll();
282
346
  }
347
+ if (this.zapperCache) {
348
+ result.zapper = await this.zapperCache.clearAll();
349
+ }
283
350
  log.info(tag, 'Cleared all caches:', result);
284
351
  return result;
285
352
  }
package/dist/index.d.ts CHANGED
@@ -5,11 +5,13 @@ export { PriceCache } from './stores/price-cache';
5
5
  export { PortfolioCache } from './stores/portfolio-cache';
6
6
  export { TransactionCache } from './stores/transaction-cache';
7
7
  export { StakingCache } from './stores/staking-cache';
8
+ export { ZapperCache } from './stores/zapper-cache';
8
9
  export { RefreshWorker, startUnifiedWorker } from './workers/refresh-worker';
9
- export type { CacheConfig, CachedValue, CacheResult, RefreshJob, HealthCheckResult, CacheStats } from './types';
10
+ export type { CacheConfig, CachedValue, CacheResult, RefreshJob, HealthCheckResult, CacheStats, CacheAlertHandler } from './types';
10
11
  export type { BalanceData } from './stores/balance-cache';
11
12
  export type { PriceData } from './stores/price-cache';
12
13
  export type { PortfolioData, ChartData } from './stores/portfolio-cache';
13
14
  export type { StakingPosition } from './stores/staking-cache';
15
+ export type { ZapperPortfolioData, ZapperNFTData, ZapperAppsData, ZapperTotalsData } from './stores/zapper-cache';
14
16
  export type { CacheManagerConfig } from './core/cache-manager';
15
17
  export type { WorkerConfig } from './workers/refresh-worker';
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@
6
6
  Provides stale-while-revalidate caching with TTL, background refresh, and health monitoring.
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.startUnifiedWorker = exports.RefreshWorker = exports.StakingCache = exports.TransactionCache = exports.PortfolioCache = exports.PriceCache = exports.BalanceCache = exports.CacheManager = exports.BaseCache = void 0;
9
+ exports.startUnifiedWorker = exports.RefreshWorker = exports.ZapperCache = exports.StakingCache = exports.TransactionCache = exports.PortfolioCache = exports.PriceCache = exports.BalanceCache = exports.CacheManager = exports.BaseCache = void 0;
10
10
  // Core exports
11
11
  var base_cache_1 = require("./core/base-cache");
12
12
  Object.defineProperty(exports, "BaseCache", { enumerable: true, get: function () { return base_cache_1.BaseCache; } });
@@ -23,6 +23,8 @@ var transaction_cache_1 = require("./stores/transaction-cache");
23
23
  Object.defineProperty(exports, "TransactionCache", { enumerable: true, get: function () { return transaction_cache_1.TransactionCache; } });
24
24
  var staking_cache_1 = require("./stores/staking-cache");
25
25
  Object.defineProperty(exports, "StakingCache", { enumerable: true, get: function () { return staking_cache_1.StakingCache; } });
26
+ var zapper_cache_1 = require("./stores/zapper-cache");
27
+ Object.defineProperty(exports, "ZapperCache", { enumerable: true, get: function () { return zapper_cache_1.ZapperCache; } });
26
28
  // Worker exports
27
29
  var refresh_worker_1 = require("./workers/refresh-worker");
28
30
  Object.defineProperty(exports, "RefreshWorker", { enumerable: true, get: function () { return refresh_worker_1.RefreshWorker; } });
@@ -19,6 +19,8 @@ export interface BalanceData {
19
19
  fetchedAt?: number;
20
20
  fetchedAtISO?: string;
21
21
  dataSource?: string;
22
+ derivedAddresses?: string[];
23
+ tokens?: any[];
22
24
  }
23
25
  /**
24
26
  * BalanceCache - Caches blockchain balance data
@@ -169,7 +169,10 @@ class BalanceCache extends base_cache_1.BaseCache {
169
169
  precision: assetInfo.decimals, // Set precision to decimals for compatibility
170
170
  fetchedAt: now,
171
171
  fetchedAtISO: new Date(now).toISOString(),
172
- dataSource
172
+ dataSource,
173
+ // CRITICAL: Pass through derived addresses and tokens from balance module for xpub subscription
174
+ derivedAddresses: balanceInfo.derivedAddresses,
175
+ tokens: balanceInfo.tokens
173
176
  };
174
177
  }
175
178
  catch (error) {