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.
@@ -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
@@ -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);
@@ -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
+ }
Binary file
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "jiren",
3
- "version": "1.3.1",
3
+ "version": "1.4.5",
4
4
  "author": "",
5
- "main": "dist/index-node.js",
6
- "module": "dist/index.js",
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
- "types": "./index.ts",
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
  },