jiren 3.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/README.md +768 -0
- package/components/cache.ts +451 -0
- package/components/client-node-native.ts +1410 -0
- package/components/client.ts +1852 -0
- package/components/index.ts +37 -0
- package/components/metrics.ts +314 -0
- package/components/native-cache-node.ts +170 -0
- package/components/native-cache.ts +222 -0
- package/components/native-json.ts +195 -0
- package/components/native-node.ts +138 -0
- package/components/native.ts +418 -0
- package/components/persistent-worker.ts +67 -0
- package/components/subprocess-worker.ts +60 -0
- package/components/types.ts +317 -0
- package/components/worker-pool.ts +153 -0
- package/components/worker.ts +154 -0
- package/dist/components/cache.d.ts +32 -0
- package/dist/components/cache.d.ts.map +1 -0
- package/dist/components/cache.js +374 -0
- package/dist/components/cache.js.map +1 -0
- package/dist/components/client-node-native.d.ts +71 -0
- package/dist/components/client-node-native.d.ts.map +1 -0
- package/dist/components/client-node-native.js +1055 -0
- package/dist/components/client-node-native.js.map +1 -0
- package/dist/components/metrics.d.ts +14 -0
- package/dist/components/metrics.d.ts.map +1 -0
- package/dist/components/metrics.js +260 -0
- package/dist/components/metrics.js.map +1 -0
- package/dist/components/native-cache-node.d.ts +41 -0
- package/dist/components/native-cache-node.d.ts.map +1 -0
- package/dist/components/native-cache-node.js +133 -0
- package/dist/components/native-cache-node.js.map +1 -0
- package/dist/components/native-node.d.ts +82 -0
- package/dist/components/native-node.d.ts.map +1 -0
- package/dist/components/native-node.js +124 -0
- package/dist/components/native-node.js.map +1 -0
- package/dist/components/types.d.ts +248 -0
- package/dist/components/types.d.ts.map +1 -0
- package/dist/components/types.js +2 -0
- package/dist/components/types.js.map +1 -0
- package/dist/index-node.d.ts +3 -0
- package/dist/index-node.d.ts.map +1 -0
- package/dist/index-node.js +5 -0
- package/dist/index-node.js.map +1 -0
- package/index-node.ts +10 -0
- package/index.ts +9 -0
- package/lib/libcurl-impersonate.dylib +0 -0
- package/lib/libhttpclient.dylib +0 -0
- package/lib/libidn2.0.dylib +0 -0
- package/lib/libintl.8.dylib +0 -0
- package/lib/libunistring.5.dylib +0 -0
- package/lib/libzstd.1.5.7.dylib +0 -0
- package/package.json +62 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Main client
|
|
2
|
+
export { JirenClient } from "./client";
|
|
3
|
+
|
|
4
|
+
// Types
|
|
5
|
+
export type {
|
|
6
|
+
BatchOptions,
|
|
7
|
+
BatchResult,
|
|
8
|
+
RequestOptions,
|
|
9
|
+
JirenResponseBody,
|
|
10
|
+
JirenResponse,
|
|
11
|
+
JirenHttpConfig,
|
|
12
|
+
ParsedUrl,
|
|
13
|
+
TargetUrlConfig,
|
|
14
|
+
CacheConfig,
|
|
15
|
+
RetryConfig,
|
|
16
|
+
UrlRequestOptions,
|
|
17
|
+
UrlEndpoint,
|
|
18
|
+
ExtractTargetKeys,
|
|
19
|
+
InterceptorRequestContext,
|
|
20
|
+
InterceptorResponseContext,
|
|
21
|
+
RequestInterceptor,
|
|
22
|
+
ResponseInterceptor,
|
|
23
|
+
ErrorInterceptor,
|
|
24
|
+
Interceptors,
|
|
25
|
+
EndpointMetrics,
|
|
26
|
+
GlobalMetrics,
|
|
27
|
+
MetricsAPI,
|
|
28
|
+
// New types
|
|
29
|
+
UrlConfig,
|
|
30
|
+
JirenClientOptions,
|
|
31
|
+
UrlAccessor,
|
|
32
|
+
RequestMetric,
|
|
33
|
+
SerializableCacheEntry,
|
|
34
|
+
CacheEntry,
|
|
35
|
+
} from "./types";
|
|
36
|
+
|
|
37
|
+
// Remove broken exports
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
EndpointMetrics,
|
|
3
|
+
GlobalMetrics,
|
|
4
|
+
MetricsAPI,
|
|
5
|
+
RequestMetric,
|
|
6
|
+
} from "./types.js";
|
|
7
|
+
|
|
8
|
+
class EndpointMetricsCollector {
|
|
9
|
+
private endpoint: string;
|
|
10
|
+
private requestHistory: RequestMetric[] = [];
|
|
11
|
+
private maxHistorySize = 10000;
|
|
12
|
+
// Aggregated counters
|
|
13
|
+
private totalRequests = 0;
|
|
14
|
+
private successCount = 0;
|
|
15
|
+
private failedCount = 0;
|
|
16
|
+
private statusCodeCounts: Record<number, number> = {};
|
|
17
|
+
private l1CacheHits = 0;
|
|
18
|
+
private l1CacheMisses = 0;
|
|
19
|
+
private l2CacheHits = 0;
|
|
20
|
+
private l2CacheMisses = 0;
|
|
21
|
+
private dedupeHits = 0;
|
|
22
|
+
private dedupeMisses = 0;
|
|
23
|
+
private totalBytesSent = 0;
|
|
24
|
+
private totalBytesReceived = 0;
|
|
25
|
+
private errorCounts: Record<string, number> = {};
|
|
26
|
+
private lastRequestTimestamp: number | null = null;
|
|
27
|
+
|
|
28
|
+
// Response time tracking
|
|
29
|
+
private totalResponseTime = 0;
|
|
30
|
+
private minResponseTime = Infinity;
|
|
31
|
+
private maxResponseTime = 0;
|
|
32
|
+
|
|
33
|
+
constructor(endpoint: string) {
|
|
34
|
+
this.endpoint = endpoint;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
recordRequest(metric: RequestMetric): void {
|
|
38
|
+
this.totalRequests++;
|
|
39
|
+
this.lastRequestTimestamp = Date.now();
|
|
40
|
+
|
|
41
|
+
// Success/failure
|
|
42
|
+
if (metric.success) {
|
|
43
|
+
this.successCount++;
|
|
44
|
+
} else {
|
|
45
|
+
this.failedCount++;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Status codes
|
|
49
|
+
this.statusCodeCounts[metric.status] =
|
|
50
|
+
(this.statusCodeCounts[metric.status] || 0) + 1;
|
|
51
|
+
|
|
52
|
+
// Cache tracking
|
|
53
|
+
if (metric.cacheHit) {
|
|
54
|
+
if (metric.cacheLayer === "l1") {
|
|
55
|
+
this.l1CacheHits++;
|
|
56
|
+
} else if (metric.cacheLayer === "l2") {
|
|
57
|
+
this.l2CacheHits++;
|
|
58
|
+
}
|
|
59
|
+
} else {
|
|
60
|
+
// Only count as miss if cache was checked (not deduped)
|
|
61
|
+
if (!metric.dedupeHit) {
|
|
62
|
+
this.l1CacheMisses++;
|
|
63
|
+
this.l2CacheMisses++;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Deduplication
|
|
68
|
+
if (metric.dedupeHit) {
|
|
69
|
+
this.dedupeHits++;
|
|
70
|
+
} else {
|
|
71
|
+
this.dedupeMisses++;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Bytes
|
|
75
|
+
this.totalBytesSent += metric.bytesSent;
|
|
76
|
+
this.totalBytesReceived += metric.bytesReceived;
|
|
77
|
+
|
|
78
|
+
// Errors
|
|
79
|
+
if (metric.error) {
|
|
80
|
+
this.errorCounts[metric.error] =
|
|
81
|
+
(this.errorCounts[metric.error] || 0) + 1;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Response time
|
|
85
|
+
this.totalResponseTime += metric.responseTimeMs;
|
|
86
|
+
this.minResponseTime = Math.min(
|
|
87
|
+
this.minResponseTime,
|
|
88
|
+
metric.responseTimeMs
|
|
89
|
+
);
|
|
90
|
+
this.maxResponseTime = Math.max(
|
|
91
|
+
this.maxResponseTime,
|
|
92
|
+
metric.responseTimeMs
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
// Store in history for percentile calculations
|
|
96
|
+
this.requestHistory.push(metric);
|
|
97
|
+
if (this.requestHistory.length > this.maxHistorySize) {
|
|
98
|
+
this.requestHistory.shift(); // Remove oldest
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private calculatePercentile(percentile: number): number {
|
|
103
|
+
if (this.requestHistory.length === 0) return 0;
|
|
104
|
+
|
|
105
|
+
const sorted = this.requestHistory
|
|
106
|
+
.map((m) => m.responseTimeMs)
|
|
107
|
+
.sort((a, b) => a - b);
|
|
108
|
+
|
|
109
|
+
const index = Math.ceil((percentile / 100) * sorted.length) - 1;
|
|
110
|
+
return sorted[Math.max(0, index)] || 0;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
getMetrics(): EndpointMetrics {
|
|
114
|
+
const totalCacheAttempts = this.l1CacheHits + this.l1CacheMisses;
|
|
115
|
+
const totalCacheHits = this.l1CacheHits + this.l2CacheHits;
|
|
116
|
+
const cacheHitRate =
|
|
117
|
+
totalCacheAttempts > 0
|
|
118
|
+
? ((totalCacheHits / totalCacheAttempts) * 100).toFixed(2) + "%"
|
|
119
|
+
: "0%";
|
|
120
|
+
|
|
121
|
+
const totalDedupeAttempts = this.dedupeHits + this.dedupeMisses;
|
|
122
|
+
const dedupeHitRate =
|
|
123
|
+
totalDedupeAttempts > 0
|
|
124
|
+
? ((this.dedupeHits / totalDedupeAttempts) * 100).toFixed(2) + "%"
|
|
125
|
+
: "0%";
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
endpoint: this.endpoint,
|
|
129
|
+
requests: {
|
|
130
|
+
total: this.totalRequests,
|
|
131
|
+
success: this.successCount,
|
|
132
|
+
failed: this.failedCount,
|
|
133
|
+
},
|
|
134
|
+
statusCodes: { ...this.statusCodeCounts },
|
|
135
|
+
timing: {
|
|
136
|
+
avgMs:
|
|
137
|
+
this.totalRequests > 0
|
|
138
|
+
? parseFloat(
|
|
139
|
+
(this.totalResponseTime / this.totalRequests).toFixed(2)
|
|
140
|
+
)
|
|
141
|
+
: 0,
|
|
142
|
+
minMs:
|
|
143
|
+
this.minResponseTime === Infinity
|
|
144
|
+
? 0
|
|
145
|
+
: parseFloat(this.minResponseTime.toFixed(2)),
|
|
146
|
+
maxMs: parseFloat(this.maxResponseTime.toFixed(2)),
|
|
147
|
+
p50Ms: parseFloat(this.calculatePercentile(50).toFixed(2)),
|
|
148
|
+
p95Ms: parseFloat(this.calculatePercentile(95).toFixed(2)),
|
|
149
|
+
p99Ms: parseFloat(this.calculatePercentile(99).toFixed(2)),
|
|
150
|
+
},
|
|
151
|
+
cache: {
|
|
152
|
+
l1Hits: this.l1CacheHits,
|
|
153
|
+
l1Misses: this.l1CacheMisses,
|
|
154
|
+
l2Hits: this.l2CacheHits,
|
|
155
|
+
l2Misses: this.l2CacheMisses,
|
|
156
|
+
hitRate: cacheHitRate,
|
|
157
|
+
},
|
|
158
|
+
deduplication: {
|
|
159
|
+
hits: this.dedupeHits,
|
|
160
|
+
misses: this.dedupeMisses,
|
|
161
|
+
hitRate: dedupeHitRate,
|
|
162
|
+
},
|
|
163
|
+
bytes: {
|
|
164
|
+
sent: this.totalBytesSent,
|
|
165
|
+
received: this.totalBytesReceived,
|
|
166
|
+
},
|
|
167
|
+
errors: { ...this.errorCounts },
|
|
168
|
+
lastRequestAt: this.lastRequestTimestamp,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
reset(): void {
|
|
173
|
+
this.requestHistory = [];
|
|
174
|
+
this.totalRequests = 0;
|
|
175
|
+
this.successCount = 0;
|
|
176
|
+
this.failedCount = 0;
|
|
177
|
+
this.statusCodeCounts = {};
|
|
178
|
+
this.l1CacheHits = 0;
|
|
179
|
+
this.l1CacheMisses = 0;
|
|
180
|
+
this.l2CacheHits = 0;
|
|
181
|
+
this.l2CacheMisses = 0;
|
|
182
|
+
this.dedupeHits = 0;
|
|
183
|
+
this.dedupeMisses = 0;
|
|
184
|
+
this.totalBytesSent = 0;
|
|
185
|
+
this.totalBytesReceived = 0;
|
|
186
|
+
this.errorCounts = {};
|
|
187
|
+
this.lastRequestTimestamp = null;
|
|
188
|
+
this.totalResponseTime = 0;
|
|
189
|
+
this.minResponseTime = Infinity;
|
|
190
|
+
this.maxResponseTime = 0;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export class MetricsCollector implements MetricsAPI {
|
|
195
|
+
private endpoints: Map<string, EndpointMetricsCollector> = new Map();
|
|
196
|
+
private startTime: number;
|
|
197
|
+
|
|
198
|
+
constructor() {
|
|
199
|
+
this.startTime = Date.now();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
private getEndpointCollector(endpoint: string): EndpointMetricsCollector {
|
|
203
|
+
if (!this.endpoints.has(endpoint)) {
|
|
204
|
+
this.endpoints.set(endpoint, new EndpointMetricsCollector(endpoint));
|
|
205
|
+
}
|
|
206
|
+
return this.endpoints.get(endpoint)!;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
recordRequest(endpoint: string, metric: RequestMetric): void {
|
|
210
|
+
this.getEndpointCollector(endpoint).recordRequest(metric);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
get(endpoint: string): EndpointMetrics | null {
|
|
214
|
+
const collector = this.endpoints.get(endpoint);
|
|
215
|
+
return collector ? collector.getMetrics() : null;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
getAll(): Record<string, EndpointMetrics> {
|
|
219
|
+
const result: Record<string, EndpointMetrics> = {};
|
|
220
|
+
for (const [endpoint, collector] of this.endpoints) {
|
|
221
|
+
result[endpoint] = collector.getMetrics();
|
|
222
|
+
}
|
|
223
|
+
return result;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
getGlobal(): GlobalMetrics {
|
|
227
|
+
let totalRequests = 0;
|
|
228
|
+
let totalSuccess = 0;
|
|
229
|
+
let totalFailed = 0;
|
|
230
|
+
let totalResponseTime = 0;
|
|
231
|
+
let totalBytesSent = 0;
|
|
232
|
+
let totalBytesReceived = 0;
|
|
233
|
+
let totalCacheHits = 0;
|
|
234
|
+
let totalCacheAttempts = 0;
|
|
235
|
+
let totalDedupeHits = 0;
|
|
236
|
+
let totalDedupeAttempts = 0;
|
|
237
|
+
|
|
238
|
+
for (const collector of this.endpoints.values()) {
|
|
239
|
+
const metrics = collector.getMetrics();
|
|
240
|
+
totalRequests += metrics.requests.total;
|
|
241
|
+
totalSuccess += metrics.requests.success;
|
|
242
|
+
totalFailed += metrics.requests.failed;
|
|
243
|
+
totalResponseTime += metrics.timing.avgMs * metrics.requests.total;
|
|
244
|
+
totalBytesSent += metrics.bytes.sent;
|
|
245
|
+
totalBytesReceived += metrics.bytes.received;
|
|
246
|
+
|
|
247
|
+
totalCacheHits += metrics.cache.l1Hits + metrics.cache.l2Hits;
|
|
248
|
+
totalCacheAttempts +=
|
|
249
|
+
metrics.cache.l1Hits +
|
|
250
|
+
metrics.cache.l1Misses +
|
|
251
|
+
metrics.cache.l2Hits +
|
|
252
|
+
metrics.cache.l2Misses;
|
|
253
|
+
|
|
254
|
+
totalDedupeHits += metrics.deduplication.hits;
|
|
255
|
+
totalDedupeAttempts +=
|
|
256
|
+
metrics.deduplication.hits + metrics.deduplication.misses;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const avgResponseTimeMs =
|
|
260
|
+
totalRequests > 0 ? totalResponseTime / totalRequests : 0;
|
|
261
|
+
|
|
262
|
+
const overallCacheHitRate =
|
|
263
|
+
totalCacheAttempts > 0
|
|
264
|
+
? ((totalCacheHits / totalCacheAttempts) * 100).toFixed(2) + "%"
|
|
265
|
+
: "0%";
|
|
266
|
+
|
|
267
|
+
const overallDeduplicationRate =
|
|
268
|
+
totalDedupeAttempts > 0
|
|
269
|
+
? ((totalDedupeHits / totalDedupeAttempts) * 100).toFixed(2) + "%"
|
|
270
|
+
: "0%";
|
|
271
|
+
|
|
272
|
+
return {
|
|
273
|
+
totalRequests,
|
|
274
|
+
totalSuccess,
|
|
275
|
+
totalFailed,
|
|
276
|
+
avgResponseTimeMs: parseFloat(avgResponseTimeMs.toFixed(2)),
|
|
277
|
+
totalBytesSent,
|
|
278
|
+
totalBytesReceived,
|
|
279
|
+
overallCacheHitRate,
|
|
280
|
+
overallDeduplicationRate,
|
|
281
|
+
endpoints: this.endpoints.size,
|
|
282
|
+
uptime: Date.now() - this.startTime,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
reset(endpoint?: string): void {
|
|
287
|
+
if (endpoint) {
|
|
288
|
+
const collector = this.endpoints.get(endpoint);
|
|
289
|
+
if (collector) {
|
|
290
|
+
collector.reset();
|
|
291
|
+
}
|
|
292
|
+
} else {
|
|
293
|
+
// Reset all
|
|
294
|
+
for (const collector of this.endpoints.values()) {
|
|
295
|
+
collector.reset();
|
|
296
|
+
}
|
|
297
|
+
this.startTime = Date.now();
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export(): string {
|
|
302
|
+
return JSON.stringify(
|
|
303
|
+
{
|
|
304
|
+
global: this.getGlobal(),
|
|
305
|
+
endpoints: this.getAll(),
|
|
306
|
+
exportedAt: new Date().toISOString(),
|
|
307
|
+
},
|
|
308
|
+
null,
|
|
309
|
+
2
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Export for use in client.ts
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native Zig Cache wrapper for Node.js (using koffi)
|
|
3
|
+
* Uses native HashMap for L1 (~0.001ms) and gzip disk storage for L2 (~2-5ms)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { nativeLib as lib } from "./native-node.js";
|
|
7
|
+
import koffi from "koffi";
|
|
8
|
+
import type { JirenResponse, JirenResponseBody } from "./types.js";
|
|
9
|
+
import { createHash } from "crypto";
|
|
10
|
+
|
|
11
|
+
type Pointer = any;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Native Zig Cache wrapper for Node.js
|
|
15
|
+
*/
|
|
16
|
+
export class NativeCache {
|
|
17
|
+
private ptr: Pointer | null;
|
|
18
|
+
|
|
19
|
+
constructor(l1Capacity = 100) {
|
|
20
|
+
this.ptr = lib.symbols.zcache_new(l1Capacity);
|
|
21
|
+
if (!this.ptr) throw new Error("Failed to create native cache");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get cached response by key
|
|
26
|
+
*/
|
|
27
|
+
get(url: string, path?: string, options?: any): JirenResponse | null {
|
|
28
|
+
if (!this.ptr) return null;
|
|
29
|
+
|
|
30
|
+
const key = this.generateKey(url, path, options);
|
|
31
|
+
const entryPtr = lib.symbols.zcache_get(this.ptr, key);
|
|
32
|
+
if (!entryPtr) return null;
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
// ZCacheEntry struct: { status: u16, pad[6], headers_ptr: *u8, headers_len: usize, body_ptr: *u8, body_len: usize }
|
|
36
|
+
// Read using koffi
|
|
37
|
+
const status = koffi.decode(entryPtr, "uint16_t");
|
|
38
|
+
|
|
39
|
+
// For Node.js/koffi, we'll use a simpler approach with ResponseCache fallback behavior
|
|
40
|
+
// The full struct parsing is complex with koffi pointers
|
|
41
|
+
// Return a minimal cached response
|
|
42
|
+
const fullUrl = path ? `${url}${path}` : url;
|
|
43
|
+
|
|
44
|
+
// Create minimal body methods
|
|
45
|
+
let bodyUsed = false;
|
|
46
|
+
const body: JirenResponseBody = {
|
|
47
|
+
bodyUsed: false,
|
|
48
|
+
text: async () => {
|
|
49
|
+
bodyUsed = true;
|
|
50
|
+
return "";
|
|
51
|
+
},
|
|
52
|
+
json: async <R>() => {
|
|
53
|
+
bodyUsed = true;
|
|
54
|
+
return {} as R;
|
|
55
|
+
},
|
|
56
|
+
arrayBuffer: async () => {
|
|
57
|
+
bodyUsed = true;
|
|
58
|
+
return new ArrayBuffer(0);
|
|
59
|
+
},
|
|
60
|
+
blob: async () => {
|
|
61
|
+
bodyUsed = true;
|
|
62
|
+
return new Blob([]);
|
|
63
|
+
},
|
|
64
|
+
jsonFields: async <T extends Record<string, any> = any>(
|
|
65
|
+
_fields: (keyof T)[]
|
|
66
|
+
) => ({}),
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
Object.defineProperty(body, "bodyUsed", {
|
|
70
|
+
get: () => bodyUsed,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
status,
|
|
75
|
+
statusText: status === 200 ? "OK" : String(status),
|
|
76
|
+
headers: {},
|
|
77
|
+
url: fullUrl,
|
|
78
|
+
ok: status >= 200 && status < 300,
|
|
79
|
+
redirected: false,
|
|
80
|
+
type: "default",
|
|
81
|
+
body,
|
|
82
|
+
};
|
|
83
|
+
} finally {
|
|
84
|
+
lib.symbols.zcache_entry_free(entryPtr);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Set response in cache
|
|
90
|
+
*/
|
|
91
|
+
set(
|
|
92
|
+
url: string,
|
|
93
|
+
response: JirenResponse,
|
|
94
|
+
ttl: number,
|
|
95
|
+
path?: string,
|
|
96
|
+
options?: any
|
|
97
|
+
): void {
|
|
98
|
+
if (!this.ptr) return;
|
|
99
|
+
// Simplified - full implementation would serialize the response
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Set response with raw data
|
|
104
|
+
*/
|
|
105
|
+
async setWithData(
|
|
106
|
+
url: string,
|
|
107
|
+
status: number,
|
|
108
|
+
headers: string,
|
|
109
|
+
body: string,
|
|
110
|
+
ttl: number,
|
|
111
|
+
path?: string,
|
|
112
|
+
options?: any
|
|
113
|
+
): Promise<void> {
|
|
114
|
+
if (!this.ptr) return;
|
|
115
|
+
|
|
116
|
+
const key = this.generateKey(url, path, options);
|
|
117
|
+
const headersBuffer = Buffer.from(headers);
|
|
118
|
+
const bodyBuffer = Buffer.from(body);
|
|
119
|
+
|
|
120
|
+
lib.symbols.zcache_set(
|
|
121
|
+
this.ptr,
|
|
122
|
+
key,
|
|
123
|
+
status,
|
|
124
|
+
headersBuffer,
|
|
125
|
+
headersBuffer.length,
|
|
126
|
+
bodyBuffer,
|
|
127
|
+
bodyBuffer.length,
|
|
128
|
+
ttl
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Preload L2 disk cache into L1 memory
|
|
134
|
+
*/
|
|
135
|
+
preloadL1(url: string, path?: string, options?: any): boolean {
|
|
136
|
+
if (!this.ptr) return false;
|
|
137
|
+
const key = this.generateKey(url, path, options);
|
|
138
|
+
return lib.symbols.zcache_preload_l1(this.ptr, key);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Clear all cache
|
|
143
|
+
*/
|
|
144
|
+
clear(url?: string): void {
|
|
145
|
+
if (!this.ptr) return;
|
|
146
|
+
lib.symbols.zcache_clear(this.ptr);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Free native resources
|
|
151
|
+
*/
|
|
152
|
+
close(): void {
|
|
153
|
+
if (this.ptr) {
|
|
154
|
+
lib.symbols.zcache_free(this.ptr);
|
|
155
|
+
this.ptr = null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Generate cache key from URL and options
|
|
161
|
+
*/
|
|
162
|
+
private generateKey(url: string, path?: string, options?: any): string {
|
|
163
|
+
const fullUrl = path ? `${url}${path}` : url;
|
|
164
|
+
const method = options?.method || "GET";
|
|
165
|
+
const headers = JSON.stringify(options?.headers || {});
|
|
166
|
+
return createHash("md5")
|
|
167
|
+
.update(`${method}:${fullUrl}:${headers}`)
|
|
168
|
+
.digest("hex");
|
|
169
|
+
}
|
|
170
|
+
}
|