@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.
@@ -0,0 +1,212 @@
1
+ "use strict";
2
+ /*
3
+ Unified Refresh Worker
4
+
5
+ Single worker that processes refresh jobs for ALL cache types (balance, price, etc.)
6
+ Replaces separate balance-refresh.worker.ts and price-refresh.worker.ts
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.RefreshWorker = void 0;
10
+ exports.startUnifiedWorker = startUnifiedWorker;
11
+ const log = require('@pioneer-platform/loggerdog')();
12
+ const TAG = ' | RefreshWorker | ';
13
+ /**
14
+ * Unified refresh worker that processes jobs for any cache type
15
+ */
16
+ class RefreshWorker {
17
+ constructor(redis, config) {
18
+ this.cacheRegistry = new Map();
19
+ this.isRunning = false;
20
+ this.isProcessing = false;
21
+ this.pollTimeoutId = null;
22
+ this.redis = redis;
23
+ this.config = config;
24
+ try {
25
+ this.redisQueue = require('@pioneer-platform/redis-queue');
26
+ log.info(TAG, `✅ RefreshWorker initialized for queue: ${config.queueName}`);
27
+ }
28
+ catch (error) {
29
+ log.error(TAG, '❌ Failed to initialize redis-queue:', error);
30
+ throw error;
31
+ }
32
+ }
33
+ /**
34
+ * Register a cache instance with this worker
35
+ * The worker will route jobs to the appropriate cache based on job type
36
+ */
37
+ registerCache(cacheName, cache) {
38
+ this.cacheRegistry.set(cacheName.toLowerCase(), cache);
39
+ log.info(TAG, `Registered ${cacheName} cache with worker`);
40
+ }
41
+ /**
42
+ * Start processing jobs from the queue
43
+ */
44
+ async start() {
45
+ const tag = TAG + 'start | ';
46
+ if (this.isRunning) {
47
+ log.warn(tag, 'Worker already running');
48
+ return;
49
+ }
50
+ log.info(tag, `🚀 Starting refresh worker for queue: ${this.config.queueName}`);
51
+ log.info(tag, `Registered caches: ${Array.from(this.cacheRegistry.keys()).join(', ')}`);
52
+ this.isRunning = true;
53
+ this.poll();
54
+ log.info(tag, '✅ Refresh worker started successfully');
55
+ }
56
+ /**
57
+ * Stop the worker gracefully
58
+ */
59
+ async stop() {
60
+ const tag = TAG + 'stop | ';
61
+ log.info(tag, 'Stopping refresh worker...');
62
+ this.isRunning = false;
63
+ if (this.pollTimeoutId) {
64
+ clearTimeout(this.pollTimeoutId);
65
+ this.pollTimeoutId = null;
66
+ }
67
+ // Wait for current job to finish
68
+ while (this.isProcessing) {
69
+ await new Promise(resolve => setTimeout(resolve, 100));
70
+ }
71
+ log.info(tag, '✅ Refresh worker stopped');
72
+ }
73
+ /**
74
+ * Poll for next job from the queue
75
+ */
76
+ async poll() {
77
+ const tag = TAG + 'poll | ';
78
+ if (!this.isRunning) {
79
+ return;
80
+ }
81
+ try {
82
+ // Don't poll if already processing
83
+ if (this.isProcessing) {
84
+ this.schedulePoll();
85
+ return;
86
+ }
87
+ // Get next job from queue
88
+ const work = await this.redisQueue.getWork(this.config.queueName, 1);
89
+ if (work) {
90
+ this.isProcessing = true;
91
+ await this.processJob(work);
92
+ this.isProcessing = false;
93
+ }
94
+ }
95
+ catch (error) {
96
+ log.error(tag, 'Error in poll loop:', error.message);
97
+ this.isProcessing = false;
98
+ }
99
+ finally {
100
+ // Schedule next poll
101
+ this.schedulePoll();
102
+ }
103
+ }
104
+ /**
105
+ * Schedule next poll
106
+ */
107
+ schedulePoll() {
108
+ if (this.pollTimeoutId) {
109
+ clearTimeout(this.pollTimeoutId);
110
+ }
111
+ const pollInterval = this.config.pollInterval || 100;
112
+ this.pollTimeoutId = setTimeout(() => {
113
+ this.poll();
114
+ }, pollInterval);
115
+ }
116
+ /**
117
+ * Process a single refresh job
118
+ */
119
+ async processJob(job) {
120
+ const tag = TAG + 'processJob | ';
121
+ const startTime = Date.now();
122
+ try {
123
+ const { type, key, params, retryCount = 0 } = job;
124
+ log.info(tag, `Processing ${type} for ${key} (retry: ${retryCount})`);
125
+ // Extract cache name from job type (e.g., "REFRESH_BALANCE" -> "balance")
126
+ const cacheName = type.replace('REFRESH_', '').toLowerCase();
127
+ // Get the appropriate cache instance
128
+ const cache = this.cacheRegistry.get(cacheName);
129
+ if (!cache) {
130
+ log.error(tag, `No cache registered for type: ${type}`);
131
+ return;
132
+ }
133
+ // Fetch fresh data using the cache's fetchFresh method
134
+ await cache.fetchFresh(params);
135
+ const processingTime = Date.now() - startTime;
136
+ log.info(tag, `✅ Processed ${type} in ${processingTime}ms: ${key}`);
137
+ }
138
+ catch (error) {
139
+ const processingTime = Date.now() - startTime;
140
+ log.error(tag, `❌ Failed to process ${job.type} after ${processingTime}ms:`, error);
141
+ // Retry logic
142
+ if ((job.retryCount || 0) < this.config.maxRetries) {
143
+ const newRetryCount = (job.retryCount || 0) + 1;
144
+ log.info(tag, `Retrying job (attempt ${newRetryCount}/${this.config.maxRetries})`);
145
+ // Re-queue with incremented retry count and delay
146
+ setTimeout(async () => {
147
+ try {
148
+ await this.redisQueue.createWork(this.config.queueName, {
149
+ ...job,
150
+ retryCount: newRetryCount
151
+ });
152
+ }
153
+ catch (requeueError) {
154
+ log.error(tag, 'Error re-queuing job:', requeueError);
155
+ }
156
+ }, this.config.retryDelay);
157
+ }
158
+ else {
159
+ log.error(tag, `Max retries reached for ${job.type}, giving up`);
160
+ }
161
+ throw error;
162
+ }
163
+ }
164
+ /**
165
+ * Get worker statistics
166
+ */
167
+ async getStats() {
168
+ const tag = TAG + 'getStats | ';
169
+ try {
170
+ const queueLength = await this.redisQueue.count(this.config.queueName);
171
+ return {
172
+ isRunning: this.isRunning,
173
+ isProcessing: this.isProcessing,
174
+ queueName: this.config.queueName,
175
+ queueLength,
176
+ registeredCaches: Array.from(this.cacheRegistry.keys()),
177
+ config: {
178
+ maxRetries: this.config.maxRetries,
179
+ retryDelay: this.config.retryDelay,
180
+ pollInterval: this.config.pollInterval || 100
181
+ }
182
+ };
183
+ }
184
+ catch (error) {
185
+ log.error(tag, 'Error getting worker stats:', error);
186
+ return {
187
+ error: error instanceof Error ? error.message : String(error)
188
+ };
189
+ }
190
+ }
191
+ }
192
+ exports.RefreshWorker = RefreshWorker;
193
+ /**
194
+ * Start a unified refresh worker for multiple cache types
195
+ * Convenience function for common usage
196
+ */
197
+ async function startUnifiedWorker(redis, caches, queueName, config) {
198
+ const workerConfig = {
199
+ queueName,
200
+ maxRetries: config?.maxRetries || 3,
201
+ retryDelay: config?.retryDelay || 5000,
202
+ pollInterval: config?.pollInterval || 100
203
+ };
204
+ const worker = new RefreshWorker(redis, workerConfig);
205
+ // Register all caches
206
+ for (const [name, cache] of caches) {
207
+ worker.registerCache(name, cache);
208
+ }
209
+ // Start worker
210
+ await worker.start();
211
+ return worker;
212
+ }
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@pioneer-platform/pioneer-cache",
3
+ "version": "1.0.0",
4
+ "description": "Unified caching system for Pioneer platform with Redis backend",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "dev": "tsc --watch",
10
+ "test": "jest",
11
+ "lint": "eslint src/**/*.ts"
12
+ },
13
+ "keywords": [
14
+ "pioneer",
15
+ "cache",
16
+ "redis",
17
+ "balance",
18
+ "price"
19
+ ],
20
+ "author": "Pioneer Platform",
21
+ "license": "MIT",
22
+ "dependencies": {
23
+ "@pioneer-platform/loggerdog": "workspace:*",
24
+ "@pioneer-platform/redis-queue": "workspace:*",
25
+ "@pioneer-platform/default-redis": "workspace:*"
26
+ },
27
+ "devDependencies": {
28
+ "@types/node": "^20.0.0",
29
+ "typescript": "^5.0.0"
30
+ }
31
+ }