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
package/README.md
ADDED
|
@@ -0,0 +1,768 @@
|
|
|
1
|
+
# Jiren 🚀
|
|
2
|
+
|
|
3
|
+
**The fastest HTTP client for JavaScript** - Simple, type-safe, and blazingly fast.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/jiren)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## ⚡ Performance: Fastest in the World
|
|
11
|
+
|
|
12
|
+
Jiren outperforms top clients in every metric—throughput, latency, and stability.
|
|
13
|
+
|
|
14
|
+
### Concurrent Benchmark (mitata)
|
|
15
|
+
|
|
16
|
+
| Client | Avg Latency | Req/sec | vs Jiren |
|
|
17
|
+
| ---------- | ----------- | ------------ | -------------- |
|
|
18
|
+
| **Jiren** | **20.9 µs** | **47,800/s** | 🏆 **Fastest** |
|
|
19
|
+
| Bun Fetch | 31.7 µs | 31,500/s | 52% slower |
|
|
20
|
+
| Node-Fetch | 29.4 µs | 34,000/s | 41% slower |
|
|
21
|
+
| Undici | 37.1 µs | 27,000/s | 77% slower |
|
|
22
|
+
| Ky | 36.5 µs | 27,400/s | 75% slower |
|
|
23
|
+
| Axios | 59.5 µs | 16,800/s | 185% slower |
|
|
24
|
+
| Got | 61.3 µs | 16,300/s | 193% slower |
|
|
25
|
+
|
|
26
|
+
### Sequential Benchmark
|
|
27
|
+
|
|
28
|
+
| Client | Avg Latency | Req/sec | vs Jiren |
|
|
29
|
+
| ---------- | ----------- | ------------ | -------------- |
|
|
30
|
+
| **Jiren** | **20.7 µs** | **48,300/s** | 🏆 **Fastest** |
|
|
31
|
+
| Bun Fetch | 27.9 µs | 35,800/s | 35% slower |
|
|
32
|
+
| Node-Fetch | 29.8 µs | 33,600/s | 44% slower |
|
|
33
|
+
| Undici | 30.6 µs | 32,700/s | 48% slower |
|
|
34
|
+
| Ky | 35.6 µs | 28,100/s | 72% slower |
|
|
35
|
+
| Axios | 56.6 µs | 17,700/s | 173% slower |
|
|
36
|
+
| Got | 59.9 µs | 16,700/s | 189% slower |
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## ✨ Why Jiren?
|
|
41
|
+
|
|
42
|
+
| Feature | Benefit |
|
|
43
|
+
| ----------------------- | ------------------------------------------------- |
|
|
44
|
+
| ⚡ **Blazing Fast** | Proven to be the fastest JS HTTP client |
|
|
45
|
+
| **HTTP/3 Support** | Automatic protocol upgrade for faster connections |
|
|
46
|
+
| 💾 **Built-in Caching** | Automatic response caching with zero config |
|
|
47
|
+
| � **Progress Tracking** | Real-time download progress with speed & ETA |
|
|
48
|
+
| �📝 **Type-Safe** | Full TypeScript support with autocomplete |
|
|
49
|
+
| 🔒 **Anti-Bot Ready** | Bypass common bot protections easily |
|
|
50
|
+
| � **Built-in Metrics** | Track performance out of the box |
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## 📦 Installation
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
bun add jiren
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## 🚀 Quick Start
|
|
63
|
+
|
|
64
|
+
### Step 1: Create Your Client
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import { JirenClient } from "jiren";
|
|
68
|
+
|
|
69
|
+
const client = new JirenClient({
|
|
70
|
+
targets: {
|
|
71
|
+
api: "https://api.example.com",
|
|
72
|
+
github: "https://api.github.com",
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Step 2: Make Requests
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
// GET request
|
|
81
|
+
const response = await client.url.api.get({ path: "/users" });
|
|
82
|
+
const users = await response.body.json();
|
|
83
|
+
|
|
84
|
+
// POST request
|
|
85
|
+
await client.url.api.post(JSON.stringify({ name: "John" }), {
|
|
86
|
+
path: "/users",
|
|
87
|
+
headers: { "Content-Type": "application/json" },
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
That's it! 🎉
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## 📖 Table of Contents
|
|
96
|
+
|
|
97
|
+
1. [Making Requests](#-making-requests)
|
|
98
|
+
2. [Response Handling](#-response-handling)
|
|
99
|
+
3. [Caching](#-caching)
|
|
100
|
+
4. [Progress Tracking](#-progress-tracking)
|
|
101
|
+
5. [Timeout Configuration](#%EF%B8%8F-timeout-configuration)
|
|
102
|
+
6. [Anti-Bot Protection](#-anti-bot-protection)
|
|
103
|
+
7. [Interceptors](#-interceptors)
|
|
104
|
+
8. [Metrics](#-metrics)
|
|
105
|
+
9. [TypeScript Support](#-typescript-support)
|
|
106
|
+
10. [API Reference](#-api-reference)
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## 🌐 Making Requests
|
|
111
|
+
|
|
112
|
+
### GET Requests
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
// Simple GET
|
|
116
|
+
const response = await client.url.api.get();
|
|
117
|
+
|
|
118
|
+
// GET with path
|
|
119
|
+
const user = await client.url.github.get({ path: "/users/octocat" });
|
|
120
|
+
|
|
121
|
+
// GET with headers
|
|
122
|
+
const data = await client.url.api.get({
|
|
123
|
+
path: "/protected",
|
|
124
|
+
headers: { Authorization: "Bearer token123" },
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### POST, PUT, PATCH, DELETE
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
// POST
|
|
132
|
+
await client.url.api.post(JSON.stringify({ name: "Jane" }), {
|
|
133
|
+
path: "/users",
|
|
134
|
+
headers: { "Content-Type": "application/json" },
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// PUT
|
|
138
|
+
await client.url.api.put(JSON.stringify({ name: "Updated" }), {
|
|
139
|
+
path: "/users/123",
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// PATCH
|
|
143
|
+
await client.url.api.patch(JSON.stringify({ email: "new@email.com" }), {
|
|
144
|
+
path: "/users/123",
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// DELETE
|
|
148
|
+
await client.url.api.delete(null, { path: "/users/123" });
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## 📤 Response Handling
|
|
154
|
+
|
|
155
|
+
### Manual Parsing
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
const response = await client.url.api.get({ path: "/data" });
|
|
159
|
+
|
|
160
|
+
// Parse as JSON
|
|
161
|
+
const json = await response.body.json();
|
|
162
|
+
|
|
163
|
+
// Parse as text
|
|
164
|
+
const text = await response.body.text();
|
|
165
|
+
|
|
166
|
+
// Get as buffer
|
|
167
|
+
const buffer = await response.body.arrayBuffer();
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Auto-Parse (Recommended)
|
|
171
|
+
|
|
172
|
+
Let Jiren parse the response automatically:
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
// Auto-parse JSON
|
|
176
|
+
const users = await client.url.api.get({
|
|
177
|
+
path: "/users",
|
|
178
|
+
responseType: "json", // ← Returns parsed data directly!
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Auto-parse text
|
|
182
|
+
const html = await client.url.api.get({
|
|
183
|
+
path: "/page",
|
|
184
|
+
responseType: "text",
|
|
185
|
+
});
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### 🚀 Specific JSON Field Extraction
|
|
189
|
+
|
|
190
|
+
Extract specific fields from JSON responses with optimized native parsing:
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
// Extract only the fields you need (faster for large JSON)
|
|
194
|
+
const { id, status } = await client.url.api.getJsonFields<{
|
|
195
|
+
id: number;
|
|
196
|
+
status: string;
|
|
197
|
+
}>(["id", "status"]);
|
|
198
|
+
|
|
199
|
+
console.log(id, status); // Only these fields are extracted
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
**Performance:**
|
|
203
|
+
|
|
204
|
+
| Method | Use Case | Speed |
|
|
205
|
+
| -------------------------------- | -------------------- | ------------------------------ |
|
|
206
|
+
| `response.body.json()` | Need full object | Standard |
|
|
207
|
+
| `getJsonFields(["field1", ...])` | Need specific fields | **2-4x faster** for large JSON |
|
|
208
|
+
|
|
209
|
+
> 💡 **Tip:** Use `getJsonFields()` when you have large JSON payloads but only need a few fields.
|
|
210
|
+
|
|
211
|
+
### Response Properties
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
const response = await client.url.api.get({ path: "/users" });
|
|
215
|
+
|
|
216
|
+
console.log(response.status); // 200
|
|
217
|
+
console.log(response.ok); // true
|
|
218
|
+
console.log(response.headers); // { "content-type": "application/json", ... }
|
|
219
|
+
console.log(response.redirected); // false
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## 💾 Caching
|
|
225
|
+
|
|
226
|
+
Enable caching for instant responses on repeated requests:
|
|
227
|
+
|
|
228
|
+
### Enable Caching
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
const client = new JirenClient({
|
|
232
|
+
targets: {
|
|
233
|
+
api: {
|
|
234
|
+
url: "https://api.example.com",
|
|
235
|
+
cache: true, // ← Enable caching (60s default)
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Custom Cache Duration
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
const client = new JirenClient({
|
|
245
|
+
targets: {
|
|
246
|
+
api: {
|
|
247
|
+
url: "https://api.example.com",
|
|
248
|
+
cache: { ttl: 300000 }, // 5 minutes
|
|
249
|
+
},
|
|
250
|
+
cdn: {
|
|
251
|
+
url: "https://cdn.example.com",
|
|
252
|
+
cache: { ttl: 3600000 }, // 1 hour
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
});
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Cache Performance
|
|
259
|
+
|
|
260
|
+
| Request Type | Speed | Improvement |
|
|
261
|
+
| -------------- | ------ | ------------------ |
|
|
262
|
+
| First request | ~150ms | - |
|
|
263
|
+
| Cached request | ~1-2ms | **100x faster** ⚡ |
|
|
264
|
+
|
|
265
|
+
### 🚀 Performance Benchmark
|
|
266
|
+
|
|
267
|
+
See the [top of this README](#-performance-fastest-in-the-world) for detailed benchmark results. Jiren consistently wins on throughput and tail latency.
|
|
268
|
+
|
|
269
|
+
### Refresh Cache
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
// Force refresh cached data
|
|
273
|
+
await client.url.api.prefetch({ path: "/users" });
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
> 💡 **Tip:** Add `.cache/` to your `.gitignore`
|
|
277
|
+
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
## 📊 Progress Tracking
|
|
281
|
+
|
|
282
|
+
Track download progress in real-time with the `download()` method:
|
|
283
|
+
|
|
284
|
+
### Basic Usage
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
const response = await client.url.cdn.download({
|
|
288
|
+
path: "/large-file.zip",
|
|
289
|
+
onDownloadProgress: (progress) => {
|
|
290
|
+
console.log(`${progress.percent}% complete`);
|
|
291
|
+
console.log(`Speed: ${(progress.speed / 1024 / 1024).toFixed(2)} MB/s`);
|
|
292
|
+
console.log(`ETA: ${(progress.eta / 1000).toFixed(1)}s remaining`);
|
|
293
|
+
},
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
const data = await response.body.arrayBuffer();
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Progress Event Properties
|
|
300
|
+
|
|
301
|
+
| Property | Type | Description |
|
|
302
|
+
| --------- | -------- | ----------------------------- |
|
|
303
|
+
| `loaded` | `number` | Bytes transferred so far |
|
|
304
|
+
| `total` | `number` | Total bytes (0 if unknown) |
|
|
305
|
+
| `percent` | `number` | Percentage complete (0-100) |
|
|
306
|
+
| `speed` | `number` | Transfer speed in bytes/sec |
|
|
307
|
+
| `eta` | `number` | Estimated time remaining (ms) |
|
|
308
|
+
|
|
309
|
+
### Progress Bar Example
|
|
310
|
+
|
|
311
|
+
```typescript
|
|
312
|
+
await client.url.cdn.download({
|
|
313
|
+
path: "/video.mp4",
|
|
314
|
+
onDownloadProgress: (p) => {
|
|
315
|
+
const bar = "█".repeat(p.percent / 2).padEnd(50, "░");
|
|
316
|
+
process.stdout.write(`\r[${bar}] ${p.percent}%`);
|
|
317
|
+
},
|
|
318
|
+
});
|
|
319
|
+
console.log("\n✅ Download complete!");
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
> 💡 **Note:** For HTTPS, progress events are fired as chunks are received. For HTTP, native streaming is used.
|
|
323
|
+
|
|
324
|
+
### Upload Progress
|
|
325
|
+
|
|
326
|
+
Track upload progress in real-time with the `upload()` method:
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
await client.url.api.upload({
|
|
330
|
+
method: "POST",
|
|
331
|
+
path: "/upload",
|
|
332
|
+
body: largeData, // string or object
|
|
333
|
+
onUploadProgress: (progress) => {
|
|
334
|
+
console.log(`${progress.percent}% uploaded`);
|
|
335
|
+
console.log(`Speed: ${(progress.speed / 1024).toFixed(1)} KB/s`);
|
|
336
|
+
},
|
|
337
|
+
});
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### Upload Progress Bar Example
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
await client.url.api.upload({
|
|
344
|
+
method: "PUT",
|
|
345
|
+
path: "/files/data.json",
|
|
346
|
+
body: JSON.stringify(bigPayload),
|
|
347
|
+
onUploadProgress: (p) => {
|
|
348
|
+
const bar = "█".repeat(p.percent / 2).padEnd(50, "░");
|
|
349
|
+
process.stdout.write(
|
|
350
|
+
`\r[${bar}] ${p.percent}% @ ${(p.speed / 1024 / 1024).toFixed(1)} MB/s`
|
|
351
|
+
);
|
|
352
|
+
},
|
|
353
|
+
});
|
|
354
|
+
console.log("\n✅ Upload complete!");
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
> 💡 **Performance:** For HTTP, native Zig streaming achieves **~540 MB/s** upload speeds. HTTPS fires initial (0%) and final (100%) events.
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
|
|
361
|
+
## ⏱️ Timeout Configuration
|
|
362
|
+
|
|
363
|
+
Set maximum wait time for requests to prevent hanging on slow or unresponsive servers:
|
|
364
|
+
|
|
365
|
+
### Basic Usage
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
// Timeout after 5 seconds
|
|
369
|
+
const response = await client.url.api.get({
|
|
370
|
+
timeout: 5000, // milliseconds
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
// Also works with POST, PUT, etc.
|
|
374
|
+
await client.url.api.post({
|
|
375
|
+
body: { data: "value" },
|
|
376
|
+
timeout: 10000, // 10 second timeout
|
|
377
|
+
});
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### Handling Timeout Errors
|
|
381
|
+
|
|
382
|
+
```typescript
|
|
383
|
+
try {
|
|
384
|
+
const response = await client.url.api.get({
|
|
385
|
+
path: "/slow-endpoint",
|
|
386
|
+
timeout: 3000,
|
|
387
|
+
});
|
|
388
|
+
} catch (error) {
|
|
389
|
+
if (error.name === "TimeoutError") {
|
|
390
|
+
console.log("Request timed out!");
|
|
391
|
+
} else {
|
|
392
|
+
throw error;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### Behavior Notes
|
|
398
|
+
|
|
399
|
+
- Timeout is specified in **milliseconds**
|
|
400
|
+
- **TimeoutError** is thrown when the timeout is exceeded
|
|
401
|
+
- Timeout errors are **not retried** (even if retry is configured)
|
|
402
|
+
- If `timeout` is not set or is `0`, no timeout is applied
|
|
403
|
+
|
|
404
|
+
---
|
|
405
|
+
|
|
406
|
+
## 🔒 Anti-Bot Protection
|
|
407
|
+
|
|
408
|
+
Bypass Cloudflare and other bot protections:
|
|
409
|
+
|
|
410
|
+
```typescript
|
|
411
|
+
const client = new JirenClient({
|
|
412
|
+
targets: {
|
|
413
|
+
protected: "https://protected-site.com",
|
|
414
|
+
},
|
|
415
|
+
antibot: true,
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
const response = await client.url.protected.get();
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
---
|
|
422
|
+
|
|
423
|
+
## 🔄 Interceptors
|
|
424
|
+
|
|
425
|
+
Add middleware to modify requests/responses:
|
|
426
|
+
|
|
427
|
+
### Add Authentication
|
|
428
|
+
|
|
429
|
+
```typescript
|
|
430
|
+
const client = new JirenClient({
|
|
431
|
+
targets: { api: "https://api.example.com" },
|
|
432
|
+
interceptors: {
|
|
433
|
+
request: [
|
|
434
|
+
(ctx) => ({
|
|
435
|
+
...ctx,
|
|
436
|
+
headers: {
|
|
437
|
+
...ctx.headers,
|
|
438
|
+
Authorization: `Bearer ${getToken()}`,
|
|
439
|
+
},
|
|
440
|
+
}),
|
|
441
|
+
],
|
|
442
|
+
},
|
|
443
|
+
});
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
### Log Responses
|
|
447
|
+
|
|
448
|
+
```typescript
|
|
449
|
+
const client = new JirenClient({
|
|
450
|
+
targets: { api: "https://api.example.com" },
|
|
451
|
+
interceptors: {
|
|
452
|
+
response: [
|
|
453
|
+
(ctx) => {
|
|
454
|
+
console.log(`${ctx.response.status} ${ctx.request.url}`);
|
|
455
|
+
return ctx;
|
|
456
|
+
},
|
|
457
|
+
],
|
|
458
|
+
},
|
|
459
|
+
});
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
### Handle Errors
|
|
463
|
+
|
|
464
|
+
```typescript
|
|
465
|
+
const client = new JirenClient({
|
|
466
|
+
targets: { api: "https://api.example.com" },
|
|
467
|
+
interceptors: {
|
|
468
|
+
error: [
|
|
469
|
+
(error, ctx) => {
|
|
470
|
+
console.error(`Request failed: ${ctx.url}`);
|
|
471
|
+
},
|
|
472
|
+
],
|
|
473
|
+
},
|
|
474
|
+
});
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
### Add Interceptors Later
|
|
478
|
+
|
|
479
|
+
```typescript
|
|
480
|
+
client.use({
|
|
481
|
+
request: [
|
|
482
|
+
(ctx) => ({ ...ctx, headers: { ...ctx.headers, "X-Custom": "value" } }),
|
|
483
|
+
],
|
|
484
|
+
});
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
---
|
|
488
|
+
|
|
489
|
+
## 📊 Metrics
|
|
490
|
+
|
|
491
|
+
Track performance and cache efficiency:
|
|
492
|
+
|
|
493
|
+
### Get Endpoint Metrics
|
|
494
|
+
|
|
495
|
+
```typescript
|
|
496
|
+
const metrics = client.metrics.get("api");
|
|
497
|
+
|
|
498
|
+
console.log(metrics.requests.total); // Total requests made
|
|
499
|
+
console.log(metrics.timing.avgMs); // Average response time
|
|
500
|
+
console.log(metrics.cache.hitRate); // Cache hit percentage
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
### Get Global Metrics
|
|
504
|
+
|
|
505
|
+
```typescript
|
|
506
|
+
const global = client.metrics.getGlobal();
|
|
507
|
+
|
|
508
|
+
console.log(global.totalRequests); // All requests
|
|
509
|
+
console.log(global.avgResponseTimeMs); // Average across all endpoints
|
|
510
|
+
console.log(global.overallCacheHitRate); // Overall cache performance
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
### Export & Reset
|
|
514
|
+
|
|
515
|
+
```typescript
|
|
516
|
+
// Export as JSON
|
|
517
|
+
const json = client.metrics.export();
|
|
518
|
+
|
|
519
|
+
// Reset metrics
|
|
520
|
+
client.metrics.reset();
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
---
|
|
524
|
+
|
|
525
|
+
## 🔷 TypeScript Support
|
|
526
|
+
|
|
527
|
+
### Type-Safe Responses
|
|
528
|
+
|
|
529
|
+
```typescript
|
|
530
|
+
interface User {
|
|
531
|
+
id: number;
|
|
532
|
+
name: string;
|
|
533
|
+
email: string;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// TypeScript knows the response type!
|
|
537
|
+
const user = await client.url.api.get<User>({
|
|
538
|
+
path: "/users/123",
|
|
539
|
+
responseType: "json",
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
console.log(user.name); // ✅ Autocomplete works!
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
### Typed URL Keys
|
|
546
|
+
|
|
547
|
+
```typescript
|
|
548
|
+
const client = new JirenClient({
|
|
549
|
+
targets: {
|
|
550
|
+
api: "https://api.example.com",
|
|
551
|
+
cdn: "https://cdn.example.com",
|
|
552
|
+
},
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
client.url.api.get(); // ✅ Valid
|
|
556
|
+
client.url.cdn.get(); // ✅ Valid
|
|
557
|
+
client.url.foo.get(); // ❌ TypeScript error!
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
---
|
|
561
|
+
|
|
562
|
+
## 📚 API Reference
|
|
563
|
+
|
|
564
|
+
### Creating a Client
|
|
565
|
+
|
|
566
|
+
```typescript
|
|
567
|
+
new JirenClient({
|
|
568
|
+
targets: {
|
|
569
|
+
// Simple URL
|
|
570
|
+
api: "https://api.example.com",
|
|
571
|
+
|
|
572
|
+
// With caching
|
|
573
|
+
cdn: {
|
|
574
|
+
url: "https://cdn.example.com",
|
|
575
|
+
cache: true, // or { ttl: 60000 }
|
|
576
|
+
},
|
|
577
|
+
},
|
|
578
|
+
|
|
579
|
+
// Optional settings
|
|
580
|
+
antibot: false, // Enable anti-bot protection
|
|
581
|
+
benchmark: false, // Benchmark mode
|
|
582
|
+
interceptors: {}, // Request/response interceptors
|
|
583
|
+
});
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
### Request Methods
|
|
587
|
+
|
|
588
|
+
| Method | Signature |
|
|
589
|
+
| ----------------- | --------------------------------------------------------- |
|
|
590
|
+
| `get()` | `get<T>(options?): Promise<Response<T>>` |
|
|
591
|
+
| `post()` | `post<T>(body?, options?): Promise<Response<T>>` |
|
|
592
|
+
| `put()` | `put<T>(body?, options?): Promise<Response<T>>` |
|
|
593
|
+
| `patch()` | `patch<T>(body?, options?): Promise<Response<T>>` |
|
|
594
|
+
| `delete()` | `delete<T>(body?, options?): Promise<Response<T>>` |
|
|
595
|
+
| `head()` | `head(options?): Promise<Response>` |
|
|
596
|
+
| `options()` | `options(options?): Promise<Response>` |
|
|
597
|
+
| `prefetch()` | `prefetch(options?): Promise<void>` |
|
|
598
|
+
| `download()` | `download<T>(options?): Promise<Response<T>>` |
|
|
599
|
+
| `getJsonFields()` | `getJsonFields<T>(fields, options?): Promise<Partial<T>>` |
|
|
600
|
+
|
|
601
|
+
### Request Options
|
|
602
|
+
|
|
603
|
+
```typescript
|
|
604
|
+
{
|
|
605
|
+
path?: string; // URL path to append
|
|
606
|
+
headers?: Record<string, string>; // Request headers
|
|
607
|
+
responseType?: "json" | "text"; // Auto-parse response
|
|
608
|
+
maxRedirects?: number; // Max redirects (default: 5)
|
|
609
|
+
}
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
### Response Object
|
|
613
|
+
|
|
614
|
+
```typescript
|
|
615
|
+
{
|
|
616
|
+
url: string;
|
|
617
|
+
status: number;
|
|
618
|
+
statusText: string;
|
|
619
|
+
headers: Record<string, string>;
|
|
620
|
+
ok: boolean;
|
|
621
|
+
redirected: boolean;
|
|
622
|
+
body: {
|
|
623
|
+
json<T>(): Promise<T>;
|
|
624
|
+
text(): Promise<string>;
|
|
625
|
+
arrayBuffer(): Promise<ArrayBuffer>;
|
|
626
|
+
blob(): Promise<Blob>;
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
---
|
|
632
|
+
|
|
633
|
+
## 💡 Examples
|
|
634
|
+
|
|
635
|
+
### API Client Pattern
|
|
636
|
+
|
|
637
|
+
```typescript
|
|
638
|
+
// lib/api.ts
|
|
639
|
+
import { JirenClient } from "jiren";
|
|
640
|
+
|
|
641
|
+
export const api = new JirenClient({
|
|
642
|
+
targets: {
|
|
643
|
+
backend: {
|
|
644
|
+
url: process.env.API_URL!,
|
|
645
|
+
cache: { ttl: 30000 },
|
|
646
|
+
},
|
|
647
|
+
},
|
|
648
|
+
interceptors: {
|
|
649
|
+
request: [
|
|
650
|
+
(ctx) => ({
|
|
651
|
+
...ctx,
|
|
652
|
+
headers: {
|
|
653
|
+
...ctx.headers,
|
|
654
|
+
Authorization: `Bearer ${getSession()?.token}`,
|
|
655
|
+
},
|
|
656
|
+
}),
|
|
657
|
+
],
|
|
658
|
+
},
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
// Usage anywhere
|
|
662
|
+
import { api } from "@/lib/api";
|
|
663
|
+
const users = await api.url.backend.get({
|
|
664
|
+
path: "/users",
|
|
665
|
+
responseType: "json",
|
|
666
|
+
});
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
### React/Next.js Hook
|
|
670
|
+
|
|
671
|
+
```typescript
|
|
672
|
+
function useApi<T>(path: string) {
|
|
673
|
+
const [data, setData] = useState<T | null>(null);
|
|
674
|
+
const [loading, setLoading] = useState(true);
|
|
675
|
+
|
|
676
|
+
useEffect(() => {
|
|
677
|
+
api.url.backend
|
|
678
|
+
.get<T>({ path, responseType: "json" })
|
|
679
|
+
.then(setData)
|
|
680
|
+
.finally(() => setLoading(false));
|
|
681
|
+
}, [path]);
|
|
682
|
+
|
|
683
|
+
return { data, loading };
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// Usage
|
|
687
|
+
function UserList() {
|
|
688
|
+
const { data: users, loading } = useApi<User[]>("/users");
|
|
689
|
+
if (loading) return <Spinner />;
|
|
690
|
+
return (
|
|
691
|
+
<ul>
|
|
692
|
+
{users?.map((u) => (
|
|
693
|
+
<li key={u.id}>{u.name}</li>
|
|
694
|
+
))}
|
|
695
|
+
</ul>
|
|
696
|
+
);
|
|
697
|
+
}
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
---
|
|
701
|
+
|
|
702
|
+
## 🏗️ Framework Integration
|
|
703
|
+
|
|
704
|
+
### Next.js (App Router)
|
|
705
|
+
|
|
706
|
+
To use Jiren in Next.js, add it to `serverExternalPackages` in `next.config.ts`:
|
|
707
|
+
|
|
708
|
+
```typescript
|
|
709
|
+
import type { NextConfig } from "next";
|
|
710
|
+
|
|
711
|
+
const nextConfig: NextConfig = {
|
|
712
|
+
// Required: Treat Jiren and Koffi as external native packages
|
|
713
|
+
serverExternalPackages: ["jiren", "koffi"],
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
export default nextConfig;
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
**Usage in API Routes:**
|
|
720
|
+
|
|
721
|
+
```typescript
|
|
722
|
+
// app/api/data/route.ts
|
|
723
|
+
import { JirenClient } from "jiren";
|
|
724
|
+
|
|
725
|
+
const client = new JirenClient({
|
|
726
|
+
targets: [{ key: "api", url: "https://api.example.com" }],
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
export async function GET() {
|
|
730
|
+
const response = await client.url.api.get({ path: "/users" });
|
|
731
|
+
return Response.json(await response.body.json());
|
|
732
|
+
}
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
---
|
|
736
|
+
|
|
737
|
+
## 📋 Requirements
|
|
738
|
+
|
|
739
|
+
- **Runtime**:
|
|
740
|
+
- **Bun** v1.0.0+
|
|
741
|
+
- **Node.js** v18.0.0+ (macOS/Linux)
|
|
742
|
+
- **OS**: macOS (ARM64/x64) or Linux (x64)
|
|
743
|
+
|
|
744
|
+
---
|
|
745
|
+
|
|
746
|
+
## 📄 License
|
|
747
|
+
|
|
748
|
+
MIT © VK
|
|
749
|
+
|
|
750
|
+
---
|
|
751
|
+
|
|
752
|
+
## 🤝 Contributing
|
|
753
|
+
|
|
754
|
+
Contributions welcome! Please open an issue or submit a pull request.
|
|
755
|
+
|
|
756
|
+
---
|
|
757
|
+
|
|
758
|
+
## 🆘 Support
|
|
759
|
+
|
|
760
|
+
- 📖 [Documentation](https://github.com/vikashkhati007/jiren)
|
|
761
|
+
- 🐛 [Issue Tracker](https://github.com/vikashkhati007/jiren/issues)
|
|
762
|
+
- 💬 [Discussions](https://github.com/vikashkhati007/jiren/discussions)
|
|
763
|
+
|
|
764
|
+
---
|
|
765
|
+
|
|
766
|
+
<p align="center">
|
|
767
|
+
<strong>Made with ⚡ by VK</strong>
|
|
768
|
+
</p>
|