jiren 1.0.10 → 1.0.12
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.js +140 -0
- package/components/{index.ts → index.js} +0 -5
- package/components/native.js +132 -0
- package/components/runtime.js +16 -0
- package/components/types.js +4 -0
- package/{index.ts → index.js} +0 -2
- package/package.json +12 -9
- package/types/index.js +5 -0
- package/components/client.ts +0 -190
- package/components/native.ts +0 -182
- package/components/runtime.ts +0 -17
- package/components/types.ts +0 -61
- package/dist/test.d.ts +0 -1
- package/types/index.ts +0 -77
- /package/{dist/components → components}/client.d.ts +0 -0
- /package/{dist/components → components}/index.d.ts +0 -0
- /package/{dist/components → components}/native.d.ts +0 -0
- /package/{dist/components → components}/runtime.d.ts +0 -0
- /package/{dist/components → components}/types.d.ts +0 -0
- /package/{dist/index.d.ts → index.d.ts} +0 -0
- /package/{dist/types → types}/index.d.ts +0 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { lib, readCString } from "./native";
|
|
2
|
+
export class JirenClient {
|
|
3
|
+
ptr;
|
|
4
|
+
constructor() {
|
|
5
|
+
this.ptr = lib.symbols.zclient_new();
|
|
6
|
+
if (!this.ptr)
|
|
7
|
+
throw new Error("Failed to create native client instance");
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Free the native client resources.
|
|
11
|
+
* Must be called when the client is no longer needed.
|
|
12
|
+
*/
|
|
13
|
+
close() {
|
|
14
|
+
if (this.ptr) {
|
|
15
|
+
lib.symbols.zclient_free(this.ptr);
|
|
16
|
+
this.ptr = null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Perform a HTTP request.
|
|
21
|
+
* @param url - The URL to request
|
|
22
|
+
* @param options - Request options (method, headers, body)
|
|
23
|
+
* @returns Response object
|
|
24
|
+
*/
|
|
25
|
+
request(url, options = {}) {
|
|
26
|
+
if (!this.ptr)
|
|
27
|
+
throw new Error("Client is closed");
|
|
28
|
+
const method = options.method || "GET";
|
|
29
|
+
const methodBuffer = Buffer.from(method + "\0");
|
|
30
|
+
const urlBuffer = Buffer.from(url + "\0");
|
|
31
|
+
let headersBuffer = null;
|
|
32
|
+
if (options.headers) {
|
|
33
|
+
const headerStr = Object.entries(options.headers)
|
|
34
|
+
.map(([k, v]) => `${k.toLowerCase()}: ${v}`)
|
|
35
|
+
.join("\r\n");
|
|
36
|
+
if (headerStr.length > 0) {
|
|
37
|
+
headersBuffer = Buffer.from(headerStr + "\0");
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
let bodyBuffer = null;
|
|
41
|
+
if (options.body) {
|
|
42
|
+
const bodyStr = typeof options.body === "string"
|
|
43
|
+
? options.body
|
|
44
|
+
: JSON.stringify(options.body);
|
|
45
|
+
bodyBuffer = Buffer.from(bodyStr + "\0");
|
|
46
|
+
}
|
|
47
|
+
const respPtr = lib.symbols.zclient_request(this.ptr, methodBuffer, urlBuffer, headersBuffer, bodyBuffer);
|
|
48
|
+
const response = this.parseResponse(respPtr);
|
|
49
|
+
// Handle Redirects
|
|
50
|
+
if (options.maxRedirects &&
|
|
51
|
+
options.maxRedirects > 0 &&
|
|
52
|
+
response.status >= 300 &&
|
|
53
|
+
response.status < 400 &&
|
|
54
|
+
response.headers &&
|
|
55
|
+
response.headers["location"]) {
|
|
56
|
+
const location = response.headers["location"];
|
|
57
|
+
const newUrl = new URL(location, url).toString(); // Resolve relative URLs
|
|
58
|
+
const newOptions = { ...options, maxRedirects: options.maxRedirects - 1 };
|
|
59
|
+
return this.request(newUrl, newOptions);
|
|
60
|
+
}
|
|
61
|
+
return response;
|
|
62
|
+
}
|
|
63
|
+
get(url, options = {}) {
|
|
64
|
+
return this.request(url, { ...options, method: "GET" });
|
|
65
|
+
}
|
|
66
|
+
post(url, body, options = {}) {
|
|
67
|
+
return this.request(url, { ...options, method: "POST", body });
|
|
68
|
+
}
|
|
69
|
+
put(url, body, options = {}) {
|
|
70
|
+
return this.request(url, { ...options, method: "PUT", body });
|
|
71
|
+
}
|
|
72
|
+
delete(url, options = {}) {
|
|
73
|
+
return this.request(url, { ...options, method: "DELETE" });
|
|
74
|
+
}
|
|
75
|
+
patch(url, body, options = {}) {
|
|
76
|
+
return this.request(url, { ...options, method: "PATCH", body });
|
|
77
|
+
}
|
|
78
|
+
head(url, options = {}) {
|
|
79
|
+
return this.request(url, { ...options, method: "HEAD" });
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Prefetch URLs to warm up connections (resolve DNS & Handshake).
|
|
83
|
+
* @param urls - List of URLs to prefetch
|
|
84
|
+
*/
|
|
85
|
+
prefetch(urls) {
|
|
86
|
+
if (!this.ptr)
|
|
87
|
+
throw new Error("Client is closed");
|
|
88
|
+
for (const url of urls) {
|
|
89
|
+
const urlBuffer = Buffer.from(url + "\0");
|
|
90
|
+
lib.symbols.zclient_prefetch(this.ptr, urlBuffer);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
parseResponse(respPtr) {
|
|
94
|
+
if (!respPtr)
|
|
95
|
+
throw new Error("Native request failed (returned null pointer)");
|
|
96
|
+
try {
|
|
97
|
+
const status = lib.symbols.zclient_response_status(respPtr);
|
|
98
|
+
const bodyLen = Number(lib.symbols.zclient_response_body_len(respPtr));
|
|
99
|
+
const bodyPtr = lib.symbols.zclient_response_body(respPtr);
|
|
100
|
+
const headersLen = Number(lib.symbols.zclient_response_headers_len(respPtr));
|
|
101
|
+
const headersPtr = lib.symbols.zclient_response_headers(respPtr);
|
|
102
|
+
let bodyString = "";
|
|
103
|
+
if (bodyLen > 0 && bodyPtr) {
|
|
104
|
+
bodyString = readCString(bodyPtr);
|
|
105
|
+
}
|
|
106
|
+
const headers = {};
|
|
107
|
+
if (headersLen > 0 && headersPtr) {
|
|
108
|
+
const headersStr = readCString(headersPtr);
|
|
109
|
+
const lines = headersStr.split("\r\n");
|
|
110
|
+
for (const line of lines) {
|
|
111
|
+
if (!line)
|
|
112
|
+
continue;
|
|
113
|
+
const colonIdx = line.indexOf(":");
|
|
114
|
+
if (colonIdx !== -1) {
|
|
115
|
+
const key = line.substring(0, colonIdx).trim().toLowerCase();
|
|
116
|
+
const val = line.substring(colonIdx + 1).trim();
|
|
117
|
+
headers[key] = val;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
status,
|
|
123
|
+
body: bodyString,
|
|
124
|
+
headers,
|
|
125
|
+
get ok() {
|
|
126
|
+
return status >= 200 && status < 300;
|
|
127
|
+
},
|
|
128
|
+
text: async () => bodyString,
|
|
129
|
+
json: () => {
|
|
130
|
+
if (!bodyString)
|
|
131
|
+
return null;
|
|
132
|
+
return JSON.parse(bodyString);
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
finally {
|
|
137
|
+
lib.symbols.zclient_response_free(respPtr);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { isBun } from "./runtime";
|
|
2
|
+
import { join, dirname } from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
// Helper to get library path
|
|
5
|
+
function getLibPath() {
|
|
6
|
+
const platform = process.platform;
|
|
7
|
+
const ext = platform === "darwin" ? "dylib" : platform === "win32" ? "dll" : "so";
|
|
8
|
+
if (isBun) {
|
|
9
|
+
// @ts-ignore - Bun-specific
|
|
10
|
+
return join(import.meta.dir, `../lib/libhttpclient.${ext}`);
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
// Node.js
|
|
14
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
15
|
+
const __dirname = dirname(__filename);
|
|
16
|
+
return join(__dirname, `../lib/libhttpclient.${ext}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
// Load the appropriate FFI library
|
|
20
|
+
async function loadLibrary() {
|
|
21
|
+
const libPath = getLibPath();
|
|
22
|
+
if (isBun) {
|
|
23
|
+
// Use Bun FFI
|
|
24
|
+
const { dlopen, FFIType } = await import("bun:ffi");
|
|
25
|
+
const ffiDef = {
|
|
26
|
+
zclient_new: { args: [], returns: FFIType.ptr },
|
|
27
|
+
zclient_free: { args: [FFIType.ptr], returns: FFIType.void },
|
|
28
|
+
zclient_get: {
|
|
29
|
+
args: [FFIType.ptr, FFIType.cstring],
|
|
30
|
+
returns: FFIType.ptr,
|
|
31
|
+
},
|
|
32
|
+
zclient_post: {
|
|
33
|
+
args: [FFIType.ptr, FFIType.cstring, FFIType.cstring],
|
|
34
|
+
returns: FFIType.ptr,
|
|
35
|
+
},
|
|
36
|
+
zclient_prefetch: {
|
|
37
|
+
args: [FFIType.ptr, FFIType.cstring],
|
|
38
|
+
returns: FFIType.void,
|
|
39
|
+
},
|
|
40
|
+
zclient_response_status: { args: [FFIType.ptr], returns: FFIType.u16 },
|
|
41
|
+
zclient_response_body: { args: [FFIType.ptr], returns: FFIType.ptr },
|
|
42
|
+
zclient_response_body_len: { args: [FFIType.ptr], returns: FFIType.u64 },
|
|
43
|
+
zclient_response_headers: { args: [FFIType.ptr], returns: FFIType.ptr },
|
|
44
|
+
zclient_response_headers_len: {
|
|
45
|
+
args: [FFIType.ptr],
|
|
46
|
+
returns: FFIType.u64,
|
|
47
|
+
},
|
|
48
|
+
zclient_response_free: { args: [FFIType.ptr], returns: FFIType.void },
|
|
49
|
+
zclient_request: {
|
|
50
|
+
args: [
|
|
51
|
+
FFIType.ptr,
|
|
52
|
+
FFIType.cstring,
|
|
53
|
+
FFIType.cstring,
|
|
54
|
+
FFIType.cstring,
|
|
55
|
+
FFIType.cstring,
|
|
56
|
+
],
|
|
57
|
+
returns: FFIType.ptr,
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
return dlopen(libPath, ffiDef);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
// Use koffi for Node.js
|
|
64
|
+
const koffi = await import("koffi");
|
|
65
|
+
const nativeLib = koffi.load(libPath);
|
|
66
|
+
return {
|
|
67
|
+
symbols: {
|
|
68
|
+
zclient_new: nativeLib.func("zclient_new", "void*", []),
|
|
69
|
+
zclient_free: nativeLib.func("zclient_free", "void", ["void*"]),
|
|
70
|
+
zclient_get: nativeLib.func("zclient_get", "void*", ["void*", "str"]),
|
|
71
|
+
zclient_post: nativeLib.func("zclient_post", "void*", [
|
|
72
|
+
"void*",
|
|
73
|
+
"str",
|
|
74
|
+
"str",
|
|
75
|
+
]),
|
|
76
|
+
zclient_prefetch: nativeLib.func("zclient_prefetch", "void", [
|
|
77
|
+
"void*",
|
|
78
|
+
"str",
|
|
79
|
+
]),
|
|
80
|
+
zclient_response_status: nativeLib.func("zclient_response_status", "uint16", ["void*"]),
|
|
81
|
+
zclient_response_body: nativeLib.func("zclient_response_body", "void*", ["void*"]),
|
|
82
|
+
zclient_response_body_len: nativeLib.func("zclient_response_body_len", "uint64", ["void*"]),
|
|
83
|
+
zclient_response_headers: nativeLib.func("zclient_response_headers", "void*", ["void*"]),
|
|
84
|
+
zclient_response_headers_len: nativeLib.func("zclient_response_headers_len", "uint64", ["void*"]),
|
|
85
|
+
zclient_response_free: nativeLib.func("zclient_response_free", "void", [
|
|
86
|
+
"void*",
|
|
87
|
+
]),
|
|
88
|
+
zclient_request: nativeLib.func("zclient_request", "void*", [
|
|
89
|
+
"void*",
|
|
90
|
+
"str",
|
|
91
|
+
"str",
|
|
92
|
+
"str",
|
|
93
|
+
"str",
|
|
94
|
+
]),
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// Export the loaded library (initialized on first import)
|
|
100
|
+
export const lib = await loadLibrary();
|
|
101
|
+
// Helper to read C string from pointer (runtime-agnostic)
|
|
102
|
+
export function readCString(ptr) {
|
|
103
|
+
if (!ptr)
|
|
104
|
+
return "";
|
|
105
|
+
if (isBun) {
|
|
106
|
+
// For Bun, we need to dynamically get CString
|
|
107
|
+
// Use a simple approach: read bytes until null terminator
|
|
108
|
+
const view = new DataView(new ArrayBuffer(0));
|
|
109
|
+
try {
|
|
110
|
+
// In Bun, pointers can be read as buffers
|
|
111
|
+
// @ts-ignore - Bun specific
|
|
112
|
+
const buf = Buffer.from(ptr);
|
|
113
|
+
const nullIdx = buf.indexOf(0);
|
|
114
|
+
return buf.toString("utf8", 0, nullIdx >= 0 ? nullIdx : undefined);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
// Fallback: try to read as string directly
|
|
118
|
+
return String(ptr);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
// Node.js with koffi
|
|
123
|
+
try {
|
|
124
|
+
const koffi = require("koffi");
|
|
125
|
+
return koffi.decode(ptr, "string");
|
|
126
|
+
}
|
|
127
|
+
catch (e) {
|
|
128
|
+
console.error("Failed to decode C string:", e);
|
|
129
|
+
return "";
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime detection utility for Jiren
|
|
3
|
+
* Detects whether we're running in Bun or Node.js
|
|
4
|
+
*/
|
|
5
|
+
export const isBun = typeof Bun !== "undefined";
|
|
6
|
+
export const isNode = !isBun &&
|
|
7
|
+
typeof process !== "undefined" &&
|
|
8
|
+
process.versions &&
|
|
9
|
+
process.versions.node;
|
|
10
|
+
export function getRuntime() {
|
|
11
|
+
if (isBun)
|
|
12
|
+
return "bun";
|
|
13
|
+
if (isNode)
|
|
14
|
+
return "node";
|
|
15
|
+
return "unknown";
|
|
16
|
+
}
|
package/{index.ts → index.js}
RENAMED
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jiren",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.12",
|
|
4
4
|
"author": "",
|
|
5
|
-
"main": "index.
|
|
6
|
-
"module": "index.
|
|
7
|
-
"types": "
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"module": "index.js",
|
|
7
|
+
"types": "index.d.ts",
|
|
8
8
|
"dependencies": {},
|
|
9
9
|
"optionalDependencies": {
|
|
10
10
|
"koffi": "^2.9.0"
|
|
@@ -18,11 +18,13 @@
|
|
|
18
18
|
},
|
|
19
19
|
"description": "Jiren is a high-performance HTTP/HTTPS client, Faster than any other HTTP/HTTPS client.",
|
|
20
20
|
"files": [
|
|
21
|
-
"index.
|
|
22
|
-
"
|
|
23
|
-
"
|
|
21
|
+
"index.js",
|
|
22
|
+
"index.d.ts",
|
|
23
|
+
"types/*.js",
|
|
24
|
+
"types/*.d.ts",
|
|
24
25
|
"lib",
|
|
25
|
-
"components"
|
|
26
|
+
"components/*.js",
|
|
27
|
+
"components/*.d.ts"
|
|
26
28
|
],
|
|
27
29
|
"keywords": [
|
|
28
30
|
"http",
|
|
@@ -40,7 +42,8 @@
|
|
|
40
42
|
"scripts": {
|
|
41
43
|
"build": "tsc",
|
|
42
44
|
"build:zig": "cd .. && zig build --release=fast",
|
|
43
|
-
"test": "bun run examples/basic.ts"
|
|
45
|
+
"test": "bun run examples/basic.ts",
|
|
46
|
+
"prepublishOnly": "npm run build"
|
|
44
47
|
},
|
|
45
48
|
"engines": {
|
|
46
49
|
"node": ">=18.0.0",
|
package/types/index.js
ADDED
package/components/client.ts
DELETED
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
import { lib, readCString } from "./native";
|
|
2
|
-
import type {
|
|
3
|
-
RequestOptions,
|
|
4
|
-
GetRequestOptions,
|
|
5
|
-
PostRequestOptions,
|
|
6
|
-
HttpResponse,
|
|
7
|
-
} from "../types";
|
|
8
|
-
|
|
9
|
-
// Runtime-agnostic pointer type
|
|
10
|
-
type Pointer = any;
|
|
11
|
-
export class JirenClient {
|
|
12
|
-
private ptr: unknown | null;
|
|
13
|
-
|
|
14
|
-
constructor() {
|
|
15
|
-
this.ptr = lib.symbols.zclient_new();
|
|
16
|
-
if (!this.ptr) throw new Error("Failed to create native client instance");
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Free the native client resources.
|
|
21
|
-
* Must be called when the client is no longer needed.
|
|
22
|
-
*/
|
|
23
|
-
public close(): void {
|
|
24
|
-
if (this.ptr) {
|
|
25
|
-
lib.symbols.zclient_free(this.ptr as Pointer);
|
|
26
|
-
this.ptr = null;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Perform a HTTP request.
|
|
32
|
-
* @param url - The URL to request
|
|
33
|
-
* @param options - Request options (method, headers, body)
|
|
34
|
-
* @returns Response object
|
|
35
|
-
*/
|
|
36
|
-
public request(url: string, options: RequestOptions = {}): HttpResponse {
|
|
37
|
-
if (!this.ptr) throw new Error("Client is closed");
|
|
38
|
-
|
|
39
|
-
const method = options.method || "GET";
|
|
40
|
-
const methodBuffer = Buffer.from(method + "\0");
|
|
41
|
-
const urlBuffer = Buffer.from(url + "\0");
|
|
42
|
-
|
|
43
|
-
let headersBuffer: Buffer | null = null;
|
|
44
|
-
if (options.headers) {
|
|
45
|
-
const headerStr = Object.entries(options.headers)
|
|
46
|
-
.map(([k, v]) => `${k.toLowerCase()}: ${v}`)
|
|
47
|
-
.join("\r\n");
|
|
48
|
-
if (headerStr.length > 0) {
|
|
49
|
-
headersBuffer = Buffer.from(headerStr + "\0");
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
let bodyBuffer: Buffer | null = null;
|
|
54
|
-
if (options.body) {
|
|
55
|
-
const bodyStr =
|
|
56
|
-
typeof options.body === "string"
|
|
57
|
-
? options.body
|
|
58
|
-
: JSON.stringify(options.body);
|
|
59
|
-
bodyBuffer = Buffer.from(bodyStr + "\0");
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const respPtr = lib.symbols.zclient_request(
|
|
63
|
-
this.ptr as Pointer,
|
|
64
|
-
methodBuffer,
|
|
65
|
-
urlBuffer,
|
|
66
|
-
headersBuffer,
|
|
67
|
-
bodyBuffer
|
|
68
|
-
);
|
|
69
|
-
|
|
70
|
-
const response = this.parseResponse(respPtr);
|
|
71
|
-
|
|
72
|
-
// Handle Redirects
|
|
73
|
-
if (
|
|
74
|
-
options.maxRedirects &&
|
|
75
|
-
options.maxRedirects > 0 &&
|
|
76
|
-
response.status >= 300 &&
|
|
77
|
-
response.status < 400 &&
|
|
78
|
-
response.headers &&
|
|
79
|
-
response.headers["location"]
|
|
80
|
-
) {
|
|
81
|
-
const location = response.headers["location"];
|
|
82
|
-
const newUrl = new URL(location, url).toString(); // Resolve relative URLs
|
|
83
|
-
const newOptions = { ...options, maxRedirects: options.maxRedirects - 1 };
|
|
84
|
-
|
|
85
|
-
return this.request(newUrl, newOptions);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return response;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
public get(url: string, options: GetRequestOptions = {}) {
|
|
92
|
-
return this.request(url, { ...options, method: "GET" });
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
public post(
|
|
96
|
-
url: string,
|
|
97
|
-
body: string | object,
|
|
98
|
-
options: PostRequestOptions = {}
|
|
99
|
-
) {
|
|
100
|
-
return this.request(url, { ...options, method: "POST", body });
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
public put(
|
|
104
|
-
url: string,
|
|
105
|
-
body: string | object,
|
|
106
|
-
options: PostRequestOptions = {}
|
|
107
|
-
) {
|
|
108
|
-
return this.request(url, { ...options, method: "PUT", body });
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
public delete(url: string, options: GetRequestOptions = {}) {
|
|
112
|
-
return this.request(url, { ...options, method: "DELETE" });
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
public patch(
|
|
116
|
-
url: string,
|
|
117
|
-
body: string | object,
|
|
118
|
-
options: PostRequestOptions = {}
|
|
119
|
-
) {
|
|
120
|
-
return this.request(url, { ...options, method: "PATCH", body });
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
public head(url: string, options: GetRequestOptions = {}) {
|
|
124
|
-
return this.request(url, { ...options, method: "HEAD" });
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Prefetch URLs to warm up connections (resolve DNS & Handshake).
|
|
129
|
-
* @param urls - List of URLs to prefetch
|
|
130
|
-
*/
|
|
131
|
-
public prefetch(urls: string[]) {
|
|
132
|
-
if (!this.ptr) throw new Error("Client is closed");
|
|
133
|
-
|
|
134
|
-
for (const url of urls) {
|
|
135
|
-
const urlBuffer = Buffer.from(url + "\0");
|
|
136
|
-
lib.symbols.zclient_prefetch(this.ptr as Pointer, urlBuffer);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
private parseResponse(respPtr: Pointer | null) {
|
|
141
|
-
if (!respPtr)
|
|
142
|
-
throw new Error("Native request failed (returned null pointer)");
|
|
143
|
-
|
|
144
|
-
try {
|
|
145
|
-
const status = lib.symbols.zclient_response_status(respPtr);
|
|
146
|
-
const bodyLen = Number(lib.symbols.zclient_response_body_len(respPtr));
|
|
147
|
-
const bodyPtr = lib.symbols.zclient_response_body(respPtr);
|
|
148
|
-
const headersLen = Number(
|
|
149
|
-
lib.symbols.zclient_response_headers_len(respPtr)
|
|
150
|
-
);
|
|
151
|
-
const headersPtr = lib.symbols.zclient_response_headers(respPtr);
|
|
152
|
-
|
|
153
|
-
let bodyString = "";
|
|
154
|
-
if (bodyLen > 0 && bodyPtr) {
|
|
155
|
-
bodyString = readCString(bodyPtr);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const headers: Record<string, string> = {};
|
|
159
|
-
if (headersLen > 0 && headersPtr) {
|
|
160
|
-
const headersStr = readCString(headersPtr);
|
|
161
|
-
const lines = headersStr.split("\r\n");
|
|
162
|
-
for (const line of lines) {
|
|
163
|
-
if (!line) continue;
|
|
164
|
-
const colonIdx = line.indexOf(":");
|
|
165
|
-
if (colonIdx !== -1) {
|
|
166
|
-
const key = line.substring(0, colonIdx).trim().toLowerCase();
|
|
167
|
-
const val = line.substring(colonIdx + 1).trim();
|
|
168
|
-
headers[key] = val;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return {
|
|
174
|
-
status,
|
|
175
|
-
body: bodyString,
|
|
176
|
-
headers,
|
|
177
|
-
get ok() {
|
|
178
|
-
return status >= 200 && status < 300;
|
|
179
|
-
},
|
|
180
|
-
text: async () => bodyString,
|
|
181
|
-
json: <T = any>() => {
|
|
182
|
-
if (!bodyString) return null as T;
|
|
183
|
-
return JSON.parse(bodyString) as T;
|
|
184
|
-
},
|
|
185
|
-
};
|
|
186
|
-
} finally {
|
|
187
|
-
lib.symbols.zclient_response_free(respPtr);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
package/components/native.ts
DELETED
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
import { isBun } from "./runtime";
|
|
2
|
-
import { join, dirname } from "path";
|
|
3
|
-
import { fileURLToPath } from "url";
|
|
4
|
-
|
|
5
|
-
// Helper to get library path
|
|
6
|
-
function getLibPath(): string {
|
|
7
|
-
const platform = process.platform;
|
|
8
|
-
const ext =
|
|
9
|
-
platform === "darwin" ? "dylib" : platform === "win32" ? "dll" : "so";
|
|
10
|
-
|
|
11
|
-
if (isBun) {
|
|
12
|
-
// @ts-ignore - Bun-specific
|
|
13
|
-
return join(import.meta.dir, `../lib/libhttpclient.${ext}`);
|
|
14
|
-
} else {
|
|
15
|
-
// Node.js
|
|
16
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
-
const __dirname = dirname(__filename);
|
|
18
|
-
return join(__dirname, `../lib/libhttpclient.${ext}`);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// Type definitions for our FFI symbols
|
|
23
|
-
export type NativeLib = {
|
|
24
|
-
symbols: {
|
|
25
|
-
zclient_new: () => any;
|
|
26
|
-
zclient_free: (ptr: any) => void;
|
|
27
|
-
zclient_get: (client: any, url: any) => any;
|
|
28
|
-
zclient_post: (client: any, url: any, body: any) => any;
|
|
29
|
-
zclient_prefetch: (client: any, url: any) => void;
|
|
30
|
-
zclient_response_status: (resp: any) => number;
|
|
31
|
-
zclient_response_body: (resp: any) => any;
|
|
32
|
-
zclient_response_body_len: (resp: any) => bigint | number;
|
|
33
|
-
zclient_response_headers: (resp: any) => any;
|
|
34
|
-
zclient_response_headers_len: (resp: any) => bigint | number;
|
|
35
|
-
zclient_response_free: (resp: any) => void;
|
|
36
|
-
zclient_request: (
|
|
37
|
-
client: any,
|
|
38
|
-
method: any,
|
|
39
|
-
url: any,
|
|
40
|
-
headers: any,
|
|
41
|
-
body: any
|
|
42
|
-
) => any;
|
|
43
|
-
};
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
// Load the appropriate FFI library
|
|
47
|
-
async function loadLibrary(): Promise<NativeLib> {
|
|
48
|
-
const libPath = getLibPath();
|
|
49
|
-
|
|
50
|
-
if (isBun) {
|
|
51
|
-
// Use Bun FFI
|
|
52
|
-
const { dlopen, FFIType } = await import("bun:ffi");
|
|
53
|
-
|
|
54
|
-
const ffiDef = {
|
|
55
|
-
zclient_new: { args: [], returns: FFIType.ptr },
|
|
56
|
-
zclient_free: { args: [FFIType.ptr], returns: FFIType.void },
|
|
57
|
-
zclient_get: {
|
|
58
|
-
args: [FFIType.ptr, FFIType.cstring],
|
|
59
|
-
returns: FFIType.ptr,
|
|
60
|
-
},
|
|
61
|
-
zclient_post: {
|
|
62
|
-
args: [FFIType.ptr, FFIType.cstring, FFIType.cstring],
|
|
63
|
-
returns: FFIType.ptr,
|
|
64
|
-
},
|
|
65
|
-
zclient_prefetch: {
|
|
66
|
-
args: [FFIType.ptr, FFIType.cstring],
|
|
67
|
-
returns: FFIType.void,
|
|
68
|
-
},
|
|
69
|
-
zclient_response_status: { args: [FFIType.ptr], returns: FFIType.u16 },
|
|
70
|
-
zclient_response_body: { args: [FFIType.ptr], returns: FFIType.ptr },
|
|
71
|
-
zclient_response_body_len: { args: [FFIType.ptr], returns: FFIType.u64 },
|
|
72
|
-
zclient_response_headers: { args: [FFIType.ptr], returns: FFIType.ptr },
|
|
73
|
-
zclient_response_headers_len: {
|
|
74
|
-
args: [FFIType.ptr],
|
|
75
|
-
returns: FFIType.u64,
|
|
76
|
-
},
|
|
77
|
-
zclient_response_free: { args: [FFIType.ptr], returns: FFIType.void },
|
|
78
|
-
zclient_request: {
|
|
79
|
-
args: [
|
|
80
|
-
FFIType.ptr,
|
|
81
|
-
FFIType.cstring,
|
|
82
|
-
FFIType.cstring,
|
|
83
|
-
FFIType.cstring,
|
|
84
|
-
FFIType.cstring,
|
|
85
|
-
],
|
|
86
|
-
returns: FFIType.ptr,
|
|
87
|
-
},
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
return dlopen(libPath, ffiDef) as NativeLib;
|
|
91
|
-
} else {
|
|
92
|
-
// Use koffi for Node.js
|
|
93
|
-
const koffi = await import("koffi");
|
|
94
|
-
|
|
95
|
-
const nativeLib = koffi.load(libPath);
|
|
96
|
-
|
|
97
|
-
return {
|
|
98
|
-
symbols: {
|
|
99
|
-
zclient_new: nativeLib.func("zclient_new", "void*", []),
|
|
100
|
-
zclient_free: nativeLib.func("zclient_free", "void", ["void*"]),
|
|
101
|
-
zclient_get: nativeLib.func("zclient_get", "void*", ["void*", "str"]),
|
|
102
|
-
zclient_post: nativeLib.func("zclient_post", "void*", [
|
|
103
|
-
"void*",
|
|
104
|
-
"str",
|
|
105
|
-
"str",
|
|
106
|
-
]),
|
|
107
|
-
zclient_prefetch: nativeLib.func("zclient_prefetch", "void", [
|
|
108
|
-
"void*",
|
|
109
|
-
"str",
|
|
110
|
-
]),
|
|
111
|
-
zclient_response_status: nativeLib.func(
|
|
112
|
-
"zclient_response_status",
|
|
113
|
-
"uint16",
|
|
114
|
-
["void*"]
|
|
115
|
-
),
|
|
116
|
-
zclient_response_body: nativeLib.func(
|
|
117
|
-
"zclient_response_body",
|
|
118
|
-
"void*",
|
|
119
|
-
["void*"]
|
|
120
|
-
),
|
|
121
|
-
zclient_response_body_len: nativeLib.func(
|
|
122
|
-
"zclient_response_body_len",
|
|
123
|
-
"uint64",
|
|
124
|
-
["void*"]
|
|
125
|
-
),
|
|
126
|
-
zclient_response_headers: nativeLib.func(
|
|
127
|
-
"zclient_response_headers",
|
|
128
|
-
"void*",
|
|
129
|
-
["void*"]
|
|
130
|
-
),
|
|
131
|
-
zclient_response_headers_len: nativeLib.func(
|
|
132
|
-
"zclient_response_headers_len",
|
|
133
|
-
"uint64",
|
|
134
|
-
["void*"]
|
|
135
|
-
),
|
|
136
|
-
zclient_response_free: nativeLib.func("zclient_response_free", "void", [
|
|
137
|
-
"void*",
|
|
138
|
-
]),
|
|
139
|
-
zclient_request: nativeLib.func("zclient_request", "void*", [
|
|
140
|
-
"void*",
|
|
141
|
-
"str",
|
|
142
|
-
"str",
|
|
143
|
-
"str",
|
|
144
|
-
"str",
|
|
145
|
-
]),
|
|
146
|
-
},
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Export the loaded library (initialized on first import)
|
|
152
|
-
export const lib: NativeLib = await loadLibrary();
|
|
153
|
-
|
|
154
|
-
// Helper to read C string from pointer (runtime-agnostic)
|
|
155
|
-
export function readCString(ptr: any): string {
|
|
156
|
-
if (!ptr) return "";
|
|
157
|
-
|
|
158
|
-
if (isBun) {
|
|
159
|
-
// For Bun, we need to dynamically get CString
|
|
160
|
-
// Use a simple approach: read bytes until null terminator
|
|
161
|
-
const view = new DataView(new ArrayBuffer(0));
|
|
162
|
-
try {
|
|
163
|
-
// In Bun, pointers can be read as buffers
|
|
164
|
-
// @ts-ignore - Bun specific
|
|
165
|
-
const buf = Buffer.from(ptr as any);
|
|
166
|
-
const nullIdx = buf.indexOf(0);
|
|
167
|
-
return buf.toString("utf8", 0, nullIdx >= 0 ? nullIdx : undefined);
|
|
168
|
-
} catch {
|
|
169
|
-
// Fallback: try to read as string directly
|
|
170
|
-
return String(ptr);
|
|
171
|
-
}
|
|
172
|
-
} else {
|
|
173
|
-
// Node.js with koffi
|
|
174
|
-
try {
|
|
175
|
-
const koffi = require("koffi");
|
|
176
|
-
return koffi.decode(ptr, "string");
|
|
177
|
-
} catch (e) {
|
|
178
|
-
console.error("Failed to decode C string:", e);
|
|
179
|
-
return "";
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
package/components/runtime.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Runtime detection utility for Jiren
|
|
3
|
-
* Detects whether we're running in Bun or Node.js
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export const isBun = typeof Bun !== "undefined";
|
|
7
|
-
export const isNode =
|
|
8
|
-
!isBun &&
|
|
9
|
-
typeof process !== "undefined" &&
|
|
10
|
-
process.versions &&
|
|
11
|
-
process.versions.node;
|
|
12
|
-
|
|
13
|
-
export function getRuntime(): "bun" | "node" | "unknown" {
|
|
14
|
-
if (isBun) return "bun";
|
|
15
|
-
if (isNode) return "node";
|
|
16
|
-
return "unknown";
|
|
17
|
-
}
|
package/components/types.ts
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
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/test.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/types/index.ts
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
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
|
-
/** True if status is 2xx */
|
|
56
|
-
ok: boolean;
|
|
57
|
-
/** Helper to get body as string (Promise for Fetch compatibility) */
|
|
58
|
-
text: () => Promise<string>;
|
|
59
|
-
/** Helper to parse JSON body */
|
|
60
|
-
json: <T = any>() => T;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/** Configuration for JirenHttpClient */
|
|
64
|
-
export interface JirenHttpConfig {
|
|
65
|
-
/** Default number of threads for batch operations */
|
|
66
|
-
defaultThreads?: number;
|
|
67
|
-
/** Base URL prefix for all requests */
|
|
68
|
-
baseUrl?: string;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/** Parsed URL components */
|
|
72
|
-
export interface ParsedUrl {
|
|
73
|
-
protocol: "http" | "https";
|
|
74
|
-
host: string;
|
|
75
|
-
port: number;
|
|
76
|
-
path: string;
|
|
77
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|