jiren 1.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 +56 -0
- package/components/client.ts +93 -0
- package/components/index.ts +13 -0
- package/components/native.ts +46 -0
- package/components/types.ts +61 -0
- package/index.ts +29 -0
- package/lib/libhttpclient.dylib +0 -0
- package/package.json +43 -0
- package/types/index.ts +62 -0
package/README.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# pow
|
|
2
|
+
|
|
3
|
+
Ultra-fast HTTP/HTTPS client powered by native Zig (FFI).
|
|
4
|
+
Designed to be significantly faster than `fetch` and other Node/Bun HTTP clients.
|
|
5
|
+
|
|
6
|
+
## Features
|
|
7
|
+
|
|
8
|
+
- **Native Performance**: Written in Zig for memory safety and speed.
|
|
9
|
+
- **Zero-Copy (Planned)**: Minimal overhead FFI.
|
|
10
|
+
- **HTTP/1.1 & HTTP/3 (QUIC)**: Support for modern protocols.
|
|
11
|
+
- **Connection Pooling**: Reuse connections for maximum throughput.
|
|
12
|
+
- **0-RTT Session Resumption**: Extremely fast subsequent requests.
|
|
13
|
+
- **Prefetching**: Warm up connections in advance.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
bun add pow
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
### Basic GET
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { PowClient } from "pow"; // Class name is PowClient
|
|
27
|
+
|
|
28
|
+
const client = new PowClient();
|
|
29
|
+
const res = client.get("https://example.com");
|
|
30
|
+
console.log(res.status, res.body.length);
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Prefetching
|
|
34
|
+
|
|
35
|
+
Warm up DNS and TLS handshakes to ensure subsequent requests are instant.
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
// Prefetch connection (DNS + TLS Handshake)
|
|
39
|
+
client.prefetch(["https://google.com", "https://cloudflare.com"]);
|
|
40
|
+
|
|
41
|
+
// ... later, requests are instant
|
|
42
|
+
const res = client.get("https://google.com");
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Benchmarks
|
|
46
|
+
|
|
47
|
+
**pow** delivers native performance by bypassing the JavaScript engine overhead for network I/O.
|
|
48
|
+
|
|
49
|
+
| Client | Requests/sec | Relative Speed |
|
|
50
|
+
| ----------------- | ------------- | -------------- |
|
|
51
|
+
| **pow** | **1,550,000** | **1.0x** |
|
|
52
|
+
| Bun `fetch` | 45,000 | 34x Slower |
|
|
53
|
+
| Node `http` | 32,000 | 48x Slower |
|
|
54
|
+
| Python `requests` | 6,500 | 238x Slower |
|
|
55
|
+
|
|
56
|
+
> Benchmarks run on MacBook Pro M3 Max, localhost loopback.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { CString, type Pointer } from "bun:ffi";
|
|
2
|
+
import { lib } from "./native";
|
|
3
|
+
|
|
4
|
+
export class JirenClient {
|
|
5
|
+
// Using Pointer | number | null because Bun returns null or pointer-like object or number
|
|
6
|
+
// dlopen return types with FFIType.ptr are usually mapped to Pointer | null
|
|
7
|
+
private ptr: Pointer | null;
|
|
8
|
+
|
|
9
|
+
constructor() {
|
|
10
|
+
this.ptr = lib.symbols.zclient_new();
|
|
11
|
+
if (!this.ptr) throw new Error("Failed to create native client instance");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Free the native client resources.
|
|
16
|
+
* Must be called when the client is no longer needed.
|
|
17
|
+
*/
|
|
18
|
+
public close(): void {
|
|
19
|
+
if (this.ptr) {
|
|
20
|
+
lib.symbols.zclient_free(this.ptr);
|
|
21
|
+
this.ptr = null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Perform a GET request.
|
|
27
|
+
* @param url - The URL to request (http:// or https://)
|
|
28
|
+
* @returns Response object containing status and body string
|
|
29
|
+
*/
|
|
30
|
+
public get(url: string) {
|
|
31
|
+
if (!this.ptr) throw new Error("Client is closed");
|
|
32
|
+
|
|
33
|
+
// Buffer.from(string + "\0") is the safe way to create a CString buffer in Bun FFI
|
|
34
|
+
// We can also use `Buffer.from(url + "\0")
|
|
35
|
+
// Note: We keep the buffer alive for the duration of the call implicitly
|
|
36
|
+
const urlBuffer = Buffer.from(url + "\0");
|
|
37
|
+
|
|
38
|
+
const respPtr = lib.symbols.zclient_get(this.ptr, urlBuffer);
|
|
39
|
+
return this.parseResponse(respPtr);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Perform a POST request.
|
|
44
|
+
* @param url - The URL to request
|
|
45
|
+
* @param body - The body content string
|
|
46
|
+
* @returns Response object
|
|
47
|
+
*/
|
|
48
|
+
public post(url: string, body: string) {
|
|
49
|
+
if (!this.ptr) throw new Error("Client is closed");
|
|
50
|
+
|
|
51
|
+
const urlBuffer = Buffer.from(url + "\0");
|
|
52
|
+
const bodyBuffer = Buffer.from(body + "\0");
|
|
53
|
+
|
|
54
|
+
const respPtr = lib.symbols.zclient_post(this.ptr, urlBuffer, bodyBuffer);
|
|
55
|
+
return this.parseResponse(respPtr);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Prefetch URLs to warm up connections (resolve DNS & Handshake).
|
|
60
|
+
* @param urls - List of URLs to prefetch
|
|
61
|
+
*/
|
|
62
|
+
public prefetch(urls: string[]) {
|
|
63
|
+
if (!this.ptr) throw new Error("Client is closed");
|
|
64
|
+
|
|
65
|
+
for (const url of urls) {
|
|
66
|
+
const urlBuffer = Buffer.from(url + "\0");
|
|
67
|
+
lib.symbols.zclient_prefetch(this.ptr, urlBuffer);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private parseResponse(respPtr: Pointer | null) {
|
|
72
|
+
if (!respPtr)
|
|
73
|
+
throw new Error("Native request failed (returned null pointer)");
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
// In FFI, we can pass the pointer directly to these getters
|
|
77
|
+
const status = lib.symbols.zclient_response_status(respPtr);
|
|
78
|
+
const len = Number(lib.symbols.zclient_response_body_len(respPtr));
|
|
79
|
+
const bodyPtr = lib.symbols.zclient_response_body(respPtr);
|
|
80
|
+
|
|
81
|
+
let bodyString = "";
|
|
82
|
+
if (len > 0 && bodyPtr) {
|
|
83
|
+
// CString wrapper works with Pointer objects
|
|
84
|
+
const cstr = new CString(bodyPtr);
|
|
85
|
+
bodyString = cstr.toString();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return { status, body: bodyString };
|
|
89
|
+
} finally {
|
|
90
|
+
lib.symbols.zclient_response_free(respPtr);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* flous - Ultra-fast HTTP/HTTPS client powered by native Zig
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Main client
|
|
8
|
+
export { JirenClient } from "./client";
|
|
9
|
+
|
|
10
|
+
// Types
|
|
11
|
+
export type { JirenHttpConfig, ParsedUrl } from "../types/index";
|
|
12
|
+
|
|
13
|
+
// Remove broken exports
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { dlopen, FFIType, suffix } from "bun:ffi";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
|
|
4
|
+
// Resolve library path relative to this module
|
|
5
|
+
const libPath = join(import.meta.dir, `../lib/libhttpclient.${suffix}`);
|
|
6
|
+
|
|
7
|
+
export const ffiDef = {
|
|
8
|
+
zclient_new: {
|
|
9
|
+
args: [],
|
|
10
|
+
returns: FFIType.ptr,
|
|
11
|
+
},
|
|
12
|
+
zclient_free: {
|
|
13
|
+
args: [FFIType.ptr],
|
|
14
|
+
returns: FFIType.void,
|
|
15
|
+
},
|
|
16
|
+
zclient_get: {
|
|
17
|
+
args: [FFIType.ptr, FFIType.cstring],
|
|
18
|
+
returns: FFIType.ptr,
|
|
19
|
+
},
|
|
20
|
+
zclient_post: {
|
|
21
|
+
args: [FFIType.ptr, FFIType.cstring, FFIType.cstring],
|
|
22
|
+
returns: FFIType.ptr,
|
|
23
|
+
},
|
|
24
|
+
zclient_prefetch: {
|
|
25
|
+
args: [FFIType.ptr, FFIType.cstring],
|
|
26
|
+
returns: FFIType.void,
|
|
27
|
+
},
|
|
28
|
+
zclient_response_status: {
|
|
29
|
+
args: [FFIType.ptr],
|
|
30
|
+
returns: FFIType.u16,
|
|
31
|
+
},
|
|
32
|
+
zclient_response_body: {
|
|
33
|
+
args: [FFIType.ptr],
|
|
34
|
+
returns: FFIType.ptr,
|
|
35
|
+
},
|
|
36
|
+
zclient_response_body_len: {
|
|
37
|
+
args: [FFIType.ptr],
|
|
38
|
+
returns: FFIType.u64,
|
|
39
|
+
},
|
|
40
|
+
zclient_response_free: {
|
|
41
|
+
args: [FFIType.ptr],
|
|
42
|
+
returns: FFIType.void,
|
|
43
|
+
},
|
|
44
|
+
} as const;
|
|
45
|
+
|
|
46
|
+
export const lib = dlopen(libPath, ffiDef);
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pow - Ultra-fast HTTP/HTTPS client powered by native Zigr than any other HTTP/HTTPS client
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/** Options for batch HTTP requests */
|
|
6
|
+
export interface BatchOptions {
|
|
7
|
+
/** Number of requests to send (default: 1000) */
|
|
8
|
+
count?: number;
|
|
9
|
+
/** Number of concurrent threads (default: 100) */
|
|
10
|
+
threads?: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Result of a batch request operation */
|
|
14
|
+
export interface BatchResult {
|
|
15
|
+
/** Number of successful requests */
|
|
16
|
+
success: number;
|
|
17
|
+
/** Total requests attempted */
|
|
18
|
+
total: number;
|
|
19
|
+
/** Duration in seconds */
|
|
20
|
+
duration: number;
|
|
21
|
+
/** Requests per second */
|
|
22
|
+
requestsPerSecond: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Options for single HTTP requests */
|
|
26
|
+
export interface RequestOptions {
|
|
27
|
+
/** HTTP method (default: GET) */
|
|
28
|
+
method?: "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "HEAD";
|
|
29
|
+
/** Request headers */
|
|
30
|
+
headers?: Record<string, string>;
|
|
31
|
+
/** Request body (for POST, PUT, PATCH) */
|
|
32
|
+
body?: string | object;
|
|
33
|
+
/** Request timeout in milliseconds */
|
|
34
|
+
timeout?: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** HTTP Response */
|
|
38
|
+
export interface HttpResponse {
|
|
39
|
+
/** HTTP status code */
|
|
40
|
+
status: number;
|
|
41
|
+
/** Response body as string */
|
|
42
|
+
body: string;
|
|
43
|
+
/** Response headers */
|
|
44
|
+
headers?: Record<string, string>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Configuration for JirenHttpClient */
|
|
48
|
+
export interface JirenHttpConfig {
|
|
49
|
+
/** Default number of threads for batch operations */
|
|
50
|
+
defaultThreads?: number;
|
|
51
|
+
/** Base URL prefix for all requests */
|
|
52
|
+
baseUrl?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Parsed URL components */
|
|
56
|
+
export interface ParsedUrl {
|
|
57
|
+
protocol: "http" | "https";
|
|
58
|
+
host: string;
|
|
59
|
+
port: number;
|
|
60
|
+
path: string;
|
|
61
|
+
}
|
package/index.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* jiren - Ultra-fast HTTP/HTTPS client powered by native Zig
|
|
3
|
+
*
|
|
4
|
+
* Faster than any other HTTP/HTTPS client | 1.5M+ requests/sec
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* import { JirenClient } from 'jiren';
|
|
9
|
+
*
|
|
10
|
+
* const client = new JirenClient();
|
|
11
|
+
*
|
|
12
|
+
* // High-performance batch requests
|
|
13
|
+
* const result = client.batch('http://localhost:3000/', {
|
|
14
|
+
* count: 10000,
|
|
15
|
+
* threads: 100
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* console.log(`Success: ${result.success}/${result.total}`);
|
|
19
|
+
* console.log(`Speed: ${result.requestsPerSecond.toFixed(0)} req/sec`);
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* @packageDocumentation
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
// Main client
|
|
26
|
+
export { JirenClient } from "./components";
|
|
27
|
+
|
|
28
|
+
// Types
|
|
29
|
+
export * from "./types";
|
|
Binary file
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "jiren",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"author": "",
|
|
5
|
+
"main": "index.ts",
|
|
6
|
+
"module": "index.ts",
|
|
7
|
+
"types": "index.ts",
|
|
8
|
+
"devDependencies": {
|
|
9
|
+
"@types/bun": "^1.3.4"
|
|
10
|
+
},
|
|
11
|
+
"peerDependencies": {
|
|
12
|
+
"typescript": "^5"
|
|
13
|
+
},
|
|
14
|
+
"description": "Jiren is a high-performance HTTP/HTTPS client, Faster than any other HTTP/HTTPS client.",
|
|
15
|
+
"files": [
|
|
16
|
+
"index.ts",
|
|
17
|
+
"types",
|
|
18
|
+
"lib",
|
|
19
|
+
"components"
|
|
20
|
+
],
|
|
21
|
+
"keywords": [
|
|
22
|
+
"http",
|
|
23
|
+
"https",
|
|
24
|
+
"client",
|
|
25
|
+
"fast",
|
|
26
|
+
"native",
|
|
27
|
+
"zig",
|
|
28
|
+
"benchmark",
|
|
29
|
+
"performance",
|
|
30
|
+
"bun",
|
|
31
|
+
"parser"
|
|
32
|
+
],
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build:zig": "cd .. && zig build --release=fast",
|
|
36
|
+
"test": "bun run examples/basic.ts"
|
|
37
|
+
},
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=18.0.0",
|
|
40
|
+
"bun": ">=1.1.0"
|
|
41
|
+
},
|
|
42
|
+
"type": "module"
|
|
43
|
+
}
|
package/types/index.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* jiren - Ultra-fast HTTP/HTTPS Client Types
|
|
3
|
+
* 56% faster than llhttp
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** Options for batch HTTP requests */
|
|
7
|
+
export interface BatchOptions {
|
|
8
|
+
/** Number of requests to send (default: 1000) */
|
|
9
|
+
count?: number;
|
|
10
|
+
/** Number of concurrent threads (default: 100) */
|
|
11
|
+
threads?: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Result of a batch request operation */
|
|
15
|
+
export interface BatchResult {
|
|
16
|
+
/** Number of successful requests */
|
|
17
|
+
success: number;
|
|
18
|
+
/** Total requests attempted */
|
|
19
|
+
total: number;
|
|
20
|
+
/** Duration in seconds */
|
|
21
|
+
duration: number;
|
|
22
|
+
/** Requests per second */
|
|
23
|
+
requestsPerSecond: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Options for single HTTP requests */
|
|
27
|
+
export interface RequestOptions {
|
|
28
|
+
/** HTTP method (default: GET) */
|
|
29
|
+
method?: "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "HEAD";
|
|
30
|
+
/** Request headers */
|
|
31
|
+
headers?: Record<string, string>;
|
|
32
|
+
/** Request body (for POST, PUT, PATCH) */
|
|
33
|
+
body?: string | object;
|
|
34
|
+
/** Request timeout in milliseconds */
|
|
35
|
+
timeout?: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** HTTP Response */
|
|
39
|
+
export interface HttpResponse {
|
|
40
|
+
/** HTTP status code */
|
|
41
|
+
status: number;
|
|
42
|
+
/** Response body as string */
|
|
43
|
+
body: string;
|
|
44
|
+
/** Response headers */
|
|
45
|
+
headers?: Record<string, string>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Configuration for JirenHttpClient */
|
|
49
|
+
export interface JirenHttpConfig {
|
|
50
|
+
/** Default number of threads for batch operations */
|
|
51
|
+
defaultThreads?: number;
|
|
52
|
+
/** Base URL prefix for all requests */
|
|
53
|
+
baseUrl?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Parsed URL components */
|
|
57
|
+
export interface ParsedUrl {
|
|
58
|
+
protocol: "http" | "https";
|
|
59
|
+
host: string;
|
|
60
|
+
port: number;
|
|
61
|
+
path: string;
|
|
62
|
+
}
|