jiren 1.5.0 → 1.5.5
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 +264 -482
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,282 +1,296 @@
|
|
|
1
1
|
# Jiren 🚀
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**The fastest HTTP client for JavaScript** - Simple, type-safe, and blazingly fast.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/jiren)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## ✨ Why Jiren?
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
| Feature | Benefit |
|
|
13
|
+
| ------------------------ | ------------------------------------------------- |
|
|
14
|
+
| ⚡ **Blazing Fast** | 2-3x faster than native `fetch` |
|
|
15
|
+
| � **HTTP/3 Support** | Automatic protocol upgrade for faster connections |
|
|
16
|
+
| �💾 **Built-in Caching** | Automatic response caching with zero config |
|
|
17
|
+
| 📝 **Type-Safe** | Full TypeScript support with autocomplete |
|
|
18
|
+
| 🔒 **Anti-Bot Ready** | Bypass common bot protections easily |
|
|
19
|
+
| 📊 **Built-in Metrics** | Track performance out of the box |
|
|
20
|
+
|
|
21
|
+
---
|
|
17
22
|
|
|
18
|
-
## Installation
|
|
23
|
+
## 📦 Installation
|
|
19
24
|
|
|
20
25
|
```bash
|
|
21
26
|
bun add jiren
|
|
22
27
|
```
|
|
23
28
|
|
|
24
|
-
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## 🚀 Quick Start
|
|
32
|
+
|
|
33
|
+
### Step 1: Create Your Client
|
|
25
34
|
|
|
26
35
|
```typescript
|
|
27
36
|
import { JirenClient } from "jiren";
|
|
28
37
|
|
|
29
|
-
// Create client with warmup URLs
|
|
30
38
|
const client = new JirenClient({
|
|
31
39
|
warmup: {
|
|
32
40
|
api: "https://api.example.com",
|
|
33
|
-
|
|
41
|
+
github: "https://api.github.com",
|
|
34
42
|
},
|
|
35
43
|
});
|
|
44
|
+
```
|
|
36
45
|
|
|
37
|
-
|
|
46
|
+
### Step 2: Make Requests
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
// GET request
|
|
38
50
|
const response = await client.url.api.get({ path: "/users" });
|
|
39
51
|
const users = await response.body.json();
|
|
40
52
|
|
|
41
|
-
|
|
53
|
+
// POST request
|
|
54
|
+
await client.url.api.post(JSON.stringify({ name: "John" }), {
|
|
55
|
+
path: "/users",
|
|
56
|
+
headers: { "Content-Type": "application/json" },
|
|
57
|
+
});
|
|
42
58
|
```
|
|
43
59
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
- [Basic Usage](#basic-usage)
|
|
47
|
-
- [Response Caching](#response-caching)
|
|
48
|
-
- [Anti-Bot Protection](#anti-bot-protection)
|
|
49
|
-
- [Request Interceptors](#request-interceptors)
|
|
50
|
-
- [Metrics & Observability](#metrics--observability)
|
|
51
|
-
- [Advanced Features](#advanced-features)
|
|
52
|
-
- [Performance](#performance)
|
|
53
|
-
- [API Reference](#api-reference)
|
|
60
|
+
That's it! 🎉
|
|
54
61
|
|
|
55
62
|
---
|
|
56
63
|
|
|
57
|
-
##
|
|
64
|
+
## 📖 Table of Contents
|
|
65
|
+
|
|
66
|
+
1. [Making Requests](#-making-requests)
|
|
67
|
+
2. [Response Handling](#-response-handling)
|
|
68
|
+
3. [Caching](#-caching)
|
|
69
|
+
4. [Anti-Bot Protection](#-anti-bot-protection)
|
|
70
|
+
5. [Interceptors](#-interceptors)
|
|
71
|
+
6. [Metrics](#-metrics)
|
|
72
|
+
7. [TypeScript Support](#-typescript-support)
|
|
73
|
+
8. [API Reference](#-api-reference)
|
|
74
|
+
|
|
75
|
+
---
|
|
58
76
|
|
|
59
|
-
|
|
77
|
+
## 🌐 Making Requests
|
|
60
78
|
|
|
61
|
-
|
|
79
|
+
### GET Requests
|
|
62
80
|
|
|
63
81
|
```typescript
|
|
64
|
-
|
|
82
|
+
// Simple GET
|
|
83
|
+
const response = await client.url.api.get();
|
|
65
84
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
85
|
+
// GET with path
|
|
86
|
+
const user = await client.url.github.get({ path: "/users/octocat" });
|
|
87
|
+
|
|
88
|
+
// GET with headers
|
|
89
|
+
const data = await client.url.api.get({
|
|
90
|
+
path: "/protected",
|
|
91
|
+
headers: { Authorization: "Bearer token123" },
|
|
71
92
|
});
|
|
72
93
|
```
|
|
73
94
|
|
|
74
|
-
###
|
|
95
|
+
### POST, PUT, PATCH, DELETE
|
|
75
96
|
|
|
76
97
|
```typescript
|
|
77
|
-
//
|
|
78
|
-
|
|
79
|
-
|
|
98
|
+
// POST
|
|
99
|
+
await client.url.api.post(JSON.stringify({ name: "Jane" }), {
|
|
100
|
+
path: "/users",
|
|
101
|
+
headers: { "Content-Type": "application/json" },
|
|
102
|
+
});
|
|
80
103
|
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
path: "/users/
|
|
84
|
-
headers: { Authorization: "token YOUR_TOKEN" },
|
|
104
|
+
// PUT
|
|
105
|
+
await client.url.api.put(JSON.stringify({ name: "Updated" }), {
|
|
106
|
+
path: "/users/123",
|
|
85
107
|
});
|
|
108
|
+
|
|
109
|
+
// PATCH
|
|
110
|
+
await client.url.api.patch(JSON.stringify({ email: "new@email.com" }), {
|
|
111
|
+
path: "/users/123",
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// DELETE
|
|
115
|
+
await client.url.api.delete(null, { path: "/users/123" });
|
|
86
116
|
```
|
|
87
117
|
|
|
88
|
-
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## 📤 Response Handling
|
|
121
|
+
|
|
122
|
+
### Manual Parsing
|
|
89
123
|
|
|
90
124
|
```typescript
|
|
91
125
|
const response = await client.url.api.get({ path: "/data" });
|
|
92
126
|
|
|
93
|
-
//
|
|
127
|
+
// Parse as JSON
|
|
94
128
|
const json = await response.body.json();
|
|
95
129
|
|
|
96
|
-
//
|
|
130
|
+
// Parse as text
|
|
97
131
|
const text = await response.body.text();
|
|
98
132
|
|
|
99
|
-
//
|
|
133
|
+
// Get as buffer
|
|
100
134
|
const buffer = await response.body.arrayBuffer();
|
|
101
135
|
```
|
|
102
136
|
|
|
103
|
-
###
|
|
137
|
+
### Auto-Parse (Recommended)
|
|
138
|
+
|
|
139
|
+
Let Jiren parse the response automatically:
|
|
104
140
|
|
|
105
141
|
```typescript
|
|
106
|
-
//
|
|
142
|
+
// Auto-parse JSON
|
|
107
143
|
const users = await client.url.api.get({
|
|
108
144
|
path: "/users",
|
|
109
|
-
responseType: "json", // Returns parsed
|
|
145
|
+
responseType: "json", // ← Returns parsed data directly!
|
|
110
146
|
});
|
|
111
147
|
|
|
112
|
-
//
|
|
113
|
-
const html = await client.url.
|
|
114
|
-
|
|
148
|
+
// Auto-parse text
|
|
149
|
+
const html = await client.url.api.get({
|
|
150
|
+
path: "/page",
|
|
151
|
+
responseType: "text",
|
|
115
152
|
});
|
|
116
153
|
```
|
|
117
154
|
|
|
118
|
-
###
|
|
155
|
+
### Response Properties
|
|
119
156
|
|
|
120
157
|
```typescript
|
|
121
|
-
|
|
122
|
-
const created = await client.url.api.post(
|
|
123
|
-
JSON.stringify({ name: "John Doe", email: "john@example.com" }),
|
|
124
|
-
{
|
|
125
|
-
path: "/users",
|
|
126
|
-
headers: { "Content-Type": "application/json" },
|
|
127
|
-
responseType: "json",
|
|
128
|
-
}
|
|
129
|
-
);
|
|
130
|
-
|
|
131
|
-
// PUT request
|
|
132
|
-
await client.url.api.put(JSON.stringify({ name: "Jane Doe" }), {
|
|
133
|
-
path: "/users/123",
|
|
134
|
-
headers: { "Content-Type": "application/json" },
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
// PATCH request
|
|
138
|
-
await client.url.api.patch(JSON.stringify({ email: "new@example.com" }), {
|
|
139
|
-
path: "/users/123",
|
|
140
|
-
});
|
|
158
|
+
const response = await client.url.api.get({ path: "/users" });
|
|
141
159
|
|
|
142
|
-
//
|
|
143
|
-
|
|
160
|
+
console.log(response.status); // 200
|
|
161
|
+
console.log(response.ok); // true
|
|
162
|
+
console.log(response.headers); // { "content-type": "application/json", ... }
|
|
163
|
+
console.log(response.redirected); // false
|
|
144
164
|
```
|
|
145
165
|
|
|
146
166
|
---
|
|
147
167
|
|
|
148
|
-
##
|
|
168
|
+
## 💾 Caching
|
|
149
169
|
|
|
150
|
-
Enable
|
|
170
|
+
Enable caching for instant responses on repeated requests:
|
|
151
171
|
|
|
152
|
-
###
|
|
172
|
+
### Enable Caching
|
|
153
173
|
|
|
154
174
|
```typescript
|
|
155
175
|
const client = new JirenClient({
|
|
156
176
|
warmup: {
|
|
157
177
|
api: {
|
|
158
178
|
url: "https://api.example.com",
|
|
159
|
-
cache: true, // Enable
|
|
179
|
+
cache: true, // ← Enable caching (60s default)
|
|
160
180
|
},
|
|
161
181
|
},
|
|
162
182
|
});
|
|
163
|
-
|
|
164
|
-
// First request: Real HTTP request (~150ms)
|
|
165
|
-
const data1 = await client.url.api.get({ path: "/users" });
|
|
166
|
-
|
|
167
|
-
// Second request: From cache (~1-2ms) - 100x faster! ⚡
|
|
168
|
-
const data2 = await client.url.api.get({ path: "/users" });
|
|
169
183
|
```
|
|
170
184
|
|
|
171
|
-
### Custom Cache
|
|
185
|
+
### Custom Cache Duration
|
|
172
186
|
|
|
173
187
|
```typescript
|
|
174
188
|
const client = new JirenClient({
|
|
175
189
|
warmup: {
|
|
176
190
|
api: {
|
|
177
191
|
url: "https://api.example.com",
|
|
178
|
-
cache: { ttl: 300000 }, // 5
|
|
192
|
+
cache: { ttl: 300000 }, // 5 minutes
|
|
179
193
|
},
|
|
180
194
|
cdn: {
|
|
181
195
|
url: "https://cdn.example.com",
|
|
182
|
-
cache: { ttl: 3600000 }, // 1
|
|
196
|
+
cache: { ttl: 3600000 }, // 1 hour
|
|
183
197
|
},
|
|
184
198
|
},
|
|
185
199
|
});
|
|
186
200
|
```
|
|
187
201
|
|
|
188
|
-
### Manual Cache Refresh
|
|
189
|
-
|
|
190
|
-
```typescript
|
|
191
|
-
// Refresh cache for a specific endpoint
|
|
192
|
-
await client.url.api.prefetch({ path: "/users" });
|
|
193
|
-
|
|
194
|
-
// Now the next request will use fresh data
|
|
195
|
-
const users = await client.url.api.get({ path: "/users" });
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
### Cache Features
|
|
199
|
-
|
|
200
|
-
- ✅ **Persistent** - Survives process restarts
|
|
201
|
-
- ✅ **Compressed** - Gzip-compressed JSON files (`.cache/jiren/*.json.gz`)
|
|
202
|
-
- ✅ **Automatic expiration** - Respects TTL
|
|
203
|
-
- ✅ **Fast** - ~100x faster than real requests
|
|
204
|
-
|
|
205
202
|
### Cache Performance
|
|
206
203
|
|
|
207
|
-
| Type
|
|
208
|
-
|
|
|
209
|
-
|
|
|
210
|
-
|
|
|
211
|
-
| Memory cache | ~0.1ms | Multiple requests in same process |
|
|
204
|
+
| Request Type | Speed | Improvement |
|
|
205
|
+
| -------------- | ------ | ------------------ |
|
|
206
|
+
| First request | ~150ms | - |
|
|
207
|
+
| Cached request | ~1-2ms | **100x faster** ⚡ |
|
|
212
208
|
|
|
213
|
-
|
|
209
|
+
### Refresh Cache
|
|
214
210
|
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
.
|
|
211
|
+
```typescript
|
|
212
|
+
// Force refresh cached data
|
|
213
|
+
await client.url.api.prefetch({ path: "/users" });
|
|
218
214
|
```
|
|
219
215
|
|
|
216
|
+
> 💡 **Tip:** Add `.cache/` to your `.gitignore`
|
|
217
|
+
|
|
220
218
|
---
|
|
221
219
|
|
|
222
|
-
## Anti-Bot Protection
|
|
220
|
+
## 🔒 Anti-Bot Protection
|
|
223
221
|
|
|
224
|
-
Bypass Cloudflare and other bot
|
|
222
|
+
Bypass Cloudflare and other bot protections:
|
|
225
223
|
|
|
226
224
|
```typescript
|
|
227
225
|
const client = new JirenClient({
|
|
228
226
|
warmup: {
|
|
229
|
-
protected: "https://
|
|
230
|
-
antibot: true,
|
|
227
|
+
protected: "https://protected-site.com",
|
|
231
228
|
},
|
|
229
|
+
antibot: true,
|
|
232
230
|
});
|
|
233
231
|
|
|
234
|
-
// Enable anti-bot mode for this request
|
|
235
232
|
const response = await client.url.protected.get();
|
|
236
233
|
```
|
|
237
234
|
|
|
238
|
-
**How it works:**
|
|
239
|
-
|
|
240
|
-
- Uses `curl-impersonate` to mimic Chrome 120 browser
|
|
241
|
-
- Includes realistic TLS fingerprint and headers
|
|
242
|
-
- Bypasses most bot detection systems
|
|
243
|
-
|
|
244
235
|
---
|
|
245
236
|
|
|
246
|
-
##
|
|
237
|
+
## 🔄 Interceptors
|
|
247
238
|
|
|
248
|
-
Add middleware to
|
|
239
|
+
Add middleware to modify requests/responses:
|
|
249
240
|
|
|
250
|
-
###
|
|
241
|
+
### Add Authentication
|
|
251
242
|
|
|
252
243
|
```typescript
|
|
253
244
|
const client = new JirenClient({
|
|
254
245
|
warmup: { api: "https://api.example.com" },
|
|
255
246
|
interceptors: {
|
|
256
|
-
// Modify requests before sending
|
|
257
247
|
request: [
|
|
258
248
|
(ctx) => ({
|
|
259
249
|
...ctx,
|
|
260
|
-
headers: {
|
|
250
|
+
headers: {
|
|
251
|
+
...ctx.headers,
|
|
252
|
+
Authorization: `Bearer ${getToken()}`,
|
|
253
|
+
},
|
|
261
254
|
}),
|
|
262
255
|
],
|
|
263
|
-
|
|
256
|
+
},
|
|
257
|
+
});
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Log Responses
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
const client = new JirenClient({
|
|
264
|
+
warmup: { api: "https://api.example.com" },
|
|
265
|
+
interceptors: {
|
|
264
266
|
response: [
|
|
265
267
|
(ctx) => {
|
|
266
|
-
console.log(
|
|
268
|
+
console.log(`${ctx.response.status} ${ctx.request.url}`);
|
|
267
269
|
return ctx;
|
|
268
270
|
},
|
|
269
271
|
],
|
|
270
|
-
// Handle errors
|
|
271
|
-
error: [(err, ctx) => console.error(`Failed: ${ctx.url}`, err)],
|
|
272
272
|
},
|
|
273
273
|
});
|
|
274
274
|
```
|
|
275
275
|
|
|
276
|
-
###
|
|
276
|
+
### Handle Errors
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
const client = new JirenClient({
|
|
280
|
+
warmup: { api: "https://api.example.com" },
|
|
281
|
+
interceptors: {
|
|
282
|
+
error: [
|
|
283
|
+
(error, ctx) => {
|
|
284
|
+
console.error(`Request failed: ${ctx.url}`);
|
|
285
|
+
},
|
|
286
|
+
],
|
|
287
|
+
},
|
|
288
|
+
});
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Add Interceptors Later
|
|
277
292
|
|
|
278
293
|
```typescript
|
|
279
|
-
// Add interceptors after client creation
|
|
280
294
|
client.use({
|
|
281
295
|
request: [
|
|
282
296
|
(ctx) => ({ ...ctx, headers: { ...ctx.headers, "X-Custom": "value" } }),
|
|
@@ -284,184 +298,47 @@ client.use({
|
|
|
284
298
|
});
|
|
285
299
|
```
|
|
286
300
|
|
|
287
|
-
### Interceptor Types
|
|
288
|
-
|
|
289
|
-
| Type | Purpose | Context |
|
|
290
|
-
| ---------- | ------------------------------------------------ | -------------------------------- |
|
|
291
|
-
| `request` | Modify method, URL, headers, body before sending | `{ method, url, headers, body }` |
|
|
292
|
-
| `response` | Transform response after receiving | `{ request, response }` |
|
|
293
|
-
| `error` | Handle errors centrally | `(error, requestContext)` |
|
|
294
|
-
|
|
295
301
|
---
|
|
296
302
|
|
|
297
|
-
## Metrics
|
|
303
|
+
## 📊 Metrics
|
|
298
304
|
|
|
299
|
-
Track performance
|
|
305
|
+
Track performance and cache efficiency:
|
|
300
306
|
|
|
301
|
-
###
|
|
307
|
+
### Get Endpoint Metrics
|
|
302
308
|
|
|
303
309
|
```typescript
|
|
304
|
-
const client = new JirenClient({
|
|
305
|
-
warmup: {
|
|
306
|
-
api: {
|
|
307
|
-
url: "https://api.example.com",
|
|
308
|
-
cache: true,
|
|
309
|
-
},
|
|
310
|
-
},
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
// Make some requests
|
|
314
|
-
await client.url.api.get({ path: "/users" });
|
|
315
|
-
await client.url.api.get({ path: "/users" }); // Cache hit
|
|
316
|
-
|
|
317
|
-
// Get endpoint metrics
|
|
318
310
|
const metrics = client.metrics.get("api");
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
requests: {
|
|
324
|
-
total: 2,
|
|
325
|
-
success: 2,
|
|
326
|
-
failed: 0
|
|
327
|
-
},
|
|
328
|
-
statusCodes: { 200: 2 },
|
|
329
|
-
timing: {
|
|
330
|
-
avgMs: 50.5,
|
|
331
|
-
minMs: 0.01,
|
|
332
|
-
maxMs: 101,
|
|
333
|
-
p50Ms: 50.5,
|
|
334
|
-
p95Ms: 101,
|
|
335
|
-
p99Ms: 101
|
|
336
|
-
},
|
|
337
|
-
cache: {
|
|
338
|
-
l1Hits: 1,
|
|
339
|
-
l1Misses: 1,
|
|
340
|
-
l2Hits: 0,
|
|
341
|
-
l2Misses: 1,
|
|
342
|
-
hitRate: "50.00%"
|
|
343
|
-
},
|
|
344
|
-
deduplication: {
|
|
345
|
-
hits: 0,
|
|
346
|
-
misses: 2,
|
|
347
|
-
hitRate: "0.00%"
|
|
348
|
-
},
|
|
349
|
-
bytes: {
|
|
350
|
-
sent: 0,
|
|
351
|
-
received: 0
|
|
352
|
-
},
|
|
353
|
-
errors: {},
|
|
354
|
-
lastRequestAt: 1234567890000
|
|
355
|
-
}
|
|
356
|
-
*/
|
|
311
|
+
|
|
312
|
+
console.log(metrics.requests.total); // Total requests made
|
|
313
|
+
console.log(metrics.timing.avgMs); // Average response time
|
|
314
|
+
console.log(metrics.cache.hitRate); // Cache hit percentage
|
|
357
315
|
```
|
|
358
316
|
|
|
359
|
-
### Global Metrics
|
|
317
|
+
### Get Global Metrics
|
|
360
318
|
|
|
361
319
|
```typescript
|
|
362
|
-
// Get aggregated metrics across all endpoints
|
|
363
320
|
const global = client.metrics.getGlobal();
|
|
364
|
-
console.log(global);
|
|
365
|
-
/*
|
|
366
|
-
{
|
|
367
|
-
totalRequests: 8,
|
|
368
|
-
totalSuccess: 8,
|
|
369
|
-
totalFailed: 0,
|
|
370
|
-
avgResponseTimeMs: 125.5,
|
|
371
|
-
totalBytesSent: 1024,
|
|
372
|
-
totalBytesReceived: 4096,
|
|
373
|
-
overallCacheHitRate: "62.50%",
|
|
374
|
-
overallDeduplicationRate: "25.00%",
|
|
375
|
-
endpoints: 2,
|
|
376
|
-
uptime: 60000
|
|
377
|
-
}
|
|
378
|
-
*/
|
|
379
|
-
```
|
|
380
321
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
// Get metrics for all endpoints
|
|
385
|
-
const allMetrics = client.metrics.getAll();
|
|
386
|
-
for (const [endpoint, metrics] of Object.entries(allMetrics)) {
|
|
387
|
-
console.log(`${endpoint}: ${metrics.cache.hitRate} cache hit rate`);
|
|
388
|
-
}
|
|
322
|
+
console.log(global.totalRequests); // All requests
|
|
323
|
+
console.log(global.avgResponseTimeMs); // Average across all endpoints
|
|
324
|
+
console.log(global.overallCacheHitRate); // Overall cache performance
|
|
389
325
|
```
|
|
390
326
|
|
|
391
|
-
### Export
|
|
327
|
+
### Export & Reset
|
|
392
328
|
|
|
393
329
|
```typescript
|
|
394
|
-
// Export
|
|
330
|
+
// Export as JSON
|
|
395
331
|
const json = client.metrics.export();
|
|
396
|
-
console.log(json); // Pretty-printed JSON string
|
|
397
|
-
|
|
398
|
-
// Save to file or send to monitoring service
|
|
399
|
-
await Bun.write("metrics.json", json);
|
|
400
|
-
```
|
|
401
332
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
```typescript
|
|
405
|
-
// Reset specific endpoint
|
|
406
|
-
client.metrics.reset("api");
|
|
407
|
-
|
|
408
|
-
// Reset all metrics
|
|
333
|
+
// Reset metrics
|
|
409
334
|
client.metrics.reset();
|
|
410
335
|
```
|
|
411
336
|
|
|
412
|
-
### Tracked Metrics
|
|
413
|
-
|
|
414
|
-
| Category | Metrics |
|
|
415
|
-
| ------------ | ---------------------------------------------------------- |
|
|
416
|
-
| **Requests** | Total, success, failed, status code distribution |
|
|
417
|
-
| **Timing** | Average, min, max, P50, P95, P99 response times |
|
|
418
|
-
| **Cache** | L1/L2 hits & misses, overall hit rate |
|
|
419
|
-
| **Dedupe** | Deduplication hits/misses for identical in-flight requests |
|
|
420
|
-
| **Bytes** | Total sent/received |
|
|
421
|
-
| **Errors** | Error counts by type |
|
|
422
|
-
| **Other** | Last request timestamp, client uptime |
|
|
423
|
-
|
|
424
|
-
### Use Cases
|
|
425
|
-
|
|
426
|
-
**Performance Monitoring:**
|
|
427
|
-
|
|
428
|
-
```typescript
|
|
429
|
-
// Track P99 latency
|
|
430
|
-
setInterval(() => {
|
|
431
|
-
const metrics = client.metrics.getGlobal();
|
|
432
|
-
if (metrics.avgResponseTimeMs > 1000) {
|
|
433
|
-
console.warn("High latency detected!");
|
|
434
|
-
}
|
|
435
|
-
}, 60000);
|
|
436
|
-
```
|
|
437
|
-
|
|
438
|
-
**Cache Optimization:**
|
|
439
|
-
|
|
440
|
-
```typescript
|
|
441
|
-
// Monitor cache effectiveness
|
|
442
|
-
const metrics = client.metrics.get("api");
|
|
443
|
-
console.log(`Cache hit rate: ${metrics.cache.hitRate}`);
|
|
444
|
-
if (parseFloat(metrics.cache.hitRate) < 50) {
|
|
445
|
-
console.log("Consider increasing cache TTL");
|
|
446
|
-
}
|
|
447
|
-
```
|
|
448
|
-
|
|
449
|
-
**Debugging:**
|
|
450
|
-
|
|
451
|
-
```typescript
|
|
452
|
-
// Export metrics for debugging
|
|
453
|
-
if (process.env.DEBUG) {
|
|
454
|
-
process.on("exit", () => {
|
|
455
|
-
console.log(client.metrics.export());
|
|
456
|
-
});
|
|
457
|
-
}
|
|
458
|
-
```
|
|
459
|
-
|
|
460
337
|
---
|
|
461
338
|
|
|
462
|
-
##
|
|
339
|
+
## 🔷 TypeScript Support
|
|
463
340
|
|
|
464
|
-
###
|
|
341
|
+
### Type-Safe Responses
|
|
465
342
|
|
|
466
343
|
```typescript
|
|
467
344
|
interface User {
|
|
@@ -470,194 +347,92 @@ interface User {
|
|
|
470
347
|
email: string;
|
|
471
348
|
}
|
|
472
349
|
|
|
473
|
-
//
|
|
350
|
+
// TypeScript knows the response type!
|
|
474
351
|
const user = await client.url.api.get<User>({
|
|
475
352
|
path: "/users/123",
|
|
476
353
|
responseType: "json",
|
|
477
354
|
});
|
|
478
355
|
|
|
479
|
-
console.log(user.name); //
|
|
356
|
+
console.log(user.name); // ✅ Autocomplete works!
|
|
480
357
|
```
|
|
481
358
|
|
|
482
|
-
###
|
|
359
|
+
### Typed URL Keys
|
|
483
360
|
|
|
484
361
|
```typescript
|
|
485
|
-
|
|
486
|
-
const client1 = new JirenClient({
|
|
362
|
+
const client = new JirenClient({
|
|
487
363
|
warmup: {
|
|
488
364
|
api: "https://api.example.com",
|
|
489
365
|
cdn: "https://cdn.example.com",
|
|
490
366
|
},
|
|
491
367
|
});
|
|
492
368
|
|
|
493
|
-
//
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
{ key: "api", url: "https://api.example.com", cache: true },
|
|
497
|
-
{ key: "cdn", url: "https://cdn.example.com", cache: { ttl: 3600000 } },
|
|
498
|
-
],
|
|
499
|
-
});
|
|
500
|
-
|
|
501
|
-
// Simple array
|
|
502
|
-
const client3 = new JirenClient({
|
|
503
|
-
warmup: ["https://api.example.com", "https://cdn.example.com"],
|
|
504
|
-
});
|
|
505
|
-
```
|
|
506
|
-
|
|
507
|
-
### Benchmark Mode
|
|
508
|
-
|
|
509
|
-
Force HTTP/2 for consistent benchmarking:
|
|
510
|
-
|
|
511
|
-
```typescript
|
|
512
|
-
const client = new JirenClient({
|
|
513
|
-
warmup: { api: "https://api.example.com" },
|
|
514
|
-
benchmark: true, // Disables HTTP/3 probing, forces HTTP/2
|
|
515
|
-
});
|
|
516
|
-
```
|
|
517
|
-
|
|
518
|
-
### Close Client
|
|
519
|
-
|
|
520
|
-
```typescript
|
|
521
|
-
// Clean up resources when done
|
|
522
|
-
client.close();
|
|
369
|
+
client.url.api.get(); // ✅ Valid
|
|
370
|
+
client.url.cdn.get(); // ✅ Valid
|
|
371
|
+
client.url.foo.get(); // ❌ TypeScript error!
|
|
523
372
|
```
|
|
524
373
|
|
|
525
374
|
---
|
|
526
375
|
|
|
527
|
-
##
|
|
528
|
-
|
|
529
|
-
### Benchmark Results
|
|
530
|
-
|
|
531
|
-
```
|
|
532
|
-
Bun fetch: ~300ms
|
|
533
|
-
JirenClient: ~120ms (2.5x faster)
|
|
534
|
-
JirenClient (cached): ~1-2ms (150x faster)
|
|
535
|
-
```
|
|
536
|
-
|
|
537
|
-
### Performance Tips
|
|
538
|
-
|
|
539
|
-
1. **Always use warmup** - Pre-establishes connections
|
|
540
|
-
2. **Enable caching** - For data that doesn't change frequently
|
|
541
|
-
3. **Reuse client instances** - Don't create new clients for each request
|
|
542
|
-
4. **Use `responseType`** - Automatic parsing is faster
|
|
543
|
-
5. **Batch requests** - Use `Promise.all()` for concurrent requests
|
|
544
|
-
|
|
545
|
-
### Why So Fast?
|
|
546
|
-
|
|
547
|
-
- **Native Zig implementation** - No JavaScript overhead
|
|
548
|
-
- **HTTP/3 (QUIC) support** - Faster than HTTP/2 when available
|
|
549
|
-
- **Connection pooling** - Reuses TCP/TLS connections
|
|
550
|
-
- **Smart caching** - Compressed persistent cache
|
|
551
|
-
- **Zero-copy operations** - Minimal memory allocations
|
|
552
|
-
- **TCP keep-alive** - Prevents connection drops
|
|
553
|
-
|
|
554
|
-
---
|
|
555
|
-
|
|
556
|
-
## API Reference
|
|
557
|
-
|
|
558
|
-
### `JirenClient` Constructor
|
|
559
|
-
|
|
560
|
-
```typescript
|
|
561
|
-
new JirenClient(options?: JirenClientOptions)
|
|
562
|
-
```
|
|
563
|
-
|
|
564
|
-
**Options:**
|
|
565
|
-
|
|
566
|
-
| Option | Type | Description |
|
|
567
|
-
| ----------- | -------------------------------------------------- | ---------------------------- |
|
|
568
|
-
| `warmup` | `Record<string, UrlConfig>` \| `WarmupUrlConfig[]` | URLs to pre-warm (required) |
|
|
569
|
-
| `benchmark` | `boolean` | Force HTTP/2 mode (optional) |
|
|
570
|
-
|
|
571
|
-
**UrlConfig:**
|
|
572
|
-
|
|
573
|
-
```typescript
|
|
574
|
-
type UrlConfig =
|
|
575
|
-
| string
|
|
576
|
-
| {
|
|
577
|
-
url: string;
|
|
578
|
-
cache?: boolean | { ttl: number };
|
|
579
|
-
};
|
|
580
|
-
```
|
|
581
|
-
|
|
582
|
-
### URL Endpoint Methods
|
|
583
|
-
|
|
584
|
-
All methods are available on `client.url.<key>`:
|
|
585
|
-
|
|
586
|
-
#### `get(options?)`
|
|
587
|
-
|
|
588
|
-
```typescript
|
|
589
|
-
get<T>(options?: UrlRequestOptions): Promise<JirenResponse<T>>
|
|
590
|
-
```
|
|
591
|
-
|
|
592
|
-
#### `post(body?, options?)`
|
|
593
|
-
|
|
594
|
-
```typescript
|
|
595
|
-
post<T>(body?: string | null, options?: UrlRequestOptions): Promise<JirenResponse<T>>
|
|
596
|
-
```
|
|
597
|
-
|
|
598
|
-
#### `put(body?, options?)`
|
|
599
|
-
|
|
600
|
-
```typescript
|
|
601
|
-
put<T>(body?: string | null, options?: UrlRequestOptions): Promise<JirenResponse<T>>
|
|
602
|
-
```
|
|
376
|
+
## 📚 API Reference
|
|
603
377
|
|
|
604
|
-
|
|
378
|
+
### Creating a Client
|
|
605
379
|
|
|
606
380
|
```typescript
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
```typescript
|
|
613
|
-
delete<T>(body?: string | null, options?: UrlRequestOptions): Promise<JirenResponse<T>>
|
|
614
|
-
```
|
|
615
|
-
|
|
616
|
-
#### `head(options?)`
|
|
617
|
-
|
|
618
|
-
```typescript
|
|
619
|
-
head(options?: UrlRequestOptions): Promise<JirenResponse<any>>
|
|
620
|
-
```
|
|
381
|
+
new JirenClient({
|
|
382
|
+
warmup: {
|
|
383
|
+
// Simple URL
|
|
384
|
+
api: "https://api.example.com",
|
|
621
385
|
|
|
622
|
-
|
|
386
|
+
// With caching
|
|
387
|
+
cdn: {
|
|
388
|
+
url: "https://cdn.example.com",
|
|
389
|
+
cache: true, // or { ttl: 60000 }
|
|
390
|
+
},
|
|
391
|
+
},
|
|
623
392
|
|
|
624
|
-
|
|
625
|
-
|
|
393
|
+
// Optional settings
|
|
394
|
+
antibot: false, // Enable anti-bot protection
|
|
395
|
+
benchmark: false, // Benchmark mode
|
|
396
|
+
interceptors: {}, // Request/response interceptors
|
|
397
|
+
});
|
|
626
398
|
```
|
|
627
399
|
|
|
628
|
-
|
|
400
|
+
### Request Methods
|
|
629
401
|
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
402
|
+
| Method | Signature |
|
|
403
|
+
| ------------ | -------------------------------------------------- |
|
|
404
|
+
| `get()` | `get<T>(options?): Promise<Response<T>>` |
|
|
405
|
+
| `post()` | `post<T>(body?, options?): Promise<Response<T>>` |
|
|
406
|
+
| `put()` | `put<T>(body?, options?): Promise<Response<T>>` |
|
|
407
|
+
| `patch()` | `patch<T>(body?, options?): Promise<Response<T>>` |
|
|
408
|
+
| `delete()` | `delete<T>(body?, options?): Promise<Response<T>>` |
|
|
409
|
+
| `head()` | `head(options?): Promise<Response>` |
|
|
410
|
+
| `options()` | `options(options?): Promise<Response>` |
|
|
411
|
+
| `prefetch()` | `prefetch(options?): Promise<void>` |
|
|
635
412
|
|
|
636
413
|
### Request Options
|
|
637
414
|
|
|
638
415
|
```typescript
|
|
639
|
-
|
|
640
|
-
path?: string;
|
|
416
|
+
{
|
|
417
|
+
path?: string; // URL path to append
|
|
641
418
|
headers?: Record<string, string>; // Request headers
|
|
642
|
-
responseType?: "json" | "text";
|
|
643
|
-
|
|
644
|
-
maxRedirects?: number; // Max redirects to follow
|
|
419
|
+
responseType?: "json" | "text"; // Auto-parse response
|
|
420
|
+
maxRedirects?: number; // Max redirects (default: 5)
|
|
645
421
|
}
|
|
646
422
|
```
|
|
647
423
|
|
|
648
424
|
### Response Object
|
|
649
425
|
|
|
650
426
|
```typescript
|
|
651
|
-
|
|
427
|
+
{
|
|
652
428
|
url: string;
|
|
653
429
|
status: number;
|
|
654
430
|
statusText: string;
|
|
655
431
|
headers: Record<string, string>;
|
|
656
432
|
ok: boolean;
|
|
657
433
|
redirected: boolean;
|
|
658
|
-
type: string;
|
|
659
434
|
body: {
|
|
660
|
-
json<
|
|
435
|
+
json<T>(): Promise<T>;
|
|
661
436
|
text(): Promise<string>;
|
|
662
437
|
arrayBuffer(): Promise<ArrayBuffer>;
|
|
663
438
|
blob(): Promise<Blob>;
|
|
@@ -667,89 +442,94 @@ interface JirenResponse<T = any> {
|
|
|
667
442
|
|
|
668
443
|
---
|
|
669
444
|
|
|
670
|
-
## Examples
|
|
445
|
+
## 💡 Examples
|
|
671
446
|
|
|
672
|
-
###
|
|
447
|
+
### API Client Pattern
|
|
673
448
|
|
|
674
449
|
```typescript
|
|
675
|
-
// api
|
|
450
|
+
// lib/api.ts
|
|
676
451
|
import { JirenClient } from "jiren";
|
|
677
452
|
|
|
678
|
-
export const
|
|
453
|
+
export const api = new JirenClient({
|
|
679
454
|
warmup: {
|
|
680
455
|
backend: {
|
|
681
|
-
url:
|
|
682
|
-
cache:
|
|
683
|
-
},
|
|
684
|
-
cdn: {
|
|
685
|
-
url: "https://cdn.myapp.com",
|
|
686
|
-
cache: { ttl: 3600000 }, // 1-hour cache for static assets
|
|
456
|
+
url: process.env.API_URL!,
|
|
457
|
+
cache: { ttl: 30000 },
|
|
687
458
|
},
|
|
688
459
|
},
|
|
460
|
+
interceptors: {
|
|
461
|
+
request: [
|
|
462
|
+
(ctx) => ({
|
|
463
|
+
...ctx,
|
|
464
|
+
headers: {
|
|
465
|
+
...ctx.headers,
|
|
466
|
+
Authorization: `Bearer ${getSession()?.token}`,
|
|
467
|
+
},
|
|
468
|
+
}),
|
|
469
|
+
],
|
|
470
|
+
},
|
|
689
471
|
});
|
|
690
472
|
|
|
691
|
-
//
|
|
692
|
-
import {
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
useEffect(() => {
|
|
698
|
-
// Fast! Uses cache if available
|
|
699
|
-
apiClient.url.backend
|
|
700
|
-
.get({ path: "/user/me", responseType: "json" })
|
|
701
|
-
.then(setUser);
|
|
702
|
-
}, []);
|
|
703
|
-
|
|
704
|
-
return <div>{user?.name}</div>;
|
|
705
|
-
}
|
|
473
|
+
// Usage anywhere
|
|
474
|
+
import { api } from "@/lib/api";
|
|
475
|
+
const users = await api.url.backend.get({
|
|
476
|
+
path: "/users",
|
|
477
|
+
responseType: "json",
|
|
478
|
+
});
|
|
706
479
|
```
|
|
707
480
|
|
|
708
|
-
###
|
|
481
|
+
### React/Next.js Hook
|
|
709
482
|
|
|
710
483
|
```typescript
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
url: "https://api.example.com",
|
|
715
|
-
cache: { ttl: 30000 }, // 30-second cache
|
|
716
|
-
},
|
|
717
|
-
},
|
|
718
|
-
});
|
|
484
|
+
function useApi<T>(path: string) {
|
|
485
|
+
const [data, setData] = useState<T | null>(null);
|
|
486
|
+
const [loading, setLoading] = useState(true);
|
|
719
487
|
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
});
|
|
488
|
+
useEffect(() => {
|
|
489
|
+
api.url.backend
|
|
490
|
+
.get<T>({ path, responseType: "json" })
|
|
491
|
+
.then(setData)
|
|
492
|
+
.finally(() => setLoading(false));
|
|
493
|
+
}, [path]);
|
|
727
494
|
|
|
728
|
-
|
|
729
|
-
}
|
|
495
|
+
return { data, loading };
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// Usage
|
|
499
|
+
function UserList() {
|
|
500
|
+
const { data: users, loading } = useApi<User[]>("/users");
|
|
501
|
+
if (loading) return <Spinner />;
|
|
502
|
+
return (
|
|
503
|
+
<ul>
|
|
504
|
+
{users?.map((u) => (
|
|
505
|
+
<li key={u.id}>{u.name}</li>
|
|
506
|
+
))}
|
|
507
|
+
</ul>
|
|
508
|
+
);
|
|
509
|
+
}
|
|
730
510
|
```
|
|
731
511
|
|
|
732
512
|
---
|
|
733
513
|
|
|
734
|
-
## Requirements
|
|
514
|
+
## 📋 Requirements
|
|
735
515
|
|
|
736
|
-
- **Bun** v1.0.0
|
|
516
|
+
- **Bun** v1.0.0+
|
|
737
517
|
|
|
738
518
|
---
|
|
739
519
|
|
|
740
|
-
## License
|
|
520
|
+
## 📄 License
|
|
741
521
|
|
|
742
522
|
MIT © Vikash Khati
|
|
743
523
|
|
|
744
524
|
---
|
|
745
525
|
|
|
746
|
-
## Contributing
|
|
526
|
+
## 🤝 Contributing
|
|
747
527
|
|
|
748
|
-
Contributions
|
|
528
|
+
Contributions welcome! Please open an issue or submit a pull request.
|
|
749
529
|
|
|
750
530
|
---
|
|
751
531
|
|
|
752
|
-
## Support
|
|
532
|
+
## 🆘 Support
|
|
753
533
|
|
|
754
534
|
- 📖 [Documentation](https://github.com/vikashkhati007/jiren)
|
|
755
535
|
- 🐛 [Issue Tracker](https://github.com/vikashkhati007/jiren/issues)
|
|
@@ -757,4 +537,6 @@ Contributions are welcome! Please open an issue or submit a pull request.
|
|
|
757
537
|
|
|
758
538
|
---
|
|
759
539
|
|
|
760
|
-
|
|
540
|
+
<p align="center">
|
|
541
|
+
<strong>Made with ⚡ by Vikash Khati</strong>
|
|
542
|
+
</p>
|