jiren 1.4.5 → 1.5.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/README.md +168 -3
- package/components/client-node-native.ts +11 -11
- package/components/client.ts +423 -126
- package/components/index.ts +3 -0
- package/components/metrics.ts +420 -0
- package/components/native.ts +42 -0
- package/components/types.ts +65 -5
- package/lib/libhttpclient.dylib +0 -0
- package/package.json +1 -1
- package/components/client-node.ts +0 -473
package/components/index.ts
CHANGED
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metrics & Observability System for Jiren HTTP Client
|
|
3
|
+
* Tracks performance, cache efficiency, errors, and request statistics
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface EndpointMetrics {
|
|
7
|
+
endpoint: string;
|
|
8
|
+
requests: {
|
|
9
|
+
total: number;
|
|
10
|
+
success: number;
|
|
11
|
+
failed: number;
|
|
12
|
+
};
|
|
13
|
+
statusCodes: Record<number, number>;
|
|
14
|
+
timing: {
|
|
15
|
+
avgMs: number;
|
|
16
|
+
minMs: number;
|
|
17
|
+
maxMs: number;
|
|
18
|
+
p50Ms: number;
|
|
19
|
+
p95Ms: number;
|
|
20
|
+
p99Ms: number;
|
|
21
|
+
};
|
|
22
|
+
cache: {
|
|
23
|
+
l1Hits: number;
|
|
24
|
+
l1Misses: number;
|
|
25
|
+
l2Hits: number;
|
|
26
|
+
l2Misses: number;
|
|
27
|
+
hitRate: string; // e.g., "75.5%"
|
|
28
|
+
};
|
|
29
|
+
deduplication: {
|
|
30
|
+
hits: number;
|
|
31
|
+
misses: number;
|
|
32
|
+
hitRate: string;
|
|
33
|
+
};
|
|
34
|
+
bytes: {
|
|
35
|
+
sent: number;
|
|
36
|
+
received: number;
|
|
37
|
+
};
|
|
38
|
+
errors: Record<string, number>;
|
|
39
|
+
lastRequestAt: number | null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface GlobalMetrics {
|
|
43
|
+
totalRequests: number;
|
|
44
|
+
totalSuccess: number;
|
|
45
|
+
totalFailed: number;
|
|
46
|
+
avgResponseTimeMs: number;
|
|
47
|
+
totalBytesSent: number;
|
|
48
|
+
totalBytesReceived: number;
|
|
49
|
+
overallCacheHitRate: string;
|
|
50
|
+
overallDeduplicationRate: string;
|
|
51
|
+
endpoints: number;
|
|
52
|
+
uptime: number; // milliseconds since client creation
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface MetricsAPI {
|
|
56
|
+
get(endpoint: string): EndpointMetrics | null;
|
|
57
|
+
getAll(): Record<string, EndpointMetrics>;
|
|
58
|
+
getGlobal(): GlobalMetrics;
|
|
59
|
+
reset(endpoint?: string): void;
|
|
60
|
+
export(): string; // JSON string
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface RequestMetric {
|
|
64
|
+
startTime: number;
|
|
65
|
+
responseTimeMs: number;
|
|
66
|
+
status: number;
|
|
67
|
+
success: boolean;
|
|
68
|
+
bytesSent: number;
|
|
69
|
+
bytesReceived: number;
|
|
70
|
+
cacheHit: boolean;
|
|
71
|
+
cacheLayer?: "l1" | "l2"; // Which cache layer hit
|
|
72
|
+
dedupeHit: boolean;
|
|
73
|
+
error?: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
class EndpointMetricsCollector {
|
|
77
|
+
private endpoint: string;
|
|
78
|
+
private requestHistory: RequestMetric[] = [];
|
|
79
|
+
private maxHistorySize = 10000; // Keep last 10k requests for percentile calculations
|
|
80
|
+
|
|
81
|
+
// Aggregated counters
|
|
82
|
+
private totalRequests = 0;
|
|
83
|
+
private successCount = 0;
|
|
84
|
+
private failedCount = 0;
|
|
85
|
+
private statusCodeCounts: Record<number, number> = {};
|
|
86
|
+
private l1CacheHits = 0;
|
|
87
|
+
private l1CacheMisses = 0;
|
|
88
|
+
private l2CacheHits = 0;
|
|
89
|
+
private l2CacheMisses = 0;
|
|
90
|
+
private dedupeHits = 0;
|
|
91
|
+
private dedupeMisses = 0;
|
|
92
|
+
private totalBytesSent = 0;
|
|
93
|
+
private totalBytesReceived = 0;
|
|
94
|
+
private errorCounts: Record<string, number> = {};
|
|
95
|
+
private lastRequestTimestamp: number | null = null;
|
|
96
|
+
|
|
97
|
+
// Response time tracking
|
|
98
|
+
private totalResponseTime = 0;
|
|
99
|
+
private minResponseTime = Infinity;
|
|
100
|
+
private maxResponseTime = 0;
|
|
101
|
+
|
|
102
|
+
constructor(endpoint: string) {
|
|
103
|
+
this.endpoint = endpoint;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Record a request metric
|
|
108
|
+
*/
|
|
109
|
+
recordRequest(metric: RequestMetric): void {
|
|
110
|
+
this.totalRequests++;
|
|
111
|
+
this.lastRequestTimestamp = Date.now();
|
|
112
|
+
|
|
113
|
+
// Success/failure
|
|
114
|
+
if (metric.success) {
|
|
115
|
+
this.successCount++;
|
|
116
|
+
} else {
|
|
117
|
+
this.failedCount++;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Status codes
|
|
121
|
+
this.statusCodeCounts[metric.status] =
|
|
122
|
+
(this.statusCodeCounts[metric.status] || 0) + 1;
|
|
123
|
+
|
|
124
|
+
// Cache tracking
|
|
125
|
+
if (metric.cacheHit) {
|
|
126
|
+
if (metric.cacheLayer === "l1") {
|
|
127
|
+
this.l1CacheHits++;
|
|
128
|
+
} else if (metric.cacheLayer === "l2") {
|
|
129
|
+
this.l2CacheHits++;
|
|
130
|
+
}
|
|
131
|
+
} else {
|
|
132
|
+
// Only count as miss if cache was checked (not deduped)
|
|
133
|
+
if (!metric.dedupeHit) {
|
|
134
|
+
this.l1CacheMisses++;
|
|
135
|
+
this.l2CacheMisses++;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Deduplication
|
|
140
|
+
if (metric.dedupeHit) {
|
|
141
|
+
this.dedupeHits++;
|
|
142
|
+
} else {
|
|
143
|
+
this.dedupeMisses++;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Bytes
|
|
147
|
+
this.totalBytesSent += metric.bytesSent;
|
|
148
|
+
this.totalBytesReceived += metric.bytesReceived;
|
|
149
|
+
|
|
150
|
+
// Errors
|
|
151
|
+
if (metric.error) {
|
|
152
|
+
this.errorCounts[metric.error] =
|
|
153
|
+
(this.errorCounts[metric.error] || 0) + 1;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Response time
|
|
157
|
+
this.totalResponseTime += metric.responseTimeMs;
|
|
158
|
+
this.minResponseTime = Math.min(
|
|
159
|
+
this.minResponseTime,
|
|
160
|
+
metric.responseTimeMs
|
|
161
|
+
);
|
|
162
|
+
this.maxResponseTime = Math.max(
|
|
163
|
+
this.maxResponseTime,
|
|
164
|
+
metric.responseTimeMs
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
// Store in history for percentile calculations
|
|
168
|
+
this.requestHistory.push(metric);
|
|
169
|
+
if (this.requestHistory.length > this.maxHistorySize) {
|
|
170
|
+
this.requestHistory.shift(); // Remove oldest
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Calculate percentiles from request history
|
|
176
|
+
*/
|
|
177
|
+
private calculatePercentile(percentile: number): number {
|
|
178
|
+
if (this.requestHistory.length === 0) return 0;
|
|
179
|
+
|
|
180
|
+
const sorted = this.requestHistory
|
|
181
|
+
.map((m) => m.responseTimeMs)
|
|
182
|
+
.sort((a, b) => a - b);
|
|
183
|
+
|
|
184
|
+
const index = Math.ceil((percentile / 100) * sorted.length) - 1;
|
|
185
|
+
return sorted[Math.max(0, index)] || 0;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Get current metrics snapshot
|
|
190
|
+
*/
|
|
191
|
+
getMetrics(): EndpointMetrics {
|
|
192
|
+
const totalCacheAttempts = this.l1CacheHits + this.l1CacheMisses;
|
|
193
|
+
const totalCacheHits = this.l1CacheHits + this.l2CacheHits;
|
|
194
|
+
const cacheHitRate =
|
|
195
|
+
totalCacheAttempts > 0
|
|
196
|
+
? ((totalCacheHits / totalCacheAttempts) * 100).toFixed(2) + "%"
|
|
197
|
+
: "0%";
|
|
198
|
+
|
|
199
|
+
const totalDedupeAttempts = this.dedupeHits + this.dedupeMisses;
|
|
200
|
+
const dedupeHitRate =
|
|
201
|
+
totalDedupeAttempts > 0
|
|
202
|
+
? ((this.dedupeHits / totalDedupeAttempts) * 100).toFixed(2) + "%"
|
|
203
|
+
: "0%";
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
endpoint: this.endpoint,
|
|
207
|
+
requests: {
|
|
208
|
+
total: this.totalRequests,
|
|
209
|
+
success: this.successCount,
|
|
210
|
+
failed: this.failedCount,
|
|
211
|
+
},
|
|
212
|
+
statusCodes: { ...this.statusCodeCounts },
|
|
213
|
+
timing: {
|
|
214
|
+
avgMs:
|
|
215
|
+
this.totalRequests > 0
|
|
216
|
+
? parseFloat(
|
|
217
|
+
(this.totalResponseTime / this.totalRequests).toFixed(2)
|
|
218
|
+
)
|
|
219
|
+
: 0,
|
|
220
|
+
minMs:
|
|
221
|
+
this.minResponseTime === Infinity
|
|
222
|
+
? 0
|
|
223
|
+
: parseFloat(this.minResponseTime.toFixed(2)),
|
|
224
|
+
maxMs: parseFloat(this.maxResponseTime.toFixed(2)),
|
|
225
|
+
p50Ms: parseFloat(this.calculatePercentile(50).toFixed(2)),
|
|
226
|
+
p95Ms: parseFloat(this.calculatePercentile(95).toFixed(2)),
|
|
227
|
+
p99Ms: parseFloat(this.calculatePercentile(99).toFixed(2)),
|
|
228
|
+
},
|
|
229
|
+
cache: {
|
|
230
|
+
l1Hits: this.l1CacheHits,
|
|
231
|
+
l1Misses: this.l1CacheMisses,
|
|
232
|
+
l2Hits: this.l2CacheHits,
|
|
233
|
+
l2Misses: this.l2CacheMisses,
|
|
234
|
+
hitRate: cacheHitRate,
|
|
235
|
+
},
|
|
236
|
+
deduplication: {
|
|
237
|
+
hits: this.dedupeHits,
|
|
238
|
+
misses: this.dedupeMisses,
|
|
239
|
+
hitRate: dedupeHitRate,
|
|
240
|
+
},
|
|
241
|
+
bytes: {
|
|
242
|
+
sent: this.totalBytesSent,
|
|
243
|
+
received: this.totalBytesReceived,
|
|
244
|
+
},
|
|
245
|
+
errors: { ...this.errorCounts },
|
|
246
|
+
lastRequestAt: this.lastRequestTimestamp,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Reset all metrics
|
|
252
|
+
*/
|
|
253
|
+
reset(): void {
|
|
254
|
+
this.requestHistory = [];
|
|
255
|
+
this.totalRequests = 0;
|
|
256
|
+
this.successCount = 0;
|
|
257
|
+
this.failedCount = 0;
|
|
258
|
+
this.statusCodeCounts = {};
|
|
259
|
+
this.l1CacheHits = 0;
|
|
260
|
+
this.l1CacheMisses = 0;
|
|
261
|
+
this.l2CacheHits = 0;
|
|
262
|
+
this.l2CacheMisses = 0;
|
|
263
|
+
this.dedupeHits = 0;
|
|
264
|
+
this.dedupeMisses = 0;
|
|
265
|
+
this.totalBytesSent = 0;
|
|
266
|
+
this.totalBytesReceived = 0;
|
|
267
|
+
this.errorCounts = {};
|
|
268
|
+
this.lastRequestTimestamp = null;
|
|
269
|
+
this.totalResponseTime = 0;
|
|
270
|
+
this.minResponseTime = Infinity;
|
|
271
|
+
this.maxResponseTime = 0;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Main Metrics Collector - manages metrics for all endpoints
|
|
277
|
+
*/
|
|
278
|
+
export class MetricsCollector implements MetricsAPI {
|
|
279
|
+
private endpoints: Map<string, EndpointMetricsCollector> = new Map();
|
|
280
|
+
private startTime: number;
|
|
281
|
+
|
|
282
|
+
constructor() {
|
|
283
|
+
this.startTime = Date.now();
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Get or create endpoint collector
|
|
288
|
+
*/
|
|
289
|
+
private getEndpointCollector(endpoint: string): EndpointMetricsCollector {
|
|
290
|
+
if (!this.endpoints.has(endpoint)) {
|
|
291
|
+
this.endpoints.set(endpoint, new EndpointMetricsCollector(endpoint));
|
|
292
|
+
}
|
|
293
|
+
return this.endpoints.get(endpoint)!;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Record a request event
|
|
298
|
+
*/
|
|
299
|
+
recordRequest(endpoint: string, metric: RequestMetric): void {
|
|
300
|
+
this.getEndpointCollector(endpoint).recordRequest(metric);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Get metrics for a specific endpoint
|
|
305
|
+
*/
|
|
306
|
+
get(endpoint: string): EndpointMetrics | null {
|
|
307
|
+
const collector = this.endpoints.get(endpoint);
|
|
308
|
+
return collector ? collector.getMetrics() : null;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Get metrics for all endpoints
|
|
313
|
+
*/
|
|
314
|
+
getAll(): Record<string, EndpointMetrics> {
|
|
315
|
+
const result: Record<string, EndpointMetrics> = {};
|
|
316
|
+
for (const [endpoint, collector] of this.endpoints) {
|
|
317
|
+
result[endpoint] = collector.getMetrics();
|
|
318
|
+
}
|
|
319
|
+
return result;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Get global aggregated metrics
|
|
324
|
+
*/
|
|
325
|
+
getGlobal(): GlobalMetrics {
|
|
326
|
+
let totalRequests = 0;
|
|
327
|
+
let totalSuccess = 0;
|
|
328
|
+
let totalFailed = 0;
|
|
329
|
+
let totalResponseTime = 0;
|
|
330
|
+
let totalBytesSent = 0;
|
|
331
|
+
let totalBytesReceived = 0;
|
|
332
|
+
let totalCacheHits = 0;
|
|
333
|
+
let totalCacheAttempts = 0;
|
|
334
|
+
let totalDedupeHits = 0;
|
|
335
|
+
let totalDedupeAttempts = 0;
|
|
336
|
+
|
|
337
|
+
for (const collector of this.endpoints.values()) {
|
|
338
|
+
const metrics = collector.getMetrics();
|
|
339
|
+
totalRequests += metrics.requests.total;
|
|
340
|
+
totalSuccess += metrics.requests.success;
|
|
341
|
+
totalFailed += metrics.requests.failed;
|
|
342
|
+
totalResponseTime += metrics.timing.avgMs * metrics.requests.total;
|
|
343
|
+
totalBytesSent += metrics.bytes.sent;
|
|
344
|
+
totalBytesReceived += metrics.bytes.received;
|
|
345
|
+
|
|
346
|
+
totalCacheHits += metrics.cache.l1Hits + metrics.cache.l2Hits;
|
|
347
|
+
totalCacheAttempts +=
|
|
348
|
+
metrics.cache.l1Hits +
|
|
349
|
+
metrics.cache.l1Misses +
|
|
350
|
+
metrics.cache.l2Hits +
|
|
351
|
+
metrics.cache.l2Misses;
|
|
352
|
+
|
|
353
|
+
totalDedupeHits += metrics.deduplication.hits;
|
|
354
|
+
totalDedupeAttempts +=
|
|
355
|
+
metrics.deduplication.hits + metrics.deduplication.misses;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const avgResponseTimeMs =
|
|
359
|
+
totalRequests > 0 ? totalResponseTime / totalRequests : 0;
|
|
360
|
+
|
|
361
|
+
const overallCacheHitRate =
|
|
362
|
+
totalCacheAttempts > 0
|
|
363
|
+
? ((totalCacheHits / totalCacheAttempts) * 100).toFixed(2) + "%"
|
|
364
|
+
: "0%";
|
|
365
|
+
|
|
366
|
+
const overallDeduplicationRate =
|
|
367
|
+
totalDedupeAttempts > 0
|
|
368
|
+
? ((totalDedupeHits / totalDedupeAttempts) * 100).toFixed(2) + "%"
|
|
369
|
+
: "0%";
|
|
370
|
+
|
|
371
|
+
return {
|
|
372
|
+
totalRequests,
|
|
373
|
+
totalSuccess,
|
|
374
|
+
totalFailed,
|
|
375
|
+
avgResponseTimeMs: parseFloat(avgResponseTimeMs.toFixed(2)),
|
|
376
|
+
totalBytesSent,
|
|
377
|
+
totalBytesReceived,
|
|
378
|
+
overallCacheHitRate,
|
|
379
|
+
overallDeduplicationRate,
|
|
380
|
+
endpoints: this.endpoints.size,
|
|
381
|
+
uptime: Date.now() - this.startTime,
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Reset metrics for specific endpoint or all
|
|
387
|
+
*/
|
|
388
|
+
reset(endpoint?: string): void {
|
|
389
|
+
if (endpoint) {
|
|
390
|
+
const collector = this.endpoints.get(endpoint);
|
|
391
|
+
if (collector) {
|
|
392
|
+
collector.reset();
|
|
393
|
+
}
|
|
394
|
+
} else {
|
|
395
|
+
// Reset all
|
|
396
|
+
for (const collector of this.endpoints.values()) {
|
|
397
|
+
collector.reset();
|
|
398
|
+
}
|
|
399
|
+
this.startTime = Date.now();
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Export all metrics as JSON string
|
|
405
|
+
*/
|
|
406
|
+
export(): string {
|
|
407
|
+
return JSON.stringify(
|
|
408
|
+
{
|
|
409
|
+
global: this.getGlobal(),
|
|
410
|
+
endpoints: this.getAll(),
|
|
411
|
+
exportedAt: new Date().toISOString(),
|
|
412
|
+
},
|
|
413
|
+
null,
|
|
414
|
+
2
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Export for use in client.ts
|
|
420
|
+
export type { RequestMetric };
|
package/components/native.ts
CHANGED
|
@@ -82,6 +82,48 @@ export const ffiDef = {
|
|
|
82
82
|
returns: FFIType.void,
|
|
83
83
|
},
|
|
84
84
|
|
|
85
|
+
// =========================================================================
|
|
86
|
+
// OPTIMIZED SINGLE-CALL API
|
|
87
|
+
// =========================================================================
|
|
88
|
+
|
|
89
|
+
zclient_request_full: {
|
|
90
|
+
args: [
|
|
91
|
+
FFIType.ptr, // client
|
|
92
|
+
FFIType.cstring, // method
|
|
93
|
+
FFIType.cstring, // url
|
|
94
|
+
FFIType.cstring, // headers (nullable)
|
|
95
|
+
FFIType.cstring, // body (nullable)
|
|
96
|
+
FFIType.u8, // max_redirects
|
|
97
|
+
FFIType.bool, // antibot
|
|
98
|
+
],
|
|
99
|
+
returns: FFIType.ptr, // ZFullResponse*
|
|
100
|
+
},
|
|
101
|
+
zclient_response_full_free: {
|
|
102
|
+
args: [FFIType.ptr],
|
|
103
|
+
returns: FFIType.void,
|
|
104
|
+
},
|
|
105
|
+
// Accessor functions for ZFullResponse
|
|
106
|
+
zfull_response_status: {
|
|
107
|
+
args: [FFIType.ptr],
|
|
108
|
+
returns: FFIType.u16,
|
|
109
|
+
},
|
|
110
|
+
zfull_response_body: {
|
|
111
|
+
args: [FFIType.ptr],
|
|
112
|
+
returns: FFIType.ptr,
|
|
113
|
+
},
|
|
114
|
+
zfull_response_body_len: {
|
|
115
|
+
args: [FFIType.ptr],
|
|
116
|
+
returns: FFIType.u64,
|
|
117
|
+
},
|
|
118
|
+
zfull_response_headers: {
|
|
119
|
+
args: [FFIType.ptr],
|
|
120
|
+
returns: FFIType.ptr,
|
|
121
|
+
},
|
|
122
|
+
zfull_response_headers_len: {
|
|
123
|
+
args: [FFIType.ptr],
|
|
124
|
+
returns: FFIType.u64,
|
|
125
|
+
},
|
|
126
|
+
|
|
85
127
|
// =========================================================================
|
|
86
128
|
// CACHE FFI
|
|
87
129
|
// =========================================================================
|
package/components/types.ts
CHANGED
|
@@ -90,11 +90,11 @@ export interface ParsedUrl {
|
|
|
90
90
|
path: string;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
/**
|
|
94
|
-
export interface
|
|
93
|
+
/** Target URL with a key for type-safe access */
|
|
94
|
+
export interface TargetUrlConfig {
|
|
95
95
|
/** Unique key to access this URL via client.url[key] */
|
|
96
96
|
key: string;
|
|
97
|
-
/** The URL
|
|
97
|
+
/** The target URL */
|
|
98
98
|
url: string;
|
|
99
99
|
/** Enable response caching for this URL (default: false) */
|
|
100
100
|
cache?: boolean | CacheConfig;
|
|
@@ -183,8 +183,8 @@ export interface UrlEndpoint {
|
|
|
183
183
|
prefetch(options?: UrlRequestOptions): Promise<void>;
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
-
/** Type helper to extract keys from
|
|
187
|
-
export type
|
|
186
|
+
/** Type helper to extract keys from target config array */
|
|
187
|
+
export type ExtractTargetKeys<T extends readonly TargetUrlConfig[]> =
|
|
188
188
|
T[number]["key"];
|
|
189
189
|
|
|
190
190
|
/** Context passed to request interceptors */
|
|
@@ -224,3 +224,63 @@ export interface Interceptors {
|
|
|
224
224
|
response?: ResponseInterceptor[];
|
|
225
225
|
error?: ErrorInterceptor[];
|
|
226
226
|
}
|
|
227
|
+
|
|
228
|
+
/** Metrics for a single endpoint */
|
|
229
|
+
export interface EndpointMetrics {
|
|
230
|
+
endpoint: string;
|
|
231
|
+
requests: {
|
|
232
|
+
total: number;
|
|
233
|
+
success: number;
|
|
234
|
+
failed: number;
|
|
235
|
+
};
|
|
236
|
+
statusCodes: Record<number, number>;
|
|
237
|
+
timing: {
|
|
238
|
+
avgMs: number;
|
|
239
|
+
minMs: number;
|
|
240
|
+
maxMs: number;
|
|
241
|
+
p50Ms: number;
|
|
242
|
+
p95Ms: number;
|
|
243
|
+
p99Ms: number;
|
|
244
|
+
};
|
|
245
|
+
cache: {
|
|
246
|
+
l1Hits: number;
|
|
247
|
+
l1Misses: number;
|
|
248
|
+
l2Hits: number;
|
|
249
|
+
l2Misses: number;
|
|
250
|
+
hitRate: string;
|
|
251
|
+
};
|
|
252
|
+
deduplication: {
|
|
253
|
+
hits: number;
|
|
254
|
+
misses: number;
|
|
255
|
+
hitRate: string;
|
|
256
|
+
};
|
|
257
|
+
bytes: {
|
|
258
|
+
sent: number;
|
|
259
|
+
received: number;
|
|
260
|
+
};
|
|
261
|
+
errors: Record<string, number>;
|
|
262
|
+
lastRequestAt: number | null;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** Global aggregated metrics across all endpoints */
|
|
266
|
+
export interface GlobalMetrics {
|
|
267
|
+
totalRequests: number;
|
|
268
|
+
totalSuccess: number;
|
|
269
|
+
totalFailed: number;
|
|
270
|
+
avgResponseTimeMs: number;
|
|
271
|
+
totalBytesSent: number;
|
|
272
|
+
totalBytesReceived: number;
|
|
273
|
+
overallCacheHitRate: string;
|
|
274
|
+
overallDeduplicationRate: string;
|
|
275
|
+
endpoints: number;
|
|
276
|
+
uptime: number;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** Public API for accessing metrics */
|
|
280
|
+
export interface MetricsAPI {
|
|
281
|
+
get(endpoint: string): EndpointMetrics | null;
|
|
282
|
+
getAll(): Record<string, EndpointMetrics>;
|
|
283
|
+
getGlobal(): GlobalMetrics;
|
|
284
|
+
reset(endpoint?: string): void;
|
|
285
|
+
export(): string;
|
|
286
|
+
}
|
package/lib/libhttpclient.dylib
CHANGED
|
Binary file
|