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.
Files changed (53) hide show
  1. package/README.md +768 -0
  2. package/components/cache.ts +451 -0
  3. package/components/client-node-native.ts +1410 -0
  4. package/components/client.ts +1852 -0
  5. package/components/index.ts +37 -0
  6. package/components/metrics.ts +314 -0
  7. package/components/native-cache-node.ts +170 -0
  8. package/components/native-cache.ts +222 -0
  9. package/components/native-json.ts +195 -0
  10. package/components/native-node.ts +138 -0
  11. package/components/native.ts +418 -0
  12. package/components/persistent-worker.ts +67 -0
  13. package/components/subprocess-worker.ts +60 -0
  14. package/components/types.ts +317 -0
  15. package/components/worker-pool.ts +153 -0
  16. package/components/worker.ts +154 -0
  17. package/dist/components/cache.d.ts +32 -0
  18. package/dist/components/cache.d.ts.map +1 -0
  19. package/dist/components/cache.js +374 -0
  20. package/dist/components/cache.js.map +1 -0
  21. package/dist/components/client-node-native.d.ts +71 -0
  22. package/dist/components/client-node-native.d.ts.map +1 -0
  23. package/dist/components/client-node-native.js +1055 -0
  24. package/dist/components/client-node-native.js.map +1 -0
  25. package/dist/components/metrics.d.ts +14 -0
  26. package/dist/components/metrics.d.ts.map +1 -0
  27. package/dist/components/metrics.js +260 -0
  28. package/dist/components/metrics.js.map +1 -0
  29. package/dist/components/native-cache-node.d.ts +41 -0
  30. package/dist/components/native-cache-node.d.ts.map +1 -0
  31. package/dist/components/native-cache-node.js +133 -0
  32. package/dist/components/native-cache-node.js.map +1 -0
  33. package/dist/components/native-node.d.ts +82 -0
  34. package/dist/components/native-node.d.ts.map +1 -0
  35. package/dist/components/native-node.js +124 -0
  36. package/dist/components/native-node.js.map +1 -0
  37. package/dist/components/types.d.ts +248 -0
  38. package/dist/components/types.d.ts.map +1 -0
  39. package/dist/components/types.js +2 -0
  40. package/dist/components/types.js.map +1 -0
  41. package/dist/index-node.d.ts +3 -0
  42. package/dist/index-node.d.ts.map +1 -0
  43. package/dist/index-node.js +5 -0
  44. package/dist/index-node.js.map +1 -0
  45. package/index-node.ts +10 -0
  46. package/index.ts +9 -0
  47. package/lib/libcurl-impersonate.dylib +0 -0
  48. package/lib/libhttpclient.dylib +0 -0
  49. package/lib/libidn2.0.dylib +0 -0
  50. package/lib/libintl.8.dylib +0 -0
  51. package/lib/libunistring.5.dylib +0 -0
  52. package/lib/libzstd.1.5.7.dylib +0 -0
  53. 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
+ }