@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,267 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Unified Refresh Worker
|
|
3
|
+
|
|
4
|
+
Single worker that processes refresh jobs for ALL cache types (balance, price, etc.)
|
|
5
|
+
Replaces separate balance-refresh.worker.ts and price-refresh.worker.ts
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { RefreshJob } from '../types';
|
|
9
|
+
import type { BaseCache } from '../core/base-cache';
|
|
10
|
+
|
|
11
|
+
const log = require('@pioneer-platform/loggerdog')();
|
|
12
|
+
const TAG = ' | RefreshWorker | ';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Worker configuration
|
|
16
|
+
*/
|
|
17
|
+
export interface WorkerConfig {
|
|
18
|
+
queueName: string;
|
|
19
|
+
maxRetries: number;
|
|
20
|
+
retryDelay: number;
|
|
21
|
+
pollInterval?: number; // Poll interval in ms (default: 100ms)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Unified refresh worker that processes jobs for any cache type
|
|
26
|
+
*/
|
|
27
|
+
export class RefreshWorker {
|
|
28
|
+
private redis: any;
|
|
29
|
+
private redisQueue: any;
|
|
30
|
+
private cacheRegistry: Map<string, BaseCache<any>> = new Map();
|
|
31
|
+
private config: WorkerConfig;
|
|
32
|
+
private isRunning: boolean = false;
|
|
33
|
+
private isProcessing: boolean = false;
|
|
34
|
+
private pollTimeoutId: NodeJS.Timeout | null = null;
|
|
35
|
+
|
|
36
|
+
constructor(redis: any, config: WorkerConfig) {
|
|
37
|
+
this.redis = redis;
|
|
38
|
+
this.config = config;
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
this.redisQueue = require('@pioneer-platform/redis-queue');
|
|
42
|
+
log.info(TAG, `✅ RefreshWorker initialized for queue: ${config.queueName}`);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
log.error(TAG, '❌ Failed to initialize redis-queue:', error);
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Register a cache instance with this worker
|
|
51
|
+
* The worker will route jobs to the appropriate cache based on job type
|
|
52
|
+
*/
|
|
53
|
+
registerCache(cacheName: string, cache: BaseCache<any>): void {
|
|
54
|
+
this.cacheRegistry.set(cacheName.toLowerCase(), cache);
|
|
55
|
+
log.info(TAG, `Registered ${cacheName} cache with worker`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Start processing jobs from the queue
|
|
60
|
+
*/
|
|
61
|
+
async start(): Promise<void> {
|
|
62
|
+
const tag = TAG + 'start | ';
|
|
63
|
+
|
|
64
|
+
if (this.isRunning) {
|
|
65
|
+
log.warn(tag, 'Worker already running');
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
log.info(tag, `🚀 Starting refresh worker for queue: ${this.config.queueName}`);
|
|
70
|
+
log.info(tag, `Registered caches: ${Array.from(this.cacheRegistry.keys()).join(', ')}`);
|
|
71
|
+
|
|
72
|
+
this.isRunning = true;
|
|
73
|
+
this.poll();
|
|
74
|
+
|
|
75
|
+
log.info(tag, '✅ Refresh worker started successfully');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Stop the worker gracefully
|
|
80
|
+
*/
|
|
81
|
+
async stop(): Promise<void> {
|
|
82
|
+
const tag = TAG + 'stop | ';
|
|
83
|
+
|
|
84
|
+
log.info(tag, 'Stopping refresh worker...');
|
|
85
|
+
|
|
86
|
+
this.isRunning = false;
|
|
87
|
+
|
|
88
|
+
if (this.pollTimeoutId) {
|
|
89
|
+
clearTimeout(this.pollTimeoutId);
|
|
90
|
+
this.pollTimeoutId = null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Wait for current job to finish
|
|
94
|
+
while (this.isProcessing) {
|
|
95
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
log.info(tag, '✅ Refresh worker stopped');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Poll for next job from the queue
|
|
103
|
+
*/
|
|
104
|
+
private async poll(): Promise<void> {
|
|
105
|
+
const tag = TAG + 'poll | ';
|
|
106
|
+
|
|
107
|
+
if (!this.isRunning) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
// Don't poll if already processing
|
|
113
|
+
if (this.isProcessing) {
|
|
114
|
+
this.schedulePoll();
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Get next job from queue
|
|
119
|
+
const work = await this.redisQueue.getWork(this.config.queueName, 1);
|
|
120
|
+
|
|
121
|
+
if (work) {
|
|
122
|
+
this.isProcessing = true;
|
|
123
|
+
await this.processJob(work);
|
|
124
|
+
this.isProcessing = false;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
} catch (error: any) {
|
|
128
|
+
log.error(tag, 'Error in poll loop:', error.message);
|
|
129
|
+
this.isProcessing = false;
|
|
130
|
+
} finally {
|
|
131
|
+
// Schedule next poll
|
|
132
|
+
this.schedulePoll();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Schedule next poll
|
|
138
|
+
*/
|
|
139
|
+
private schedulePoll(): void {
|
|
140
|
+
if (this.pollTimeoutId) {
|
|
141
|
+
clearTimeout(this.pollTimeoutId);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const pollInterval = this.config.pollInterval || 100;
|
|
145
|
+
this.pollTimeoutId = setTimeout(() => {
|
|
146
|
+
this.poll();
|
|
147
|
+
}, pollInterval);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Process a single refresh job
|
|
152
|
+
*/
|
|
153
|
+
private async processJob(job: RefreshJob): Promise<void> {
|
|
154
|
+
const tag = TAG + 'processJob | ';
|
|
155
|
+
const startTime = Date.now();
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
const { type, key, params, retryCount = 0 } = job;
|
|
159
|
+
|
|
160
|
+
log.info(tag, `Processing ${type} for ${key} (retry: ${retryCount})`);
|
|
161
|
+
|
|
162
|
+
// Extract cache name from job type (e.g., "REFRESH_BALANCE" -> "balance")
|
|
163
|
+
const cacheName = type.replace('REFRESH_', '').toLowerCase();
|
|
164
|
+
|
|
165
|
+
// Get the appropriate cache instance
|
|
166
|
+
const cache = this.cacheRegistry.get(cacheName);
|
|
167
|
+
|
|
168
|
+
if (!cache) {
|
|
169
|
+
log.error(tag, `No cache registered for type: ${type}`);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Fetch fresh data using the cache's fetchFresh method
|
|
174
|
+
await cache.fetchFresh(params);
|
|
175
|
+
|
|
176
|
+
const processingTime = Date.now() - startTime;
|
|
177
|
+
log.info(tag, `✅ Processed ${type} in ${processingTime}ms: ${key}`);
|
|
178
|
+
|
|
179
|
+
} catch (error) {
|
|
180
|
+
const processingTime = Date.now() - startTime;
|
|
181
|
+
log.error(tag, `❌ Failed to process ${job.type} after ${processingTime}ms:`, error);
|
|
182
|
+
|
|
183
|
+
// Retry logic
|
|
184
|
+
if ((job.retryCount || 0) < this.config.maxRetries) {
|
|
185
|
+
const newRetryCount = (job.retryCount || 0) + 1;
|
|
186
|
+
log.info(tag, `Retrying job (attempt ${newRetryCount}/${this.config.maxRetries})`);
|
|
187
|
+
|
|
188
|
+
// Re-queue with incremented retry count and delay
|
|
189
|
+
setTimeout(async () => {
|
|
190
|
+
try {
|
|
191
|
+
await this.redisQueue.createWork(this.config.queueName, {
|
|
192
|
+
...job,
|
|
193
|
+
retryCount: newRetryCount
|
|
194
|
+
});
|
|
195
|
+
} catch (requeueError) {
|
|
196
|
+
log.error(tag, 'Error re-queuing job:', requeueError);
|
|
197
|
+
}
|
|
198
|
+
}, this.config.retryDelay);
|
|
199
|
+
|
|
200
|
+
} else {
|
|
201
|
+
log.error(tag, `Max retries reached for ${job.type}, giving up`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
throw error;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Get worker statistics
|
|
210
|
+
*/
|
|
211
|
+
async getStats(): Promise<any> {
|
|
212
|
+
const tag = TAG + 'getStats | ';
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
const queueLength = await this.redisQueue.count(this.config.queueName);
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
isRunning: this.isRunning,
|
|
219
|
+
isProcessing: this.isProcessing,
|
|
220
|
+
queueName: this.config.queueName,
|
|
221
|
+
queueLength,
|
|
222
|
+
registeredCaches: Array.from(this.cacheRegistry.keys()),
|
|
223
|
+
config: {
|
|
224
|
+
maxRetries: this.config.maxRetries,
|
|
225
|
+
retryDelay: this.config.retryDelay,
|
|
226
|
+
pollInterval: this.config.pollInterval || 100
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
} catch (error) {
|
|
231
|
+
log.error(tag, 'Error getting worker stats:', error);
|
|
232
|
+
return {
|
|
233
|
+
error: error instanceof Error ? error.message : String(error)
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Start a unified refresh worker for multiple cache types
|
|
241
|
+
* Convenience function for common usage
|
|
242
|
+
*/
|
|
243
|
+
export async function startUnifiedWorker(
|
|
244
|
+
redis: any,
|
|
245
|
+
caches: Map<string, BaseCache<any>>,
|
|
246
|
+
queueName: string,
|
|
247
|
+
config?: Partial<WorkerConfig>
|
|
248
|
+
): Promise<RefreshWorker> {
|
|
249
|
+
const workerConfig: WorkerConfig = {
|
|
250
|
+
queueName,
|
|
251
|
+
maxRetries: config?.maxRetries || 3,
|
|
252
|
+
retryDelay: config?.retryDelay || 5000,
|
|
253
|
+
pollInterval: config?.pollInterval || 100
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const worker = new RefreshWorker(redis, workerConfig);
|
|
257
|
+
|
|
258
|
+
// Register all caches
|
|
259
|
+
for (const [name, cache] of caches) {
|
|
260
|
+
worker.registerCache(name, cache);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Start worker
|
|
264
|
+
await worker.start();
|
|
265
|
+
|
|
266
|
+
return worker;
|
|
267
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"lib": ["ES2020"],
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"outDir": "./dist",
|
|
8
|
+
"rootDir": "./src",
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
"moduleResolution": "node"
|
|
15
|
+
},
|
|
16
|
+
"include": ["src/**/*"],
|
|
17
|
+
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
|
18
|
+
}
|