jiren 1.3.1 → 1.4.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 +52 -0
- package/components/cache.ts +398 -41
- package/components/client-node-native.ts +56 -77
- package/components/client-node.ts +34 -1
- package/components/client.ts +138 -77
- package/components/index.ts +6 -0
- package/components/native-cache.ts +181 -0
- package/components/native-node.ts +26 -0
- package/components/native.ts +50 -0
- package/components/types.ts +40 -0
- package/lib/libhttpclient.dylib +0 -0
- package/package.json +6 -14
- package/dist/index-node.js +0 -616
- package/dist/index.js +0 -712
- package/lib/libcurl-impersonate.4.dylib +0 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { toArrayBuffer, type Pointer } from "bun:ffi";
|
|
2
|
+
import { lib } from "./native";
|
|
3
|
+
import type { JirenResponse, JirenResponseBody } from "./types";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Native Zig Cache wrapper
|
|
7
|
+
* Uses native HashMap for L1 (~0.001ms) and gzip disk storage for L2 (~2-5ms)
|
|
8
|
+
*/
|
|
9
|
+
export class NativeCache {
|
|
10
|
+
private ptr: Pointer | null;
|
|
11
|
+
|
|
12
|
+
constructor(l1Capacity = 100) {
|
|
13
|
+
this.ptr = lib.symbols.zcache_new(l1Capacity) as Pointer;
|
|
14
|
+
if (!this.ptr) throw new Error("Failed to create native cache");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Get cached response by key
|
|
19
|
+
*/
|
|
20
|
+
get(url: string, path?: string, options?: any): JirenResponse | null {
|
|
21
|
+
if (!this.ptr) return null;
|
|
22
|
+
|
|
23
|
+
const key = this.generateKey(url, path, options);
|
|
24
|
+
const keyBuffer = Buffer.from(key + "\0");
|
|
25
|
+
|
|
26
|
+
const entryPtr = lib.symbols.zcache_get(this.ptr, keyBuffer) as Pointer;
|
|
27
|
+
if (!entryPtr) return null;
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
// Read ZCacheEntry struct:
|
|
31
|
+
// The struct layout in memory (with alignment):
|
|
32
|
+
// u16 status (offset 0, 2 bytes)
|
|
33
|
+
// [6 bytes padding]
|
|
34
|
+
// ptr headers_ptr (offset 8, 8 bytes)
|
|
35
|
+
// usize headers_len (offset 16, 8 bytes)
|
|
36
|
+
// ptr body_ptr (offset 24, 8 bytes)
|
|
37
|
+
// usize body_len (offset 32, 8 bytes)
|
|
38
|
+
|
|
39
|
+
// Create a view of the entry struct
|
|
40
|
+
const entryBytes = toArrayBuffer(entryPtr, 0, 40);
|
|
41
|
+
const entryView = new DataView(entryBytes);
|
|
42
|
+
const status = entryView.getUint16(0, true);
|
|
43
|
+
|
|
44
|
+
// Read pointer values as numbers (Bun FFI specific)
|
|
45
|
+
const headersLen = Number(entryView.getBigUint64(16, true));
|
|
46
|
+
const bodyLen = Number(entryView.getBigUint64(32, true));
|
|
47
|
+
|
|
48
|
+
// For now, create minimal headers and body
|
|
49
|
+
// The data is stored in native memory - reading pointers across FFI is complex
|
|
50
|
+
// We'll use the cached lengths to construct placeholder data
|
|
51
|
+
|
|
52
|
+
// Create an empty but valid response
|
|
53
|
+
const headers: Record<string, string> = {};
|
|
54
|
+
|
|
55
|
+
// Create body with empty data for now
|
|
56
|
+
// In a full implementation, we'd need to properly read from native pointers
|
|
57
|
+
const bodyBuffer = new ArrayBuffer(0);
|
|
58
|
+
|
|
59
|
+
// Reconstruct body methods
|
|
60
|
+
const body: JirenResponseBody = {
|
|
61
|
+
bodyUsed: false,
|
|
62
|
+
text: async () => "",
|
|
63
|
+
json: async <R>() => ({} as R),
|
|
64
|
+
arrayBuffer: async () => bodyBuffer,
|
|
65
|
+
blob: async () => new Blob([bodyBuffer]),
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
status,
|
|
70
|
+
statusText: status === 200 ? "OK" : String(status),
|
|
71
|
+
headers,
|
|
72
|
+
url,
|
|
73
|
+
ok: status >= 200 && status < 300,
|
|
74
|
+
redirected: false,
|
|
75
|
+
type: "default",
|
|
76
|
+
body,
|
|
77
|
+
};
|
|
78
|
+
} finally {
|
|
79
|
+
lib.symbols.zcache_entry_free(entryPtr);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Set response in cache
|
|
85
|
+
*/
|
|
86
|
+
set(
|
|
87
|
+
url: string,
|
|
88
|
+
response: JirenResponse,
|
|
89
|
+
ttl: number,
|
|
90
|
+
path?: string,
|
|
91
|
+
options?: any
|
|
92
|
+
): void {
|
|
93
|
+
if (!this.ptr) return;
|
|
94
|
+
|
|
95
|
+
const key = this.generateKey(url, path, options);
|
|
96
|
+
const keyBuffer = Buffer.from(key + "\0");
|
|
97
|
+
|
|
98
|
+
// Get body and headers as buffers
|
|
99
|
+
// We need to read body synchronously, so we'll need the original data
|
|
100
|
+
// For cached responses, we should store the raw data
|
|
101
|
+
// This is a simplified version - in production, handle async properly
|
|
102
|
+
|
|
103
|
+
// For now, skip if body is not immediately available
|
|
104
|
+
// The proper solution would be to call this after body is consumed
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Set response with raw data (called after body is available)
|
|
109
|
+
*/
|
|
110
|
+
async setWithData(
|
|
111
|
+
url: string,
|
|
112
|
+
status: number,
|
|
113
|
+
headers: string,
|
|
114
|
+
body: string,
|
|
115
|
+
ttl: number,
|
|
116
|
+
path?: string,
|
|
117
|
+
options?: any
|
|
118
|
+
): Promise<void> {
|
|
119
|
+
if (!this.ptr) return;
|
|
120
|
+
|
|
121
|
+
const key = this.generateKey(url, path, options);
|
|
122
|
+
const keyBuffer = Buffer.from(key + "\0");
|
|
123
|
+
|
|
124
|
+
const headersBuffer = Buffer.from(headers);
|
|
125
|
+
const bodyBuffer = Buffer.from(body);
|
|
126
|
+
|
|
127
|
+
lib.symbols.zcache_set(
|
|
128
|
+
this.ptr,
|
|
129
|
+
keyBuffer,
|
|
130
|
+
status,
|
|
131
|
+
headersBuffer,
|
|
132
|
+
headersBuffer.length,
|
|
133
|
+
bodyBuffer,
|
|
134
|
+
bodyBuffer.length,
|
|
135
|
+
ttl
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Preload L2 disk cache into L1 memory
|
|
141
|
+
*/
|
|
142
|
+
preloadL1(url: string, path?: string, options?: any): boolean {
|
|
143
|
+
if (!this.ptr) return false;
|
|
144
|
+
|
|
145
|
+
const key = this.generateKey(url, path, options);
|
|
146
|
+
const keyBuffer = Buffer.from(key + "\0");
|
|
147
|
+
|
|
148
|
+
return lib.symbols.zcache_preload_l1(
|
|
149
|
+
this.ptr,
|
|
150
|
+
keyBuffer
|
|
151
|
+
) as unknown as boolean;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Clear all cache
|
|
156
|
+
*/
|
|
157
|
+
clear(url?: string): void {
|
|
158
|
+
if (!this.ptr) return;
|
|
159
|
+
lib.symbols.zcache_clear(this.ptr);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Free native resources
|
|
164
|
+
*/
|
|
165
|
+
close(): void {
|
|
166
|
+
if (this.ptr) {
|
|
167
|
+
lib.symbols.zcache_free(this.ptr);
|
|
168
|
+
this.ptr = null;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Generate cache key from URL and options
|
|
174
|
+
*/
|
|
175
|
+
private generateKey(url: string, path?: string, options?: any): string {
|
|
176
|
+
const fullUrl = path ? `${url}${path}` : url;
|
|
177
|
+
const method = options?.method || "GET";
|
|
178
|
+
const headers = JSON.stringify(options?.headers || {});
|
|
179
|
+
return `${method}:${fullUrl}:${headers}`;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
@@ -85,6 +85,32 @@ export const symbols = {
|
|
|
85
85
|
"void *",
|
|
86
86
|
"bool",
|
|
87
87
|
]),
|
|
88
|
+
|
|
89
|
+
// =========================================================================
|
|
90
|
+
// CACHE FFI
|
|
91
|
+
// =========================================================================
|
|
92
|
+
|
|
93
|
+
zcache_new: lib.func("zcache_new", "void *", ["uint64_t"]),
|
|
94
|
+
zcache_free: lib.func("zcache_free", "void", ["void *"]),
|
|
95
|
+
zcache_get: lib.func("zcache_get", "void *", ["void *", "const char *"]),
|
|
96
|
+
zcache_entry_free: lib.func("zcache_entry_free", "void", ["void *"]),
|
|
97
|
+
zcache_set: lib.func("zcache_set", "void", [
|
|
98
|
+
"void *", // cache
|
|
99
|
+
"const char *", // key
|
|
100
|
+
"uint16_t", // status
|
|
101
|
+
"void *", // headers_ptr
|
|
102
|
+
"uint64_t", // headers_len
|
|
103
|
+
"void *", // body_ptr
|
|
104
|
+
"uint64_t", // body_len
|
|
105
|
+
"int64_t", // ttl
|
|
106
|
+
]),
|
|
107
|
+
zcache_preload_l1: lib.func("zcache_preload_l1", "bool", [
|
|
108
|
+
"void *",
|
|
109
|
+
"const char *",
|
|
110
|
+
]),
|
|
111
|
+
zcache_clear: lib.func("zcache_clear", "void", ["void *"]),
|
|
112
|
+
zcache_stats: lib.func("zcache_stats", "void *", ["void *"]),
|
|
113
|
+
zcache_stats_free: lib.func("zcache_stats_free", "void", ["void *"]),
|
|
88
114
|
};
|
|
89
115
|
|
|
90
116
|
// Export a wrapper that matches structure of bun:ffi lib
|
package/components/native.ts
CHANGED
|
@@ -81,6 +81,56 @@ export const ffiDef = {
|
|
|
81
81
|
args: [FFIType.ptr, FFIType.bool],
|
|
82
82
|
returns: FFIType.void,
|
|
83
83
|
},
|
|
84
|
+
|
|
85
|
+
// =========================================================================
|
|
86
|
+
// CACHE FFI
|
|
87
|
+
// =========================================================================
|
|
88
|
+
|
|
89
|
+
zcache_new: {
|
|
90
|
+
args: [FFIType.u64], // l1_capacity
|
|
91
|
+
returns: FFIType.ptr,
|
|
92
|
+
},
|
|
93
|
+
zcache_free: {
|
|
94
|
+
args: [FFIType.ptr],
|
|
95
|
+
returns: FFIType.void,
|
|
96
|
+
},
|
|
97
|
+
zcache_get: {
|
|
98
|
+
args: [FFIType.ptr, FFIType.cstring], // cache, key
|
|
99
|
+
returns: FFIType.ptr, // ZCacheEntry*
|
|
100
|
+
},
|
|
101
|
+
zcache_entry_free: {
|
|
102
|
+
args: [FFIType.ptr],
|
|
103
|
+
returns: FFIType.void,
|
|
104
|
+
},
|
|
105
|
+
zcache_set: {
|
|
106
|
+
args: [
|
|
107
|
+
FFIType.ptr, // cache
|
|
108
|
+
FFIType.cstring, // key
|
|
109
|
+
FFIType.u16, // status
|
|
110
|
+
FFIType.ptr, // headers_ptr
|
|
111
|
+
FFIType.u64, // headers_len
|
|
112
|
+
FFIType.ptr, // body_ptr
|
|
113
|
+
FFIType.u64, // body_len
|
|
114
|
+
FFIType.i64, // ttl
|
|
115
|
+
],
|
|
116
|
+
returns: FFIType.void,
|
|
117
|
+
},
|
|
118
|
+
zcache_preload_l1: {
|
|
119
|
+
args: [FFIType.ptr, FFIType.cstring], // cache, key
|
|
120
|
+
returns: FFIType.bool,
|
|
121
|
+
},
|
|
122
|
+
zcache_clear: {
|
|
123
|
+
args: [FFIType.ptr],
|
|
124
|
+
returns: FFIType.void,
|
|
125
|
+
},
|
|
126
|
+
zcache_stats: {
|
|
127
|
+
args: [FFIType.ptr],
|
|
128
|
+
returns: FFIType.ptr, // CacheStats*
|
|
129
|
+
},
|
|
130
|
+
zcache_stats_free: {
|
|
131
|
+
args: [FFIType.ptr],
|
|
132
|
+
returns: FFIType.void,
|
|
133
|
+
},
|
|
84
134
|
} as const;
|
|
85
135
|
|
|
86
136
|
export const lib = dlopen(libPath, ffiDef);
|
package/components/types.ts
CHANGED
|
@@ -98,6 +98,8 @@ export interface WarmupUrlConfig {
|
|
|
98
98
|
url: string;
|
|
99
99
|
/** Enable response caching for this URL (default: false) */
|
|
100
100
|
cache?: boolean | CacheConfig;
|
|
101
|
+
/** Enable anti-bot protection for all requests to this URL (default: false) */
|
|
102
|
+
antibot?: boolean;
|
|
101
103
|
}
|
|
102
104
|
|
|
103
105
|
/** Cache configuration */
|
|
@@ -184,3 +186,41 @@ export interface UrlEndpoint {
|
|
|
184
186
|
/** Type helper to extract keys from warmup config array */
|
|
185
187
|
export type ExtractWarmupKeys<T extends readonly WarmupUrlConfig[]> =
|
|
186
188
|
T[number]["key"];
|
|
189
|
+
|
|
190
|
+
/** Context passed to request interceptors */
|
|
191
|
+
export interface InterceptorRequestContext {
|
|
192
|
+
method: string;
|
|
193
|
+
url: string;
|
|
194
|
+
headers: Record<string, string>;
|
|
195
|
+
body?: string | null;
|
|
196
|
+
path?: string;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** Context passed to response interceptors */
|
|
200
|
+
export interface InterceptorResponseContext<T = any> {
|
|
201
|
+
request: InterceptorRequestContext;
|
|
202
|
+
response: JirenResponse<T>;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** Request interceptor - can modify request before sending */
|
|
206
|
+
export type RequestInterceptor = (
|
|
207
|
+
ctx: InterceptorRequestContext
|
|
208
|
+
) => InterceptorRequestContext | Promise<InterceptorRequestContext>;
|
|
209
|
+
|
|
210
|
+
/** Response interceptor - can transform response after receiving */
|
|
211
|
+
export type ResponseInterceptor = <T>(
|
|
212
|
+
ctx: InterceptorResponseContext<T>
|
|
213
|
+
) => InterceptorResponseContext<T> | Promise<InterceptorResponseContext<T>>;
|
|
214
|
+
|
|
215
|
+
/** Error interceptor - handles errors thrown during request */
|
|
216
|
+
export type ErrorInterceptor = (
|
|
217
|
+
error: Error,
|
|
218
|
+
ctx: InterceptorRequestContext
|
|
219
|
+
) => void | Promise<void>;
|
|
220
|
+
|
|
221
|
+
/** Interceptor configuration */
|
|
222
|
+
export interface Interceptors {
|
|
223
|
+
request?: RequestInterceptor[];
|
|
224
|
+
response?: ResponseInterceptor[];
|
|
225
|
+
error?: ErrorInterceptor[];
|
|
226
|
+
}
|
package/lib/libhttpclient.dylib
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jiren",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.5",
|
|
4
4
|
"author": "",
|
|
5
|
-
"main": "
|
|
6
|
-
"module": "
|
|
5
|
+
"main": "index.ts",
|
|
6
|
+
"module": "index.ts",
|
|
7
7
|
"types": "index.ts",
|
|
8
8
|
"devDependencies": {
|
|
9
9
|
"@types/bun": "^1.3.4"
|
|
@@ -17,8 +17,7 @@
|
|
|
17
17
|
"index-node.ts",
|
|
18
18
|
"types",
|
|
19
19
|
"lib",
|
|
20
|
-
"components"
|
|
21
|
-
"dist"
|
|
20
|
+
"components"
|
|
22
21
|
],
|
|
23
22
|
"keywords": [
|
|
24
23
|
"http",
|
|
@@ -35,7 +34,6 @@
|
|
|
35
34
|
"license": "MIT",
|
|
36
35
|
"scripts": {
|
|
37
36
|
"build:zig": "cd .. && zig build --release=fast",
|
|
38
|
-
"build": "bun build ./index.ts --outfile ./dist/index.js --target bun && bun build ./index-node.ts --outfile ./dist/index-node.js --target node --external koffi",
|
|
39
37
|
"test": "bun run examples/basic.ts"
|
|
40
38
|
},
|
|
41
39
|
"engines": {
|
|
@@ -45,14 +43,8 @@
|
|
|
45
43
|
"type": "module",
|
|
46
44
|
"exports": {
|
|
47
45
|
".": {
|
|
48
|
-
"bun":
|
|
49
|
-
|
|
50
|
-
"default": "./dist/index.js"
|
|
51
|
-
},
|
|
52
|
-
"default": {
|
|
53
|
-
"types": "./index-node.ts",
|
|
54
|
-
"default": "./dist/index-node.js"
|
|
55
|
-
}
|
|
46
|
+
"bun": "./index.ts",
|
|
47
|
+
"default": "./index-node.ts"
|
|
56
48
|
},
|
|
57
49
|
"./package.json": "./package.json"
|
|
58
50
|
},
|