jiren 1.0.14 → 1.1.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/components/client-bun.ts +188 -0
- package/components/client-node.ts +105 -0
- package/components/index.ts +13 -0
- package/components/native.ts +64 -0
- package/components/runtime.ts +11 -0
- package/components/types.ts +61 -0
- package/dist/index.js +151 -62
- package/package.json +10 -6
- package/types/index.ts +73 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { CString, type Pointer } from "bun:ffi";
|
|
2
|
+
import { lib } from "./native";
|
|
3
|
+
import type {
|
|
4
|
+
RequestOptions,
|
|
5
|
+
GetRequestOptions,
|
|
6
|
+
PostRequestOptions,
|
|
7
|
+
HttpResponse,
|
|
8
|
+
} from "../types";
|
|
9
|
+
|
|
10
|
+
export class JirenClient {
|
|
11
|
+
private ptr: Pointer | null;
|
|
12
|
+
|
|
13
|
+
constructor() {
|
|
14
|
+
this.ptr = lib.symbols.zclient_new();
|
|
15
|
+
if (!this.ptr) throw new Error("Failed to create native client instance");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Free the native client resources.
|
|
20
|
+
* Must be called when the client is no longer needed.
|
|
21
|
+
*/
|
|
22
|
+
public close(): void {
|
|
23
|
+
if (this.ptr) {
|
|
24
|
+
lib.symbols.zclient_free(this.ptr);
|
|
25
|
+
this.ptr = null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Perform a HTTP request.
|
|
31
|
+
* @param url - The URL to request
|
|
32
|
+
* @param options - Request options (method, headers, body)
|
|
33
|
+
* @returns Response object
|
|
34
|
+
*/
|
|
35
|
+
public request(url: string, options: RequestOptions = {}): HttpResponse {
|
|
36
|
+
if (!this.ptr) throw new Error("Client is closed");
|
|
37
|
+
|
|
38
|
+
const method = options.method || "GET";
|
|
39
|
+
const methodBuffer = Buffer.from(method + "\0");
|
|
40
|
+
const urlBuffer = Buffer.from(url + "\0");
|
|
41
|
+
|
|
42
|
+
let headersBuffer: Buffer | null = null;
|
|
43
|
+
if (options.headers) {
|
|
44
|
+
const headerStr = Object.entries(options.headers)
|
|
45
|
+
.map(([k, v]) => `${k.toLowerCase()}: ${v}`)
|
|
46
|
+
.join("\r\n");
|
|
47
|
+
if (headerStr.length > 0) {
|
|
48
|
+
headersBuffer = Buffer.from(headerStr + "\0");
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let bodyBuffer: Buffer | null = null;
|
|
53
|
+
if (options.body) {
|
|
54
|
+
const bodyStr =
|
|
55
|
+
typeof options.body === "string"
|
|
56
|
+
? options.body
|
|
57
|
+
: JSON.stringify(options.body);
|
|
58
|
+
bodyBuffer = Buffer.from(bodyStr + "\0");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const respPtr = lib.symbols.zclient_request(
|
|
62
|
+
this.ptr,
|
|
63
|
+
methodBuffer,
|
|
64
|
+
urlBuffer,
|
|
65
|
+
headersBuffer,
|
|
66
|
+
bodyBuffer
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const response = this.parseResponse(respPtr);
|
|
70
|
+
|
|
71
|
+
// Handle Redirects
|
|
72
|
+
if (
|
|
73
|
+
options.maxRedirects &&
|
|
74
|
+
options.maxRedirects > 0 &&
|
|
75
|
+
response.status >= 300 &&
|
|
76
|
+
response.status < 400 &&
|
|
77
|
+
response.headers &&
|
|
78
|
+
response.headers["location"]
|
|
79
|
+
) {
|
|
80
|
+
const location = response.headers["location"];
|
|
81
|
+
const newUrl = new URL(location, url).toString(); // Resolve relative URLs
|
|
82
|
+
const newOptions = { ...options, maxRedirects: options.maxRedirects - 1 };
|
|
83
|
+
|
|
84
|
+
return this.request(newUrl, newOptions);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return response;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
public get(url: string, options: GetRequestOptions = {}) {
|
|
91
|
+
return this.request(url, { ...options, method: "GET" });
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
public post(
|
|
95
|
+
url: string,
|
|
96
|
+
body: string | object,
|
|
97
|
+
options: PostRequestOptions = {}
|
|
98
|
+
) {
|
|
99
|
+
return this.request(url, { ...options, method: "POST", body });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
public put(
|
|
103
|
+
url: string,
|
|
104
|
+
body: string | object,
|
|
105
|
+
options: PostRequestOptions = {}
|
|
106
|
+
) {
|
|
107
|
+
return this.request(url, { ...options, method: "PUT", body });
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
public delete(url: string, options: GetRequestOptions = {}) {
|
|
111
|
+
return this.request(url, { ...options, method: "DELETE" });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
public patch(
|
|
115
|
+
url: string,
|
|
116
|
+
body: string | object,
|
|
117
|
+
options: PostRequestOptions = {}
|
|
118
|
+
) {
|
|
119
|
+
return this.request(url, { ...options, method: "PATCH", body });
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
public head(url: string, options: GetRequestOptions = {}) {
|
|
123
|
+
return this.request(url, { ...options, method: "HEAD" });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Prefetch URLs to warm up connections (resolve DNS & Handshake).
|
|
128
|
+
* @param urls - List of URLs to prefetch
|
|
129
|
+
*/
|
|
130
|
+
public prefetch(urls: string[]) {
|
|
131
|
+
if (!this.ptr) throw new Error("Client is closed");
|
|
132
|
+
|
|
133
|
+
for (const url of urls) {
|
|
134
|
+
const urlBuffer = Buffer.from(url + "\0");
|
|
135
|
+
lib.symbols.zclient_prefetch(this.ptr, urlBuffer);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private parseResponse(respPtr: Pointer | null) {
|
|
140
|
+
if (!respPtr)
|
|
141
|
+
throw new Error("Native request failed (returned null pointer)");
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
const status = lib.symbols.zclient_response_status(respPtr);
|
|
145
|
+
const bodyLen = Number(lib.symbols.zclient_response_body_len(respPtr));
|
|
146
|
+
const bodyPtr = lib.symbols.zclient_response_body(respPtr);
|
|
147
|
+
const headersLen = Number(
|
|
148
|
+
lib.symbols.zclient_response_headers_len(respPtr)
|
|
149
|
+
);
|
|
150
|
+
const headersPtr = lib.symbols.zclient_response_headers(respPtr);
|
|
151
|
+
|
|
152
|
+
let bodyString = "";
|
|
153
|
+
if (bodyLen > 0 && bodyPtr) {
|
|
154
|
+
// CString uses the full length if not null-terminated or we can just read the ptr
|
|
155
|
+
// Since we know the length, we can be more precise, but CString assumes null-terminated
|
|
156
|
+
// for now we trust it is null terminated from Zig
|
|
157
|
+
bodyString = new CString(bodyPtr).toString();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const headers: Record<string, string> = {};
|
|
161
|
+
if (headersLen > 0 && headersPtr) {
|
|
162
|
+
const headersStr = new CString(headersPtr).toString();
|
|
163
|
+
const lines = headersStr.split("\r\n");
|
|
164
|
+
for (const line of lines) {
|
|
165
|
+
if (!line) continue;
|
|
166
|
+
const colonIdx = line.indexOf(":");
|
|
167
|
+
if (colonIdx !== -1) {
|
|
168
|
+
const key = line.substring(0, colonIdx).trim().toLowerCase();
|
|
169
|
+
const val = line.substring(colonIdx + 1).trim();
|
|
170
|
+
headers[key] = val;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
status,
|
|
177
|
+
body: bodyString,
|
|
178
|
+
headers,
|
|
179
|
+
json: <T = any>() => {
|
|
180
|
+
if (!bodyString) return null as T;
|
|
181
|
+
return JSON.parse(bodyString) as T;
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
} finally {
|
|
185
|
+
lib.symbols.zclient_response_free(respPtr);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
RequestOptions,
|
|
3
|
+
GetRequestOptions,
|
|
4
|
+
PostRequestOptions,
|
|
5
|
+
HttpResponse,
|
|
6
|
+
} from "../types/index.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Node.js fallback client using native fetch
|
|
10
|
+
*/
|
|
11
|
+
export class NodeJirenClient {
|
|
12
|
+
constructor() {
|
|
13
|
+
// No initialization needed for Node.js fetch
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public close(): void {
|
|
17
|
+
// No cleanup needed
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public async request(
|
|
21
|
+
url: string,
|
|
22
|
+
options: RequestOptions = {}
|
|
23
|
+
): Promise<HttpResponse> {
|
|
24
|
+
const method = options.method || "GET";
|
|
25
|
+
const headers = options.headers || {};
|
|
26
|
+
|
|
27
|
+
const fetchOptions: RequestInit = {
|
|
28
|
+
method,
|
|
29
|
+
headers,
|
|
30
|
+
redirect:
|
|
31
|
+
options.maxRedirects && options.maxRedirects > 0 ? "follow" : "manual",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
if (options.body) {
|
|
35
|
+
fetchOptions.body =
|
|
36
|
+
typeof options.body === "string"
|
|
37
|
+
? options.body
|
|
38
|
+
: JSON.stringify(options.body);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const response = await fetch(url, fetchOptions);
|
|
43
|
+
const body = await response.text();
|
|
44
|
+
|
|
45
|
+
// Convert Headers to plain object
|
|
46
|
+
const responseHeaders: Record<string, string> = {};
|
|
47
|
+
response.headers.forEach((value, key) => {
|
|
48
|
+
responseHeaders[key.toLowerCase()] = value;
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
status: response.status,
|
|
53
|
+
body,
|
|
54
|
+
headers: responseHeaders,
|
|
55
|
+
json: <T = any>() => {
|
|
56
|
+
if (!body) return null as T;
|
|
57
|
+
return JSON.parse(body) as T;
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
} catch (error) {
|
|
61
|
+
throw new Error(`HTTP request failed: ${error}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public async get(url: string, options: GetRequestOptions = {}) {
|
|
66
|
+
return this.request(url, { ...options, method: "GET" });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public async post(
|
|
70
|
+
url: string,
|
|
71
|
+
body: string | object,
|
|
72
|
+
options: PostRequestOptions = {}
|
|
73
|
+
) {
|
|
74
|
+
return this.request(url, { ...options, method: "POST", body });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
public async put(
|
|
78
|
+
url: string,
|
|
79
|
+
body: string | object,
|
|
80
|
+
options: PostRequestOptions = {}
|
|
81
|
+
) {
|
|
82
|
+
return this.request(url, { ...options, method: "PUT", body });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
public async delete(url: string, options: GetRequestOptions = {}) {
|
|
86
|
+
return this.request(url, { ...options, method: "DELETE" });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
public async patch(
|
|
90
|
+
url: string,
|
|
91
|
+
body: string | object,
|
|
92
|
+
options: PostRequestOptions = {}
|
|
93
|
+
) {
|
|
94
|
+
return this.request(url, { ...options, method: "PATCH", body });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
public async head(url: string, options: GetRequestOptions = {}) {
|
|
98
|
+
return this.request(url, { ...options, method: "HEAD" });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
public prefetch(urls: string[]) {
|
|
102
|
+
// No-op in Node.js
|
|
103
|
+
// Could implement DNS prefetch here if needed
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { isBun } from "./runtime.js";
|
|
2
|
+
|
|
3
|
+
// Conditionally export the right client
|
|
4
|
+
export const JirenClient = isBun
|
|
5
|
+
? (await import("./client-bun.js")).JirenClient
|
|
6
|
+
: (await import("./client-node.js")).NodeJirenClient;
|
|
7
|
+
|
|
8
|
+
export type {
|
|
9
|
+
HttpResponse,
|
|
10
|
+
RequestOptions,
|
|
11
|
+
GetRequestOptions,
|
|
12
|
+
PostRequestOptions,
|
|
13
|
+
} from "../types/index.js";
|
|
@@ -0,0 +1,64 @@
|
|
|
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_headers: {
|
|
41
|
+
args: [FFIType.ptr],
|
|
42
|
+
returns: FFIType.ptr,
|
|
43
|
+
},
|
|
44
|
+
zclient_response_headers_len: {
|
|
45
|
+
args: [FFIType.ptr],
|
|
46
|
+
returns: FFIType.u64,
|
|
47
|
+
},
|
|
48
|
+
zclient_response_free: {
|
|
49
|
+
args: [FFIType.ptr],
|
|
50
|
+
returns: FFIType.void,
|
|
51
|
+
},
|
|
52
|
+
zclient_request: {
|
|
53
|
+
args: [
|
|
54
|
+
FFIType.ptr, // client
|
|
55
|
+
FFIType.cstring, // method
|
|
56
|
+
FFIType.cstring, // url
|
|
57
|
+
FFIType.cstring, // headers (nullable)
|
|
58
|
+
FFIType.cstring, // body (nullable)
|
|
59
|
+
],
|
|
60
|
+
returns: FFIType.ptr,
|
|
61
|
+
},
|
|
62
|
+
} as const;
|
|
63
|
+
|
|
64
|
+
export const lib = dlopen(libPath, ffiDef);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Runtime detection
|
|
2
|
+
export const isBun = typeof Bun !== "undefined";
|
|
3
|
+
export const isNode = !isBun && typeof process !== "undefined";
|
|
4
|
+
|
|
5
|
+
// Re-export types
|
|
6
|
+
export type {
|
|
7
|
+
HttpResponse,
|
|
8
|
+
RequestOptions,
|
|
9
|
+
GetRequestOptions,
|
|
10
|
+
PostRequestOptions,
|
|
11
|
+
} from "../types/index.js";
|
|
@@ -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/dist/index.js
CHANGED
|
@@ -1,70 +1,88 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __export = (target, all) => {
|
|
4
|
+
for (var name in all)
|
|
5
|
+
__defProp(target, name, {
|
|
6
|
+
get: all[name],
|
|
7
|
+
enumerable: true,
|
|
8
|
+
configurable: true,
|
|
9
|
+
set: (newValue) => all[name] = () => newValue
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
4
13
|
|
|
5
14
|
// components/native.ts
|
|
6
15
|
import { dlopen, FFIType, suffix } from "bun:ffi";
|
|
7
16
|
import { join } from "path";
|
|
8
|
-
var libPath
|
|
9
|
-
var
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
17
|
+
var libPath, ffiDef, lib;
|
|
18
|
+
var init_native = __esm(() => {
|
|
19
|
+
libPath = join(import.meta.dir, `../lib/libhttpclient.${suffix}`);
|
|
20
|
+
ffiDef = {
|
|
21
|
+
zclient_new: {
|
|
22
|
+
args: [],
|
|
23
|
+
returns: FFIType.ptr
|
|
24
|
+
},
|
|
25
|
+
zclient_free: {
|
|
26
|
+
args: [FFIType.ptr],
|
|
27
|
+
returns: FFIType.void
|
|
28
|
+
},
|
|
29
|
+
zclient_get: {
|
|
30
|
+
args: [FFIType.ptr, FFIType.cstring],
|
|
31
|
+
returns: FFIType.ptr
|
|
32
|
+
},
|
|
33
|
+
zclient_post: {
|
|
34
|
+
args: [FFIType.ptr, FFIType.cstring, FFIType.cstring],
|
|
35
|
+
returns: FFIType.ptr
|
|
36
|
+
},
|
|
37
|
+
zclient_prefetch: {
|
|
38
|
+
args: [FFIType.ptr, FFIType.cstring],
|
|
39
|
+
returns: FFIType.void
|
|
40
|
+
},
|
|
41
|
+
zclient_response_status: {
|
|
42
|
+
args: [FFIType.ptr],
|
|
43
|
+
returns: FFIType.u16
|
|
44
|
+
},
|
|
45
|
+
zclient_response_body: {
|
|
46
|
+
args: [FFIType.ptr],
|
|
47
|
+
returns: FFIType.ptr
|
|
48
|
+
},
|
|
49
|
+
zclient_response_body_len: {
|
|
50
|
+
args: [FFIType.ptr],
|
|
51
|
+
returns: FFIType.u64
|
|
52
|
+
},
|
|
53
|
+
zclient_response_headers: {
|
|
54
|
+
args: [FFIType.ptr],
|
|
55
|
+
returns: FFIType.ptr
|
|
56
|
+
},
|
|
57
|
+
zclient_response_headers_len: {
|
|
58
|
+
args: [FFIType.ptr],
|
|
59
|
+
returns: FFIType.u64
|
|
60
|
+
},
|
|
61
|
+
zclient_response_free: {
|
|
62
|
+
args: [FFIType.ptr],
|
|
63
|
+
returns: FFIType.void
|
|
64
|
+
},
|
|
65
|
+
zclient_request: {
|
|
66
|
+
args: [
|
|
67
|
+
FFIType.ptr,
|
|
68
|
+
FFIType.cstring,
|
|
69
|
+
FFIType.cstring,
|
|
70
|
+
FFIType.cstring,
|
|
71
|
+
FFIType.cstring
|
|
72
|
+
],
|
|
73
|
+
returns: FFIType.ptr
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
lib = dlopen(libPath, ffiDef);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// components/client-bun.ts
|
|
80
|
+
var exports_client_bun = {};
|
|
81
|
+
__export(exports_client_bun, {
|
|
82
|
+
JirenClient: () => JirenClient
|
|
83
|
+
});
|
|
84
|
+
import { CString } from "bun:ffi";
|
|
66
85
|
|
|
67
|
-
// components/client.ts
|
|
68
86
|
class JirenClient {
|
|
69
87
|
ptr;
|
|
70
88
|
constructor() {
|
|
@@ -177,6 +195,77 @@ class JirenClient {
|
|
|
177
195
|
}
|
|
178
196
|
}
|
|
179
197
|
}
|
|
198
|
+
var init_client_bun = __esm(() => {
|
|
199
|
+
init_native();
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// components/client-node.ts
|
|
203
|
+
var exports_client_node = {};
|
|
204
|
+
__export(exports_client_node, {
|
|
205
|
+
NodeJirenClient: () => NodeJirenClient
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
class NodeJirenClient {
|
|
209
|
+
constructor() {}
|
|
210
|
+
close() {}
|
|
211
|
+
async request(url, options = {}) {
|
|
212
|
+
const method = options.method || "GET";
|
|
213
|
+
const headers = options.headers || {};
|
|
214
|
+
const fetchOptions = {
|
|
215
|
+
method,
|
|
216
|
+
headers,
|
|
217
|
+
redirect: options.maxRedirects && options.maxRedirects > 0 ? "follow" : "manual"
|
|
218
|
+
};
|
|
219
|
+
if (options.body) {
|
|
220
|
+
fetchOptions.body = typeof options.body === "string" ? options.body : JSON.stringify(options.body);
|
|
221
|
+
}
|
|
222
|
+
try {
|
|
223
|
+
const response = await fetch(url, fetchOptions);
|
|
224
|
+
const body = await response.text();
|
|
225
|
+
const responseHeaders = {};
|
|
226
|
+
response.headers.forEach((value, key) => {
|
|
227
|
+
responseHeaders[key.toLowerCase()] = value;
|
|
228
|
+
});
|
|
229
|
+
return {
|
|
230
|
+
status: response.status,
|
|
231
|
+
body,
|
|
232
|
+
headers: responseHeaders,
|
|
233
|
+
json: () => {
|
|
234
|
+
if (!body)
|
|
235
|
+
return null;
|
|
236
|
+
return JSON.parse(body);
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
} catch (error) {
|
|
240
|
+
throw new Error(`HTTP request failed: ${error}`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
async get(url, options = {}) {
|
|
244
|
+
return this.request(url, { ...options, method: "GET" });
|
|
245
|
+
}
|
|
246
|
+
async post(url, body, options = {}) {
|
|
247
|
+
return this.request(url, { ...options, method: "POST", body });
|
|
248
|
+
}
|
|
249
|
+
async put(url, body, options = {}) {
|
|
250
|
+
return this.request(url, { ...options, method: "PUT", body });
|
|
251
|
+
}
|
|
252
|
+
async delete(url, options = {}) {
|
|
253
|
+
return this.request(url, { ...options, method: "DELETE" });
|
|
254
|
+
}
|
|
255
|
+
async patch(url, body, options = {}) {
|
|
256
|
+
return this.request(url, { ...options, method: "PATCH", body });
|
|
257
|
+
}
|
|
258
|
+
async head(url, options = {}) {
|
|
259
|
+
return this.request(url, { ...options, method: "HEAD" });
|
|
260
|
+
}
|
|
261
|
+
prefetch(urls) {}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// components/runtime.ts
|
|
265
|
+
var isBun = typeof Bun !== "undefined";
|
|
266
|
+
|
|
267
|
+
// components/index.ts
|
|
268
|
+
var JirenClient2 = isBun ? (await Promise.resolve().then(() => (init_client_bun(), exports_client_bun))).JirenClient : (await Promise.resolve().then(() => exports_client_node)).NodeJirenClient;
|
|
180
269
|
export {
|
|
181
|
-
JirenClient
|
|
270
|
+
JirenClient2 as JirenClient
|
|
182
271
|
};
|
package/package.json
CHANGED
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jiren",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"author": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|
|
7
|
-
"types": "
|
|
7
|
+
"types": "types/index.ts",
|
|
8
8
|
"devDependencies": {
|
|
9
9
|
"@types/bun": "^1.3.4",
|
|
10
|
+
"@types/node": "^20",
|
|
10
11
|
"bun-types": "^1.3.4"
|
|
11
12
|
},
|
|
12
13
|
"peerDependencies": {
|
|
13
14
|
"typescript": "^5"
|
|
14
15
|
},
|
|
15
|
-
"description": "Jiren is a high-performance HTTP/HTTPS client,
|
|
16
|
+
"description": "Jiren is a high-performance HTTP/HTTPS client. Ultra-fast with Bun FFI, falls back to fetch in Node.js.",
|
|
16
17
|
"files": [
|
|
17
18
|
"dist",
|
|
18
|
-
"lib"
|
|
19
|
+
"lib",
|
|
20
|
+
"components",
|
|
21
|
+
"types"
|
|
19
22
|
],
|
|
20
23
|
"keywords": [
|
|
21
24
|
"http",
|
|
@@ -27,17 +30,18 @@
|
|
|
27
30
|
"benchmark",
|
|
28
31
|
"performance",
|
|
29
32
|
"bun",
|
|
33
|
+
"node",
|
|
30
34
|
"parser"
|
|
31
35
|
],
|
|
32
36
|
"license": "MIT",
|
|
33
37
|
"scripts": {
|
|
34
38
|
"build:zig": "cd .. && zig build --release=fast",
|
|
35
|
-
"build": "bun build ./index.ts --outdir dist --target bun
|
|
36
|
-
"types": "bun x tsc --declaration --emitDeclarationOnly --outDir dist",
|
|
39
|
+
"build": "bun build ./index.ts --outdir dist --target bun",
|
|
37
40
|
"prepare": "bun run build",
|
|
38
41
|
"test": "bun run examples/basic.ts"
|
|
39
42
|
},
|
|
40
43
|
"engines": {
|
|
44
|
+
"node": ">=18.0.0",
|
|
41
45
|
"bun": ">=1.1.0"
|
|
42
46
|
},
|
|
43
47
|
"type": "module"
|
package/types/index.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
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
|
+
/** Options for single HTTP requests */
|
|
28
|
+
export interface RequestOptions {
|
|
29
|
+
/** HTTP method (default: GET) */
|
|
30
|
+
method?: string;
|
|
31
|
+
/** Request headers */
|
|
32
|
+
headers?: Record<string, string>;
|
|
33
|
+
/** Request body (for POST, PUT, PATCH) */
|
|
34
|
+
body?: string | object;
|
|
35
|
+
/** Request timeout in milliseconds */
|
|
36
|
+
timeout?: number;
|
|
37
|
+
/** Maximum number of redirects to follow (default: 0) */
|
|
38
|
+
maxRedirects?: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Options for requests without a body (GET, DELETE, HEAD) */
|
|
42
|
+
export type GetRequestOptions = Omit<RequestOptions, "method" | "body">;
|
|
43
|
+
|
|
44
|
+
/** Options for requests with a body (POST, PUT, PATCH) where body is a separate argument */
|
|
45
|
+
export type PostRequestOptions = Omit<RequestOptions, "method" | "body">;
|
|
46
|
+
|
|
47
|
+
/** HTTP Response */
|
|
48
|
+
export interface HttpResponse {
|
|
49
|
+
/** HTTP status code */
|
|
50
|
+
status: number;
|
|
51
|
+
/** Response body as string */
|
|
52
|
+
body: string;
|
|
53
|
+
/** Response headers */
|
|
54
|
+
headers?: Record<string, string>;
|
|
55
|
+
/** Helper to parse JSON body */
|
|
56
|
+
json: <T = any>() => T;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Configuration for JirenHttpClient */
|
|
60
|
+
export interface JirenHttpConfig {
|
|
61
|
+
/** Default number of threads for batch operations */
|
|
62
|
+
defaultThreads?: number;
|
|
63
|
+
/** Base URL prefix for all requests */
|
|
64
|
+
baseUrl?: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Parsed URL components */
|
|
68
|
+
export interface ParsedUrl {
|
|
69
|
+
protocol: "http" | "https";
|
|
70
|
+
host: string;
|
|
71
|
+
port: number;
|
|
72
|
+
path: string;
|
|
73
|
+
}
|