@pioneer-platform/pioneer-cache 1.0.0 โ 1.0.2
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/CHANGELOG.md +13 -0
- package/dist/core/base-cache.d.ts +3 -0
- package/dist/core/base-cache.js +107 -23
- package/dist/core/cache-manager.d.ts +6 -1
- package/dist/core/cache-manager.js +26 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -1
- package/dist/stores/balance-cache.d.ts +1 -0
- package/dist/stores/balance-cache.js +63 -10
- package/dist/stores/portfolio-cache.d.ts +79 -0
- package/dist/stores/portfolio-cache.js +189 -0
- package/dist/stores/price-cache.d.ts +2 -1
- package/dist/stores/price-cache.js +41 -36
- package/dist/types/index.d.ts +1 -0
- package/package.json +4 -4
- package/src/core/base-cache.ts +121 -23
- package/src/core/cache-manager.ts +34 -2
- package/src/index.ts +2 -0
- package/src/stores/balance-cache.ts +69 -10
- package/src/stores/portfolio-cache.ts +244 -0
- package/src/stores/price-cache.ts +42 -36
- package/src/types/index.ts +1 -0
- package/test/redis-persistence.test.ts +265 -0
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/*
|
|
2
|
+
CRITICAL: Redis Persistence Diagnostic Test
|
|
3
|
+
|
|
4
|
+
This test validates that Redis writes are ACTUALLY persisting to the Redis server.
|
|
5
|
+
It will catch the bug where writes appear successful but data isn't stored.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it, expect, beforeAll, afterAll } from 'bun:test';
|
|
9
|
+
|
|
10
|
+
// @ts-ignore
|
|
11
|
+
const TAG = ' | REDIS-PERSISTENCE-TEST | ';
|
|
12
|
+
|
|
13
|
+
describe('Redis Persistence Validation', () => {
|
|
14
|
+
let redis: any;
|
|
15
|
+
let redisModule: any;
|
|
16
|
+
|
|
17
|
+
beforeAll(async () => {
|
|
18
|
+
console.log(TAG, '๐ Starting Redis persistence diagnostic...');
|
|
19
|
+
|
|
20
|
+
// Force load REAL Redis, not mock
|
|
21
|
+
delete require.cache[require.resolve('@pioneer-platform/default-redis')];
|
|
22
|
+
const originalEnv = process.env.NODE_ENV;
|
|
23
|
+
delete process.env.NODE_ENV; // Ensure we don't use mock
|
|
24
|
+
|
|
25
|
+
redisModule = require('@pioneer-platform/default-redis');
|
|
26
|
+
redis = redisModule.redis;
|
|
27
|
+
|
|
28
|
+
console.log(TAG, 'โ
Redis module loaded');
|
|
29
|
+
console.log(TAG, ` NODE_ENV: ${process.env.NODE_ENV || '(not set)'}`);
|
|
30
|
+
console.log(TAG, ` Redis constructor: ${redis.constructor.name}`);
|
|
31
|
+
console.log(TAG, ` Is mock?: ${redis.constructor.name === 'RedisMock' ? 'โ ๏ธ YES - THIS IS THE BUG!' : 'โ
NO'}`);
|
|
32
|
+
|
|
33
|
+
// Test basic connection
|
|
34
|
+
const ping = await redis.ping();
|
|
35
|
+
console.log(TAG, ` Ping: ${ping}`);
|
|
36
|
+
|
|
37
|
+
// Verify connection details
|
|
38
|
+
const options = redis.options;
|
|
39
|
+
console.log(TAG, ` Host: ${options.host}:${options.port}`);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
afterAll(async () => {
|
|
43
|
+
// Clean up test keys
|
|
44
|
+
const testKeys = await redis.keys('test:*');
|
|
45
|
+
if (testKeys.length > 0) {
|
|
46
|
+
await redis.del(...testKeys);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Don't disconnect - other tests might be using it
|
|
50
|
+
console.log(TAG, '๐งน Cleaned up test keys');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should verify Redis is real not mock', async () => {
|
|
54
|
+
expect(redis.constructor.name).not.toBe('RedisMock');
|
|
55
|
+
expect(redis.constructor.name).toBe('Redis');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should write and immediately read back the same value', async () => {
|
|
59
|
+
const key = 'test:simple:write';
|
|
60
|
+
const value = JSON.stringify({ test: 'data', timestamp: Date.now() });
|
|
61
|
+
|
|
62
|
+
// Write
|
|
63
|
+
await redis.set(key, value);
|
|
64
|
+
console.log(TAG, `โ
Wrote key: ${key}`);
|
|
65
|
+
|
|
66
|
+
// Read immediately
|
|
67
|
+
const result = await redis.get(key);
|
|
68
|
+
console.log(TAG, `โ
Read back: ${result ? 'SUCCESS' : 'โ FAILED - NULL'}`);
|
|
69
|
+
|
|
70
|
+
expect(result).toBe(value);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should persist data with TTL', async () => {
|
|
74
|
+
const key = 'test:ttl:write';
|
|
75
|
+
const value = JSON.stringify({ test: 'ttl-data', timestamp: Date.now() });
|
|
76
|
+
const ttlSeconds = 60;
|
|
77
|
+
|
|
78
|
+
// Write with TTL
|
|
79
|
+
await redis.set(key, value, 'EX', ttlSeconds);
|
|
80
|
+
console.log(TAG, `โ
Wrote key with ${ttlSeconds}s TTL: ${key}`);
|
|
81
|
+
|
|
82
|
+
// Verify it exists
|
|
83
|
+
const result = await redis.get(key);
|
|
84
|
+
expect(result).toBe(value);
|
|
85
|
+
|
|
86
|
+
// Check TTL is set
|
|
87
|
+
const ttl = await redis.ttl(key);
|
|
88
|
+
console.log(TAG, ` TTL remaining: ${ttl}s`);
|
|
89
|
+
expect(ttl).toBeGreaterThan(0);
|
|
90
|
+
expect(ttl).toBeLessThanOrEqual(ttlSeconds);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should detect if writes disappear after small delay', async () => {
|
|
94
|
+
const key = 'test:delayed:read';
|
|
95
|
+
const value = JSON.stringify({ test: 'delayed-data', timestamp: Date.now() });
|
|
96
|
+
|
|
97
|
+
// Write
|
|
98
|
+
await redis.set(key, value);
|
|
99
|
+
console.log(TAG, `โ
Wrote key: ${key}`);
|
|
100
|
+
|
|
101
|
+
// Wait 100ms
|
|
102
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
103
|
+
|
|
104
|
+
// Read after delay
|
|
105
|
+
const result = await redis.get(key);
|
|
106
|
+
console.log(TAG, ` Read after 100ms: ${result ? 'SUCCESS' : 'โ FAILED - DATA DISAPPEARED!'}`);
|
|
107
|
+
|
|
108
|
+
expect(result).toBe(value);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should verify balance_v2 cache format works', async () => {
|
|
112
|
+
const key = 'balance_v2:eip155:1:0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb';
|
|
113
|
+
const cacheValue = {
|
|
114
|
+
value: {
|
|
115
|
+
caip: 'eip155:1/slip44:60',
|
|
116
|
+
pubkey: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
|
|
117
|
+
balance: '123456789'
|
|
118
|
+
},
|
|
119
|
+
timestamp: Date.now(),
|
|
120
|
+
source: 'test',
|
|
121
|
+
lastUpdated: new Date().toISOString()
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const ttlSeconds = 300;
|
|
125
|
+
|
|
126
|
+
// Write balance cache
|
|
127
|
+
await redis.set(key, JSON.stringify(cacheValue), 'EX', ttlSeconds);
|
|
128
|
+
console.log(TAG, `โ
Wrote balance cache: ${key}`);
|
|
129
|
+
|
|
130
|
+
// Read back
|
|
131
|
+
const result = await redis.get(key);
|
|
132
|
+
expect(result).not.toBeNull();
|
|
133
|
+
|
|
134
|
+
const parsed = JSON.parse(result);
|
|
135
|
+
expect(parsed.value.balance).toBe('123456789');
|
|
136
|
+
|
|
137
|
+
// Check TTL
|
|
138
|
+
const ttl = await redis.ttl(key);
|
|
139
|
+
console.log(TAG, ` TTL: ${ttl}s`);
|
|
140
|
+
expect(ttl).toBeGreaterThan(0);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should detect keys using SCAN command', async () => {
|
|
144
|
+
const keyPrefix = 'test:scan:';
|
|
145
|
+
const numKeys = 5;
|
|
146
|
+
|
|
147
|
+
// Write multiple keys
|
|
148
|
+
for (let i = 0; i < numKeys; i++) {
|
|
149
|
+
await redis.set(`${keyPrefix}${i}`, `value${i}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Use SCAN to find them
|
|
153
|
+
let cursor = '0';
|
|
154
|
+
const foundKeys: string[] = [];
|
|
155
|
+
|
|
156
|
+
do {
|
|
157
|
+
const result = await redis.scan(cursor, 'MATCH', `${keyPrefix}*`, 'COUNT', 10);
|
|
158
|
+
cursor = result[0];
|
|
159
|
+
foundKeys.push(...result[1]);
|
|
160
|
+
} while (cursor !== '0');
|
|
161
|
+
|
|
162
|
+
console.log(TAG, ` SCAN found ${foundKeys.length} keys with pattern ${keyPrefix}*`);
|
|
163
|
+
expect(foundKeys.length).toBeGreaterThanOrEqual(numKeys);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('should verify connection string is correct', async () => {
|
|
167
|
+
const options = redis.options;
|
|
168
|
+
|
|
169
|
+
console.log(TAG, '๐ Redis Connection Details:');
|
|
170
|
+
console.log(TAG, ` Host: ${options.host}`);
|
|
171
|
+
console.log(TAG, ` Port: ${options.port}`);
|
|
172
|
+
console.log(TAG, ` DB: ${options.db || 0}`);
|
|
173
|
+
console.log(TAG, ` Family: ${options.family || 4}`);
|
|
174
|
+
|
|
175
|
+
expect(options.host).toBe('127.0.0.1');
|
|
176
|
+
expect(options.port).toBe(6379);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('should test actual BalanceCache class persistence', async () => {
|
|
180
|
+
// Import after Redis is loaded
|
|
181
|
+
const { BalanceCache } = require('../src/stores/balance-cache');
|
|
182
|
+
const balanceModule = require('@pioneer-platform/pioneer-balance');
|
|
183
|
+
|
|
184
|
+
const cache = new BalanceCache(redis, balanceModule, {
|
|
185
|
+
logCacheHits: true,
|
|
186
|
+
logCacheMisses: true,
|
|
187
|
+
enableQueue: false // Disable queue for this test
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
const testKey = 'balance_v2:eip155:1:0xTESTADDRESS';
|
|
191
|
+
const testData = {
|
|
192
|
+
caip: 'eip155:1/slip44:60',
|
|
193
|
+
pubkey: '0xTESTADDRESS',
|
|
194
|
+
balance: '999999999'
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
// Write via BalanceCache
|
|
198
|
+
console.log(TAG, ' Writing via BalanceCache.updateCache()...');
|
|
199
|
+
await cache.updateCache(testKey, testData);
|
|
200
|
+
|
|
201
|
+
// Verify it actually wrote
|
|
202
|
+
const result = await redis.get(testKey);
|
|
203
|
+
console.log(TAG, ` Direct Redis read: ${result ? 'SUCCESS' : 'โ FAILED'}`);
|
|
204
|
+
|
|
205
|
+
expect(result).not.toBeNull();
|
|
206
|
+
|
|
207
|
+
if (result) {
|
|
208
|
+
const parsed = JSON.parse(result);
|
|
209
|
+
expect(parsed.value.balance).toBe('999999999');
|
|
210
|
+
|
|
211
|
+
// Check TTL is set
|
|
212
|
+
const ttl = await redis.ttl(testKey);
|
|
213
|
+
console.log(TAG, ` TTL: ${ttl}s (expected: 300s)`);
|
|
214
|
+
expect(ttl).toBeGreaterThan(0);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Clean up
|
|
218
|
+
await redis.del(testKey);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('should run comprehensive persistence check', async () => {
|
|
222
|
+
console.log(TAG, '\n๐ฌ COMPREHENSIVE PERSISTENCE CHECK:');
|
|
223
|
+
|
|
224
|
+
const testKey = 'test:comprehensive';
|
|
225
|
+
const testValue = 'persistence-check-' + Date.now();
|
|
226
|
+
|
|
227
|
+
// 1. Write
|
|
228
|
+
console.log(TAG, ' Step 1: Writing value...');
|
|
229
|
+
await redis.set(testKey, testValue);
|
|
230
|
+
|
|
231
|
+
// 2. Confirm with GET
|
|
232
|
+
console.log(TAG, ' Step 2: Reading back immediately...');
|
|
233
|
+
const read1 = await redis.get(testKey);
|
|
234
|
+
console.log(TAG, ` Read: ${read1 === testValue ? 'โ
MATCH' : 'โ MISMATCH'}`);
|
|
235
|
+
expect(read1).toBe(testValue);
|
|
236
|
+
|
|
237
|
+
// 3. Check EXISTS
|
|
238
|
+
console.log(TAG, ' Step 3: Checking EXISTS...');
|
|
239
|
+
const exists = await redis.exists(testKey);
|
|
240
|
+
console.log(TAG, ` EXISTS: ${exists === 1 ? 'โ
TRUE' : 'โ FALSE'}`);
|
|
241
|
+
expect(exists).toBe(1);
|
|
242
|
+
|
|
243
|
+
// 4. Check with KEYS pattern
|
|
244
|
+
console.log(TAG, ' Step 4: Searching with KEYS...');
|
|
245
|
+
const keys = await redis.keys('test:comprehensive');
|
|
246
|
+
console.log(TAG, ` KEYS found: ${keys.length === 1 ? 'โ
1' : 'โ ' + keys.length}`);
|
|
247
|
+
expect(keys.length).toBe(1);
|
|
248
|
+
|
|
249
|
+
// 5. Wait and re-read
|
|
250
|
+
console.log(TAG, ' Step 5: Waiting 200ms and re-reading...');
|
|
251
|
+
await new Promise(resolve => setTimeout(resolve, 200));
|
|
252
|
+
const read2 = await redis.get(testKey);
|
|
253
|
+
console.log(TAG, ` Read: ${read2 === testValue ? 'โ
STILL THERE' : 'โ DISAPPEARED!'}`);
|
|
254
|
+
expect(read2).toBe(testValue);
|
|
255
|
+
|
|
256
|
+
// 6. DEL and verify
|
|
257
|
+
console.log(TAG, ' Step 6: Deleting and verifying...');
|
|
258
|
+
await redis.del(testKey);
|
|
259
|
+
const read3 = await redis.get(testKey);
|
|
260
|
+
console.log(TAG, ` After DEL: ${read3 === null ? 'โ
DELETED' : 'โ STILL EXISTS!'}`);
|
|
261
|
+
expect(read3).toBeNull();
|
|
262
|
+
|
|
263
|
+
console.log(TAG, 'โ
All persistence checks PASSED!\n');
|
|
264
|
+
});
|
|
265
|
+
});
|