@pioneer-platform/markets 8.12.0 → 8.15.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/CHANGELOG.md +13 -0
- package/METRICS_MONITORING.md +285 -0
- package/PRICING_IMPROVEMENTS.md +327 -0
- package/lib/api-metrics-reporter.d.ts +20 -0
- package/lib/api-metrics-reporter.js +356 -0
- package/lib/ccxt-pricing.d.ts +34 -0
- package/lib/ccxt-pricing.js +421 -0
- package/lib/index.js +784 -145
- package/lib/metrics-logger.d.ts +72 -0
- package/lib/metrics-logger.js +435 -0
- package/lib/refill-scheduler.d.ts +22 -0
- package/lib/refill-scheduler.js +226 -0
- package/lib/token-bucket-manager.d.ts +47 -0
- package/lib/token-bucket-manager.js +342 -0
- package/lib/token-bucket.d.ts +87 -0
- package/lib/token-bucket.js +341 -0
- package/package.json +6 -3
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
Token Bucket Refill Scheduler
|
|
4
|
+
|
|
5
|
+
Schedules daily token bucket refills at midnight UTC
|
|
6
|
+
*/
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
+
}) : function(o, v) {
|
|
21
|
+
o["default"] = v;
|
|
22
|
+
});
|
|
23
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
24
|
+
var ownKeys = function(o) {
|
|
25
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
+
var ar = [];
|
|
27
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
+
return ar;
|
|
29
|
+
};
|
|
30
|
+
return ownKeys(o);
|
|
31
|
+
};
|
|
32
|
+
return function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
40
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
41
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
42
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
43
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
44
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
45
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
46
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
50
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
51
|
+
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
52
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
53
|
+
function step(op) {
|
|
54
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
55
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
56
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
57
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
58
|
+
switch (op[0]) {
|
|
59
|
+
case 0: case 1: t = op; break;
|
|
60
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
61
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
62
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
63
|
+
default:
|
|
64
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
65
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
66
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
67
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
68
|
+
if (t[2]) _.ops.pop();
|
|
69
|
+
_.trys.pop(); continue;
|
|
70
|
+
}
|
|
71
|
+
op = body.call(thisArg, _);
|
|
72
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
73
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
77
|
+
exports.scheduleDailyRefill = scheduleDailyRefill;
|
|
78
|
+
exports.manualRefill = manualRefill;
|
|
79
|
+
exports.cancelRefillSchedule = cancelRefillSchedule;
|
|
80
|
+
exports.getNextRefillTime = getNextRefillTime;
|
|
81
|
+
exports.isRefillScheduled = isRefillScheduled;
|
|
82
|
+
var schedule = __importStar(require("node-schedule"));
|
|
83
|
+
var log = require('@pioneer-platform/loggerdog')();
|
|
84
|
+
var TAG = ' | RefillScheduler | ';
|
|
85
|
+
var refillJob = null;
|
|
86
|
+
/**
|
|
87
|
+
* Schedule daily token bucket refill at midnight UTC
|
|
88
|
+
*/
|
|
89
|
+
function scheduleDailyRefill(manager, metricsLogger) {
|
|
90
|
+
var _this = this;
|
|
91
|
+
var tag = TAG + ' | scheduleDailyRefill | ';
|
|
92
|
+
// Cancel existing job if any
|
|
93
|
+
if (refillJob) {
|
|
94
|
+
refillJob.cancel();
|
|
95
|
+
log.info(tag, 'Cancelled existing refill job');
|
|
96
|
+
}
|
|
97
|
+
// Schedule for midnight UTC (0 0 * * *)
|
|
98
|
+
refillJob = schedule.scheduleJob('0 0 * * *', function () { return __awaiter(_this, void 0, void 0, function () {
|
|
99
|
+
var status_1, error_1;
|
|
100
|
+
return __generator(this, function (_a) {
|
|
101
|
+
switch (_a.label) {
|
|
102
|
+
case 0:
|
|
103
|
+
log.info(tag, '🔄 Daily token bucket refill starting...');
|
|
104
|
+
_a.label = 1;
|
|
105
|
+
case 1:
|
|
106
|
+
_a.trys.push([1, 6, , 9]);
|
|
107
|
+
return [4 /*yield*/, manager.refillAll()];
|
|
108
|
+
case 2:
|
|
109
|
+
_a.sent();
|
|
110
|
+
if (!metricsLogger) return [3 /*break*/, 4];
|
|
111
|
+
return [4 /*yield*/, metricsLogger.logAPICall({
|
|
112
|
+
apiName: 'system',
|
|
113
|
+
endpoint: '/refill',
|
|
114
|
+
success: true,
|
|
115
|
+
timestamp: new Date()
|
|
116
|
+
})];
|
|
117
|
+
case 3:
|
|
118
|
+
_a.sent();
|
|
119
|
+
_a.label = 4;
|
|
120
|
+
case 4: return [4 /*yield*/, manager.getAllStatus()];
|
|
121
|
+
case 5:
|
|
122
|
+
status_1 = _a.sent();
|
|
123
|
+
log.info(tag, '✅ Daily token bucket refill complete!');
|
|
124
|
+
log.info(tag, 'Bucket status:', status_1);
|
|
125
|
+
return [3 /*break*/, 9];
|
|
126
|
+
case 6:
|
|
127
|
+
error_1 = _a.sent();
|
|
128
|
+
log.error(tag, 'Failed to refill token buckets:', error_1);
|
|
129
|
+
if (!metricsLogger) return [3 /*break*/, 8];
|
|
130
|
+
return [4 /*yield*/, metricsLogger.logAPICall({
|
|
131
|
+
apiName: 'system',
|
|
132
|
+
endpoint: '/refill',
|
|
133
|
+
success: false,
|
|
134
|
+
error: error_1.message,
|
|
135
|
+
timestamp: new Date()
|
|
136
|
+
})];
|
|
137
|
+
case 7:
|
|
138
|
+
_a.sent();
|
|
139
|
+
_a.label = 8;
|
|
140
|
+
case 8: return [3 /*break*/, 9];
|
|
141
|
+
case 9: return [2 /*return*/];
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
}); });
|
|
145
|
+
log.info(tag, '✅ Scheduled daily token bucket refill at midnight UTC (cron: 0 0 * * *)');
|
|
146
|
+
// Log next refill time
|
|
147
|
+
var nextRun = refillJob.nextInvocation();
|
|
148
|
+
if (nextRun) {
|
|
149
|
+
log.info(tag, "Next refill scheduled for: ".concat(nextRun.toString()));
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Manual refill trigger (for admin use)
|
|
154
|
+
*/
|
|
155
|
+
function manualRefill(manager, metricsLogger) {
|
|
156
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
157
|
+
var tag, error_2;
|
|
158
|
+
return __generator(this, function (_a) {
|
|
159
|
+
switch (_a.label) {
|
|
160
|
+
case 0:
|
|
161
|
+
tag = TAG + ' | manualRefill | ';
|
|
162
|
+
log.info(tag, 'Manual token bucket refill triggered');
|
|
163
|
+
_a.label = 1;
|
|
164
|
+
case 1:
|
|
165
|
+
_a.trys.push([1, 5, , 8]);
|
|
166
|
+
return [4 /*yield*/, manager.refillAll()];
|
|
167
|
+
case 2:
|
|
168
|
+
_a.sent();
|
|
169
|
+
if (!metricsLogger) return [3 /*break*/, 4];
|
|
170
|
+
return [4 /*yield*/, metricsLogger.logAPICall({
|
|
171
|
+
apiName: 'system',
|
|
172
|
+
endpoint: '/refill/manual',
|
|
173
|
+
success: true,
|
|
174
|
+
timestamp: new Date()
|
|
175
|
+
})];
|
|
176
|
+
case 3:
|
|
177
|
+
_a.sent();
|
|
178
|
+
_a.label = 4;
|
|
179
|
+
case 4:
|
|
180
|
+
log.info(tag, '✅ Manual refill complete');
|
|
181
|
+
return [3 /*break*/, 8];
|
|
182
|
+
case 5:
|
|
183
|
+
error_2 = _a.sent();
|
|
184
|
+
log.error(tag, 'Manual refill failed:', error_2);
|
|
185
|
+
if (!metricsLogger) return [3 /*break*/, 7];
|
|
186
|
+
return [4 /*yield*/, metricsLogger.logAPICall({
|
|
187
|
+
apiName: 'system',
|
|
188
|
+
endpoint: '/refill/manual',
|
|
189
|
+
success: false,
|
|
190
|
+
error: error_2.message,
|
|
191
|
+
timestamp: new Date()
|
|
192
|
+
})];
|
|
193
|
+
case 6:
|
|
194
|
+
_a.sent();
|
|
195
|
+
_a.label = 7;
|
|
196
|
+
case 7: throw error_2;
|
|
197
|
+
case 8: return [2 /*return*/];
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Cancel scheduled refill job
|
|
204
|
+
*/
|
|
205
|
+
function cancelRefillSchedule() {
|
|
206
|
+
if (refillJob) {
|
|
207
|
+
refillJob.cancel();
|
|
208
|
+
refillJob = null;
|
|
209
|
+
log.info(TAG, 'Cancelled refill schedule');
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Get next refill time
|
|
214
|
+
*/
|
|
215
|
+
function getNextRefillTime() {
|
|
216
|
+
if (refillJob) {
|
|
217
|
+
return refillJob.nextInvocation();
|
|
218
|
+
}
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Check if refill is scheduled
|
|
223
|
+
*/
|
|
224
|
+
function isRefillScheduled() {
|
|
225
|
+
return refillJob !== null;
|
|
226
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Db } from 'mongodb';
|
|
2
|
+
export declare class TokenBucketManager {
|
|
3
|
+
private buckets;
|
|
4
|
+
private db;
|
|
5
|
+
private initialized;
|
|
6
|
+
constructor(db: Db);
|
|
7
|
+
/**
|
|
8
|
+
* Initialize all API buckets
|
|
9
|
+
*/
|
|
10
|
+
initialize(): Promise<void>;
|
|
11
|
+
/**
|
|
12
|
+
* Check if manager is initialized
|
|
13
|
+
*/
|
|
14
|
+
isInitialized(): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Try to consume tokens from specific API
|
|
17
|
+
*/
|
|
18
|
+
tryConsume(apiName: string, count?: number): Promise<boolean>;
|
|
19
|
+
/**
|
|
20
|
+
* Get status of all buckets
|
|
21
|
+
*/
|
|
22
|
+
getAllStatus(): Promise<Record<string, any>>;
|
|
23
|
+
/**
|
|
24
|
+
* Get status of specific bucket
|
|
25
|
+
*/
|
|
26
|
+
getBucketStatus(apiName: string): Promise<any>;
|
|
27
|
+
/**
|
|
28
|
+
* Refill all buckets (scheduled job runs at midnight UTC)
|
|
29
|
+
*/
|
|
30
|
+
refillAll(): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Refill specific bucket
|
|
33
|
+
*/
|
|
34
|
+
refillBucket(apiName: string): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Get remaining tokens for API
|
|
37
|
+
*/
|
|
38
|
+
getRemainingTokens(apiName: string): Promise<number>;
|
|
39
|
+
/**
|
|
40
|
+
* Get bucket configurations
|
|
41
|
+
*/
|
|
42
|
+
getBucketConfigs(): Record<string, any>;
|
|
43
|
+
/**
|
|
44
|
+
* Get daily budget summary
|
|
45
|
+
*/
|
|
46
|
+
getDailyBudgetSummary(): Promise<any>;
|
|
47
|
+
}
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
Token Bucket Manager
|
|
4
|
+
|
|
5
|
+
Manages all API token buckets with realistic daily budgets
|
|
6
|
+
based on monthly cost targets
|
|
7
|
+
*/
|
|
8
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
9
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
10
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
11
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
12
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
13
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
14
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
18
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
19
|
+
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
20
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
21
|
+
function step(op) {
|
|
22
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
23
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
24
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
25
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
26
|
+
switch (op[0]) {
|
|
27
|
+
case 0: case 1: t = op; break;
|
|
28
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
29
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
30
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
31
|
+
default:
|
|
32
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
33
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
34
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
35
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
36
|
+
if (t[2]) _.ops.pop();
|
|
37
|
+
_.trys.pop(); continue;
|
|
38
|
+
}
|
|
39
|
+
op = body.call(thisArg, _);
|
|
40
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
41
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
exports.TokenBucketManager = void 0;
|
|
46
|
+
var token_bucket_1 = require("./token-bucket");
|
|
47
|
+
var log = require('@pioneer-platform/loggerdog')();
|
|
48
|
+
var TAG = ' | TokenBucketManager | ';
|
|
49
|
+
// REALISTIC daily allocations based on budget
|
|
50
|
+
// To stay within monthly budget limits
|
|
51
|
+
var REALISTIC_DAILY_BUDGETS = {
|
|
52
|
+
coingecko: 167, // To stay within $100/month ($0.02/call)
|
|
53
|
+
coinmarketcap: 111, // To stay within $50/month ($0.015/call)
|
|
54
|
+
coincap: 100 // To stay within $30/month ($0.01/call)
|
|
55
|
+
};
|
|
56
|
+
var TokenBucketManager = /** @class */ (function () {
|
|
57
|
+
function TokenBucketManager(db) {
|
|
58
|
+
this.initialized = false;
|
|
59
|
+
this.buckets = new Map();
|
|
60
|
+
this.db = db;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Initialize all API buckets
|
|
64
|
+
*/
|
|
65
|
+
TokenBucketManager.prototype.initialize = function () {
|
|
66
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
67
|
+
var tag, coingeckoBucket, cmcBucket, coincapBucket;
|
|
68
|
+
return __generator(this, function (_a) {
|
|
69
|
+
switch (_a.label) {
|
|
70
|
+
case 0:
|
|
71
|
+
tag = TAG + ' | initialize | ';
|
|
72
|
+
log.info(tag, 'Initializing token buckets for all APIs...');
|
|
73
|
+
coingeckoBucket = new token_bucket_1.TokenBucket({
|
|
74
|
+
apiName: 'coingecko',
|
|
75
|
+
capacity: REALISTIC_DAILY_BUDGETS.coingecko,
|
|
76
|
+
refillAmount: REALISTIC_DAILY_BUDGETS.coingecko,
|
|
77
|
+
refillInterval: 86400000, // 24 hours
|
|
78
|
+
tokensPerRequest: 1,
|
|
79
|
+
costPerToken: 0.02, // $0.02 per call
|
|
80
|
+
monthlyBudget: 100 // $100/month
|
|
81
|
+
}, this.db);
|
|
82
|
+
return [4 /*yield*/, coingeckoBucket.initialize()];
|
|
83
|
+
case 1:
|
|
84
|
+
_a.sent();
|
|
85
|
+
this.buckets.set('coingecko', coingeckoBucket);
|
|
86
|
+
cmcBucket = new token_bucket_1.TokenBucket({
|
|
87
|
+
apiName: 'coinmarketcap',
|
|
88
|
+
capacity: REALISTIC_DAILY_BUDGETS.coinmarketcap,
|
|
89
|
+
refillAmount: REALISTIC_DAILY_BUDGETS.coinmarketcap,
|
|
90
|
+
refillInterval: 86400000,
|
|
91
|
+
tokensPerRequest: 1,
|
|
92
|
+
costPerToken: 0.015, // $0.015 per call
|
|
93
|
+
monthlyBudget: 50 // $50/month
|
|
94
|
+
}, this.db);
|
|
95
|
+
return [4 /*yield*/, cmcBucket.initialize()];
|
|
96
|
+
case 2:
|
|
97
|
+
_a.sent();
|
|
98
|
+
this.buckets.set('coinmarketcap', cmcBucket);
|
|
99
|
+
coincapBucket = new token_bucket_1.TokenBucket({
|
|
100
|
+
apiName: 'coincap',
|
|
101
|
+
capacity: REALISTIC_DAILY_BUDGETS.coincap,
|
|
102
|
+
refillAmount: REALISTIC_DAILY_BUDGETS.coincap,
|
|
103
|
+
refillInterval: 86400000,
|
|
104
|
+
tokensPerRequest: 1,
|
|
105
|
+
costPerToken: 0.01, // $0.01 per call
|
|
106
|
+
monthlyBudget: 30 // $30/month
|
|
107
|
+
}, this.db);
|
|
108
|
+
return [4 /*yield*/, coincapBucket.initialize()];
|
|
109
|
+
case 3:
|
|
110
|
+
_a.sent();
|
|
111
|
+
this.buckets.set('coincap', coincapBucket);
|
|
112
|
+
this.initialized = true;
|
|
113
|
+
log.info(tag, "\u2705 Initialized ".concat(this.buckets.size, " token buckets"));
|
|
114
|
+
log.info(tag, "Daily budgets: CoinGecko=".concat(REALISTIC_DAILY_BUDGETS.coingecko, ", CMC=").concat(REALISTIC_DAILY_BUDGETS.coinmarketcap, ", CoinCap=").concat(REALISTIC_DAILY_BUDGETS.coincap));
|
|
115
|
+
log.info(tag, "Monthly target cost: $180 total ($100 + $50 + $30)");
|
|
116
|
+
return [2 /*return*/];
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* Check if manager is initialized
|
|
123
|
+
*/
|
|
124
|
+
TokenBucketManager.prototype.isInitialized = function () {
|
|
125
|
+
return this.initialized;
|
|
126
|
+
};
|
|
127
|
+
/**
|
|
128
|
+
* Try to consume tokens from specific API
|
|
129
|
+
*/
|
|
130
|
+
TokenBucketManager.prototype.tryConsume = function (apiName_1) {
|
|
131
|
+
return __awaiter(this, arguments, void 0, function (apiName, count) {
|
|
132
|
+
var bucket;
|
|
133
|
+
if (count === void 0) { count = 1; }
|
|
134
|
+
return __generator(this, function (_a) {
|
|
135
|
+
switch (_a.label) {
|
|
136
|
+
case 0:
|
|
137
|
+
if (!this.initialized) {
|
|
138
|
+
log.warn(TAG, 'TokenBucketManager not initialized, allowing request');
|
|
139
|
+
return [2 /*return*/, true]; // Allow requests if not initialized yet
|
|
140
|
+
}
|
|
141
|
+
bucket = this.buckets.get(apiName);
|
|
142
|
+
if (!bucket) {
|
|
143
|
+
log.error(TAG, "Unknown API: ".concat(apiName));
|
|
144
|
+
return [2 /*return*/, false];
|
|
145
|
+
}
|
|
146
|
+
return [4 /*yield*/, bucket.tryConsume(count)];
|
|
147
|
+
case 1: return [2 /*return*/, _a.sent()];
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
};
|
|
152
|
+
/**
|
|
153
|
+
* Get status of all buckets
|
|
154
|
+
*/
|
|
155
|
+
TokenBucketManager.prototype.getAllStatus = function () {
|
|
156
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
157
|
+
var status, _i, _a, _b, apiName, bucket, _c, _d;
|
|
158
|
+
return __generator(this, function (_e) {
|
|
159
|
+
switch (_e.label) {
|
|
160
|
+
case 0:
|
|
161
|
+
status = {};
|
|
162
|
+
_i = 0, _a = Array.from(this.buckets.entries());
|
|
163
|
+
_e.label = 1;
|
|
164
|
+
case 1:
|
|
165
|
+
if (!(_i < _a.length)) return [3 /*break*/, 4];
|
|
166
|
+
_b = _a[_i], apiName = _b[0], bucket = _b[1];
|
|
167
|
+
_c = status;
|
|
168
|
+
_d = apiName;
|
|
169
|
+
return [4 /*yield*/, bucket.getStatus()];
|
|
170
|
+
case 2:
|
|
171
|
+
_c[_d] = _e.sent();
|
|
172
|
+
_e.label = 3;
|
|
173
|
+
case 3:
|
|
174
|
+
_i++;
|
|
175
|
+
return [3 /*break*/, 1];
|
|
176
|
+
case 4: return [2 /*return*/, status];
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
};
|
|
181
|
+
/**
|
|
182
|
+
* Get status of specific bucket
|
|
183
|
+
*/
|
|
184
|
+
TokenBucketManager.prototype.getBucketStatus = function (apiName) {
|
|
185
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
186
|
+
var bucket;
|
|
187
|
+
return __generator(this, function (_a) {
|
|
188
|
+
switch (_a.label) {
|
|
189
|
+
case 0:
|
|
190
|
+
bucket = this.buckets.get(apiName);
|
|
191
|
+
if (!bucket) {
|
|
192
|
+
throw new Error("Unknown API: ".concat(apiName));
|
|
193
|
+
}
|
|
194
|
+
return [4 /*yield*/, bucket.getStatus()];
|
|
195
|
+
case 1: return [2 /*return*/, _a.sent()];
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
};
|
|
200
|
+
/**
|
|
201
|
+
* Refill all buckets (scheduled job runs at midnight UTC)
|
|
202
|
+
*/
|
|
203
|
+
TokenBucketManager.prototype.refillAll = function () {
|
|
204
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
205
|
+
var tag, _i, _a, _b, apiName, bucket;
|
|
206
|
+
return __generator(this, function (_c) {
|
|
207
|
+
switch (_c.label) {
|
|
208
|
+
case 0:
|
|
209
|
+
tag = TAG + ' | refillAll | ';
|
|
210
|
+
log.info(tag, 'Refilling all token buckets...');
|
|
211
|
+
_i = 0, _a = Array.from(this.buckets.entries());
|
|
212
|
+
_c.label = 1;
|
|
213
|
+
case 1:
|
|
214
|
+
if (!(_i < _a.length)) return [3 /*break*/, 4];
|
|
215
|
+
_b = _a[_i], apiName = _b[0], bucket = _b[1];
|
|
216
|
+
return [4 /*yield*/, bucket.refill()];
|
|
217
|
+
case 2:
|
|
218
|
+
_c.sent();
|
|
219
|
+
log.info(tag, "Refilled ".concat(apiName));
|
|
220
|
+
_c.label = 3;
|
|
221
|
+
case 3:
|
|
222
|
+
_i++;
|
|
223
|
+
return [3 /*break*/, 1];
|
|
224
|
+
case 4:
|
|
225
|
+
log.info(tag, '✅ All token buckets refilled');
|
|
226
|
+
return [2 /*return*/];
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
};
|
|
231
|
+
/**
|
|
232
|
+
* Refill specific bucket
|
|
233
|
+
*/
|
|
234
|
+
TokenBucketManager.prototype.refillBucket = function (apiName) {
|
|
235
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
236
|
+
var bucket;
|
|
237
|
+
return __generator(this, function (_a) {
|
|
238
|
+
switch (_a.label) {
|
|
239
|
+
case 0:
|
|
240
|
+
bucket = this.buckets.get(apiName);
|
|
241
|
+
if (!bucket) {
|
|
242
|
+
throw new Error("Unknown API: ".concat(apiName));
|
|
243
|
+
}
|
|
244
|
+
return [4 /*yield*/, bucket.refill()];
|
|
245
|
+
case 1:
|
|
246
|
+
_a.sent();
|
|
247
|
+
log.info(TAG, "Refilled ".concat(apiName));
|
|
248
|
+
return [2 /*return*/];
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
};
|
|
253
|
+
/**
|
|
254
|
+
* Get remaining tokens for API
|
|
255
|
+
*/
|
|
256
|
+
TokenBucketManager.prototype.getRemainingTokens = function (apiName) {
|
|
257
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
258
|
+
var bucket;
|
|
259
|
+
return __generator(this, function (_a) {
|
|
260
|
+
switch (_a.label) {
|
|
261
|
+
case 0:
|
|
262
|
+
bucket = this.buckets.get(apiName);
|
|
263
|
+
if (!bucket) {
|
|
264
|
+
throw new Error("Unknown API: ".concat(apiName));
|
|
265
|
+
}
|
|
266
|
+
return [4 /*yield*/, bucket.getRemainingTokens()];
|
|
267
|
+
case 1: return [2 /*return*/, _a.sent()];
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
};
|
|
272
|
+
/**
|
|
273
|
+
* Get bucket configurations
|
|
274
|
+
*/
|
|
275
|
+
TokenBucketManager.prototype.getBucketConfigs = function () {
|
|
276
|
+
return {
|
|
277
|
+
coingecko: {
|
|
278
|
+
dailyBudget: REALISTIC_DAILY_BUDGETS.coingecko,
|
|
279
|
+
monthlyCost: 100,
|
|
280
|
+
costPerCall: 0.02
|
|
281
|
+
},
|
|
282
|
+
coinmarketcap: {
|
|
283
|
+
dailyBudget: REALISTIC_DAILY_BUDGETS.coinmarketcap,
|
|
284
|
+
monthlyCost: 50,
|
|
285
|
+
costPerCall: 0.015
|
|
286
|
+
},
|
|
287
|
+
coincap: {
|
|
288
|
+
dailyBudget: REALISTIC_DAILY_BUDGETS.coincap,
|
|
289
|
+
monthlyCost: 30,
|
|
290
|
+
costPerCall: 0.01
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
};
|
|
294
|
+
/**
|
|
295
|
+
* Get daily budget summary
|
|
296
|
+
*/
|
|
297
|
+
TokenBucketManager.prototype.getDailyBudgetSummary = function () {
|
|
298
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
299
|
+
var status, configs, totalAllocated, totalUsed, totalCostToday, summary, _i, _a, _b, apiName, apiStatus, config, used, cost;
|
|
300
|
+
return __generator(this, function (_c) {
|
|
301
|
+
switch (_c.label) {
|
|
302
|
+
case 0: return [4 /*yield*/, this.getAllStatus()];
|
|
303
|
+
case 1:
|
|
304
|
+
status = _c.sent();
|
|
305
|
+
configs = this.getBucketConfigs();
|
|
306
|
+
totalAllocated = 0;
|
|
307
|
+
totalUsed = 0;
|
|
308
|
+
totalCostToday = 0;
|
|
309
|
+
summary = {};
|
|
310
|
+
for (_i = 0, _a = Object.entries(status); _i < _a.length; _i++) {
|
|
311
|
+
_b = _a[_i], apiName = _b[0], apiStatus = _b[1];
|
|
312
|
+
config = configs[apiName];
|
|
313
|
+
used = config.dailyBudget - apiStatus.available;
|
|
314
|
+
cost = used * config.costPerCall;
|
|
315
|
+
summary[apiName] = {
|
|
316
|
+
allocated: config.dailyBudget,
|
|
317
|
+
used: used,
|
|
318
|
+
remaining: apiStatus.available,
|
|
319
|
+
utilizationRate: apiStatus.utilizationRate,
|
|
320
|
+
costToday: cost,
|
|
321
|
+
exhausted: apiStatus.exhausted
|
|
322
|
+
};
|
|
323
|
+
totalAllocated += config.dailyBudget;
|
|
324
|
+
totalUsed += used;
|
|
325
|
+
totalCostToday += cost;
|
|
326
|
+
}
|
|
327
|
+
summary.total = {
|
|
328
|
+
allocated: totalAllocated,
|
|
329
|
+
used: totalUsed,
|
|
330
|
+
remaining: totalAllocated - totalUsed,
|
|
331
|
+
utilizationRate: totalUsed / totalAllocated,
|
|
332
|
+
costToday: totalCostToday,
|
|
333
|
+
projectedMonthly: totalCostToday * 30
|
|
334
|
+
};
|
|
335
|
+
return [2 /*return*/, summary];
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
};
|
|
340
|
+
return TokenBucketManager;
|
|
341
|
+
}());
|
|
342
|
+
exports.TokenBucketManager = TokenBucketManager;
|